export type LadderKind = "anthropic" | "openai" | "deepseek" | "gemini" | "ollama"; export type Shape = { /** * - Short stable key (the MODELS table refers to shapes by this). */ key: string; /** * - Human-readable request-field description (error messages, thinkingSupport). */ id: string; /** * - Returns a NEW body with * the field set (copy-on-write; never mutates `body`), or `null` when the shape cannot apply to this body. */ apply: (body: Record) => (Record | null); }; export type ThinkingStatus = "applied" | "unsupported" | "ignored" | "unknown"; /** * What bare-agent knows about switching a model's thinking on/off, with no network. Measured models * answer true/false; a model not in the measured table on a provider with a shape ladder answers * 'unverified' (the ladder will try the shapes at runtime; if they fail with client validation errors a plain request decides: OK ⇒ thinkingStatus 'unsupported', failure ⇒ that error is thrown); * clipipe and unknown providers answer false. * @param {'anthropic'|'openai'|'deepseek'|'gemini'|'ollama'|'clipipe'|string} provider * @param {string} [model] * @param {{ baseUrl?: string }} [opts] - `baseUrl` only matters for provider 'openai': an OpenAI-wire endpoint on a DeepSeek host answers with DeepSeek's shapes, exactly as the request path does (same shared `isDeepSeek`). Omitted ⇒ model-id detection only. A non-object (incl. `null`) throws ValidationError (type only). * @returns {{ switchable: boolean|'unverified', off: string|null, on: string|null, note: string }} * @when you need to know, before building a request, whether a provider/model can have thinking turned on or off and which request field does it — pure lookup, no network * @fails returns switchable:false for clipipe and unknown providers (never throws); 'unverified' means not measured, not unsupported; Ollama's shapes are not live-verified. * @category providers * @example * thinkingSupport('anthropic', 'claude-opus-5-5'); // { switchable: false, off: null, on: "thinking:{type:'adaptive'}", note: '...' } */ export function thinkingSupport(provider: "anthropic" | "openai" | "deepseek" | "gemini" | "ollama" | "clipipe" | string, model?: string, opts?: { baseUrl?: string; }): { switchable: boolean | "unverified"; off: string | null; on: string | null; note: string; }; /** * Validate a `thinkingMode` value. `undefined`/`null` ⇒ `null` (unset); anything but 'on'/'off' throws. * @param {any} value * @param {string} label - provider name for the message * @returns {'on'|'off'|null} */ export function normalizeThinkingMode(value: any, label: string): "on" | "off" | null; /** * Resolve the EFFECTIVE mode for one call and reject the raw-`thinking` conflict (no silent precedence). * Per-call overrides the instance default; `null` suppresses it. * @param {object} p * @param {string} p.label * @param {any} p.instanceMode * @param {Record} p.options - per-call options * @param {any} [p.instanceThinking] - the instance's raw `thinking` (providers that have one) * @param {boolean} [p.hasRawThinking] - whether this provider has a raw `thinking` option at all * @returns {'on'|'off'|null} */ export function resolveThinkingMode({ label, instanceMode, options, instanceThinking, hasRawThinking }: { label: string; instanceMode: any; options: Record; instanceThinking?: any; hasRawThinking?: boolean | undefined; }): "on" | "off" | null; /** * Send a request with the `thinkingMode` field applied. Order: the instance cache (a shape that already * worked for this mode+model), else the table's known-good shape, else the ladder's first; then the * remaining shapes in ladder order (a shape whose apply() returns null is skipped). * * When a shape request fails with a client validation error (400/422, not temperature-named), the next * shape is tried; after the last, ONE plain request (no thinking field) decides: it succeeds ⇒ the * setting was the problem (`track.unsupported = true`, result kept); it fails too ⇒ it was a real error * and the PLAIN request's error is thrown as-is (it is the one without our field). Any other failure * (5xx, 429, timeout, transport, 401/403/404) propagates immediately — no ladder. * * A model the table marks not switchable gets ONE plain request, `unsupported`. Only the winning shape * is cached; "unsupported" is never cached (a runtime unsupported cache poisoned on body-dependent * skips and transcript/signature errors), so an unknown model that cannot switch re-pays its refused * shape requests on every call. * * @param {object} p * @param {LadderKind} p.kind * @param {'on'|'off'} p.mode * @param {string} p.model * @param {Record} p.body - the base body (NOT mutated; each attempt gets a patched copy) * @param {(body: Record) => Promise} p.request * @param {Map} p.cache - per provider instance; holds winning shape keys only * @param {{ unsupported: boolean }} p.track - written: `unsupported` is set true when the call ran without our field * @returns {Promise} the reply data */ export function requestWithThinkingMode({ kind, mode, model, body, request, cache, track }: { kind: LadderKind; mode: "on" | "off"; model: string; body: Record; request: (body: Record) => Promise; cache: Map; track: { unsupported: boolean; }; }): Promise; /** * The outcome of a thinkingMode call: `{thinkingStatus, thinkingApplied}` ready to spread onto a * GenerateResult. `unsupported` ⇒ the call ran without our field. Otherwise 'on' ⇒ 'applied' (an * adaptive model may legitimately choose not to think, so 'on' is never flagged); 'off' ⇒ 'applied' * when the reply shows zero thinking, 'ignored' when thinking happened anyway, 'unknown' when the * signal is unreadable. Warns ONCE per `owner` on unsupported/ignored and never throws. * @param {object} p * @param {LadderKind} p.kind * @param {'on'|'off'} p.mode * @param {any} p.data * @param {boolean} [p.unsupported] * @param {string} [p.reason] - why it is unsupported, when the wording differs (see warnThinkingStatus) * @param {any} p.owner * @param {string} p.label * @param {string} p.model * @returns {{ thinkingStatus: ThinkingStatus, thinkingApplied: boolean|null }} */ export function assessThinking({ kind, mode, data, unsupported, reason, owner, label, model }: { kind: LadderKind; mode: "on" | "off"; data: any; unsupported?: boolean | undefined; reason?: string | undefined; owner: any; label: string; model: string; }): { thinkingStatus: ThinkingStatus; thinkingApplied: boolean | null; }; /** * Derive the legacy `thinkingApplied` boolean from a status (applied⇒true, ignored/unsupported⇒false, unknown⇒null). * @param {ThinkingStatus} status * @returns {boolean|null} */ export function appliedOf(status: ThinkingStatus): boolean | null; /** * Warn ONCE per provider instance when the requested mode did not (fully) take effect. * @param {object} p * @param {any} p.owner - provider instance (holds the warn-once flag) * @param {string} p.label * @param {string} p.model * @param {'on'|'off'} p.mode * @param {ThinkingStatus} p.status * @param {string} [p.reason] - 'forced-tool': the request was sent with thinking explicitly disabled */ export function warnThinkingStatus({ owner, label, model, mode, status, reason }: { owner: any; label: string; model: string; mode: "on" | "off"; status: ThinkingStatus; reason?: string | undefined; }): void; /** * The DeepSeek body for thinking-ON + a forced tool choice: thinking-on is impossible there (the API * 400s), so the request goes out with `thinking:{type:'disabled'}` — the only shape that works with a * forced tool_choice (measured) — and the caller reports `unsupported`. * @param {Record} body * @returns {Record} */ export function deepseekDisabledBody(body: Record): Record; /** * Read the thinking signal off a raw reply: 'thinking' | 'none' | 'unknown'. "Absent field ⇒ none" is * used ONLY where measured: Gemini thoughtsTokenCount (given usageMetadata is present), DeepSeek * reasoning_content, Ollama message.thinking. Anthropic counts thinking/redacted_thinking blocks. * OpenAI reasoning_tokens absent ⇒ 'unknown'. * @param {LadderKind} kind * @param {any} data - raw provider reply * @returns {'thinking'|'none'|'unknown'} */ export function readThinkingSignal(kind: LadderKind, data: any): "thinking" | "none" | "unknown"; /** * @param {any} model * @param {any} baseUrl * @returns {boolean} */ export function isDeepSeek(model: any, baseUrl: any): boolean; /** * Longest-prefix lookup of a model's measured facts. A dated/`-latest` suffix of a listed id counts * (`claude-haiku-4-5-20251001`); any other suffix does NOT (so `gpt-5-chat` never inherits `gpt-5`). * @param {LadderKind} kind * @param {string} model * @returns {{ off: string|null, on: string|null }|null} */ export function lookupModel(kind: LadderKind, model: string): { off: string | null; on: string | null; } | null;