import type * as plugins from './plugins.js'; import type { IControllerEvent, IControllerMessage, IControllerReasoningUpdate, IControllerRuntimeId, IControllerTextUpdate, IControllerToolExecution, IControllerSession, TControllerHarnessLifecycleState, } from '../ts_interfaces/index.js'; export const openCodeExpectedVersion = '1.18.15' as const; export const openCodeUsername = 'harness-controller' as const; export interface IOpenCodeHealth { healthy: true; version: typeof openCodeExpectedVersion; } export interface IOpenCodeLogEntry { timestamp: number; stream: 'stdout' | 'stderr'; line: string; truncated: boolean; } export interface IOpenCodeSupervisorStatus { state: TControllerHarnessLifecycleState; healthy: boolean; pid?: number; version?: string; startedAt?: number; } export interface IOpenCodeConnectionConfig { baseUrl: string; directory: string; username: typeof openCodeUsername; password: string; } export interface IOpenCodeProcessGroupController { isAlive(processGroupIdArg: number): boolean | Promise; signal( processGroupIdArg: number, signalArg: 'SIGTERM' | 'SIGKILL', ): void | Promise; } export interface IOpenCodeSupervisorOptions { directory: string; port: number; startupTimeoutMs?: number; healthRequestTimeoutMs?: number; healthPollIntervalMs?: number; stopTimeoutMs?: number; finalStopTimeoutMs?: number; logEntryLimit?: number; logLineByteLimit?: number; spawnFactory?: typeof plugins.childProcess.spawn; fetchImplementation?: typeof globalThis.fetch; listenerOwnershipVerifier?: ( processId: number, port: number ) => boolean | Promise; processGroupController?: IOpenCodeProcessGroupController; /** Called immediately after the owned child exits, before process-group cleanup. */ onChildExitObserved?: () => void; /** Called after the owned child exits and can no longer access operation resources. */ onChildExit?: () => void; /** * Mints the caller credential the OpenCode server hands to the `agl mcp` servers it launches. * OpenCode addresses MCP servers per directory and never per task, so this identifies the * runtime, not one chat. Minted per start, so a restart strands the previous credential. */ mintCallerCredential?: () => string | undefined; } export type TOpenCodeSdkClient = ReturnType; export interface IOpenCodeClientOptions extends IOpenCodeConnectionConfig { clientFactory?: typeof plugins.opencodeSdk.createOpencodeClient; fetchImplementation?: typeof globalThis.fetch; /** Current-generation lease held until each non-streaming SDK response body settles. */ acquireRuntimeLease?: () => () => void; } export interface IOpenCodeSessionAuthorityObservation { session: IControllerSession; providerSessionGeneration: string; } export interface IOpenCodeDirectChildObservation extends IOpenCodeSessionAuthorityObservation { parentNativeId: string; } /** Adapter-internal normalized entries derived from one OpenCode source message. */ export interface IOpenCodeNormalizedMessageBundle { sourceMessageId: IControllerRuntimeId; structuralDigest: string; sourceRole: 'user' | 'assistant'; parentMessageId?: IControllerRuntimeId; terminal: boolean; messages: IControllerMessage[]; } export interface IOpenCodeMessageLifecycle { sessionId: IControllerRuntimeId; messageId: IControllerRuntimeId; role: 'user' | 'assistant'; parentMessageId?: IControllerRuntimeId; terminal: boolean; } /** Adapter-internal page shape; cursors remain opaque OpenCode values. */ export interface IOpenCodeMessagePage { bundles: IOpenCodeNormalizedMessageBundle[]; nextCursor?: string; } export type TOpenCodeToolExecution = Omit< IControllerToolExecution, 'revision' | 'streamEpoch' >; export type TOpenCodeReasoningUpdate = Omit< IControllerReasoningUpdate, 'order' | 'revision' | 'streamEpoch' >; export type TOpenCodeTextUpdate = Omit< IControllerTextUpdate, 'order' | 'revision' | 'streamEpoch' >; export interface IOpenCodeEventStreamOptions { /** directory is the raw project worktree the event belongs to. */ onEvent: (event: IControllerEvent, directory: string) => void | Promise; /** Replace-only tool snapshot; the controller assigns delivery ordering. */ onToolExecution?: ( execution: TOpenCodeToolExecution, directory: string, timestamp: number, ) => void | Promise; /** Replace-only Assistant reasoning snapshot; the controller assigns ordering. */ onReasoningUpdate?: ( update: TOpenCodeReasoningUpdate, directory: string, ) => void | Promise; /** Replace-only Assistant text snapshot; the controller assigns ordering. */ onTextUpdate?: ( update: TOpenCodeTextUpdate, directory: string, ) => void | Promise; /** Exact source-message lifecycle used for controller-owned resource cleanup. */ onMessageLifecycle?: ( lifecycle: IOpenCodeMessageLifecycle, directory: string, ) => void | Promise; /** Fires in stream order for the initial connection and every SDK reconnect. */ onConnected?: () => void | Promise; /** Signals that live snapshots may have been lost or could not be normalized. */ onToolStreamGap?: () => void | Promise; /** Signals that live Assistant snapshots may have been lost or could not be normalized. */ onMessageStreamGap?: () => void | Promise; /** Signals that the shared SDK stream ended and both snapshot streams were lost. */ onStreamGap?: () => void | Promise; onError?: (error: Error) => void; signal?: AbortSignal; }