import { useEffect, useState, type ReactNode } from "react"; import { Link, useNavigate } from "@tanstack/react-router"; import { type CompleteSourceOAuthResult, type ConnectSourceBatchPayload, type ConnectSourceBatchResult, type ConnectSourcePayload, type ConnectSourceResult, type CreateWorkspaceOauthClientPayload, type DiscoverSourcePayload, type InstanceConfig, type Loadable, type SecretListItem, type Source, type SourceDiscoveryResult, type WorkspaceOauthClient, useConnectSourceBatch, useConnectSource, useCreateWorkspaceOauthClient, useCreateSecret, useDiscoverSource, useInvalidateExecutorQueries, useInstanceConfig, useRefreshSecrets, useSecrets, useStartSourceOAuth, useWorkspaceOauthClients, } from "@executor/react"; import { Badge } from "../components/ui/badge"; import { Button } from "../components/ui/button"; import { LocalMcpInstallCard } from "../components/local-mcp-install-card"; import { SourceFavicon } from "../components/source-favicon"; import { IconArrowLeft, IconCheck, IconDiscover, IconPlus, IconSpinner, } from "../components/icons"; import { cn } from "../lib/utils"; import { sourceTemplates, type SourceTemplate } from "./source-templates"; import { getDomain } from "tldts"; // --------------------------------------------------------------------------- // State machine // --------------------------------------------------------------------------- type FlowPhase = | "idle" | "discovering" | "editing" | "connecting" | "connected" | "credential_required" | "oauth_required"; type ProbeAuthKind = "none" | "bearer" | "basic" | "headers"; type ProbeAuthState = { kind: ProbeAuthKind; token: string; headerName: string; prefix: string; username: string; password: string; headersText: string; }; type ConnectFormState = { kind: "mcp" | "openapi" | "graphql" | "google_discovery"; endpoint: string; specUrl: string; service: string; version: string; discoveryUrl: string; name: string; namespace: string; transport: "" | "auto" | "streamable-http" | "sse"; authKind: "none" | "bearer" | "oauth2"; authHeaderName: string; authPrefix: string; bearerToken: string; bearerProviderId: string; bearerHandle: string; workspaceOauthClientId: string; oauthClientId: string; oauthClientSecret: string; queryParamsText: string; headersText: string; }; type OAuthRequiredInfo = { source: Source; sessionId: string; authorizationUrl: string; }; type BatchOAuthRequiredInfo = { sessionId: string; authorizationUrl: string; sourceIds: ReadonlyArray; }; // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- const kindOptions: ReadonlyArray = [ "mcp", "openapi", "graphql", "google_discovery", ]; const transportOptions: ReadonlyArray<"auto" | "streamable-http" | "sse"> = [ "auto", "streamable-http", "sse", ]; const authOptions: ReadonlyArray = ["none", "bearer", "oauth2"]; const probeAuthOptions: ReadonlyArray = ["none", "bearer", "basic", "headers"]; const SOURCE_OAUTH_POPUP_RESULT_TIMEOUT_MS = 2 * 60_000; const SOURCE_OAUTH_POPUP_RESULT_STORAGE_KEY_PREFIX = "executor:oauth-result:"; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- const trimToNull = (value: string): string | null => { const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; }; const namespaceFromUrl = (url: string): string => { try { const domain = getDomain(url); if (!domain) return ""; // Strip the TLD: "github.com" -> "github", "linear.app" -> "linear" const dot = domain.indexOf("."); return dot > 0 ? domain.slice(0, dot) : domain; } catch { return ""; } }; const googleDiscoveryNamespace = (service: string): string => `google.${service.trim().toLowerCase().replace(/[^a-z0-9]+/g, ".").replace(/^\.+|\.+$/g, "")}`; const googleDiscoveryDefaultsFromUrl = (value: string): { service: string; version: string; discoveryUrl: string; } | null => { try { const url = new URL(value); const byDirectory = url.pathname.match(/^\/discovery\/v1\/apis\/([^/]+)\/([^/]+)\/rest$/); if (byDirectory) { return { service: decodeURIComponent(byDirectory[1] ?? ""), version: decodeURIComponent(byDirectory[2] ?? ""), discoveryUrl: url.toString(), }; } const versionParam = url.searchParams.get("version"); const version = versionParam ? trimToNull(versionParam) : null; const isHostScopedDiscovery = url.pathname === "/$discovery/rest" && url.hostname.endsWith(".googleapis.com") && url.hostname !== "www.googleapis.com"; if (version && isHostScopedDiscovery) { return { service: url.hostname.split(".")[0] ?? "", version, discoveryUrl: url.toString(), }; } return null; } catch { return null; } }; const parseJsonStringMap = (label: string, text: string): Record | null => { const trimmed = text.trim(); if (trimmed.length === 0) { return null; } let parsed: unknown; try { parsed = JSON.parse(trimmed); } catch { throw new Error(`${label} must be valid JSON.`); } if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") { throw new Error(`${label} must be a JSON object with string values.`); } const entries = Object.entries(parsed as Record); const normalized: Record = {}; for (const [key, value] of entries) { if (typeof value !== "string") { throw new Error(`${label} must only contain string values.`); } normalized[key] = value; } return Object.keys(normalized).length === 0 ? null : normalized; }; // --------------------------------------------------------------------------- // Derived defaults from discovery result // --------------------------------------------------------------------------- const defaultConnectForm = (discovery?: SourceDiscoveryResult): ConnectFormState => { if (!discovery || discovery.detectedKind === "unknown") { return { kind: "openapi", endpoint: discovery?.endpoint ?? "", specUrl: discovery?.specUrl ?? "", service: "", version: "", discoveryUrl: "", name: discovery?.name ?? "", namespace: discovery?.namespace || namespaceFromUrl(discovery?.endpoint ?? ""), transport: "", authKind: "none", authHeaderName: "Authorization", authPrefix: "Bearer ", bearerToken: "", bearerProviderId: "", bearerHandle: "", workspaceOauthClientId: "", oauthClientId: "", oauthClientSecret: "", queryParamsText: "", headersText: "", }; } const kind = discovery.detectedKind as ConnectFormState["kind"]; const auth = discovery.authInference; // Map auth suggestion to what backend connect supports let authKind: ConnectFormState["authKind"] = "none"; let authHeaderName = "Authorization"; let authPrefix = "Bearer "; if (auth.supported) { if (auth.suggestedKind === "bearer" || auth.suggestedKind === "apiKey") { authKind = "bearer"; authHeaderName = auth.headerName ?? "Authorization"; authPrefix = auth.prefix ?? "Bearer "; } else if (auth.suggestedKind === "oauth2") { authKind = "oauth2"; authHeaderName = auth.headerName ?? "Authorization"; authPrefix = auth.prefix ?? "Bearer "; } else if (auth.suggestedKind === "basic") { // Backend connect doesn't support basic auth natively; map to bearer authKind = "bearer"; authHeaderName = "Authorization"; authPrefix = "Basic "; } } const googleDiscoveryDefaults = kind === "google_discovery" ? googleDiscoveryDefaultsFromUrl(discovery.specUrl ?? discovery.endpoint) : null; return { kind, endpoint: discovery.endpoint, specUrl: discovery.specUrl ?? "", service: googleDiscoveryDefaults?.service ?? "", version: googleDiscoveryDefaults?.version ?? "", discoveryUrl: googleDiscoveryDefaults?.discoveryUrl ?? "", name: discovery.name ?? "", namespace: discovery.namespace || namespaceFromUrl(discovery.endpoint), transport: kind === "mcp" ? (discovery.transport ?? "auto") : "", authKind, authHeaderName, authPrefix, bearerToken: "", bearerProviderId: "", bearerHandle: "", workspaceOauthClientId: "", oauthClientId: "", oauthClientSecret: "", queryParamsText: "", headersText: "", }; }; const connectFormFromTemplate = (template: SourceTemplate): ConnectFormState => ({ ...defaultConnectForm(), kind: template.kind as ConnectFormState["kind"], endpoint: template.endpoint, specUrl: "specUrl" in template ? template.specUrl : "", service: "service" in template ? template.service : "", version: "version" in template ? template.version : "", discoveryUrl: "discoveryUrl" in template ? template.discoveryUrl : "", name: template.name, namespace: "service" in template ? googleDiscoveryNamespace(template.service) : namespaceFromUrl(template.endpoint), transport: template.kind === "mcp" ? "auto" : "", authKind: template.kind === "google_discovery" ? "oauth2" : "none", workspaceOauthClientId: "", }); const authKindForSourceKind = ( currentAuthKind: ConnectFormState["authKind"], nextKind: ConnectFormState["kind"], ): ConnectFormState["authKind"] => nextKind === "google_discovery" && currentAuthKind === "none" ? "oauth2" : currentAuthKind; const buildProbeAuth = (state: ProbeAuthState): DiscoverSourcePayload["probeAuth"] => { if (state.kind === "none") return { kind: "none" }; if (state.kind === "bearer") { if (!state.token.trim()) throw new Error("Token is required for bearer probe auth."); return { kind: "bearer", headerName: trimToNull(state.headerName), prefix: trimToNull(state.prefix), token: state.token.trim(), }; } if (state.kind === "basic") { if (!state.username.trim()) throw new Error("Username is required for basic probe auth."); return { kind: "basic", username: state.username.trim(), password: state.password, }; } // headers const headers = parseJsonStringMap("Probe headers", state.headersText); if (!headers) throw new Error("At least one header is required for headers probe auth."); return { kind: "headers", headers }; }; const buildConnectPayload = (form: ConnectFormState): ConnectSourcePayload => { if (form.kind === "mcp") { const endpoint = form.endpoint.trim(); if (!endpoint) throw new Error("Endpoint is required."); return { kind: "mcp", endpoint, name: trimToNull(form.name), namespace: trimToNull(form.namespace), transport: form.transport === "" ? "auto" : form.transport, queryParams: parseJsonStringMap("Query params", form.queryParamsText), headers: parseJsonStringMap("Request headers", form.headersText), }; } // Build HTTP auth for openapi/graphql const auth = buildHttpAuth(form); if (form.kind === "openapi") { const endpoint = form.endpoint.trim(); if (!endpoint) throw new Error("Endpoint is required."); const specUrl = form.specUrl.trim(); if (!specUrl) throw new Error("OpenAPI sources require a spec URL."); return { kind: "openapi", endpoint, specUrl, name: trimToNull(form.name), namespace: trimToNull(form.namespace), auth, }; } if (form.kind === "google_discovery") { const service = form.service.trim(); const version = form.version.trim(); if (!service) throw new Error("Google Discovery sources require a service name."); if (!version) throw new Error("Google Discovery sources require a version."); if ( form.authKind === "oauth2" && form.workspaceOauthClientId.trim().length === 0 && form.oauthClientId.trim().length === 0 ) { throw new Error("Google OAuth requires a workspace OAuth client or a new client ID."); } return { kind: "google_discovery", service, version, discoveryUrl: trimToNull(form.discoveryUrl), name: trimToNull(form.name), namespace: trimToNull(form.namespace), workspaceOauthClientId: form.authKind === "oauth2" && form.workspaceOauthClientId.trim().length > 0 ? form.workspaceOauthClientId.trim() : undefined, oauthClient: form.authKind === "oauth2" && form.workspaceOauthClientId.trim().length === 0 ? { clientId: form.oauthClientId.trim(), clientSecret: trimToNull(form.oauthClientSecret), } : null, auth, }; } const endpoint = form.endpoint.trim(); if (!endpoint) throw new Error("Endpoint is required."); return { kind: "graphql", endpoint, name: trimToNull(form.name), namespace: trimToNull(form.namespace), auth, }; }; const buildHttpAuth = ( form: ConnectFormState, ): { kind: "none" } | { kind: "bearer"; headerName?: string | null; prefix?: string | null; token?: string | null; tokenRef?: { providerId: string; handle: string } | null } | undefined => { if (form.authKind === "none") return { kind: "none" }; if (form.authKind === "bearer") { const headerName = trimToNull(form.authHeaderName); const prefix = form.authPrefix.length === 0 ? null : form.authPrefix; // Prefer secret ref if set if (form.bearerProviderId.trim() && form.bearerHandle.trim()) { return { kind: "bearer", headerName, prefix, tokenRef: { providerId: form.bearerProviderId.trim(), handle: form.bearerHandle.trim(), }, }; } // Fall back to inline token if (form.bearerToken.trim()) { return { kind: "bearer", headerName, prefix, token: form.bearerToken.trim(), }; } throw new Error("Bearer auth requires a token. Select or create a secret, or enter a token directly."); } // oauth2 is handled via the connect result flow, not pre-filled return undefined; }; // --------------------------------------------------------------------------- // OAuth popup helpers (shared with source-editor.tsx) // --------------------------------------------------------------------------- type SourceOAuthPopupMessage = | { type: "executor:oauth-result"; ok: true; sessionId: string; auth: CompleteSourceOAuthResult["auth"]; } | { type: "executor:oauth-result"; ok: false; sessionId: string | null; error: string; } | { type: "executor:source-oauth-result"; ok: true; sourceId: string; } | { type: "executor:source-oauth-result"; ok: false; error: string; }; const readStoredSourceOAuthPopupResult = (sessionId: string): SourceOAuthPopupMessage | null => { if (typeof window === "undefined") return null; const raw = window.localStorage.getItem( `${SOURCE_OAUTH_POPUP_RESULT_STORAGE_KEY_PREFIX}${sessionId}`, ); if (!raw) return null; try { return JSON.parse(raw) as SourceOAuthPopupMessage; } catch { return null; } }; const clearStoredSourceOAuthPopupResult = (sessionId: string): void => { if (typeof window === "undefined") return; window.localStorage.removeItem(`${SOURCE_OAUTH_POPUP_RESULT_STORAGE_KEY_PREFIX}${sessionId}`); }; const startSourceOAuthPopup = async (input: { authorizationUrl: string; sessionId: string; }): Promise => { if (typeof window === "undefined") { throw new Error("OAuth popup is only available in a browser context"); } clearStoredSourceOAuthPopupResult(input.sessionId); const popup = window.open( input.authorizationUrl, "executor-source-oauth", "popup=yes,width=520,height=720", ); if (!popup) { throw new Error("Popup blocked. Allow popups and try again."); } popup.focus(); return await new Promise((resolve, reject) => { let settled = false; let closedPoll = 0; let resultTimeout = 0; const cleanup = () => { window.removeEventListener("message", onMessage); if (closedPoll) window.clearInterval(closedPoll); if (resultTimeout) window.clearTimeout(resultTimeout); if (!popup.closed) popup.close(); clearStoredSourceOAuthPopupResult(input.sessionId); }; const settleWithError = (message: string) => { if (settled) return; settled = true; cleanup(); reject(new Error(message)); }; const settleFromPayload = (data: SourceOAuthPopupMessage) => { if (!data.ok) { settleWithError(data.error || "OAuth failed"); return; } if (settled) return; settled = true; cleanup(); resolve(); }; const onMessage = (event: MessageEvent) => { if (event.origin !== window.location.origin) return; const data = event.data as SourceOAuthPopupMessage | undefined; if (!data) return; if (data.type === "executor:oauth-result") { if (data.ok && data.sessionId !== input.sessionId) return; if (!data.ok && data.sessionId !== null && data.sessionId !== input.sessionId) return; } else if (data.type !== "executor:source-oauth-result") { return; } settleFromPayload(data); }; window.addEventListener("message", onMessage); resultTimeout = window.setTimeout(() => { settleWithError("OAuth popup timed out before completion. Please try again."); }, SOURCE_OAUTH_POPUP_RESULT_TIMEOUT_MS); closedPoll = window.setInterval(() => { const stored = readStoredSourceOAuthPopupResult(input.sessionId); if (stored) { settleFromPayload(stored); return; } if (popup.closed) { // Stop polling — only run one final deferred check to give the // callback page time to write localStorage before we give up. window.clearInterval(closedPoll); closedPoll = 0; window.setTimeout(() => { const delayedStored = readStoredSourceOAuthPopupResult(input.sessionId); if (delayedStored) { settleFromPayload(delayedStored); return; } settleWithError("OAuth popup was closed before completion."); }, 1500); } }, 300); }); }; // --------------------------------------------------------------------------- // Confidence badge helper // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // Page // --------------------------------------------------------------------------- export function AddSourcePage() { const navigate = useNavigate(); const discoverSource = useDiscoverSource(); const connectSource = useConnectSource(); const connectSourceBatch = useConnectSourceBatch(); const createWorkspaceOauthClient = useCreateWorkspaceOauthClient(); const workspaceOauthClients = useWorkspaceOauthClients("google_workspace"); const instanceConfig = useInstanceConfig(); const invalidateExecutorQueries = useInvalidateExecutorQueries(); const secrets = useSecrets(); const refreshSecrets = useRefreshSecrets(); const startSourceOAuth = useStartSourceOAuth(); // URL input const [url, setUrl] = useState(""); // Probe auth const [showProbeAuth, setShowProbeAuth] = useState(false); const [probeAuth, setProbeAuth] = useState({ kind: "none", token: "", headerName: "Authorization", prefix: "Bearer ", username: "", password: "", headersText: "", }); // Phase const [phase, setPhase] = useState("idle"); // Discovery result const [discovery, setDiscovery] = useState(null); // Editable connect form (populated after discovery) const [connectForm, setConnectForm] = useState(defaultConnectForm()); // Connect result const [connectResult, setConnectResult] = useState(null); // OAuth required state const [oauthInfo, setOauthInfo] = useState(null); const [batchOauthInfo, setBatchOauthInfo] = useState(null); const [oauthBusy, setOauthBusy] = useState(false); const [batchConnecting, setBatchConnecting] = useState(false); const [selectedGoogleTemplateIds, setSelectedGoogleTemplateIds] = useState>([]); // Status banner const [statusBanner, setStatusBanner] = useState<{ tone: "info" | "success" | "error"; text: string; } | null>(null); const setFormField = (key: K, value: ConnectFormState[K]) => { setConnectForm((current) => ({ ...current, [key]: value })); }; const setSourceKind = (kind: ConnectFormState["kind"]) => { setConnectForm((current) => ({ ...current, kind, authKind: authKindForSourceKind(current.authKind, kind), workspaceOauthClientId: kind === "google_discovery" ? current.workspaceOauthClientId : "", })); }; const setProbeField = (key: K, value: ProbeAuthState[K]) => { setProbeAuth((current) => ({ ...current, [key]: value })); }; const googleTemplates = sourceTemplates.filter( (template) => template.groupId === "google_workspace" && template.batchable, ); // ------------------------------------------------------------------------- // Handlers // ------------------------------------------------------------------------- const handleDiscover = async () => { setStatusBanner(null); const trimmedUrl = url.trim(); if (!trimmedUrl) { setStatusBanner({ tone: "error", text: "Please enter a URL to discover." }); return; } setPhase("discovering"); try { const payload: DiscoverSourcePayload = { url: trimmedUrl, ...(showProbeAuth && probeAuth.kind !== "none" ? { probeAuth: buildProbeAuth(probeAuth) } : {}), }; const result = await discoverSource.mutateAsync(payload); setDiscovery(result); setConnectForm(defaultConnectForm(result)); setPhase("editing"); if (result.detectedKind === "unknown") { setStatusBanner({ tone: "info", text: "Could not auto-detect the source type. Please configure manually.", }); } else if (result.warnings.length > 0) { setStatusBanner({ tone: "info", text: result.warnings.join(" "), }); } } catch (error) { setPhase("idle"); setStatusBanner({ tone: "error", text: error instanceof Error ? error.message : "Discovery failed.", }); } }; const handleSkipDiscovery = () => { setDiscovery(null); setConnectForm(defaultConnectForm()); setPhase("editing"); setStatusBanner(null); }; const applyTemplate = async (template: SourceTemplate) => { if (template.batchable) { setSelectedGoogleTemplateIds((current) => current.includes(template.id) ? current.filter((id) => id !== template.id) : [...current, template.id], ); setStatusBanner(null); return; } const discoveryUrl = "specUrl" in template ? template.specUrl : template.endpoint; setUrl(template.endpoint); setStatusBanner(null); setPhase("discovering"); try { const result = await discoverSource.mutateAsync({ url: discoveryUrl }); setDiscovery(result); const form = defaultConnectForm(result); // Prefer the template's own values over whatever discover returned form.name = template.name; form.endpoint = template.endpoint; form.namespace = namespaceFromUrl(template.endpoint); if ("specUrl" in template) { form.specUrl = template.specUrl; } setConnectForm(form); setPhase("editing"); if (result.warnings.length > 0) { setStatusBanner({ tone: "info", text: result.warnings.join(" "), }); } } catch (error) { // Discovery failed — fall back to just the template basics setDiscovery(null); setConnectForm(connectFormFromTemplate(template)); setPhase("editing"); setStatusBanner({ tone: "error", text: `Discovery failed for ${template.name}: ${error instanceof Error ? error.message : "unknown error"}. Configure manually.`, }); } }; const handleConnect = async () => { setStatusBanner(null); try { const payload = buildConnectPayload(connectForm); setPhase("connecting"); const result = await connectSource.mutateAsync(payload); setConnectResult(result); if (result.kind === "connected") { setBatchOauthInfo(null); setPhase("connected"); setStatusBanner({ tone: "success", text: `"${result.source.name}" connected successfully.`, }); // Navigate to source detail after short delay setTimeout(() => { void navigate({ to: "/sources/$sourceId", params: { sourceId: result.source.id }, search: { tab: "model" }, }); }, 1200); } else if (result.kind === "credential_required") { setBatchOauthInfo(null); setPhase("credential_required"); setStatusBanner({ tone: "info", text: "This source requires credentials. Configure auth below, then connect again.", }); // Pre-select bearer auth if not already set if (connectForm.authKind === "none") { setFormField("authKind", "bearer"); } } else if (result.kind === "oauth_required") { setBatchOauthInfo(null); setPhase("oauth_required"); setOauthInfo({ source: result.source, sessionId: result.sessionId, authorizationUrl: result.authorizationUrl, }); setStatusBanner({ tone: "info", text: "This source requires OAuth authentication. Click the button below to sign in.", }); } } catch (error) { setPhase("editing"); setStatusBanner({ tone: "error", text: error instanceof Error ? error.message : "Connection failed.", }); } }; const handleOAuthPopup = async () => { if (!oauthInfo && !batchOauthInfo) return; setStatusBanner(null); setOauthBusy(true); try { await startSourceOAuthPopup({ authorizationUrl: oauthInfo?.authorizationUrl ?? batchOauthInfo!.authorizationUrl, sessionId: oauthInfo?.sessionId ?? batchOauthInfo!.sessionId, }); refreshSecrets(); invalidateExecutorQueries(); setPhase("connected"); setStatusBanner({ tone: "success", text: oauthInfo ? `"${oauthInfo.source.name}" connected via OAuth.` : `Connected ${batchOauthInfo?.sourceIds.length ?? 0} Google source${(batchOauthInfo?.sourceIds.length ?? 0) === 1 ? "" : "s"} via OAuth.`, }); setTimeout(() => { if (oauthInfo) { void navigate({ to: "/sources/$sourceId", params: { sourceId: oauthInfo.source.id }, search: { tab: "model" }, }); return; } void navigate({ to: "/", }); }, 1200); } catch (error) { setStatusBanner({ tone: "error", text: error instanceof Error ? error.message : "OAuth flow failed.", }); } finally { setOauthBusy(false); } }; const ensureGoogleWorkspaceOauthClientId = async (): Promise => { const selectedClientId = connectForm.workspaceOauthClientId.trim(); if (selectedClientId.length > 0) { return selectedClientId; } const clientId = connectForm.oauthClientId.trim(); if (clientId.length === 0) { throw new Error("Choose an existing Google workspace OAuth client or enter a new client ID."); } const created = await createWorkspaceOauthClient.mutateAsync({ providerKey: "google_workspace", label: "Google Workspace", oauthClient: { clientId, clientSecret: trimToNull(connectForm.oauthClientSecret), }, } satisfies CreateWorkspaceOauthClientPayload); setFormField("workspaceOauthClientId", created.id); return created.id; }; const handleConnectGoogleBatch = async () => { if (selectedGoogleTemplateIds.length === 0) { return; } setStatusBanner(null); try { const workspaceOauthClientId = await ensureGoogleWorkspaceOauthClientId(); const selectedTemplates = googleTemplates.filter( (template): template is Extract => selectedGoogleTemplateIds.includes(template.id), ); const payload: ConnectSourceBatchPayload = { workspaceOauthClientId: workspaceOauthClientId as WorkspaceOauthClient["id"], sources: selectedTemplates.map((template) => ({ service: template.service, version: template.version, discoveryUrl: template.discoveryUrl, name: template.name, namespace: googleDiscoveryNamespace(template.service), })), }; setBatchConnecting(true); setPhase("connecting"); const result = await connectSourceBatch.mutateAsync(payload); if (result.providerOauthSession) { setBatchOauthInfo({ sessionId: result.providerOauthSession.sessionId, authorizationUrl: result.providerOauthSession.authorizationUrl, sourceIds: result.providerOauthSession.sourceIds, }); setOauthInfo(null); setBatchConnecting(false); setPhase("oauth_required"); setStatusBanner(null); return; } setSelectedGoogleTemplateIds([]); setBatchOauthInfo(null); setBatchConnecting(false); setPhase("connected"); setStatusBanner({ tone: "success", text: `Connected ${result.results.length} Google source${result.results.length === 1 ? "" : "s"}.`, }); setTimeout(() => { void navigate({ to: "/" }); }, 1200); } catch (error) { setBatchConnecting(false); setPhase("idle"); setStatusBanner({ tone: "error", text: error instanceof Error ? error.message : "Batch connection failed.", }); } }; const handleCredentialConnect = async () => { // Re-run connect with the auth now configured await handleConnect(); }; const handleBackToEditing = () => { setPhase("editing"); setStatusBanner(null); setConnectResult(null); setOauthInfo(null); }; const isDiscovering = phase === "discovering"; const isConnecting = phase === "connecting"; const isBusy = isDiscovering || isConnecting || oauthBusy || connectSourceBatch.status === "pending" || createWorkspaceOauthClient.status === "pending"; // ------------------------------------------------------------------------- // Render // ------------------------------------------------------------------------- return (
{/* Back */} Back

Add source

{phase === "editing" && ( {connectForm.kind} )}
{/* Step indicator */} {phase !== "idle" && phase !== "discovering" && (
{(["discover", "configure", "connect"] as const).map((step, i) => { const stepPhases: Record = { editing: 1, connecting: 2, credential_required: 1, oauth_required: 2, connected: 2, }; const currentStep = stepPhases[phase] ?? 0; const isComplete = i < currentStep; const isCurrent = i === currentStep; return (
{i > 0 && (
)}
{isComplete ? ( ) : ( {i + 1} )} {step}
); })}
)} {statusBanner && } {(phase === "idle" || phase === "discovering") && ( )} {/* Step 1: Discovery */} {(phase === "idle" || phase === "discovering") && (

Enter a URL and we'll auto-detect the source type, endpoint, auth requirements, and more.

setUrl(e.target.value)} placeholder="https://api.example.com or https://mcp.example.com/mcp" onKeyDown={(e) => { if (e.key === "Enter" && !isDiscovering) { void handleDiscover(); } }} className="h-9 flex-1 rounded-lg border border-input bg-background px-3 font-mono text-[12px] text-foreground outline-none transition-colors placeholder:text-muted-foreground/35 focus:border-ring focus:ring-1 focus:ring-ring/25" />
{isDiscovering && (
)}
{/* Probe auth toggle */}
{showProbeAuth && (
setProbeField("kind", v as ProbeAuthKind)} options={probeAuthOptions.map((v) => ({ value: v, label: v }))} /> {probeAuth.kind === "bearer" && (
setProbeField("headerName", v)} placeholder="Authorization" /> setProbeField("prefix", v)} placeholder="Bearer " /> setProbeField("token", v)} placeholder="sk-..." mono />
)} {probeAuth.kind === "basic" && (
setProbeField("username", v)} placeholder="user" /> setProbeField("password", v)} placeholder="pass" />
)} {probeAuth.kind === "headers" && ( setProbeField("headersText", v)} placeholder={'{\n "x-api-key": "..."\n}'} /> )}
)} {/* Skip discovery link */}
{/* ---- Template catalogue ---- */}

Start from a template

Pick a known source to skip discovery, or select multiple Google APIs for batch connect.

{/* -- Popular / non-Google templates -- */} {(() => { const popularTemplates = sourceTemplates.filter((t) => t.groupId !== "google_workspace"); return popularTemplates.length > 0 && (

Popular

{popularTemplates.map((template) => ( ))}
); })()} {/* -- Google Workspace batch section -- */}

Google Workspace

{googleTemplates.length > 0 && (
{selectedGoogleTemplateIds.length > 0 && ( )}
)}
{googleTemplates.map((template) => { const selected = selectedGoogleTemplateIds.includes(template.id); return ( ); })}
{/* -- Batch connect panel (slides in when templates selected) -- */} {selectedGoogleTemplateIds.length > 0 && (
{/* Header strip */}
{selectedGoogleTemplateIds.length}

Connect Google API{selectedGoogleTemplateIds.length === 1 ? "" : "s"}

One consent screen for all

{/* Body */}
{connectForm.workspaceOauthClientId.trim().length === 0 && (
setFormField("oauthClientId", v)} placeholder="1234567890-abcdef.apps.googleusercontent.com" /> setFormField("oauthClientSecret", v)} placeholder="GOCSPX-..." />
)}
)}
)} {/* Batch connecting interstitial */} {batchConnecting && phase === "connecting" && (

Connecting {selectedGoogleTemplateIds.length} source{selectedGoogleTemplateIds.length === 1 ? "" : "s"}

Setting up Google Workspace APIs\u2026

)} {/* Step 2-4: Editing / Connecting */} {(phase === "editing" || (phase === "connecting" && !batchConnecting) || phase === "credential_required") && (
setFormField("name", v)} placeholder="My API" /> setSourceKind(v as ConnectFormState["kind"])} options={kindOptions.map((v) => ({ value: v, label: v }))} /> {connectForm.kind !== "google_discovery" && ( setFormField("endpoint", v)} placeholder="https://api.example.com" mono /> )} setFormField("namespace", v)} placeholder="example" /> {connectForm.kind === "openapi" && ( setFormField("specUrl", v)} placeholder="https://example.com/openapi.yaml" mono /> )} {connectForm.kind === "google_discovery" && ( <> setFormField("service", v)} placeholder="sheets" /> setFormField("version", v)} placeholder="v4" /> setFormField("discoveryUrl", v)} placeholder="https://www.googleapis.com/discovery/v1/apis/sheets/v4/rest" mono />
Common Google API versions: Gmail `v1`, Sheets `v4`, Drive `v3`, Calendar `v3`, Docs `v1`.
)}
{/* MCP Transport */} {connectForm.kind === "mcp" && (
setFormField("transport", v as ConnectFormState["transport"])} options={transportOptions.map((v) => ({ value: v, label: v }))} />
setFormField("queryParamsText", v)} placeholder={'{\n "workspace": "demo"\n}'} /> setFormField("headersText", v)} placeholder={'{\n "x-api-key": "..."\n}'} />
)} {/* Auth section (for non-MCP kinds, or when credential_required) */} {phase === "credential_required" && (

Credentials required

The server responded that this source requires authentication. Add a token or secret below, then try connecting again.

)} {connectForm.kind !== "mcp" && (
setFormField("authKind", v as ConnectFormState["authKind"])} options={authOptions.map((v) => ({ value: v, label: v }))} /> {connectForm.kind === "google_discovery" && (
{connectForm.authKind === "oauth2" ? "OAuth starts after you click Connect. Choose an existing workspace OAuth client or enter a new client once, then reuse it across Google sources." : "Choosing auth mode 'none' skips the Google OAuth popup. Use 'oauth2' if you want executor to start the sign-in flow for you."}
)} {connectForm.kind === "google_discovery" && connectForm.authKind === "oauth2" && ( <> setFormField("workspaceOauthClientId", value)} options={[ { value: "", label: "Enter new client" }, ...(workspaceOauthClients.status === "ready" ? workspaceOauthClients.data.map((client) => ({ value: client.id, label: client.label ?? client.clientId, })) : []), ]} /> {connectForm.workspaceOauthClientId.trim().length === 0 && ( <> setFormField("oauthClientId", v)} placeholder="1234567890-abcdef.apps.googleusercontent.com" /> setFormField("oauthClientSecret", v)} placeholder="GOCSPX-..." /> )} )} {connectForm.authKind !== "none" && ( <> setFormField("authHeaderName", v)} placeholder="Authorization" /> setFormField("authPrefix", v)} placeholder="Bearer " /> )} {connectForm.authKind === "bearer" && ( { setFormField("bearerProviderId", providerId); setFormField("bearerHandle", handle); setFormField("bearerToken", ""); }} onChangeToken={(token) => { setFormField("bearerToken", token); setFormField("bearerProviderId", ""); setFormField("bearerHandle", ""); }} /> )}
)} {/* Actions */}
{phase === "credential_required" && ( )}
)} {/* Step 5a: OAuth required */} {phase === "oauth_required" && (oauthInfo || batchOauthInfo) && (
{/* Visual anchor */}
{oauthBusy ? ( ) : ( )}

{oauthBusy ? "Waiting for sign-in\u2026" : "Sign in to continue"}

{oauthInfo ? <> {oauthInfo.source.name} requires OAuth authentication. A popup will open for you to authorize access. : <> {batchOauthInfo!.sourceIds.length} Google source{batchOauthInfo!.sourceIds.length === 1 ? "" : "s"} need{batchOauthInfo!.sourceIds.length === 1 ? "s" : ""} OAuth. One consent screen covers all selected APIs. }

{!oauthBusy && ( )}
)} {/* Step 5b: Connected */} {phase === "connected" && (

{connectResult ? connectResult.source.name : batchOauthInfo ? `${batchOauthInfo.sourceIds.length} source${batchOauthInfo.sourceIds.length === 1 ? "" : "s"}` : "Source"} {" "}connected

Redirecting\u2026

)}
); } // --------------------------------------------------------------------------- // Form building blocks // --------------------------------------------------------------------------- function Section(props: { title: string; children: ReactNode; className?: string }) { return (

{props.title}

{props.children}
); } function Field(props: { label: string; className?: string; children: ReactNode; }) { return ( ); } function TextInput(props: { type?: "text" | "password"; value: string; onChange: (value: string) => void; placeholder?: string; mono?: boolean; }) { return ( props.onChange(event.target.value)} placeholder={props.placeholder} className={cn( "h-9 w-full rounded-lg border border-input bg-background px-3 text-[13px] text-foreground outline-none transition-colors placeholder:text-muted-foreground/35 focus:border-ring focus:ring-1 focus:ring-ring/25", props.mono && "font-mono text-[12px]", )} /> ); } function SelectInput(props: { value: string; onChange: (value: string) => void; options: ReadonlyArray<{ value: string; label: string }>; }) { return ( ); } function CodeEditor(props: { value: string; onChange: (value: string) => void; placeholder?: string; }) { return (