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.
This commit is contained in:
mateo-berri 2026-05-28 07:24:32 +00:00
parent 8145ece4ca
commit 1a9bce33be
No known key found for this signature in database
4 changed files with 64 additions and 84 deletions

View file

@ -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<AddAgentFormProps> = ({
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<string, any>,
): Record<string, any> => {
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<AddAgentFormProps> = ({
return null;
}
return overlayDiscoveredCardParams(agentData);
return overlayDiscoveredCardParams(
agentData,
appliedDiscoveredSelection?.selected_card,
);
};
const handleCreateAgent = async () => {

View file

@ -199,7 +199,10 @@ const AgentCardDiscovery: React.FC<AgentCardDiscoveryProps> = ({
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<AgentCardDiscoveryProps> = ({
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) => {

View file

@ -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<string, any>,
discovered: DiscoveredAgentCard | null | undefined,
): Record<string, any> => {
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<string, any>,

View file

@ -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<AgentInfoViewProps> = ({
[watchedFormValues, selectedAgentTypeInfo, detectedAgentType],
);
const overlayDiscoveredCardParams = (
agentData: Record<string, any>,
): Record<string, any> => {
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<AgentInfoViewProps> = ({
}
if (appliedDiscoveredSelection) {
updateData = overlayDiscoveredCardParams(updateData);
updateData = overlayDiscoveredCardParams(
updateData,
appliedDiscoveredSelection.selected_card,
);
}
await patchAgentCall(accessToken, agentId, updateData);