/** * Backend abstraction for AgentLink. * * Stage 1: defines the interface only. ClaudeBackend implementation lives in * agent/src/claude.ts (refactor to backends/claude/* is a follow-up PR). */ export type BackendType = 'claude' | 'codex' | 'copilot' | 'yeaft' | 'pi'; export declare const KNOWN_BACKEND_TYPES: readonly BackendType[]; /** * Backend-neutral file attachment for `UserTurnInput.files`. * Defined here (not in claude.ts) so the boundary stays clean. * (PR3 will re-export this as `ChatFile` from claude.ts for back-compat; * not yet wired in Stage 1.) */ export interface BackendFileAttachment { name: string; mimeType: string; /** base64-encoded content */ data: string; } /** * Backend-neutral history message — fully opaque from the backend interface's * point of view. Concrete backends (Claude's `{ role, content, ... }`, * Codex's event records) all flow through unchanged; the wire/UI layer * narrows as needed. PR3 will define the normalized shape if/when needed. */ export type BackendHistoryMessage = Record; /** * Backend-neutral session listing entry. Carries identity required for * cross-backend routing — `backendType` + `backendSessionId` together * uniquely identify a session. */ export interface BackendSessionInfo { /** Composite identity for routing / resume. */ session: BackendSessionRef; /** Display title (custom title > derived). */ title: string; /** Optional user-set custom title (separate from derived). */ customTitle?: string; /** Short preview of last message (UI hint). */ preview?: string; /** Epoch ms of last activity. */ lastModified: number; } /** Backend-neutral session identifier. Avoids leaking `claudeSessionId` into shared code. */ export interface BackendSessionRef { backendType: BackendType; /** Stable id used for routing, listing, resume. For Claude: claudeSessionId. For Codex: Thread.id. */ backendSessionId: string; /** Thread id when the provider exposes one. Claude/Codex set this equal to backendSessionId. */ backendThreadId?: string | null; /** Provider's raw id, kept for debugging / cross-backend migration. */ providerSessionId?: string | null; } /** Backend-neutral sandbox / permission mode. Each backend maps internally. */ export type SandboxMode = 'safe' | 'workspace' | 'unrestricted'; /** Capability descriptor with optional per-feature details. */ export interface Capability { supported: boolean; details?: TDetails; } export interface BackendCapabilities { lifecycle: { persistentProcess: boolean; resumableSessions: boolean; concurrentTurns: boolean; backgroundTurns: boolean; fork: Capability; compaction: Capability; }; approvals: { command: Capability; fileChange: Capability; permissions: Capability; genericTool: Capability; userInput: Capability; }; streaming: { text: boolean; reasoning: Capability; plan: Capability; usage: Capability<{ incremental: boolean; }>; processOutput: Capability; fsWatch: Capability; remoteControl: Capability<{ statusEvents: boolean; }>; }; history: { list: boolean; pagination?: SessionPaginationCapability; read: boolean; rename: boolean; delete: boolean; search: boolean; }; models: { list: Capability; switchPerSession: boolean; switchPerTurn: boolean; }; tools: { /** Item kinds this backend emits (e.g. 'Read','Write','Bash' for Claude; 'command_execution','file_change' for Codex). */ itemTypes: string[]; fallbackRenderer: boolean; specializedRenderers: string[]; }; commands: { discovery: Capability<{ kinds: Array<'skill' | 'prompt' | 'extension' | 'command'>; timing: 'pre_prompt' | 'after_first_prompt'; supportsForceReload: boolean; presentation: 'replace_static' | 'augment_static'; }>; }; integrations: { accountInfo: Capability; codeReview: Capability; mcpManagement: Capability; hooks: Capability; personalitySwitch: Capability; skills: Capability; }; copilotOptions: { model: boolean; context: boolean; reasoningEffort: boolean; attachments: boolean; }; yeaftOptions: { model: boolean; reasoningEffort: boolean; vps: boolean; }; piOptions: { model: boolean; thinking: boolean; attachments: boolean; }; input: { attachments: boolean; imageAttachments: boolean; }; sandboxModes: SandboxMode[]; } export type SessionPaginationCapability = Capability<{ surfaces: Array<'workdir' | 'global'>; modeEcho: boolean; requestIdEcho: boolean; }>; /** Approval / confirmation requests from the backend to the user. */ export type ApprovalRequest = { kind: 'command'; command: string; cwd?: string; reason?: string; } | { kind: 'file_change'; summary: string; changes?: unknown; } | { kind: 'permissions'; permissions: unknown; reason?: string; } | { kind: 'tool'; toolName: string; input: unknown; reason?: string; }; export type UserInputSource = 'ask_user_question' | 'mcp_elicitation' | 'tool_request_user_input'; export interface UserInputQuestion { question: string; header?: string; options: Array<{ label: string; description?: string; }>; multiSelect?: boolean; } export interface UserInputRequest { questions: UserInputQuestion[]; source: UserInputSource; } export interface UsageInfo { provider?: BackendType; model?: string; contextTier?: string; reasoningEffort?: string; inputTokens?: number; outputTokens?: number; cacheReadInputTokens?: number; cacheCreationInputTokens?: number; totalCostUsd?: number; totalCost?: number; durationMs?: number; sessionDurationMs?: number; } /** Normalized tool event (start / update / result). */ export interface NormalizedToolEvent { id: string; name: string; /** Backend item kind (e.g. 'Read','Bash','command_execution','file_change'). */ itemKind?: string; input?: unknown; output?: unknown; isError?: boolean; status?: 'in_progress' | 'completed' | 'error'; } export interface NormalizedYeaftTaskEvent { subtype: string; task: unknown; } export interface NormalizedYeaftSubAgentEvent { subtype: string; agentId?: string; payload: unknown; } export interface NormalizedAsyncTaskWaitEvent { status: 'waiting' | 'resumed'; pendingTaskIds?: string[]; remainingTaskIds?: string[]; aborted?: boolean; } export interface NormalizedEventScope { vpId?: string; threadId?: string; } export type NormalizedEvent = ({ type: 'session_started'; session: BackendSessionRef; } & NormalizedEventScope) | ({ type: 'turn_started'; session: BackendSessionRef; turnId?: string; } & NormalizedEventScope) | ({ type: 'text_delta'; session: BackendSessionRef; text: string; } & NormalizedEventScope) | ({ type: 'tool_started'; session: BackendSessionRef; tool: NormalizedToolEvent; } & NormalizedEventScope) | ({ type: 'tool_updated'; session: BackendSessionRef; tool: NormalizedToolEvent; } & NormalizedEventScope) | ({ type: 'tool_completed'; session: BackendSessionRef; tool: NormalizedToolEvent; } & NormalizedEventScope) | ({ type: 'task_updated'; session: BackendSessionRef; update: NormalizedYeaftTaskEvent; } & NormalizedEventScope) | ({ type: 'sub_agent_updated'; session: BackendSessionRef; update: NormalizedYeaftSubAgentEvent; } & NormalizedEventScope) | ({ type: 'async_task_wait'; session: BackendSessionRef; wait: NormalizedAsyncTaskWaitEvent; } & NormalizedEventScope) | ({ type: 'plan_delta'; session: BackendSessionRef; plan: unknown; } & NormalizedEventScope) | ({ type: 'reasoning_delta'; session: BackendSessionRef; text: string; } & NormalizedEventScope) | ({ type: 'compact_started'; session: BackendSessionRef; } & NormalizedEventScope) | ({ type: 'compact_completed'; session: BackendSessionRef; } & NormalizedEventScope) | ({ type: 'usage_update'; session: BackendSessionRef; usage: UsageInfo; } & NormalizedEventScope) | ({ type: 'fs_changed'; session: BackendSessionRef; paths: string[]; } & NormalizedEventScope) | ({ type: 'process_output'; session: BackendSessionRef; processId: string; chunk: string; } & NormalizedEventScope) | ({ type: 'process_exited'; session: BackendSessionRef; processId: string; exitCode: number; } & NormalizedEventScope) | ({ type: 'approval_requested'; session: BackendSessionRef; requestId: string; request: ApprovalRequest; } & NormalizedEventScope) | ({ type: 'user_input_requested'; session: BackendSessionRef; requestId: string; request: UserInputRequest; } & NormalizedEventScope) | ({ type: 'turn_completed'; session: BackendSessionRef; usage?: UsageInfo; } & NormalizedEventScope) | ({ type: 'turn_cancelled'; session: BackendSessionRef; } & NormalizedEventScope) | ({ type: 'error'; session?: BackendSessionRef; message: string; } & NormalizedEventScope); export interface StartOpts { workDir: string; /** Backend-neutral permission mode; backend maps to its own sandbox/permission flags. */ permissions?: { mode: SandboxMode; additionalWritableDirs?: string[]; }; } export interface EnsureSessionOpts { conversationId: string; workDir: string; resumeSessionId?: string; /** Optional metadata that backends with rich session metadata (Claude's `recapId`, `briefingDate` etc.) can store. */ metadata?: Record; } export interface UserTurnInput { text: string; files?: BackendFileAttachment[]; } export interface ApprovalAnswer { requestId: string; decision: 'allow' | 'deny'; reason?: string; } export interface UserInputAnswer { requestId: string; /** questionText -> selected option label */ answers: Record; } export type EventListener = (event: NormalizedEvent) => void; /** * Backend interface. Methods marked optional are capability-gated: callers must * check `capabilities` before invoking. Required methods all backends must * implement (even if as no-ops or throws). */ export interface AgentBackend { readonly type: BackendType; readonly capabilities: BackendCapabilities; start(opts: StartOpts): Promise; shutdown(): Promise; ensureSession(opts: EnsureSessionOpts): Promise; startTurn(session: BackendSessionRef, input: UserTurnInput): Promise; interruptTurn(session: BackendSessionRef): Promise; shutdownSession?(session: BackendSessionRef): Promise; answerUserInput(answer: UserInputAnswer): void; answerApproval(answer: ApprovalAnswer): void; listSessions(workDir: string): Promise; readSession(workDir: string, session: BackendSessionRef): Promise; renameSession?(workDir: string, session: BackendSessionRef, title: string): Promise; deleteSession?(workDir: string, session: BackendSessionRef): Promise; compact?(session: BackendSessionRef): Promise; fork?(session: BackendSessionRef, fromMessageId?: string): Promise; getAccountInfo?(): Promise<{ usage?: UsageInfo; limits?: unknown; }>; listModels?(): Promise>; setModel?(session: BackendSessionRef | null, model: string | null): void; on(listener: EventListener): () => void; } /** Helper: minimal capabilities object for a stub/unknown backend. */ export declare function emptyCapabilities(): BackendCapabilities; /** * Capabilities declared by the ClaudeBackend adapter (current implementation * as of 2026-05-09). These reflect what the adapter exposes through the * AgentBackend interface — not the full surface of the underlying claude CLI. * For example, claude.ts has restartConversation and an interactive /compact * flow, but neither is wired through AgentBackend.fork / AgentBackend.compact * yet, so both are reported as unsupported. */ export declare function claudeCapabilities(): BackendCapabilities; /** * Capabilities declared by the CopilotBackend adapter. * Copilot CLI uses `--output-format json` JSONL, per-turn process spawning, * and `~/.copilot/session-state/` for session persistence. */ export declare function copilotCapabilities(): BackendCapabilities; /** * Capabilities declared by the Codex exec-based MVP adapter. * Codex is driven via `codex exec --json` / `codex exec resume --json`, * so each turn is a separate process and the thread id is the resumable * session id. */ export declare function codexCapabilities(): BackendCapabilities; export declare function piCapabilities(): BackendCapabilities; /** * Capabilities declared by the Yeaft stream-json MVP adapter. * Yeaft is driven as a persistent local CLI process per conversation using * `--input-format stream-json --output-format stream-json`. */ export declare function yeaftCapabilities(): BackendCapabilities;