diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_card_discovery.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_card_discovery.test.tsx
index dcd6d8a978a..6823ac158cf 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_card_discovery.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_card_discovery.test.tsx
@@ -189,6 +189,34 @@ describe("AgentCardDiscovery", () => {
});
});
+ it("sends the credentials only when the admin clicks Discover", async () => {
+ mockDiscover.mockRejectedValueOnce(new Error("HTTP 401")).mockResolvedValue({
+ url: "https://lg.example.com",
+ agent_card: sampleCard,
+ });
+ const plan = {
+ url: "https://lg.example.com",
+ discovery_mode: "langgraph_platform" as const,
+ params: { assistant_id: "asst_1" },
+ api_key: "sk-1",
+ static_headers: { "x-tenant": "t1" },
+ };
+ renderWithProviders();
+ await vi.advanceTimersByTimeAsync(500);
+ expect(await screen.findByText(/HTTP 401/)).toBeInTheDocument();
+
+ fireEvent.click(screen.getByRole("button", { name: /Discover/ }));
+
+ await waitFor(() => expect(mockDiscover).toHaveBeenCalledTimes(2));
+ const anonymous = { discovery_mode: "langgraph_platform", params: { assistant_id: "asst_1" } };
+ expect(mockDiscover).toHaveBeenNthCalledWith(1, "tok", "https://lg.example.com", anonymous);
+ expect(mockDiscover).toHaveBeenNthCalledWith(2, "tok", "https://lg.example.com", {
+ ...anonymous,
+ api_key: "sk-1",
+ static_headers: { "x-tenant": "t1" },
+ });
+ });
+
it("disables Re-discover until the parent provides a usable URL", async () => {
renderWithProviders(
= ({
[discoveryRequest?.params],
);
- const handleDiscover = useCallback(async () => {
- if (!accessToken) {
- setError(i18n.t("agents.cardDiscovery.errors.noAccessToken"));
- onApplyRef.current(null);
- return;
- }
- const trimmed = effectiveUrl.trim();
- if (!trimmed) {
- setError(
- isParentDriven
- ? i18n.t("agents.cardDiscovery.errors.fillConnectionDetails")
- : i18n.t("agents.cardDiscovery.errors.enterBaseUrl"),
- );
- setCard(null);
- onApplyRef.current(null);
- return;
- }
-
- const currentDiscoveryRequest = discoveryRequestRef.current;
- const requestId = ++discoverRequestIdRef.current;
- setLoading(true);
- setError(null);
- try {
- const response = await discoverAgentCardCall(
- accessToken,
- trimmed,
- isParentDriven && currentDiscoveryRequest
- ? {
- discovery_mode: currentDiscoveryRequest.discovery_mode,
- params: currentDiscoveryRequest.params,
- }
- : undefined,
- );
- if (requestId !== discoverRequestIdRef.current) return;
- lastSyncedSelectionRef.current = null;
- setCard(response.agent_card);
- resetSelections(response.agent_card);
- } catch (e: any) {
- if (requestId !== discoverRequestIdRef.current) return;
- setError(e?.message ? String(e.message) : i18n.t("agents.cardDiscovery.errors.discoverFailed"));
- setCard(null);
- lastSyncedSelectionRef.current = null;
- onApplyRef.current(null);
- } finally {
- if (requestId === discoverRequestIdRef.current) {
- setLoading(false);
+ // Credentials go only with an explicit Discover, never to a URL the admin is still typing.
+ const handleDiscover = useCallback(
+ async (withCredentials?: boolean) => {
+ if (!accessToken) {
+ setError(i18n.t("agents.cardDiscovery.errors.noAccessToken"));
+ onApplyRef.current(null);
+ return;
}
- }
- // ``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 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, effectiveUrl, isParentDriven, discoveryMode, discoveryParamsKey]);
+ const trimmed = effectiveUrl.trim();
+ if (!trimmed) {
+ setError(
+ isParentDriven
+ ? i18n.t("agents.cardDiscovery.errors.fillConnectionDetails")
+ : i18n.t("agents.cardDiscovery.errors.enterBaseUrl"),
+ );
+ setCard(null);
+ onApplyRef.current(null);
+ return;
+ }
+
+ const currentDiscoveryRequest = discoveryRequestRef.current;
+ const requestId = ++discoverRequestIdRef.current;
+ setLoading(true);
+ setError(null);
+ try {
+ const response = await discoverAgentCardCall(
+ accessToken,
+ trimmed,
+ isParentDriven && currentDiscoveryRequest
+ ? {
+ discovery_mode: currentDiscoveryRequest.discovery_mode,
+ params: currentDiscoveryRequest.params,
+ ...(withCredentials && {
+ api_key: currentDiscoveryRequest.api_key,
+ static_headers: currentDiscoveryRequest.static_headers,
+ }),
+ }
+ : undefined,
+ );
+ if (requestId !== discoverRequestIdRef.current) return;
+ lastSyncedSelectionRef.current = null;
+ setCard(response.agent_card);
+ resetSelections(response.agent_card);
+ } catch (e: any) {
+ if (requestId !== discoverRequestIdRef.current) return;
+ setError(e?.message ? String(e.message) : i18n.t("agents.cardDiscovery.errors.discoverFailed"));
+ setCard(null);
+ lastSyncedSelectionRef.current = null;
+ onApplyRef.current(null);
+ } finally {
+ if (requestId === discoverRequestIdRef.current) {
+ setLoading(false);
+ }
+ }
+ // ``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 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, effectiveUrl, isParentDriven, discoveryMode, discoveryParamsKey],
+ );
const debouncedDiscover = useDebouncedCallback(
() => {
@@ -283,7 +291,7 @@ const AgentCardDiscovery: React.FC = ({
)}
-