import type { ChatMessage, NativeToolCall, ReasoningArtifact, ReasoningArtifactReplayObserver, ReasoningArtifactReplayTarget, ReasoningBlock, ReasoningPreference, ProviderId, TokenUsage, ToolChoice, ToolDefinition, ReasoningEffort } from "../types.js"; import { type CompatibleUsageAliases } from "./token-usage.js"; import { type ProviderStreamEventSink } from "./stream-events.js"; import { type StreamTerminalPolicy } from "./stream-terminal.js"; import type { FinalTurnPreservation } from "./provider-profile.js"; import { type ReasoningControlSurface } from "./reasoning-controls.js"; import { type RequestPlanV1 } from "./request-plan.js"; export declare class ProviderError extends Error { readonly status?: number | undefined; readonly body?: string | undefined; readonly retryAfterSeconds?: number | undefined; constructor(message: string, status?: number | undefined, body?: string | undefined, retryAfterSeconds?: number | undefined); } export declare function catalogEntryVision(entry: unknown): boolean | undefined; export declare function ingestModelCatalogEntries(provider: ProviderId, entries: readonly unknown[]): string[]; export declare function ingestOpenAiModelCatalog(provider: ProviderId, payload: unknown): string[]; export declare function readJson(response: Response, signal?: AbortSignal): Promise; /** How much of a provider error body we retain on the ProviderError. */ export declare const MAX_ERROR_BODY_CHARS = 8000; /** How much of that body is embedded in the user-visible error message. */ export declare const MAX_ERROR_BODY_IN_MESSAGE_CHARS = 2000; export declare function collapseWhitespace(text: string): string; /** * True when the raw response body carries information beyond the message we * already extracted from it (extra JSON fields, non-JSON payloads, …). Pure * `{"error":{"message": …}}` envelopes are redundant and stay compact. */ export declare function bodyAddsInformation(bodyText: string, extracted: string): boolean; /** * Mid-stream silence budget. * * This is deliberately generous because "no bytes" does **not** mean "dead * socket" on an OpenAI-compatible endpoint. Most self-hosted runtimes (vLLM / * SGLang and the tool-call parsers layered on top of them) buffer an entire * `tool_calls` delta before emitting it, so a model writing a large file goes * completely silent on the wire for as long as the generation takes. A 90s * budget aborted those healthy streams at `firstToken + 90s`, reported the * abort as a network failure, and burned three identical retries that each * re-generated the same prefix before one happened to finish inside the window. */ export declare const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 240000; export declare const THINKING_STREAM_IDLE_TIMEOUT_MS = 900000; export declare const THINKING_STREAM_INITIAL_IDLE_TIMEOUT_MS = 900000; export declare function streamIdleBudgets(reasoningEnabled: boolean): { idleTimeoutMs: number; outputIdleTimeoutMs: number; }; /** * Marker appended to the message of a stall that happened on a **live** * connection (bytes/keepalives were still arriving, or output had already * started and simply stopped). Such a stall is not a transport failure, so the * recovery layer must not classify it as `network` and retry the identical * request against the identical route. */ export declare const STREAM_STALL_MARKER = "no model output"; export interface StreamLineReaderOptions { signal?: AbortSignal | undefined; idleTimeoutMs?: number | undefined; maxBytes?: number | undefined; /** If provided, called after every read so callers can reset their own * watchdogs (eg the OpenAI-compatible streamer's existing one). */ onActivity?: (() => void) | undefined; outputIdleTimeoutMs?: number | undefined; outputProgress?: (() => number) | undefined; } export declare function readStreamLines(response: Response, options?: StreamLineReaderOptions): AsyncGenerator; /** Controls how a compatible route's captured plaintext/details can persist. */ export interface CompatibleReasoningArtifactPolicy { readonly scope: ReasoningArtifact["replay"]["scope"]; readonly persistence: ReasoningArtifact["replay"]["persistence"]; } export declare function compatibleArtifactPolicyFor(preservation: FinalTurnPreservation): CompatibleReasoningArtifactPolicy; /** Result of OpenAI-compatible complete/stream (text + optional native tools). */ export interface OpenAiCompatibleResult { text: string; toolCalls?: NativeToolCall[] | undefined; finishReason?: string | undefined; usage?: TokenUsage | undefined; reasoningBlock?: ReasoningBlock | undefined; reasoningArtifacts?: readonly ReasoningArtifact[] | undefined; } /** Map shared OpenAI-compatible payload → CompletionResult (includes usage). */ export declare function toCompletionResult(provider: ProviderId, model: string, payload: OpenAiCompatibleResult): import("../types.js").CompletionResult; /** * Reassembles SSE `data:` frames. * * Per the SSE spec a single event's payload may be split across several `data:` * lines that the client must concatenate before parsing; each fragment was * previously parsed on its own, failed `JSON.parse`, and was dropped as a * malformed keepalive — losing content without a trace. * * A payload is released as soon as it is syntactically complete, so * single-line frames behave exactly as before. A blank line (frame terminator) * discards an incomplete remainder, and a runaway fragment is dropped rather * than corrupting later frames. */ export declare function createSseFrameAssembler(options?: { maxBufferedBytes?: number; }): { /** Returns a complete payload, or undefined while still buffering. */ pushLine: (line: string) => string | undefined; }; export declare function toOpenAiMessages(messages: ChatMessage[], supportsVision?: boolean, replay?: { target: ReasoningArtifactReplayTarget; observe?: ReasoningArtifactReplayObserver | undefined; forceScope?: boolean | undefined; }): Array>; export type ReasoningStyle = "openai" | "nvidia" | "openrouter" | "agentrouter" | "modal" | "stepfun" | "meta" | "bynara" | "none"; export { classifyBynaraModel, classifyNvidiaModel, } from "./model-families.js"; export type { BynaraReasoningKind, NvidiaReasoningKind, } from "./model-families.js"; export interface ReasoningControlContext { readonly profile: ReasoningControlSurface; readonly willReplayReasoning: boolean; readonly suppressed?: boolean | undefined; } export declare function buildReasoningPayload(reasoning: ReasoningPreference | undefined, style: ReasoningStyle, model?: string, providerId?: ProviderId | undefined, control?: ReasoningControlContext | undefined): Record; /** * Detects provider errors that mean the model rejected one of our * reasoning/thinking knobs (chat_template_kwargs, enable_thinking, * clear_thinking, reasoning_effort, reasoning_budget, thinking). NVIDIA NIM and * other OpenAI-compatible gateways return a 400/422 for chat templates that do * not accept these fields. When this matches, the router strips the reasoning * payload and retries so an unsupported option never fails the whole request. */ export declare function isReasoningUnsupportedError(error: unknown): boolean; export interface ReasoningRejectionAdvice { mandatory: boolean; acceptedEfforts: readonly ReasoningEffort[]; } export declare function reasoningRejectionAdvice(error: unknown): ReasoningRejectionAdvice | undefined; export declare function isStreamOptionsUnsupportedError(error: unknown): boolean; export declare function isImageInputUnsupportedError(error: unknown): boolean; export declare function stripImagesFromMessages(messages: ChatMessage[]): ChatMessage[]; export declare function imageCapableMessages(provider: ProviderId, model: string, messages: ChatMessage[]): ChatMessage[]; /** * legacy Chat Completions sampling knobs: `max_tokens` must be * `max_completion_tokens`, and `temperature`/`top_p` must be omitted or left * at their default (non-default values return HTTP 400 "Unsupported * parameter"). Matched by model name (not provider) since OpenAI-compatible * gateways that pass these model IDs through to the real OpenAI API hit the * same restriction. * https://help.openai.com/en/articles/5072518 (Chat Completions section). */ export declare function isOpenAiReasoningModel(model: string): boolean; export interface ChatCompletionsBodyOptions { model: string; /** * Canonical provider id. When given, the capability table is consulted so the * wire payload and the UI cannot disagree about whether the model has a * reasoning knob at all. */ providerId?: ProviderId | undefined; messages: ChatMessage[]; maxTokens?: number | undefined; temperature?: number | undefined; stream: boolean; /** Set false only after a compatible endpoint rejects `stream_options`. */ includeStreamUsage?: boolean | undefined; reasoning?: ReasoningPreference | undefined; reasoningStyle?: ReasoningStyle | undefined; supportsVision?: boolean | undefined; tools?: ToolDefinition[] | undefined; toolChoice?: ToolChoice | undefined; parallelToolCalls?: boolean | undefined; replayTarget?: ReasoningArtifactReplayTarget | undefined; reasoningArtifactReplayObserver?: ReasoningArtifactReplayObserver | undefined; forceReasoningReplay?: boolean | undefined; control?: ReasoningControlContext | undefined; outputTokenLimit?: number | undefined; resolvedSampling?: { readonly temperature?: number | undefined; readonly topP?: number | undefined; } | undefined; } export declare function buildChatBody(options: ChatCompletionsBodyOptions): string; /** * Serializes a compiled canonical plan onto the Chat Completions wire. Wire * dialect knobs that the plan intentionally does not model (gateway reasoning * style, stream-usage flag, replay observer) stay serializer-side extras. */ export declare function chatCompletionsBodyFromPlan(plan: RequestPlanV1, extras?: { reasoningStyle?: ReasoningStyle | undefined; includeStreamUsage?: boolean | undefined; reasoningArtifactReplayObserver?: ReasoningArtifactReplayObserver | undefined; forceReasoningReplay?: boolean | undefined; }): string; export declare function openAiCompatibleComplete(options: { provider: string; /** Canonical provider id used for capability lookups. */ providerId: ProviderId; baseUrl: string; apiKey: string; model: string; messages: ChatMessage[]; maxTokens?: number | undefined; temperature?: number | undefined; headers?: Record | undefined; signal?: AbortSignal | undefined; reasoning?: ReasoningPreference | undefined; reasoningStyle?: ReasoningStyle | undefined; tools?: ToolDefinition[] | undefined; toolChoice?: ToolChoice | undefined; parallelToolCalls?: boolean | undefined; /** Optional response-usage aliases for one configured compatible route. */ usageAliases?: CompatibleUsageAliases | undefined; /** Explicit route policy; without one, final-turn artifacts are not retained. */ reasoningArtifactPolicy?: CompatibleReasoningArtifactPolicy | undefined; /** Metadata-only replay decisions; raw artifact payloads are never exposed. */ reasoningArtifactReplayObserver?: ReasoningArtifactReplayObserver | undefined; forceReasoningReplay?: boolean | undefined; }): Promise; export declare function openAiCompatibleStream(options: { provider: string; /** Canonical provider id used for capability lookups. */ providerId: ProviderId; baseUrl: string; apiKey: string; model: string; messages: ChatMessage[]; maxTokens?: number | undefined; temperature?: number | undefined; headers?: Record | undefined; signal?: AbortSignal | undefined; onToken: (token: string) => void; reasoning?: ReasoningPreference | undefined; reasoningStyle?: ReasoningStyle | undefined; tools?: ToolDefinition[] | undefined; toolChoice?: ToolChoice | undefined; parallelToolCalls?: boolean | undefined; onStreamEvent?: ProviderStreamEventSink | undefined; streamTerminal?: StreamTerminalPolicy | undefined; /** Omit usage stream options for strict OpenAI-compatible gateways. */ includeStreamUsage?: boolean | undefined; /** Optional response-usage aliases for one configured compatible route. */ usageAliases?: CompatibleUsageAliases | undefined; /** Explicit route policy; without one, final-turn artifacts are not retained. */ reasoningArtifactPolicy?: CompatibleReasoningArtifactPolicy | undefined; /** Metadata-only replay decisions; raw artifact payloads are never exposed. */ reasoningArtifactReplayObserver?: ReasoningArtifactReplayObserver | undefined; forceReasoningReplay?: boolean | undefined; /** Early native tool-call name/args progress (P2-3). */ onToolCallDelta?: ((delta: { index: number; id?: string; name?: string; argumentsBytes?: number; }) => void) | undefined; /** Abort a stream that delivers no bytes for this long (mid-stream). */ idleTimeoutMs?: number | undefined; initialIdleTimeoutMs?: number | undefined; /** * Abort a stream that delivers bytes but no model output for this long. * Bounds a keepalive-only stream. Defaults to 1.5x the largest byte budget so * it always outlasts the transport watchdog. */ outputIdleTimeoutMs?: number | undefined; }): Promise; export declare function openAiCompatiblePing(baseUrl: string, apiKey: string, headers?: Record | undefined): Promise;