827 lines · 25.6 KB · TypeScript
RawDownload
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.