import { applyCookiePlacementsToHeaders, applyHttpQueryPlacementsToUrl, applyJsonBodyPlacements,} from "@executor/codemode-core";import { serializeOpenApiParameterValue, serializeOpenApiRequestBody, withSerializedQueryEntries,} from "@executor/codemode-openapi";import { createMcpToolsFromManifest, createSdkMcpConnector,} from "@executor/codemode-mcp";import type { AccountId, Source } from "#schema";import * as Effect from "effect/Effect"; import type { OnElicitation, ToolExecutionContext, ToolInput, ToolPath,} from "@executor/codemode-core";import { buildInvocationPlan, type InvocationPlan } from "../ir/catalog";import type { CatalogV1, Capability, Executable, GraphQLExecutable, HttpExecutable, McpExecutable, ParameterSymbol, Scope,} from "../ir/model";import type { LoadedSourceCatalogToolIndexEntry } from "./source-catalog-runtime";import type { ResolvedSourceAuthMaterial } from "./source-auth-material"; const asObject = (value: unknown): Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : {}; const asString = (value: unknown): string | null => typeof value === "string" && value.trim().length > 0 ? value : null; const asStringRecord = (value: unknown): Record<string, string> => Object.fromEntries( Object.entries(asObject(value)).flatMap(([key, entry]) => typeof entry === "string" ? [[key, entry]] : []), ); const readSourceHeaders = (source: Source): Record<string, string> => { const binding = asObject(source.binding); const defaultHeaders = binding.defaultHeaders ?? binding.headers; return asStringRecord(defaultHeaders);}; const readSourceQueryParams = (source: Source): Record<string, string> => asStringRecord(asObject(source.binding).queryParams); const readSourceTransport = (source: Source): "auto" | "streamable-http" | "sse" | undefined => { const transport = asString(asObject(source.binding).transport); if (transport === "streamable-http" || transport === "sse" || transport === "auto") { return transport; } return undefined;}; type ExecutionEnvelope = { data: unknown; error: unknown; headers: Record<string, string>; status: number | null;}; type HttpInvocationRequestPlan = { protocol: "http"; method: HttpExecutable["method"]; url: string; headers: Record<string, string>; body?: string;}; type GraphqlInvocationRequestPlan = { protocol: "graphql"; endpoint: string; headers: Record<string, string>; query: string; variables?: Record<string, unknown>; operationName?: string; responseRootField?: string;}; type McpInvocationRequestPlan = { protocol: "mcp"; endpoint: string; transport?: "auto" | "streamable-http" | "sse"; queryParams: Record<string, string>; headers: Record<string, string>; authProvider?: ResolvedSourceAuthMaterial["authProvider"]; toolName: string; payload: unknown; inputSchema: unknown; outputSchema: unknown; toolTitle?: string; toolSummary?: string;}; type IrInvocationRequestPlan = | HttpInvocationRequestPlan | GraphqlInvocationRequestPlan | McpInvocationRequestPlan; const decodeFetchBody = async (response: Response): Promise<unknown> => { const contentType = response.headers.get("content-type")?.toLowerCase() ?? ""; if (contentType.includes("application/json")) { return response.json(); } if (response.status === 204) { return null; } return response.text();}; const responseHeadersRecord = (response: Response): Record<string, string> => { const headers: Record<string, string> = {}; response.headers.forEach((value, key) => { headers[key] = value; }); return headers;}; const executionEnvelope = (input: ExecutionEnvelope): ExecutionEnvelope => input; const resolvedAuthSummary = (auth: ResolvedSourceAuthMaterial) => ({ placementCount: auth.placements.length, headerKeys: Object.keys(auth.headers).sort(), queryParamKeys: Object.keys(auth.queryParams).sort(), cookieKeys: Object.keys(auth.cookies).sort(), bodyValueKeys: Object.keys(auth.bodyValues).sort(), expiresAt: auth.expiresAt, refreshAfter: auth.refreshAfter,}); const parameterById = (catalog: CatalogV1, parameterId: string): ParameterSymbol | undefined => { const symbol = catalog.symbols[parameterId]; return symbol?.kind === "parameter" ? symbol : undefined;}; const httpParameterLookupMode = (catalog: CatalogV1, executable: HttpExecutable): "flat" | "grouped" => { const reserved = new Set(["body", "args", "select", "path", "query", "headers", "cookies"]); const allParameters = [ ...(executable.pathParameterIds ?? []), ...(executable.queryParameterIds ?? []), ...(executable.headerParameterIds ?? []), ...(executable.cookieParameterIds ?? []), ] .map((parameterId) => parameterById(catalog, parameterId)) .filter((parameter): parameter is ParameterSymbol => parameter !== undefined); const names = allParameters.map((parameter) => parameter.name); const hasCollision = names.some((name, index) => reserved.has(name) || names.findIndex((candidate) => candidate === name) !== index ); return hasCollision ? "grouped" : "flat";}; const readHttpParameterValue = (input: { catalog: CatalogV1; executable: HttpExecutable; parameter: ParameterSymbol; args: Record<string, unknown>;}): unknown => { const mode = httpParameterLookupMode(input.catalog, input.executable); if (mode === "flat") { return input.args[input.parameter.name]; } const groupKey = input.parameter.location === "header" ? "headers" : input.parameter.location === "cookie" ? "cookies" : input.parameter.location; return asObject(input.args[groupKey])[input.parameter.name];}; const scopeDefaultServers = ( catalog: CatalogV1, scopeId: HttpExecutable["scopeId"],): Array<{ url: string; variables?: Record<string, string> }> => { let currentId: HttpExecutable["scopeId"] | undefined = scopeId; while (currentId) { const scope: Scope | undefined = catalog.scopes[currentId]; if (!scope) { break; } if (scope.defaults?.servers && scope.defaults.servers.length > 0) { return [...scope.defaults.servers]; } currentId = scope.parentId; } return [];}; const resolveScopedServerUrl = ( source: Source, server: { url: string; variables?: Record<string, string>; },): URL => { const expanded = Object.entries(server.variables ?? {}).reduce( (url, [name, value]) => url.replaceAll(`{${name}}`, value), server.url, ); return new URL(expanded, source.endpoint);}; const resolveHttpBaseUrl = (source: Source, catalog: CatalogV1, executable: HttpExecutable): URL => { const scopedServers = scopeDefaultServers(catalog, executable.scopeId); if (scopedServers.length > 0) { return resolveScopedServerUrl(source, scopedServers[0]!); } return new URL(source.endpoint);}; const resolveHttpRequestUrl = (baseUrl: URL, resolvedPath: string): URL => { try { return new URL(resolvedPath); } catch { const resolved = new URL(baseUrl.toString()); const basePath = resolved.pathname === "/" ? "" : resolved.pathname.endsWith("/") ? resolved.pathname.slice(0, -1) : resolved.pathname; const pathPart = resolvedPath.startsWith("/") ? resolvedPath : `/${resolvedPath}`; resolved.pathname = `${basePath}${pathPart}`.replace(/\/{2,}/g, "/"); resolved.search = ""; resolved.hash = ""; return resolved; }}; const planHttpInvocation = (input: { source: Source; catalog: CatalogV1; executable: HttpExecutable; auth: ResolvedSourceAuthMaterial; args: unknown;}): { resolvedEndpoint: string; requestPlan: HttpInvocationRequestPlan;} => { const argsRecord = asObject(input.args); let resolvedPath = input.executable.pathTemplate; const url = applyHttpQueryPlacementsToUrl({ url: resolveHttpBaseUrl(input.source, input.catalog, input.executable), queryParams: readSourceQueryParams(input.source), }); const headers: Record<string, string> = { ...readSourceHeaders(input.source), }; const queryEntries: Array<{ name: string; value: string; allowReserved?: boolean }> = []; const cookieParts: string[] = []; const allParameterIds = [ ...(input.executable.pathParameterIds ?? []), ...(input.executable.queryParameterIds ?? []), ...(input.executable.headerParameterIds ?? []), ...(input.executable.cookieParameterIds ?? []), ]; for (const parameterId of allParameterIds) { const parameter = parameterById(input.catalog, parameterId); if (!parameter) { continue; } const value = readHttpParameterValue({ catalog: input.catalog, executable: input.executable, parameter, args: argsRecord, }); if (value === undefined || value === null) { if (parameter.required) { throw new Error(`Missing required ${parameter.location} parameter ${parameter.name}`); } continue; } const serialized = serializeOpenApiParameterValue({ name: parameter.name, location: parameter.location, style: parameter.style, explode: parameter.explode, allowReserved: parameter.allowReserved, content: parameter.content?.map((content) => ({ mediaType: content.mediaType })), }, value); if (serialized.kind === "path") { resolvedPath = resolvedPath.replace( new RegExp(`{${parameter.name}}`, "g"), serialized.value, ); continue; } if (serialized.kind === "query") { queryEntries.push(...serialized.entries); continue; } if (serialized.kind === "header") { headers[parameter.name] = serialized.value; continue; } if (serialized.kind === "cookie") { cookieParts.push( ...serialized.pairs.map((pair) => `${pair.name}=${encodeURIComponent(pair.value)}`), ); } } const bodySymbol = input.executable.requestBodyId ? input.catalog.symbols[input.executable.requestBodyId] : undefined; let body: string | undefined; if (bodySymbol?.kind === "requestBody") { const bodyValue = argsRecord.body ?? argsRecord.input; if (bodyValue !== undefined) { const serializedBody = serializeOpenApiRequestBody({ requestBody: { contentTypes: bodySymbol.contents.map((content) => content.mediaType), contents: bodySymbol.contents.map((content) => ({ mediaType: content.mediaType })), }, body: applyJsonBodyPlacements({ body: bodyValue, bodyValues: input.auth.bodyValues, label: `${input.executable.method} ${input.executable.pathTemplate}`, }), }); headers["content-type"] = serializedBody.contentType; body = serializedBody.bodyText; } } const requestUrl = resolveHttpRequestUrl(url, resolvedPath); const urlWithAuth = applyHttpQueryPlacementsToUrl({ url: requestUrl, queryParams: input.auth.queryParams, }); const urlWithOpenApiQuery = withSerializedQueryEntries(urlWithAuth, queryEntries); if (cookieParts.length > 0) { headers.cookie = cookieParts.join("; "); } const headersWithCookies = applyCookiePlacementsToHeaders({ headers: { ...headers, ...input.auth.headers, }, cookies: { ...input.auth.cookies, }, }); return { resolvedEndpoint: urlWithOpenApiQuery.toString(), requestPlan: { protocol: "http", method: input.executable.method, url: urlWithOpenApiQuery.toString(), headers: headersWithCookies, ...(body !== undefined ? { body } : {}), }, };}; const executeHttpPlan = (requestPlan: HttpInvocationRequestPlan) => Effect.tryPromise({ try: async () => { const response = await fetch(requestPlan.url, { method: requestPlan.method, headers: requestPlan.headers, ...(requestPlan.body !== undefined ? { body: requestPlan.body } : {}), }); const responseBody = await decodeFetchBody(response); return executionEnvelope({ data: response.ok ? responseBody : null, error: response.ok ? null : responseBody, headers: responseHeadersRecord(response), status: response.status, }); }, catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)), }); const graphqlSelectionFromArgs = (value: unknown): string => { const record = asObject(value); const fields = Object.entries(record).flatMap(([key, entry]) => { if (entry === true) { return [key]; } if (entry && typeof entry === "object") { const child = graphqlSelectionFromArgs(entry); return child.length > 0 ? [`${key} { ${child} }`] : []; } return []; }); return fields.join(" ");}; const graphqlArgsPayload = (input: { catalog: CatalogV1; executable: GraphQLExecutable; args: Record<string, unknown>;}): Record<string, unknown> => { const argumentShape = input.catalog.symbols[input.executable.argumentShapeId]; const reserved = new Set(["body", "args", "select", "path", "query", "headers", "cookies"]); if ( argumentShape?.kind === "shape" && argumentShape.node.type === "object" && Object.keys(argumentShape.node.fields).every((name) => !reserved.has(name)) ) { return Object.fromEntries( Object.entries(input.args).filter(([key]) => key !== "select" && key !== "headers"), ); } return asObject(input.args.args);}; const planGraphqlInvocation = (input: { source: Source; catalog: CatalogV1; executable: GraphQLExecutable; auth: ResolvedSourceAuthMaterial; args: unknown;}): { resolvedEndpoint: string; requestPlan: GraphqlInvocationRequestPlan;} => { const argsRecord = asObject(input.args); const requestHeaders = applyCookiePlacementsToHeaders({ headers: { ...readSourceHeaders(input.source), ...input.auth.headers, ...asStringRecord(argsRecord.headers), "content-type": "application/json", }, cookies: input.auth.cookies, }); const queryParams = { ...readSourceQueryParams(input.source), ...input.auth.queryParams, }; const endpoint = applyHttpQueryPlacementsToUrl({ url: input.source.endpoint, queryParams, }).toString(); if ( input.executable.toolKind === "request" || typeof input.executable.operationDocument !== "string" || input.executable.operationDocument.trim().length === 0 ) { const query = asString(argsRecord.query); if (query === null) { throw new Error("GraphQL request tools require args.query"); } return { resolvedEndpoint: endpoint, requestPlan: { protocol: "graphql", endpoint, headers: requestHeaders, query, ...(argsRecord.variables ? { variables: asObject(argsRecord.variables) } : {}), ...(asString(argsRecord.operationName) ? { operationName: asString(argsRecord.operationName)! } : {}), }, }; } const operationDocument = input.executable.operationDocument; const operationName = input.executable.operationName; const variables = graphqlArgsPayload({ catalog: input.catalog,Lines 1–500 of 827. Load more
import { applyCookiePlacementsToHeaders, applyHttpQueryPlacementsToUrl, applyJsonBodyPlacements, } from "@executor/codemode-core"; import { serializeOpenApiParameterValue, serializeOpenApiRequestBody, withSerializedQueryEntries, } from "@executor/codemode-openapi"; import { createMcpToolsFromManifest, createSdkMcpConnector, } from "@executor/codemode-mcp"; import type { AccountId, Source } from "#schema"; import * as Effect from "effect/Effect"; import type { OnElicitation, ToolExecutionContext, ToolInput, ToolPath, } from "@executor/codemode-core"; import { buildInvocationPlan, type InvocationPlan } from "../ir/catalog"; import type { CatalogV1, Capability, Executable, GraphQLExecutable, HttpExecutable, McpExecutable, ParameterSymbol, Scope, } from "../ir/model"; import type { LoadedSourceCatalogToolIndexEntry } from "./source-catalog-runtime"; import type { ResolvedSourceAuthMaterial } from "./source-auth-material"; const asObject = (value: unknown): Record<string, unknown> => value !== null && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : {}; const asString = (value: unknown): string | null => typeof value === "string" && value.trim().length > 0 ? value : null; const asStringRecord = (value: unknown): Record<string, string> => Object.fromEntries( Object.entries(asObject(value)).flatMap(([key, entry]) => typeof entry === "string" ? [[key, entry]] : []), ); const readSourceHeaders = (source: Source): Record<string, string> => { const binding = asObject(source.binding); const defaultHeaders = binding.defaultHeaders ?? binding.headers; return asStringRecord(defaultHeaders); }; const readSourceQueryParams = (source: Source): Record<string, string> => asStringRecord(asObject(source.binding).queryParams); const readSourceTransport = (source: Source): "auto" | "streamable-http" | "sse" | undefined => { const transport = asString(asObject(source.binding).transport); if (transport === "streamable-http" || transport === "sse" || transport === "auto") { return transport; } return undefined; }; type ExecutionEnvelope = { data: unknown; error: unknown; headers: Record<string, string>; status: number | null; }; type HttpInvocationRequestPlan = { protocol: "http"; method: HttpExecutable["method"]; url: string; headers: Record<string, string>; body?: string; }; type GraphqlInvocationRequestPlan = { protocol: "graphql"; endpoint: string; headers: Record<string, string>; query: string; variables?: Record<string, unknown>; operationName?: string; responseRootField?: string; }; type McpInvocationRequestPlan = { protocol: "mcp"; endpoint: string; transport?: "auto" | "streamable-http" | "sse"; queryParams: Record<string, string>; headers: Record<string, string>; authProvider?: ResolvedSourceAuthMaterial["authProvider"]; toolName: string; payload: unknown; inputSchema: unknown; outputSchema: unknown; toolTitle?: string; toolSummary?: string; }; type IrInvocationRequestPlan = | HttpInvocationRequestPlan | GraphqlInvocationRequestPlan | McpInvocationRequestPlan; const decodeFetchBody = async (response: Response): Promise<unknown> => { const contentType = response.headers.get("content-type")?.toLowerCase() ?? ""; if (contentType.includes("application/json")) { return response.json(); } if (response.status === 204) { return null; } return response.text(); }; const responseHeadersRecord = (response: Response): Record<string, string> => { const headers: Record<string, string> = {}; response.headers.forEach((value, key) => { headers[key] = value; }); return headers; }; const executionEnvelope = (input: ExecutionEnvelope): ExecutionEnvelope => input; const resolvedAuthSummary = (auth: ResolvedSourceAuthMaterial) => ({ placementCount: auth.placements.length, headerKeys: Object.keys(auth.headers).sort(), queryParamKeys: Object.keys(auth.queryParams).sort(), cookieKeys: Object.keys(auth.cookies).sort(), bodyValueKeys: Object.keys(auth.bodyValues).sort(), expiresAt: auth.expiresAt, refreshAfter: auth.refreshAfter, }); const parameterById = (catalog: CatalogV1, parameterId: string): ParameterSymbol | undefined => { const symbol = catalog.symbols[parameterId]; return symbol?.kind === "parameter" ? symbol : undefined; }; const httpParameterLookupMode = (catalog: CatalogV1, executable: HttpExecutable): "flat" | "grouped" => { const reserved = new Set(["body", "args", "select", "path", "query", "headers", "cookies"]); const allParameters = [ ...(executable.pathParameterIds ?? []), ...(executable.queryParameterIds ?? []), ...(executable.headerParameterIds ?? []), ...(executable.cookieParameterIds ?? []), ] .map((parameterId) => parameterById(catalog, parameterId)) .filter((parameter): parameter is ParameterSymbol => parameter !== undefined); const names = allParameters.map((parameter) => parameter.name); const hasCollision = names.some((name, index) => reserved.has(name) || names.findIndex((candidate) => candidate === name) !== index ); return hasCollision ? "grouped" : "flat"; }; const readHttpParameterValue = (input: { catalog: CatalogV1; executable: HttpExecutable; parameter: ParameterSymbol; args: Record<string, unknown>; }): unknown => { const mode = httpParameterLookupMode(input.catalog, input.executable); if (mode === "flat") { return input.args[input.parameter.name]; } const groupKey = input.parameter.location === "header" ? "headers" : input.parameter.location === "cookie" ? "cookies" : input.parameter.location; return asObject(input.args[groupKey])[input.parameter.name]; }; const scopeDefaultServers = ( catalog: CatalogV1, scopeId: HttpExecutable["scopeId"], ): Array<{ url: string; variables?: Record<string, string> }> => { let currentId: HttpExecutable["scopeId"] | undefined = scopeId; while (currentId) { const scope: Scope | undefined = catalog.scopes[currentId]; if (!scope) { break; } if (scope.defaults?.servers && scope.defaults.servers.length > 0) { return [...scope.defaults.servers]; } currentId = scope.parentId; } return []; }; const resolveScopedServerUrl = ( source: Source, server: { url: string; variables?: Record<string, string>; }, ): URL => { const expanded = Object.entries(server.variables ?? {}).reduce( (url, [name, value]) => url.replaceAll(`{${name}}`, value), server.url, ); return new URL(expanded, source.endpoint); }; const resolveHttpBaseUrl = (source: Source, catalog: CatalogV1, executable: HttpExecutable): URL => { const scopedServers = scopeDefaultServers(catalog, executable.scopeId); if (scopedServers.length > 0) { return resolveScopedServerUrl(source, scopedServers[0]!); } return new URL(source.endpoint); }; const resolveHttpRequestUrl = (baseUrl: URL, resolvedPath: string): URL => { try { return new URL(resolvedPath); } catch { const resolved = new URL(baseUrl.toString()); const basePath = resolved.pathname === "/" ? "" : resolved.pathname.endsWith("/") ? resolved.pathname.slice(0, -1) : resolved.pathname; const pathPart = resolvedPath.startsWith("/") ? resolvedPath : `/${resolvedPath}`; resolved.pathname = `${basePath}${pathPart}`.replace(/\/{2,}/g, "/"); resolved.search = ""; resolved.hash = ""; return resolved; } }; const planHttpInvocation = (input: { source: Source; catalog: CatalogV1; executable: HttpExecutable; auth: ResolvedSourceAuthMaterial; args: unknown; }): { resolvedEndpoint: string; requestPlan: HttpInvocationRequestPlan; } => { const argsRecord = asObject(input.args); let resolvedPath = input.executable.pathTemplate; const url = applyHttpQueryPlacementsToUrl({ url: resolveHttpBaseUrl(input.source, input.catalog, input.executable), queryParams: readSourceQueryParams(input.source), }); const headers: Record<string, string> = { ...readSourceHeaders(input.source), }; const queryEntries: Array<{ name: string; value: string; allowReserved?: boolean }> = []; const cookieParts: string[] = []; const allParameterIds = [ ...(input.executable.pathParameterIds ?? []), ...(input.executable.queryParameterIds ?? []), ...(input.executable.headerParameterIds ?? []), ...(input.executable.cookieParameterIds ?? []), ]; for (const parameterId of allParameterIds) { const parameter = parameterById(input.catalog, parameterId); if (!parameter) { continue; } const value = readHttpParameterValue({ catalog: input.catalog, executable: input.executable, parameter, args: argsRecord, }); if (value === undefined || value === null) { if (parameter.required) { throw new Error(`Missing required ${parameter.location} parameter ${parameter.name}`); } continue; } const serialized = serializeOpenApiParameterValue({ name: parameter.name, location: parameter.location, style: parameter.style, explode: parameter.explode, allowReserved: parameter.allowReserved, content: parameter.content?.map((content) => ({ mediaType: content.mediaType })), }, value); if (serialized.kind === "path") { resolvedPath = resolvedPath.replace( new RegExp(`{${parameter.name}}`, "g"), serialized.value, ); continue; } if (serialized.kind === "query") { queryEntries.push(...serialized.entries); continue; } if (serialized.kind === "header") { headers[parameter.name] = serialized.value; continue; } if (serialized.kind === "cookie") { cookieParts.push( ...serialized.pairs.map((pair) => `${pair.name}=${encodeURIComponent(pair.value)}`), ); } } const bodySymbol = input.executable.requestBodyId ? input.catalog.symbols[input.executable.requestBodyId] : undefined; let body: string | undefined; if (bodySymbol?.kind === "requestBody") { const bodyValue = argsRecord.body ?? argsRecord.input; if (bodyValue !== undefined) { const serializedBody = serializeOpenApiRequestBody({ requestBody: { contentTypes: bodySymbol.contents.map((content) => content.mediaType), contents: bodySymbol.contents.map((content) => ({ mediaType: content.mediaType })), }, body: applyJsonBodyPlacements({ body: bodyValue, bodyValues: input.auth.bodyValues, label: `${input.executable.method} ${input.executable.pathTemplate}`, }), }); headers["content-type"] = serializedBody.contentType; body = serializedBody.bodyText; } } const requestUrl = resolveHttpRequestUrl(url, resolvedPath); const urlWithAuth = applyHttpQueryPlacementsToUrl({ url: requestUrl, queryParams: input.auth.queryParams, }); const urlWithOpenApiQuery = withSerializedQueryEntries(urlWithAuth, queryEntries); if (cookieParts.length > 0) { headers.cookie = cookieParts.join("; "); } const headersWithCookies = applyCookiePlacementsToHeaders({ headers: { ...headers, ...input.auth.headers, }, cookies: { ...input.auth.cookies, }, }); return { resolvedEndpoint: urlWithOpenApiQuery.toString(), requestPlan: { protocol: "http", method: input.executable.method, url: urlWithOpenApiQuery.toString(), headers: headersWithCookies, ...(body !== undefined ? { body } : {}), }, }; }; const executeHttpPlan = (requestPlan: HttpInvocationRequestPlan) => Effect.tryPromise({ try: async () => { const response = await fetch(requestPlan.url, { method: requestPlan.method, headers: requestPlan.headers, ...(requestPlan.body !== undefined ? { body: requestPlan.body } : {}), }); const responseBody = await decodeFetchBody(response); return executionEnvelope({ data: response.ok ? responseBody : null, error: response.ok ? null : responseBody, headers: responseHeadersRecord(response), status: response.status, }); }, catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)), }); const graphqlSelectionFromArgs = (value: unknown): string => { const record = asObject(value); const fields = Object.entries(record).flatMap(([key, entry]) => { if (entry === true) { return [key]; } if (entry && typeof entry === "object") { const child = graphqlSelectionFromArgs(entry); return child.length > 0 ? [`${key} { ${child} }`] : []; } return []; }); return fields.join(" "); }; const graphqlArgsPayload = (input: { catalog: CatalogV1; executable: GraphQLExecutable; args: Record<string, unknown>; }): Record<string, unknown> => { const argumentShape = input.catalog.symbols[input.executable.argumentShapeId]; const reserved = new Set(["body", "args", "select", "path", "query", "headers", "cookies"]); if ( argumentShape?.kind === "shape" && argumentShape.node.type === "object" && Object.keys(argumentShape.node.fields).every((name) => !reserved.has(name)) ) { return Object.fromEntries( Object.entries(input.args).filter(([key]) => key !== "select" && key !== "headers"), ); } return asObject(input.args.args); }; const planGraphqlInvocation = (input: { source: Source; catalog: CatalogV1; executable: GraphQLExecutable; auth: ResolvedSourceAuthMaterial; args: unknown; }): { resolvedEndpoint: string; requestPlan: GraphqlInvocationRequestPlan; } => { const argsRecord = asObject(input.args); const requestHeaders = applyCookiePlacementsToHeaders({ headers: { ...readSourceHeaders(input.source), ...input.auth.headers, ...asStringRecord(argsRecord.headers), "content-type": "application/json", }, cookies: input.auth.cookies, }); const queryParams = { ...readSourceQueryParams(input.source), ...input.auth.queryParams, }; const endpoint = applyHttpQueryPlacementsToUrl({ url: input.source.endpoint, queryParams, }).toString(); if ( input.executable.toolKind === "request" || typeof input.executable.operationDocument !== "string" || input.executable.operationDocument.trim().length === 0 ) { const query = asString(argsRecord.query); if (query === null) { throw new Error("GraphQL request tools require args.query"); } return { resolvedEndpoint: endpoint, requestPlan: { protocol: "graphql", endpoint, headers: requestHeaders, query, ...(argsRecord.variables ? { variables: asObject(argsRecord.variables) } : {}), ...(asString(argsRecord.operationName) ? { operationName: asString(argsRecord.operationName)! } : {}), }, }; } const operationDocument = input.executable.operationDocument; const operationName = input.executable.operationName; const variables = graphqlArgsPayload({ catalog: input.catalog,