import type { NormalizedAgentEvent } from '../agent-events'; import type { streamExecInContainer } from './docker'; import type { AdapterWorkspace } from './provider'; export type AgentRuntimeName = 'codex' | 'opencode' | 'claude'; export type ExecStreamFn = typeof streamExecInContainer; export type TurnResult = { // Assistant text the adapter captured out-of-band (Codex --output-last-message // file, Claude `result` event). Empty/undefined when everything streamed via onEvent. finalMessage?: string; // Runtime session id to persist for continuity (resume/--session/--resume). sessionId?: string; // Non-empty when the runtime reported a hard failure the caller must surface. error?: string; }; export type AgentRuntime = { readonly name: AgentRuntimeName; prepareAuth(workspace: AdapterWorkspace): Promise; runTurn( workspace: AdapterWorkspace, prompt: string, onEvent: (event: NormalizedAgentEvent) => Promise, ): Promise; abort(workspace: AdapterWorkspace): Promise; }; export type AdapterFactory = (deps?: { execStream?: ExecStreamFn; }) => AgentRuntime; const factories = new Map(); export const registerAdapter = ( name: AgentRuntimeName, factory: AdapterFactory, ): void => { factories.set(name, factory); }; export const getAdapter = (name: AgentRuntimeName): AgentRuntime => { const factory = factories.get(name); if (!factory) { throw new Error(`No agent runtime adapter registered for "${name}".`); } return factory(); };