import type { Kernel } from '../kernel.js'; import type { Logger, Capability } from '../types.js'; /** A provider the gateway can execute against (resolved from config). */ export interface GatewayProvider { id: string; /** OpenAI-compatible base URL ending in the version segment (…/v1). */ baseURL: string; apiKey?: string; /** Extra headers (e.g. a custom auth header name for providers that need it). */ headers?: Record; models: Array<{ id: string; name?: string; contextLength?: number; }>; } /** A fully-resolved call target (provider + chosen model). */ export interface GatewayTarget { providerId: string; baseURL: string; apiKey?: string; headers?: Record; model: string; } /** * The execution plane. Swap this to route calls through Bifrost / a remote * gateway / a mock without changing any kernel routing/recovery logic. */ export interface ProviderExecutor { execute(target: GatewayTarget, body: Record, signal: AbortSignal): Promise; } /** Default executor: call the upstream OpenAI-compatible endpoint directly. */ export declare class FetchExecutor implements ProviderExecutor { execute(target: GatewayTarget, body: Record, signal: AbortSignal): Promise; } export interface KernelGatewayOptions { kernel: Kernel; providers: GatewayProvider[]; /** Policy name to route under (e.g. 'balanced'). */ policyName: string; host?: string; /** Preferred port; the gateway scans upward if it is taken. */ port?: number; logger: Logger; executor?: ProviderExecutor; /** Capabilities to route interactive chat under. */ capabilities?: Capability[]; /** * Soft benchmark floor for `auto` routing. Biases interactive coding toward * capable models (weak ones become fallbacks, not first picks) so demanding * work doesn't open on a lazy model — without hard-pinning anything. */ minBenchmark?: number; /** Stall guards (ms). Override the defaults (mainly for tests). */ headersTimeoutMs?: number; firstChunkTimeoutMs?: number; idleTimeoutMs?: number; } export declare class KernelGateway { private opts; private server?; private table; private executor; private boundPort; private modelsCache?; constructor(opts: KernelGatewayOptions); get port(): number; get url(): string; /** * Start listening. Scans ports [port .. port+20] then falls back to an OS-chosen * ephemeral port. Retries past BOTH EADDRINUSE (a lingering session holds it) and * EACCES (a Windows excluded/reserved range) — never dies on a single bad port, * since the whole point is that the gateway must come up so the TUI gets routing. */ start(): Promise; private listen; stop(): Promise; private handle; private listModels; private handleChat; /** * Build a continuation request: the original messages plus the partial answer as * an assistant prefix so a fresh model resumes it instead of restarting. `prefix: * true` is the continue-this-message convention (DeepSeek/Mistral/Zhipu/Qwen); * providers that ignore it at worst restart — still better than a dead task. */ private buildContinuation; /** Close an already-open SSE stream with a synthetic clean finish so the client * gets a proper end-of-turn instead of hanging on a half-open connection. */ private endStream; /** * Run one model. Returns forwarded:true only after the response is validated * and sent to the client; otherwise nothing is written to `res` and the caller * swaps to the next model. Catches BOTH transport failures (non-2xx) AND bad * tool calls the model streams back on a 200 (a weak model naming a tool that * isn't in request.tools — e.g. `glob={...}` — which downstream would reject). */ private attempt; /** * Relay an SSE stream, but with a small look-ahead: buffer frames until the * first tool-call name or content delta. If that first tool call names a tool * not in request.tools (or an error frame arrives), fail WITHOUT writing to the * client so the caller can swap models. Once a valid start is seen, commit and * stream the rest live (so large generations still stream token-by-token). */ private streamValidated; /** * Classify one SSE frame: does it error, is it a safe commit point, is it a * TERMINAL frame (finish_reason / `[DONE]` — held back so a truncation can be * continued), and what visible content does it carry (accumulated so a fresh * model can continue the answer from where a dead provider left off)? */ private inspectFrame; /** Validate a non-streamed response body for unknown tool calls / errors. */ private validateResponse; /** The set of tool names the request declared (OpenAI tools[].function.name). */ private allowedTools; /** Resolve the initial target: honor an explicit provider/model, else route. */ private resolveTarget; /** Ask the kernel router for the best untried, executable model. */ private routerPick; /** * Choose the next target after a failure. 429 / provider-down → rotate to a * different provider; everything else (incl. Groq's tool_use_failed) → * escalate to a stronger model via the kernel ladder, then fall back to rank. */ private nextTarget; /** First configured provider/model not in `tried` (optionally skipping a provider). */ private firstUntried; private toTarget; private rank; private routingRequest; private recordError; private emitSwitch; private sendJson; private readBody; }