/** * Two failure classes that look alike in the transcript and need different fixes. * * 1. **Authentication** — `code: "AUTH"`, and the message says which side said * no (below). * 2. **Reasoning replay** — a 400 from a thinking-mode gateway that wants the * previous turn's reasoning passed back; the harness drops it when it rebuilds * a long multi-turn history. Reported upstream (deepseek-harness #1780, #231), * so the advice is a workaround, not a fix. * * Where an authentication failure came from. * * A turn can die with `code: "AUTH"` for two very different reasons, and the * difference decides what the reader should do: * * - **provider**: the request left the machine and the provider (or something * between it and the model) rejected it. The local credential is present, so * changing it is the wrong move — the usual causes are a transient upstream * failure, a plan/quota limit, or a model the account may not call. Retrying * is reasonable. * - **local**: nothing usable is configured on this machine, so every request * would fail the same way. Retrying changes nothing; the key has to be set. * * The host reports both as `AUTH`, and the only place the difference survives is * the message body — which is why this is a text classifier and why it is kept * pure: the caller resolves the actual credential and decides what to say. * * Observed bodies (2026-09-29, command-code): * provider → `OpenAI API error (403): {"message":"Authentication failed. * Please check your credentials.","type":"permission_error"}` * provider → `403 {"type":"error","error":{"type":"permission_error", * "message":"MODEL_NOT_IN_PLAN: …"}}` * local → `dsh-llm: no API key for provider "x" (set X_API_KEY)` * A *missing* credential on that gateway answered with a 401 and its own * envelope (`{"success":false,"error":{"code":"UNAUTHORIZED"}}`), which is why * an HTTP status alone cannot classify this: 401 and 403 both appear on the * provider side. * * @module dsh-ssh-tui/auth-failure */ /** Which side rejected the request. */ export type AuthFailureOrigin = 'provider' | 'local'; export interface AuthFailure { origin: AuthFailureOrigin; /** The HTTP status, when the message carried one. */ status?: number; } /** The HTTP status inside a host-formatted failure, if there is one. */ export declare function statusOf(message: string): number | undefined; /** * Classify one failure message. * @param message - the failure text the host attached to the turn. * @returns the origin (and status when known), or undefined when the message * says nothing about authentication — an unrelated failure keeps its own row * and gets no advice. */ export declare function classifyAuthFailure(message: string): AuthFailure | undefined; /** * A thinking-mode gateway refusing a follow-up because the reasoning is missing. * * Seen on the 0.2.0-rc line with the command-code route and `effort: max` * (five times in one long session, always on a *continuation*, never on the * first request of a turn): * * {"type":"invalid_request_error","code":"invalid_request_error", * "message":"The `reasoning_text` in the thinking mode must be passed back * to the API."} * * Both a live probe and the upstream reports agree on the shape: the client is * expected to send the previous assistant turn's reasoning items back, and the * harness does not do that once a session grows long. Nothing the reader can do * fixes the client, but two workarounds exist — no thinking on that route, or * the same gateway's Anthropic-flavoured route, which carries signed thinking * blocks the harness does replay. * @param message - the failure text the host attached to the turn. * @returns true when this is that failure. */ export declare function isReasoningReplayFailure(message: string): boolean; /** * A gateway that refused the request itself, without saying why. * * The sibling of the failure above, and the one a reader actually meets most: the * same thinking-mode route answers a bare envelope with a trace id and nothing else. * Measured in one long session (2026-10-03, command-code, `effort: max`): * * 13 of 14 failed turns were this shape, 12 of them bare * they arrived in runs — eight consecutive turns over twelve minutes, then the * same route, model and key answered normally again * the harness made 14 attempts and 0 retries (a 400 is outside its retryable set) * * Those three facts decide what the plugin may say and may do. It may say the * request body was refused (not the credential) and that the known member of this * family is the reasoning-replay defect. It may *not* claim that sending again will * work: the envelope carries no reason, and a run that deep may equally be a request * the API will keep refusing. What it must not do is retry on its own — the sibling's * case below is deterministic, and this one re-sends a whole context to find out. * * The envelope is deliberately narrow: a 400 whose body names a field * (`unknown field "summary"`) is a different fix and is not this. * @param message - the failure text the host attached to the turn. * @returns true when this is a bare 400 rejection. */ export declare function isRequestRejectedFailure(message: string): boolean;