import type { Capabilities, Provider, Request, Response, StreamEvent } from '@wrongstack/core/types'; import { ProviderError } from '@wrongstack/core/types'; import { type HeadersLike } from './error-parse.js'; import type { BuildBodyContext } from './model-output-limits.js'; /** Configuration for WireAdapter stream-level debugging and hang detection. */ export interface WireAdapterStreamOptions { /** * When true, accumulate per-chunk stats into the shared debug-sink * (stream-debug-state.ts). The sink batches every 200 ms and pushes to * a registered callback. The CLI default callback writes to stderr; the * TUI replaces it with a reducer dispatch that renders in StatusBar line 3, * keeping all output inside Ink's layout. * * Controlled by WRONGSTACK_DEBUG_STREAM=1 env var or the runtime * /settings debug-stream toggle. */ debugStream?: boolean | undefined; /** * Maximum time (ms) to wait for the next chunk of data before declaring * a stream hang. Default: 60_000 (60 seconds). Set to 0 to disable. * When a hang is detected, a StreamHangError is thrown so the agent * loop can retry the iteration. */ streamHangTimeoutMs?: number | undefined; /** * Maximum time (ms) to wait for response HEADERS to arrive before aborting * the request. This bounds the header phase, which `streamHangTimeoutMs` * (a body-only, inter-chunk guard) does not cover: a proxy that accepts the * TCP connection but never sends a response line would otherwise hang until * the caller's own signal fires (forever, for long-lived signals). A header * timeout surfaces as a retryable ProviderError. Default: 60_000. Set to 0 * to disable. */ headersTimeoutMs?: number | undefined; } /** * Shared HTTP mechanics for streaming providers. * Providers extend this to get: * - canonical error handling (ProviderError with retryable flag) * - SSE body parsing via parseSSE() * - abort signal wiring * - optional raw-stream debug logging * - optional stream hang detection * * Subclasses implement the abstract members to provide their specific wire format. */ export declare abstract class WireAdapter implements Provider { protected readonly apiKey: string; protected readonly baseUrl: string; readonly fetchImpl: typeof fetch; abstract readonly id: string; abstract readonly capabilities: Capabilities; protected readonly debugStream: boolean; protected readonly streamHangTimeoutMs: number; protected readonly headersTimeoutMs: number; /** * Provider-imposed tool-count limit (0 or undefined = no limit). When > 0, * `stream()` filters `req.tools` down to this many entries before delegating * to `buildBody()`, so every wire family (OpenAI, Anthropic, Google, …) * gets the same guarantee without each adapter repeating the logic. * Set by subclasses from their provider-specific options/quirks. * * Public so consumers (e.g. the TUI status bar view model) can read it to * compute the dropped-tool count: `max(0, ctx.tools.length - maxToolsCount)`. */ maxToolsCount: number; constructor(apiKey: string, baseUrl: string, fetchImpl?: typeof fetch, streamOpts?: WireAdapterStreamOptions); private static readonly _warnedProviders; private static readonly _suppressedProviders; /** * Suppress the one-time maxTools warning for this provider id. Set by the * TUI host when it takes over the terminal, since the status-bar chip * provides equivalent visibility without writing to stderr. Persists across * provider rebuilds (model switch, fallback hop) because it is keyed by * provider id, not instance. */ suppressMaxToolsWarning(): void; /** * Apply the maxTools limit to a request, returning a possibly-filtered copy. * Centralized so both {@link stream} and provider overrides (e.g. * {@link GoogleProvider.stream}) share one implementation. * * Returns the original request reference unchanged when no filtering is * needed (no allocation, preserves WeakMap caches). */ protected applyMaxToolsFilter(req: Request): Request; /** * Emit a one-time warning when the maxTools limit drops tools, so the user * knows conversation history may reference tools the model can no longer * call. Suppressed when the TUI owns the terminal (use the status-bar chip * instead). */ private logMaxToolsWarning; complete(req: Request, opts: { signal: AbortSignal; }): Promise; stream(req: Request, opts: { signal: AbortSignal; }): AsyncIterable; /** * Wrap a readable stream body to log a compact status line per incoming * byte chunk to stderr. This is a diagnostic tool for tracking stream * activity — chunk count, sizes, and inter-chunk deltas — without * printing payload contents. */ private wrapDebugStream; private wrapDebugNodeStream; private wrapDebugWebStream; /** * Wrap a readable stream to detect hangs — when no data arrives for * longer than `streamHangTimeoutMs`. When a hang is detected, throws * `StreamHangError` so the caller can retry or fall back. */ private wrapWithHangDetection; private wrapHangNodeStream; private wrapHangWebStream; /** HTTP endpoint for this provider's chat completions / messages API. */ protected abstract buildUrl(req: Request): string; /** Per-request headers. `apiKey` is already in scope — call `super.buildHeaders` first. */ protected buildHeaders(_req: Request): Record; /** Map Request fields to the wire request body. Receives the provider's * resolved `Capabilities` and id so the body can size the response with * `resolveMaxOutputTokens(req, ctx)` when `req.maxTokens` is undefined. */ protected abstract buildBody(req: Request, ctx: BuildBodyContext): Record; /** Translate wire SSE events into canonical StreamEvent[]. */ protected abstract parseStream(body: ReadableStream | NodeJS.ReadableStream | null, fallbackModel: string, req: Request): AsyncIterable; /** Build a ProviderError from an HTTP failure response. `headers` (when the * fetch impl provides them) lets the parser honour Retry-After hints. */ protected translateError(status: number, body: string, headers?: HeadersLike): ProviderError; } //# sourceMappingURL=wire-adapter.d.ts.map