import type { Source, SourceInspectionToolDetail, } from "@executor/react"; import { Result, SecretReferenceField, defineExecutorPluginHttpApiClient, useAtomValue, useAtomSet, useExecutorMutation, useSource, useWorkspaceRequestContext, } from "@executor/react"; import { Alert, Button, DocumentPanel, IconPencil, Input, Label, Select, SourceToolExplorer, parseSourceToolExplorerSearch, type SourceToolExplorerSearch, useSourcePluginNavigation, useSourcePluginRouteParams, useSourcePluginSearch, } from "@executor/react/plugins"; import { openApiHttpApiExtension, } from "@executor/plugin-openapi-http"; import type { OpenApiConnectInput, OpenApiPreviewRequest, OpenApiPreviewSecurityScheme, OpenApiPreviewResponse, OpenApiSourceConfigPayload, } from "@executor/plugin-openapi-shared"; import { startTransition, useEffect, useState, type ReactNode } from "react"; type RouteToolSearch = SourceToolExplorerSearch; const defaultOpenApiInput = (): OpenApiConnectInput => ({ name: "My OpenAPI Source", specUrl: "https://example.com/openapi.json", baseUrl: null, auth: { kind: "none", }, }); const DEFAULT_BEARER_HEADER_NAME = "Authorization"; const DEFAULT_BEARER_PREFIX = "Bearer "; const presetString = ( search: Record, key: string, ): string | null => { const value = search[key]; return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; }; const firstSearchString = ( search: Record, keys: ReadonlyArray, ): string | null => { for (const key of keys) { const value = presetString(search, key); if (value) { return value; } } return null; }; const looksLikeOpenApiSpecUrl = (value: string): boolean => { const lower = value.trim().toLowerCase(); if (!/^https?:\/\//.test(lower)) { return false; } return ( lower.endsWith(".json") || lower.endsWith(".yaml") || lower.endsWith(".yml") || lower.includes("/openapi") || lower.includes("openapi.") || lower.includes("/swagger") || lower.includes("swagger.") || lower.includes("/api-docs") ); }; const inferOpenApiUrls = (value: string): { specUrl: string; baseUrl: string | null; } => { const trimmed = value.trim(); if (!trimmed) { return { specUrl: defaultOpenApiInput().specUrl, baseUrl: defaultOpenApiInput().baseUrl, }; } if (looksLikeOpenApiSpecUrl(trimmed)) { return { specUrl: trimmed, baseUrl: null, }; } const normalizedBaseUrl = trimmed.replace(/\/+$/, ""); return { specUrl: `${normalizedBaseUrl}/openapi.json`, baseUrl: normalizedBaseUrl, }; }; const openApiInputFromSearch = ( search: Record, ): OpenApiConnectInput => { const defaults = defaultOpenApiInput(); const genericUrl = firstSearchString(search, ["inputUrl", "pastedUrl", "url"]); const inferred = genericUrl ? inferOpenApiUrls(genericUrl) : defaults; return { ...defaults, name: presetString(search, "presetName") ?? defaults.name, specUrl: presetString(search, "presetSpecUrl") ?? inferred.specUrl, baseUrl: presetString(search, "presetBaseUrl") ?? inferred.baseUrl, }; }; const getOpenApiHttpClient = defineExecutorPluginHttpApiClient<"OpenApiReactHttpClient">()( "OpenApiReactHttpClient", [openApiHttpApiExtension] as const, ); const Section = (props: { title: string; children: ReactNode; }) => (

{props.title}

{props.children}
); const isPreviewableSpecUrl = (value: string): boolean => { const trimmed = value.trim(); if (!/^https?:\/\//i.test(trimmed)) { return false; } try { const url = new URL(trimmed); return url.hostname !== "example.com"; } catch { return false; } }; const previewSecuritySchemeLabel = (scheme: OpenApiPreviewSecurityScheme): string => { if (scheme.kind === "apiKey") { return scheme.placement ? `API key in ${scheme.placement}` : "API key"; } if (scheme.kind === "http") { return scheme.scheme ? `HTTP ${scheme.scheme}` : "HTTP auth"; } if (scheme.kind === "oauth2") { return "OAuth 2.0"; } if (scheme.kind === "openIdConnect") { return "OpenID Connect"; } return "Custom auth"; }; const inputFromConfig = ( config: OpenApiSourceConfigPayload, ): OpenApiConnectInput => ({ name: config.name, specUrl: config.specUrl, baseUrl: config.baseUrl, auth: config.auth, }); const secretValue = (input: OpenApiConnectInput["auth"]): string => input.kind === "bearer" ? JSON.stringify(input.tokenSecretRef) : ""; const bearerHeaderNameValue = (input: OpenApiConnectInput["auth"]): string => input.kind === "bearer" ? input.headerName?.trim() || DEFAULT_BEARER_HEADER_NAME : DEFAULT_BEARER_HEADER_NAME; const bearerPrefixValue = (input: OpenApiConnectInput["auth"]): string => input.kind === "bearer" ? input.prefix ?? DEFAULT_BEARER_PREFIX : DEFAULT_BEARER_PREFIX; const trimToNull = (value: string): string | null => { const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; }; const authFromSecretValue = ( authKind: OpenApiConnectInput["auth"]["kind"], value: string, headerName: string, prefix: string, ): OpenApiConnectInput["auth"] => { if (authKind === "none") { return { kind: "none", }; } if (!value) { throw new Error("Select a secret for bearer auth."); } return { kind: "bearer", tokenSecretRef: JSON.parse(value) as OpenApiConnectInput["auth"] & { tokenSecretRef: never }["tokenSecretRef"], headerName: trimToNull(headerName), prefix: prefix === DEFAULT_BEARER_PREFIX ? null : prefix, }; }; function OpenApiSourceForm(props: { mode: "create" | "edit"; workspaceId: Source["scopeId"]; initialValue: OpenApiConnectInput; submitLabel: string; busyLabel: string; onSubmit: (input: OpenApiConnectInput) => Promise; }) { const openApiHttpClient = getOpenApiHttpClient(); const previewDocument = useAtomSet( openApiHttpClient.mutation("openapi", "previewDocument"), { mode: "promise" }, ); const [name, setName] = useState(props.initialValue.name); const [specUrl, setSpecUrl] = useState(props.initialValue.specUrl); const [baseUrl, setBaseUrl] = useState(props.initialValue.baseUrl ?? ""); const [authKind, setAuthKind] = useState( props.initialValue.auth.kind, ); const [tokenSecretRef, setTokenSecretRef] = useState( secretValue(props.initialValue.auth), ); const [authHeaderName, setAuthHeaderName] = useState( bearerHeaderNameValue(props.initialValue.auth), ); const [authPrefix, setAuthPrefix] = useState( bearerPrefixValue(props.initialValue.auth), ); const [error, setError] = useState(null); const [preview, setPreview] = useState(null); const [nameEdited, setNameEdited] = useState(false); const [baseUrlEdited, setBaseUrlEdited] = useState(false); const [lastPreviewedSpecUrl, setLastPreviewedSpecUrl] = useState(null); const previewMutation = useExecutorMutation< OpenApiPreviewRequest, OpenApiPreviewResponse >(async (payload) => previewDocument({ path: { workspaceId: props.workspaceId }, payload, }) ); const submitMutation = useExecutorMutation(props.onSubmit); const runPreview = async (input: { mode: "auto" | "manual"; }) => { const trimmedSpecUrl = specUrl.trim(); if (!trimmedSpecUrl) { if (input.mode === "manual") { setError("Spec URL is required."); } setPreview(null); setLastPreviewedSpecUrl(null); return; } if (!isPreviewableSpecUrl(trimmedSpecUrl)) { return; } try { const result = await previewMutation.mutateAsync({ specUrl: trimmedSpecUrl, }); setPreview({ ...result, warnings: [...result.warnings], securitySchemes: [...result.securitySchemes], }); setLastPreviewedSpecUrl(trimmedSpecUrl); if (error) { setError(null); } if (!nameEdited && result.title) { setName(result.title); } if (!baseUrlEdited && result.baseUrl) { setBaseUrl(result.baseUrl); } } catch (cause) { if (input.mode === "manual") { setError(cause instanceof Error ? cause.message : "Failed previewing document."); } setPreview(null); } }; useEffect(() => { const trimmedSpecUrl = specUrl.trim(); if (!isPreviewableSpecUrl(trimmedSpecUrl)) { return; } if (trimmedSpecUrl === lastPreviewedSpecUrl) { return; } const timeout = window.setTimeout(() => { void runPreview({ mode: "auto" }); }, 450); return () => { window.clearTimeout(timeout); }; }, [specUrl, lastPreviewedSpecUrl]); const handleSubmit = async () => { setError(null); const trimmedName = name.trim(); const trimmedSpecUrl = specUrl.trim(); const trimmedBaseUrl = baseUrl.trim(); if (!trimmedName) { setError("Name is required."); return; } if (!trimmedSpecUrl) { setError("Spec URL is required."); return; } if (authKind === "bearer" && tokenSecretRef.trim().length === 0) { setError("Select a secret for bearer auth."); return; } try { const auth = authFromSecretValue( authKind, tokenSecretRef, authHeaderName, authPrefix, ); await submitMutation.mutateAsync({ name: trimmedName, specUrl: trimmedSpecUrl, baseUrl: trimmedBaseUrl || null, auth, }); } catch (cause) { setError(cause instanceof Error ? cause.message : "Failed saving source."); } }; return (
{ setNameEdited(true); setName(event.target.value); }} placeholder="GitHub REST" />
setSpecUrl(event.target.value)} onBlur={() => { void runPreview({ mode: "manual" }); }} placeholder="https://example.com/openapi.json" className="font-mono text-xs" />
{ setBaseUrlEdited(true); setBaseUrl(event.target.value); }} placeholder="https://api.example.com" className="font-mono text-xs" />
{authKind === "bearer" && (
setAuthHeaderName(event.target.value)} placeholder={DEFAULT_BEARER_HEADER_NAME} className="font-mono text-xs" />
setAuthPrefix(event.target.value)} placeholder={DEFAULT_BEARER_PREFIX} className="font-mono text-xs" />
)}
{(preview || previewMutation.status === "pending") && (
{previewMutation.status === "pending" ? (
Loading preview...
) : preview && (
Title
{preview.title ?? "—"}
Version
{preview.version ?? "—"}
Operations
{preview.operationCount}
Base URL
{preview.baseUrl ?? "—"}
{preview.namespace && ( <>
Namespace
{preview.namespace}
)} {preview.securitySchemes.length > 0 && ( <>
Auth
{preview.securitySchemes .map((scheme) => `${scheme.name} (${previewSecuritySchemeLabel(scheme)})`) .join(", ")}
)}
{preview.warnings.length > 0 && ( {preview.warnings.join(" ")} )}
)}
)} {error && ( {error} )}
); } export function OpenApiAddSourcePage() { const navigation = useSourcePluginNavigation(); const initialValue = openApiInputFromSearch(useSourcePluginSearch()); const openApiHttpClient = getOpenApiHttpClient(); const workspace = useWorkspaceRequestContext(); const createSource = useAtomSet( openApiHttpClient.mutation("openapi", "createSource"), { mode: "promise" }, ); if (!workspace.enabled) { return (
Loading workspace...
); } return (

Add OpenAPI Source

Import operations from an OpenAPI specification as callable tools.

{ const source = await createSource({ path: { workspaceId: workspace.workspaceId }, payload, reactivityKeys: { sources: [workspace.workspaceId], }, }); startTransition(() => { void navigation.detail(source.id, { tab: "model", }); }); }} />
); } export function OpenApiEditSourcePage(props: { source: Source; }) { const workspace = useWorkspaceRequestContext(); if (!workspace.enabled) { return (
Loading workspace...
); } return ( ); } function OpenApiEditSourcePageReady(props: { source: Source; workspaceId: Source["scopeId"]; }) { const navigation = useSourcePluginNavigation(); const openApiHttpClient = getOpenApiHttpClient(); const configResult = useAtomValue( openApiHttpClient.query("openapi", "getSourceConfig", { path: { workspaceId: props.workspaceId, sourceId: props.source.id, }, reactivityKeys: { source: [props.workspaceId, props.source.id], }, timeToLive: "30 seconds", }), ); const updateSource = useAtomSet( openApiHttpClient.mutation("openapi", "updateSource"), { mode: "promise" }, ); if (!Result.isSuccess(configResult)) { return (
{Result.isFailure(configResult) ? ( Failed loading source configuration. ) : (
Loading configuration...
)}
); } return (

Edit OpenAPI Source

Update the connection settings for {props.source.name}.

{ const source = await updateSource({ path: { workspaceId: props.workspaceId, sourceId: props.source.id, }, payload: config, reactivityKeys: { sources: [props.workspaceId], source: [props.workspaceId, props.source.id], sourceInspection: [props.workspaceId, props.source.id], sourceInspectionTool: [props.workspaceId, props.source.id], sourceDiscovery: [props.workspaceId, props.source.id], }, }); startTransition(() => { void navigation.detail(source.id, { tab: "model", }); }); }} />
); } export function OpenApiSourceDetailPage(props: { source: Source; }) { const workspace = useWorkspaceRequestContext(); if (!workspace.enabled) { return (
Loading workspace...
); } return ( ); } function OpenApiSourceDetailPageReady(props: { source: Source; workspaceId: Source["scopeId"]; }) { const navigation = useSourcePluginNavigation(); const openApiHttpClient = getOpenApiHttpClient(); const removeSource = useAtomSet( openApiHttpClient.mutation("openapi", "removeSource"), { mode: "promise" }, ); const refreshSource = useAtomSet( openApiHttpClient.mutation("openapi", "refreshSource"), { mode: "promise" }, ); const refreshMutation = useExecutorMutation(async (sourceId) => refreshSource({ path: { workspaceId: props.workspaceId, sourceId, }, reactivityKeys: { sources: [props.workspaceId], source: [props.workspaceId, sourceId], sourceInspection: [props.workspaceId, sourceId], sourceInspectionTool: [props.workspaceId, sourceId], sourceDiscovery: [props.workspaceId, sourceId], }, }) ); const removeMutation = useExecutorMutation(async (sourceId) => removeSource({ path: { workspaceId: props.workspaceId, sourceId, }, reactivityKeys: { sources: [props.workspaceId], source: [props.workspaceId, sourceId], sourceInspection: [props.workspaceId, sourceId], sourceInspectionTool: [props.workspaceId, sourceId], sourceDiscovery: [props.workspaceId, sourceId], }, }) ); const search = parseSourceToolExplorerSearch( useSourcePluginSearch(), ) satisfies RouteToolSearch; const tab = search.tab === "discover" ? "discover" : "model"; const query = search.query ?? ""; const [confirmDelete, setConfirmDelete] = useState(false); return ( navigation.updateSearch({ tab: next.tab ?? tab, ...(next.tool !== undefined ? { tool: next.tool || undefined } : { tool: search.tool }), ...(next.query !== undefined ? { query: next.query || undefined } : { query }), })} actions={( <> {confirmDelete ? (
Confirm delete?
) : ( )} )} renderHeaderMeta={renderOpenApiToolHeaderMeta} renderSchemaExtras={renderOpenApiToolSchemaExtras} /> ); } const renderOpenApiToolHeaderMeta = (detail: SourceInspectionToolDetail) => detail.summary.pathTemplate ? ( {detail.summary.pathTemplate} ) : null; const renderOpenApiToolSchemaExtras = (detail: SourceInspectionToolDetail) => ( <> {detail.contract.input.exampleJson && ( )} {detail.contract.output.exampleJson && ( )} ); function OpenApiSourceRoute(props: { children: (source: Source) => ReactNode; }) { const params = useSourcePluginRouteParams<{ sourceId?: string }>(); const sourceId = typeof params.sourceId === "string" ? params.sourceId : null; const source = useSource(sourceId ?? ""); if (sourceId === null || source.status === "error") { return (
This OpenAPI source is unavailable.
); } if (source.status === "loading") { return (
Loading source...
); } if (source.data.kind !== "openapi") { return (
Expected an `openapi` source, but received `{source.data.kind}`.
); } return props.children(source.data); } export function OpenApiDetailRoute() { return ( {(source) => } ); } export function OpenApiEditRoute() { return ( {(source) => } ); }