import {
type ElicitationResponse,
type OnElicitation,
type ToolInvocationContext,
type ToolMetadata,
toTool,
type ToolMap,
type ToolPath,
} from "@executor/codemode-core";
import {
ExecutionIdSchema,
ExecutionInteractionIdSchema,
SourceSchema,
type Source,
type WorkspaceId,
} from "#schema";
import * as Effect from "effect/Effect";
import * as Cause from "effect/Cause";
import * as Exit from "effect/Exit";
import * as Schema from "effect/Schema";
/** Run an Effect as a Promise, preserving the original error (not FiberFailure). */
const runEffect = async (effect: Effect.Effect): Promise => {
const exit = await Effect.runPromiseExit(effect);
if (Exit.isSuccess(exit)) return exit.value;
throw Cause.squash(exit.cause);
};
import {
type ExecutorAddSourceInput,
type ExecutorHttpSourceAuthInput,
type RuntimeSourceAuthService,
} from "./source-auth-service";
import {
deriveSchemaJson,
deriveSchemaTypeSignature,
} from "./schema-type-signature";
import { decodeSourceCredentialSelectionContent } from "./source-credential-interactions";
const ExecutorMcpSourceAddInputSchema = Schema.Struct({
kind: Schema.optional(Schema.Literal("mcp")),
endpoint: Schema.String,
name: Schema.optional(Schema.NullOr(Schema.String)),
namespace: Schema.optional(Schema.NullOr(Schema.String)),
});
const ExecutorOpenApiSourceAddInputSchema = Schema.Struct({
kind: Schema.Literal("openapi"),
endpoint: Schema.String,
specUrl: Schema.String,
name: Schema.optional(Schema.NullOr(Schema.String)),
namespace: Schema.optional(Schema.NullOr(Schema.String)),
});
const ExecutorGraphqlSourceAddInputSchema = Schema.Struct({
kind: Schema.Literal("graphql"),
endpoint: Schema.String,
name: Schema.optional(Schema.NullOr(Schema.String)),
namespace: Schema.optional(Schema.NullOr(Schema.String)),
});
const ExecutorSourcesAddSchema = Schema.Union(
ExecutorMcpSourceAddInputSchema,
ExecutorOpenApiSourceAddInputSchema,
ExecutorGraphqlSourceAddInputSchema,
);
const ExecutorSourcesAddInputSchema = Schema.standardSchemaV1(
ExecutorSourcesAddSchema,
);
const ExecutorSourcesAddOutputSchema = Schema.standardSchemaV1(SourceSchema);
export const EXECUTOR_SOURCES_ADD_MCP_INPUT_SIGNATURE = deriveSchemaTypeSignature(
ExecutorMcpSourceAddInputSchema,
240,
);
export const EXECUTOR_SOURCES_ADD_OPENAPI_INPUT_SIGNATURE = deriveSchemaTypeSignature(
ExecutorOpenApiSourceAddInputSchema,
420,
);
export const EXECUTOR_SOURCES_ADD_GRAPHQL_INPUT_SIGNATURE = deriveSchemaTypeSignature(
ExecutorGraphqlSourceAddInputSchema,
320,
);
export const EXECUTOR_SOURCES_ADD_INPUT_HINT = deriveSchemaTypeSignature(
ExecutorSourcesAddInputSchema,
320,
);
export const EXECUTOR_SOURCES_ADD_OUTPUT_SIGNATURE = deriveSchemaTypeSignature(
SourceSchema,
260,
);
export const EXECUTOR_SOURCES_ADD_INPUT_SCHEMA_JSON = JSON.stringify(
deriveSchemaJson(ExecutorSourcesAddSchema) ?? {},
);
export const EXECUTOR_SOURCES_ADD_OUTPUT_SCHEMA_JSON = JSON.stringify(
deriveSchemaJson(SourceSchema) ?? {},
);
export const EXECUTOR_SOURCES_ADD_HELP_LINES = [
"Source add input shapes:",
`- MCP: ${EXECUTOR_SOURCES_ADD_MCP_INPUT_SIGNATURE}`,
' Omit kind or set kind: "mcp". endpoint is the MCP server URL.',
`- OpenAPI: ${EXECUTOR_SOURCES_ADD_OPENAPI_INPUT_SIGNATURE}`,
" endpoint is the base API URL. specUrl is the OpenAPI document URL.",
`- GraphQL: ${EXECUTOR_SOURCES_ADD_GRAPHQL_INPUT_SIGNATURE}`,
" endpoint is the GraphQL HTTP endpoint.",
" executor handles the credential setup for you.",
] as const;
export const buildExecutorSourcesAddDescription = (): string =>
[
"Add an MCP, OpenAPI, or GraphQL source to the current workspace.",
...EXECUTOR_SOURCES_ADD_HELP_LINES,
].join("\n");
const toExecutionId = (value: unknown) => {
if (typeof value !== "string" || value.trim().length === 0) {
throw new Error("Missing execution run id for executor.sources.add");
}
return ExecutionIdSchema.make(value);
};
const asToolPath = (value: string): ToolPath => value as ToolPath;
const trimOrNull = (value: string | null | undefined): string | null => {
if (value === null || value === undefined) {
return null;
}
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : null;
};
const resolveOpenApiSourceLabel = (input: {
name?: string | null;
endpoint: string;
}): string => trimOrNull(input.name) ?? input.endpoint;
const resolveLocalCredentialUrl = (input: {
baseUrl: string;
workspaceId: WorkspaceId;
sourceId: Source["id"];
executionId: string;
interactionId: string;
}): string =>
new URL(
`/v1/workspaces/${encodeURIComponent(input.workspaceId)}/sources/${encodeURIComponent(input.sourceId)}/credentials?interactionId=${encodeURIComponent(`${input.executionId}:${input.interactionId}`)}`,
input.baseUrl,
).toString();
const promptForSourceCredentialSelection = (input: {
args: {
workspaceId: WorkspaceId;
sourceId: Source["id"];
kind: "openapi" | "graphql";
endpoint: string;
specUrl?: string;
name?: string | null;
namespace?: string | null;
};
source: Source;
executionId: string;
interactionId: string;
path: ToolPath;
sourceKey: string;
localServerBaseUrl: string | null;
metadata?: ToolMetadata;
invocation?: ToolInvocationContext;
onElicitation?: OnElicitation;
}) =>
Effect.gen(function* () {
if (!input.onElicitation) {
return yield* Effect.fail(
new Error("executor.sources.add requires an elicitation-capable host"),
);
}
if (input.localServerBaseUrl === null) {
return yield* Effect.fail(
new Error("executor.sources.add requires a local server base URL for credential capture"),
);
}
const response: ElicitationResponse = yield* input.onElicitation({
interactionId: input.interactionId,
path: input.path,
sourceKey: input.sourceKey,
args: input.args,
metadata: input.metadata,
context: input.invocation,
elicitation: {
mode: "url",
message: `Open the secure credential page to connect ${input.source.name}`,
url: resolveLocalCredentialUrl({
baseUrl: input.localServerBaseUrl,
workspaceId: input.args.workspaceId,
sourceId: input.args.sourceId,
executionId: input.executionId,
interactionId: input.interactionId,
}),
elicitationId: input.interactionId,
},
}).pipe(Effect.mapError((cause) => cause instanceof Error ? cause : new Error(String(cause))));
if (response.action !== "accept") {
return yield* Effect.fail(
new Error(`Source credential setup was not completed for ${input.source.name}`),
);
}
const content = yield* Effect.try({
try: () => decodeSourceCredentialSelectionContent(response.content),
catch: () =>
new Error("Credential capture did not return a valid source auth choice for executor.sources.add"),
});
if (content.authKind === "none") {
return { kind: "none" } satisfies ExecutorHttpSourceAuthInput;
}
return {
kind: "bearer",
tokenRef: content.tokenRef,
} satisfies ExecutorHttpSourceAuthInput;
});
export const createExecutorToolMap = (input: {
workspaceId: WorkspaceId;
sourceAuthService: RuntimeSourceAuthService;
}): ToolMap => ({
"executor.sources.add": toTool({
tool: {
description: buildExecutorSourcesAddDescription(),
inputSchema: ExecutorSourcesAddInputSchema,
outputSchema: ExecutorSourcesAddOutputSchema,
execute: async (
args:
| {
kind?: "mcp";
endpoint: string;
name?: string | null;
namespace?: string | null;
}
| {
kind: "openapi";
endpoint: string;
specUrl: string;
name?: string | null;
namespace?: string | null;
}
| {
kind: "graphql";
endpoint: string;
name?: string | null;
namespace?: string | null;
},
context,
): Promise => {
const executionId = toExecutionId(context?.invocation?.runId);
const interactionId = ExecutionInteractionIdSchema.make(
`executor.sources.add:${crypto.randomUUID()}`,
);
const preparedArgs: ExecutorAddSourceInput =
args.kind === "openapi" || args.kind === "graphql"
? {
...args,
workspaceId: input.workspaceId,
executionId,
interactionId,
}
: {
kind: args.kind,
endpoint: args.endpoint,
name: args.name ?? null,
namespace: args.namespace ?? null,
workspaceId: input.workspaceId,
executionId,
interactionId,
};
const result = await runEffect(
input.sourceAuthService.addExecutorSource(
preparedArgs,
context?.onElicitation
? {
mcpDiscoveryElicitation: {
onElicitation: context.onElicitation,
path: context.path ?? asToolPath("executor.sources.add"),
sourceKey: context.sourceKey,
args,
metadata: context.metadata,
invocation: context.invocation,
},
}
: undefined,
),
);
if (result.kind === "connected") {
return result.source;
}
if (result.kind === "credential_required") {
const preparedHttpArgs = preparedArgs as Extract<
ExecutorAddSourceInput,
{ kind: "openapi" | "graphql" }
>;
const selectedAuth = await runEffect(
promptForSourceCredentialSelection({
args: {
...preparedHttpArgs,
workspaceId: input.workspaceId,
sourceId: result.source.id,
},
source: result.source,
executionId,
interactionId,
path: context?.path ?? asToolPath("executor.sources.add"),
sourceKey: context?.sourceKey ?? "executor",
localServerBaseUrl: input.sourceAuthService.getLocalServerBaseUrl(),
metadata: context?.metadata,
invocation: context?.invocation,
onElicitation: context?.onElicitation,
}),
);
const completed = await runEffect(
input.sourceAuthService.addExecutorSource(
{
...preparedHttpArgs,
auth: selectedAuth,
},
context?.onElicitation
? {
mcpDiscoveryElicitation: {
onElicitation: context.onElicitation,
path: context.path ?? asToolPath("executor.sources.add"),
sourceKey: context.sourceKey,
args,
metadata: context.metadata,
invocation: context.invocation,
},
}
: undefined,
),
);
if (completed.kind === "connected") {
return completed.source;
}
throw new Error(`Source add was not completed for ${result.source.id}`);
}
if (!context?.onElicitation) {
throw new Error("executor.sources.add requires an elicitation-capable host");
}
const response: ElicitationResponse = await runEffect(
context.onElicitation({
interactionId,
path: context.path ?? asToolPath("executor.sources.add"),
sourceKey: context.sourceKey,
args: preparedArgs,
metadata: context.metadata,
context: context.invocation,
elicitation: {
mode: "url",
message: `Open the provider sign-in page to connect ${result.source.name}`,
url: result.authorizationUrl,
elicitationId: result.sessionId,
},
}),
);
if (response.action !== "accept") {
throw new Error(`Source add was not completed for ${result.source.id}`);
}
return await runEffect(
input.sourceAuthService.getSourceById({
workspaceId: input.workspaceId,
sourceId: result.source.id,
}),
);
},
},
metadata: {
inputType: EXECUTOR_SOURCES_ADD_INPUT_HINT,
outputType: EXECUTOR_SOURCES_ADD_OUTPUT_SIGNATURE,
inputSchemaJson: EXECUTOR_SOURCES_ADD_INPUT_SCHEMA_JSON,
outputSchemaJson: EXECUTOR_SOURCES_ADD_OUTPUT_SCHEMA_JSON,
sourceKey: "executor",
interaction: "auto",
},
}),
});