import type { APIRoute } from "astro"; import { Effect } from "effect"; import { createExecutor, makeTestConfig, type Tool } from "@executor-js/sdk"; import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api"; import { graphqlHttpPlugin } from "@executor-js/plugin-graphql/api"; import { googleDiscoveryHttpPlugin } from "@executor-js/plugin-google-discovery/api"; export const prerender = false; function inferMethod(toolName: string, pluginKey: string): string { if (pluginKey === "graphql") { return toolName.startsWith("mutation.") ? "mutation" : "query"; } if (pluginKey === "mcp") return "tool"; // OpenAPI / Google Discovery: infer from tool name const lower = toolName.toLowerCase(); if (/\.delete|\.remove|\.destroy/.test(lower)) return "DELETE"; if (/\.create|\.insert|\.add|\.send|\.post/.test(lower)) return "POST"; if (/\.update|\.patch/.test(lower)) return "PATCH"; if (/\.put|\.merge|\.replace/.test(lower)) return "PUT"; return "GET"; } function inferPolicy(method: string, toolName: string): "read" | "write" | "destructive" { const m = method.toUpperCase(); if (m === "DELETE") return "destructive"; const lower = toolName.toLowerCase(); if (/delete|remove|destroy|drop|purge/.test(lower)) return "destructive"; if (m === "GET" || m === "HEAD" || m === "QUERY") return "read"; if (/list|get|query|check|read|search|find|fetch/.test(lower)) return "read"; return "write"; } function formatTools(tools: readonly Tool[]) { return tools.map((t) => { const method = inferMethod(t.name, t.pluginId); return { name: t.name, desc: t.description?.slice(0, 80) || t.name, method, policy: inferPolicy(method, t.name), }; }); } export const POST: APIRoute = async ({ request }) => { // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: Astro route converts request/parsing failures to a stable HTTP response try { const body = (await request.json()) as { url?: string }; const url = body.url?.trim(); if (!url) { return new Response(JSON.stringify({ error: "URL is required" }), { status: 400, headers: { "Content-Type": "application/json" }, }); } // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: URL constructor is the platform validator for request input try { new URL(url); } catch { return new Response(JSON.stringify({ error: "Invalid URL" }), { status: 400, headers: { "Content-Type": "application/json" }, }); } const program = Effect.gen(function* () { const config = makeTestConfig({ plugins: [openApiHttpPlugin(), graphqlHttpPlugin(), googleDiscoveryHttpPlugin()], }); const executor = yield* createExecutor(config); // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: ensure executor cleanup runs after best-effort marketing detection try { // Detect what kind of source lives at this URL const detected = yield* executor.sources.detect(url).pipe(Effect.timeout("10 seconds")); if (!detected || detected.length === 0) return null; const match = detected[0]; // Add source to register its tools (Google Discovery needs auth so skip) if (match.kind === "openapi") { yield* executor.openapi.addSpec({ spec: match.endpoint, namespace: match.namespace, scope: "test-scope", }); } else if (match.kind === "graphql") { yield* executor.graphql.addSource({ endpoint: match.endpoint, namespace: match.namespace, scope: "test-scope", }); } else { // For kinds we can't fully add (e.g. Google Discovery needs auth), // return just the detection metadata return { kind: match.kind, name: match.name, count: 0, tools: [], }; } const tools = yield* executor.tools.list({ sourceId: match.namespace, }); const mapped = formatTools(tools); return { kind: match.kind, name: match.name, count: mapped.length, tools: mapped.slice(0, 50), }; } finally { yield* executor.close(); } }); const result = await Effect.runPromise( program.pipe( Effect.catchCause(() => Effect.succeed(null)), Effect.timeout("25 seconds"), Effect.catchCause(() => Effect.succeed(null)), ), ); if (!result) { return new Response( JSON.stringify({ error: "Could not detect an API at this URL. Try an OpenAPI spec, GraphQL endpoint, or Google Discovery document.", }), { status: 404, headers: { "Content-Type": "application/json" } }, ); } return new Response(JSON.stringify(result), { status: 200, headers: { "Content-Type": "application/json" }, }); } catch { return new Response(JSON.stringify({ error: "Detection failed" }), { status: 500, headers: { "Content-Type": "application/json" }, }); } };