import { type DialectRefusalSignal } from "./tool-dialects.js"; /** * Final-wire streamed response commit probe. * * A structurally valid SSE preamble is not yet an answer. Keep the downstream head provisional * until the client-facing protocol carries meaningful assistant output, then replay every byte * exactly as received. Before that point, an in-band error or an empty completion is safe to * fail over because the client has observed nothing. */ export type StreamCommitProtocol = "anthropic-messages" | "openai-chat" | "openai-responses"; /** * The classified cause of a dead stream, when the backend STATED one. * * A pre-commit empty stream is the one failure shape the relay cannot explain from its own * behaviour: nothing was written to the client, the provider answered 200, and the bytes carry * only the trace of what happened. Measured 2026-09-10: a streamed `deepseek/deepseek-flash` * request that spent its whole `max_tokens` budget on reasoning and emitted no text answered the * client `502 stream completed without meaningful content`, and the real cause was visible only by * re-sending the request outside the relay and reading the raw response. * * ⚠ Every member here is a STOP REASON THE BACKEND SENT, normalized. There is deliberately no * `unknown` member and no default: the field is ABSENT when the backend stated nothing, because a * guess wearing a classification is exactly what the provenance invariant forbids and this * codebase's most-repeated defect class produces. A caller that finds it absent must fall back to * the generic message rather than filling the gap. */ export type StreamStopCause = /** The backend stopped because it hit the output ceiling — `max_tokens` / `length`. */ "max_tokens" /** The backend stopped by finishing a tool call; the relay saw no committed content. */ | "tool_use"; /** * The metadata-log / wire classifier for a dead stream. A SHORT enum-like string, safe to log: * it says what the backend did, never anything it said. * * ⚠ `stopReason` is the `StreamStopCause` above or `null` when the backend stated none — the two * are different outcomes and the log must be able to tell them apart, which is why the unknown * case is spelled `stop_reason_unknown` rather than reusing a member. */ export interface StreamDeadClassification { stopReason: StreamStopCause | null; /** * How many REASONING tokens the backend reported for this response, when it reported a count. * `null` when no usage frame carried one — never 0, which would read as a measurement. */ reasoningTokens: number | null; } export type StreamCommitProbe = { kind: "ready"; body: ReadableStream; } | { kind: "dead"; reason: string; provenance: "upstream" | "local"; /** * The relay's own error code, when this dead verdict is the relay's decision rather than an * upstream failure. Absent for every ordinary dead stream, so a front that ignores it keeps * serving exactly the generic `api_error` it always did. */ errorType?: string; /** * What the BACKEND said about why it stopped, when it said anything. Absent for every dead * verdict whose cause the backend did not state — a local mapper defect, a probe-limit * overrun, a cancelled read — so a front that ignores it behaves exactly as before. */ classification?: StreamDeadClassification; } | { kind: "cancelled"; }; export interface StreamCommitProbeOptions { /** Client cancellation wins races with EOF/read failures and must never start another target. */ isCancelled?: () => boolean; /** Malformed final wire produced by a response mapper is a local defect, not target health. */ malformedProvenance?: "upstream" | "local"; /** A rejected body read is normally transport/upstream even when parsing is mapper-local. */ readFailureProvenance?: "upstream" | "local"; /** * Set by the dialect-rescue wrapper on THIS stream when it refused a recovered destructive call. * Absent on every lane the relay did not wrap, which is what stops an upstream minting `local` * provenance for itself by echoing the code. See `DialectRefusalSignal`. */ relayRefusal?: DialectRefusalSignal; } /** * The client protocol a front is answering, as far as provenance is concerned. * * Deliberately NOT `ResponseProtocol` (`backend/envelope-validator.ts`), which names the shape a * response mapper reads. This names the shape the CLIENT asked for, and the two front doors do not * offer the same set: `/v1/messages` only ever answers `anthropic-messages`, while the OpenAI front * answers `chat` or `responses`. */ export type FrontProtocol = "anthropic-messages" | "chat" | "responses"; /** * Did the RELAY author the bytes of this response, or did the provider? * * `upstream` means the provider produced them, so a malformed final wire is the PROVIDER's fault: * the outcome is retriable and the walk fails over to the next candidate. `local` means the relay * produced them by translating, so the outcome is TERMINAL — the same line this module draws for a * relay-authored refusal, and the same one `CLAUDE.md` draws when it says a hard cap "is config, * not health". * * ⚠ The rule is ONE rule: the relay authored the bytes unless the response was a byte passthrough, * and a passthrough happens exactly when the target's native protocol is the one the client asked * for. Until 2026-09-06 (CLONE-07) it was spelled twice, once per front, against each front's own * passthrough condition — `openai`-kind means translated on the Anthropic front, while on the * OpenAI front only `openai`-kind PLUS `chat` is a passthrough. The two spellings never disagreed: * a truth table over all six reachable combinations is in * `docs/history/reviews/clone-07-clone-26-evidence-2026-09-05.md`, and `test/stream-commit.test.ts` pins * every row. Naming it once is what stops a fifth call site inventing a seventh row, because a new * front or a new protocol currently has two places to get right and no compiler help. */ export declare function relayAuthoredResponse(targetKind: "anthropic" | "openai", frontProtocol: FrontProtocol): "upstream" | "local"; /** Shared by structural preflight and final-wire commit probing. */ export declare const STREAM_PREFLIGHT_LIMIT: number; /** * The served-error and metadata-log classifier for a dead stream's cause. * * A SHORT enum-like string, quoted verbatim into the error body and carried on the log's * `errorKinds` allow-list. It is derived from the backend's own statement and never from content, * which is what makes it safe to record: see `STOP_CAUSE_ERROR_KINDS`. */ export declare function stopCauseToken(classification: StreamDeadClassification | undefined): string; /** * Name the cause in the operator- and caller-visible message, when the backend stated one. * * The generic sentence is PRESERVED as the prefix rather than replaced. It is what existing * operators, tests and log greps match on, and — more importantly — it remains TRUE: the stream did * complete without meaningful content. The cause is appended, so a reader who only needs "the * backend sent nothing usable" reads exactly what they read before, and a reader who needs to know * WHY no longer has to re-send the request outside the relay to find out. * * ⚠ The reasoning count is quoted only when the backend STATED it, and the wording distinguishes * "produced N reasoning tokens and no text" (measured) from "stopped at max_tokens with no text" * (the stop reason alone). `null` never becomes 0. */ export declare function describeStreamStopCause(classification: StreamDeadClassification): string; /** * Read through metadata-only SSE events until the final wire contains meaningful assistant output. * The returned stream replays the exact raw chunks and continues on the same locked reader. */ export declare function probeStreamForCommit(body: ReadableStream, protocol: StreamCommitProtocol, options?: StreamCommitProbeOptions): Promise;