// --------------------------------------------------------------------------- // BaseProviderAdapter — Template Method pattern for provider lifecycle // Spec reference: §3.4 // --------------------------------------------------------------------------- import type { TaskHandle } from '../task/task-handle.js'; import type { Provider } from '../task/task-state.js'; import type { ProviderCapabilities } from './provider-capabilities.js'; // --------------------------------------------------------------------------- // Supporting types // --------------------------------------------------------------------------- /** * Options passed to `BaseProviderAdapter.spawn()` to start a provider session. */ export interface ProviderSpawnOptions { taskId: string; prompt: string; cwd: string; timeout: number; model?: string; effort?: 'low' | 'medium' | 'high' | 'xhigh'; developerInstructions?: string; } /** * Result of an availability check on a provider adapter. */ export type AvailabilityResult = | { available: true } | { available: false; reason: string }; // --------------------------------------------------------------------------- // Abstract class // --------------------------------------------------------------------------- /** * Base class for all provider adapters. * * Implements the Template Method pattern: `spawn()` provides the common * lifecycle skeleton (abort controller, timeout, error handling) and * delegates the actual provider interaction to `executeSession()`. */ export abstract class BaseProviderAdapter { /** Unique provider identifier. */ abstract readonly id: Provider; /** Human-readable display name. */ abstract readonly displayName: string; // -- Abstract methods (subclasses must implement) -------------------------- /** Check whether the provider is currently available. */ abstract checkAvailability(): AvailabilityResult; /** Return the capability matrix for this provider. */ abstract getCapabilities(): ProviderCapabilities; /** Return runtime statistics for observability. */ abstract getStats(): Record; /** * Template Method hook — the actual provider session logic. * Subclasses implement this to drive the provider (spawn process, call API, etc.). */ protected abstract executeSession( handle: TaskHandle, prompt: string, signal: AbortSignal, options: ProviderSpawnOptions, ): Promise; // -- Public lifecycle methods --------------------------------------------- /** * Spawn a provider session for the given task. * * Template method: checks terminal state, sets up AbortController + timeout, * calls `executeSession`, and handles errors. */ async spawn(options: ProviderSpawnOptions, handle: TaskHandle): Promise { // Guard: do nothing if already in a terminal state if (handle.isTerminal()) { return; } const controller = new AbortController(); handle.registerAbort(controller); let timer: ReturnType | undefined; if (options.timeout > 0) { timer = setTimeout(() => { controller.abort(); }, options.timeout); // Allow the Node.js process to exit even if this timer is pending if (timer && typeof timer === 'object' && 'unref' in timer) { timer.unref(); } } try { await this.executeSession(handle, options.prompt, controller.signal, options); } catch (err: unknown) { if (controller.signal.aborted) { handle.markCancelled('Session timed out'); } else { const message = err instanceof Error ? err.message : String(err); handle.markFailed(message); } } finally { if (timer !== undefined) { clearTimeout(timer); } handle.unregisterAbort(); } } /** * Attempt to abort a running task by ID. * Default implementation returns false (not supported). */ async abort(_taskId: string): Promise { return false; } /** * Graceful shutdown hook. Called when the server is stopping. * Default implementation is a no-op. */ async shutdown(): Promise { // no-op by default } }