From a168eaa98b50e508139fe4b7cd62b1c09545cfa0 Mon Sep 17 00:00:00 2001 From: Joshua Valluru <326636767+joshua-berri@users.noreply.github.com> Date: Wed, 30 Sep 2026 14:04:26 -0700 Subject: [PATCH] fix(agents): retain intentional card edits in the dashboard --- .../agents/_components/agent_identity.ts | 3 ++- .../agent_info.integration.test.tsx | 18 ++++++++++++++++-- .../agents/_components/agent_info.tsx | 7 ++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_identity.ts b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_identity.ts index ae4784b0c37..23045adcf20 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_identity.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_identity.ts @@ -92,9 +92,10 @@ export const withAgentIdentity = ( payload: AgentRequestPayload, values: AgentFormValues, existing?: Partial, + cardEdited = false, ): AgentRequestPayload => { const { agent_card_params, ...settings } = payload; - const hasCard = !existing || Object.keys(existing.agent_card_params ?? {}).length > 0; + const hasCard = !existing || cardEdited || Object.keys(existing.agent_card_params ?? {}).length > 0; const identityFields = buildIdentityParams(values, existing?.identity); const managed = values.identity_provider === "microsoft_entra" || Boolean(readAgentIdentity(existing?.identity)); return { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_info.integration.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_info.integration.test.tsx index 60d180cc5db..e08cab776c4 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_info.integration.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_info.integration.test.tsx @@ -156,7 +156,11 @@ describe("AgentInfoView update payload", () => { .mockResolvedValue({} as never); }); - it.each(["complete", "empty"])("preserves the Entra binding while renaming an agent with a %s card", async (card) => { + it.each([ + { card: "complete", editCard: false }, + { card: "empty", editCard: false }, + { card: "empty", editCard: true }, + ])("preserves identity and runtime intent with a $card card (card edits: $editCard)", async ({ card, editCard }) => { const user = setup(); const identity = { provider: "microsoft_entra", @@ -188,10 +192,20 @@ describe("AgentInfoView update payload", () => { expect(screen.getByRole("combobox", { name: "Execution Mode" })).toHaveTextContent("Autonomous"); expect(screen.getByRole("combobox", { name: /^Execution$/ })).toHaveTextContent("Enabled"); fireEvent.change(screen.getByLabelText("Agent Name"), { target: { value: "Renamed agent" } }); + if (editCard) { + fireEvent.change(screen.getByLabelText("Display Name"), { target: { value: "Configured runtime" } }); + fireEvent.change(screen.getByLabelText("URL"), { target: { value: "https://runtime.example/a2a" } }); + } await save(user); expect(patchedPayload().agent_name).toBe("Renamed agent"); expect(patchedPayload()).not.toHaveProperty("litellm_params"); - expect(patchedPayload().agent_card_params === undefined).toBe(card === "empty"); + expect(patchedPayload().agent_card_params === undefined).toBe(card === "empty" && !editCard); + if (editCard) { + expect(patchedPayload().agent_card_params).toMatchObject({ + name: "Configured runtime", + url: "https://runtime.example/a2a", + }); + } expect(patchedPayload().identity).toMatchObject(identity); expect(patchedPayload().access_group_ids).toEqual(["ag-entra"]); expect(networking.patchAgentCall).toHaveBeenCalledWith( diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_info.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_info.tsx index d4c05d0ebac..c9f154c3ce1 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_info.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_info.tsx @@ -238,9 +238,14 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT const updateData = appliedDiscoveredSelection ? overlayDiscoveredCardParams(built, appliedDiscoveredSelection.selected_card) : built; + const cardEdited = + Boolean(appliedDiscoveredSelection) || + [AGENT_FORM_CONFIG.basic, AGENT_FORM_CONFIG.skills, AGENT_FORM_CONFIG.capabilities, AGENT_FORM_CONFIG.optional] + .flatMap((section) => section.fields) + .some((field) => form.getFieldState(field.name).isDirty); await patchAgentCall(accessToken, agentId, { - ...withAgentIdentity(updateData, values, agent), + ...withAgentIdentity(updateData, values, agent, cardEdited), object_permission: buildMcpObjectPermission(values), access_group_ids: values.access_group_ids ?? [], });