From dc2fa2b40ba97f1914e652b64a6970e942de39bf Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 12 May 2026 22:03:09 -0400 Subject: [PATCH] fix: restore inherited Langfuse agent state --- api/server/controllers/agents/v1.spec.js | 30 +++++++++++++ client/src/common/agents-types.ts | 6 ++- .../Agents/Advanced/AgentLangfuse.tsx | 28 +++++++++--- .../Advanced/__tests__/AgentLangfuse.spec.tsx | 45 +++++++++++++++++++ .../SidePanel/Agents/AgentPanel.tsx | 4 +- .../__tests__/AgentPanel.helpers.spec.ts | 25 +++++++++++ client/src/locales/en/translation.json | 1 + packages/api/src/agents/langfuse.spec.ts | 31 +++++++++++++ packages/api/src/agents/langfuse.ts | 3 +- packages/api/src/agents/validation.ts | 6 ++- 10 files changed, 170 insertions(+), 9 deletions(-) create mode 100644 client/src/components/SidePanel/Agents/Advanced/__tests__/AgentLangfuse.spec.tsx diff --git a/api/server/controllers/agents/v1.spec.js b/api/server/controllers/agents/v1.spec.js index a325dbcbb5..fe87b5d1a2 100644 --- a/api/server/controllers/agents/v1.spec.js +++ b/api/server/controllers/agents/v1.spec.js @@ -988,6 +988,36 @@ describe('Agent Controllers - Mass Assignment Protection', () => { expect(latestVersion.langfuse).toBeUndefined(); }); + test('should remove Langfuse config when update clears only enabled inheritance override', async () => { + await Agent.updateOne( + { id: existingAgentId }, + { + langfuse: { + enabled: false, + }, + }, + ); + + mockReq.params.id = existingAgentId; + mockReq.body = { + langfuse: { + enabled: null, + publicKey: '', + secretKey: '', + baseUrl: '', + }, + }; + + await updateAgentHandler(mockReq, mockRes); + + expect(mockRes.json).toHaveBeenCalled(); + const updatedAgent = mockRes.json.mock.calls[0][0]; + expect(updatedAgent.langfuse).toBeUndefined(); + + const agentInDb = await Agent.findOne({ id: existingAgentId }).lean(); + expect(agentInDb.langfuse).toBeUndefined(); + }); + test('uploadAgentAvatarHandler should redact Langfuse secret in response', async () => { await Agent.updateOne( { id: existingAgentId }, diff --git a/client/src/common/agents-types.ts b/client/src/common/agents-types.ts index 25a259b624..1a4a972a49 100644 --- a/client/src/common/agents-types.ts +++ b/client/src/common/agents-types.ts @@ -29,6 +29,10 @@ export type TAgentCapabilities = { [AgentCapabilities.hide_sequential_outputs]?: boolean; }; +type AgentLangfuseFormConfig = Omit & { + enabled?: boolean | null; +}; + export type AgentForm = { agent?: TAgentOption; id: string; @@ -47,7 +51,7 @@ export type AgentForm = { agent_ids?: string[]; edges?: GraphEdge[]; subagents?: AgentSubagentsConfig; - langfuse?: LangfuseConfig; + langfuse?: AgentLangfuseFormConfig; [AgentCapabilities.artifacts]?: ArtifactModes | string; recursion_limit?: number; support_contact?: SupportContact; diff --git a/client/src/components/SidePanel/Agents/Advanced/AgentLangfuse.tsx b/client/src/components/SidePanel/Agents/Advanced/AgentLangfuse.tsx index c30fc35e32..eaef7ebe29 100644 --- a/client/src/components/SidePanel/Agents/Advanced/AgentLangfuse.tsx +++ b/client/src/components/SidePanel/Agents/Advanced/AgentLangfuse.tsx @@ -1,5 +1,5 @@ import { useCallback, useMemo, useState } from 'react'; -import { Activity, Eye, EyeOff, X } from 'lucide-react'; +import { Activity, Eye, EyeOff, RotateCcw, X } from 'lucide-react'; import { Input, Label, Switch } from '@librechat/client'; import { LANGFUSE_SECRET_CLEAR_VALUE } from 'librechat-data-provider'; import type { ControllerRenderProps } from 'react-hook-form'; @@ -11,7 +11,7 @@ interface AgentLangfuseProps { } const fieldDefaults = { - enabled: undefined as boolean | undefined, + enabled: undefined as boolean | null | undefined, publicKey: '', secretKey: '', baseUrl: '', @@ -22,15 +22,19 @@ export default function AgentLangfuse({ field }: AgentLangfuseProps) { const [showSecret, setShowSecret] = useState(false); const value = useMemo(() => ({ ...fieldDefaults, ...(field.value ?? {}) }), [field.value]); const enabled = value.enabled === true; + const hasEnabledOverride = typeof value.enabled === 'boolean'; let statusKey = 'com_ui_agent_langfuse_inherited'; - if (typeof value.enabled === 'boolean') { + if (hasEnabledOverride) { statusKey = enabled ? 'com_ui_agent_langfuse_enabled' : 'com_ui_agent_langfuse_disabled'; } const secretMarkedForClear = value.secretKey === LANGFUSE_SECRET_CLEAR_VALUE; const secretInputValue = secretMarkedForClear ? '' : value.secretKey; + const hasCredentialValue = + value.publicKey !== '' || secretInputValue !== '' || value.baseUrl !== ''; + const showConfigFields = enabled || hasCredentialValue || secretMarkedForClear; const updateField = useCallback( - (key: keyof typeof fieldDefaults, next: string | boolean | undefined) => { + (key: keyof typeof fieldDefaults, next: string | boolean | null | undefined) => { field.onChange({ ...value, [key]: next, @@ -43,6 +47,10 @@ export default function AgentLangfuse({ field }: AgentLangfuseProps) { updateField('secretKey', secretMarkedForClear ? '' : LANGFUSE_SECRET_CLEAR_VALUE); }, [secretMarkedForClear, updateField]); + const inheritEnabled = useCallback(() => { + updateField('enabled', null); + }, [updateField]); + const enableId = 'agent-langfuse-enable-toggle'; return ( @@ -65,6 +73,16 @@ export default function AgentLangfuse({ field }: AgentLangfuseProps) { {localize(statusKey)} + {hasEnabledOverride && ( + + )} - {enabled && ( + {showConfigFields && (