import type { ThoughtSignatureMode, ToolCallIdMode, ReasoningMode, EffortLevel } from "./config-types.js"; /** * A request shape this mapper will not put on the wire. The caller turns it into a clean 400, * exactly as `DocumentError` does — local origin, provider never asked. */ export declare class RequestMappingError extends Error { constructor(message: string); } export interface AnthropicToOpenAiOptions { /** * The resolved deployment's model id. Absent => NO `model` key is sent. There is deliberately * no fallback to the caller's `body.model`: that is an Anthropic model id naming a deployment * this target does not have, so forwarding it would ask the host for a model nobody selected. * (`config.ts` makes `model` mandatory for an `openai`-kind target, so absence is unreachable * in production; the pre-2026-08-23 path assigned `target.model` unconditionally and therefore * emitted no key at all in that case — this keeps that behaviour identical.) */ model?: string | undefined; /** Whether THIS hop streams — a relay decision, not the caller's. Falls back to the body. */ stream?: boolean | undefined; /** * The RESOLVED outbound tool-call-id shape for this deployment (`config.ts` * `resolveToolCallIdMode`). Absent ⇒ `"preserve"` ⇒ the outbound bytes are identical to what * this mapper emitted before the mode existed. Never a provider name: the mapper is handed a * decision, it does not make one. */ toolCallIds?: ToolCallIdMode | undefined; /** * Called once per run with how many ids the `"strict9"` pass rewrote (0 included). The same * out-param idiom `backend.ts` uses for the streamed `tool_use` mint — the count is metadata * about the translation, not part of the body it returns. */ onToolCallIdsRewritten?: ((count: number) => void) | undefined; /** * The RESOLVED thought-signature mode for this deployment (`config.ts` * `resolveThoughtSignatureMode`). Absent ⇒ `"none"` ⇒ the outbound bytes are identical to what * this mapper emitted before the mode existed. Never a provider name, same rule as * `toolCallIds`. */ thoughtSignature?: ThoughtSignatureMode | undefined; /** * Called once per run with how many tool calls the `"sentinel"` pass stamped (0 included). Same * out-param idiom as `onToolCallIdsRewritten`, and the count is metadata about the translation, * not part of the body it returns. */ onThoughtSignatureSentinels?: ((count: number) => void) | undefined; /** * The RESOLVED reasoning-mapping mode for this deployment (`config.ts` `resolveReasoningMode`). * Absent ⇒ `"none"` ⇒ the outbound bytes are identical to what this mapper emitted before the * mode existed — the caller's `thinking` control and effort are DROPPED, same rule as before. * Never a provider name: the mapper is handed a decision, it does not make one. */ reasoning?: ReasoningMode | undefined; /** * The routed pool's effort band, when this deployment was resolved through a single dynamic pool * whose policy declares an `effort` (`ResolvedTarget.effort`). Only read under `reasoning: * "deepseek"` — the band is what maps to `reasoning_effort`. Absent ⇒ no effort to map. */ effort?: EffortLevel | undefined; /** * Called once per run with 1 when a `"deepseek"` target's NATURAL thinking decision * (`deepSeekThinkingSpec`'s own return) got overridden to `{thinking:{type:"disabled"}}` — a * forced tool choice, or a replayed tool-call turn with no reasoning available to carry forward * (F10/F11, 2026-09-10) — and with 0 otherwise. Only ever fires under `reasoning: "deepseek"`; * the same out-param idiom as `onThoughtSignatureSentinels`. */ onDeepSeekThinkingOverridden?: ((count: number) => void) | undefined; } /** * The hash `strict9` derives an outbound id from — SHA-256 in production, and the ONLY reason this * is a parameter at all is the collision policy below. * * ⚠ **TEST-ONLY SEAM, and it exists because the policy is otherwise unverifiable.** The `#k` * re-hash in `ToolCallIds.map` fires when two source ids land on the same 9-character base62 * value, i.e. at odds of 62⁻⁹, so reaching that branch against real SHA-256 would take a preimage * — the documented behaviour ("collisions resolve deterministically by FIRST-APPEARANCE order") * could regress in either direction with nothing turning red. Injecting the digest lets * `test/tool-call-ids.test.ts` construct the collision directly. Production never passes one: the * default IS SHA-256, and the same test pins a real-SHA mapping so the default path stays covered. */ export type ToolCallIdDigest = (input: string) => Uint8Array; /** * The one per-run source-id → outbound-id map, shared by `toolCall` and `toolResultMessage` so * both halves of a pair always land on the same value (mistral v13 checks that linkage). * * An id that already conforms is kept as-is, so a mistral-native id coming back through a later * turn round-trips unchanged. Collisions resolve deterministically by FIRST-APPEARANCE order: * the run is a single in-order walk of the conversation, so the same conversation always produces * the same assignment. * * ⚠ Exported ONLY so a test can hand it a colliding digest — see `ToolCallIdDigest`. Nothing * outside this module constructs one on the request path; `anthropicRequestToOpenAi` owns the * per-run instance and hands it to both halves of the pair itself. */ export declare class ToolCallIds { private readonly digest; private readonly bySource; private readonly taken; private rewritten; constructor(digest?: ToolCallIdDigest); map(id: string): string; count(): number; } /** * The per-run thought-signature stamper — `null` under `"none"`, which is every provider but * Google's Generative Language API. * * EVERY replayed tool call is stamped, not just the first of a turn. That is the placement the * 2026-08-23 live check verified: the sentinel on the single call of a one-call turn was accepted, * and the sentinel on BOTH entries of a parallel pair was accepted too (which contradicts a public * report that a parallel pair rejects it — against this endpoint and model, on that date, it did * not). Stamping every entry is also the only placement whose correctness does not depend on which * entry the validator happens to inspect. * * Exported so the Responses-wire request mapper (`src/backend.ts`, `anthropicRequestToOpenAiResponses`) * can share it rather than hand-copy the stamping logic — the same reuse `ToolCallIds` already gets. */ export declare class ThoughtSignatures { private stamped; stamp(call: Rec): Rec; count(): number; } type Rec = Record; /** * Translate one Anthropic Messages request body into an OpenAI Chat Completions request body. * * Turn order is preserved exactly; no turn is merged or dropped. Unknown top-level fields are not * forwarded — this is a translation between two contracts, not a passthrough. * * @throws {RequestMappingError} for a block or declaration that cannot be represented. */ export declare function anthropicRequestToOpenAi(reqJson: unknown, opts?: AnthropicToOpenAiOptions): Record; export {};