From 59e36bcfb4ad0a853614a1a9d77552b524ca018e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 28 May 2026 04:16:11 +0000 Subject: [PATCH] fix: address PR bugfix review for a2a discovery + metadata forwarding - agent create form (add_agent_form.tsx): drop the skills.length > 0 guard so an admin can clear all discovered skills during creation, matching the edit form's overlay behavior (consistency between create and edit flows). - agent_card_discovery.tsx: stop including savedAgentCard in the handleDiscover useCallback deps. Read it via a ref inside resetSelections instead, so a parent-driven re-render that hands us a new savedAgentCard object reference (e.g. a background refresh of the agent record) does not recreate handleDiscover and re-fire the auto-discover effect, which would otherwise overwrite in-progress user edits in parent-driven mode (debounceMs = 0). - a2a_endpoints.invoke_agent_a2a: skip 'metadata' when moving litellm params off of A2A MessageSendParams into body. The A2A protocol defines params.metadata as a first-class request-level field, and the completion bridge's get_forward_metadata is supposed to merge it with message.metadata. Previously the proxy always stripped params.metadata before constructing MessageSendParams, so the params-level branch in get_forward_metadata was dead code in the proxy flow. Co-authored-by: Yassin Kortam --- .../proxy/agent_endpoints/a2a_endpoints.py | 7 +++- .../src/components/agents/add_agent_form.tsx | 5 ++- .../agents/agent_card_discovery.tsx | 39 ++++++++++++------- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/litellm/proxy/agent_endpoints/a2a_endpoints.py b/litellm/proxy/agent_endpoints/a2a_endpoints.py index 72a34f61570..8c9b0434d8f 100644 --- a/litellm/proxy/agent_endpoints/a2a_endpoints.py +++ b/litellm/proxy/agent_endpoints/a2a_endpoints.py @@ -332,9 +332,14 @@ async def invoke_agent_a2a( # noqa: PLR0915 if params: # extract any litellm params from the params - eg. 'guardrails' + # ``metadata`` is intentionally excluded: it's a first-class A2A + # ``MessageSendParams`` field that the completion bridge forwards + # downstream via ``get_forward_metadata``. Stripping it here would + # collide with litellm's spend-tracking ``metadata`` kwarg and + # silently drop the caller's A2A request-level metadata. params_to_remove = [] for key, value in params.items(): - if key in all_litellm_params: + if key in all_litellm_params and key != "metadata": params_to_remove.append(key) body[key] = value for key in params_to_remove: 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 7a54ab4fd2f..4e52766cc59 100644 --- a/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx +++ b/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx @@ -254,8 +254,9 @@ const AddAgentForm: React.FC = ({ const discovered = appliedDiscoveredSelection.selected_card; agentData.agent_card_params = { ...agentData.agent_card_params, - ...(Array.isArray(discovered.skills) && - discovered.skills.length > 0 && { skills: discovered.skills }), + ...(Array.isArray(discovered.skills) && { + skills: discovered.skills, + }), ...(discovered.capabilities && { capabilities: discovered.capabilities, }), 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 7395c2feb91..f0fc9f2192c 100644 --- a/ui/litellm-dashboard/src/components/agents/agent_card_discovery.tsx +++ b/ui/litellm-dashboard/src/components/agents/agent_card_discovery.tsx @@ -96,16 +96,6 @@ const AgentCardDiscovery: React.FC = ({ Record >({}); - const resetSelections = (fresh: DiscoveredAgentCard) => { - const initial = savedAgentCard - ? selectionsFromSavedAgentCard(fresh, savedAgentCard) - : selectionsFromUpstreamCard(fresh); - setEditedName(initial.editedName); - setEditedDescription(initial.editedDescription); - setSelectedSkillIds(initial.selectedSkillIds); - setSelectedCapabilities(initial.selectedCapabilities); - }; - const onApplyRef = useRef(onApply); onApplyRef.current = onApply; const discoverRequestIdRef = useRef(0); @@ -118,6 +108,25 @@ const AgentCardDiscovery: React.FC = ({ // the callback / effect only re-run when content actually changes. const discoveryRequestRef = useRef(discoveryRequest); discoveryRequestRef.current = discoveryRequest; + // Hold ``savedAgentCard`` in a ref so ``resetSelections`` always sees the + // latest value without making it a dependency of ``handleDiscover``. + // Putting ``savedAgentCard`` directly in the callback deps means any parent + // re-render that hands us a new object reference (e.g. a background + // agent-data refresh during editing) recreates ``handleDiscover``, which + // re-fires the auto-discover effect and overwrites in-progress user edits. + const savedAgentCardRef = useRef(savedAgentCard); + savedAgentCardRef.current = savedAgentCard; + + const resetSelections = (fresh: DiscoveredAgentCard) => { + const saved = savedAgentCardRef.current; + const initial = saved + ? selectionsFromSavedAgentCard(fresh, saved) + : selectionsFromUpstreamCard(fresh); + setEditedName(initial.editedName); + setEditedDescription(initial.editedDescription); + setSelectedSkillIds(initial.selectedSkillIds); + setSelectedCapabilities(initial.selectedCapabilities); + }; const discoveryMode = discoveryRequest?.discovery_mode; const discoveryParamsKey = useMemo( @@ -176,9 +185,12 @@ const AgentCardDiscovery: React.FC = ({ // ``discoveryMode`` / ``discoveryParamsKey`` are primitive proxies for // ``discoveryRequest`` content; the actual object is read via the ref // above so identity churn from the parent doesn't recreate this callback. - // ``savedAgentCard`` is captured indirectly via ``resetSelections``: if - // the parent refetches the agent and hands us a new saved card, we need - // a fresh callback so "Re-discover" pre-selects against the latest data. + // ``savedAgentCard`` is intentionally NOT a dep — it's read via + // ``savedAgentCardRef`` inside ``resetSelections``. Including it here + // would recreate this callback whenever the parent hands us a new + // ``savedAgentCard`` object (e.g. a background refresh of agent data + // during editing), which would re-fire the auto-discover effect and + // wipe in-progress user selections. // eslint-disable-next-line react-hooks/exhaustive-deps }, [ accessToken, @@ -186,7 +198,6 @@ const AgentCardDiscovery: React.FC = ({ isParentDriven, discoveryMode, discoveryParamsKey, - savedAgentCard, ]); // Auto-discover when the URL (or parent plan) becomes available.