import type { TSchema } from "typebox"; import type { AgentHistoryEntry } from "./agent-history.js"; import { type ClaudeCodeHarnessOptions, createClaudeCodeHarness } from "./claude-code-harness.js"; import { type CodexHarnessOptions, createCodexHarness } from "./codex-harness.js"; import { WorkflowError, WorkflowErrorCode } from "./errors.js"; import type { StructuredOutputSchema } from "./structured-output.js"; /** Providers that can execute a workflow agent. */ export type WorkflowExecutor = "pi" | "codex" | "claude-code"; export type ExecutorCapability = "structured-output" | "jsonl" | "usage" | "history" | "workspace-write"; export interface ExecutorCapabilityDescriptor { executor: WorkflowExecutor; /** Human-readable wire protocol identity, useful for diagnostics and telemetry. */ protocol: string; /** Provider protocol/CLI shape version, not this package version. */ protocolVersion: string; command?: string; capabilities: readonly ExecutorCapability[]; } export const EXECUTOR_CAPABILITY_DESCRIPTORS: Readonly> = { pi: { executor: "pi", protocol: "pi-agent-session", protocolVersion: "0.80+", capabilities: ["structured-output", "usage", "history", "workspace-write"], }, codex: { executor: "codex", protocol: "codex-exec-jsonl", protocolVersion: "0.145.0", command: "codex", capabilities: ["structured-output", "jsonl", "usage", "history", "workspace-write"], }, "claude-code": { executor: "claude-code", protocol: "claude-cli-stream-json", protocolVersion: "stream-json", command: "claude", capabilities: ["structured-output", "jsonl", "usage", "history", "workspace-write"], }, }; export interface ExecutorUsage { input: number; output: number; total: number; cost: number; cacheRead: number; cacheWrite: number; } export interface ExecutorRunRequest { prompt: string; cwd?: string; model?: string; schema?: TSchema | StructuredOutputSchema; signal?: AbortSignal; env?: NodeJS.ProcessEnv; label?: string; /** * Best-effort cumulative provider usage. May fire before a failed run rejects; * omitted when the provider reports no usage. Callback failures are ignored. */ onUsage?: (usage: ExecutorUsage) => void; } export interface ExecutorRunResult { /** Validated structured result, or final text for an unstructured request. */ result: T; /** Original final assistant text when the provider exposed text. */ text?: string; usage?: ExecutorUsage; history?: AgentHistoryEntry[]; /** Provider protocol events retained for diagnostics; callers should not mutate. */ events?: readonly unknown[]; } export interface AgentExecutor { readonly executor: WorkflowExecutor; readonly descriptor: ExecutorCapabilityDescriptor; run(request: ExecutorRunRequest): Promise>; } export type ExecutorFactory = () => AgentExecutor; export type ExecutorRegistration = AgentExecutor | ExecutorFactory; export interface ExecutorRegistry { get(executor: WorkflowExecutor): AgentExecutor | undefined; require(executor: WorkflowExecutor): AgentExecutor; register(executor: WorkflowExecutor, registration: ExecutorRegistration): this; descriptors(): readonly ExecutorCapabilityDescriptor[]; } class Registry implements ExecutorRegistry { private readonly registrations = new Map(); private readonly instances = new Map(); constructor(registrations?: Partial>) { for (const [executor, registration] of Object.entries(registrations ?? {})) { if (registration) this.registrations.set(executor as WorkflowExecutor, registration); } } get(executor: WorkflowExecutor): AgentExecutor | undefined { const existing = this.instances.get(executor); if (existing) return existing; const registration = this.registrations.get(executor); if (!registration) return undefined; const value = typeof registration === "function" ? registration() : registration; if (!value || value.executor !== executor || typeof value.run !== "function") { throw new WorkflowError( `invalid ${executor} executor registration`, WorkflowErrorCode.EXECUTOR_CAPABILITY_ERROR, { recoverable: false }, ); } this.instances.set(executor, value); return value; } require(executor: WorkflowExecutor): AgentExecutor { const value = this.get(executor); if (!value) { throw new WorkflowError(`executor unavailable: ${executor}`, WorkflowErrorCode.EXECUTOR_UNAVAILABLE, { recoverable: false, }); } return value; } register(executor: WorkflowExecutor, registration: ExecutorRegistration): this { this.registrations.set(executor, registration); this.instances.delete(executor); return this; } descriptors(): readonly ExecutorCapabilityDescriptor[] { return Object.values(EXECUTOR_CAPABILITY_DESCRIPTORS); } } /** Build an injectable registry. Registrations may be concrete or lazy factories. */ export function createExecutorRegistry( registrations?: Partial>, ): ExecutorRegistry { return new Registry(registrations); } export interface DefaultExecutorRegistryOptions { /** Optional host-provided Pi adapter; Pi itself is owned by WorkflowAgent. */ pi?: ExecutorRegistration; codex?: CodexHarnessOptions; claudeCode?: ClaudeCodeHarnessOptions; } /** * Create the standard registry without probing binaries or credentials. Codex and * Claude adapters are constructed on first `get`, so importing the package remains * safe on hosts that install neither CLI. */ export function createDefaultExecutorRegistry(options: DefaultExecutorRegistryOptions = {}): ExecutorRegistry { const registry = new Registry(); if (options.pi) registry.register("pi", options.pi); registry.register("codex", () => createCodexHarness(options.codex)); registry.register("claude-code", () => createClaudeCodeHarness(options.claudeCode)); return registry; } /** A fresh default registry; no executable is launched until an adapter is run. */ export const defaultExecutorRegistry = (): ExecutorRegistry => createDefaultExecutorRegistry(); export const DEFAULT_EXECUTOR_REGISTRY = defaultExecutorRegistry; export function getExecutorProtocolVersion(executor: WorkflowExecutor): string { return EXECUTOR_CAPABILITY_DESCRIPTORS[executor].protocolVersion; }