From 1a9bce33bea108a7b67ade6be3ed15a3d2ae127d Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Thu, 28 May 2026 07:24:32 +0000 Subject: [PATCH] fix(ui): extract overlayDiscoveredCardParams + debounce parent-driven discovery Two findings from greptile review: 1. `overlayDiscoveredCardParams` was copy-pasted between `add_agent_form.tsx` and `agent_info.tsx`. Move it to `agent_discovery_utils.ts` so the create and edit flows share the same overlay logic and there's only one place to update when discovered fields change. 2. `agent_card_discovery.tsx` used a zero-debounce path for parent-driven mode, which fires one discovery HTTP request per keystroke when an admin types into the parent form's URL / api_base / assistant_id fields (the parent rebuilds the plan from watched form values every render). Apply the same 400ms debounce uniformly. --- .../src/components/agents/add_agent_form.tsx | 51 +++---------------- .../agents/agent_card_discovery.tsx | 10 ++-- .../agents/agent_discovery_utils.ts | 42 +++++++++++++++ .../src/components/agents/agent_info.tsx | 45 +++------------- 4 files changed, 64 insertions(+), 84 deletions(-) diff --git a/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx b/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx index 45ee941a43f..3929a9e1832 100644 --- a/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx +++ b/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx @@ -22,7 +22,10 @@ import AgentFormFields from "./agent_form_fields"; import AgentCardDiscovery, { DiscoveredAgentCardSelection, } from "./agent_card_discovery"; -import { buildDiscoveryRequest } from "./agent_discovery_utils"; +import { + buildDiscoveryRequest, + overlayDiscoveredCardParams, +} from "./agent_discovery_utils"; import DynamicAgentFormFields, { buildDynamicAgentData } from "./dynamic_agent_form_fields"; import { getDefaultFormValues, buildAgentDataFromForm } from "./agent_config"; import MCPServerSelector from "../mcp_server_management/MCPServerSelector"; @@ -210,47 +213,6 @@ const AddAgentForm: React.FC = ({ setCurrentStep((s) => Math.max(0, s - 1)); }; - // Overlay the user's discovery selections onto the agent_card_params built - // from the form. Name and description are overlaid (falling back to the - // form-built value) along with skills, capabilities, input/output modes, - // provider, and icon/doc URLs because dynamic agent forms (e.g. LangGraph) - // don't register Form.Items for them, so AntD's setFieldsValue silently - // drops those keys and the values never make it back through buildAgentData. - const overlayDiscoveredCardParams = ( - agentData: Record, - ): Record => { - if (!appliedDiscoveredSelection) return agentData; - const discovered = appliedDiscoveredSelection.selected_card; - return { - ...agentData, - agent_card_params: { - ...agentData.agent_card_params, - name: discovered.name ?? agentData.agent_card_params?.name, - description: - discovered.description ?? agentData.agent_card_params?.description, - ...(Array.isArray(discovered.skills) && { - skills: discovered.skills, - }), - ...(discovered.capabilities && { - capabilities: discovered.capabilities, - }), - ...(Array.isArray(discovered.defaultInputModes) && - discovered.defaultInputModes.length > 0 && { - defaultInputModes: discovered.defaultInputModes, - }), - ...(Array.isArray(discovered.defaultOutputModes) && - discovered.defaultOutputModes.length > 0 && { - defaultOutputModes: discovered.defaultOutputModes, - }), - ...(discovered.provider && { provider: discovered.provider }), - ...(discovered.iconUrl && { iconUrl: discovered.iconUrl }), - ...(discovered.documentationUrl && { - documentationUrl: discovered.documentationUrl, - }), - }, - }; - }; - const buildAgentData = (values: any) => { if (agentType === CUSTOM_AGENT_TYPE) { return { @@ -292,7 +254,10 @@ const AddAgentForm: React.FC = ({ return null; } - return overlayDiscoveredCardParams(agentData); + return overlayDiscoveredCardParams( + agentData, + appliedDiscoveredSelection?.selected_card, + ); }; const handleCreateAgent = async () => { diff --git a/ui/litellm-dashboard/src/components/agents/agent_card_discovery.tsx b/ui/litellm-dashboard/src/components/agents/agent_card_discovery.tsx index 1549150e8c3..ee34450d092 100644 --- a/ui/litellm-dashboard/src/components/agents/agent_card_discovery.tsx +++ b/ui/litellm-dashboard/src/components/agents/agent_card_discovery.tsx @@ -199,7 +199,10 @@ const AgentCardDiscovery: React.FC = ({ discoveryParamsKey, ]); - // Auto-discover when the URL (or parent plan) becomes available. + // Auto-discover when the URL (or parent plan) becomes available. Debounce + // is applied uniformly so rapid changes from a watched parent form (e.g. + // typing into a LangGraph api_base / assistant_id field) don't fire one + // HTTP request per keystroke. useEffect(() => { if (!accessToken) return; const trimmed = effectiveUrl.trim(); @@ -211,12 +214,11 @@ const AgentCardDiscovery: React.FC = ({ return; } - const debounceMs = isParentDriven ? 0 : 400; const timer = window.setTimeout(() => { void handleDiscover(); - }, debounceMs); + }, 400); return () => window.clearTimeout(timer); - }, [accessToken, effectiveUrl, isParentDriven, handleDiscover]); + }, [accessToken, effectiveUrl, handleDiscover]); const toggleSkill = (id: string, checked: boolean) => { setSelectedSkillIds((prev) => { diff --git a/ui/litellm-dashboard/src/components/agents/agent_discovery_utils.ts b/ui/litellm-dashboard/src/components/agents/agent_discovery_utils.ts index 2520102a5a9..bb3d79e259b 100644 --- a/ui/litellm-dashboard/src/components/agents/agent_discovery_utils.ts +++ b/ui/litellm-dashboard/src/components/agents/agent_discovery_utils.ts @@ -94,6 +94,48 @@ export const selectionsFromUpstreamCard = ( }; }; +/** + * Overlay the admin's discovery selections onto the ``agent_card_params`` + * built from the form. Dynamic agent forms (e.g. LangGraph) don't register + * Form.Items for name / description / skills / capabilities, so AntD's + * setFieldsValue silently drops those keys and the values never make it back + * through buildAgentData — we re-apply them here from the selection. + */ +export const overlayDiscoveredCardParams = ( + agentData: Record, + discovered: DiscoveredAgentCard | null | undefined, +): Record => { + if (!discovered) return agentData; + return { + ...agentData, + agent_card_params: { + ...agentData.agent_card_params, + name: discovered.name ?? agentData.agent_card_params?.name, + description: + discovered.description ?? agentData.agent_card_params?.description, + ...(Array.isArray(discovered.skills) && { + skills: discovered.skills, + }), + ...(discovered.capabilities && { + capabilities: discovered.capabilities, + }), + ...(Array.isArray(discovered.defaultInputModes) && + discovered.defaultInputModes.length > 0 && { + defaultInputModes: discovered.defaultInputModes, + }), + ...(Array.isArray(discovered.defaultOutputModes) && + discovered.defaultOutputModes.length > 0 && { + defaultOutputModes: discovered.defaultOutputModes, + }), + ...(discovered.provider && { provider: discovered.provider }), + ...(discovered.iconUrl && { iconUrl: discovered.iconUrl }), + ...(discovered.documentationUrl && { + documentationUrl: discovered.documentationUrl, + }), + }, + }; +}; + export const buildDiscoveryRequest = ( agentType: string, values: Record, diff --git a/ui/litellm-dashboard/src/components/agents/agent_info.tsx b/ui/litellm-dashboard/src/components/agents/agent_info.tsx index ba968921c93..586a341737f 100644 --- a/ui/litellm-dashboard/src/components/agents/agent_info.tsx +++ b/ui/litellm-dashboard/src/components/agents/agent_info.tsx @@ -13,7 +13,10 @@ import { detectAgentType, parseDynamicAgentForForm } from "./agent_type_utils"; import AgentCardDiscovery, { DiscoveredAgentCardSelection, } from "./agent_card_discovery"; -import { buildDiscoveryRequest } from "./agent_discovery_utils"; +import { + buildDiscoveryRequest, + overlayDiscoveredCardParams, +} from "./agent_discovery_utils"; interface AgentInfoViewProps { agentId: string; @@ -111,41 +114,6 @@ const AgentInfoView: React.FC = ({ [watchedFormValues, selectedAgentTypeInfo, detectedAgentType], ); - const overlayDiscoveredCardParams = ( - agentData: Record, - ): Record => { - if (!appliedDiscoveredSelection) return agentData; - const discovered = appliedDiscoveredSelection.selected_card; - return { - ...agentData, - agent_card_params: { - ...agentData.agent_card_params, - name: discovered.name ?? agentData.agent_card_params?.name, - description: - discovered.description ?? agentData.agent_card_params?.description, - ...(Array.isArray(discovered.skills) && { - skills: discovered.skills, - }), - ...(discovered.capabilities && { - capabilities: discovered.capabilities, - }), - ...(Array.isArray(discovered.defaultInputModes) && - discovered.defaultInputModes.length > 0 && { - defaultInputModes: discovered.defaultInputModes, - }), - ...(Array.isArray(discovered.defaultOutputModes) && - discovered.defaultOutputModes.length > 0 && { - defaultOutputModes: discovered.defaultOutputModes, - }), - ...(discovered.provider && { provider: discovered.provider }), - ...(discovered.iconUrl && { iconUrl: discovered.iconUrl }), - ...(discovered.documentationUrl && { - documentationUrl: discovered.documentationUrl, - }), - }, - }; - }; - const handleApplyDiscoveredCard = ( selection: DiscoveredAgentCardSelection | null, ) => { @@ -196,7 +164,10 @@ const AgentInfoView: React.FC = ({ } if (appliedDiscoveredSelection) { - updateData = overlayDiscoveredCardParams(updateData); + updateData = overlayDiscoveredCardParams( + updateData, + appliedDiscoveredSelection.selected_card, + ); } await patchAgentCall(accessToken, agentId, updateData);