/** * Thinking/reasoning parameter mapping. * * Different providers express the reasoning budget differently: * * - OpenAI-style (`openai-compatible`, `openrouter`): `reasoning_effort` * (`"low" | "medium" | "high"`), optionally `max_tokens` for the reasoning * budget. * - Anthropic: a `thinking` object `{ enabled: boolean, budget_tokens: number }` * plus `thinking_budget` (must be <= 32768 and > 0). * - Google (Gemini): `reasoning_effort` + `thinking_budget`. * * This module normalizes the provider-agnostic `ThinkingConfig` into the * provider-specific wire format. See CHUNK#8.md ยง3. */ import type { ProviderKind, ThinkingConfig } from "./providers/types.js"; /** The maximum reasoning budget Anthropic accepts. */ const ANTHROPIC_MAX_THINKING_TOKENS = 32768; /** Normalize a thinking config into the wire-format body fragment for a kind. */ export function normalizeThinking( kind: ProviderKind, thinking?: ThinkingConfig, ): Record { if (!thinking?.enabled) { return {}; } switch (kind) { case "anthropic": return normalizeAnthropicThinking(thinking); case "google": return normalizeGoogleThinking(thinking); case "openai-compatible": case "openrouter": default: return normalizeOpenAIThinking(thinking); } } /** OpenAI-style: `reasoning_effort` + optional `max_tokens`. */ function normalizeOpenAIThinking(thinking: ThinkingConfig): Record { const effort = thinking.effort ?? "medium"; const body: Record = { reasoning_effort: effort, }; if (typeof thinking.budget === "number") { body.max_tokens = thinking.budget; } return body; } /** Anthropic: `thinking` object + `thinking_budget`. */ function normalizeAnthropicThinking(thinking: ThinkingConfig): Record { const budget = typeof thinking.budget === "number" ? Math.min(thinking.budget, ANTHROPIC_MAX_THINKING_TOKENS) : 2048; return { thinking: { enabled: true, budget_tokens: budget, }, thinking_budget: budget, }; } /** Google: `reasoning_effort` + `thinking_budget`. */ function normalizeGoogleThinking(thinking: ThinkingConfig): Record { const effort = thinking.effort ?? "medium"; const budget = typeof thinking.budget === "number" ? thinking.budget : 2048; return { reasoning_effort: effort, thinking_budget: budget, }; } /** * Compute the reasoning budget (in tokens) to advertise to the budget engine, * so the output budget accounts for the reasoning spend. Returns 0 when * thinking is disabled. */ export function reasoningBudgetTokens(thinking?: ThinkingConfig): number { if (!thinking?.enabled) { return 0; } return typeof thinking.budget === "number" ? thinking.budget : 2048; }