{"repo": "antonioan23/openclaude-atuscode", "files": {"web/src/data/configuration.ts": {"b2e10afbdf8cb6ae97322d1ef80e9140f5173378": {"t": 1785595209.2531545, "n": 3307, "h": "7e4503ef83a56e45", "c": "// Seeded from src/utils/envValidation.ts, src/utils/config.ts, and the README.\n\nexport interface SettingsFile {\n path: string\n scope: string\n notes: string\n}\n\nexport const settingsFiles: SettingsFile[] = [\n {\n path: '~/.claude/settings.json',\n scope: 'user',\n notes: 'Global settings for every project on the machine.',\n },\n {\n path: '.claude/settings.json',\n scope: 'project',\n notes: 'Shared project settings, committed to the repo.',\n },\n {\n path: '.claude/settings.local.json',\n scope: 'local',\n notes: 'Per-machine overrides for one project; typically gitignored.',\n },\n {\n path: '~/.claude/keybindings.json',\n scope: 'user',\n notes: 'Keyboard shortcut overrides — see the keybindings page.',\n },\n {\n path: 'CLAUDE.md / .claude/CLAUDE.md',\n scope: 'project',\n notes: 'Project instructions loaded into context at session start.',\n },\n]\n\nexport interface SettingOption {\n key: string\n description: string\n}\n\nexport const settingOptions: SettingOption[] = [\n { key: 'model', description: \"Default model (alias like 'sonnet' or a full model name).\" },\n { key: 'provider', description: 'Default provider preset for new sessions.' },\n { key: 'effort', description: 'Default effort level: low, medium, high, xhigh, or max.' },\n { key: 'agent', description: 'Default agent for new sessions.' },\n { key: 'permissions', description: 'Allow/deny rules for tools, plus the default permission mode.' },\n { key: 'env', description: 'Environment variables applied to every session.' },\n { key: 'theme', description: 'Terminal color theme.' },\n { key: 'verbose', description: 'Verbose output by default.' },\n { key: 'allowAutoUpdates', description: 'Enable or disable the auto-updater.' },\n { key: 'hooks', description: 'Shell hooks that run on tool events (PreToolUse, PostToolUse, …).' },\n]\n\nexport interface EnvVar {\n name: string\n description: string\n}\n\nexport const envVars: EnvVar[] = [\n { name: 'ANTHROPIC_API_KEY', description: 'Anthropic API key (also the strict auth path in --bare mode).' },\n { name: 'ANTHROPIC_AUTH_TOKEN', description: 'Bearer token alternative to an Anthropic API key.' },\n { name: 'OPENAI_API_KEY', description: 'Key for OpenAI-compatible providers and gateways (incl. Opengateway).' },\n { name: 'OPENAI_BASE_URL', description: 'Base URL of an OpenAI-compatible /v1 endpoint (OpenRouter, LM Studio, LiteLLM, …).' },\n { name: 'OPENAI_MODEL', description: 'Model name to request from the OpenAI-compatible endpoint.' },\n { name: 'GOOGLE_API_KEY', description: 'Google Gemini API key.' },\n { name: 'NEARAI_API_KEY', description: 'NEAR AI unified gateway key.' },\n { name: 'MIMO_API_KEY', description: 'Xiaomi MiMo API key.' },\n { name: 'OPENCODE_API_KEY', description: 'OpenCode Zen / Go gateway key.' },\n { name: 'GITHUB_TOKEN', description: 'GitHub token for GitHub Models and PR workflows.' },\n { name: 'CLAUDE_CONFIG_DIR', description: 'Override the config directory (default ~/.claude).' },\n { name: 'HTTP_PROXY / HTTPS_PROXY', description: 'Route API traffic through a proxy.' },\n { name: 'NODE_EXTRA_CA_CERTS', description: 'Extra CA certificates for corporate TLS interception.' },\n { name: 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC', description: 'Disable non-essential network traffic.' },\n]\n"}}, "src/services/api/openaiShim.ts": {"b2e10afbdf8cb6ae97322d1ef80e9140f5173378": {"t": 1785595279.1297145, "n": 129302, "h": "41f68c711333cd0e", "c": "/**\n * OpenAI-compatible API shim for Claude Code.\n *\n * Translates Anthropic SDK calls (anthropic.beta.messages.create) into\n * OpenAI-compatible chat completion requests and streams back events\n * in the Anthropic streaming format so the rest of the codebase is unaware.\n *\n * Supports: OpenAI, Azure OpenAI, Ollama, LM Studio, OpenRouter,\n * Together, Groq, Fireworks, DeepSeek, Mistral, and any OpenAI-compatible API.\n *\n * Environment variables:\n * CLAUDE_CODE_USE_OPENAI=1 — enable this provider\n * OPENAI_API_KEY=sk-... — API key (optional for local models)\n * OPENAI_AUTH_HEADER=api-key — optional custom auth header name\n * OPENAI_AUTH_HEADER_VALUE=... — optional custom auth header value\n * OPENAI_AUTH_SCHEME=bearer|raw — auth scheme for Authorization/custom header handling\n * OPENAI_API_FORMAT=chat_completions|responses — request format for compatible APIs\n * OPENAI_BASE_URL=http://... — base URL (default: https://api.openai.com/v1)\n * OPENAI_MODEL=gpt-4o — default model override\n * CODEX_API_KEY / ~/.codex/auth.json — Codex auth for codexplan/codexspark\n *\n * GitHub Copilot API (api.githubcopilot.com), OpenAI-compatible:\n * CLAUDE_CODE_USE_GITHUB=1 — enable GitHub inference (no need for USE_OPENAI)\n * GITHUB_TOKEN or GH_TOKEN — Copilot API token (mapped to Bearer auth)\n * OPENAI_MODEL — optional; use github:copilot or openai/gpt-4.1 style IDs\n *\n * Azure OpenAI / Microsoft Foundry (OpenAI-compatible chat):\n * AZURE_OPENAI_API_VERSION — query param for chat/completions (default: 2024-12-01-preview)\n * OPENAI_AZURE_STYLE=1 — force Azure deployment URL + api-key header when the hostname\n * would not otherwise match (for example inference.ml.azure.com)\n */\n\nimport { APIError } from '@anthropic-ai/sdk'\nimport {\n readCodexCredentialsAsync,\n refreshCodexAccessTokenIfNeeded,\n} from '../../utils/codexCredentials.js'\nimport { logForDebugging } from '../../utils/debug.js'\nimport { isBareMode, isEnvTruthy } from '../../utils/envUtils.js'\nimport { resolveGeminiCredential } from '../../utils/geminiAuth.js'\nimport { hydrateGeminiAccessTokenFromSecureStorage } from '../../utils/geminiCredentials.js'\nimport { hydrateGithubModelsTokenFromSecureStorage } from '../../utils/githubModelsCredentials.js'\nimport { resolveXaiAccessToken } from '../../utils/xaiCredentials.js'\nimport { resolveOpenAIShimRuntimeContext } from '../../integrations/runtimeMetadata.js'\nimport {\n isXaiBaseUrl,\n resolveRouteCredentialValue,\n} from '../../integrations/routeMetadata.js'\nimport { getSessionId } from '../../bootstrap/state.js'\nimport {\n createThinkTagFilter,\n stripThinkTags,\n} from './thinkTagSanitizer.js'\nimport {\n codexStreamToAnthropic,\n collectCodexCompletedResponse,\n convertAnthropicMessagesToResponsesInput,\n convertCodexResponseToAnthropicMessage,\n convertToolsToResponsesTools,\n performCodexRequest,\n type AnthropicStreamEvent,\n type AnthropicUsage,\n type ShimCreateParams,\n} from './codexShim.js'\nimport { buildAnthropicUsageFromRawUsage } from './cacheMetrics.js'\nimport { compressToolHistory } from './compressToolHistory.js'\nimport { fetchWithProxyRetry } from './fetchWithProxyRetry.js'\nimport {\n getLocalFastPathConfig,\n getLocalProviderRetryBaseUrls,\n getGithubEndpointType,\n isLikelyOllamaEndpoint,\n isLocalProviderUrl,\n resolveRuntimeCodexCredentials,\n resolveProviderRequest,\n shouldAttemptLocalToollessRetry,\n type LocalFastPathConfig,\n} from './providerConfig.js'\nimport {\n buildOpenAICompatibilityErrorMessage,\n classifyOpenAIHttpFailure,\n classifyOpenAINetworkFailure,\n} from './openaiErrorClassification.js'\nimport { sanitizeSchemaForOpenAICompat } from '../../utils/schemaSanitizer.js'\nimport { redactSecretValueForDisplay } from '../../utils/providerProfile.js'\nimport { shouldRedactUrlQueryParam } from '../../utils/urlRedaction.js'\nimport {\n normalizeToolArguments,\n hasToolFieldMapping,\n} from './toolArgumentNormalization.js'\nimport { logApiCallStart, logApiCallEnd } from '../../utils/requestLogging.js'\nimport {\n createStreamState,\n processStreamChunk,\n getStreamStats,\n} from '../../utils/streamingOptimizer.js'\nimport { stableStringifyJson } from '../../utils/stableStringify.js'\n\ntype SecretValueSource = Partial<{\n OPENAI_API_KEY: string\n OPENAI_AUTH_HEADER_VALUE: string\n CODEX_API_KEY: string\n GEMINI_API_KEY: string\n GOOGLE_API_KEY: string\n GEMINI_ACCESS_TOKEN: string\n MISTRAL_API_KEY: string\n}>\n\nconst GITHUB_429_MAX_RETRIES = 3\nconst GITHUB_429_BASE_DELAY_SEC = 1\nconst GITHUB_429_MAX_DELAY_SEC = 32\nconst GEMINI_API_HOST = 'generativelanguage.googleapis.com'\nconst COPILOT_HEADERS: Record = {\n 'User-Agent': 'GitHubCopilotChat/0.26.7',\n 'Editor-Version': 'vscode/1.99.3',\n 'Editor-Plugin-Version': 'copilot-chat/0.26.7',\n 'Copilot-Integration-Id': 'vscode-chat',\n}\n\nfunction isGithubModelsMode(): boolean {\n return isEnvTruthy(process.env.CLAUDE_CODE_USE_GITHUB)\n}\n\nfunction filterAnthropicHeaders(\n headers: Record | undefined,\n): Record {\n if (!headers) return {}\n\n const filtered: Record = {}\n for (const [key, value] of Object.entries(headers)) {\n const lower = key.toLowerCase()\n if (\n lower.startsWith('x-anthropic') ||\n lower.startsWith('anthropic-') ||\n lower.startsWith('x-claude') ||\n lower === 'x-app' ||\n lower === 'x-client-app' ||\n lower === 'authorization' ||\n lower === 'x-api-key' ||\n lower === 'api-key'\n ) {\n continue\n }\n filtered[key] = value\n }\n\n return filtered\n}\n\nfunction hasGeminiApiHost(baseUrl: string | undefined): boolean {\n if (!baseUrl) return false\n\n try {\n return new URL(baseUrl).hostname.toLowerCase() === GEMINI_API_HOST\n } catch {\n return false\n }\n}\n\nfunction isGeminiModelName(model: string | undefined): boolean {\n const normalized = model?.trim().toLowerCase()\n return (\n normalized?.startsWith('google/gemini-') === true ||\n normalized?.startsWith('gemini-') === true\n )\n}\n\nfunction shouldPreserveGeminiThoughtSignature(\n model: string | undefined,\n baseUrl?: string,\n): boolean {\n return isGeminiMode() || hasGeminiApiHost(baseUrl) || isGeminiModelName(model)\n}\n\nfunction geminiThoughtSignatureFromExtraContent(\n extraContent: unknown,\n): string | undefined {\n if (!extraContent || typeof extraContent !== 'object') return undefined\n const google = (extraContent as Record).google\n if (!google || typeof google !== 'object') return undefined\n const signature = (google as Record).thought_signature\n return typeof signature === 'string' && signature.length > 0 ? signature : undefined\n}\n\nfunction mergeGeminiThoughtSignature(\n extraContent: Record | undefined,\n signature: string | undefined,\n): Record | undefined {\n if (!signature) return extraContent\n const existingGoogle =\n extraContent?.google && typeof extraContent.google === 'object'\n ? extraContent.google as Record\n : {}\n return {\n ...extraContent,\n google: {\n ...existingGoogle,\n thought_signature: signature,\n },\n }\n}\n\nfunction hasCerebrasApiHost(baseUrl: string | undefined): boolean {\n if (!baseUrl) return false\n\n try {\n const host = new URL(baseUrl).hostname.toLowerCase()\n return host === 'api.cerebras.ai' || host.endsWith('.cerebras.ai')\n } catch {\n return false\n }\n}\n\nfunction normalizeDeepSeekReasoningEffort(\n effort: 'low' | 'medium' | 'high' | 'xhigh',\n): 'high' | 'max' {\n return effort === 'xhigh' ? 'max' : 'high'\n}\n\nfunction formatRetryAfterHint(response: Response): string {\n const ra = response.headers.get('retry-after')\n return ra ? ` (Retry-After: ${ra})` : ''\n}\n\nfunction redactUrlForDiagnostics(url: string): string {\n try {\n const parsed = new URL(url)\n if (parsed.username) {\n parsed.username = 'redacted'\n }\n if (parsed.password) {\n parsed.password = 'redacted'\n }\n\n for (const key of parsed.searchParams.keys()) {\n if (shouldRedactUrlQueryParam(key)) {\n parsed.searchParams.set(key, 'redacted')\n }\n }\n\n const serialized = parsed.toString()\n return redactSecretValueForDisplay(serialized, process.env as SecretValueSource) ?? serialized\n } catch {\n return redactSecretValueForDisplay(url, process.env as SecretValueSource) ?? url\n }\n}\n\nfunction redactUrlsInMessage(message: string): string {\n return message.replace(/https?:\\/\\/\\S+/g, match => redactUrlForDiagnostics(match))\n}\n\nfunction sleepMs(ms: number): Promise {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n// ---------------------------------------------------------------------------\n// Types — minimal subset of Anthropic SDK types we need to produce\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Message format conversion: Anthropic → OpenAI\n// ---------------------------------------------------------------------------\n\ninterface OpenAIMessage {\n role: 'system' | 'user' | 'assistant' | 'tool'\n content?: string | OpenAIContentPart[]\n tool_calls?: Array<{\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n extra_content?: Record\n }>\n tool_call_id?: string\n name?: string\n /**\n * Per-assistant-message chain-of-thought, attached when echoing an\n * assistant message back to providers that require it (notably Moonshot:\n * \"thinking is enabled but reasoning_content is missing in assistant\n * tool call message at index N\" 400). Derived from the Anthropic thinking\n * block captured when the original response was translated.\n */\n reasoning_content?: string\n}\n\ntype OpenAIContentPart =\n | { type: 'text'; text: string }\n | { type: 'image_url'; image_url: { url: string } }\n\ninterface OpenAITool {\n type: 'function'\n function: {\n name: string\n description: string\n parameters: Record\n strict?: boolean\n }\n}\n\nfunction convertSystemPrompt(\n system: unknown,\n): string {\n if (!system) return ''\n if (typeof system === 'string') return system\n if (Array.isArray(system)) {\n return system\n .map((block: { type?: string; text?: string }) =>\n block.type === 'text' ? block.text ?? '' : '',\n )\n // Drop the Anthropic billing/attribution block — it's only meaningful to\n // Anthropic's `_parse_cc_header` and is dead weight (plus a churning\n // per-build fingerprint that busts prefix KV cache) for OpenAI-compat\n // providers like local Ollama / llama.cpp / Codex pass-throughs.\n .filter(text => !text.startsWith('x-anthropic-billing-header'))\n .join('\\n\\n')\n }\n return String(system)\n}\n\nfunction ensureTextPartForImageContent(\n parts: OpenAIContentPart[],\n): OpenAIContentPart[] {\n const hasImage = parts.some(part => part.type === 'image_url')\n if (!hasImage) {\n return parts\n }\n\n const hasText = parts.some(\n part => part.type === 'text' && (part.text ?? '').trim().length > 0,\n )\n if (hasText) {\n return parts\n }\n\n return [{ type: 'text', text: 'Image attached.' }, ...parts]\n}\n\nfunction joinTextContentParts(parts: OpenAIContentPart[]): string {\n return parts.map(part => part.type === 'text' ? part.text : '').join('')\n}\n\nfunction convertToolResultContent(\n content: unknown,\n isError?: boolean,\n): string | OpenAIContentPart[] {\n if (typeof content === 'string') {\n return isError ? `Error: ${content}` : content\n }\n if (!Array.isArray(content)) {\n const text = JSON.stringify(content ?? '')\n return isError ? `Error: ${text}` : text\n }\n\n const parts: OpenAIContentPart[] = []\n for (const block of content) {\n if (block?.type === 'text' && typeof block.text === 'string') {\n parts.push({ type: 'text', text: block.text })\n continue\n }\n\n // ToolSearch results are tool_reference blocks with no text payload —\n // render them so the model learns which deferred tools were loaded\n // (their schemas arrive in the next request's tools array).\n if (block?.type === 'tool_reference' && typeof block.tool_name === 'string') {\n parts.push({\n type: 'text',\n text: `Tool \"${block.tool_name}\" is now loaded and available to call.`,\n })\n continue\n }\n\n if (block?.type === 'image') {\n const source = block.source\n if (source?.type === 'url' && source.url) {\n parts.push({ type: 'image_url', image_url: { url: source.url } })\n } else if (source?.type === 'base64' && source.media_type && source.data) {\n parts.push({\n type: 'image_url',\n image_url: {\n url: `data:${source.media_type};base64,${source.data}`,\n },\n })\n }\n continue\n }\n\n if (typeof block?.text === 'string') {\n parts.push({ type: 'text', text: block.text })\n }\n }\n\n if (parts.length === 0) return ''\n if (parts.length === 1 && parts[0].type === 'text') {\n const text = parts[0].text ?? ''\n return isError ? `Error: ${text}` : text\n }\n\n // Collapse arrays of only text blocks into a single string for DeepSeek\n // compatibility (issue #774). DeepSeek rejects arrays in role: \"tool\" messages.\n const allText = parts.every(p => p.type === 'text')\n if (allText) {\n const text = parts.map(p => p.text ?? '').join('\\n\\n')\n return isError ? `Error: ${text}` : text\n }\n\n if (isError && parts[0]?.type === 'text') {\n parts[0] = { ...parts[0], text: `Error: ${parts[0].text ?? ''}` }\n } else if (isError) {\n parts.unshift({ type: 'text', text: 'Error:' })\n }\n\n return parts\n}\n\nfunction convertContentBlocks(\n content: unknown,\n): string | OpenAIContentPart[] {\n if (typeof content === 'string') return content\n if (!Array.isArray(content)) return String(content ?? '')\n\n const parts: OpenAIContentPart[] = []\n for (const block of content) {\n switch (block.type) {\n case 'text':\n parts.push({ type: 'text', text: block.text ?? '' })\n break\n case 'image': {\n const src = block.source\n if (src?.type === 'base64') {\n parts.push({\n type: 'image_url',\n image_url: {\n url: `data:${src.media_type};base64,${src.data}`,\n },\n })\n } else if (src?.type === 'url') {\n parts.push({ type: 'image_url', image_url: { url: src.url } })\n }\n break\n }\n case 'tool_use':\n // handled separately\n break\n case 'tool_result':\n // handled separately\n break\n case 'thinking':\n case 'redacted_thinking':\n // Strip thinking blocks for OpenAI-compatible providers.\n // These are Anthropic-specific content types that 3P providers\n // don't understand. Serializing them as text corrupts\n // multi-turn context: the model sees the tags as part of its\n // previous reply and may mimic or misattribute them.\n break\n default:\n if (block.text) {\n parts.push({ type: 'text', text: block.text })\n }\n }\n }\n\n if (parts.length === 0) return ''\n if (parts.length === 1 && parts[0].type === 'text') return parts[0].text ?? ''\n\n // Collapse arrays of only text blocks into a single string for DeepSeek\n // compatibility (issue #774).\n const allText = parts.every(p => p.type === 'text')\n if (allText) {\n return parts.map(p => p.text ?? '').join('\\n\\n')\n }\n\n return ensureTextPartForImageContent(parts)\n}\n\nfunction isGeminiMode(): boolean {\n return (\n isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI) ||\n hasGeminiApiHost(process.env.OPENAI_BASE_URL)\n )\n}\n\nfunction hydrateOpenAIShimCompatibilityEnv(\n processEnv: NodeJS.ProcessEnv = process.env,\n): void {\n // Provider selection, base URL defaults, and model defaults now flow\n // through resolveProviderRequest(). The shim still needs a few legacy\n // credential aliases because downstream auth/header paths read OPENAI_*.\n if (isEnvTruthy(processEnv.CLAUDE_CODE_USE_GEMINI)) {\n const geminiApiKey =\n processEnv.GEMINI_API_KEY ?? processEnv.GOOGLE_API_KEY\n if (geminiApiKey && !processEnv.OPENAI_API_KEY) {\n processEnv.OPENAI_API_KEY = geminiApiKey\n }\n return\n }\n\n if (isEnvTruthy(processEnv.CLAUDE_CODE_USE_MISTRAL)) {\n if (processEnv.MISTRAL_API_KEY && !processEnv.OPENAI_API_KEY) {\n processEnv.OPENAI_API_KEY = processEnv.MISTRAL_API_KEY\n }\n return\n }\n\n if (isEnvTruthy(processEnv.CLAUDE_CODE_USE_GITHUB)) {\n processEnv.OPENAI_API_KEY ??=\n processEnv.GITHUB_TOKEN ?? processEnv.GH_TOKEN ?? ''\n return\n }\n\n if (processEnv.BANKR_BASE_URL && !processEnv.OPENAI_BASE_URL) {\n processEnv.OPENAI_BASE_URL = processEnv.BANKR_BASE_URL\n }\n if (processEnv.BANKR_MODEL && !processEnv.OPENAI_MODEL) {\n processEnv.OPENAI_MODEL = processEnv.BANKR_MODEL\n }\n\n const routeCredential = resolveRouteCredentialValue({\n processEnv,\n baseUrl: processEnv.OPENAI_BASE_URL ?? processEnv.OPENAI_API_BASE,\n })\n if (routeCredential && !processEnv.OPENAI_API_KEY) {\n processEnv.OPENAI_API_KEY = routeCredential\n }\n}\n\nfunction convertMessages(\n messages: Array<{\n role: string\n message?: { role?: string; content?: unknown }\n content?: unknown\n }>,\n system: unknown,\n options?: {\n preserveReasoningContent?: boolean\n reasoningContentFallback?: '' | 'omit'\n preserveGeminiThoughtSignature?: boolean\n },\n): OpenAIMessage[] {\n const preserveReasoningContent = options?.preserveReasoningContent === true\n const reasoningContentFallback = options?.reasoningContentFallback\n const preserveGeminiThoughtSignature = options?.preserveGeminiThoughtSignature === true\n const result: OpenAIMessage[] = []\n const knownToolCallIds = new Set()\n\n // Pre-scan for all tool results in the history to identify valid tool calls\n const toolResultIds = new Set()\n for (const msg of messages) {\n const inner = msg.message ?? msg\n const content = (inner as { content?: unknown }).content\n if (Array.isArray(content)) {\n for (const block of content) {\n if (\n (block as { type?: string }).type === 'tool_result' &&\n (block as { tool_use_id?: string }).tool_use_id\n ) {\n toolResultIds.add((block as { tool_use_id: string }).tool_use_id)\n }\n }\n }\n }\n\n // System message first\n const sysText = convertSystemPrompt(system)\n if (sysText) {\n result.push({ role: 'system', content: sysText })\n }\n\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i]\n const isLastInHistory = i === messages.length - 1\n\n // Claude Code wraps messages in { role, message: { role, content } }\n const inner = msg.message ?? msg\n const role = (inner as { role?: string }).role ?? msg.role\n const content = (inner as { content?: unknown }).content\n\n if (role === 'user') {\n // Check for tool_result blocks in user messages\n if (Array.isArray(content)) {\n const toolResults = content.filter(\n (b: { type?: string }) => b.type === 'tool_result',\n )\n const otherContent = content.filter(\n (b: { type?: string }) => b.type !== 'tool_result',\n )\n\n // Emit tool results as tool messages, but ONLY if we have a matching tool_use ID.\n // Mistral/OpenAI strictly require tool messages to follow an assistant message with tool_calls.\n // If the user interrupted (ESC) and a synthetic tool_result was generated without a recorded tool_use,\n // emitting it here would cause a \"role must alternate\" or \"unexpected role\" error.\n for (const tr of toolResults) {\n const id = tr.tool_use_id ?? 'unknown'\n if (knownToolCallIds.has(id)) {\n result.push({\n role: 'tool',\n tool_call_id: id,\n content: convertToolResultContent(tr.content, tr.is_error),\n })\n } else {\n logForDebugging(\n `Dropping orphan tool_result for ID: ${id} to prevent API error`,\n )\n }\n }\n\n // Emit remaining user content\n if (otherContent.length > 0) {\n result.push({\n role: 'user',\n content: convertContentBlocks(otherContent),\n })\n }\n } else {\n result.push({\n role: 'user',\n content: convertContentBlocks(content),\n })\n }\n } else if (role === 'assistant') {\n // Check for tool_use blocks\n if (Array.isArray(content)) {\n const toolUses = content.filter(\n (b: { type?: string }) => b.type === 'tool_use',\n )\n const thinkingBlock = content.find(\n (b: { type?: string }) =>\n b.type === 'thinking' ||\n b.type === 'redacted_thinking',\n )\n const textContent = content.filter(\n (b: { type?: string }) =>\n b.type !== 'tool_use' &&\n b.type !== 'thinking' &&\n b.type !== 'redacted_thinking',\n )\n\n const assistantMsg: OpenAIMessage = {\n role: 'assistant',\n content: (() => {\n const c = convertContentBlocks(textContent)\n return typeof c === 'string'\n ? c\n : Array.isArray(c)\n ? joinTextContentParts(c)\n : ''\n })(),\n }\n\n // Providers that validate reasoning continuity (Moonshot/Kimi Code: \"thinking\n // is enabled but reasoning_content is missing in assistant tool call\n // message at index N\" 400) need the original chain-of-thought echoed\n // back on each assistant message that carries a tool_call. We kept\n // the thinking block on the Anthropic side; re-attach it here as the\n // `reasoning_content` field on the outgoing OpenAI-shaped message.\n // Gated per-provider because other endpoints either ignore the field\n // (harmless) or strict-reject unknown fields (harmful).\n if (preserveReasoningContent) {\n // `thinking` blocks carry their content in `.thinking`; `redacted_thinking`\n // blocks carry it in `.data` (see thinkingTokenExtractor, token estimation,\n // and message-size accounting). Read the right field per type so a real\n // redacted block with non-empty content is not silently dropped to \"\".\n const block = thinkingBlock as\n | { type?: string; thinking?: string; data?: string }\n | undefined\n const thinkingText =\n block?.type === 'redacted_thinking'\n ? block?.data\n : block?.thinking\n if (typeof thinkingText === 'string' && thinkingText.trim().length > 0) {\n assistantMsg.reasoning_content = thinkingText\n } else if (\n toolUses.length > 0 &&\n reasoningContentFallback === ''\n ) {\n assistantMsg.reasoning_content = ''\n }\n }\n\n if (toolUses.length > 0) {\n const mappedToolCalls = toolUses\n .map(\n (tu: {\n id?: string\n name?: string\n input?: unknown\n extra_content?: Record\n signature?: string\n }) => {\n const id = tu.id ?? `call_${crypto.randomUUID().replace(/-/g, '')}`\n\n // Only keep tool calls that have a corresponding result in the history,\n // or if it's the last message (prefill scenario).\n // Orphaned tool calls (e.g. from user interruption) cause 400 errors.\n if (!toolResultIds.has(id) && !isLastInHistory) {\n return null\n }\n\n knownToolCallIds.add(id)\n const toolCall: NonNullable<\n OpenAIMessage['tool_calls']\n >[number] = {\n id,\n type: 'function' as const,\n function: {\n name: tu.name ?? 'unknown',\n arguments:\n typeof tu.input === 'string'\n ? tu.input\n : JSON.stringify(tu.input ?? {}),\n },\n }\n\n // Preserve existing extra_content if present\n if (tu.extra_content) {\n toolCall.extra_content = { ...tu.extra_content }\n }\n\n // Gemini OpenAI-compatible endpoints require Google's\n // thought_signature to be replayed with prior function-call\n // parts. Preserve only real signatures received from the\n // provider; synthetic placeholders are rejected by GMI.\n if (preserveGeminiThoughtSignature) {\n const signature =\n tu.signature ??\n geminiThoughtSignatureFromExtraContent(tu.extra_content) ??\n (thinkingBlock as { signature?: string } | undefined)?.signature\n\n toolCall.extra_content = mergeGeminiThoughtSignature(\n toolCall.extra_content,\n signature,\n )\n }\n\n return toolCall\n },\n )\n .filter((tc): tc is NonNullable => tc !== null)\n\n if (mappedToolCalls.length > 0) {\n assistantMsg.tool_calls = mappedToolCalls\n }\n }\n\n // Only push assistant message if it has content or tool calls.\n // Stripped thinking-only blocks from user interruptions are empty and cause 400s.\n if (assistantMsg.content || assistantMsg.tool_calls?.length) {\n result.push(assistantMsg)\n }\n } else {\n const assistantMsg: OpenAIMessage = {\n role: 'assistant',\n content: (() => {\n const c = convertContentBlocks(content)\n return typeof c === 'string'\n ? c\n : Array.isArray(c)\n ? joinTextContentParts(c)\n : ''\n })(),\n }\n\n if (assistantMsg.content) {\n result.push(assistantMsg)\n }\n }\n }\n }\n\n // Coalescing pass: merge consecutive messages of the same role.\n // OpenAI/vLLM/Ollama require strict user↔assistant alternation.\n // Multiple consecutive tool messages are allowed (assistant → tool* → user).\n // Consecutive user or assistant messages must be merged to avoid Jinja\n // template errors like \"roles must alternate\" (Devstral, Mistral models).\n const coalesced: OpenAIMessage[] = []\n for (const msg of result) {\n const prev = coalesced[coalesced.length - 1]\n\n // Mistral/Devstral: 'tool' message must be followed by an 'assistant' message.\n // If a 'tool' result is followed by a 'user' message, inject a neutral\n // assistant boundary to satisfy the strict role sequence without implying\n // that the user interrupted or cancelled anything:\n // ... -> assistant (calls) -> tool (results) -> assistant (semantic) -> user (next)\n if (prev && prev.role === 'tool' && msg.role === 'user') {\n coalesced.push({\n role: 'assistant',\n content: '[Tool results received]',\n })\n }\n\n const lastAfterPossibleInjection = coalesced[coalesced.length - 1]\n if (\n lastAfterPossibleInjection &&\n lastAfterPossibleInjection.role === msg.role &&\n msg.role !== 'tool' &&\n msg.role !== 'system'\n ) {\n const prevContent = lastAfterPossibleInjection.content\n const curContent = msg.content\n\n if (typeof prevContent === 'string' && typeof curContent === 'string') {\n lastAfterPossibleInjection.content =\n prevContent + (prevContent && curContent ? '\\n' : '') + curContent\n } else {\n const toArray = (\n c: string | OpenAIContentPart[] | undefined,\n ): OpenAIContentPart[] => {\n if (!c) return []\n if (typeof c === 'string') return c ? [{ type: 'text', text: c }] : []\n return c\n }\n lastAfterPossibleInjection.content = [\n ...toArray(prevContent),\n ...toArray(curContent),\n ]\n }\n\n if (msg.tool_calls?.length) {\n lastAfterPossibleInjection.tool_calls = [\n ...(lastAfterPossibleInjection.tool_calls ?? []),\n ...msg.tool_calls,\n ]\n }\n } else {\n coalesced.push(msg)\n }\n }\n\n return coalesced\n}\n\n/**\n * OpenAI requires every key in `properties` to also appear in `required`.\n * Anthropic schemas often mark fields as optional (omitted from `required`),\n * which causes 400 errors on OpenAI/Codex endpoints. This normalizes the\n * schema by ensuring `required` is a superset of `properties` keys.\n */\nfunction normalizeSchemaForOpenAI(\n schema: Record,\n strict = true,\n): Record {\n const record = sanitizeSchemaForOpenAICompat(schema)\n\n if (record.type === 'object' && record.properties) {\n const properties = record.properties as Record>\n const existingRequired = Array.isArray(record.required) ? record.required as string[] : []\n\n // Recurse into each property\n const normalizedProps: Record = {}\n for (const [key, value] of Object.entries(properties)) {\n normalizedProps[key] = normalizeSchemaForOpenAI(\n value as Record,\n strict,\n )\n }\n record.properties = normalizedProps\n\n if (strict) {\n // Keep only the properties that were originally marked required in the schema.\n // Adding every property to required[] (the previous behaviour) caused strict\n // OpenAI-compatible providers (Groq, Azure, etc.) to reject tool calls because\n // the model correctly omits optional arguments — but the provider treats them\n // as missing required fields and returns a 400 / tool_use_failed error.\n record.required = existingRequired.filter(k => k in normalizedProps)\n // additionalProperties: false is still required by strict-mode providers.\n record.additionalProperties = false\n } else {\n // For Gemini: keep only existing required keys that are present in properties\n record.required = existingRequired.filter(k => k in normalizedProps)\n }\n }\n\n // Recurse into array items\n if ('items' in record) {\n if (Array.isArray(record.items)) {\n record.items = (record.items as unknown[]).map(\n item => normalizeSchemaForOpenAI(item as Record, strict),\n )\n } else {\n record.items = normalizeSchemaForOpenAI(record.items as Record, strict)\n }\n }\n\n // Recurse into combinators\n for (const key of ['anyOf', 'oneOf', 'allOf'] as const) {\n if (key in record && Array.isArray(record[key])) {\n record[key] = (record[key] as unknown[]).map(\n item => normalizeSchemaForOpenAI(item as Record, strict),\n )\n }\n }\n\n return record\n}\n\nfunction convertTools(\n tools: Array<{ name: string; description?: string; input_schema?: Record }>,\n options: { skipStrict?: boolean } = {},\n): OpenAITool[] {\n const isGemini = isGeminiMode()\n const strict =\n !isGemini &&\n !isEnvTruthy(process.env.ATUSCODE_DISABLE_STRICT_TOOLS) &&\n !options.skipStrict\n\n return tools\n .filter(t => t.name !== 'ToolSearchTool') // Not relevant for OpenAI\n .map(t => {\n const schema = { ...(t.input_schema ?? { type: 'object', properties: {} }) } as Record\n\n // For Codex/OpenAI: promote known Agent sub-fields into required[] only if\n // they actually exist in properties (Gemini rejects required keys absent from properties).\n if (t.name === 'Agent' && schema.properties) {\n const props = schema.properties as Record\n if (!Array.isArray(schema.required)) schema.required = []\n const req = schema.required as string[]\n for (const key of ['message', 'subagent_type']) {\n if (key in props && !req.includes(key)) req.push(key)\n }\n }\n\n return {\n type: 'function' as const,\n function: {\n name: t.name,\n description: t.description ?? '',\n parameters: normalizeSchemaForOpenAI(schema, strict),\n },\n }\n })\n}\n\n// ---------------------------------------------------------------------------\n// Streaming: OpenAI SSE → Anthropic stream events\n// ---------------------------------------------------------------------------\n\ninterface OpenAIStreamChunk {\n id: string\n object: string\n model: string\n choices: Array<{\n index: number\n delta: {\n role?: string\n content?: string | null\n reasoning_content?: string | null\n extra_content?: Record\n tool_calls?: Array<{\n index: number\n id?: string\n type?: string\n function?: { name?: string; arguments?: string }\n extra_content?: Record\n }>\n }\n finish_reason: string | null\n }>\n usage?: {\n prompt_tokens?: number\n completion_tokens?: number\n total_tokens?: number\n prompt_tokens_details?: {\n cached_tokens?: number\n }\n }\n}\n\nfunction makeMessageId(): string {\n return `msg_${crypto.randomUUID().replace(/-/g, '')}`\n}\n\nfunction convertChunkUsage(\n usage: OpenAIStreamChunk['usage'] | undefined,\n): Partial | undefined {\n if (!usage) return undefined\n // Delegates to the shared helper so this path, codexShim.makeUsage,\n // the non-streaming response below, and the integration tests all\n // produce byte-identical output for the same raw input.\n return buildAnthropicUsageFromRawUsage(\n usage as unknown as Record,\n )\n}\n\nconst JSON_REPAIR_SUFFIXES = [\n '}', '\"}', ']}', '\"]}', '}}', '\"}}', ']}}', '\"]}}', '\"]}]}', '}]}'\n]\n\nconst RAW_TOOL_CALLS_REQUESTED_PREFIX = 'Tool calls requested:'\n\ntype ParsedRawToolCall = {\n id: string\n name: string\n argumentsJson: string\n}\n\nfunction couldBeRawToolCallsRequestedPrefix(text: string): boolean {\n const trimmedStart = text.trimStart()\n return (\n RAW_TOOL_CALLS_REQUESTED_PREFIX.startsWith(trimmedStart) ||\n trimmedStart.startsWith(RAW_TOOL_CALLS_REQUESTED_PREFIX)\n )\n}\n\nfunction parseRawToolCallsRequestedText(text: string): ParsedRawToolCall[] | null {\n const trimmed = text.trim()\n if (!trimmed.startsWith(RAW_TOOL_CALLS_REQUESTED_PREFIX)) {\n return null\n }\n\n const lines = trimmed\n .slice(RAW_TOOL_CALLS_REQUESTED_PREFIX.length)\n .split(/\\r?\\n/)\n .map(line => line.trim())\n .filter(Boolean)\n\n if (lines.length === 0) return null\n\n const toolCalls: ParsedRawToolCall[] = []\n for (const line of lines) {\n const match = line.match(\n /^-\\s*([A-Za-z_][A-Za-z0-9_.-]*)\\(([\\s\\S]*)\\)\\s*\\[id:\\s*([^\\]\\s]+)\\]\\s*$/,\n )\n if (!match) return null\n\n const [, name, rawArguments, id] = match\n if (!name || !id || rawArguments === undefined) return null\n\n const normalizedArguments = normalizeToolArguments(name, rawArguments)\n toolCalls.push({\n id,\n name,\n argumentsJson: JSON.stringify(normalizedArguments ?? {}),\n })\n }\n\n return toolCalls.length > 0 ? toolCalls : null\n}\n\nfunction repairPossiblyTruncatedObjectJson(raw: string): string | null {\n try {\n const parsed = JSON.parse(raw)\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? raw\n : null\n } catch {\n for (const combo of JSON_REPAIR_SUFFIXES) {\n try {\n const repaired = raw + combo\n const parsed = JSON.parse(repaired)\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return repaired\n }\n } catch {}\n }\n return null\n }\n}\n\n// ---------------------------------------------------------------------------\n// Ollama text-based tool call parser (fix for #1053)\n//\n// When Ollama models cannot emit structured tool_calls via the OpenAI-compat\n// API, they fall back to printing the call as a JSON block in the response\n// text. This parser extracts those calls so the agent loop can execute them.\n//\n// Supported formats emitted by qwen2.5-coder, llama3.x, phi-4, gemma:\n// ```json\\n{\"name\":\"X\",\"arguments\":{...}}\\n```\n// {\"name\":\"X\",\"arguments\":{...}}\n// {\"type\":\"function\",\"function\":{\"name\":\"X\",\"arguments\":{...}}}\n// ---------------------------------------------------------------------------\n\n// Fenced code block arm: non-greedy is safe because ``` acts as terminator.\nconst FENCED_TOOL_CALL_RE = /```(?:json)?\\s*\\n?\\s*(\\{[\\s\\S]*?\\})\\s*\\n?\\s*```/g\n// Bare JSON arm: marks candidate start positions only; balanced extraction follows.\n// Allow optional whitespace (including newlines) before the property key so\n// pretty-printed objects like \"{\\n \\\"name\\\":\" are detected.\nconst BARE_TOOL_CALL_START_RE = /\\{\\s*\"(?:name|type)\"\\s*:/g\n\ninterface ParsedTextToolCall {\n id: string\n name: string\n arguments: Record\n}\n\n// Module-level counter ensures unique IDs across calls within a session.\nlet _textToolCallCounter = 0\n\n// Walks forward from `start` (which must be `{`) tracking string/escape/brace\n// state and returns the substring up to and including the matching `}`, or\n// null if the braces are never balanced (truncated input).\nfunction extractBalancedJson(text: string, start: number): string | null {\n let depth = 0\n let inString = false\n let escape = false\n for (let i = start; i < text.length; i++) {\n const c = text[i]!\n if (escape) { escape = false; continue }\n if (c === '\\\\' && inString) { escape = true; continue }\n if (c === '\"') { inString = !inString; continue }\n if (inString) continue\n if (c === '{') depth++\n else if (c === '}') {\n depth--\n if (depth === 0) return text.slice(start, i + 1)\n }\n }\n return null\n}\n\nfunction parseAndAdd(\n raw: string,\n results: ParsedTextToolCall[],\n seen: Set,\n): boolean {\n let obj: Record\n try {\n obj = JSON.parse(raw)\n } catch {\n return false\n }\n\n let name: string | undefined\n let args: Record = {}\n\n if (typeof obj['name'] === 'string') {\n // {\"name\": \"X\", \"arguments\": {...}}\n name = obj['name'] as string\n args = (obj['arguments'] as Record) ?? {}\n } else if (\n obj['type'] === 'function' &&\n typeof (obj['function'] as any)?.name === 'string'\n ) {\n // {\"type\":\"function\",\"function\":{\"name\":\"X\",\"arguments\":{...}}}\n const fn = obj['function'] as { name: string; arguments?: unknown }\n name = fn.name\n const rawArgs = fn.arguments\n args =\n typeof rawArgs === 'string'\n ? (() => {\n try {\n return JSON.parse(rawArgs)\n } catch {\n return {}\n }\n })()\n : (rawArgs as Record) ?? {}\n }\n\n if (!name) return false\n\n const dedupKey = `${name}:${JSON.stringify(args)}`\n if (seen.has(dedupKey)) return false\n seen.add(dedupKey)\n\n results.push({ id: `ollama_tc_${++_textToolCallCounter}`, name, arguments: args })\n return true\n}\n\n/** Removes character ranges from `text`, returning the remaining content. */\nfunction stripRanges(text: string, ranges: Array<[number, number]>): string {\n const sorted = [...ranges].sort((a, b) => a[0] - b[0])\n let result = ''\n let pos = 0\n for (const [s, e] of sorted) {\n result += text.slice(pos, s)\n pos = e\n }\n return result + text.slice(pos)\n}\n\n/** Exported for unit testing only. */\nexport function parseTextToolCalls(text: string): {\n calls: ParsedTextToolCall[]\n toolCallRanges: Array<[number, number]>\n} {\n const results: ParsedTextToolCall[] = []\n const seen = new Set()\n const fencedRanges: Array<[number, number]> = []\n // acceptedRanges tracks only ranges where parseAndAdd confirmed a valid tool\n // call was emitted — these are what callers strip from text. fencedRanges\n // (all fenced blocks regardless of acceptance) is kept separately so Pass 2\n // can skip over them and avoid double-processing.\n const acceptedRanges: Array<[number, number]> = []\n\n // Pass 1: fenced code blocks — regex is safe, ``` bounds the non-greedy match.\n // Context guard: same heuristic as Pass 2 — if non-whitespace, non-`{` text\n // immediately follows the closing fence, the model is explaining a format rather\n // than calling a tool; skip to avoid false positives on fenced examples.\n for (const match of text.matchAll(FENCED_TOOL_CALL_RE)) {\n const raw = (match[1] ?? '').trim()\n const after = text.slice(match.index! + match[0].length).trimStart()\n if (after.length > 0 && !after.startsWith('{')) continue\n const range: [number, number] = [match.index!, match.index! + match[0].length]\n fencedRanges.push(range)\n if (raw && parseAndAdd(raw, results, seen)) {\n acceptedRanges.push(range)\n }\n }\n\n // Pass 2: bare JSON — use the brace scanner so nested objects are captured fully.\n // processedRanges grows as we extract; inner objects nested inside an outer\n // tool call are skipped because their start falls inside an already-extracted range.\n const processedRanges: Array<[number, number]> = [...fencedRanges]\n for (const match of text.matchAll(BARE_TOOL_CALL_START_RE)) {\n const start = match.index!\n if (processedRanges.some(([s, e]) => start >= s && start < e)) continue\n const raw = extractBalancedJson(text, start)\n if (raw) {\n // Context guard: if non-whitespace, non-`{` text immediately follows the JSON\n // the model is likely explaining, not calling — skip to avoid false positives.\n const after = text.slice(start + raw.length).trimStart()\n if (after.length > 0 && !after.startsWith('{')) continue\n const range: [number, number] = [start, start + raw.length]\n processedRanges.push(range)\n if (parseAndAdd(raw, results, seen)) {\n acceptedRanges.push(range)\n }\n }\n }\n\n return { calls: results, toolCallRanges: acceptedRanges }\n}\n\n/**\n * Async generator that transforms an OpenAI SSE stream into\n * Anthropic-format BetaRawMessageStreamEvent objects.\n */\n/**\n * Passthrough for Anthropic Messages API SSE streams.\n * The response events are already in AnthropicStreamEvent format —\n * we just parse the SSE frames and yield them directly.\n */\nasync function* anthropicSsePassthrough(\n response: Response,\n _model: string,\n signal?: AbortSignal,\n): AsyncGenerator {\n const readerOrNull = response.body?.getReader()\n if (!readerOrNull) throw new Error('Response body is not readable')\n const reader: ReadableStreamDefaultReader = readerOrNull\n const decoder = new TextDecoder()\n let buffer = ''\n\n // Read helper that properly cleans up abort listeners (mirrors codexShim.ts pattern).\n type ReadResult = Awaited>\n function readWithAbort(): Promise {\n if (!signal) return reader.read()\n return new Promise((resolve, reject) => {\n const onAbort = () => reject(new DOMException('Aborted', 'AbortError'))\n signal.addEventListener('abort', onAbort, { once: true })\n reader.read().then(\n result => { signal.removeEventListener('abort', onAbort); resolve(result) },\n err => { signal.removeEventListener('abort', onAbort); reject(err) },\n )\n })\n }\n\n try {\n while (true) {\n const { done, value } = await readWithAbort()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const chunks = buffer.split('\\n\\n')\n buffer = chunks.pop() ?? ''\n\n for (const chunk of chunks) {\n const lines = chunk.split('\\n').map(l => l.trim()).filter(Boolean)\n if (lines.length === 0) continue\n\n const dataLines = lines.filter(l => l.startsWith('data: '))\n if (dataLines.length === 0) continue\n\n const rawData = dataLines.map(l => l.slice(6)).join('\\n')\n if (rawData === '[DONE]') return\n\n try {\n const parsed = JSON.parse(rawData) as AnthropicStreamEvent\n if (parsed && typeof parsed === 'object' && 'type' in parsed) {\n yield parsed\n }\n } catch {\n // skip malformed frames\n }\n }\n }\n } finally {\n reader.releaseLock()\n }\n}\n\n/**\n * Transforms Google AI SDK SSE stream into Anthropic-format stream events.\n * Google AI SDK yields frames with { candidates: [{ content: { role, parts } }] }.\n */\nasync function* geminiSseToAnthropic(\n response: Response,\n model: string,\n signal?: AbortSignal,\n): AsyncGenerator {\n const reader: ReadableStreamDefaultReader | undefined = response.body?.getReader()\n if (!reader) throw new Error('Response body is not readable')\n const decoder = new TextDecoder()\n let buffer = ''\n const messageId = makeMessageId()\n let contentBlockIndex = 0\n let hasEmittedStart = false\n let hasEmittedTextStart = false\n let hasEmittedCurrentTool = false\n let usage: Partial | undefined\n let finishReason: string | undefined\n\n function readWithAbort(): Promise> {\n if (!signal) return reader!.read() as Promise>\n return new Promise((resolve, reject) => {\n const onAbort = () => reject(new DOMException('Aborted', 'AbortError'))\n signal.addEventListener('abort', onAbort, { once: true })\n reader!.read().then(\n result => { signal.removeEventListener('abort', onAbort); resolve(result as ReadableStreamReadResult) },\n err => { signal.removeEventListener('abort', onAbort); reject(err) },\n )\n })\n }\n\n function mapFinishReason(reason: string | undefined, hasToolUse: boolean): string {\n if (hasToolUse) return 'tool_use'\n if (reason === 'MAX_TOKENS') return 'max_tokens'\n return 'end_turn'\n }\n\n try {\n while (true) {\n const { done, value } = await readWithAbort()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const chunks = buffer.split('\\n\\n')\n buffer = chunks.pop() ?? ''\n\n for (const chunk of chunks) {\n const lines = chunk.split('\\n').map(l => l.trim()).filter(Boolean)\n const dataLines = lines.filter(l => l.startsWith('data: '))\n if (dataLines.length === 0) continue\n\n const rawData = dataLines.map(l => l.slice(6)).join('\\n')\n if (rawData === '[DONE]') {\n if (hasEmittedTextStart || hasEmittedCurrentTool) {\n yield { type: 'content_block_stop', index: contentBlockIndex }\n }\n yield {\n type: 'message_delta',\n delta: { stop_reason: mapFinishReason(finishReason, hasEmittedCurrentTool) },\n usage: usage ?? {},\n }\n yield { type: 'message_stop' }\n return\n }\n\n let parsed: Record\n try {\n parsed = JSON.parse(rawData) as Record\n } catch {\n continue\n }\n\n if (!hasEmittedStart) {\n yield {\n type: 'message_start',\n message: {\n id: messageId,\n type: 'message',\n role: 'assistant',\n content: [],\n model,\n stop_reason: null,\n stop_sequence: null,\n usage: { input_tokens: 0, output_tokens: 0 },\n },\n }\n hasEmittedStart = true\n }\n\n if (parsed.usageMetadata && typeof parsed.usageMetadata === 'object') {\n const um = parsed.usageMetadata as Record\n usage = buildAnthropicUsageFromRawUsage({\n input_tokens: um.promptTokenCount ?? 0,\n output_tokens: (um.candidatesTokenCount ?? 0) + (um.thoughtsTokenCount ?? 0),\n })\n }\n\n const candidates = parsed.candidates as Array> | undefined\n if (!candidates || candidates.length === 0) continue\n const candidate = candidates[0]\n\n if (typeof candidate.finishReason === 'string') {\n finishReason = candidate.finishReason\n }\n\n const content = candidate.content as { role?: string; parts?: Array> } | undefined\n if (!content || !content.parts) continue\n\n for (const part of content.parts) {\n const text = part.text as string | undefined\n const fc = part.functionCall as { name?: string; args?: unknown } | undefined\n\n if (text) {\n if (hasEmittedCurrentTool) {\n yield { type: 'content_block_stop', index: contentBlockIndex }\n contentBlockIndex++\n hasEmittedCurrentTool = false\n }\n if (!hasEmittedTextStart) {\n yield {\n type: 'content_block_start',\n index: contentBlockIndex,\n content_block: { type: 'text', text: '' },\n }\n hasEmittedTextStart = true\n }\n yield {\n type: 'content_block_delta',\n index: contentBlockIndex,\n delta: { type: 'text_delta', text },\n }\n } else if (fc?.name) {\n if (hasEmittedTextStart) {\n yield { type: 'content_block_stop', index: contentBlockIndex }\n contentBlockIndex++\n hasEmittedTextStart = false\n }\n const toolId = `toolu_${crypto.randomUUID().replace(/-/g, '').slice(0, 24)}`\n yield {\n type: 'content_block_start',\n index: contentBlockIndex,\n content_block: {\n type: 'tool_use',\n id: toolId,\n name: fc.name,\n input: {},\n },\n }\n hasEmittedCurrentTool = true\n yield {\n type: 'content_block_delta',\n index: contentBlockIndex,\n delta: {\n type: 'input_json_delta',\n partial_json: typeof fc.args === 'string' ? fc.args : JSON.stringify(fc.args ?? {}),\n },\n }\n }\n }\n }\n }\n\n if (hasEmittedTextStart || hasEmittedCurrentTool) {\n yield { type: 'content_block_stop', index: contentBlockIndex }\n }\n yield {\n type: 'message_delta',\n delta: { stop_reason: mapFinishReason(finishReason, hasEmittedCurrentTool) },\n usage: usage ?? {},\n }\n yield { type: 'message_stop' }\n } finally {\n reader.releaseLock()\n }\n}\n\nasync function* openaiStreamToAnthropic(\n response: Response,\n model: string,\n signal?: AbortSignal,\n isOllama = false,\n): AsyncGenerator {\n const messageId = makeMessageId()\n let contentBlockIndex = 0\n const activeToolCalls = new Map<\n number,\n {\n id: string\n name: string\n index: number\n jsonBuffer: string\n normalizeAtStop: boolean\n }\n >()\n let hasEmittedContentStart = false\n let hasEmittedThinkingStart = false\n let hasClosedThinking = false\n const thinkFilter = createThinkTagFilter()\n let lastStopReason: 'tool_use' | 'max_tokens' | 'end_turn' | null = null\n let hasEmittedFinalUsage = false\n let hasProcessedFinishReason = false\n // Accumulated text for Ollama text-based tool call fallback parsing (#1053)\n let accumulatedText = ''\n // Use the resolved value threaded from the call site (resolveProviderRequest)\n // rather than re-reading env vars inside the generator.\n const isOllamaStream = isOllama\n // Buffer Ollama text deltas so raw tool-call JSON is never emitted as text_delta\n // before extraction at finish_reason=stop (P2 fix for #1053).\n let ollamaTextBuffer = ''\n const streamState = createStreamState()\n let bufferedRawToolCallsText: string | null = null\n\n // Emit message_start\n yield {\n type: 'message_start',\n message: {\n id: messageId,\n type: 'message',\n role: 'assistant',\n content: [],\n model,\n stop_reason: null,\n stop_sequence: null,\n usage: {\n input_tokens: 0,\n output_tokens: 0,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n },\n },\n }\n\n const readerOrNull = response.body?.getReader()\n if (!readerOrNull) throw new Error('Response body is not readable')\n const reader: ReadableStreamDefaultReader = readerOrNull\n\n const decoder = new TextDecoder()\n let buffer = ''\n const STREAM_IDLE_TIMEOUT_MS = 120_000 // 2 minutes without data = connection likely dead\n let lastDataTime = Date.now()\n\n /**\n * Read from the stream with an idle timeout. If no data arrives within\n * STREAM_IDLE_TIMEOUT_MS, assume the connection is dead and throw so\n * withRetry can reconnect. This prevents indefinite hangs on stale\n * SSE connections from OpenAI/Gemini during long-running sessions.\n * Respects the caller's AbortSignal — clears the idle timer on abort\n * so the rejection reason is AbortError, not a spurious idle timeout.\n */\n type ReadResult = Awaited>\n async function readWithTimeout(): Promise {\n return new Promise((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n const elapsed = Math.round((Date.now() - lastDataTime) / 1000)\n reject(new Error(\n `OpenAI/Gemini SSE stream idle for ${elapsed}s (limit: ${STREAM_IDLE_TIMEOUT_MS / 1000}s). Connection likely dropped.`,\n ))\n }, STREAM_IDLE_TIMEOUT_MS)\n\n // If the caller aborts, clear the timer so the AbortError surfaces\n // cleanly instead of being masked by a spurious idle timeout.\n let abortCleanup: (() => void) | undefined\n if (signal) {\n abortCleanup = () => {\n clearTimeout(timeoutId)\n }\n signal.addEventListener('abort', abortCleanup, { once: true })\n }\n\n reader.read().then(\n result => {\n clearTimeout(timeoutId)\n if (signal && abortCleanup) signal.removeEventListener('abort', abortCleanup)\n if (result.value) lastDataTime = Date.now()\n resolve(result)\n },\n err => {\n clearTimeout(timeoutId)\n if (signal && abortCleanup) signal.removeEventListener('abort', abortCleanup)\n reject(err)\n },\n )\n })\n }\n\n const closeActiveContentBlock = async function* () {\n if (!hasEmittedContentStart) return\n\n const tail = thinkFilter.flush()\n if (tail) {\n yield {\n type: 'content_block_delta',\n index: contentBlockIndex,\n delta: { type: 'text_delta', text: tail },\n }\n }\n\n yield {\n type: 'content_block_stop',\n index: contentBlockIndex,\n }\n contentBlockIndex++\n hasEmittedContentStart = false\n }\n\n const emitTextDelta = async function* (text: string) {\n if (!text) return\n if (!hasEmittedContentStart) {\n yield {\n type: 'content_block_start',\n index: contentBlockIndex,\n content_block: { type: 'text', text: '' },\n }\n hasEmittedContentStart = true\n }\n\n const visible = thinkFilter.feed(text)\n if (visible) {\n yield {\n type: 'content_block_delta',\n index: contentBlockIndex,\n delta: { type: 'text_delta', text: visible },\n }\n }\n processStreamChunk(streamState, text)\n }\n\n const emitParsedRawToolCalls = async function* (\n toolCalls: ParsedRawToolCall[],\n ) {\n if (hasEmittedThinkingStart && !hasClosedThinking) {\n yield { type: 'content_block_stop', index: contentBlockIndex }\n contentBlockIndex++\n hasClosedThinking = true\n }\n if (hasEmittedContentStart) {\n yield* closeActiveContentBlock()\n }\n\n for (const toolCall of toolCalls) {\n const toolBlockIndex = contentBlockIndex\n yield {\n type: 'content_block_start',\n index: toolBlockIndex,\n content_block: {\n type: 'tool_use',\n id: toolCall.id,\n name: toolCall.name,\n input: {},\n },\n }\n contentBlockIndex++\n yield {\n type: 'content_block_delta',\n index: toolBlockIndex,\n delta: {\n type: 'input_json_delta',\n partial_json: toolCall.argumentsJson,\n },\n }\n yield { type: 'content_block_stop', index: toolBlockIndex }\n processStreamChunk(streamState, toolCall.argumentsJson)\n }\n }\n\n try {\n while (true) {\n const { done, value } = await readWithTimeout()\n if (done) break\n\n buffer += decoder.decode(value, { stream: true })\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n\n for (const line of lines) {\n const trimmed = line.trim()\n if (!trimmed || trimmed === 'data: [DONE]') continue\n if (!trimmed.startsWith('data: ')) continue\n\n let chunk: OpenAIStreamChunk\n try {\n chunk = JSON.parse(trimmed.slice(6))\n } catch {\n continue\n }\n\n // In-stream error event. Used by OpenAI when a stream fails after\n // headers have been sent, and by intermediaries (e.g. gateways) that\n // want to signal a structured failure without dropping the TCP\n // connection. Surface it as an APIError so callers see a clean\n // message instead of \"stream ended without [DONE]\".\n const inStreamError = (chunk as unknown as { error?: { message?: string; type?: string; code?: string } }).error\n if (inStreamError && typeof inStreamError === 'object') {\n const message =\n typeof inStreamError.message === 'string'\n ? inStreamError.message\n : 'Provider returned an in-stream error'\n const errorPayload = {\n error: {\n message,\n type: inStreamError.type ?? 'api_error',\n code: inStreamError.code ?? null,\n },\n }\n throw APIError.generate(\n (response.status ?? 200) as number,\n errorPayload,\n message,\n response.headers as unknown as Headers,\n )\n }\n\n const chunkUsage = convertChunkUsage(chunk.usage)\n\n for (const choice of chunk.choices ?? []) {\n const delta = choice.delta\n\n // Reasoning models (e.g. GLM-5, DeepSeek) may stream chain-of-thought\n // in `reasoning_content` before the actual reply appears in `content`.\n // Emit reasoning as a thinking block and content as a text block.\n if (delta.reasoning_content != null && delta.reasoning_content !== '') {\n if (!hasEmittedThinkingStart) {\n yield {\n type: 'content_block_start',\n index: contentBlockIndex,\n content_block: { type: 'thinking', thinking: '' },\n }\n hasEmittedThinkingStart = true\n }\n yield {\n type: 'content_block_delta',\n index: contentBlockIndex,\n delta: { type: 'thinking_delta', thinking: delta.reasoning_content },\n }\n }\n\n // Text content — use != null to distinguish absent field from empty string,\n // some providers send \"\" as first delta to signal streaming start\n if (delta.content != null && delta.content !== '') {\n // Close thinking block if transitioning from reasoning to content\n if (hasEmittedThinkingStart && !hasClosedThinking) {\n yield { type: 'content_block_stop', index: contentBlockIndex }\n contentBlockIndex++\n hasClosedThinking = true\n }\n\n accumulatedText += delta.content\n if (isOllamaStream) {\n const visible = thinkFilter.feed(delta.content)\n if (visible) {\n ollamaTextBuffer += visible\n }\n } else if (\n !hasEmittedContentStart &&\n bufferedRawToolCallsText === null &&\n couldBeRawToolCallsRequestedPrefix(delta.content)\n ) {\n bufferedRawToolCallsText = delta.content\n processStreamChunk(streamState, delta.content)\n } else if (bufferedRawToolCallsText !== null) {\n bufferedRawToolCallsText += delta.content\n processStreamChunk(streamState, delta.content)\n if (!couldBeRawToolCallsRequestedPrefix(bufferedRawToolCallsText)) {\n yield* emitTextDelta(bufferedRawToolCallsText)\n bufferedRawToolCallsText = null\n }\n } else {\n yield* emitTextDelta(delta.content)\n }\n }\n\n // Tool calls\n if (delta.tool_calls) {\n if (bufferedRawToolCallsText !== null) {\n const parsedBufferedToolCalls = parseRawToolCallsRequestedText(\n bufferedRawToolCallsText,\n )\n if (\n !parsedBufferedToolCalls &&\n !couldBeRawToolCallsRequestedPrefix(bufferedRawToolCallsText)\n ) {\n yield* emitTextDelta(bufferedRawToolCallsText)\n }\n bufferedRawToolCallsText = null\n }\n for (const tc of delta.tool_calls) {\n if (tc.id && tc.function?.name) {\n // New tool call starting — close any open thinking block first\n if (hasEmittedThinkingStart && !hasClosedThinking) {\n yield { type: 'content_block_stop', index: contentBlockIndex }\n contentBlockIndex++\n hasClosedThinking = true\n }\n // Flush buffered Ollama text before processing the tool call.\n // Must run before hasEmittedContentStart check because for Ollama\n // streams the text block may not have been opened yet (we buffer\n // instead of emitting during the streaming phase).\n if (isOllamaStream && ollamaTextBuffer) {\n if (!hasEmittedContentStart) {\n yield {\n type: 'content_block_start',\n index: contentBlockIndex,\n content_block: { type: 'text', text: '' },\n }\n hasEmittedContentStart = true\n }\n yield {\n type: 'content_block_delta',\n index: contentBlockIndex,\n delta: { type: 'text_delta', text: ollamaTextBuffer },\n }\n ollamaTextBuffer = ''\n }\n if (hasEmittedContentStart) {\n yield* closeActiveContentBlock()\n }\n\n const toolBlockIndex = contentBlockIndex\n const initialArguments = tc.function.arguments ?? ''\n const normalizeAtStop = hasToolFieldMapping(tc.function.name)\n const toolExtraContent = tc.extra_content ?? delta.extra_content\n const toolSignature =\n geminiThoughtSignatureFromExtraContent(tc.extra_content) ??\n geminiThoughtSignatureFromExtraContent(delta.extra_content)\n const mergedToolExtraContent = mergeGeminiThoughtSignature(\n toolExtraContent,\n toolSignature,\n )\n processStreamChunk(streamState, tc.function.arguments ?? '')\n activeToolCalls.set(tc.index, {\n id: tc.id,\n name: tc.function.name,\n index: toolBlockIndex,\n jsonBuffer: initialArguments,\n normalizeAtStop,\n })\n\n yield {\n type: 'content_block_start',\n index: toolBlockIndex,\n content_block: {\n type: 'tool_use',\n id: tc.id,\n name: tc.function.name,\n input: {},\n ...(mergedToolExtraContent ? { extra_content: mergedToolExtraContent } : {}),\n ...(toolSignature ? { signature: toolSignature } : {}),\n },\n }\n contentBlockIndex++\n\n // Emit any initial arguments\n if (tc.function.arguments && !normalizeAtStop) {\n yield {\n type: 'content_block_delta',\n index: toolBlockIndex,\n delta: {\n type: 'input_json_delta',\n partial_json: tc.function.arguments,\n },\n }\n }\n } else if (tc.function?.arguments) {\n // Continuation of existing tool call\n const active = activeToolCalls.get(tc.index)\n if (active) {\n if (tc.function.arguments) {\n active.jsonBuffer += tc.function.arguments\n }\n\n if (active.normalizeAtStop) {\n continue\n }\n\n yield {\n type: 'content_block_delta',\n index: active.index,\n delta: {\n type: 'input_json_delta',\n partial_json: tc.function.arguments,\n },\n }\n }\n }\n }\n }\n\n // Finish — guard ensures we only process finish_reason once even if\n // multiple chunks arrive with finish_reason set (some providers do this)\n if (choice.finish_reason && !hasProcessedFinishReason) {\n hasProcessedFinishReason = true\n\n // Close any open thinking block that wasn't closed by content transition\n if (hasEmittedThinkingStart && !hasClosedThinking) {\n yield { type: 'content_block_stop', index: contentBlockIndex }\n contentBlockIndex++\n hasClosedThinking = true\n }\n // Ollama text-based tool call fallback (#1053):\n // Must run before closeActiveContentBlock so the text buffer can be flushed\n // with tool-call JSON stripped (P2). Ollama models emit tool calls as raw\n // JSON text; scan accumulated text on any terminal finish reason with no\n // API tool calls. finish_reason is mutated to 'tool_calls' only for 'stop'\n // so the JSON fallback remains scoped to normal completions.\n //\n // 0.18.11 HYBRID: enable the markdown→tool_calls parser for ALL\n // upstreams, not just Ollama. Many text-completion models (M3 via\n // tokenrouter, llama-3.1-8b, github/gpt-4o-mini) emit tool calls\n // as fenced JSON in `content` instead of a `tool_calls` array.\n // We detect that pattern by checking for `\"name\":` inside a fenced\n // block and run the parser. The parser is safe (only acts on\n // explicit fences) and idempotent.\n const OLLAMA_TERMINAL_REASONS = new Set(['stop', 'length', 'content_filter', 'safety'])\n const looksLikeTextToolCall =\n OLLAMA_TERMINAL_REASONS.has(choice.finish_reason ?? '') &&\n activeToolCalls.size === 0 &&\n (isOllamaStream ||\n // Hybrid: any non-tool-calls response with a fenced JSON block\n // containing a \"name\" field is a tool call expressed as text.\n /```[a-zA-Z0-9_-]*\\n[\\s\\S]*?\"name\"\\s*:[\\s\\S]*?```/.test(accumulatedText))\n const isTerminalOllamaFinish = looksLikeTextToolCall\n const originalFinishReason = choice.finish_reason\n let ollamaClosedContentBlock = false\n if (isTerminalOllamaFinish) {\n const { calls: textToolCalls, toolCallRanges } = parseTextToolCalls(accumulatedText)\n if (textToolCalls.length > 0) {\n ollamaClosedContentBlock = true\n // Compute visible prose (tool-call JSON stripped, think-tags removed).\n // Use accumulatedText (raw) as source because toolCallRanges are relative to it.\n const stripped = stripRanges(accumulatedText, toolCallRanges).trim()\n const strippedVisible = stripThinkTags(stripped).trim()\n if (hasEmittedContentStart) {\n // Text block was already open — emit stripped prose then close it.\n if (strippedVisible) {\n yield {\n type: 'content_block_delta',\n index: contentBlockIndex,\n delta: { type: 'text_delta', text: strippedVisible },\n }\n }\n yield* closeActiveContentBlock()\n } else if (strippedVisible) {\n // Text was buffered (Ollama path, hasEmittedContentStart === false).\n // Open a text block, emit the visible prose before the tool call, close it.\n yield {\n type: 'content_block_start',\n index: contentBlockIndex,\n content_block: { type: 'text', text: '' },\n }\n hasEmittedContentStart = true\n yield {\n type: 'content_block_delta',\n index: contentBlockIndex,\n delta: { type: 'text_delta', text: strippedVisible },\n }\n yield* closeActiveContentBlock()\n }\n for (const tc of textToolCalls) {\n const toolBlockIndex = contentBlockIndex\n yield {\n type: 'content_block_start',\n index: toolBlockIndex,\n content_block: { type: 'tool_use', id: tc.id, name: tc.name, input: {} },\n }\n contentBlockIndex++\n yield {\n type: 'content_block_delta',\n index: toolBlockIndex,\n delta: { type: 'input_json_delta', partial_json: JSON.stringify(tc.arguments) },\n }\n yield { type: 'content_block_stop', index: toolBlockIndex }\n }\n // Only remap finish_reason to 'tool_calls' for the normal stop case;\n // non-stop terminal reasons keep their original reason.\n if (originalFinishReason === 'stop') {\n choice.finish_reason = 'tool_calls'\n }\n } else if (ollamaTextBuffer) {\n // No tool calls — flush the buffered text before the normal close below.\n // Open a text block first if one is not already open (guards the edge case\n // where hasEmittedContentStart is false but the buffer has content).\n if (!hasEmittedContentStart) {\n yield {\n type: 'content_block_start',\n index: contentBlockIndex,\n content_block: { type: 'text', text: '' },\n }\n hasEmittedContentStart = true\n }\n yield {\n type: 'content_block_delta',\n index: contentBlockIndex,\n delta: { type: 'text_delta', text: ollamaTextBuffer },\n }\n }\n }\n\n // Flush bufferedRawToolCallsText for non-Ollama providers\n const parsedBufferedToolCalls = bufferedRawToolCallsText\n ? parseRawToolCallsRequestedText(bufferedRawToolCallsText)\n : null\n if (parsedBufferedToolCalls) {\n yield* emitParsedRawToolCalls(parsedBufferedToolCalls)\n bufferedRawToolCallsText = null\n } else if (bufferedRawToolCallsText !== null) {\n yield* emitTextDelta(bufferedRawToolCallsText)\n bufferedRawToolCallsText = null\n }\n\n // Close any open content blocks (skipped when Ollama already closed it above)\n if (hasEmittedContentStart && !ollamaClosedContentBlock) {\n yield* closeActiveContentBlock()\n }\n // Close active tool calls\n for (const [, tc] of activeToolCalls) {\n if (tc.normalizeAtStop) {\n let partialJson: string\n if (choice.finish_reason === 'length') {\n // Truncated by max tokens — preserve raw buffer to avoid\n // turning an incomplete tool call into an executable command\n partialJson = tc.jsonBuffer\n } else {\n const repairedStructuredJson = repairPossiblyTruncatedObjectJson(\n tc.jsonBuffer,\n )\n if (repairedStructuredJson) {\n partialJson = repairedStructuredJson\n } else {\n partialJson = JSON.stringify(\n normalizeToolArguments(tc.name, tc.jsonBuffer),\n )\n }\n }\n\n yield {\n type: 'content_block_delta',\n index: tc.index,\n delta: {\n type: 'input_json_delta',\n partial_json: partialJson,\n },\n }\n yield { type: 'content_block_stop', index: tc.index }\n continue\n }\n\n let suffixToAdd = ''\n if (tc.jsonBuffer) {\n try {\n JSON.parse(tc.jsonBuffer)\n } catch {\n const str = tc.jsonBuffer.trimEnd()\n for (const combo of JSON_REPAIR_SUFFIXES) {\n try {\n JSON.parse(str + combo)\n suffixToAdd = combo\n break\n } catch {}\n }\n }\n }\n\n if (suffixToAdd) {\n yield {\n type: 'content_block_delta',\n index: tc.index,\n delta: {\n type: 'input_json_delta',\n partial_json: suffixToAdd,\n },\n }\n }\n\n yield { type: 'content_block_stop', index: tc.index }\n }\n\n const stopReason =\n parsedBufferedToolCalls || choice.finish_reason === 'tool_calls'\n ? 'tool_use'\n : choice.finish_reason === 'length'\n ? 'max_tokens'\n : 'end_turn'\n if (choice.finish_reason === 'content_filter' || choice.finish_reason === 'safety') {\n // Gemini/Azure content safety filter blocked the response.\n // Emit a visible text block so the user knows why output was truncated.\n if (!hasEmittedContentStart) {\n yield {\n type: 'content_block_start',\n index: contentBlockIndex,\n content_block: { type: 'text', text: '' },\n }\n hasEmittedContentStart = true\n }\n yield {\n type: 'content_block_delta',\n index: contentBlockIndex,\n delta: { type: 'text_delta', text: '\\n\\n[Content blocked by provider safety filter]' },\n }\n } else if (choice.finish_reason === 'length') {\n // Response was truncated — either the model hit max_tokens, or\n // an upstream/gateway watchdog synthesized a graceful end after\n // detecting a stalled stream. Either way, the user should know\n // the answer they're seeing isn't complete.\n if (!hasEmittedContentStart) {\n yield {\n type: 'content_block_start',\n index: contentBlockIndex,\n content_block: { type: 'text', text: '' },\n }\n hasEmittedContentStart = true\n }\n yield {\n type: 'content_block_delta',\n index: contentBlockIndex,\n delta: { type: 'text_delta', text: '\\n\\n[Response truncated — reached length limit or upstream stalled. Ask the model to continue.]' },\n }\n }\n lastStopReason = stopReason\n\n yield {\n type: 'message_delta',\n delta: { stop_reason: stopReason, stop_sequence: null },\n ...(chunkUsage ? { usage: chunkUsage } : {}),\n }\n if (chunkUsage) {\n hasEmittedFinalUsage = true\n }\n }\n }\n\n if (\n !hasEmittedFinalUsage &&\n chunkUsage &&\n (chunk.choices?.length ?? 0) === 0 &&\n lastStopReason !== null\n ) {\n yield {\n type: 'message_delta',\n delta: { stop_reason: lastStopReason, stop_sequence: null },\n usage: chunkUsage,\n }\n hasEmittedFinalUsage = true\n }\n }\n }\n } finally {\n reader.releaseLock()\n }\n\n const stats = getStreamStats(streamState)\n if (stats.totalChunks > 0) {\n logForDebugging(\n JSON.stringify({\n type: 'stream_stats',\n model,\n total_chunks: stats.totalChunks,\n first_token_ms: stats.firstTokenMs,\n duration_ms: stats.durationMs,\n }),\n { level: 'debug' },\n )\n }\n\n yield { type: 'message_stop' }\n}\n\n// ---------------------------------------------------------------------------\n// The shim client — duck-types as Anthropic SDK\n// ---------------------------------------------------------------------------\n\nclass OpenAIShimStream {\n private generator: AsyncGenerator\n // The controller property is checked by claude.ts to distinguish streams from error messages\n controller = new AbortController()\n\n constructor(generator: AsyncGenerator) {\n this.generator = generator\n }\n\n async *[Symbol.asyncIterator]() {\n yield* this.generator\n }\n}\n\nclass OpenAIShimMessages {\n private defaultHeaders: Record\n private reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh'\n private providerOverride?: { model: string; baseURL: string; apiKey: string }\n\n constructor(defaultHeaders: Record, reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh', providerOverride?: { model: string; baseURL: string; apiKey: string }) {\n this.defaultHeaders = filterAnthropicHeaders(defaultHeaders)\n this.reasoningEffort = reasoningEffort\n this.providerOverride = providerOverride\n }\n\n create(\n params: ShimCreateParams,\n options?: { signal?: AbortSignal; headers?: Record },\n ) {\n const self = this\n\n let httpResponse: Response | undefined\n\n const promise = (async () => {\n const request = resolveProviderRequest({ model: self.providerOverride?.model ?? params.model, baseUrl: self.providerOverride?.baseURL, reasoningEffortOverride: self.reasoningEffort })\n const response = await self._doRequest(request, params, options)\n httpResponse = response\n\n if (params.stream) {\n const isResponsesStream = response.url?.includes('/responses')\n const isMessagesStream = response.url?.includes('/messages')\n const isGeminiStream = response.url?.includes('/models/gemini-')\n return new OpenAIShimStream(\n (\n request.transport === 'codex_responses' ||\n request.transport === 'responses' ||\n isResponsesStream\n )\n ? codexStreamToAnthropic(response, request.resolvedModel, options?.signal)\n : isMessagesStream\n ? anthropicSsePassthrough(response, request.resolvedModel, options?.signal)\n : isGeminiStream\n ? geminiSseToAnthropic(response, request.resolvedModel, options?.signal)\n : openaiStreamToAnthropic(response, request.resolvedModel, options?.signal, isLikelyOllamaEndpoint(request.baseUrl)),\n )\n }\n\n if (request.transport === 'codex_responses') {\n const data = await collectCodexCompletedResponse(response, options?.signal)\n return convertCodexResponseToAnthropicMessage(\n data,\n request.resolvedModel,\n )\n }\n\n const isResponsesNonStream = response.url?.includes('/responses')\n const isMessagesNonStream = response.url?.includes('/messages')\n const isGeminiNonStream = response.url?.includes('/models/gemini-')\n if (\n request.transport === 'responses' ||\n isResponsesNonStream ||\n (request.transport === 'chat_completions' && isGithubModelsMode())\n ) {\n const contentType = response.headers.get('content-type') ?? ''\n if (contentType.includes('application/json')) {\n const parsed = await response.json() as Record\n if (\n parsed &&\n typeof parsed === 'object' &&\n ('output' in parsed || 'incomplete_details' in parsed)\n ) {\n return convertCodexResponseToAnthropicMessage(\n parsed,\n request.resolvedModel,\n )\n }\n return self._convertNonStreamingResponse(parsed, request.resolvedModel)\n }\n }\n\n // Anthropic Messages API response — already in Anthropic format,\n // pass through directly without conversion.\n if (isMessagesNonStream) {\n const contentType = response.headers.get('content-type') ?? ''\n if (contentType.includes('application/json')) {\n return await response.json() as Record\n }\n }\n\n // Google AI SDK response — convert to Anthropic format\n if (isGeminiNonStream) {\n const contentType = response.headers.get('content-type') ?? ''\n if (contentType.includes('application/json')) {\n const parsed = await response.json() as Record\n return self._convertGeminiToAnthropicResponse(parsed, request.resolvedModel)\n }\n }\n\n const contentType = response.headers.get('content-type') ?? ''\n if (contentType.includes('application/json')) {\n const data = await response.json()\n return self._convertNonStreamingResponse(data, request.resolvedModel)\n }\n\n const textBody = await response.text().catch(() => '')\n throw APIError.generate(\n response.status,\n undefined,\n `OpenAI API error ${response.status}: unexpected response content-type: ${response.headers.get('content-type') ?? 'unknown'}`,\n response.headers as unknown as Headers,\n )\n })()\n\n ; (promise as unknown as Record).withResponse =\n async () => {\n const data = await promise\n return {\n data,\n response: httpResponse ?? new Response(),\n request_id:\n httpResponse?.headers.get('x-request-id') ?? makeMessageId(),\n }\n }\n\n return promise\n }\n\n private async _doRequest(\n request: ReturnType,\n params: ShimCreateParams,\n options?: { signal?: AbortSignal; headers?: Record },\n ): Promise {\n const githubEndpointType = getGithubEndpointType(request.baseUrl)\n const isGithubMode = isGithubModelsMode()\n const isGithubWithCodexTransport = isGithubMode && request.transport === 'codex_responses'\n\n if (isGithubWithCodexTransport) {\n const apiKey = this.providerOverride?.apiKey ?? process.env.OPENAI_API_KEY ?? ''\n if (!apiKey) {\n throw new Error(\n 'GitHub Copilot auth is required. Run /onboard-github to sign in.',\n )\n }\n\n return performCodexRequest({\n request,\n credentials: {\n apiKey,\n source: 'env',\n },\n params,\n defaultHeaders: {\n ...this.defaultHeaders,\n ...filterAnthropicHeaders(options?.headers),\n ...COPILOT_HEADERS,\n },\n signal: options?.signal,\n })\n }\n\n if (request.transport === 'codex_responses' && !isGithubMode) {\n const refreshResult = await refreshCodexAccessTokenIfNeeded().catch(\n async error => {\n logForDebugging(\n `[codex] access token refresh failed before request: ${error instanceof Error ? error.message : String(error)}`,\n { level: 'warn' },\n )\n return {\n refreshed: false,\n credentials: await readCodexCredentialsAsync(),\n }\n },\n )\n const credentials = resolveRuntimeCodexCredentials({\n storedCredentials: refreshResult.credentials,\n })\n if (!credentials.apiKey) {\n const oauthHint = isBareMode() ? '' : ', choose Codex OAuth in /provider'\n const authHint = credentials.authPath\n ? `${oauthHint} or place a Codex auth.json at ${credentials.authPath}`\n : oauthHint\n const safeModel =\n redactSecretValueForDisplay(request.requestedModel, process.env as SecretValueSource) ??\n 'the requested model'\n throw new Error(\n `Codex auth is required for ${safeModel}. Set CODEX_API_KEY${authHint}.`,\n )\n }\n if (!credentials.accountId) {\n throw new Error(\n 'Codex auth is missing chatgpt_account_id. Re-login with Codex OAuth, the Codex CLI, or set CHATGPT_ACCOUNT_ID/CODEX_ACCOUNT_ID.',\n )\n }\n\n return performCodexRequest({\n request,\n credentials,\n params,\n defaultHeaders: {\n ...this.defaultHeaders,\n ...filterAnthropicHeaders(options?.headers),\n },\n signal: options?.signal,\n })\n }\n\n return this._doOpenAIRequest(request, params, options)\n }\n\n private async _doOpenAIRequest(\n request: ReturnType,\n params: ShimCreateParams,\n options?: { signal?: AbortSignal; headers?: Record },\n ): Promise {\n // Local backends (llama.cpp, vLLM, Ollama, LM Studio, …) do not implement\n // the cloud-side caching/strict-validation behaviours that several of our\n // pre-send transforms target. Computing the fast-path config once here\n // lets us skip those transforms uniformly. See providerConfig.ts.\n const fastPath: LocalFastPathConfig = getLocalFastPathConfig(request.baseUrl)\n\n const rawMessages = params.messages as Array<{\n role: string\n message?: { role?: string; content?: unknown }\n content?: unknown\n }>\n const compressedMessages = fastPath.skipToolHistoryCompression\n ? rawMessages\n : compressToolHistory(rawMessages, request.resolvedModel)\n const runtimeShimContext = resolveOpenAIShimRuntimeContext({\n processEnv: process.env,\n baseUrl: request.baseUrl,\n model: request.resolvedModel,\n treatAsLocal: isLocalProviderUrl(request.baseUrl),\n })\n const shimConfig = runtimeShimContext.openaiShimConfig\n // When endpointPath is overridden, the body format must match the target\n // API contract rather than request.transport from providerConfig.\n // - /responses → OpenAI Responses API (input, max_output_tokens, instructions)\n // - /messages → Anthropic Messages API (system, max_tokens, content blocks)\n // - /models/gemini-* → Google AI SDK (contents, systemInstruction, generationConfig)\n const effectiveTransport = shimConfig.endpointPath === '/responses'\n ? 'responses'\n : shimConfig.endpointPath === '/messages'\n ? 'anthropic_messages'\n : shimConfig.endpointPath?.startsWith('/models/gemini-')\n ? 'gemini'\n : request.transport\n const openaiMessages = convertMessages(compressedMessages, params.system, {\n preserveReasoningContent: shimConfig.preserveReasoningContent,\n reasoningContentFallback: shimConfig.reasoningContentFallback,\n preserveGeminiThoughtSignature: shouldPreserveGeminiThoughtSignature(\n request.resolvedModel,\n request.baseUrl,\n ),\n })\n\n const body: Record = {\n model: request.resolvedModel,\n messages: openaiMessages,\n stream: params.stream ?? false,\n store: false,\n }\n // Emit reasoning_effort for chat_completions when the resolved provider\n // request carries a reasoning effort (set via /effort, model alias default,\n // or `?reasoning=` query on the model string). OpenAI, Codex, and\n // most OpenAI-compatible endpoints read it from this top-level field.\n if (request.reasoning) {\n body.reasoning_effort = request.reasoning.effort\n }\n // Convert max_tokens to max_completion_tokens for OpenAI API compatibility.\n // Azure OpenAI requires max_completion_tokens and does not accept max_tokens.\n // Ensure max_tokens is a valid positive number before using it.\n const maxTokensValue = typeof params.max_tokens === 'number' && params.max_tokens > 0\n ? params.max_tokens\n : undefined\n const maxCompletionTokensValue = typeof (params as Record).max_completion_tokens === 'number'\n ? (params as Record).max_completion_tokens as number\n : undefined\n\n if (maxTokensValue !== undefined) {\n body.max_completion_tokens = maxTokensValue\n } else if (maxCompletionTokensValue !== undefined) {\n body.max_completion_tokens = maxCompletionTokensValue\n }\n\n if (params.stream && !isLocalProviderUrl(request.baseUrl)) {\n body.stream_options = { include_usage: true }\n }\n\n const isGithub = isGithubModelsMode()\n const isLocal = isLocalProviderUrl(request.baseUrl)\n\n const githubEndpointType = getGithubEndpointType(request.baseUrl)\n const isGithubCopilot = isGithub && githubEndpointType === 'copilot'\n const isGithubModels = isGithub && (githubEndpointType === 'models' || githubEndpointType === 'custom')\n const shouldStripResponsesStore =\n (shimConfig.removeBodyFields ?? []).includes('store') ||\n isGeminiMode() ||\n hasGeminiApiHost(request.baseUrl) ||\n hasCerebrasApiHost(request.baseUrl) ||\n isLocal\n\n if (\n shimConfig.maxTokensField === 'max_tokens' &&\n body.max_completion_tokens !== undefined\n ) {\n body.max_tokens = body.max_completion_tokens\n delete body.max_completion_tokens\n }\n\n for (const field of shimConfig.removeBodyFields ?? []) {\n delete body[field]\n }\n\n if (shouldStripResponsesStore) {\n delete body.store\n }\n\n if (params.temperature !== undefined) body.temperature = params.temperature\n if (params.top_p !== undefined) body.top_p = params.top_p\n\n if (shimConfig.thinkingRequestFormat === 'deepseek-compatible') {\n const requestedThinkingType = (params.thinking as { type?: string } | undefined)?.type\n const deepSeekThinkingType =\n requestedThinkingType === 'disabled'\n ? 'disabled'\n : requestedThinkingType === 'enabled' || requestedThinkingType === 'adaptive'\n ? 'enabled'\n : undefined\n\n if (deepSeekThinkingType) {\n body.thinking = { type: deepSeekThinkingType }\n }\n\n if (deepSeekThinkingType === 'enabled') {\n const effort = request.reasoning?.effort\n if (effort) {\n body.reasoning_effort = normalizeDeepSeekReasoningEffort(effort)\n }\n }\n }\n\n if (params.tools && params.tools.length > 0) {\n const converted = convertTools(\n params.tools as Array<{\n name: string\n description?: string\n input_schema?: Record\n }>,\n { skipStrict: fastPath.skipStrictTools },\n )\n if (converted.length > 0) {\n body.tools = converted\n if (params.tool_choice) {\n const tc = params.tool_choice as { type?: string; name?: string }\n if (tc.type === 'auto') {\n body.tool_choice = 'auto'\n } else if (tc.type === 'tool' && tc.name) {\n body.tool_choice = {\n type: 'function',\n function: { name: tc.name },\n }\n } else if (tc.type === 'any') {\n body.tool_choice = 'required'\n } else if (tc.type === 'none') {\n body.tool_choice = 'none'\n }\n }\n }\n }\n\n let omitResponsesTools = false\n const buildResponsesBody = (): Record => {\n const responsesBody: Record = {\n model: request.resolvedModel,\n input: convertAnthropicMessagesToResponsesInput(\n params.messages as Array<{\n role?: string\n message?: { role?: string; content?: unknown }\n content?: unknown\n }>,\n effectiveTransport === 'responses_compat',\n ),\n stream: params.stream ?? false,\n store: false,\n }\n\n if (shouldStripResponsesStore) {\n delete responsesBody.store\n }\n\n if (!Array.isArray(responsesBody.input) || responsesBody.input.length === 0) {\n responsesBody.input = [\n {\n type: 'message',\n role: 'user',\n content: [{ type: effectiveTransport === 'responses_compat' ? 'text' : 'input_text', text: '' }],\n },\n ]\n }\n\n const systemText = convertSystemPrompt(params.system)\n if (systemText) {\n responsesBody.instructions = systemText\n }\n\n if (body.max_tokens !== undefined) {\n responsesBody.max_output_tokens = body.max_tokens\n } else if (body.max_completion_tokens !== undefined) {\n responsesBody.max_output_tokens = body.max_completion_tokens\n }\n\n if (params.temperature !== undefined) responsesBody.temperature = params.temperature\n if (params.top_p !== undefined) responsesBody.top_p = params.top_p\n if (request.reasoning?.effort) {\n responsesBody.reasoning_effort = request.reasoning.effort\n responsesBody.reasoning_summary = 'auto'\n responsesBody.include = ['reasoning.encrypted_content']\n }\n\n if (!omitResponsesTools && params.tools && params.tools.length > 0) {\n const convertedTools = convertToolsToResponsesTools(\n params.tools as Array<{\n name?: string\n description?: string\n input_schema?: Record\n }>,\n )\n if (convertedTools.length > 0) {\n responsesBody.tools = convertedTools\n }\n }\n\n return responsesBody\n }\n\n // Anthropic Messages API body — used when endpointPath is /messages.\n // params.messages, params.tools, etc. are already in Anthropic format\n // (they originate from the Anthropic SDK). We pass them through directly,\n // only adding the top-level system (as string or content-block array)\n // and max_tokens.\n let omitAnthropicTools = false\n const buildAnthropicMessagesBody = (): Record => {\n const anthropicBody: Record = {\n model: request.resolvedModel,\n messages: params.messages,\n max_tokens: params.max_tokens,\n stream: params.stream ?? false,\n }\n\n // Pass system through in native format. The Anthropic Messages API\n // accepts either a string or an array of content blocks (with optional\n // cache_control markers). Only filter the billing header block.\n if (Array.isArray(params.system)) {\n const filtered = (params.system as Array<{ type?: string; text?: string }>)\n .filter(block => !(block.type === 'text' && (block.text ?? '').startsWith('x-anthropic-billing-header')))\n if (filtered.length > 0) anthropicBody.system = filtered\n } else if (params.system) {\n const text = typeof params.system === 'string' ? params.system : String(params.system)\n if (text && !text.startsWith('x-anthropic-billing-header')) anthropicBody.system = text\n }\n\n if (!omitAnthropicTools && params.tools && params.tools.length > 0) {\n anthropicBody.tools = params.tools\n }\n if (params.tool_choice) {\n anthropicBody.tool_choice = params.tool_choice\n }\n\n if (request.reasoning?.effort) {\n // Shim receives OpenAI effort levels (xhigh) from client.ts, but\n // Anthropic API expects 'max' not 'xhigh'. Convert for the effort field.\n const effort = request.reasoning.effort === 'xhigh' ? 'max' : request.reasoning.effort\n const modelLower = request.resolvedModel.toLowerCase()\n const isAdaptive = modelLower.includes('opus-4-7') || modelLower.includes('opus-4-6') ||\n modelLower.includes('opus-4-8') ||\n modelLower.includes('opus-4.6') || modelLower.includes('opus-4.7') ||\n modelLower.includes('opus-4.8') ||\n modelLower.includes('sonnet-4-6') || modelLower.includes('sonnet-4.6')\n const isOpus45 = modelLower.includes('opus-4-5') || modelLower.includes('opus-4.5')\n\n if (isAdaptive) {\n anthropicBody.thinking = { type: 'adaptive' }\n anthropicBody.effort = effort\n } else if (isOpus45) {\n anthropicBody.effort = effort\n } else if (effort === 'high' || effort === 'max') {\n anthropicBody.thinking = {\n type: 'enabled',\n budgetTokens: effort === 'max' ? 31_999 : 16_000,\n }\n }\n }\n\n return anthropicBody\n }\n\n // Google AI SDK body — used when endpointPath is /models/gemini-*.\n // Converts Anthropic-format params to Google AI SDK format.\n let omitGeminiTools = false\n const buildGeminiBody = (): Record => {\n const contents: Array<{ role: string; parts: Array> }> = []\n\n // Build a lookup from tool_use_id → function name so tool_result\n // blocks can emit the correct functionResponse.name (Gemini requires\n // the function name, not the Anthropic tool_use_id).\n const toolUseIdToName = new Map()\n const messages = params.messages as Array<{\n role?: string\n content?: unknown\n }>\n for (const msg of messages) {\n if (!Array.isArray(msg.content)) continue\n for (const block of msg.content as Array<{ type?: string; id?: string; name?: string }>) {\n if (block.type === 'tool_use' && block.id && block.name) {\n toolUseIdToName.set(block.id, block.name)\n }\n }\n }\n\n for (const msg of messages) {\n const role = msg.role === 'assistant' ? 'model' : 'user'\n const parts: Array> = []\n\n if (typeof msg.content === 'string') {\n parts.push({ text: msg.content })\n } else if (Array.isArray(msg.content)) {\n for (const block of msg.content as Array<{ type?: string; text?: string; id?: string; name?: string; input?: unknown; tool_use_id?: string; content?: unknown; is_error?: boolean }>) {\n if (block.type === 'text' && block.text) {\n parts.push({ text: block.text })\n } else if (block.type === 'tool_use' && block.id && block.name) {\n parts.push({\n functionCall: {\n name: block.name,\n args: block.input ?? {},\n },\n })\n } else if (block.type === 'tool_result' && block.tool_use_id) {\n const funcName = toolUseIdToName.get(block.tool_use_id) ?? block.tool_use_id\n let resultContent = typeof block.content === 'string'\n ? block.content\n : Array.isArray(block.content)\n ? (block.content as Array<{ type?: string; text?: string }>)\n .filter(b => b.type === 'text')\n .map(b => b.text ?? '')\n .join('\\n')\n : ''\n if (block.is_error) {\n resultContent = `Error: ${resultContent}`\n }\n parts.push({\n functionResponse: {\n name: funcName,\n response: {\n name: funcName,\n content: resultContent,\n },\n },\n })\n }\n }\n }\n\n if (parts.length > 0) {\n contents.push({ role, parts })\n }\n }\n\n const geminiBody: Record = { contents }\n\n // System instruction\n const systemText = convertSystemPrompt(params.system)\n if (systemText) {\n geminiBody.systemInstruction = { parts: [{ text: systemText }] }\n }\n\n // Generation config\n const genConfig: Record = {}\n if (params.max_tokens !== undefined) {\n genConfig.maxOutputTokens = params.max_tokens\n } else if (maxTokensValue !== undefined) {\n genConfig.maxOutputTokens = maxTokensValue\n } else if (maxCompletionTokensValue !== undefined) {\n genConfig.maxOutputTokens = maxCompletionTokensValue\n }\n if (params.temperature !== undefined) genConfig.temperature = params.temperature\n if (params.top_p !== undefined) genConfig.topP = params.top_p\n if (request.reasoning?.effort) {\n const level = request.reasoning.effort === 'xhigh' ? 'high' : request.reasoning.effort\n genConfig.thinkingConfig = { includeThoughts: true, thinkingLevel: level }\n }\n if (Object.keys(genConfig).length > 0) {\n geminiBody.generationConfig = genConfig\n }\n\n // Tools — convert Anthropic tool format to Google functionDeclarations\n if (!omitGeminiTools && params.tools && params.tools.length > 0) {\n const functionDeclarations = (params.tools as Array<{\n name?: string\n description?: string\n input_schema?: Record\n }>).map(tool => ({\n name: tool.name ?? '',\n description: tool.description ?? '',\n ...(tool.input_schema ? { parameters: tool.input_schema } : {}),\n }))\n if (functionDeclarations.length > 0) {\n geminiBody.tools = [{ functionDeclarations }]\n }\n }\n\n return geminiBody\n }\n\n const headers: Record = {\n 'Content-Type': 'application/json',\n ...filterAnthropicHeaders(shimConfig.headers),\n ...this.defaultHeaders,\n ...filterAnthropicHeaders(options?.headers),\n }\n\n const isGemini = isGeminiMode()\n const routeCredential = resolveRouteCredentialValue({\n routeId: runtimeShimContext.routeId,\n baseUrl: request.baseUrl,\n processEnv: process.env,\n })\n // xAI OAuth: when the active route is xAI and no API key is set, fall\n // back to a stored OAuth access token (auto-refreshed). The token is\n // sent as a Bearer to api.x.ai/v1 — same surface as an API key.\n const isXaiRoute =\n runtimeShimContext.routeId === 'xai' || isXaiBaseUrl(request.baseUrl)\n const xaiOAuthToken =\n isXaiRoute &&\n !this.providerOverride?.apiKey &&\n !routeCredential &&\n !process.env.OPENAI_API_KEY\n ? await resolveXaiAccessToken()\n : undefined\n const apiKey =\n this.providerOverride?.apiKey ??\n routeCredential ??\n process.env.OPENAI_API_KEY ??\n xaiOAuthToken ??\n ''\n const configuredAuthHeaderValue = process.env.OPENAI_AUTH_HEADER_VALUE?.trim()\n if (configuredAuthHeaderValue && /[\\r\\n]/.test(configuredAuthHeaderValue)) {\n throw new Error('OPENAI_AUTH_HEADER_VALUE must not contain CR/LF characters')\n }\n const customAuthHeader = process.env.OPENAI_AUTH_HEADER?.trim()\n const hasCustomAuthHeader = Boolean(\n customAuthHeader &&\n /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/.test(customAuthHeader),\n )\n const authValue = hasCustomAuthHeader\n ? configuredAuthHeaderValue || apiKey\n : apiKey\n // Detect Azure endpoints by hostname (not raw URL) to prevent bypass via\n // path segments like https://evil.com/cognitiveservices.azure.com/\n let isAzure = isEnvTruthy(process.env.OPENAI_AZURE_STYLE)\n if (!isAzure) {\n try {\n const { hostname } = new URL(request.baseUrl)\n isAzure =\n hostname.endsWith('.azure.com') &&\n (hostname.includes('cognitiveservices') ||\n hostname.includes('openai') ||\n hostname.includes('services.ai') ||\n hostname.includes('inference.ml'))\n } catch {\n /* malformed URL — not Azure */\n }\n }\n\n let isBankr = false\n try {\n isBankr =\n runtimeShimContext.routeId === 'bankr' ||\n request.baseUrl.toLowerCase().includes('bankr')\n } catch { /* malformed URL — not Bankr */ }\n\n if (authValue) {\n if (hasCustomAuthHeader && customAuthHeader) {\n const defaultCustomAuthScheme =\n customAuthHeader.toLowerCase() === 'authorization' ? 'bearer' : 'raw'\n const customAuthScheme =\n process.env.OPENAI_AUTH_SCHEME === 'raw' ||\n process.env.OPENAI_AUTH_SCHEME === 'bearer'\n ? process.env.OPENAI_AUTH_SCHEME\n : defaultCustomAuthScheme\n headers[customAuthHeader] =\n customAuthScheme === 'bearer'\n ? `Bearer ${authValue}`\n : authValue\n } else if (isAzure) {\n // Azure uses api-key header instead of Bearer token\n headers['api-key'] = authValue\n } else if (isBankr) {\n // Bankr uses X-API-Key header instead of Bearer token\n headers['X-API-Key'] = authValue\n } else if (shimConfig.defaultAuthHeader?.name) {\n headers[shimConfig.defaultAuthHeader.name] =\n shimConfig.defaultAuthHeader.scheme === 'bearer'\n ? `Bearer ${authValue}`\n : authValue\n } else {\n headers.Authorization = `Bearer ${authValue}`\n }\n } else if (isGemini) {\n const geminiCredential = await resolveGeminiCredential(process.env)\n if (geminiCredential.kind !== 'none') {\n headers.Authorization = `Bearer ${geminiCredential.credential}`\n if (geminiCredential.kind !== 'api-key' && 'projectId' in geminiCredential && geminiCredential.projectId) {\n headers['x-goog-user-project'] = geminiCredential.projectId\n }\n }\n }\n\n if (isGithubCopilot) {\n Object.assign(headers, COPILOT_HEADERS)\n } else if (isGithubModels) {\n headers['Accept'] = 'application/vnd.github+json'\n headers['X-GitHub-Api-Version'] = '2022-11-28'\n }\n\n // xAI / Grok prompt caching. Pinning the session id via x-grok-conv-id\n // routes follow-up requests to the same backend so xAI can reuse the\n // cached system prompt and conversation history. Mirrors the Hermes\n // implementation (RELEASE_v0.8.0 PR #5604).\n if (isXaiRoute) {\n headers['x-grok-conv-id'] ??= getSessionId()\n }\n\n const buildChatCompletionsUrl = (baseUrl: string): string => {\n // Azure Cognitive Services / Azure OpenAI require a deployment-specific\n // path and an api-version query parameter.\n if (isAzure) {\n const apiVersion = process.env.AZURE_OPENAI_API_VERSION ?? '2024-12-01-preview'\n const deployment = encodeURIComponent(request.resolvedModel ?? process.env.OPENAI_MODEL ?? 'gpt-4o')\n\n // If base URL already contains /deployments/, use it as-is with api-version.\n if (/\\/deployments\\//i.test(baseUrl)) {\n const normalizedBase = baseUrl.replace(/\\/+$/, '')\n return `${normalizedBase}/chat/completions?api-version=${apiVersion}`\n }\n\n // Strip trailing /v1 or /openai/v1 if present, then build Azure path.\n const normalizedBase = baseUrl\n .replace(/\\/(openai\\/)?v1\\/?$/, '')\n .replace(/\\/+$/, '')\n\n return `${normalizedBase}/openai/deployments/${deployment}/chat/completions?api-version=${apiVersion}`\n }\n\n return `${baseUrl}/chat/completions`\n }\n\n const localRetryBaseUrls = isLocal\n ? getLocalProviderRetryBaseUrls(request.baseUrl)\n : []\n\n const buildRequestUrl = (baseUrl: string): string => {\n if (shimConfig.endpointPath) {\n return `${baseUrl}${shimConfig.endpointPath}`\n }\n return request.transport === 'responses' || request.transport === 'responses_compat'\n ? `${baseUrl}/responses`\n : buildChatCompletionsUrl(baseUrl)\n }\n\n let activeBaseUrl = request.baseUrl\n let requestUrl = buildRequestUrl(activeBaseUrl)\n const attemptedLocalBaseUrls = new Set([activeBaseUrl])\n let didRetryWithoutTools = false\n\n const promoteNextLocalBaseUrl = (\n reason: 'endpoint_not_found' | 'localhost_resolution_failed',\n ): boolean => {\n for (const candidateBaseUrl of localRetryBaseUrls) {\n if (attemptedLocalBaseUrls.has(candidateBaseUrl)) {\n continue\n }\n\n const previousUrl = requestUrl\n attemptedLocalBaseUrls.add(candidateBaseUrl)\n activeBaseUrl = candidateBaseUrl\n requestUrl = buildRequestUrl(activeBaseUrl)\n\n logForDebugging(\n `[OpenAIShim] self-heal retry reason=${reason} method=POST from=${redactUrlForDiagnostics(previousUrl)} to=${redactUrlForDiagnostics(requestUrl)} model=${request.resolvedModel}`,\n { level: 'warn' },\n )\n\n return true\n }\n\n return false\n }\n\n const bodyContainsImages = (): boolean => {\n if (request.transport === 'responses') {\n const responsesBody = buildResponsesBody()\n const input = responsesBody.input as Array> | undefined\n if (!Array.isArray(input)) return false\n return input.some(item => {\n const content = item.content as Array> | undefined\n return Array.isArray(content) && content.some(part => part.type === 'input_image')\n })\n }\n const messages = body.messages as Array> | undefined\n if (!Array.isArray(messages)) return false\n return messages.some(msg => {\n const content = msg.content\n if (!Array.isArray(content)) return false\n return content.some((part: Record) => part.type === 'image_url')\n })\n }\n\n // WHY: byte-identity required for implicit prefix caching in\n // OpenAI/Kimi/DeepSeek. stableStringify sorts object keys at every\n // depth so spurious insertion-order differences across rebuilds of\n // `body` (spread-merge, conditional assignments above) don't bust\n // the provider's prefix hash.\n //\n // Local backends do not implement prefix caching, so the deep key-sort\n // is pure CPU overhead per request (issue #1016). Drop to the native\n // `JSON.stringify` fast path when the fast-path config opts out.\n const serializeBody = (): string => {\n const payload =\n effectiveTransport === 'responses' || effectiveTransport === 'responses_compat' ? buildResponsesBody()\n : effectiveTransport === 'anthropic_messages' ? buildAnthropicMessagesBody()\n : effectiveTransport === 'gemini' ? buildGeminiBody()\n : body\n return fastPath.skipStableStringify\n ? JSON.stringify(payload)\n : stableStringifyJson(payload)\n }\n let serializedBody = serializeBody()\n\n const refreshSerializedBody = (): void => {\n serializedBody = serializeBody()\n }\n\n const buildFetchInit = () => ({\n method: 'POST' as const,\n headers,\n body: serializedBody,\n signal: options?.signal,\n })\n\n const maxSelfHealAttempts = isLocal\n ? localRetryBaseUrls.length + 1\n : 0\n const maxAttempts = (isGithub ? GITHUB_429_MAX_RETRIES : 1) + maxSelfHealAttempts\n\n const throwClassifiedTransportError = (\n error: unknown,\n requestUrl: string,\n preclassifiedFailure?: ReturnType,\n ): never => {\n if (options?.signal?.aborted) {\n throw error\n }\n\n const failure =\n preclassifiedFailure ??\n classifyOpenAINetworkFailure(error, {\n url: requestUrl,\n })\n const redactedUrl = redactUrlForDiagnostics(requestUrl)\n const safeMessage =\n redactSecretValueForDisplay(\n redactUrlsInMessage(failure.message),\n process.env as SecretValueSource,\n ) || 'Request failed'\n\n logForDebugging(\n `[OpenAIShim] transport failure category=${failure.category} retryable=${failure.retryable} code=${failure.code ?? 'unknown'} method=POST url=${redactedUrl} model=${request.resolvedModel} message=${safeMessage}`,\n { level: 'warn' },\n )\n\n throw APIError.generate(\n 0,\n undefined,\n buildOpenAICompatibilityErrorMessage(\n `OpenAI API transport error: ${safeMessage}${failure.code ? ` (code=${failure.code})` : ''}`,\n failure,\n ),\n new Headers(),\n )\n }\n\n const throwClassifiedHttpError = (\n status: number,\n errorBody: string,\n parsedBody: object | undefined,\n responseHeaders: Headers,\n requestUrl: string,\n rateHint = '',\n preclassifiedFailure?: ReturnType,\n ): never => {\n const failure =\n preclassifiedFailure ??\n classifyOpenAIHttpFailure({\n status,\n body: errorBody,\n url: requestUrl,\n hasImages: bodyContainsImages(),\n })\n const failureWithUrl = { ...failure, requestUrl: failure.requestUrl ?? requestUrl }\n const redactedUrl = redactUrlForDiagnostics(requestUrl)\n\n logForDebugging(\n `[OpenAIShim] request failed category=${failure.category} retryable=${failure.retryable} status=${status} method=POST url=${redactedUrl} model=${request.resolvedModel}`,\n { level: 'warn' },\n )\n\n throw APIError.generate(\n status,\n parsedBody,\n buildOpenAICompatibilityErrorMessage(\n `OpenAI API error ${status}: ${errorBody}${rateHint}`,\n failureWithUrl,\n ),\n responseHeaders,\n )\n }\n\n let response: Response | undefined\n const provider = request.baseUrl.includes('nvidia') ? 'nvidia-nim'\n : request.baseUrl.includes('minimax') ? 'minimax'\n : request.baseUrl.includes('xiaomimimo') || request.baseUrl.includes('mimo-v2') ? 'xiaomi-mimo'\n : request.baseUrl.includes('localhost:11434') || request.baseUrl.includes('localhost:11435') ? 'ollama'\n : request.baseUrl.includes('anthropic') ? 'anthropic'\n : 'openai'\n const { correlationId, startTime } = logApiCallStart(provider, request.resolvedModel)\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n response = await fetchWithProxyRetry(\n requestUrl,\n buildFetchInit(),\n )\n } catch (error) {\n const isAbortError =\n options?.signal?.aborted === true ||\n (typeof DOMException !== 'undefined' &&\n error instanceof DOMException &&\n error.name === 'AbortError') ||\n (typeof error === 'object' &&\n error !== null &&\n 'name' in error &&\n error.name === 'AbortError')\n\n if (isAbortError) {\n throw error\n }\n\n const failure = classifyOpenAINetworkFailure(error, {\n url: requestUrl,\n })\n\n if (\n isLocal &&\n failure.category === 'localhost_resolution_failed' &&\n promoteNextLocalBaseUrl('localhost_resolution_failed')\n ) {\n continue\n }\n\n throwClassifiedTransportError(error, requestUrl, failure)\n }\n\n // After the try/catch, response is guaranteed to be defined — the catch\n // block always throws (throwClassifiedTransportError returns never).\n if (!response) continue\n\n if (response.ok) {\n let tokensIn = 0\n let tokensOut = 0\n // Skip clone() for streaming responses - it blocks until full body is received,\n // defeating the purpose of streaming. Usage data is already sent via\n // stream_options: { include_usage: true } and can be extracted from the stream.\n if (!params.stream) {\n try {\n const clone = response.clone()\n const data = await clone.json()\n tokensIn = data.usage?.prompt_tokens ?? 0\n tokensOut = data.usage?.completion_tokens ?? 0\n } catch { /* ignore */ }\n }\n logApiCallEnd(correlationId, startTime, request.resolvedModel, 'success', tokensIn, tokensOut, false)\n return response\n }\n\n if (\n isGithub &&\n response.status === 429 &&\n attempt < maxAttempts - 1\n ) {\n await response.text().catch(() => {})\n const delaySec = Math.min(\n GITHUB_429_BASE_DELAY_SEC * 2 ** attempt,\n GITHUB_429_MAX_DELAY_SEC,\n )\n await sleepMs(delaySec * 1000)\n continue\n }\n // Read body exactly once here — Response body is a stream that can only\n // be consumed a single time.\n const errorBody = await response.text().catch(() => 'unknown error')\n const rateHint =\n isGithub && response.status === 429 ? formatRetryAfterHint(response) : ''\n\n // If GitHub Copilot returns error about /chat/completions,\n // try the /responses endpoint (needed for GPT-5+ models)\n if (isGithub && response.status === 400) {\n if (errorBody.includes('/chat/completions') || errorBody.includes('not accessible')) {\n const responsesUrl = `${request.baseUrl}/responses`\n const responsesBody = buildResponsesBody()\n\n let responsesResponse!: Response\n try {\n responsesResponse = await fetchWithProxyRetry(responsesUrl, {\n method: 'POST',\n headers,\n body: stableStringifyJson(responsesBody),\n signal: options?.signal,\n })\n } catch (error) {\n throwClassifiedTransportError(error, responsesUrl)\n }\n\n if (responsesResponse.ok) {\n return responsesResponse\n }\n const responsesErrorBody = await responsesResponse.text().catch(() => 'unknown error')\n const responsesFailure = classifyOpenAIHttpFailure({\n status: responsesResponse.status,\n body: responsesErrorBody,\n hasImages: bodyContainsImages(),\n })\n let responsesErrorResponse: object | undefined\n try { responsesErrorResponse = JSON.parse(responsesErrorBody) } catch { /* raw text */ }\n throwClassifiedHttpError(\n responsesResponse.status,\n responsesErrorBody,\n responsesErrorResponse,\n responsesResponse.headers,\n responsesUrl,\n '',\n responsesFailure,\n )\n }\n }\n\n const failure = classifyOpenAIHttpFailure({\n status: response.status,\n body: errorBody,\n hasImages: bodyContainsImages(),\n })\n\n if (\n isLocal &&\n failure.category === 'endpoint_not_found' &&\n promoteNextLocalBaseUrl('endpoint_not_found')\n ) {\n continue\n }\n\n const hasToolsPayload =\n effectiveTransport === 'responses' || effectiveTransport === 'responses_compat' || effectiveTransport === 'anthropic_messages' || effectiveTransport === 'gemini'\n ? Array.isArray(params.tools) && params.tools.length > 0\n : Array.isArray(body.tools) && body.tools.length > 0\n\n if (\n !didRetryWithoutTools &&\n failure.category === 'tool_call_incompatible' &&\n shouldAttemptLocalToollessRetry({\n baseUrl: activeBaseUrl,\n hasTools: hasToolsPayload,\n })\n ) {\n didRetryWithoutTools = true\n delete body.tools\n delete body.tool_choice\n omitResponsesTools = true\n omitAnthropicTools = true\n omitGeminiTools = true\n refreshSerializedBody()\n\n logForDebugging(\n `[OpenAIShim] self-heal retry reason=tool_call_incompatible mode=toolless method=POST url=${redactUrlForDiagnostics(requestUrl)} model=${request.resolvedModel}`,\n { level: 'warn' },\n )\n continue\n }\n\n let errorResponse: object | undefined\n try { errorResponse = JSON.parse(errorBody) } catch { /* raw text */ }\n throwClassifiedHttpError(\n response.status,\n errorBody,\n errorResponse,\n response.headers as unknown as Headers,\n requestUrl,\n rateHint,\n failure,\n )\n }\n\n throw APIError.generate(\n 500, undefined, 'OpenAI shim: request loop exited unexpectedly',\n new Headers(),\n )\n }\n\n private _convertNonStreamingResponse(\n data: {\n id?: string\n model?: string\n choices?: Array<{\n message?: {\n role?: string\n content?:\n | string\n | null\n | Array<{ type?: string; text?: string }>\n reasoning_content?: string | null\n extra_content?: Record\n tool_calls?: Array<{\n id: string\n function: { name: string; arguments: string }\n extra_content?: Record\n }>\n }\n finish_reason?: string\n }>\n usage?: {\n prompt_tokens?: number\n completion_tokens?: number\n prompt_tokens_details?: {\n cached_tokens?: number\n }\n }\n },\n model: string,\n ) {\n const choice = data.choices?.[0]\n const content: Array> = []\n\n // Some reasoning models (e.g. GLM-5) put their chain-of-thought in\n // reasoning_content while content stays null. Preserve it as a thinking\n // block, but do not surface it as visible assistant text.\n const reasoningText = choice?.message?.reasoning_content\n if (typeof reasoningText === 'string' && reasoningText) {\n content.push({ type: 'thinking', thinking: reasoningText })\n }\n const rawContent =\n choice?.message?.content !== '' && choice?.message?.content != null\n ? choice?.message?.content\n : null\n if (typeof rawContent === 'string' && rawContent) {\n const strippedContent = stripThinkTags(rawContent)\n const rawToolCalls = choice?.message?.tool_calls\n ? null\n : parseRawToolCallsRequestedText(strippedContent)\n if (rawToolCalls) {\n for (const toolCall of rawToolCalls) {\n content.push({\n type: 'tool_use',\n id: toolCall.id,\n name: toolCall.name,\n input: JSON.parse(toolCall.argumentsJson),\n })\n }\n } else {\n content.push({\n type: 'text',\n text: strippedContent,\n })\n }\n } else if (Array.isArray(rawContent) && rawContent.length > 0) {\n const parts: string[] = []\n for (const part of rawContent) {\n if (\n part &&\n typeof part === 'object' &&\n part.type === 'text' &&\n typeof part.text === 'string'\n ) {\n parts.push(part.text)\n }\n }\n const joined = parts.join('\\n')\n if (joined) {\n const strippedContent = stripThinkTags(joined)\n const rawToolCalls = choice?.message?.tool_calls\n ? null\n : parseRawToolCallsRequestedText(strippedContent)\n if (rawToolCalls) {\n for (const toolCall of rawToolCalls) {\n content.push({\n type: 'tool_use',\n id: toolCall.id,\n name: toolCall.name,\n input: JSON.parse(toolCall.argumentsJson),\n })\n }\n } else {\n content.push({\n type: 'text',\n text: strippedContent,\n })\n }\n }\n }\n\n // 0.18.11 HYBRID: also parse markdown-style tool calls when\n // choice.message.tool_calls exists but is empty (some text-completion\n // models like MiniMax-M3 via tokenrouter sometimes return tool_calls:[]\n // and put the actual tool call in `content` as a fenced JSON block).\n // The parser is idempotent — if `tool_calls` is non-empty, the array\n // wins. If empty, we try the markdown parser on the content.\n const hasNativeToolCalls = choice?.message?.tool_calls && choice.message.tool_calls.length > 0\n if (hasNativeToolCalls) {\n for (const tc of choice.message.tool_calls) {\n const input = normalizeToolArguments(\n tc.function.name,\n tc.function.arguments,\n )\n const toolExtraContent = tc.extra_content ?? choice.message.extra_content\n const toolSignature =\n geminiThoughtSignatureFromExtraContent(tc.extra_content) ??\n geminiThoughtSignatureFromExtraContent(choice.message.extra_content)\n const mergedToolExtraContent = mergeGeminiThoughtSignature(\n toolExtraContent,\n toolSignature,\n )\n content.push({\n type: 'tool_use',\n id: tc.id,\n name: tc.function.name,\n input,\n ...(mergedToolExtraContent ? { extra_content: mergedToolExtraContent } : {}),\n ...(toolSignature ? { signature: toolSignature } : {}),\n })\n }\n } else if (choice?.message?.content) {\n // 0.18.11 HYBRID FALLBACK: the upstream returned content but no\n // native tool_calls array. Many text-completion models (M3 via\n // tokenrouter, llama-3.1-8b) emit tool calls as fenced JSON\n // blocks in `content` instead of a `tool_calls` array. Try to\n // parse those and convert to tool_use blocks. This makes the\n // atuscode CLI work with EVERY text-completion model — not just\n // Ollama — without requiring the user to swap to a native-\n // tool_calls model.\n const strippedContent = stripThinkTags(choice.message.content)\n const rawToolCalls = parseRawToolCallsRequestedText(strippedContent)\n if (rawToolCalls) {\n for (const toolCall of rawToolCalls) {\n content.push({\n type: 'tool_use',\n id: toolCall.id,\n name: toolCall.name,\n input: JSON.parse(toolCall.argumentsJson),\n })\n }\n // Replace the original text content with the version that has\n // the fenced tool-call block stripped, so the user doesn't see\n // the raw JSON in the terminal.\n if (content.length > 0 && content[0].type === 'text') {\n content[0] = { ...content[0], text: strippedContent.replace(/```[a-zA-Z0-9_-]*\\n[\\s\\S]*?\"name\"\\s*:[\\s\\S]*?```/g, '').trim() || '(tool call emitted)' }\n }\n }\n }\n\n const stopReason =\n choice?.finish_reason === 'tool_calls' ||\n content.some(block => block.type === 'tool_use')\n ? 'tool_use'\n : choice?.finish_reason === 'length'\n ? 'max_tokens'\n : 'end_turn'\n\n if (choice?.finish_reason === 'content_filter' || choice?.finish_reason === 'safety') {\n content.push({\n type: 'text',\n text: '\\n\\n[Content blocked by provider safety filter]',\n })\n }\n\n return {\n id: data.id ?? makeMessageId(),\n type: 'message',\n role: 'assistant',\n content,\n model: data.model ?? model,\n stop_reason: stopReason,\n stop_sequence: null,\n usage: buildAnthropicUsageFromRawUsage(\n data.usage as unknown as Record | undefined,\n ),\n }\n }\n\n private _convertGeminiToAnthropicResponse(\n data: Record,\n model: string,\n ) {\n const content: Array> = []\n let hasToolUse = false\n const candidates = data.candidates as Array> | undefined\n const candidate = candidates?.[0]\n const candidateContent = candidate?.content as { parts?: Array> } | undefined\n\n if (candidateContent?.parts) {\n for (const part of candidateContent.parts) {\n const text = part.text as string | undefined\n if (text) {\n content.push({ type: 'text', text })\n }\n const fc = part.functionCall as { name?: string; args?: unknown } | undefined\n if (fc?.name) {\n hasToolUse = true\n content.push({\n type: 'tool_use',\n id: `toolu_${crypto.randomUUID().replace(/-/g, '').slice(0, 24)}`,\n name: fc.name,\n input: fc.args ?? {},\n })\n }\n }\n }\n\n const stopReason =\n hasToolUse\n ? 'tool_use'\n : candidate?.finishReason === 'MAX_TOKENS'\n ? 'max_tokens'\n : 'end_turn'\n\n const usageMetadata = data.usageMetadata as Record | undefined\n const usage = buildAnthropicUsageFromRawUsage({\n input_tokens: usageMetadata?.promptTokenCount ?? 0,\n output_tokens: (usageMetadata?.candidatesTokenCount ?? 0) + (usageMetadata?.thoughtsTokenCount ?? 0),\n } as unknown as Record)\n\n return {\n id: makeMessageId(),\n type: 'message',\n role: 'assistant',\n content,\n model,\n stop_reason: stopReason,\n stop_sequence: null,\n usage,\n }\n }\n}\n\nclass OpenAIShimBeta {\n messages: OpenAIShimMessages\n reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh'\n\n constructor(defaultHeaders: Record, reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh', providerOverride?: { model: string; baseURL: string; apiKey: string }) {\n this.messages = new OpenAIShimMessages(defaultHeaders, reasoningEffort, providerOverride)\n this.reasoningEffort = reasoningEffort\n }\n}\n\nexport function createOpenAIShimClient(options: {\n defaultHeaders?: Record\n maxRetries?: number\n timeout?: number\n reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh'\n providerOverride?: { model: string; baseURL: string; apiKey: string }\n}): unknown {\n hydrateGeminiAccessTokenFromSecureStorage()\n hydrateGithubModelsTokenFromSecureStorage()\n hydrateOpenAIShimCompatibilityEnv()\n\n const beta = new OpenAIShimBeta({\n ...(options.defaultHeaders ?? {}),\n }, options.reasoningEffort, options.providerOverride)\n\n return {\n beta,\n messages: beta.messages,\n }\n}\n\n// Test-only surface (same pattern as WebSearchTool's __test export).\nexport const __test = { convertMessages }\n"}}, "src/integrations/vendors/atlas-cloud.ts": {"b2e10afbdf8cb6ae97322d1ef80e9140f5173378": {"t": 1785596051.983992, "n": 7192, "h": "4aaf9cf00159ad75", "c": "import { defineVendor } from '../define.js'\n\nexport default defineVendor({\n id: 'atlas-cloud',\n label: 'Atlas Cloud',\n classification: 'openai-compatible',\n defaultBaseUrl: 'https://api.atlascloud.ai/v1',\n defaultModel: 'deepseek-ai/deepseek-v4-pro',\n requiredEnvVars: ['ATLAS_CLOUD_API_KEY'],\n setup: {\n requiresAuth: true,\n authMode: 'api-key',\n credentialEnvVars: ['ATLAS_CLOUD_API_KEY'],\n dedicatedCredentialsOnly: true,\n },\n transportConfig: {\n kind: 'openai-compatible',\n },\n preset: {\n id: 'atlas-cloud',\n description: 'Atlas Cloud AI (OpenAI-compatible)',\n apiKeyEnvVars: ['ATLAS_CLOUD_API_KEY'],\n modelEnvVars: ['ATLAS_CLOUD_MODEL', 'OPENAI_MODEL'],\n },\n validation: {\n kind: 'credential-env',\n routing: {\n matchDefaultBaseUrl: true,\n },\n credentialEnvVars: ['ATLAS_CLOUD_API_KEY'],\n missingCredentialMessage:\n 'Atlas Cloud auth is required. Set ATLAS_CLOUD_API_KEY.',\n },\n catalog: {\n source: 'static',\n // Mirrors https://www.atlascloud.ai/models/list/llm\n models: [\n { id: 'deepseek-ai/deepseek-v4-pro', apiName: 'deepseek-ai/deepseek-v4-pro', label: 'DeepSeek V4 Pro', contextWindow: 1_048_576 },\n { id: 'deepseek-ai/deepseek-v4-flash', apiName: 'deepseek-ai/deepseek-v4-flash', label: 'DeepSeek V4 Flash', contextWindow: 1_048_576 },\n { id: 'deepseek-ai/deepseek-v3.2', apiName: 'deepseek-ai/deepseek-v3.2', label: 'DeepSeek V3.2', contextWindow: 163_840 },\n { id: 'deepseek-ai/DeepSeek-V3.2-Exp', apiName: 'deepseek-ai/DeepSeek-V3.2-Exp', label: 'DeepSeek V3.2 Exp', contextWindow: 163_840 },\n { id: 'deepseek-ai/DeepSeek-V3.1', apiName: 'deepseek-ai/DeepSeek-V3.1', label: 'DeepSeek V3.1', contextWindow: 131_072 },\n { id: 'deepseek-ai/DeepSeek-V3-0324', apiName: 'deepseek-ai/DeepSeek-V3-0324', label: 'DeepSeek V3 0324', contextWindow: 131_072 },\n { id: 'deepseek-ai/deepseek-r1-0528', apiName: 'deepseek-ai/deepseek-r1-0528', label: 'DeepSeek R1 0528', contextWindow: 131_072 },\n { id: 'anthropic/claude-opus-4.8', apiName: 'anthropic/claude-opus-4.8', label: 'Claude Opus 4.8', contextWindow: 1_000_000 },\n { id: 'openai/gpt-5.5', apiName: 'openai/gpt-5.5', label: 'GPT 5.5', contextWindow: 1_050_000 },\n { id: 'openai/gpt-5.4', apiName: 'openai/gpt-5.4', label: 'GPT 5.4', contextWindow: 400_000 },\n { id: 'google/gemini-3.5-flash', apiName: 'google/gemini-3.5-flash', label: 'Gemini 3.5 Flash', contextWindow: 1_048_576 },\n { id: 'google/gemini-3.1-pro-preview', apiName: 'google/gemini-3.1-pro-preview', label: 'Gemini 3.1 Pro Preview', contextWindow: 1_000_000 },\n { id: 'google/gemini-3.1-flash-lite', apiName: 'google/gemini-3.1-flash-lite', label: 'Gemini 3.1 Flash Lite', contextWindow: 1_048_576 },\n { id: 'xai/grok-4.3', apiName: 'xai/grok-4.3', label: 'Grok 4.3', contextWindow: 1_000_000 },\n { id: 'xai/grok-build-0.1', apiName: 'xai/grok-build-0.1', label: 'Grok Build 0.1', contextWindow: 262_144 },\n { id: 'moonshotai/kimi-k2.6', apiName: 'moonshotai/kimi-k2.6', label: 'Kimi K2.6', contextWindow: 262_144 },\n { id: 'moonshotai/kimi-k2.5', apiName: 'moonshotai/kimi-k2.5', label: 'Kimi K2.5', contextWindow: 262_144 },\n { id: 'moonshotai/Kimi-K2-Thinking', apiName: 'moonshotai/Kimi-K2-Thinking', label: 'Kimi K2 Thinking', contextWindow: 262_144 },\n { id: 'moonshotai/Kimi-K2-Instruct-0905', apiName: 'moonshotai/Kimi-K2-Instruct-0905', label: 'Kimi K2 Instruct 0905', contextWindow: 262_144 },\n { id: 'moonshotai/Kimi-K2-Instruct', apiName: 'moonshotai/Kimi-K2-Instruct', label: 'Kimi K2 Instruct', contextWindow: 131_072 },\n { id: 'zai-org/glm-5.1', apiName: 'zai-org/glm-5.1', label: 'GLM 5.1', contextWindow: 202_752 },\n { id: 'zai-org/glm-5', apiName: 'zai-org/glm-5', label: 'GLM 5', contextWindow: 202_752 },\n { id: 'zai-org/glm-5-turbo', apiName: 'zai-org/glm-5-turbo', label: 'GLM 5 Turbo', contextWindow: 262_144 },\n { id: 'zai-org/glm-5v-turbo', apiName: 'zai-org/glm-5v-turbo', label: 'GLM 5V Turbo', contextWindow: 202_752 },\n { id: 'zai-org/glm-4.7', apiName: 'zai-org/glm-4.7', label: 'GLM 4.7', contextWindow: 202_752 },\n { id: 'zai-org/GLM-4.6', apiName: 'zai-org/GLM-4.6', label: 'GLM 4.6', contextWindow: 202_752 },\n { id: 'minimaxai/minimax-m3', apiName: 'minimaxai/minimax-m3', label: 'MiniMax M3', contextWindow: 524_288 },\n { id: 'minimaxai/minimax-m2.7', apiName: 'minimaxai/minimax-m2.7', label: 'MiniMax M2.7', contextWindow: 196_608 },\n { id: 'minimaxai/minimax-m2.5', apiName: 'minimaxai/minimax-m2.5', label: 'MiniMax M2.5', contextWindow: 196_608 },\n { id: 'minimaxai/minimax-m2.1', apiName: 'minimaxai/minimax-m2.1', label: 'MiniMax M2.1', contextWindow: 196_608 },\n { id: 'MiniMaxAI/MiniMax-M2', apiName: 'MiniMaxAI/MiniMax-M2', label: 'MiniMax M2', contextWindow: 196_608 },\n { id: 'qwen/qwen3.6-plus', apiName: 'qwen/qwen3.6-plus', label: 'Qwen3.6 Plus', contextWindow: 1_000_000 },\n { id: 'qwen/qwen3.6-35b-a3b', apiName: 'qwen/qwen3.6-35b-a3b', label: 'Qwen3.6 35B A3B', contextWindow: 262_144 },\n { id: 'qwen/qwen3.5-397b-a17b', apiName: 'qwen/qwen3.5-397b-a17b', label: 'Qwen3.5 397B A17B', contextWindow: 262_144 },\n { id: 'qwen/qwen3.5-122b-a10b', apiName: 'qwen/qwen3.5-122b-a10b', label: 'Qwen3.5 122B A10B', contextWindow: 262_144 },\n { id: 'qwen/qwen3.5-35b-a3b', apiName: 'qwen/qwen3.5-35b-a3b', label: 'Qwen3.5 35B A3B', contextWindow: 262_144 },\n { id: 'qwen/qwen3.5-27b', apiName: 'qwen/qwen3.5-27b', label: 'Qwen3.5 27B', contextWindow: 262_144 },\n { id: 'qwen/qwen3-coder-next', apiName: 'qwen/qwen3-coder-next', label: 'Qwen3 Coder Next', contextWindow: 262_144 },\n { id: 'qwen/qwen3-max-2026-01-23', apiName: 'qwen/qwen3-max-2026-01-23', label: 'Qwen3 Max 2026-01-23', contextWindow: 252_000 },\n { id: 'qwen/qwen3-vl-30b-a3b-thinking', apiName: 'qwen/qwen3-vl-30b-a3b-thinking', label: 'Qwen3 VL 30B A3B Thinking', contextWindow: 128_000 },\n { id: 'qwen/qwen3-vl-30b-a3b-instruct', apiName: 'qwen/qwen3-vl-30b-a3b-instruct', label: 'Qwen3 VL 30B A3B Instruct', contextWindow: 128_000 },\n { id: 'qwen/qwen3-vl-8b-instruct', apiName: 'qwen/qwen3-vl-8b-instruct', label: 'Qwen3 VL 8B Instruct', contextWindow: 128_000 },\n { id: 'Qwen/Qwen3-VL-235B-A22B-Instruct', apiName: 'Qwen/Qwen3-VL-235B-A22B-Instruct', label: 'Qwen3 VL 235B A22B Instruct', contextWindow: 131_072 },\n { id: 'Qwen/Qwen3-Next-80B-A3B-Thinking', apiName: 'Qwen/Qwen3-Next-80B-A3B-Thinking', label: 'Qwen3 Next 80B A3B Thinking', contextWindow: 262_144 },\n { id: 'Qwen/Qwen3-Next-80B-A3B-Instruct', apiName: 'Qwen/Qwen3-Next-80B-A3B-Instruct', label: 'Qwen3 Next 80B A3B Instruct', contextWindow: 262_144 },\n { id: 'Qwen/Qwen3-30B-A3B-Instruct-2507', apiName: 'Qwen/Qwen3-30B-A3B-Instruct-2507', label: 'Qwen3 30B A3B Instruct 2507', contextWindow: 131_072 },\n { id: 'kwaipilot/kat-coder-pro-v2', apiName: 'kwaipilot/kat-coder-pro-v2', label: 'KAT Coder Pro V2', contextWindow: 262_144 },\n { id: 'owl', apiName: 'owl', label: 'OWL', contextWindow: 1_048_576, notes: 'Free' },\n ],\n },\n usage: { supported: false },\n})\n"}}, "src/services/api/openaiShim.test.ts": {"b2e10afbdf8cb6ae97322d1ef80e9140f5173378": {"t": 1785746538.0608664, "n": 189362, "h": "f722d017d16b4847", "c": "import { afterEach, beforeEach, expect, mock, test } from 'bun:test'\nimport { acquireSharedMutationLock, releaseSharedMutationLock } from '../../test/sharedMutationLock.js'\nimport { asMockFetch } from '../../test/typedMocks.js'\nimport { _clearRegistryForTesting, ensureIntegrationsLoaded, registerGateway } from '../../integrations/index.ts'\nimport { applyProviderFlag } from '../../utils/providerFlag.ts'\nimport { applyProviderProfileToProcessEnv } from '../../utils/providerProfiles.ts'\nimport { createOpenAIShimClient } from './openaiShim.ts'\n\ntype FetchType = typeof globalThis.fetch\n\nconst originalEnv = {\n OPENAI_BASE_URL: process.env.OPENAI_BASE_URL,\n OPENAI_API_BASE: process.env.OPENAI_API_BASE,\n OPENAI_API_KEY: process.env.OPENAI_API_KEY,\n OPENAI_MODEL: process.env.OPENAI_MODEL,\n OPENAI_API_FORMAT: process.env.OPENAI_API_FORMAT,\n OPENAI_AUTH_HEADER: process.env.OPENAI_AUTH_HEADER,\n OPENAI_AUTH_SCHEME: process.env.OPENAI_AUTH_SCHEME,\n OPENAI_AUTH_HEADER_VALUE: process.env.OPENAI_AUTH_HEADER_VALUE,\n CLAUDE_CODE_USE_GITHUB: process.env.CLAUDE_CODE_USE_GITHUB,\n GITHUB_TOKEN: process.env.GITHUB_TOKEN,\n GH_TOKEN: process.env.GH_TOKEN,\n CLAUDE_CODE_USE_OPENAI: process.env.CLAUDE_CODE_USE_OPENAI,\n CLAUDE_CODE_USE_GEMINI: process.env.CLAUDE_CODE_USE_GEMINI,\n GEMINI_API_KEY: process.env.GEMINI_API_KEY,\n GOOGLE_API_KEY: process.env.GOOGLE_API_KEY,\n GEMINI_ACCESS_TOKEN: process.env.GEMINI_ACCESS_TOKEN,\n GEMINI_AUTH_MODE: process.env.GEMINI_AUTH_MODE,\n GEMINI_BASE_URL: process.env.GEMINI_BASE_URL,\n GEMINI_MODEL: process.env.GEMINI_MODEL,\n GOOGLE_CLOUD_PROJECT: process.env.GOOGLE_CLOUD_PROJECT,\n ANTHROPIC_CUSTOM_HEADERS: process.env.ANTHROPIC_CUSTOM_HEADERS,\n NVIDIA_API_KEY: process.env.NVIDIA_API_KEY,\n NVIDIA_NIM: process.env.NVIDIA_NIM,\n MINIMAX_API_KEY: process.env.MINIMAX_API_KEY,\n BNKR_API_KEY: process.env.BNKR_API_KEY,\n BANKR_BASE_URL: process.env.BANKR_BASE_URL,\n BANKR_MODEL: process.env.BANKR_MODEL,\n OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY,\n DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,\n MIMO_API_KEY: process.env.MIMO_API_KEY,\n OPENGATEWAY_API_KEY: process.env.OPENGATEWAY_API_KEY,\n OPENGATEWAY_BASE_URL: process.env.OPENGATEWAY_BASE_URL,\n CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED: process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED,\n CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID: process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID,\n}\n\nconst originalFetch = globalThis.fetch\n\nfunction restoreEnv(key: string, value: string | undefined): void {\n if (value === undefined) {\n delete process.env[key]\n } else {\n process.env[key] = value\n }\n}\n\ntype OpenAIShimClient = {\n beta: {\n messages: {\n create: (\n params: Record,\n options?: Record,\n ) => Promise & {\n withResponse: () => Promise<{ data: AsyncIterable> }>\n }\n }\n }\n}\n\nfunction makeSseResponse(lines: string[]): Response {\n const encoder = new TextEncoder()\n return new Response(\n new ReadableStream({\n start(controller) {\n for (const line of lines) {\n controller.enqueue(encoder.encode(line))\n }\n controller.close()\n },\n }),\n {\n headers: {\n 'Content-Type': 'text/event-stream',\n },\n },\n )\n}\n\nfunction makeStreamChunks(chunks: unknown[]): string[] {\n return [\n ...chunks.map(chunk => `data: ${JSON.stringify(chunk)}\\n\\n`),\n 'data: [DONE]\\n\\n',\n ]\n}\n\nfunction makeChatCompletionResponse(model: string): Response {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-test',\n model,\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n}\n\nasync function captureChatCompletionRequest(\n model = 'mimo-v2.5-pro',\n): Promise<{ authorization: string | null; url: string | null }> {\n let authorization: string | null = null\n let url: string | null = null\n\n globalThis.fetch = (async (input, init) => {\n url = String(input)\n const headers = init?.headers as Record | undefined\n authorization = headers?.Authorization ?? headers?.authorization ?? null\n\n return makeChatCompletionResponse(model)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model,\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n return { authorization, url }\n}\n\nbeforeEach(async () => {\n await acquireSharedMutationLock('openaiShim.test.ts')\n process.env.OPENAI_BASE_URL = 'http://example.test/v1'\n delete process.env.OPENAI_API_BASE\n process.env.OPENAI_API_KEY = 'test-key'\n delete process.env.OPENAI_MODEL\n delete process.env.OPENAI_API_FORMAT\n delete process.env.OPENAI_AUTH_HEADER\n delete process.env.OPENAI_AUTH_SCHEME\n delete process.env.OPENAI_AUTH_HEADER_VALUE\n delete process.env.CLAUDE_CODE_USE_GITHUB\n delete process.env.GITHUB_TOKEN\n delete process.env.GH_TOKEN\n delete process.env.CLAUDE_CODE_USE_OPENAI\n delete process.env.CLAUDE_CODE_USE_GEMINI\n delete process.env.GEMINI_API_KEY\n delete process.env.GOOGLE_API_KEY\n delete process.env.GEMINI_ACCESS_TOKEN\n delete process.env.GEMINI_AUTH_MODE\n delete process.env.GEMINI_BASE_URL\n delete process.env.GEMINI_MODEL\n delete process.env.GOOGLE_CLOUD_PROJECT\n delete process.env.ANTHROPIC_CUSTOM_HEADERS\n delete process.env.NVIDIA_API_KEY\n delete process.env.NVIDIA_NIM\n delete process.env.MINIMAX_API_KEY\n delete process.env.BNKR_API_KEY\n delete process.env.BANKR_BASE_URL\n delete process.env.BANKR_MODEL\n delete process.env.OPENROUTER_API_KEY\n delete process.env.DEEPSEEK_API_KEY\n delete process.env.MIMO_API_KEY\n delete process.env.OPENGATEWAY_API_KEY\n delete process.env.OPENGATEWAY_BASE_URL\n delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED\n delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID\n})\n\nafterEach(() => {\n try {\n restoreEnv('OPENAI_BASE_URL', originalEnv.OPENAI_BASE_URL)\n restoreEnv('OPENAI_API_BASE', originalEnv.OPENAI_API_BASE)\n restoreEnv('OPENAI_API_KEY', originalEnv.OPENAI_API_KEY)\n restoreEnv('OPENAI_MODEL', originalEnv.OPENAI_MODEL)\n restoreEnv('OPENAI_API_FORMAT', originalEnv.OPENAI_API_FORMAT)\n restoreEnv('OPENAI_AUTH_HEADER', originalEnv.OPENAI_AUTH_HEADER)\n restoreEnv('OPENAI_AUTH_SCHEME', originalEnv.OPENAI_AUTH_SCHEME)\n restoreEnv('OPENAI_AUTH_HEADER_VALUE', originalEnv.OPENAI_AUTH_HEADER_VALUE)\n restoreEnv('CLAUDE_CODE_USE_GITHUB', originalEnv.CLAUDE_CODE_USE_GITHUB)\n restoreEnv('GITHUB_TOKEN', originalEnv.GITHUB_TOKEN)\n restoreEnv('GH_TOKEN', originalEnv.GH_TOKEN)\n restoreEnv('CLAUDE_CODE_USE_OPENAI', originalEnv.CLAUDE_CODE_USE_OPENAI)\n restoreEnv('CLAUDE_CODE_USE_GEMINI', originalEnv.CLAUDE_CODE_USE_GEMINI)\n restoreEnv('GEMINI_API_KEY', originalEnv.GEMINI_API_KEY)\n restoreEnv('GOOGLE_API_KEY', originalEnv.GOOGLE_API_KEY)\n restoreEnv('GEMINI_ACCESS_TOKEN', originalEnv.GEMINI_ACCESS_TOKEN)\n restoreEnv('GEMINI_AUTH_MODE', originalEnv.GEMINI_AUTH_MODE)\n restoreEnv('GEMINI_BASE_URL', originalEnv.GEMINI_BASE_URL)\n restoreEnv('GEMINI_MODEL', originalEnv.GEMINI_MODEL)\n restoreEnv('GOOGLE_CLOUD_PROJECT', originalEnv.GOOGLE_CLOUD_PROJECT)\n restoreEnv('ANTHROPIC_CUSTOM_HEADERS', originalEnv.ANTHROPIC_CUSTOM_HEADERS)\n restoreEnv('NVIDIA_API_KEY', originalEnv.NVIDIA_API_KEY)\n restoreEnv('NVIDIA_NIM', originalEnv.NVIDIA_NIM)\n restoreEnv('MINIMAX_API_KEY', originalEnv.MINIMAX_API_KEY)\n restoreEnv('BNKR_API_KEY', originalEnv.BNKR_API_KEY)\n restoreEnv('BANKR_BASE_URL', originalEnv.BANKR_BASE_URL)\n restoreEnv('BANKR_MODEL', originalEnv.BANKR_MODEL)\n restoreEnv('OPENROUTER_API_KEY', originalEnv.OPENROUTER_API_KEY)\n restoreEnv('DEEPSEEK_API_KEY', originalEnv.DEEPSEEK_API_KEY)\n restoreEnv('MIMO_API_KEY', originalEnv.MIMO_API_KEY)\n restoreEnv('OPENGATEWAY_API_KEY', originalEnv.OPENGATEWAY_API_KEY)\n restoreEnv('OPENGATEWAY_BASE_URL', originalEnv.OPENGATEWAY_BASE_URL)\n restoreEnv('CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED', originalEnv.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED)\n restoreEnv('CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID', originalEnv.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID)\n globalThis.fetch = originalFetch\n _clearRegistryForTesting()\n ensureIntegrationsLoaded()\n } finally {\n releaseSharedMutationLock()\n }\n})\n\ntest('strips canonical Anthropic headers from direct shim defaultHeaders', async () => {\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n defaultHeaders: {\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\n 'x-anthropic-additional-protection': 'true',\n 'x-claude-remote-session-id': 'remote-123',\n 'x-app': 'cli',\n 'x-client-app': 'sdk',\n 'x-safe-header': 'keep-me',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('anthropic-version')).toBeNull()\n expect(capturedHeaders?.get('anthropic-beta')).toBeNull()\n expect(capturedHeaders?.get('x-anthropic-additional-protection')).toBeNull()\n expect(capturedHeaders?.get('x-claude-remote-session-id')).toBeNull()\n expect(capturedHeaders?.get('x-app')).toBeNull()\n expect(capturedHeaders?.get('x-client-app')).toBeNull()\n expect(capturedHeaders?.get('x-safe-header')).toBe('keep-me')\n})\n\ntest('uses OpenAI-compatible responses endpoint when OPENAI_API_FORMAT=responses', async () => {\n process.env.OPENAI_API_FORMAT = 'responses'\n let capturedUrl = ''\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (input, init) => {\n capturedUrl = String(input)\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'resp-1',\n model: 'gpt-5.4',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n usage: {\n input_tokens: 8,\n output_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedUrl).toBe('http://example.test/v1/responses')\n expect(capturedBody?.model).toBe('gpt-5.4')\n expect(capturedBody?.instructions).toBe('test system')\n expect(capturedBody?.max_output_tokens).toBe(64)\n expect(capturedBody?.store).toBe(false)\n expect(capturedBody?.input).toEqual([\n {\n type: 'message',\n role: 'user',\n content: [{ type: 'input_text', text: 'hello' }],\n },\n ])\n})\n\ntest('uses OpenAI-compatible responses endpoint with text chunk types when OPENAI_API_FORMAT=responses_compat', async () => {\n process.env.OPENAI_API_FORMAT = 'responses_compat'\n let capturedUrl = ''\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (input, init) => {\n capturedUrl = String(input)\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'resp-1',\n model: 'gpt-5.4',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n usage: {\n input_tokens: 8,\n output_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedUrl).toBe('http://example.test/v1/responses')\n expect(capturedBody?.model).toBe('gpt-5.4')\n expect(capturedBody?.instructions).toBe('test system')\n expect(capturedBody?.max_output_tokens).toBe(64)\n expect(capturedBody?.store).toBe(false)\n expect(capturedBody?.input).toEqual([\n {\n type: 'message',\n role: 'user',\n content: [{ type: 'text', text: 'hello' }],\n },\n ])\n})\n\ntest('uses correct empty input fallback schema for standard responses and responses_compat', async () => {\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n return new Response(JSON.stringify({\n id: 'resp-1',\n model: 'test',\n output: [{ type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'ok' }] }]\n }), { headers: { 'Content-Type': 'application/json' } })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n process.env.OPENAI_API_FORMAT = 'responses'\n await client.beta.messages.create({\n model: 'test',\n max_tokens: 10,\n messages: [{ role: 'user', content: [] }],\n })\n\n expect(capturedBody?.input).toEqual([\n {\n type: 'message',\n role: 'user',\n content: [{ type: 'input_text', text: '' }],\n },\n ])\n\n process.env.OPENAI_API_FORMAT = 'responses_compat'\n await client.beta.messages.create({\n model: 'test',\n max_tokens: 10,\n messages: [{ role: 'user', content: [] }],\n })\n\n expect(capturedBody?.input).toEqual([\n {\n type: 'message',\n role: 'user',\n content: [{ type: 'text', text: '' }],\n },\n ])\n})\n\ntest('strips store from strict OpenAI-compatible responses providers', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.moonshot.ai/v1'\n process.env.OPENAI_API_FORMAT = 'responses'\n let capturedUrl = ''\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (input, init) => {\n capturedUrl = String(input)\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'resp-1',\n model: 'kimi-k2.5',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'kimi-k2.5',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedUrl).toBe('https://api.moonshot.ai/v1/responses')\n expect(capturedBody?.store).toBeUndefined()\n})\n\ntest('strips store when providerOverride routes chat_completions to the Gemini host', async () => {\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-gemini',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n defaultHeaders: {},\n providerOverride: {\n model: 'gemini-3.1-pro',\n baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',\n apiKey: 'gemini-key',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gemini-3.1-pro',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedBody?.store).toBeUndefined()\n})\n\ntest('strips store when providerOverride routes responses API to the Gemini host', async () => {\n process.env.OPENAI_API_FORMAT = 'responses'\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n return new Response(\n JSON.stringify({\n id: 'resp-gemini',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n defaultHeaders: {},\n providerOverride: {\n model: 'gemini-3.1-pro',\n baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',\n apiKey: 'gemini-key',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gemini-3.1-pro',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedBody?.store).toBeUndefined()\n})\n\ntest('uses custom OpenAI-compatible auth header value when configured', async () => {\n process.env.OPENAI_API_KEY = 'generic-key'\n process.env.OPENAI_AUTH_HEADER = 'api-key'\n process.env.OPENAI_AUTH_HEADER_VALUE = 'hicap-header-value'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('api-key')).toBe('hicap-header-value')\n expect(capturedHeaders?.get('authorization')).toBeNull()\n})\n\ntest('uses Hicap api-key auth header for the Hicap route', async () => {\n process.env.OPENAI_API_KEY = 'hicap-live-key'\n process.env.OPENAI_BASE_URL = 'https://api.hicap.ai/v1'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'claude-opus-4.7',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('api-key')).toBe('hicap-live-key')\n expect(capturedHeaders?.get('authorization')).toBeNull()\n})\n\ntest('defaults Authorization custom auth header to bearer scheme', async () => {\n process.env.OPENAI_API_KEY = 'authorization-key'\n process.env.OPENAI_AUTH_HEADER = 'Authorization'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('authorization')).toBe('Bearer authorization-key')\n})\n\ntest('honors bearer scheme for custom OpenAI-compatible auth headers', async () => {\n process.env.OPENAI_API_KEY = 'custom-key'\n process.env.OPENAI_AUTH_HEADER = 'X-Custom-Authorization'\n process.env.OPENAI_AUTH_SCHEME = 'bearer'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('x-custom-authorization')).toBe('Bearer custom-key')\n expect(capturedHeaders?.get('authorization')).toBeNull()\n})\n\ntest('ignores custom auth header value when no custom header is configured', async () => {\n delete process.env.OPENAI_API_KEY\n process.env.OPENAI_AUTH_HEADER_VALUE = 'gateway-header-value'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('authorization')).toBeNull()\n})\n\ntest('strips canonical Anthropic headers from per-request shim headers too', async () => {\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'gpt-4o',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n },\n {\n headers: {\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\n 'x-safe-header': 'keep-me',\n },\n },\n )\n\n expect(capturedHeaders?.get('anthropic-version')).toBeNull()\n expect(capturedHeaders?.get('anthropic-beta')).toBeNull()\n expect(capturedHeaders?.get('x-safe-header')).toBe('keep-me')\n})\n\ntest('applies descriptor static headers before client and request headers', async () => {\n let capturedHeaders: Headers | undefined\n\n registerGateway({\n id: 'shim-header-test',\n label: 'Shim Header Test',\n category: 'hosted',\n defaultBaseUrl: 'https://shim-header-test.example/v1',\n defaultModel: 'shim-test-model',\n setup: {\n requiresAuth: true,\n authMode: 'api-key',\n credentialEnvVars: ['OPENAI_API_KEY'],\n },\n transportConfig: {\n kind: 'openai-compatible',\n openaiShim: {\n headers: {\n 'x-static-header': 'from-descriptor',\n 'x-override-header': 'from-descriptor',\n },\n },\n },\n })\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://shim-header-test.example/v1'\n process.env.OPENAI_MODEL = 'shim-test-model'\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'shim-test-model',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n defaultHeaders: {\n 'x-override-header': 'from-client',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'shim-test-model',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n },\n {\n headers: {\n 'x-override-header': 'from-request',\n },\n },\n )\n\n expect(capturedHeaders?.get('x-static-header')).toBe('from-descriptor')\n expect(capturedHeaders?.get('x-override-header')).toBe('from-request')\n})\n\ntest('opengateway sends Accept-Encoding: identity header on chat requests', async () => {\n let capturedHeaders: Headers | undefined\n\n registerGateway({\n id: 'atuscode-opengateway-test',\n label: 'AtusCode Opengateway',\n category: 'aggregating',\n defaultBaseUrl: 'https://opengateway.atuscode.com/v1/xiaomi-mimo',\n defaultModel: 'mimo-v2.5-pro',\n setup: {\n requiresAuth: false,\n authMode: 'none',\n },\n transportConfig: {\n kind: 'openai-compatible',\n openaiShim: {\n headers: {\n 'Accept-Encoding': 'identity',\n },\n defaultAuthHeader: {\n name: 'api-key',\n scheme: 'raw',\n },\n preserveReasoningContent: true,\n requireReasoningContentOnAssistantMessages: true,\n reasoningContentFallback: '',\n maxTokensField: 'max_completion_tokens',\n supportsApiFormatSelection: false,\n supportsAuthHeaders: false,\n },\n },\n })\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://opengateway.atuscode.com/v1/xiaomi-mimo'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'mimo-v2.5-pro',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n },\n {},\n )\n\n expect(capturedHeaders?.get('Accept-Encoding')).toBe('identity')\n})\n\ntest('strips Anthropic-specific headers on GitHub Codex transport requests', async () => {\n let capturedHeaders: Headers | undefined\n\n process.env.CLAUDE_CODE_USE_GITHUB = '1'\n process.env.OPENAI_API_KEY = 'github-test-key'\n delete process.env.OPENAI_BASE_URL\n delete process.env.OPENAI_MODEL\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response('', {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n },\n })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'github:gpt-5-codex',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: true,\n },\n {\n headers: {\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\n 'x-anthropic-additional-protection': 'true',\n 'x-safe-header': 'keep-me',\n },\n },\n )\n\n expect(capturedHeaders?.get('anthropic-version')).toBeNull()\n expect(capturedHeaders?.get('anthropic-beta')).toBeNull()\n expect(capturedHeaders?.get('x-anthropic-additional-protection')).toBeNull()\n expect(capturedHeaders?.get('x-safe-header')).toBe('keep-me')\n expect(capturedHeaders?.get('authorization')).toBe('Bearer github-test-key')\n expect(capturedHeaders?.get('editor-plugin-version')).toBe('copilot-chat/0.26.7')\n})\n\ntest('strips Anthropic-specific headers on GitHub Codex transport with providerOverride API key', async () => {\n let capturedHeaders: Headers | undefined\n\n process.env.CLAUDE_CODE_USE_GITHUB = '1'\n process.env.OPENAI_API_KEY = 'env-should-not-win'\n delete process.env.OPENAI_BASE_URL\n delete process.env.OPENAI_MODEL\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response('', {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n },\n })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n providerOverride: {\n model: 'github:gpt-5-codex',\n baseURL: 'https://api.githubcopilot.com',\n apiKey: 'provider-override-key',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'ignored',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: true,\n },\n {\n headers: {\n 'anthropic-version': '2023-06-01',\n 'x-claude-remote-session-id': 'remote-123',\n 'x-safe-header': 'keep-me',\n },\n },\n )\n\n expect(capturedHeaders?.get('anthropic-version')).toBeNull()\n expect(capturedHeaders?.get('x-claude-remote-session-id')).toBeNull()\n expect(capturedHeaders?.get('x-safe-header')).toBe('keep-me')\n expect(capturedHeaders?.get('authorization')).toBe('Bearer provider-override-key')\n expect(capturedHeaders?.get('editor-plugin-version')).toBe('copilot-chat/0.26.7')\n})\n\ntest('preserves usage from final OpenAI stream chunk with empty choices', async () => {\n globalThis.fetch = (async (_input, init) => {\n const url = typeof _input === 'string' ? _input : _input.url\n expect(url).toBe('http://example.test/v1/chat/completions')\n\n const body = JSON.parse(String(init?.body))\n expect(body.stream).toBe(true)\n expect(body.stream_options).toEqual({ include_usage: true })\n\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'fake-model',\n choices: [\n {\n index: 0,\n delta: { role: 'assistant', content: 'hello world' },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'fake-model',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'fake-model',\n choices: [],\n usage: {\n prompt_tokens: 123,\n completion_tokens: 45,\n total_tokens: 168,\n },\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'fake-model',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const usageEvent = events.find(\n event => event.type === 'message_delta' && typeof event.usage === 'object' && event.usage !== null,\n ) as { usage?: { input_tokens?: number; output_tokens?: number } } | undefined\n\n expect(usageEvent).toBeDefined()\n expect(usageEvent?.usage?.input_tokens).toBe(123)\n expect(usageEvent?.usage?.output_tokens).toBe(45)\n})\n\ntest('uses max_tokens instead of max_completion_tokens for local providers', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n globalThis.fetch = (async (_input, init) => {\n const body = JSON.parse(String(init?.body))\n expect(body.max_tokens).toBe(64)\n expect(body.max_completion_tokens).toBeUndefined()\n expect(body.stream_options).toBeUndefined()\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'llama3.1:8b',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'hello',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 5,\n completion_tokens: 1,\n total_tokens: 6,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'llama3.1:8b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n})\n\ntest('keeps max_completion_tokens for non-local non-github providers', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n\n globalThis.fetch = (async (_input, init) => {\n const body = JSON.parse(String(init?.body))\n expect(body.max_completion_tokens).toBe(64)\n expect(body.max_tokens).toBeUndefined()\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'hello',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 5,\n completion_tokens: 1,\n total_tokens: 6,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n})\n\ntest('uses route-specific credential env vars for descriptor-backed openai-compatible routes', async () => {\n let capturedHeaders: Headers | undefined\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENROUTER_API_KEY = 'or-route-key'\n delete process.env.OPENAI_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'openai/gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 5,\n completion_tokens: 1,\n total_tokens: 6,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'openai/gpt-5-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('authorization')).toBe('Bearer or-route-key')\n})\n\ntest('preserves Gemini tool call extra_content in follow-up requests', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Use Bash' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'pwd' },\n extra_content: {\n google: {\n thought_signature: 'sig-123',\n },\n },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_1',\n content: 'D:\\\\repo',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n ) as { tool_calls?: Array> } | undefined\n\n expect(assistantWithToolCall?.tool_calls?.[0]).toMatchObject({\n id: 'call_1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: JSON.stringify({ command: 'pwd' }),\n },\n extra_content: {\n google: {\n thought_signature: 'sig-123',\n },\n },\n })\n})\n\ntest('replays Gemini tool signatures for OpenGateway Gemini models', async () => {\n process.env.OPENAI_BASE_URL = 'https://opengateway.atuscode.com/v1'\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [\n { role: 'user', content: 'Use Write' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Write',\n input: { file_path: 'todo.md', content: 'todo' },\n signature: 'sig-opengateway',\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_1',\n content: 'created',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n ) as { tool_calls?: Array> } | undefined\n\n expect(assistantWithToolCall?.tool_calls?.[0]).toMatchObject({\n id: 'call_1',\n extra_content: {\n google: {\n thought_signature: 'sig-opengateway',\n },\n },\n })\n})\n\ntest('OpenGateway MiMo replays real reasoning_content without adding empty fallback', async () => {\n process.env.OPENAI_BASE_URL = 'https://opengateway.atuscode.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-opengateway-mimo',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [\n { role: 'user', content: 'Use an agent' },\n {\n role: 'assistant',\n content: [\n {\n type: 'thinking',\n thinking: 'Need to inspect code with an agent.',\n },\n {\n type: 'tool_use',\n id: 'call_agent_1',\n name: 'Agent',\n input: {\n description: 'Inspect code',\n prompt: 'Look at the relevant code',\n subagent_type: 'general-purpose',\n },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_agent_1',\n content: 'Agent finished',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n )\n\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'Need to inspect code with an agent.',\n )\n expect(requestBody).not.toHaveProperty('store')\n})\n\ntest('Xiaomi MiMo replays real reasoning_content without adding empty fallback', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.xiaomimimo.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n process.env.MIMO_API_KEY = 'mimo-test-key'\n delete process.env.OPENAI_API_KEY\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-mimo',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [\n { role: 'user', content: 'Use an agent' },\n {\n role: 'assistant',\n content: [\n {\n type: 'thinking',\n thinking: 'Need to inspect code with an agent.',\n },\n {\n type: 'tool_use',\n id: 'call_agent_1',\n name: 'Agent',\n input: {\n description: 'Inspect code',\n prompt: 'Look at the relevant code',\n subagent_type: 'general-purpose',\n },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_agent_1',\n content: 'Agent finished',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n )\n\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'Need to inspect code with an agent.',\n )\n expect(requestBody).not.toHaveProperty('store')\n})\n\ntest('OpenGateway MiMo does not synthesize empty reasoning_content when missing', async () => {\n process.env.OPENAI_BASE_URL = 'https://opengateway.atuscode.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-opengateway-mimo',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [\n { role: 'user', content: 'Use an agent' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_agent_1',\n name: 'Agent',\n input: {\n description: 'Inspect code',\n prompt: 'Look at the relevant code',\n subagent_type: 'general-purpose',\n },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_agent_1',\n content: 'Agent finished',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n )\n\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall).not.toHaveProperty('reasoning_content')\n expect(requestBody).not.toHaveProperty('store')\n})\n\ntest('strips unsupported stream_options for Xiaomi MiMo streams', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.xiaomimimo.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n process.env.MIMO_API_KEY = 'mimo-test-key'\n delete process.env.OPENAI_API_KEY\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return makeSseResponse(\n makeStreamChunks([\n {\n id: 'chatcmpl-mimo',\n object: 'chat.completion.chunk',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n index: 0,\n delta: { role: 'assistant', content: 'done' },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-mimo',\n object: 'chat.completion.chunk',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ]),\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: true,\n })\n\n expect(requestBody).toMatchObject({\n stream: true,\n max_completion_tokens: 64,\n })\n expect(requestBody).not.toHaveProperty('stream_options')\n expect(requestBody).not.toHaveProperty('store')\n})\n\ntest('preserves Grep tool pattern field in OpenAI-compatible schemas', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-grep-schema',\n model: 'qwen/qwen3.6-plus',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'qwen/qwen3.6-plus',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Grep' }],\n tools: [\n {\n name: 'Grep',\n description: 'Search file contents',\n input_schema: {\n type: 'object',\n properties: {\n pattern: { type: 'string', description: 'Search pattern' },\n path: { type: 'string' },\n },\n required: ['pattern'],\n additionalProperties: false,\n },\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const tools = requestBody?.tools as Array> | undefined\n const grepTool = tools?.find(tool => (tool.function as Record)?.name === 'Grep') as\n | { function?: { parameters?: { properties?: Record; required?: string[] } } }\n | undefined\n\n expect(Object.keys(grepTool?.function?.parameters?.properties ?? {})).toContain('pattern')\n expect(grepTool?.function?.parameters?.required).toContain('pattern')\n})\n\ntest('does not infer Gemini mode from OPENAI_BASE_URL path substrings', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.OPENAI_BASE_URL =\n 'https://evil.example/generativelanguage.googleapis.com/v1beta/openai'\n delete process.env.OPENAI_API_KEY\n process.env.GEMINI_API_KEY = 'gemini-secret'\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'fake-model',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'fake-model',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedAuthorization).toBeNull()\n})\n\ntest('preserves image tool results as placeholders in follow-up requests', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen/qwen3.6-plus',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'qwen/qwen3.6-plus',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Read this screenshot' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_image_1',\n name: 'Read',\n input: { file_path: 'C:\\\\temp\\\\screenshot.png' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_image_1',\n content: [\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: 'ZmFrZQ==',\n },\n },\n ],\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const toolMessage = (requestBody?.messages as Array>).find(\n message => message.role === 'tool',\n ) as {\n content?: Array<{\n type: string\n text?: string\n image_url?: { url: string }\n }> | string\n } | undefined\n\n expect(Array.isArray(toolMessage?.content)).toBe(true)\n const parts = toolMessage?.content as Array<{\n type: string\n text?: string\n image_url?: { url: string }\n }>\n expect(parts).toEqual([\n {\n type: 'image_url',\n image_url: { url: 'data:image/png;base64,ZmFrZQ==' },\n },\n ])\n})\n\ntest('adds text part for image-only user messages', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n system: 'test system',\n messages: [\n {\n role: 'user',\n content: [\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: 'ZmFrZQ==',\n },\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const userMessage = (requestBody?.messages as Array>).find(\n message => message.role === 'user',\n ) as {\n content?: Array<{\n type: string\n text?: string\n image_url?: { url: string }\n }>\n } | undefined\n\n expect(userMessage?.content).toEqual([\n { type: 'text', text: 'Image attached.' },\n {\n type: 'image_url',\n image_url: { url: 'data:image/png;base64,ZmFrZQ==' },\n },\n ])\n})\n\ntest('preserves mixed text and image tool results as multipart content', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Read this screenshot' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_image_2',\n name: 'Read',\n input: { file_path: 'C:\\\\temp\\\\screenshot.png' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_image_2',\n content: [\n { type: 'text', text: 'Screenshot captured' },\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: 'ZmFrZQ==',\n },\n },\n ],\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const toolMessage = (requestBody?.messages as Array>).find(\n message => message.role === 'tool',\n ) as {\n content?: Array<{\n type: string\n text?: string\n image_url?: { url: string }\n }>\n } | undefined\n\n expect(Array.isArray(toolMessage?.content)).toBe(true)\n const parts = toolMessage?.content ?? []\n expect(parts[0]).toEqual({ type: 'text', text: 'Screenshot captured' })\n expect(parts[1]).toEqual({\n type: 'image_url',\n image_url: { url: 'data:image/png;base64,ZmFrZQ==' },\n })\n})\n\ntest('uses GEMINI_ACCESS_TOKEN for Gemini OpenAI-compatible requests', async () => {\n let capturedAuthorization: string | null = null\n let capturedProject: string | null = null\n let requestUrl: string | undefined\n\n process.env.CLAUDE_CODE_USE_GEMINI = '1'\n process.env.GEMINI_AUTH_MODE = 'access-token'\n process.env.GEMINI_ACCESS_TOKEN = 'gemini-access-token'\n process.env.GOOGLE_CLOUD_PROJECT = 'gemini-project'\n process.env.GEMINI_BASE_URL =\n 'https://generativelanguage.googleapis.com/v1beta/openai'\n process.env.GEMINI_MODEL = 'gemini-2.0-flash'\n delete process.env.OPENAI_BASE_URL\n delete process.env.OPENAI_API_KEY\n delete process.env.GEMINI_API_KEY\n delete process.env.GOOGLE_API_KEY\n\n globalThis.fetch = (async (input, init) => {\n requestUrl = typeof input === 'string' ? input : input.url\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n capturedProject =\n headers?.['x-goog-user-project'] ??\n headers?.['X-Goog-User-Project'] ??\n null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-gemini',\n model: 'gemini-2.0-flash',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 3,\n completion_tokens: 1,\n total_tokens: 4,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gemini-2.0-flash',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(requestUrl).toBe(\n 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions',\n )\n // Explicit type argument: TS narrows the closure-assigned variables to\n // their `null` initializer at this point (microsoft/TypeScript#9998).\n expect(capturedAuthorization).toBe('Bearer gemini-access-token')\n expect(capturedProject).toBe('gemini-project')\n})\n\ntest('uses NVIDIA_API_KEY for NVIDIA NIM requests without OPENAI_API_KEY', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.NVIDIA_NIM = '1'\n process.env.OPENAI_BASE_URL = 'https://integrate.api.nvidia.com/v1'\n process.env.OPENAI_MODEL = 'nvidia/llama-3.1-nemotron-70b-instruct'\n process.env.NVIDIA_API_KEY = 'nvidia-live-key'\n delete process.env.OPENAI_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-nvidia',\n model: 'nvidia/llama-3.1-nemotron-70b-instruct',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 3,\n completion_tokens: 1,\n total_tokens: 4,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'nvidia/llama-3.1-nemotron-70b-instruct',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedAuthorization).toBe('Bearer nvidia-live-key')\n})\n\ntest('does not use stale NVIDIA_API_KEY for non-NVIDIA OpenAI-compatible routes', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.NVIDIA_NIM = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENAI_MODEL = 'openai/gpt-5-mini'\n process.env.NVIDIA_API_KEY = 'nvidia-live-key'\n delete process.env.OPENAI_API_KEY\n delete process.env.OPENROUTER_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-openrouter',\n model: 'openai/gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'openai/gpt-5-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedAuthorization).toBeNull()\n})\n\ntest('does not use MINIMAX_API_KEY for non-MiniMax OpenAI-compatible routes', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENAI_MODEL = 'openai/gpt-5-mini'\n process.env.MINIMAX_API_KEY = 'minimax-live-key'\n delete process.env.OPENAI_API_KEY\n delete process.env.OPENROUTER_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-openrouter',\n model: 'openai/gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'openai/gpt-5-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedAuthorization).toBeNull()\n})\n\ntest('xiaomi mimo route uses api-key auth header and max_completion_tokens', async () => {\n let capturedHeaders: Record | undefined\n let capturedBody: Record | undefined\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://api.xiaomimimo.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n process.env.MIMO_API_KEY = 'mimo-live-key'\n delete process.env.OPENAI_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = init?.headers as Record\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-mimo',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedHeaders).toMatchObject({ 'api-key': 'mimo-live-key' })\n expect(capturedHeaders).not.toHaveProperty('Authorization')\n expect(capturedBody).toMatchObject({ max_completion_tokens: 32 })\n expect(capturedBody).not.toHaveProperty('max_tokens')\n})\n\ntest('atuscode opengateway provider flag sends OPENGATEWAY_API_KEY as bearer auth despite stale generic base URL', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_MODEL = 'gpt-5.5'\n process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'\n delete process.env.OPENAI_API_KEY\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('https://opengateway.atuscode.com/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('atuscode opengateway provider flag accepts OPENAI_API_KEY compatibility fallback', async () => {\n delete process.env.OPENAI_BASE_URL\n delete process.env.OPENGATEWAY_API_KEY\n process.env.OPENAI_API_KEY = 'fake-openai-fallback'\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.authorization).toBe('Bearer fake-openai-fallback')\n})\n\ntest('atuscode opengateway provider flag sends OPENAI_API_KEY fallback despite stale generic base URL', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'fake-openai-fallback'\n delete process.env.OPENGATEWAY_API_KEY\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('https://opengateway.atuscode.com/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-openai-fallback')\n})\n\ntest('atuscode opengateway provider flag trims OPENGATEWAY_API_KEY before bearer auth', async () => {\n process.env.OPENGATEWAY_API_KEY = ' fake-ogw-key '\n delete process.env.OPENAI_API_KEY\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('atuscode opengateway provider flag ignores blank OPENGATEWAY_API_KEY and uses OPENAI_API_KEY fallback', async () => {\n process.env.OPENGATEWAY_API_KEY = ' '\n process.env.OPENAI_API_KEY = 'fake-openai-fallback'\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.authorization).toBe('Bearer fake-openai-fallback')\n})\n\ntest('atuscode opengateway provider flag sends OPENGATEWAY_API_KEY to OPENGATEWAY_BASE_URL override', async () => {\n process.env.OPENGATEWAY_BASE_URL = 'http://localhost:8181/v1'\n process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'\n delete process.env.OPENAI_API_KEY\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('http://localhost:8181/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('atuscode opengateway provider flag sends OPENGATEWAY_API_KEY to custom OPENAI_BASE_URL fallback', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:8181/v1'\n process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'\n delete process.env.OPENGATEWAY_BASE_URL\n delete process.env.OPENAI_API_KEY\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('http://localhost:8181/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('atuscode opengateway provider flag prefers OPENGATEWAY_API_KEY over generic OPENAI_API_KEY for custom base URL', async () => {\n process.env.OPENGATEWAY_BASE_URL = 'http://localhost:8181/v1'\n process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'\n process.env.OPENAI_API_KEY = 'fake-generic-openai-key'\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('http://localhost:8181/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('atuscode opengateway stored provider profile key becomes bearer auth', async () => {\n delete process.env.OPENAI_API_KEY\n delete process.env.OPENGATEWAY_API_KEY\n\n applyProviderProfileToProcessEnv({\n id: 'stored-opengateway',\n provider: 'atuscode-opengateway',\n name: 'AtusCode Opengateway',\n baseUrl: 'https://opengateway.atuscode.com/v1',\n model: 'mimo-v2.5-pro',\n apiKey: 'fake-profile-key',\n })\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.authorization).toBe('Bearer fake-profile-key')\n})\n\ntest('openai route still sends OPENAI_API_KEY as bearer auth', async () => {\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_MODEL = 'gpt-5.5'\n process.env.OPENAI_API_KEY = 'fake-openai-key'\n delete process.env.OPENGATEWAY_API_KEY\n\n const captured = await captureChatCompletionRequest('gpt-5.5')\n\n expect(captured.authorization).toBe('Bearer fake-openai-key')\n})\n\ntest('does not use BNKR_API_KEY for non-Bankr OpenAI-compatible routes', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENAI_MODEL = 'openai/gpt-5-mini'\n process.env.BNKR_API_KEY = 'bankr-live-key'\n delete process.env.OPENAI_API_KEY\n delete process.env.OPENROUTER_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-openrouter',\n model: 'openai/gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'openai/gpt-5-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedAuthorization).toBeNull()\n})\n\ntest('preserves Gemini tool call extra_content from streaming chunks', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n extra_content: {\n google: {\n thought_signature: 'sig-stream',\n },\n },\n function: {\n name: 'Bash',\n arguments: '{\"command\":\"pwd\"}',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const toolStart = events.find(\n event =>\n event.type === 'content_block_start' &&\n typeof event.content_block === 'object' &&\n event.content_block !== null &&\n (event.content_block as Record).type === 'tool_use',\n ) as { content_block?: Record } | undefined\n\n expect(toolStart?.content_block).toMatchObject({\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n extra_content: {\n google: {\n thought_signature: 'sig-stream',\n },\n },\n })\n})\n\ntest('preserves Gemini thought signature from streaming delta extra_content', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n extra_content: {\n google: {\n thought_signature: 'sig-delta',\n },\n },\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Write',\n arguments: '{\"file_path\":\"todo.md\",\"content\":\"todo\"}',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [{ role: 'user', content: 'Use Write' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const toolStart = events.find(\n event =>\n event.type === 'content_block_start' &&\n typeof event.content_block === 'object' &&\n event.content_block !== null &&\n (event.content_block as Record).type === 'tool_use',\n ) as { content_block?: Record } | undefined\n\n expect(toolStart?.content_block).toMatchObject({\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Write',\n extra_content: {\n google: {\n thought_signature: 'sig-delta',\n },\n },\n signature: 'sig-delta',\n })\n})\n\ntest('preserves Gemini thought signature from non-streaming message extra_content', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n message: {\n role: 'assistant',\n extra_content: {\n google: {\n thought_signature: 'sig-message',\n },\n },\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Write',\n arguments: '{\"file_path\":\"todo.md\",\"content\":\"todo\"}',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [{ role: 'user', content: 'Use Write' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content?.[0]).toMatchObject({\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Write',\n extra_content: {\n google: {\n thought_signature: 'sig-message',\n },\n },\n signature: 'sig-message',\n })\n})\n\ntest('converts Gemini raw tool-call text into streaming tool_use blocks', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-raw-tool',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n content: 'Tool calls',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-raw-tool',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {\n content:\n ' requested:\\n- Write({\"file_path\":\"style.css\",\"content\":\"ul { padding: 0; }\"}) [id: call79435b5a26564619b0151197]',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-raw-tool',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [{ role: 'user', content: 'Write CSS' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n expect(\n events.some(\n event =>\n event.type === 'content_block_start' &&\n (event.content_block as Record | undefined)?.type ===\n 'text',\n ),\n ).toBe(false)\n\n const toolStart = events.find(\n event =>\n event.type === 'content_block_start' &&\n (event.content_block as Record | undefined)?.type ===\n 'tool_use',\n ) as { content_block?: Record } | undefined\n expect(toolStart?.content_block).toMatchObject({\n type: 'tool_use',\n id: 'call79435b5a26564619b0151197',\n name: 'Write',\n })\n\n const toolInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n (event.delta as Record | undefined)?.type ===\n 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n expect(JSON.parse(toolInput)).toEqual({\n file_path: 'style.css',\n content: 'ul { padding: 0; }',\n })\n\n const stop = events.find(event => event.type === 'message_delta') as\n | { delta?: Record }\n | undefined\n expect(stop?.delta?.stop_reason).toBe('tool_use')\n})\n\ntest('converts Gemini raw tool-call text into non-streaming tool_use blocks', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-raw-tool',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n message: {\n role: 'assistant',\n content:\n 'Tool calls requested:\\n- Agent({\"description\":\"Verify the todo list application functionality.\",\"prompt\":\"Check files.\",\"subagent_type\":\"verification\"}) [id: call9a8b7c6d5e4f3a2b1c0d9e8f]',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [{ role: 'user', content: 'Verify' }],\n max_tokens: 64,\n stream: false,\n }) as {\n stop_reason?: string\n content?: Array>\n }\n\n expect(message.stop_reason).toBe('tool_use')\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'call9a8b7c6d5e4f3a2b1c0d9e8f',\n name: 'Agent',\n input: {\n description: 'Verify the todo list application functionality.',\n prompt: 'Check files.',\n subagent_type: 'verification',\n },\n },\n ])\n})\n\ntest('normalizes plain string Bash tool arguments from OpenAI-compatible responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: false,\n }) as {\n stop_reason?: string\n content?: Array>\n }\n\n expect(message.stop_reason).toBe('tool_use')\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n input: { command: 'pwd' },\n },\n ])\n})\n\ntest('normalizes Bash tool arguments that are valid JSON strings', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '\"pwd\"',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n input: { command: 'pwd' },\n },\n ])\n})\n\ntest.each([\n ['false', false],\n ['null', null],\n ['[]', []],\n])(\n 'preserves malformed Bash JSON literals as parsed values in non-streaming responses: %s',\n async (argumentsValue, expectedInput) => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: argumentsValue,\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n input: expectedInput,\n },\n ])\n },\n)\n\ntest('keeps terminal empty Bash tool arguments invalid in non-streaming responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n input: {},\n },\n ])\n})\n\ntest('normalizes plain string Bash tool arguments in streaming responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"pwd\"}')\n})\n\ntest('normalizes plain string Bash tool arguments when streaming starts with an empty chunk', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: 0,\n type: 'function',\n function: {\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"pwd\"}')\n})\n\ntest('normalizes plain string Bash tool arguments when streaming starts with whitespace', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: ' ',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: 0,\n type: 'function',\n function: {\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\" pwd\"}')\n})\n\ntest('keeps terminal whitespace-only Bash arguments invalid in streaming responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: ' ',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{}')\n})\n\ntest('normalizes streaming Bash arguments that begin with bracket syntax', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '[ -f package.json ] && pwd',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"[ -f package.json ] && pwd\"}')\n})\n\ntest('normalizes streaming Bash arguments when the first chunk is only an opening brace', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '{',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: 0,\n type: 'function',\n function: {\n arguments: ' pwd; }',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"{ pwd; }\"}')\n})\n\ntest('repairs truncated structured Bash JSON in streaming responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '{\"command\":\"pwd\"',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"pwd\"}')\n})\n\ntest('does not normalize incomplete streamed Bash commands when finish_reason is length', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: 'rg --fi',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'length',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const streamedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(streamedInput).toBe('rg --fi')\n})\n\ntest('repairs truncated JSON objects even without command field', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '{\"cwd\":\"/tmp\"',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const streamedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(streamedInput).toBe('{\"cwd\":\"/tmp\"}')\n})\n\ntest('preserves raw input for unknown plain string tool arguments', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'UnknownTool',\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use tool' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'UnknownTool',\n input: {},\n },\n ])\n})\n\ntest('preserves parsed string input for unknown JSON string tool arguments', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'UnknownTool',\n arguments: '\"pwd\"',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use tool' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'UnknownTool',\n input: 'pwd',\n },\n ])\n})\n\ntest('sanitizes malformed MCP tool schemas before sending them to OpenAI', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 1,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n tools: [\n {\n name: 'mcp__clientry__create_task',\n description: 'Create a task',\n input_schema: {\n type: 'object',\n properties: {\n priority: {\n type: 'integer',\n description: 'Priority: 0=low, 1=medium, 2=high, 3=urgent',\n default: true,\n enum: [false, 0, 1, 2, 3],\n },\n },\n },\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const parameters = (\n requestBody?.tools as Array<{ function?: { parameters?: Record } }>\n )?.[0]?.function?.parameters\n const properties = parameters?.properties as\n | Record\n | undefined\n\n expect(parameters?.additionalProperties).toBe(false)\n // No required[] in the original schema → none added (optional properties must not be forced required)\n expect(parameters?.required).toEqual([])\n expect(properties?.priority?.type).toBe('integer')\n expect(properties?.priority?.enum).toEqual([0, 1, 2, 3])\n expect(properties?.priority).not.toHaveProperty('default')\n})\n\ntest('optional tool properties are not added to required[] — fixes Groq/Azure 400 tool_use_failed', async () => {\n // Regression test for: all optional properties being sent as required in strict mode,\n // causing providers like Groq to reject valid tool calls where the model omits optional args.\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-4',\n model: 'gpt-4o',\n choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }],\n usage: { prompt_tokens: 5, completion_tokens: 2, total_tokens: 7 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'read a file' }],\n tools: [\n {\n name: 'Read',\n description: 'Read a file',\n input_schema: {\n type: 'object',\n properties: {\n file_path: { type: 'string', description: 'Absolute path to file' },\n offset: { type: 'number', description: 'Line to start from' },\n limit: { type: 'number', description: 'Max lines to read' },\n pages: { type: 'string', description: 'Page range for PDFs' },\n },\n required: ['file_path'],\n },\n },\n ],\n max_tokens: 16,\n stream: false,\n })\n\n const parameters = (\n requestBody?.tools as Array<{ function?: { parameters?: Record } }>\n )?.[0]?.function?.parameters\n\n expect(parameters?.required).toEqual(['file_path'])\n\n const required = parameters?.required as string[] | undefined\n expect(required).not.toContain('offset')\n expect(required).not.toContain('limit')\n expect(required).not.toContain('pages')\n expect(parameters?.additionalProperties).toBe(false)\n})\n\n// ---------------------------------------------------------------------------\n// Issue #202 — consecutive role coalescing (Devstral, Mistral strict templates)\n// ---------------------------------------------------------------------------\n\nfunction makeNonStreamResponse(content = 'ok'): Response {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-test',\n model: 'test-model',\n choices: [{ message: { role: 'assistant', content }, finish_reason: 'stop' }],\n usage: { prompt_tokens: 5, completion_tokens: 1, total_tokens: 6 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n}\n\ntest('coalesces consecutive user messages to avoid alternation errors (issue #202)', async () => {\n let sentMessages: Array<{ role: string; content: unknown }> | undefined\n\n globalThis.fetch = (async (_input: unknown, init: RequestInit | undefined) => {\n sentMessages = JSON.parse(String(init?.body)).messages\n return makeNonStreamResponse()\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'test-model',\n system: 'sys',\n messages: [\n { role: 'user', content: 'first message' },\n { role: 'user', content: 'second message' },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n expect(sentMessages?.length).toBe(2)\n expect(sentMessages?.[0]?.role).toBe('system')\n expect(sentMessages?.[1]?.role).toBe('user')\n const userContent = sentMessages?.[1]?.content as string\n expect(userContent).toContain('first message')\n expect(userContent).toContain('second message')\n})\n\ntest('coalesces consecutive assistant messages preserving tool_calls (issue #202)', async () => {\n let sentMessages: Array<{ role: string; content: unknown; tool_calls?: unknown[] }> | undefined\n\n globalThis.fetch = (async (_input: unknown, init: RequestInit | undefined) => {\n sentMessages = JSON.parse(String(init?.body)).messages\n return makeNonStreamResponse()\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'test-model',\n system: 'sys',\n messages: [\n { role: 'user', content: 'go' },\n { role: 'assistant', content: 'thinking...' },\n {\n role: 'assistant',\n content: [{ type: 'tool_use', id: 'call_1', name: 'Bash', input: { command: 'ls' } }],\n },\n { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'call_1', content: 'file.txt' }] },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantMsgs = sentMessages?.filter(m => m.role === 'assistant')\n expect(assistantMsgs?.length).toBe(1)\n expect(assistantMsgs?.[0]?.tool_calls?.length).toBeGreaterThan(0)\n})\n\ntest('non-streaming: reasoning_content emitted as thinking block only when content is null', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'glm-5',\n choices: [\n {\n message: {\n role: 'assistant',\n content: null,\n reasoning_content: 'Let me think about this step by step.',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 20,\n total_tokens: 30,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = (await client.beta.messages.create({\n model: 'glm-5',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })) as { content: Array> }\n\n expect(result.content).toEqual([\n { type: 'thinking', thinking: 'Let me think about this step by step.' },\n ])\n})\n\ntest('non-streaming: empty string content does not fall through to reasoning_content as text', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'glm-5',\n choices: [\n {\n message: {\n role: 'assistant',\n content: '',\n reasoning_content: 'Chain of thought here.',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 20,\n total_tokens: 30,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = (await client.beta.messages.create({\n model: 'glm-5',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })) as { content: Array> }\n\n expect(result.content).toEqual([\n { type: 'thinking', thinking: 'Chain of thought here.' },\n ])\n})\n\ntest('non-streaming: real content takes precedence over reasoning_content', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'glm-5',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'The answer is 42.',\n reasoning_content: 'I need to calculate this.',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 20,\n total_tokens: 30,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = (await client.beta.messages.create({\n model: 'glm-5',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })) as { content: Array> }\n\n expect(result.content).toEqual([\n { type: 'thinking', thinking: 'I need to calculate this.' },\n { type: 'text', text: 'The answer is 42.' },\n ])\n})\n\ntest('non-streaming: strips tag block from assistant content', async () => {\n globalThis.fetch = asMockFetch(mock(async () => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content:\n 'user wants a greeting, respond brieflyHey! How can I help you today?',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 20,\n total_tokens: 30,\n },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n const result = (await client.beta.messages.create({\n model: 'gpt-5-mini',\n system: 'test system',\n messages: [{ role: 'user', content: 'hey' }],\n max_tokens: 64,\n stream: false,\n })) as { content: Array> }\n\n expect(result.content).toEqual([\n { type: 'text', text: 'Hey! How can I help you today?' },\n ])\n})\n\ntest('streaming: thinking block closed before tool call', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'glm-5',\n choices: [\n {\n index: 0,\n delta: { role: 'assistant', reasoning_content: 'Thinking...' },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'glm-5',\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: 0,\n id: 'call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '{\"command\":\"ls\"}',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'glm-5',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'glm-5',\n system: 'test system',\n messages: [{ role: 'user', content: 'Run ls' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const types = events.map(e => e.type)\n\n const thinkingStartIdx = types.indexOf('content_block_start')\n const firstStopIdx = types.indexOf('content_block_stop')\n const toolStartIdx = types.indexOf(\n 'content_block_start',\n thinkingStartIdx + 1,\n )\n\n expect(thinkingStartIdx).toBeGreaterThanOrEqual(0)\n expect(firstStopIdx).toBeGreaterThan(thinkingStartIdx)\n expect(toolStartIdx).toBeGreaterThan(firstStopIdx)\n\n const thinkingStart = events[thinkingStartIdx] as {\n content_block?: Record\n }\n expect(thinkingStart?.content_block?.type).toBe('thinking')\n})\n\ntest('streaming: strips tag block from assistant content deltas', async () => {\n globalThis.fetch = asMockFetch(mock(async () => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n content:\n 'user wants a greeting, respond brieflyHey! How can I help you today?',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n const result = await client.beta.messages\n .create({\n model: 'gpt-5-mini',\n system: 'test system',\n messages: [{ role: 'user', content: 'hey' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const textDeltas: string[] = []\n for await (const event of result.data) {\n const delta = (event as { delta?: { type?: string; text?: string } }).delta\n if (delta?.type === 'text_delta' && typeof delta.text === 'string') {\n textDeltas.push(delta.text)\n }\n }\n\n expect(textDeltas.join('')).toBe('Hey! How can I help you today?')\n})\n\ntest('streaming: strips tag split across multiple content chunks', async () => {\n globalThis.fetch = asMockFetch(mock(async () => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n content: 'user wants a greeting,',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {\n content: ' respond brieflyHey! How can I help you today?',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'gpt-5-mini',\n system: 'test system',\n messages: [{ role: 'user', content: 'hey' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const textDeltas: string[] = []\n for await (const event of result.data) {\n const delta = (event as { delta?: { type?: string; text?: string } }).delta\n if (delta?.type === 'text_delta' && typeof delta.text === 'string') {\n textDeltas.push(delta.text)\n }\n }\n\n expect(textDeltas.join('')).toBe('Hey! How can I help you today?')\n})\n\ntest('streaming: preserves prose without tags (no phrase-based false positive)', async () => {\n // Regression: older phrase-based sanitizer would strip \"I should...\" prose.\n // The tag-based approach leaves legitimate assistant output alone.\n globalThis.fetch = asMockFetch(mock(async () => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n content:\n 'I should note that the user role requires a briefly concise friendly response format.',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n const result = await client.beta.messages\n .create({\n model: 'gpt-5-mini',\n system: 'test system',\n messages: [{ role: 'user', content: 'hey' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const textDeltas: string[] = []\n for await (const event of result.data) {\n const delta = (event as { delta?: { type?: string; text?: string } }).delta\n if (delta?.type === 'text_delta' && typeof delta.text === 'string') {\n textDeltas.push(delta.text)\n }\n }\n\n expect(textDeltas.join('')).toBe(\n 'I should note that the user role requires a briefly concise friendly response format.',\n )\n})\n\ntest('strips credentials and query params from URL in fetch network error message', async () => {\n process.env.OPENAI_BASE_URL =\n 'https://user:password@internal.example.test/v1?token=abc123'\n process.env.OPENAI_API_KEY = 'test-key'\n\n globalThis.fetch = asMockFetch(mock(async () => {\n throw new TypeError(\n 'fetch failed https://user:password@internal.example.test/v1?token=abc123/chat/completions',\n )\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n let caught: unknown\n try {\n await client.beta.messages.create({\n model: 'test-model',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n } catch (error) {\n caught = error\n }\n\n const message = (caught as Error).message\n expect(message).toContain('internal.example.test')\n expect(message).toContain('fetch failed')\n expect(message).not.toContain('password')\n expect(message).not.toContain('user:')\n expect(message).not.toContain('token=abc123')\n})\n\ntest('classifies localhost transport failures with actionable category marker', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n const transportError = Object.assign(new TypeError('fetch failed'), {\n code: 'ECONNREFUSED',\n })\n\n globalThis.fetch = asMockFetch(mock(async () => {\n throw transportError\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).rejects.toThrow('openai_category=connection_refused')\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).rejects.toThrow('local server is running')\n})\n\ntest('transport failures are not labeled with HTTP status 503', async () => {\n // Issue #971: ENETDOWN (and other transport errors) are emitted before any\n // HTTP response is received. Reporting them as \"503\" makes users believe the\n // upstream server returned 503 Service Unavailable.\n process.env.OPENAI_BASE_URL = 'https://intranet.example.test/v1'\n\n const transportError = Object.assign(new TypeError('fetch failed'), {\n code: 'ENETDOWN',\n })\n\n globalThis.fetch = asMockFetch(mock(async () => {\n throw transportError\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n let caught: unknown\n try {\n await client.beta.messages.create({\n model: 'gpt-4o-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n } catch (error) {\n caught = error\n }\n\n expect(caught).toBeDefined()\n const err = caught as { status?: number; message: string; constructor: { name: string } }\n expect(err.constructor.name).toBe('APIConnectionError')\n expect(err.status).toBeUndefined()\n expect(err.message).not.toMatch(/^503\\b/)\n expect(err.message).toContain('OpenAI API transport error')\n expect(err.message).toContain('code=ENETDOWN')\n expect(err.message).toContain('openai_category=network_error')\n})\n\ntest('propagates AbortError without wrapping it as transport failure', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n const abortError = new DOMException('The operation was aborted.', 'AbortError')\n globalThis.fetch = asMockFetch(mock(async () => {\n throw abortError\n }))\n\n const controller = new AbortController()\n controller.abort()\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create(\n {\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n },\n { signal: controller.signal },\n ),\n ).rejects.toBe(abortError)\n})\n\ntest('classifies chat-completions endpoint 404 failures with endpoint_not_found marker', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434'\n\n globalThis.fetch = asMockFetch(mock(async () =>\n new Response('Not Found', {\n status: 404,\n headers: {\n 'Content-Type': 'text/plain',\n },\n })))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).rejects.toThrow('openai_category=endpoint_not_found')\n})\ntest('self-heals localhost resolution failures by retrying local loopback base URL', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n const requestUrls: string[] = []\n globalThis.fetch = (async (input, _init) => {\n const url = typeof input === 'string' ? input : input.url\n requestUrls.push(url)\n\n if (url.includes('localhost')) {\n const error = Object.assign(new TypeError('fetch failed'), {\n code: 'ENOTFOUND',\n })\n throw error\n }\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen2.5-coder:7b',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'hello from loopback',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 4,\n completion_tokens: 3,\n total_tokens: 7,\n },\n }),\n {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).resolves.toBeDefined()\n\n expect(requestUrls[0]).toBe('http://localhost:11434/v1/chat/completions')\n expect(requestUrls).toContain('http://127.0.0.1:11434/v1/chat/completions')\n})\n\ntest('self-heals local endpoint_not_found by retrying with /v1 base URL', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434'\n\n const requestUrls: string[] = []\n globalThis.fetch = (async (input, _init) => {\n const url = typeof input === 'string' ? input : input.url\n requestUrls.push(url)\n\n if (url === 'http://localhost:11434/chat/completions') {\n return new Response('Not Found', {\n status: 404,\n headers: {\n 'Content-Type': 'text/plain',\n },\n })\n }\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen2.5-coder:7b',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'hello from /v1',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 5,\n completion_tokens: 2,\n total_tokens: 7,\n },\n }),\n {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).resolves.toBeDefined()\n\n expect(requestUrls).toEqual([\n 'http://localhost:11434/chat/completions',\n 'http://localhost:11434/v1/chat/completions',\n ])\n})\n\ntest('self-heals tool-call incompatibility by retrying local Ollama requests without tools', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n const requestBodies: Array> = []\n globalThis.fetch = (async (_input, init) => {\n const requestBody = JSON.parse(String(init?.body)) as Record\n requestBodies.push(requestBody)\n\n if (requestBodies.length === 1) {\n return new Response('tool_calls are not supported', {\n status: 400,\n headers: {\n 'Content-Type': 'text/plain',\n },\n })\n }\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen2.5-coder:7b',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'fallback without tools',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 4,\n total_tokens: 12,\n },\n }),\n {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n tools: [\n {\n name: 'Read',\n description: 'Read a file',\n input_schema: {\n type: 'object',\n properties: {\n filePath: { type: 'string' },\n },\n required: ['filePath'],\n },\n },\n ],\n max_tokens: 64,\n stream: false,\n }),\n ).resolves.toBeDefined()\n\n expect(requestBodies).toHaveLength(2)\n expect(Array.isArray(requestBodies[0]?.tools)).toBe(true)\n expect(requestBodies[0]?.tool_choice).toBeUndefined()\n expect(\n requestBodies[1]?.tools === undefined ||\n (Array.isArray(requestBodies[1]?.tools) && requestBodies[1]?.tools.length === 0),\n ).toBe(true)\n expect(requestBodies[1]?.tool_choice).toBeUndefined()\n})\n\ntest('preserves valid tool_result and drops orphan tool_result', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'mistral-large-latest',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mistral-large-latest',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Search and then I will interrupt' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'valid_call_1',\n name: 'Search',\n input: { query: 'atuscode' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'valid_call_1',\n content: 'Found it!',\n },\n {\n type: 'tool_result',\n tool_use_id: 'orphan_call_2',\n content: 'Interrupted result',\n },\n {\n role: 'user',\n content: 'What happened?',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n\n // Should have: system, user, assistant (tool_use), tool (valid_call_1), user\n // Should NOT have: tool (orphan_call_2)\n\n const toolMessages = messages.filter(m => m.role === 'tool')\n expect(toolMessages.length).toBe(1)\n expect(toolMessages[0].tool_call_id).toBe('valid_call_1')\n\n const orphanMessage = toolMessages.find(m => m.tool_call_id === 'orphan_call_2')\n expect(orphanMessage).toBeUndefined()\n \n // Actually, the semantic message IS injected here because the user block with orphan \n // tool result is converted to:\n // 1. Tool result (valid_call_1) -> role 'tool'\n // 2. User content (\"What happened?\") -> role 'user'\n // This triggers the tool -> assistant injection.\n const assistantMessages = messages.filter(m => m.role === 'assistant')\n expect(assistantMessages.some(m => m.content === '[Tool results received]')).toBe(true)\n})\n\ntest('drops empty assistant message when only thinking block was present and stripped', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(JSON.stringify({\n id: 'chatcmpl-1',\n object: 'chat.completion',\n created: 123456789,\n model: 'mistral-large-latest',\n choices: [{ message: { role: 'assistant', content: 'hi' }, finish_reason: 'stop' }],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }\n }), { headers: { 'Content-Type': 'application/json' } })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mistral-large-latest',\n messages: [\n { role: 'user', content: 'Initial' },\n { role: 'assistant', content: [{ type: 'thinking', thinking: 'I am thinking...', signature: 'sig' }] },\n { role: 'user', content: 'Interrupting query' },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n // The assistant msg is dropped because thinking is stripped.\n // The two user messages are coalesced.\n expect(messages.length).toBe(1)\n expect(messages[0].role).toBe('user')\n expect(String(messages[0].content)).toContain('Initial')\n expect(String(messages[0].content)).toContain('Interrupting query')\n})\n\ntest('injects semantic assistant message when tool result is followed by user message', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(JSON.stringify({\n id: 'chatcmpl-2',\n object: 'chat.completion',\n created: 123456789,\n model: 'mistral-large-latest',\n choices: [{ message: { role: 'assistant', content: 'hi' }, finish_reason: 'stop' }],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }\n }), { headers: { 'Content-Type': 'application/json' } })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mistral-large-latest',\n messages: [\n { \n role: 'assistant', \n content: [{ type: 'tool_use', id: 'call_1', name: 'search', input: {} }] \n },\n { \n role: 'user', \n content: [\n { type: 'tool_result', tool_use_id: 'call_1', content: 'Result' }\n ] \n },\n { role: 'user', content: 'Next user query' },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n // Roles should be: assistant (tool_calls) -> tool -> assistant (semantic) -> user\n const roles = messages.map(m => m.role)\n expect(roles).toEqual(['assistant', 'tool', 'assistant', 'user'])\n \n const semanticMsg = messages[2]\n expect(semanticMsg.role).toBe('assistant')\n expect(semanticMsg.content).toBe('[Tool results received]')\n expect(semanticMsg.content).not.toContain('interrupted')\n expect(semanticMsg.content).not.toContain('user')\n})\n\ntest('Moonshot: uses max_tokens (not max_completion_tokens) and strips store', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.moonshot.ai/v1'\n process.env.OPENAI_API_KEY = 'sk-moonshot-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-k2.6',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-k2.6',\n system: 'you are kimi',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.max_tokens).toBe(256)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Cerebras: strips unsupported store on chat_completions (#1023)', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.cerebras.ai/v1'\n process.env.OPENAI_API_KEY = 'csk-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'llama3.1-8b',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'llama3.1-8b',\n system: 'you are cerebras',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Local provider (vLLM/Ollama/etc.): strips unsupported store on chat_completions (#672)', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:8000/v1'\n process.env.OPENAI_API_KEY = 'sk-local'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen-3.5-27b',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'qwen-3.5-27b',\n system: 'you are local',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Groq: keeps max_completion_tokens and strips unsupported store', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.groq.com/openai/v1'\n process.env.OPENAI_API_KEY = 'gsk-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'llama-3.3-70b-versatile',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'llama-3.3-70b-versatile',\n system: 'you are groq',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.max_completion_tokens).toBe(256)\n expect(requestBody?.max_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Moonshot: echoes reasoning_content on assistant tool-call messages', async () => {\n // Regression for: \"API Error: 400 {\"error\":{\"message\":\"thinking is enabled\n // but reasoning_content is missing in assistant tool call message at index\n // N\"}}\" when the agent sends a prior-turn assistant response back to Kimi.\n // The thinking block captured from the inbound response must round-trip\n // as reasoning_content on the outgoing echoed assistant message.\n process.env.OPENAI_BASE_URL = 'https://api.moonshot.ai/v1'\n process.env.OPENAI_API_KEY = 'sk-moonshot-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-k2.6',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-k2.6',\n system: 'you are kimi',\n messages: [\n { role: 'user', content: 'check the logs' },\n {\n role: 'assistant',\n content: [\n {\n type: 'thinking',\n thinking: 'Need to inspect logs via Bash; running a cat.',\n },\n { type: 'text', text: \"I'll inspect the logs.\" },\n {\n type: 'tool_use',\n id: 'call_bash_1',\n name: 'Bash',\n input: { command: 'cat /tmp/app.log' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_bash_1',\n content: 'log line 1\\nlog line 2',\n },\n ],\n },\n ],\n max_tokens: 256,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'Need to inspect logs via Bash; running a cat.',\n )\n})\n\ntest('DeepSeek echoes reasoning_content on assistant tool-call messages', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-v4-flash',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-v4-flash',\n system: 'test',\n messages: [\n { role: 'user', content: 'hi' },\n {\n role: 'assistant',\n content: [\n { type: 'thinking', thinking: 'thought' },\n { type: 'text', text: 'hello' },\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_1', content: 'files' },\n ],\n },\n ],\n max_tokens: 32,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe('thought')\n})\n\ntest('generic OpenAI-compatible providers do not echo reasoning_content on assistant tool-call messages', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'sk-openai-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test',\n messages: [\n { role: 'user', content: 'hi' },\n {\n role: 'assistant',\n content: [\n { type: 'thinking', thinking: 'thought' },\n { type: 'text', text: 'hello' },\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_1', content: 'files' },\n ],\n },\n ],\n max_tokens: 32,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBeUndefined()\n})\n\ntest('gateway-routed DeepSeek models inherit descriptor-backed reasoning and token shaping', async () => {\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENAI_API_KEY = 'sk-openrouter-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek/deepseek-reasoner',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n reasoningEffort: 'xhigh',\n }) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek/deepseek-reasoner',\n system: 'test',\n messages: [\n { role: 'user', content: 'hi' },\n {\n role: 'assistant',\n content: [\n { type: 'thinking', thinking: 'thought' },\n { type: 'text', text: 'hello' },\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_1', content: 'files' },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n thinking: { type: 'enabled' },\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n message => message.role === 'assistant' && Array.isArray(message.tool_calls),\n )\n\n expect(assistantWithToolCall?.reasoning_content).toBe('thought')\n expect(requestBody?.thinking).toEqual({ type: 'enabled' })\n expect(requestBody?.reasoning_effort).toBe('max')\n expect(requestBody?.max_tokens).toBe(64)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Moonshot: cn host is also detected', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.moonshot.cn/v1'\n process.env.OPENAI_API_KEY = 'sk-moonshot-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-k2.6',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-k2.6',\n system: 'you are kimi',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Kimi Code endpoint inherits Moonshot max_tokens/store compatibility', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.kimi.com/coding/v1'\n process.env.OPENAI_API_KEY = 'sk-kimi-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-for-coding',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-for-coding',\n system: 'you are kimi code',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.max_tokens).toBe(256)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Kimi Code endpoint echoes reasoning_content on assistant tool-call messages', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.kimi.com/coding/v1'\n process.env.OPENAI_API_KEY = 'sk-kimi-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-for-coding',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-for-coding',\n system: 'you are kimi code',\n messages: [\n { role: 'user', content: 'check the logs' },\n {\n role: 'assistant',\n content: [\n {\n type: 'thinking',\n thinking: 'Need to inspect logs via Bash; running a cat.',\n },\n { type: 'text', text: \"I'll inspect the logs.\" },\n {\n type: 'tool_use',\n id: 'call_bash_1',\n name: 'Bash',\n input: { command: 'cat /tmp/app.log' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_bash_1',\n content: 'log line 1\\nlog line 2',\n },\n ],\n },\n ],\n max_tokens: 256,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'Need to inspect logs via Bash; running a cat.',\n )\n})\n\ntest('DeepSeek sends thinking toggle and normalized reasoning effort', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-v4-pro',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n reasoningEffort: 'xhigh',\n }) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-v4-pro',\n system: 'test',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 64,\n stream: false,\n thinking: { type: 'enabled' },\n })\n\n expect(requestBody?.thinking).toEqual({ type: 'enabled' })\n expect(requestBody?.reasoning_effort).toBe('max')\n expect(requestBody?.max_tokens).toBe(64)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('DeepSeek omits thinking controls when the Anthropic-side request does not set them', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-v4-flash',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-v4-flash',\n system: 'test',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(requestBody?.thinking).toBeUndefined()\n expect(requestBody?.reasoning_effort).toBeUndefined()\n})\n\ntest('DeepSeek forwards an explicit thinking disable toggle for V4 models', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-v4-flash',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-v4-flash',\n system: 'test',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 32,\n stream: false,\n thinking: { type: 'disabled' },\n })\n\n expect(requestBody?.thinking).toEqual({ type: 'disabled' })\n expect(requestBody?.reasoning_effort).toBeUndefined()\n})\n\n\ntest('collapses multiple text blocks in tool_result to string for DeepSeek compatibility (issue #774)', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-reasoner',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'deepseek-reasoner',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Run ls' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_1',\n content: [\n { type: 'text', text: 'line one' },\n { type: 'text', text: 'line two' },\n ],\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const toolMessages = messages.filter(m => m.role === 'tool')\n expect(toolMessages.length).toBe(1)\n expect(toolMessages[0].tool_call_id).toBe('call_1')\n expect(typeof toolMessages[0].content).toBe('string')\n expect(toolMessages[0].content).toBe('line one\\n\\nline two')\n})\n\ntest('collapses multiple text blocks into a single string for DeepSeek compatibility (issue #774)', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-reasoner',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'deepseek-reasoner',\n system: 'test system',\n messages: [\n {\n role: 'user',\n content: [\n { type: 'text', text: 'Hello!' },\n { type: 'text', text: 'How are you?' },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n expect(messages.length).toBe(2) // system + user\n expect(messages[1].role).toBe('user')\n expect(typeof messages[1].content).toBe('string')\n expect(messages[1].content).toBe('Hello!\\n\\nHow are you?')\n})\n\ntest('preserves mixed text and image tool results as multipart content', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Show me' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'cat image.png' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_1',\n content: [\n { type: 'text', text: 'Here is the image:' },\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: 'iVBORw0KGgo=',\n },\n },\n ],\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const toolMessages = messages.filter(m => m.role === 'tool')\n expect(toolMessages.length).toBe(1)\n expect(Array.isArray(toolMessages[0].content)).toBe(true)\n const content = toolMessages[0].content as Array>\n expect(content.length).toBe(2)\n expect(content[0].type).toBe('text')\n expect(content[1].type).toBe('image_url')\n})\n\ntest('Z.AI: uses max_tokens (not max_completion_tokens) and strips store', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.z.ai/api/coding/paas/v4'\n process.env.OPENAI_API_KEY = 'sk-zai-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'GLM-5.1',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'GLM-5.1',\n system: 'you are glm',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.max_tokens).toBe(256)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Z.AI: thinking mode enabled when requested', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.z.ai/api/coding/paas/v4'\n process.env.OPENAI_API_KEY = 'sk-zai-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'GLM-5.1',\n choices: [\n {\n message: {\n role: 'assistant',\n content: null,\n reasoning_content: 'Let me think...',\n },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'GLM-5.1',\n system: 'you are glm',\n messages: [{ role: 'user', content: 'think hard' }],\n max_tokens: 1024,\n stream: false,\n thinking: { type: 'enabled', budget_tokens: 1024 },\n })\n\n expect((requestBody?.thinking as Record)?.type).toBe('enabled')\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.max_tokens).toBe(1024)\n})\n\ntest('strips Anthropic attribution header block from chat-completions system prompt (#607)', async () => {\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: { role: 'assistant', content: 'ok' },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 8, completion_tokens: 3, total_tokens: 11 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: [\n {\n type: 'text',\n text:\n 'x-anthropic-billing-header: cc_version=0.8.0.abc123; ' +\n 'cc_entrypoint=cli;',\n },\n { type: 'text', text: 'You are Claude Code, helpful assistant.' },\n { type: 'text', text: 'Project context: bun + react.' },\n ],\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = capturedBody?.messages as Array<{ role: string; content: string }>\n const sysMsg = messages.find(m => m.role === 'system')\n expect(sysMsg).toBeDefined()\n expect(sysMsg?.content).not.toContain('x-anthropic-billing-header')\n expect(sysMsg?.content).not.toContain('cc_version=')\n expect(sysMsg?.content).toContain('You are Claude Code, helpful assistant.')\n expect(sysMsg?.content).toContain('Project context: bun + react.')\n})\n\ntest('strips Anthropic attribution header block from responses-API instructions (#607)', async () => {\n process.env.OPENAI_API_FORMAT = 'responses'\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'resp-1',\n model: 'gpt-5.4',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n usage: { input_tokens: 8, output_tokens: 3, total_tokens: 11 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n system: [\n {\n type: 'text',\n text: 'x-anthropic-billing-header: cc_version=0.8.0.abc123; cc_entrypoint=cli;',\n },\n { type: 'text', text: 'You are Claude Code.' },\n ],\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n const instructions = capturedBody?.instructions as string\n expect(instructions).not.toContain('x-anthropic-billing-header')\n expect(instructions).not.toContain('cc_version=')\n expect(instructions).toContain('You are Claude Code.')\n})\n\ntest('emits reasoning_effort on chat_completions when reasoningEffort is passed', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'test-key'\n\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-5.4',\n choices: [\n {\n message: { role: 'assistant', content: 'ok' },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n reasoningEffort: 'xhigh',\n }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 16,\n stream: false,\n })\n\n expect(requestBody?.reasoning_effort).toBe('xhigh')\n})\n\ntest('omits reasoning_effort on chat_completions when no override and model has no alias default', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'test-key'\n\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: { role: 'assistant', content: 'ok' },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 16,\n stream: false,\n })\n\n expect(requestBody && 'reasoning_effort' in requestBody).toBe(false)\n})\n\ntest('emits reasoning_effort from codex alias default when no override is passed', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'test-key'\n\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-5.4',\n choices: [\n {\n message: { role: 'assistant', content: 'ok' },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 16,\n stream: false,\n })\n\n expect(requestBody?.reasoning_effort).toBe('high')\n})\n\ntest('DeepSeek: redacted_thinking block preserves continuity with reasoning_content: \"\"', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-chat',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-chat',\n system: 'test',\n messages: [\n { role: 'user', content: 'analyze this' },\n {\n role: 'assistant',\n content: [\n // real redacted_thinking shape: content lives in `.data`, not `.thinking`\n { type: 'redacted_thinking', data: '', signature: 'sig123' },\n { type: 'text', text: 'Analysis complete.' },\n {\n type: 'tool_use',\n id: 'call_redacted_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_redacted_1', content: 'files' },\n ],\n },\n ],\n max_tokens: 32,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n // redacted_thinking is recognized as a thinking block; its .data is \"\" and the\n // message carries a tool_call, so it falls back to reasoning_content: \"\"\n expect(assistantWithToolCall?.reasoning_content).toBe('')\n})\n\ntest('DeepSeek: redacted_thinking block with non-empty data propagates data into reasoning_content', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-2',\n model: 'deepseek-chat',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-chat',\n system: 'test',\n messages: [\n { role: 'user', content: 'analyze this' },\n {\n role: 'assistant',\n content: [\n // real redacted_thinking with content in .data\n {\n type: 'redacted_thinking',\n data: 'encrypted_chain_of_thought_payload_v1',\n signature: 'sig456',\n },\n { type: 'text', text: 'Analysis complete.' },\n {\n type: 'tool_use',\n id: 'call_redacted_2',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_redacted_2', content: 'files' },\n ],\n },\n ],\n max_tokens: 32,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n // The real .data payload must be preserved in reasoning_content — this is the\n // case the original test missed (it used a synthetic .thinking field).\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'encrypted_chain_of_thought_payload_v1',\n )\n})\n\ntest('renders tool_reference blocks as text on the chat/completions path', async () => {\n const { __test } = await import('./openaiShim.ts')\n\n const messages = __test.convertMessages(\n [\n {\n role: 'assistant',\n content: [\n { type: 'tool_use', id: 'call_ts1', name: 'ToolSearch', input: { query: 'memory' } },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_ts1',\n content: [\n { type: 'tool_reference', tool_name: 'mcp__example__memory_search' },\n { type: 'tool_reference', tool_name: 'mcp__example__memory_store' },\n ],\n },\n ],\n },\n ],\n undefined,\n )\n\n const toolMsg = messages.find(m => m.role === 'tool')\n expect(toolMsg).toBeDefined()\n // The rendering contract is plain text: text-only parts collapse to a string.\n expect(typeof toolMsg!.content).toBe('string')\n const content = toolMsg!.content as string\n expect(content).toContain('mcp__example__memory_search')\n expect(content).toContain('mcp__example__memory_store')\n})\n"}, "b45fea8785f876b796dcd8124fb98f1c7b9c6318": {"t": 1785765443.1823938, "n": 189362, "h": "f722d017d16b4847", "c": "import { afterEach, beforeEach, expect, mock, test } from 'bun:test'\nimport { acquireSharedMutationLock, releaseSharedMutationLock } from '../../test/sharedMutationLock.js'\nimport { asMockFetch } from '../../test/typedMocks.js'\nimport { _clearRegistryForTesting, ensureIntegrationsLoaded, registerGateway } from '../../integrations/index.ts'\nimport { applyProviderFlag } from '../../utils/providerFlag.ts'\nimport { applyProviderProfileToProcessEnv } from '../../utils/providerProfiles.ts'\nimport { createOpenAIShimClient } from './openaiShim.ts'\n\ntype FetchType = typeof globalThis.fetch\n\nconst originalEnv = {\n OPENAI_BASE_URL: process.env.OPENAI_BASE_URL,\n OPENAI_API_BASE: process.env.OPENAI_API_BASE,\n OPENAI_API_KEY: process.env.OPENAI_API_KEY,\n OPENAI_MODEL: process.env.OPENAI_MODEL,\n OPENAI_API_FORMAT: process.env.OPENAI_API_FORMAT,\n OPENAI_AUTH_HEADER: process.env.OPENAI_AUTH_HEADER,\n OPENAI_AUTH_SCHEME: process.env.OPENAI_AUTH_SCHEME,\n OPENAI_AUTH_HEADER_VALUE: process.env.OPENAI_AUTH_HEADER_VALUE,\n CLAUDE_CODE_USE_GITHUB: process.env.CLAUDE_CODE_USE_GITHUB,\n GITHUB_TOKEN: process.env.GITHUB_TOKEN,\n GH_TOKEN: process.env.GH_TOKEN,\n CLAUDE_CODE_USE_OPENAI: process.env.CLAUDE_CODE_USE_OPENAI,\n CLAUDE_CODE_USE_GEMINI: process.env.CLAUDE_CODE_USE_GEMINI,\n GEMINI_API_KEY: process.env.GEMINI_API_KEY,\n GOOGLE_API_KEY: process.env.GOOGLE_API_KEY,\n GEMINI_ACCESS_TOKEN: process.env.GEMINI_ACCESS_TOKEN,\n GEMINI_AUTH_MODE: process.env.GEMINI_AUTH_MODE,\n GEMINI_BASE_URL: process.env.GEMINI_BASE_URL,\n GEMINI_MODEL: process.env.GEMINI_MODEL,\n GOOGLE_CLOUD_PROJECT: process.env.GOOGLE_CLOUD_PROJECT,\n ANTHROPIC_CUSTOM_HEADERS: process.env.ANTHROPIC_CUSTOM_HEADERS,\n NVIDIA_API_KEY: process.env.NVIDIA_API_KEY,\n NVIDIA_NIM: process.env.NVIDIA_NIM,\n MINIMAX_API_KEY: process.env.MINIMAX_API_KEY,\n BNKR_API_KEY: process.env.BNKR_API_KEY,\n BANKR_BASE_URL: process.env.BANKR_BASE_URL,\n BANKR_MODEL: process.env.BANKR_MODEL,\n OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY,\n DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,\n MIMO_API_KEY: process.env.MIMO_API_KEY,\n OPENGATEWAY_API_KEY: process.env.OPENGATEWAY_API_KEY,\n OPENGATEWAY_BASE_URL: process.env.OPENGATEWAY_BASE_URL,\n CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED: process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED,\n CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID: process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID,\n}\n\nconst originalFetch = globalThis.fetch\n\nfunction restoreEnv(key: string, value: string | undefined): void {\n if (value === undefined) {\n delete process.env[key]\n } else {\n process.env[key] = value\n }\n}\n\ntype OpenAIShimClient = {\n beta: {\n messages: {\n create: (\n params: Record,\n options?: Record,\n ) => Promise & {\n withResponse: () => Promise<{ data: AsyncIterable> }>\n }\n }\n }\n}\n\nfunction makeSseResponse(lines: string[]): Response {\n const encoder = new TextEncoder()\n return new Response(\n new ReadableStream({\n start(controller) {\n for (const line of lines) {\n controller.enqueue(encoder.encode(line))\n }\n controller.close()\n },\n }),\n {\n headers: {\n 'Content-Type': 'text/event-stream',\n },\n },\n )\n}\n\nfunction makeStreamChunks(chunks: unknown[]): string[] {\n return [\n ...chunks.map(chunk => `data: ${JSON.stringify(chunk)}\\n\\n`),\n 'data: [DONE]\\n\\n',\n ]\n}\n\nfunction makeChatCompletionResponse(model: string): Response {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-test',\n model,\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n}\n\nasync function captureChatCompletionRequest(\n model = 'mimo-v2.5-pro',\n): Promise<{ authorization: string | null; url: string | null }> {\n let authorization: string | null = null\n let url: string | null = null\n\n globalThis.fetch = (async (input, init) => {\n url = String(input)\n const headers = init?.headers as Record | undefined\n authorization = headers?.Authorization ?? headers?.authorization ?? null\n\n return makeChatCompletionResponse(model)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model,\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n return { authorization, url }\n}\n\nbeforeEach(async () => {\n await acquireSharedMutationLock('openaiShim.test.ts')\n process.env.OPENAI_BASE_URL = 'http://example.test/v1'\n delete process.env.OPENAI_API_BASE\n process.env.OPENAI_API_KEY = 'test-key'\n delete process.env.OPENAI_MODEL\n delete process.env.OPENAI_API_FORMAT\n delete process.env.OPENAI_AUTH_HEADER\n delete process.env.OPENAI_AUTH_SCHEME\n delete process.env.OPENAI_AUTH_HEADER_VALUE\n delete process.env.CLAUDE_CODE_USE_GITHUB\n delete process.env.GITHUB_TOKEN\n delete process.env.GH_TOKEN\n delete process.env.CLAUDE_CODE_USE_OPENAI\n delete process.env.CLAUDE_CODE_USE_GEMINI\n delete process.env.GEMINI_API_KEY\n delete process.env.GOOGLE_API_KEY\n delete process.env.GEMINI_ACCESS_TOKEN\n delete process.env.GEMINI_AUTH_MODE\n delete process.env.GEMINI_BASE_URL\n delete process.env.GEMINI_MODEL\n delete process.env.GOOGLE_CLOUD_PROJECT\n delete process.env.ANTHROPIC_CUSTOM_HEADERS\n delete process.env.NVIDIA_API_KEY\n delete process.env.NVIDIA_NIM\n delete process.env.MINIMAX_API_KEY\n delete process.env.BNKR_API_KEY\n delete process.env.BANKR_BASE_URL\n delete process.env.BANKR_MODEL\n delete process.env.OPENROUTER_API_KEY\n delete process.env.DEEPSEEK_API_KEY\n delete process.env.MIMO_API_KEY\n delete process.env.OPENGATEWAY_API_KEY\n delete process.env.OPENGATEWAY_BASE_URL\n delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED\n delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID\n})\n\nafterEach(() => {\n try {\n restoreEnv('OPENAI_BASE_URL', originalEnv.OPENAI_BASE_URL)\n restoreEnv('OPENAI_API_BASE', originalEnv.OPENAI_API_BASE)\n restoreEnv('OPENAI_API_KEY', originalEnv.OPENAI_API_KEY)\n restoreEnv('OPENAI_MODEL', originalEnv.OPENAI_MODEL)\n restoreEnv('OPENAI_API_FORMAT', originalEnv.OPENAI_API_FORMAT)\n restoreEnv('OPENAI_AUTH_HEADER', originalEnv.OPENAI_AUTH_HEADER)\n restoreEnv('OPENAI_AUTH_SCHEME', originalEnv.OPENAI_AUTH_SCHEME)\n restoreEnv('OPENAI_AUTH_HEADER_VALUE', originalEnv.OPENAI_AUTH_HEADER_VALUE)\n restoreEnv('CLAUDE_CODE_USE_GITHUB', originalEnv.CLAUDE_CODE_USE_GITHUB)\n restoreEnv('GITHUB_TOKEN', originalEnv.GITHUB_TOKEN)\n restoreEnv('GH_TOKEN', originalEnv.GH_TOKEN)\n restoreEnv('CLAUDE_CODE_USE_OPENAI', originalEnv.CLAUDE_CODE_USE_OPENAI)\n restoreEnv('CLAUDE_CODE_USE_GEMINI', originalEnv.CLAUDE_CODE_USE_GEMINI)\n restoreEnv('GEMINI_API_KEY', originalEnv.GEMINI_API_KEY)\n restoreEnv('GOOGLE_API_KEY', originalEnv.GOOGLE_API_KEY)\n restoreEnv('GEMINI_ACCESS_TOKEN', originalEnv.GEMINI_ACCESS_TOKEN)\n restoreEnv('GEMINI_AUTH_MODE', originalEnv.GEMINI_AUTH_MODE)\n restoreEnv('GEMINI_BASE_URL', originalEnv.GEMINI_BASE_URL)\n restoreEnv('GEMINI_MODEL', originalEnv.GEMINI_MODEL)\n restoreEnv('GOOGLE_CLOUD_PROJECT', originalEnv.GOOGLE_CLOUD_PROJECT)\n restoreEnv('ANTHROPIC_CUSTOM_HEADERS', originalEnv.ANTHROPIC_CUSTOM_HEADERS)\n restoreEnv('NVIDIA_API_KEY', originalEnv.NVIDIA_API_KEY)\n restoreEnv('NVIDIA_NIM', originalEnv.NVIDIA_NIM)\n restoreEnv('MINIMAX_API_KEY', originalEnv.MINIMAX_API_KEY)\n restoreEnv('BNKR_API_KEY', originalEnv.BNKR_API_KEY)\n restoreEnv('BANKR_BASE_URL', originalEnv.BANKR_BASE_URL)\n restoreEnv('BANKR_MODEL', originalEnv.BANKR_MODEL)\n restoreEnv('OPENROUTER_API_KEY', originalEnv.OPENROUTER_API_KEY)\n restoreEnv('DEEPSEEK_API_KEY', originalEnv.DEEPSEEK_API_KEY)\n restoreEnv('MIMO_API_KEY', originalEnv.MIMO_API_KEY)\n restoreEnv('OPENGATEWAY_API_KEY', originalEnv.OPENGATEWAY_API_KEY)\n restoreEnv('OPENGATEWAY_BASE_URL', originalEnv.OPENGATEWAY_BASE_URL)\n restoreEnv('CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED', originalEnv.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED)\n restoreEnv('CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID', originalEnv.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID)\n globalThis.fetch = originalFetch\n _clearRegistryForTesting()\n ensureIntegrationsLoaded()\n } finally {\n releaseSharedMutationLock()\n }\n})\n\ntest('strips canonical Anthropic headers from direct shim defaultHeaders', async () => {\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n defaultHeaders: {\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\n 'x-anthropic-additional-protection': 'true',\n 'x-claude-remote-session-id': 'remote-123',\n 'x-app': 'cli',\n 'x-client-app': 'sdk',\n 'x-safe-header': 'keep-me',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('anthropic-version')).toBeNull()\n expect(capturedHeaders?.get('anthropic-beta')).toBeNull()\n expect(capturedHeaders?.get('x-anthropic-additional-protection')).toBeNull()\n expect(capturedHeaders?.get('x-claude-remote-session-id')).toBeNull()\n expect(capturedHeaders?.get('x-app')).toBeNull()\n expect(capturedHeaders?.get('x-client-app')).toBeNull()\n expect(capturedHeaders?.get('x-safe-header')).toBe('keep-me')\n})\n\ntest('uses OpenAI-compatible responses endpoint when OPENAI_API_FORMAT=responses', async () => {\n process.env.OPENAI_API_FORMAT = 'responses'\n let capturedUrl = ''\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (input, init) => {\n capturedUrl = String(input)\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'resp-1',\n model: 'gpt-5.4',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n usage: {\n input_tokens: 8,\n output_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedUrl).toBe('http://example.test/v1/responses')\n expect(capturedBody?.model).toBe('gpt-5.4')\n expect(capturedBody?.instructions).toBe('test system')\n expect(capturedBody?.max_output_tokens).toBe(64)\n expect(capturedBody?.store).toBe(false)\n expect(capturedBody?.input).toEqual([\n {\n type: 'message',\n role: 'user',\n content: [{ type: 'input_text', text: 'hello' }],\n },\n ])\n})\n\ntest('uses OpenAI-compatible responses endpoint with text chunk types when OPENAI_API_FORMAT=responses_compat', async () => {\n process.env.OPENAI_API_FORMAT = 'responses_compat'\n let capturedUrl = ''\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (input, init) => {\n capturedUrl = String(input)\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'resp-1',\n model: 'gpt-5.4',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n usage: {\n input_tokens: 8,\n output_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedUrl).toBe('http://example.test/v1/responses')\n expect(capturedBody?.model).toBe('gpt-5.4')\n expect(capturedBody?.instructions).toBe('test system')\n expect(capturedBody?.max_output_tokens).toBe(64)\n expect(capturedBody?.store).toBe(false)\n expect(capturedBody?.input).toEqual([\n {\n type: 'message',\n role: 'user',\n content: [{ type: 'text', text: 'hello' }],\n },\n ])\n})\n\ntest('uses correct empty input fallback schema for standard responses and responses_compat', async () => {\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n return new Response(JSON.stringify({\n id: 'resp-1',\n model: 'test',\n output: [{ type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'ok' }] }]\n }), { headers: { 'Content-Type': 'application/json' } })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n process.env.OPENAI_API_FORMAT = 'responses'\n await client.beta.messages.create({\n model: 'test',\n max_tokens: 10,\n messages: [{ role: 'user', content: [] }],\n })\n\n expect(capturedBody?.input).toEqual([\n {\n type: 'message',\n role: 'user',\n content: [{ type: 'input_text', text: '' }],\n },\n ])\n\n process.env.OPENAI_API_FORMAT = 'responses_compat'\n await client.beta.messages.create({\n model: 'test',\n max_tokens: 10,\n messages: [{ role: 'user', content: [] }],\n })\n\n expect(capturedBody?.input).toEqual([\n {\n type: 'message',\n role: 'user',\n content: [{ type: 'text', text: '' }],\n },\n ])\n})\n\ntest('strips store from strict OpenAI-compatible responses providers', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.moonshot.ai/v1'\n process.env.OPENAI_API_FORMAT = 'responses'\n let capturedUrl = ''\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (input, init) => {\n capturedUrl = String(input)\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'resp-1',\n model: 'kimi-k2.5',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'kimi-k2.5',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedUrl).toBe('https://api.moonshot.ai/v1/responses')\n expect(capturedBody?.store).toBeUndefined()\n})\n\ntest('strips store when providerOverride routes chat_completions to the Gemini host', async () => {\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-gemini',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n defaultHeaders: {},\n providerOverride: {\n model: 'gemini-3.1-pro',\n baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',\n apiKey: 'gemini-key',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gemini-3.1-pro',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedBody?.store).toBeUndefined()\n})\n\ntest('strips store when providerOverride routes responses API to the Gemini host', async () => {\n process.env.OPENAI_API_FORMAT = 'responses'\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n return new Response(\n JSON.stringify({\n id: 'resp-gemini',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n defaultHeaders: {},\n providerOverride: {\n model: 'gemini-3.1-pro',\n baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',\n apiKey: 'gemini-key',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gemini-3.1-pro',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedBody?.store).toBeUndefined()\n})\n\ntest('uses custom OpenAI-compatible auth header value when configured', async () => {\n process.env.OPENAI_API_KEY = 'generic-key'\n process.env.OPENAI_AUTH_HEADER = 'api-key'\n process.env.OPENAI_AUTH_HEADER_VALUE = 'hicap-header-value'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('api-key')).toBe('hicap-header-value')\n expect(capturedHeaders?.get('authorization')).toBeNull()\n})\n\ntest('uses Hicap api-key auth header for the Hicap route', async () => {\n process.env.OPENAI_API_KEY = 'hicap-live-key'\n process.env.OPENAI_BASE_URL = 'https://api.hicap.ai/v1'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'claude-opus-4.7',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('api-key')).toBe('hicap-live-key')\n expect(capturedHeaders?.get('authorization')).toBeNull()\n})\n\ntest('defaults Authorization custom auth header to bearer scheme', async () => {\n process.env.OPENAI_API_KEY = 'authorization-key'\n process.env.OPENAI_AUTH_HEADER = 'Authorization'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('authorization')).toBe('Bearer authorization-key')\n})\n\ntest('honors bearer scheme for custom OpenAI-compatible auth headers', async () => {\n process.env.OPENAI_API_KEY = 'custom-key'\n process.env.OPENAI_AUTH_HEADER = 'X-Custom-Authorization'\n process.env.OPENAI_AUTH_SCHEME = 'bearer'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('x-custom-authorization')).toBe('Bearer custom-key')\n expect(capturedHeaders?.get('authorization')).toBeNull()\n})\n\ntest('ignores custom auth header value when no custom header is configured', async () => {\n delete process.env.OPENAI_API_KEY\n process.env.OPENAI_AUTH_HEADER_VALUE = 'gateway-header-value'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('authorization')).toBeNull()\n})\n\ntest('strips canonical Anthropic headers from per-request shim headers too', async () => {\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'gpt-4o',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n },\n {\n headers: {\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\n 'x-safe-header': 'keep-me',\n },\n },\n )\n\n expect(capturedHeaders?.get('anthropic-version')).toBeNull()\n expect(capturedHeaders?.get('anthropic-beta')).toBeNull()\n expect(capturedHeaders?.get('x-safe-header')).toBe('keep-me')\n})\n\ntest('applies descriptor static headers before client and request headers', async () => {\n let capturedHeaders: Headers | undefined\n\n registerGateway({\n id: 'shim-header-test',\n label: 'Shim Header Test',\n category: 'hosted',\n defaultBaseUrl: 'https://shim-header-test.example/v1',\n defaultModel: 'shim-test-model',\n setup: {\n requiresAuth: true,\n authMode: 'api-key',\n credentialEnvVars: ['OPENAI_API_KEY'],\n },\n transportConfig: {\n kind: 'openai-compatible',\n openaiShim: {\n headers: {\n 'x-static-header': 'from-descriptor',\n 'x-override-header': 'from-descriptor',\n },\n },\n },\n })\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://shim-header-test.example/v1'\n process.env.OPENAI_MODEL = 'shim-test-model'\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'shim-test-model',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n defaultHeaders: {\n 'x-override-header': 'from-client',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'shim-test-model',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n },\n {\n headers: {\n 'x-override-header': 'from-request',\n },\n },\n )\n\n expect(capturedHeaders?.get('x-static-header')).toBe('from-descriptor')\n expect(capturedHeaders?.get('x-override-header')).toBe('from-request')\n})\n\ntest('opengateway sends Accept-Encoding: identity header on chat requests', async () => {\n let capturedHeaders: Headers | undefined\n\n registerGateway({\n id: 'atuscode-opengateway-test',\n label: 'AtusCode Opengateway',\n category: 'aggregating',\n defaultBaseUrl: 'https://opengateway.atuscode.com/v1/xiaomi-mimo',\n defaultModel: 'mimo-v2.5-pro',\n setup: {\n requiresAuth: false,\n authMode: 'none',\n },\n transportConfig: {\n kind: 'openai-compatible',\n openaiShim: {\n headers: {\n 'Accept-Encoding': 'identity',\n },\n defaultAuthHeader: {\n name: 'api-key',\n scheme: 'raw',\n },\n preserveReasoningContent: true,\n requireReasoningContentOnAssistantMessages: true,\n reasoningContentFallback: '',\n maxTokensField: 'max_completion_tokens',\n supportsApiFormatSelection: false,\n supportsAuthHeaders: false,\n },\n },\n })\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://opengateway.atuscode.com/v1/xiaomi-mimo'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'mimo-v2.5-pro',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n },\n {},\n )\n\n expect(capturedHeaders?.get('Accept-Encoding')).toBe('identity')\n})\n\ntest('strips Anthropic-specific headers on GitHub Codex transport requests', async () => {\n let capturedHeaders: Headers | undefined\n\n process.env.CLAUDE_CODE_USE_GITHUB = '1'\n process.env.OPENAI_API_KEY = 'github-test-key'\n delete process.env.OPENAI_BASE_URL\n delete process.env.OPENAI_MODEL\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response('', {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n },\n })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'github:gpt-5-codex',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: true,\n },\n {\n headers: {\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\n 'x-anthropic-additional-protection': 'true',\n 'x-safe-header': 'keep-me',\n },\n },\n )\n\n expect(capturedHeaders?.get('anthropic-version')).toBeNull()\n expect(capturedHeaders?.get('anthropic-beta')).toBeNull()\n expect(capturedHeaders?.get('x-anthropic-additional-protection')).toBeNull()\n expect(capturedHeaders?.get('x-safe-header')).toBe('keep-me')\n expect(capturedHeaders?.get('authorization')).toBe('Bearer github-test-key')\n expect(capturedHeaders?.get('editor-plugin-version')).toBe('copilot-chat/0.26.7')\n})\n\ntest('strips Anthropic-specific headers on GitHub Codex transport with providerOverride API key', async () => {\n let capturedHeaders: Headers | undefined\n\n process.env.CLAUDE_CODE_USE_GITHUB = '1'\n process.env.OPENAI_API_KEY = 'env-should-not-win'\n delete process.env.OPENAI_BASE_URL\n delete process.env.OPENAI_MODEL\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response('', {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n },\n })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n providerOverride: {\n model: 'github:gpt-5-codex',\n baseURL: 'https://api.githubcopilot.com',\n apiKey: 'provider-override-key',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'ignored',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: true,\n },\n {\n headers: {\n 'anthropic-version': '2023-06-01',\n 'x-claude-remote-session-id': 'remote-123',\n 'x-safe-header': 'keep-me',\n },\n },\n )\n\n expect(capturedHeaders?.get('anthropic-version')).toBeNull()\n expect(capturedHeaders?.get('x-claude-remote-session-id')).toBeNull()\n expect(capturedHeaders?.get('x-safe-header')).toBe('keep-me')\n expect(capturedHeaders?.get('authorization')).toBe('Bearer provider-override-key')\n expect(capturedHeaders?.get('editor-plugin-version')).toBe('copilot-chat/0.26.7')\n})\n\ntest('preserves usage from final OpenAI stream chunk with empty choices', async () => {\n globalThis.fetch = (async (_input, init) => {\n const url = typeof _input === 'string' ? _input : _input.url\n expect(url).toBe('http://example.test/v1/chat/completions')\n\n const body = JSON.parse(String(init?.body))\n expect(body.stream).toBe(true)\n expect(body.stream_options).toEqual({ include_usage: true })\n\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'fake-model',\n choices: [\n {\n index: 0,\n delta: { role: 'assistant', content: 'hello world' },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'fake-model',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'fake-model',\n choices: [],\n usage: {\n prompt_tokens: 123,\n completion_tokens: 45,\n total_tokens: 168,\n },\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'fake-model',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const usageEvent = events.find(\n event => event.type === 'message_delta' && typeof event.usage === 'object' && event.usage !== null,\n ) as { usage?: { input_tokens?: number; output_tokens?: number } } | undefined\n\n expect(usageEvent).toBeDefined()\n expect(usageEvent?.usage?.input_tokens).toBe(123)\n expect(usageEvent?.usage?.output_tokens).toBe(45)\n})\n\ntest('uses max_tokens instead of max_completion_tokens for local providers', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n globalThis.fetch = (async (_input, init) => {\n const body = JSON.parse(String(init?.body))\n expect(body.max_tokens).toBe(64)\n expect(body.max_completion_tokens).toBeUndefined()\n expect(body.stream_options).toBeUndefined()\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'llama3.1:8b',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'hello',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 5,\n completion_tokens: 1,\n total_tokens: 6,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'llama3.1:8b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n})\n\ntest('keeps max_completion_tokens for non-local non-github providers', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n\n globalThis.fetch = (async (_input, init) => {\n const body = JSON.parse(String(init?.body))\n expect(body.max_completion_tokens).toBe(64)\n expect(body.max_tokens).toBeUndefined()\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'hello',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 5,\n completion_tokens: 1,\n total_tokens: 6,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n})\n\ntest('uses route-specific credential env vars for descriptor-backed openai-compatible routes', async () => {\n let capturedHeaders: Headers | undefined\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENROUTER_API_KEY = 'or-route-key'\n delete process.env.OPENAI_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'openai/gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 5,\n completion_tokens: 1,\n total_tokens: 6,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'openai/gpt-5-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('authorization')).toBe('Bearer or-route-key')\n})\n\ntest('preserves Gemini tool call extra_content in follow-up requests', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Use Bash' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'pwd' },\n extra_content: {\n google: {\n thought_signature: 'sig-123',\n },\n },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_1',\n content: 'D:\\\\repo',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n ) as { tool_calls?: Array> } | undefined\n\n expect(assistantWithToolCall?.tool_calls?.[0]).toMatchObject({\n id: 'call_1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: JSON.stringify({ command: 'pwd' }),\n },\n extra_content: {\n google: {\n thought_signature: 'sig-123',\n },\n },\n })\n})\n\ntest('replays Gemini tool signatures for OpenGateway Gemini models', async () => {\n process.env.OPENAI_BASE_URL = 'https://opengateway.atuscode.com/v1'\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [\n { role: 'user', content: 'Use Write' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Write',\n input: { file_path: 'todo.md', content: 'todo' },\n signature: 'sig-opengateway',\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_1',\n content: 'created',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n ) as { tool_calls?: Array> } | undefined\n\n expect(assistantWithToolCall?.tool_calls?.[0]).toMatchObject({\n id: 'call_1',\n extra_content: {\n google: {\n thought_signature: 'sig-opengateway',\n },\n },\n })\n})\n\ntest('OpenGateway MiMo replays real reasoning_content without adding empty fallback', async () => {\n process.env.OPENAI_BASE_URL = 'https://opengateway.atuscode.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-opengateway-mimo',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [\n { role: 'user', content: 'Use an agent' },\n {\n role: 'assistant',\n content: [\n {\n type: 'thinking',\n thinking: 'Need to inspect code with an agent.',\n },\n {\n type: 'tool_use',\n id: 'call_agent_1',\n name: 'Agent',\n input: {\n description: 'Inspect code',\n prompt: 'Look at the relevant code',\n subagent_type: 'general-purpose',\n },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_agent_1',\n content: 'Agent finished',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n )\n\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'Need to inspect code with an agent.',\n )\n expect(requestBody).not.toHaveProperty('store')\n})\n\ntest('Xiaomi MiMo replays real reasoning_content without adding empty fallback', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.xiaomimimo.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n process.env.MIMO_API_KEY = 'mimo-test-key'\n delete process.env.OPENAI_API_KEY\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-mimo',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [\n { role: 'user', content: 'Use an agent' },\n {\n role: 'assistant',\n content: [\n {\n type: 'thinking',\n thinking: 'Need to inspect code with an agent.',\n },\n {\n type: 'tool_use',\n id: 'call_agent_1',\n name: 'Agent',\n input: {\n description: 'Inspect code',\n prompt: 'Look at the relevant code',\n subagent_type: 'general-purpose',\n },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_agent_1',\n content: 'Agent finished',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n )\n\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'Need to inspect code with an agent.',\n )\n expect(requestBody).not.toHaveProperty('store')\n})\n\ntest('OpenGateway MiMo does not synthesize empty reasoning_content when missing', async () => {\n process.env.OPENAI_BASE_URL = 'https://opengateway.atuscode.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-opengateway-mimo',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [\n { role: 'user', content: 'Use an agent' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_agent_1',\n name: 'Agent',\n input: {\n description: 'Inspect code',\n prompt: 'Look at the relevant code',\n subagent_type: 'general-purpose',\n },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_agent_1',\n content: 'Agent finished',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n )\n\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall).not.toHaveProperty('reasoning_content')\n expect(requestBody).not.toHaveProperty('store')\n})\n\ntest('strips unsupported stream_options for Xiaomi MiMo streams', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.xiaomimimo.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n process.env.MIMO_API_KEY = 'mimo-test-key'\n delete process.env.OPENAI_API_KEY\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return makeSseResponse(\n makeStreamChunks([\n {\n id: 'chatcmpl-mimo',\n object: 'chat.completion.chunk',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n index: 0,\n delta: { role: 'assistant', content: 'done' },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-mimo',\n object: 'chat.completion.chunk',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ]),\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: true,\n })\n\n expect(requestBody).toMatchObject({\n stream: true,\n max_completion_tokens: 64,\n })\n expect(requestBody).not.toHaveProperty('stream_options')\n expect(requestBody).not.toHaveProperty('store')\n})\n\ntest('preserves Grep tool pattern field in OpenAI-compatible schemas', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-grep-schema',\n model: 'qwen/qwen3.6-plus',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'qwen/qwen3.6-plus',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Grep' }],\n tools: [\n {\n name: 'Grep',\n description: 'Search file contents',\n input_schema: {\n type: 'object',\n properties: {\n pattern: { type: 'string', description: 'Search pattern' },\n path: { type: 'string' },\n },\n required: ['pattern'],\n additionalProperties: false,\n },\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const tools = requestBody?.tools as Array> | undefined\n const grepTool = tools?.find(tool => (tool.function as Record)?.name === 'Grep') as\n | { function?: { parameters?: { properties?: Record; required?: string[] } } }\n | undefined\n\n expect(Object.keys(grepTool?.function?.parameters?.properties ?? {})).toContain('pattern')\n expect(grepTool?.function?.parameters?.required).toContain('pattern')\n})\n\ntest('does not infer Gemini mode from OPENAI_BASE_URL path substrings', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.OPENAI_BASE_URL =\n 'https://evil.example/generativelanguage.googleapis.com/v1beta/openai'\n delete process.env.OPENAI_API_KEY\n process.env.GEMINI_API_KEY = 'gemini-secret'\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'fake-model',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'fake-model',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedAuthorization).toBeNull()\n})\n\ntest('preserves image tool results as placeholders in follow-up requests', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen/qwen3.6-plus',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'qwen/qwen3.6-plus',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Read this screenshot' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_image_1',\n name: 'Read',\n input: { file_path: 'C:\\\\temp\\\\screenshot.png' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_image_1',\n content: [\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: 'ZmFrZQ==',\n },\n },\n ],\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const toolMessage = (requestBody?.messages as Array>).find(\n message => message.role === 'tool',\n ) as {\n content?: Array<{\n type: string\n text?: string\n image_url?: { url: string }\n }> | string\n } | undefined\n\n expect(Array.isArray(toolMessage?.content)).toBe(true)\n const parts = toolMessage?.content as Array<{\n type: string\n text?: string\n image_url?: { url: string }\n }>\n expect(parts).toEqual([\n {\n type: 'image_url',\n image_url: { url: 'data:image/png;base64,ZmFrZQ==' },\n },\n ])\n})\n\ntest('adds text part for image-only user messages', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n system: 'test system',\n messages: [\n {\n role: 'user',\n content: [\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: 'ZmFrZQ==',\n },\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const userMessage = (requestBody?.messages as Array>).find(\n message => message.role === 'user',\n ) as {\n content?: Array<{\n type: string\n text?: string\n image_url?: { url: string }\n }>\n } | undefined\n\n expect(userMessage?.content).toEqual([\n { type: 'text', text: 'Image attached.' },\n {\n type: 'image_url',\n image_url: { url: 'data:image/png;base64,ZmFrZQ==' },\n },\n ])\n})\n\ntest('preserves mixed text and image tool results as multipart content', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Read this screenshot' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_image_2',\n name: 'Read',\n input: { file_path: 'C:\\\\temp\\\\screenshot.png' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_image_2',\n content: [\n { type: 'text', text: 'Screenshot captured' },\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: 'ZmFrZQ==',\n },\n },\n ],\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const toolMessage = (requestBody?.messages as Array>).find(\n message => message.role === 'tool',\n ) as {\n content?: Array<{\n type: string\n text?: string\n image_url?: { url: string }\n }>\n } | undefined\n\n expect(Array.isArray(toolMessage?.content)).toBe(true)\n const parts = toolMessage?.content ?? []\n expect(parts[0]).toEqual({ type: 'text', text: 'Screenshot captured' })\n expect(parts[1]).toEqual({\n type: 'image_url',\n image_url: { url: 'data:image/png;base64,ZmFrZQ==' },\n })\n})\n\ntest('uses GEMINI_ACCESS_TOKEN for Gemini OpenAI-compatible requests', async () => {\n let capturedAuthorization: string | null = null\n let capturedProject: string | null = null\n let requestUrl: string | undefined\n\n process.env.CLAUDE_CODE_USE_GEMINI = '1'\n process.env.GEMINI_AUTH_MODE = 'access-token'\n process.env.GEMINI_ACCESS_TOKEN = 'gemini-access-token'\n process.env.GOOGLE_CLOUD_PROJECT = 'gemini-project'\n process.env.GEMINI_BASE_URL =\n 'https://generativelanguage.googleapis.com/v1beta/openai'\n process.env.GEMINI_MODEL = 'gemini-2.0-flash'\n delete process.env.OPENAI_BASE_URL\n delete process.env.OPENAI_API_KEY\n delete process.env.GEMINI_API_KEY\n delete process.env.GOOGLE_API_KEY\n\n globalThis.fetch = (async (input, init) => {\n requestUrl = typeof input === 'string' ? input : input.url\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n capturedProject =\n headers?.['x-goog-user-project'] ??\n headers?.['X-Goog-User-Project'] ??\n null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-gemini',\n model: 'gemini-2.0-flash',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 3,\n completion_tokens: 1,\n total_tokens: 4,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gemini-2.0-flash',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(requestUrl).toBe(\n 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions',\n )\n // Explicit type argument: TS narrows the closure-assigned variables to\n // their `null` initializer at this point (microsoft/TypeScript#9998).\n expect(capturedAuthorization).toBe('Bearer gemini-access-token')\n expect(capturedProject).toBe('gemini-project')\n})\n\ntest('uses NVIDIA_API_KEY for NVIDIA NIM requests without OPENAI_API_KEY', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.NVIDIA_NIM = '1'\n process.env.OPENAI_BASE_URL = 'https://integrate.api.nvidia.com/v1'\n process.env.OPENAI_MODEL = 'nvidia/llama-3.1-nemotron-70b-instruct'\n process.env.NVIDIA_API_KEY = 'nvidia-live-key'\n delete process.env.OPENAI_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-nvidia',\n model: 'nvidia/llama-3.1-nemotron-70b-instruct',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 3,\n completion_tokens: 1,\n total_tokens: 4,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'nvidia/llama-3.1-nemotron-70b-instruct',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedAuthorization).toBe('Bearer nvidia-live-key')\n})\n\ntest('does not use stale NVIDIA_API_KEY for non-NVIDIA OpenAI-compatible routes', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.NVIDIA_NIM = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENAI_MODEL = 'openai/gpt-5-mini'\n process.env.NVIDIA_API_KEY = 'nvidia-live-key'\n delete process.env.OPENAI_API_KEY\n delete process.env.OPENROUTER_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-openrouter',\n model: 'openai/gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'openai/gpt-5-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedAuthorization).toBeNull()\n})\n\ntest('does not use MINIMAX_API_KEY for non-MiniMax OpenAI-compatible routes', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENAI_MODEL = 'openai/gpt-5-mini'\n process.env.MINIMAX_API_KEY = 'minimax-live-key'\n delete process.env.OPENAI_API_KEY\n delete process.env.OPENROUTER_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-openrouter',\n model: 'openai/gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'openai/gpt-5-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedAuthorization).toBeNull()\n})\n\ntest('xiaomi mimo route uses api-key auth header and max_completion_tokens', async () => {\n let capturedHeaders: Record | undefined\n let capturedBody: Record | undefined\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://api.xiaomimimo.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n process.env.MIMO_API_KEY = 'mimo-live-key'\n delete process.env.OPENAI_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = init?.headers as Record\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-mimo',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedHeaders).toMatchObject({ 'api-key': 'mimo-live-key' })\n expect(capturedHeaders).not.toHaveProperty('Authorization')\n expect(capturedBody).toMatchObject({ max_completion_tokens: 32 })\n expect(capturedBody).not.toHaveProperty('max_tokens')\n})\n\ntest('atuscode opengateway provider flag sends OPENGATEWAY_API_KEY as bearer auth despite stale generic base URL', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_MODEL = 'gpt-5.5'\n process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'\n delete process.env.OPENAI_API_KEY\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('https://opengateway.atuscode.com/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('atuscode opengateway provider flag accepts OPENAI_API_KEY compatibility fallback', async () => {\n delete process.env.OPENAI_BASE_URL\n delete process.env.OPENGATEWAY_API_KEY\n process.env.OPENAI_API_KEY = 'fake-openai-fallback'\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.authorization).toBe('Bearer fake-openai-fallback')\n})\n\ntest('atuscode opengateway provider flag sends OPENAI_API_KEY fallback despite stale generic base URL', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'fake-openai-fallback'\n delete process.env.OPENGATEWAY_API_KEY\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('https://opengateway.atuscode.com/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-openai-fallback')\n})\n\ntest('atuscode opengateway provider flag trims OPENGATEWAY_API_KEY before bearer auth', async () => {\n process.env.OPENGATEWAY_API_KEY = ' fake-ogw-key '\n delete process.env.OPENAI_API_KEY\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('atuscode opengateway provider flag ignores blank OPENGATEWAY_API_KEY and uses OPENAI_API_KEY fallback', async () => {\n process.env.OPENGATEWAY_API_KEY = ' '\n process.env.OPENAI_API_KEY = 'fake-openai-fallback'\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.authorization).toBe('Bearer fake-openai-fallback')\n})\n\ntest('atuscode opengateway provider flag sends OPENGATEWAY_API_KEY to OPENGATEWAY_BASE_URL override', async () => {\n process.env.OPENGATEWAY_BASE_URL = 'http://localhost:8181/v1'\n process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'\n delete process.env.OPENAI_API_KEY\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('http://localhost:8181/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('atuscode opengateway provider flag sends OPENGATEWAY_API_KEY to custom OPENAI_BASE_URL fallback', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:8181/v1'\n process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'\n delete process.env.OPENGATEWAY_BASE_URL\n delete process.env.OPENAI_API_KEY\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('http://localhost:8181/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('atuscode opengateway provider flag prefers OPENGATEWAY_API_KEY over generic OPENAI_API_KEY for custom base URL', async () => {\n process.env.OPENGATEWAY_BASE_URL = 'http://localhost:8181/v1'\n process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'\n process.env.OPENAI_API_KEY = 'fake-generic-openai-key'\n\n const result = applyProviderFlag('atuscode-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('http://localhost:8181/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('atuscode opengateway stored provider profile key becomes bearer auth', async () => {\n delete process.env.OPENAI_API_KEY\n delete process.env.OPENGATEWAY_API_KEY\n\n applyProviderProfileToProcessEnv({\n id: 'stored-opengateway',\n provider: 'atuscode-opengateway',\n name: 'AtusCode Opengateway',\n baseUrl: 'https://opengateway.atuscode.com/v1',\n model: 'mimo-v2.5-pro',\n apiKey: 'fake-profile-key',\n })\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.authorization).toBe('Bearer fake-profile-key')\n})\n\ntest('openai route still sends OPENAI_API_KEY as bearer auth', async () => {\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_MODEL = 'gpt-5.5'\n process.env.OPENAI_API_KEY = 'fake-openai-key'\n delete process.env.OPENGATEWAY_API_KEY\n\n const captured = await captureChatCompletionRequest('gpt-5.5')\n\n expect(captured.authorization).toBe('Bearer fake-openai-key')\n})\n\ntest('does not use BNKR_API_KEY for non-Bankr OpenAI-compatible routes', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENAI_MODEL = 'openai/gpt-5-mini'\n process.env.BNKR_API_KEY = 'bankr-live-key'\n delete process.env.OPENAI_API_KEY\n delete process.env.OPENROUTER_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-openrouter',\n model: 'openai/gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'openai/gpt-5-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedAuthorization).toBeNull()\n})\n\ntest('preserves Gemini tool call extra_content from streaming chunks', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n extra_content: {\n google: {\n thought_signature: 'sig-stream',\n },\n },\n function: {\n name: 'Bash',\n arguments: '{\"command\":\"pwd\"}',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const toolStart = events.find(\n event =>\n event.type === 'content_block_start' &&\n typeof event.content_block === 'object' &&\n event.content_block !== null &&\n (event.content_block as Record).type === 'tool_use',\n ) as { content_block?: Record } | undefined\n\n expect(toolStart?.content_block).toMatchObject({\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n extra_content: {\n google: {\n thought_signature: 'sig-stream',\n },\n },\n })\n})\n\ntest('preserves Gemini thought signature from streaming delta extra_content', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n extra_content: {\n google: {\n thought_signature: 'sig-delta',\n },\n },\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Write',\n arguments: '{\"file_path\":\"todo.md\",\"content\":\"todo\"}',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [{ role: 'user', content: 'Use Write' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const toolStart = events.find(\n event =>\n event.type === 'content_block_start' &&\n typeof event.content_block === 'object' &&\n event.content_block !== null &&\n (event.content_block as Record).type === 'tool_use',\n ) as { content_block?: Record } | undefined\n\n expect(toolStart?.content_block).toMatchObject({\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Write',\n extra_content: {\n google: {\n thought_signature: 'sig-delta',\n },\n },\n signature: 'sig-delta',\n })\n})\n\ntest('preserves Gemini thought signature from non-streaming message extra_content', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n message: {\n role: 'assistant',\n extra_content: {\n google: {\n thought_signature: 'sig-message',\n },\n },\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Write',\n arguments: '{\"file_path\":\"todo.md\",\"content\":\"todo\"}',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [{ role: 'user', content: 'Use Write' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content?.[0]).toMatchObject({\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Write',\n extra_content: {\n google: {\n thought_signature: 'sig-message',\n },\n },\n signature: 'sig-message',\n })\n})\n\ntest('converts Gemini raw tool-call text into streaming tool_use blocks', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-raw-tool',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n content: 'Tool calls',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-raw-tool',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {\n content:\n ' requested:\\n- Write({\"file_path\":\"style.css\",\"content\":\"ul { padding: 0; }\"}) [id: call79435b5a26564619b0151197]',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-raw-tool',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [{ role: 'user', content: 'Write CSS' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n expect(\n events.some(\n event =>\n event.type === 'content_block_start' &&\n (event.content_block as Record | undefined)?.type ===\n 'text',\n ),\n ).toBe(false)\n\n const toolStart = events.find(\n event =>\n event.type === 'content_block_start' &&\n (event.content_block as Record | undefined)?.type ===\n 'tool_use',\n ) as { content_block?: Record } | undefined\n expect(toolStart?.content_block).toMatchObject({\n type: 'tool_use',\n id: 'call79435b5a26564619b0151197',\n name: 'Write',\n })\n\n const toolInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n (event.delta as Record | undefined)?.type ===\n 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n expect(JSON.parse(toolInput)).toEqual({\n file_path: 'style.css',\n content: 'ul { padding: 0; }',\n })\n\n const stop = events.find(event => event.type === 'message_delta') as\n | { delta?: Record }\n | undefined\n expect(stop?.delta?.stop_reason).toBe('tool_use')\n})\n\ntest('converts Gemini raw tool-call text into non-streaming tool_use blocks', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-raw-tool',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n message: {\n role: 'assistant',\n content:\n 'Tool calls requested:\\n- Agent({\"description\":\"Verify the todo list application functionality.\",\"prompt\":\"Check files.\",\"subagent_type\":\"verification\"}) [id: call9a8b7c6d5e4f3a2b1c0d9e8f]',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [{ role: 'user', content: 'Verify' }],\n max_tokens: 64,\n stream: false,\n }) as {\n stop_reason?: string\n content?: Array>\n }\n\n expect(message.stop_reason).toBe('tool_use')\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'call9a8b7c6d5e4f3a2b1c0d9e8f',\n name: 'Agent',\n input: {\n description: 'Verify the todo list application functionality.',\n prompt: 'Check files.',\n subagent_type: 'verification',\n },\n },\n ])\n})\n\ntest('normalizes plain string Bash tool arguments from OpenAI-compatible responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: false,\n }) as {\n stop_reason?: string\n content?: Array>\n }\n\n expect(message.stop_reason).toBe('tool_use')\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n input: { command: 'pwd' },\n },\n ])\n})\n\ntest('normalizes Bash tool arguments that are valid JSON strings', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '\"pwd\"',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n input: { command: 'pwd' },\n },\n ])\n})\n\ntest.each([\n ['false', false],\n ['null', null],\n ['[]', []],\n])(\n 'preserves malformed Bash JSON literals as parsed values in non-streaming responses: %s',\n async (argumentsValue, expectedInput) => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: argumentsValue,\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n input: expectedInput,\n },\n ])\n },\n)\n\ntest('keeps terminal empty Bash tool arguments invalid in non-streaming responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n input: {},\n },\n ])\n})\n\ntest('normalizes plain string Bash tool arguments in streaming responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"pwd\"}')\n})\n\ntest('normalizes plain string Bash tool arguments when streaming starts with an empty chunk', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: 0,\n type: 'function',\n function: {\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"pwd\"}')\n})\n\ntest('normalizes plain string Bash tool arguments when streaming starts with whitespace', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: ' ',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: 0,\n type: 'function',\n function: {\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\" pwd\"}')\n})\n\ntest('keeps terminal whitespace-only Bash arguments invalid in streaming responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: ' ',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{}')\n})\n\ntest('normalizes streaming Bash arguments that begin with bracket syntax', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '[ -f package.json ] && pwd',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"[ -f package.json ] && pwd\"}')\n})\n\ntest('normalizes streaming Bash arguments when the first chunk is only an opening brace', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '{',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: 0,\n type: 'function',\n function: {\n arguments: ' pwd; }',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"{ pwd; }\"}')\n})\n\ntest('repairs truncated structured Bash JSON in streaming responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '{\"command\":\"pwd\"',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"pwd\"}')\n})\n\ntest('does not normalize incomplete streamed Bash commands when finish_reason is length', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: 'rg --fi',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'length',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const streamedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(streamedInput).toBe('rg --fi')\n})\n\ntest('repairs truncated JSON objects even without command field', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '{\"cwd\":\"/tmp\"',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const streamedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(streamedInput).toBe('{\"cwd\":\"/tmp\"}')\n})\n\ntest('preserves raw input for unknown plain string tool arguments', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'UnknownTool',\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use tool' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'UnknownTool',\n input: {},\n },\n ])\n})\n\ntest('preserves parsed string input for unknown JSON string tool arguments', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'UnknownTool',\n arguments: '\"pwd\"',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use tool' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'UnknownTool',\n input: 'pwd',\n },\n ])\n})\n\ntest('sanitizes malformed MCP tool schemas before sending them to OpenAI', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 1,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n tools: [\n {\n name: 'mcp__clientry__create_task',\n description: 'Create a task',\n input_schema: {\n type: 'object',\n properties: {\n priority: {\n type: 'integer',\n description: 'Priority: 0=low, 1=medium, 2=high, 3=urgent',\n default: true,\n enum: [false, 0, 1, 2, 3],\n },\n },\n },\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const parameters = (\n requestBody?.tools as Array<{ function?: { parameters?: Record } }>\n )?.[0]?.function?.parameters\n const properties = parameters?.properties as\n | Record\n | undefined\n\n expect(parameters?.additionalProperties).toBe(false)\n // No required[] in the original schema → none added (optional properties must not be forced required)\n expect(parameters?.required).toEqual([])\n expect(properties?.priority?.type).toBe('integer')\n expect(properties?.priority?.enum).toEqual([0, 1, 2, 3])\n expect(properties?.priority).not.toHaveProperty('default')\n})\n\ntest('optional tool properties are not added to required[] — fixes Groq/Azure 400 tool_use_failed', async () => {\n // Regression test for: all optional properties being sent as required in strict mode,\n // causing providers like Groq to reject valid tool calls where the model omits optional args.\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-4',\n model: 'gpt-4o',\n choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }],\n usage: { prompt_tokens: 5, completion_tokens: 2, total_tokens: 7 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'read a file' }],\n tools: [\n {\n name: 'Read',\n description: 'Read a file',\n input_schema: {\n type: 'object',\n properties: {\n file_path: { type: 'string', description: 'Absolute path to file' },\n offset: { type: 'number', description: 'Line to start from' },\n limit: { type: 'number', description: 'Max lines to read' },\n pages: { type: 'string', description: 'Page range for PDFs' },\n },\n required: ['file_path'],\n },\n },\n ],\n max_tokens: 16,\n stream: false,\n })\n\n const parameters = (\n requestBody?.tools as Array<{ function?: { parameters?: Record } }>\n )?.[0]?.function?.parameters\n\n expect(parameters?.required).toEqual(['file_path'])\n\n const required = parameters?.required as string[] | undefined\n expect(required).not.toContain('offset')\n expect(required).not.toContain('limit')\n expect(required).not.toContain('pages')\n expect(parameters?.additionalProperties).toBe(false)\n})\n\n// ---------------------------------------------------------------------------\n// Issue #202 — consecutive role coalescing (Devstral, Mistral strict templates)\n// ---------------------------------------------------------------------------\n\nfunction makeNonStreamResponse(content = 'ok'): Response {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-test',\n model: 'test-model',\n choices: [{ message: { role: 'assistant', content }, finish_reason: 'stop' }],\n usage: { prompt_tokens: 5, completion_tokens: 1, total_tokens: 6 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n}\n\ntest('coalesces consecutive user messages to avoid alternation errors (issue #202)', async () => {\n let sentMessages: Array<{ role: string; content: unknown }> | undefined\n\n globalThis.fetch = (async (_input: unknown, init: RequestInit | undefined) => {\n sentMessages = JSON.parse(String(init?.body)).messages\n return makeNonStreamResponse()\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'test-model',\n system: 'sys',\n messages: [\n { role: 'user', content: 'first message' },\n { role: 'user', content: 'second message' },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n expect(sentMessages?.length).toBe(2)\n expect(sentMessages?.[0]?.role).toBe('system')\n expect(sentMessages?.[1]?.role).toBe('user')\n const userContent = sentMessages?.[1]?.content as string\n expect(userContent).toContain('first message')\n expect(userContent).toContain('second message')\n})\n\ntest('coalesces consecutive assistant messages preserving tool_calls (issue #202)', async () => {\n let sentMessages: Array<{ role: string; content: unknown; tool_calls?: unknown[] }> | undefined\n\n globalThis.fetch = (async (_input: unknown, init: RequestInit | undefined) => {\n sentMessages = JSON.parse(String(init?.body)).messages\n return makeNonStreamResponse()\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'test-model',\n system: 'sys',\n messages: [\n { role: 'user', content: 'go' },\n { role: 'assistant', content: 'thinking...' },\n {\n role: 'assistant',\n content: [{ type: 'tool_use', id: 'call_1', name: 'Bash', input: { command: 'ls' } }],\n },\n { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'call_1', content: 'file.txt' }] },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantMsgs = sentMessages?.filter(m => m.role === 'assistant')\n expect(assistantMsgs?.length).toBe(1)\n expect(assistantMsgs?.[0]?.tool_calls?.length).toBeGreaterThan(0)\n})\n\ntest('non-streaming: reasoning_content emitted as thinking block only when content is null', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'glm-5',\n choices: [\n {\n message: {\n role: 'assistant',\n content: null,\n reasoning_content: 'Let me think about this step by step.',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 20,\n total_tokens: 30,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = (await client.beta.messages.create({\n model: 'glm-5',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })) as { content: Array> }\n\n expect(result.content).toEqual([\n { type: 'thinking', thinking: 'Let me think about this step by step.' },\n ])\n})\n\ntest('non-streaming: empty string content does not fall through to reasoning_content as text', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'glm-5',\n choices: [\n {\n message: {\n role: 'assistant',\n content: '',\n reasoning_content: 'Chain of thought here.',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 20,\n total_tokens: 30,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = (await client.beta.messages.create({\n model: 'glm-5',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })) as { content: Array> }\n\n expect(result.content).toEqual([\n { type: 'thinking', thinking: 'Chain of thought here.' },\n ])\n})\n\ntest('non-streaming: real content takes precedence over reasoning_content', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'glm-5',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'The answer is 42.',\n reasoning_content: 'I need to calculate this.',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 20,\n total_tokens: 30,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = (await client.beta.messages.create({\n model: 'glm-5',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })) as { content: Array> }\n\n expect(result.content).toEqual([\n { type: 'thinking', thinking: 'I need to calculate this.' },\n { type: 'text', text: 'The answer is 42.' },\n ])\n})\n\ntest('non-streaming: strips tag block from assistant content', async () => {\n globalThis.fetch = asMockFetch(mock(async () => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content:\n 'user wants a greeting, respond brieflyHey! How can I help you today?',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 20,\n total_tokens: 30,\n },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n const result = (await client.beta.messages.create({\n model: 'gpt-5-mini',\n system: 'test system',\n messages: [{ role: 'user', content: 'hey' }],\n max_tokens: 64,\n stream: false,\n })) as { content: Array> }\n\n expect(result.content).toEqual([\n { type: 'text', text: 'Hey! How can I help you today?' },\n ])\n})\n\ntest('streaming: thinking block closed before tool call', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'glm-5',\n choices: [\n {\n index: 0,\n delta: { role: 'assistant', reasoning_content: 'Thinking...' },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'glm-5',\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: 0,\n id: 'call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '{\"command\":\"ls\"}',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'glm-5',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'glm-5',\n system: 'test system',\n messages: [{ role: 'user', content: 'Run ls' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const types = events.map(e => e.type)\n\n const thinkingStartIdx = types.indexOf('content_block_start')\n const firstStopIdx = types.indexOf('content_block_stop')\n const toolStartIdx = types.indexOf(\n 'content_block_start',\n thinkingStartIdx + 1,\n )\n\n expect(thinkingStartIdx).toBeGreaterThanOrEqual(0)\n expect(firstStopIdx).toBeGreaterThan(thinkingStartIdx)\n expect(toolStartIdx).toBeGreaterThan(firstStopIdx)\n\n const thinkingStart = events[thinkingStartIdx] as {\n content_block?: Record\n }\n expect(thinkingStart?.content_block?.type).toBe('thinking')\n})\n\ntest('streaming: strips tag block from assistant content deltas', async () => {\n globalThis.fetch = asMockFetch(mock(async () => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n content:\n 'user wants a greeting, respond brieflyHey! How can I help you today?',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n const result = await client.beta.messages\n .create({\n model: 'gpt-5-mini',\n system: 'test system',\n messages: [{ role: 'user', content: 'hey' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const textDeltas: string[] = []\n for await (const event of result.data) {\n const delta = (event as { delta?: { type?: string; text?: string } }).delta\n if (delta?.type === 'text_delta' && typeof delta.text === 'string') {\n textDeltas.push(delta.text)\n }\n }\n\n expect(textDeltas.join('')).toBe('Hey! How can I help you today?')\n})\n\ntest('streaming: strips tag split across multiple content chunks', async () => {\n globalThis.fetch = asMockFetch(mock(async () => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n content: 'user wants a greeting,',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {\n content: ' respond brieflyHey! How can I help you today?',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'gpt-5-mini',\n system: 'test system',\n messages: [{ role: 'user', content: 'hey' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const textDeltas: string[] = []\n for await (const event of result.data) {\n const delta = (event as { delta?: { type?: string; text?: string } }).delta\n if (delta?.type === 'text_delta' && typeof delta.text === 'string') {\n textDeltas.push(delta.text)\n }\n }\n\n expect(textDeltas.join('')).toBe('Hey! How can I help you today?')\n})\n\ntest('streaming: preserves prose without tags (no phrase-based false positive)', async () => {\n // Regression: older phrase-based sanitizer would strip \"I should...\" prose.\n // The tag-based approach leaves legitimate assistant output alone.\n globalThis.fetch = asMockFetch(mock(async () => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n content:\n 'I should note that the user role requires a briefly concise friendly response format.',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n const result = await client.beta.messages\n .create({\n model: 'gpt-5-mini',\n system: 'test system',\n messages: [{ role: 'user', content: 'hey' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const textDeltas: string[] = []\n for await (const event of result.data) {\n const delta = (event as { delta?: { type?: string; text?: string } }).delta\n if (delta?.type === 'text_delta' && typeof delta.text === 'string') {\n textDeltas.push(delta.text)\n }\n }\n\n expect(textDeltas.join('')).toBe(\n 'I should note that the user role requires a briefly concise friendly response format.',\n )\n})\n\ntest('strips credentials and query params from URL in fetch network error message', async () => {\n process.env.OPENAI_BASE_URL =\n 'https://user:password@internal.example.test/v1?token=abc123'\n process.env.OPENAI_API_KEY = 'test-key'\n\n globalThis.fetch = asMockFetch(mock(async () => {\n throw new TypeError(\n 'fetch failed https://user:password@internal.example.test/v1?token=abc123/chat/completions',\n )\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n let caught: unknown\n try {\n await client.beta.messages.create({\n model: 'test-model',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n } catch (error) {\n caught = error\n }\n\n const message = (caught as Error).message\n expect(message).toContain('internal.example.test')\n expect(message).toContain('fetch failed')\n expect(message).not.toContain('password')\n expect(message).not.toContain('user:')\n expect(message).not.toContain('token=abc123')\n})\n\ntest('classifies localhost transport failures with actionable category marker', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n const transportError = Object.assign(new TypeError('fetch failed'), {\n code: 'ECONNREFUSED',\n })\n\n globalThis.fetch = asMockFetch(mock(async () => {\n throw transportError\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).rejects.toThrow('openai_category=connection_refused')\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).rejects.toThrow('local server is running')\n})\n\ntest('transport failures are not labeled with HTTP status 503', async () => {\n // Issue #971: ENETDOWN (and other transport errors) are emitted before any\n // HTTP response is received. Reporting them as \"503\" makes users believe the\n // upstream server returned 503 Service Unavailable.\n process.env.OPENAI_BASE_URL = 'https://intranet.example.test/v1'\n\n const transportError = Object.assign(new TypeError('fetch failed'), {\n code: 'ENETDOWN',\n })\n\n globalThis.fetch = asMockFetch(mock(async () => {\n throw transportError\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n let caught: unknown\n try {\n await client.beta.messages.create({\n model: 'gpt-4o-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n } catch (error) {\n caught = error\n }\n\n expect(caught).toBeDefined()\n const err = caught as { status?: number; message: string; constructor: { name: string } }\n expect(err.constructor.name).toBe('APIConnectionError')\n expect(err.status).toBeUndefined()\n expect(err.message).not.toMatch(/^503\\b/)\n expect(err.message).toContain('OpenAI API transport error')\n expect(err.message).toContain('code=ENETDOWN')\n expect(err.message).toContain('openai_category=network_error')\n})\n\ntest('propagates AbortError without wrapping it as transport failure', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n const abortError = new DOMException('The operation was aborted.', 'AbortError')\n globalThis.fetch = asMockFetch(mock(async () => {\n throw abortError\n }))\n\n const controller = new AbortController()\n controller.abort()\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create(\n {\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n },\n { signal: controller.signal },\n ),\n ).rejects.toBe(abortError)\n})\n\ntest('classifies chat-completions endpoint 404 failures with endpoint_not_found marker', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434'\n\n globalThis.fetch = asMockFetch(mock(async () =>\n new Response('Not Found', {\n status: 404,\n headers: {\n 'Content-Type': 'text/plain',\n },\n })))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).rejects.toThrow('openai_category=endpoint_not_found')\n})\ntest('self-heals localhost resolution failures by retrying local loopback base URL', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n const requestUrls: string[] = []\n globalThis.fetch = (async (input, _init) => {\n const url = typeof input === 'string' ? input : input.url\n requestUrls.push(url)\n\n if (url.includes('localhost')) {\n const error = Object.assign(new TypeError('fetch failed'), {\n code: 'ENOTFOUND',\n })\n throw error\n }\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen2.5-coder:7b',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'hello from loopback',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 4,\n completion_tokens: 3,\n total_tokens: 7,\n },\n }),\n {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).resolves.toBeDefined()\n\n expect(requestUrls[0]).toBe('http://localhost:11434/v1/chat/completions')\n expect(requestUrls).toContain('http://127.0.0.1:11434/v1/chat/completions')\n})\n\ntest('self-heals local endpoint_not_found by retrying with /v1 base URL', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434'\n\n const requestUrls: string[] = []\n globalThis.fetch = (async (input, _init) => {\n const url = typeof input === 'string' ? input : input.url\n requestUrls.push(url)\n\n if (url === 'http://localhost:11434/chat/completions') {\n return new Response('Not Found', {\n status: 404,\n headers: {\n 'Content-Type': 'text/plain',\n },\n })\n }\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen2.5-coder:7b',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'hello from /v1',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 5,\n completion_tokens: 2,\n total_tokens: 7,\n },\n }),\n {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).resolves.toBeDefined()\n\n expect(requestUrls).toEqual([\n 'http://localhost:11434/chat/completions',\n 'http://localhost:11434/v1/chat/completions',\n ])\n})\n\ntest('self-heals tool-call incompatibility by retrying local Ollama requests without tools', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n const requestBodies: Array> = []\n globalThis.fetch = (async (_input, init) => {\n const requestBody = JSON.parse(String(init?.body)) as Record\n requestBodies.push(requestBody)\n\n if (requestBodies.length === 1) {\n return new Response('tool_calls are not supported', {\n status: 400,\n headers: {\n 'Content-Type': 'text/plain',\n },\n })\n }\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen2.5-coder:7b',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'fallback without tools',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 4,\n total_tokens: 12,\n },\n }),\n {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n tools: [\n {\n name: 'Read',\n description: 'Read a file',\n input_schema: {\n type: 'object',\n properties: {\n filePath: { type: 'string' },\n },\n required: ['filePath'],\n },\n },\n ],\n max_tokens: 64,\n stream: false,\n }),\n ).resolves.toBeDefined()\n\n expect(requestBodies).toHaveLength(2)\n expect(Array.isArray(requestBodies[0]?.tools)).toBe(true)\n expect(requestBodies[0]?.tool_choice).toBeUndefined()\n expect(\n requestBodies[1]?.tools === undefined ||\n (Array.isArray(requestBodies[1]?.tools) && requestBodies[1]?.tools.length === 0),\n ).toBe(true)\n expect(requestBodies[1]?.tool_choice).toBeUndefined()\n})\n\ntest('preserves valid tool_result and drops orphan tool_result', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'mistral-large-latest',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mistral-large-latest',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Search and then I will interrupt' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'valid_call_1',\n name: 'Search',\n input: { query: 'atuscode' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'valid_call_1',\n content: 'Found it!',\n },\n {\n type: 'tool_result',\n tool_use_id: 'orphan_call_2',\n content: 'Interrupted result',\n },\n {\n role: 'user',\n content: 'What happened?',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n\n // Should have: system, user, assistant (tool_use), tool (valid_call_1), user\n // Should NOT have: tool (orphan_call_2)\n\n const toolMessages = messages.filter(m => m.role === 'tool')\n expect(toolMessages.length).toBe(1)\n expect(toolMessages[0].tool_call_id).toBe('valid_call_1')\n\n const orphanMessage = toolMessages.find(m => m.tool_call_id === 'orphan_call_2')\n expect(orphanMessage).toBeUndefined()\n \n // Actually, the semantic message IS injected here because the user block with orphan \n // tool result is converted to:\n // 1. Tool result (valid_call_1) -> role 'tool'\n // 2. User content (\"What happened?\") -> role 'user'\n // This triggers the tool -> assistant injection.\n const assistantMessages = messages.filter(m => m.role === 'assistant')\n expect(assistantMessages.some(m => m.content === '[Tool results received]')).toBe(true)\n})\n\ntest('drops empty assistant message when only thinking block was present and stripped', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(JSON.stringify({\n id: 'chatcmpl-1',\n object: 'chat.completion',\n created: 123456789,\n model: 'mistral-large-latest',\n choices: [{ message: { role: 'assistant', content: 'hi' }, finish_reason: 'stop' }],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }\n }), { headers: { 'Content-Type': 'application/json' } })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mistral-large-latest',\n messages: [\n { role: 'user', content: 'Initial' },\n { role: 'assistant', content: [{ type: 'thinking', thinking: 'I am thinking...', signature: 'sig' }] },\n { role: 'user', content: 'Interrupting query' },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n // The assistant msg is dropped because thinking is stripped.\n // The two user messages are coalesced.\n expect(messages.length).toBe(1)\n expect(messages[0].role).toBe('user')\n expect(String(messages[0].content)).toContain('Initial')\n expect(String(messages[0].content)).toContain('Interrupting query')\n})\n\ntest('injects semantic assistant message when tool result is followed by user message', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(JSON.stringify({\n id: 'chatcmpl-2',\n object: 'chat.completion',\n created: 123456789,\n model: 'mistral-large-latest',\n choices: [{ message: { role: 'assistant', content: 'hi' }, finish_reason: 'stop' }],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }\n }), { headers: { 'Content-Type': 'application/json' } })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mistral-large-latest',\n messages: [\n { \n role: 'assistant', \n content: [{ type: 'tool_use', id: 'call_1', name: 'search', input: {} }] \n },\n { \n role: 'user', \n content: [\n { type: 'tool_result', tool_use_id: 'call_1', content: 'Result' }\n ] \n },\n { role: 'user', content: 'Next user query' },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n // Roles should be: assistant (tool_calls) -> tool -> assistant (semantic) -> user\n const roles = messages.map(m => m.role)\n expect(roles).toEqual(['assistant', 'tool', 'assistant', 'user'])\n \n const semanticMsg = messages[2]\n expect(semanticMsg.role).toBe('assistant')\n expect(semanticMsg.content).toBe('[Tool results received]')\n expect(semanticMsg.content).not.toContain('interrupted')\n expect(semanticMsg.content).not.toContain('user')\n})\n\ntest('Moonshot: uses max_tokens (not max_completion_tokens) and strips store', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.moonshot.ai/v1'\n process.env.OPENAI_API_KEY = 'sk-moonshot-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-k2.6',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-k2.6',\n system: 'you are kimi',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.max_tokens).toBe(256)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Cerebras: strips unsupported store on chat_completions (#1023)', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.cerebras.ai/v1'\n process.env.OPENAI_API_KEY = 'csk-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'llama3.1-8b',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'llama3.1-8b',\n system: 'you are cerebras',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Local provider (vLLM/Ollama/etc.): strips unsupported store on chat_completions (#672)', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:8000/v1'\n process.env.OPENAI_API_KEY = 'sk-local'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen-3.5-27b',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'qwen-3.5-27b',\n system: 'you are local',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Groq: keeps max_completion_tokens and strips unsupported store', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.groq.com/openai/v1'\n process.env.OPENAI_API_KEY = 'gsk-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'llama-3.3-70b-versatile',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'llama-3.3-70b-versatile',\n system: 'you are groq',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.max_completion_tokens).toBe(256)\n expect(requestBody?.max_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Moonshot: echoes reasoning_content on assistant tool-call messages', async () => {\n // Regression for: \"API Error: 400 {\"error\":{\"message\":\"thinking is enabled\n // but reasoning_content is missing in assistant tool call message at index\n // N\"}}\" when the agent sends a prior-turn assistant response back to Kimi.\n // The thinking block captured from the inbound response must round-trip\n // as reasoning_content on the outgoing echoed assistant message.\n process.env.OPENAI_BASE_URL = 'https://api.moonshot.ai/v1'\n process.env.OPENAI_API_KEY = 'sk-moonshot-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-k2.6',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-k2.6',\n system: 'you are kimi',\n messages: [\n { role: 'user', content: 'check the logs' },\n {\n role: 'assistant',\n content: [\n {\n type: 'thinking',\n thinking: 'Need to inspect logs via Bash; running a cat.',\n },\n { type: 'text', text: \"I'll inspect the logs.\" },\n {\n type: 'tool_use',\n id: 'call_bash_1',\n name: 'Bash',\n input: { command: 'cat /tmp/app.log' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_bash_1',\n content: 'log line 1\\nlog line 2',\n },\n ],\n },\n ],\n max_tokens: 256,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'Need to inspect logs via Bash; running a cat.',\n )\n})\n\ntest('DeepSeek echoes reasoning_content on assistant tool-call messages', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-v4-flash',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-v4-flash',\n system: 'test',\n messages: [\n { role: 'user', content: 'hi' },\n {\n role: 'assistant',\n content: [\n { type: 'thinking', thinking: 'thought' },\n { type: 'text', text: 'hello' },\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_1', content: 'files' },\n ],\n },\n ],\n max_tokens: 32,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe('thought')\n})\n\ntest('generic OpenAI-compatible providers do not echo reasoning_content on assistant tool-call messages', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'sk-openai-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test',\n messages: [\n { role: 'user', content: 'hi' },\n {\n role: 'assistant',\n content: [\n { type: 'thinking', thinking: 'thought' },\n { type: 'text', text: 'hello' },\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_1', content: 'files' },\n ],\n },\n ],\n max_tokens: 32,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBeUndefined()\n})\n\ntest('gateway-routed DeepSeek models inherit descriptor-backed reasoning and token shaping', async () => {\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENAI_API_KEY = 'sk-openrouter-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek/deepseek-reasoner',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n reasoningEffort: 'xhigh',\n }) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek/deepseek-reasoner',\n system: 'test',\n messages: [\n { role: 'user', content: 'hi' },\n {\n role: 'assistant',\n content: [\n { type: 'thinking', thinking: 'thought' },\n { type: 'text', text: 'hello' },\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_1', content: 'files' },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n thinking: { type: 'enabled' },\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n message => message.role === 'assistant' && Array.isArray(message.tool_calls),\n )\n\n expect(assistantWithToolCall?.reasoning_content).toBe('thought')\n expect(requestBody?.thinking).toEqual({ type: 'enabled' })\n expect(requestBody?.reasoning_effort).toBe('max')\n expect(requestBody?.max_tokens).toBe(64)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Moonshot: cn host is also detected', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.moonshot.cn/v1'\n process.env.OPENAI_API_KEY = 'sk-moonshot-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-k2.6',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-k2.6',\n system: 'you are kimi',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Kimi Code endpoint inherits Moonshot max_tokens/store compatibility', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.kimi.com/coding/v1'\n process.env.OPENAI_API_KEY = 'sk-kimi-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-for-coding',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-for-coding',\n system: 'you are kimi code',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.max_tokens).toBe(256)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Kimi Code endpoint echoes reasoning_content on assistant tool-call messages', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.kimi.com/coding/v1'\n process.env.OPENAI_API_KEY = 'sk-kimi-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-for-coding',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-for-coding',\n system: 'you are kimi code',\n messages: [\n { role: 'user', content: 'check the logs' },\n {\n role: 'assistant',\n content: [\n {\n type: 'thinking',\n thinking: 'Need to inspect logs via Bash; running a cat.',\n },\n { type: 'text', text: \"I'll inspect the logs.\" },\n {\n type: 'tool_use',\n id: 'call_bash_1',\n name: 'Bash',\n input: { command: 'cat /tmp/app.log' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_bash_1',\n content: 'log line 1\\nlog line 2',\n },\n ],\n },\n ],\n max_tokens: 256,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'Need to inspect logs via Bash; running a cat.',\n )\n})\n\ntest('DeepSeek sends thinking toggle and normalized reasoning effort', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-v4-pro',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n reasoningEffort: 'xhigh',\n }) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-v4-pro',\n system: 'test',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 64,\n stream: false,\n thinking: { type: 'enabled' },\n })\n\n expect(requestBody?.thinking).toEqual({ type: 'enabled' })\n expect(requestBody?.reasoning_effort).toBe('max')\n expect(requestBody?.max_tokens).toBe(64)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('DeepSeek omits thinking controls when the Anthropic-side request does not set them', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-v4-flash',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-v4-flash',\n system: 'test',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(requestBody?.thinking).toBeUndefined()\n expect(requestBody?.reasoning_effort).toBeUndefined()\n})\n\ntest('DeepSeek forwards an explicit thinking disable toggle for V4 models', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-v4-flash',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-v4-flash',\n system: 'test',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 32,\n stream: false,\n thinking: { type: 'disabled' },\n })\n\n expect(requestBody?.thinking).toEqual({ type: 'disabled' })\n expect(requestBody?.reasoning_effort).toBeUndefined()\n})\n\n\ntest('collapses multiple text blocks in tool_result to string for DeepSeek compatibility (issue #774)', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-reasoner',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'deepseek-reasoner',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Run ls' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_1',\n content: [\n { type: 'text', text: 'line one' },\n { type: 'text', text: 'line two' },\n ],\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const toolMessages = messages.filter(m => m.role === 'tool')\n expect(toolMessages.length).toBe(1)\n expect(toolMessages[0].tool_call_id).toBe('call_1')\n expect(typeof toolMessages[0].content).toBe('string')\n expect(toolMessages[0].content).toBe('line one\\n\\nline two')\n})\n\ntest('collapses multiple text blocks into a single string for DeepSeek compatibility (issue #774)', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-reasoner',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'deepseek-reasoner',\n system: 'test system',\n messages: [\n {\n role: 'user',\n content: [\n { type: 'text', text: 'Hello!' },\n { type: 'text', text: 'How are you?' },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n expect(messages.length).toBe(2) // system + user\n expect(messages[1].role).toBe('user')\n expect(typeof messages[1].content).toBe('string')\n expect(messages[1].content).toBe('Hello!\\n\\nHow are you?')\n})\n\ntest('preserves mixed text and image tool results as multipart content', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Show me' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'cat image.png' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_1',\n content: [\n { type: 'text', text: 'Here is the image:' },\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: 'iVBORw0KGgo=',\n },\n },\n ],\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const toolMessages = messages.filter(m => m.role === 'tool')\n expect(toolMessages.length).toBe(1)\n expect(Array.isArray(toolMessages[0].content)).toBe(true)\n const content = toolMessages[0].content as Array>\n expect(content.length).toBe(2)\n expect(content[0].type).toBe('text')\n expect(content[1].type).toBe('image_url')\n})\n\ntest('Z.AI: uses max_tokens (not max_completion_tokens) and strips store', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.z.ai/api/coding/paas/v4'\n process.env.OPENAI_API_KEY = 'sk-zai-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'GLM-5.1',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'GLM-5.1',\n system: 'you are glm',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.max_tokens).toBe(256)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Z.AI: thinking mode enabled when requested', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.z.ai/api/coding/paas/v4'\n process.env.OPENAI_API_KEY = 'sk-zai-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'GLM-5.1',\n choices: [\n {\n message: {\n role: 'assistant',\n content: null,\n reasoning_content: 'Let me think...',\n },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'GLM-5.1',\n system: 'you are glm',\n messages: [{ role: 'user', content: 'think hard' }],\n max_tokens: 1024,\n stream: false,\n thinking: { type: 'enabled', budget_tokens: 1024 },\n })\n\n expect((requestBody?.thinking as Record)?.type).toBe('enabled')\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.max_tokens).toBe(1024)\n})\n\ntest('strips Anthropic attribution header block from chat-completions system prompt (#607)', async () => {\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: { role: 'assistant', content: 'ok' },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 8, completion_tokens: 3, total_tokens: 11 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: [\n {\n type: 'text',\n text:\n 'x-anthropic-billing-header: cc_version=0.8.0.abc123; ' +\n 'cc_entrypoint=cli;',\n },\n { type: 'text', text: 'You are Claude Code, helpful assistant.' },\n { type: 'text', text: 'Project context: bun + react.' },\n ],\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = capturedBody?.messages as Array<{ role: string; content: string }>\n const sysMsg = messages.find(m => m.role === 'system')\n expect(sysMsg).toBeDefined()\n expect(sysMsg?.content).not.toContain('x-anthropic-billing-header')\n expect(sysMsg?.content).not.toContain('cc_version=')\n expect(sysMsg?.content).toContain('You are Claude Code, helpful assistant.')\n expect(sysMsg?.content).toContain('Project context: bun + react.')\n})\n\ntest('strips Anthropic attribution header block from responses-API instructions (#607)', async () => {\n process.env.OPENAI_API_FORMAT = 'responses'\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'resp-1',\n model: 'gpt-5.4',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n usage: { input_tokens: 8, output_tokens: 3, total_tokens: 11 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n system: [\n {\n type: 'text',\n text: 'x-anthropic-billing-header: cc_version=0.8.0.abc123; cc_entrypoint=cli;',\n },\n { type: 'text', text: 'You are Claude Code.' },\n ],\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n const instructions = capturedBody?.instructions as string\n expect(instructions).not.toContain('x-anthropic-billing-header')\n expect(instructions).not.toContain('cc_version=')\n expect(instructions).toContain('You are Claude Code.')\n})\n\ntest('emits reasoning_effort on chat_completions when reasoningEffort is passed', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'test-key'\n\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-5.4',\n choices: [\n {\n message: { role: 'assistant', content: 'ok' },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n reasoningEffort: 'xhigh',\n }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 16,\n stream: false,\n })\n\n expect(requestBody?.reasoning_effort).toBe('xhigh')\n})\n\ntest('omits reasoning_effort on chat_completions when no override and model has no alias default', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'test-key'\n\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: { role: 'assistant', content: 'ok' },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 16,\n stream: false,\n })\n\n expect(requestBody && 'reasoning_effort' in requestBody).toBe(false)\n})\n\ntest('emits reasoning_effort from codex alias default when no override is passed', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'test-key'\n\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-5.4',\n choices: [\n {\n message: { role: 'assistant', content: 'ok' },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 16,\n stream: false,\n })\n\n expect(requestBody?.reasoning_effort).toBe('high')\n})\n\ntest('DeepSeek: redacted_thinking block preserves continuity with reasoning_content: \"\"', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-chat',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-chat',\n system: 'test',\n messages: [\n { role: 'user', content: 'analyze this' },\n {\n role: 'assistant',\n content: [\n // real redacted_thinking shape: content lives in `.data`, not `.thinking`\n { type: 'redacted_thinking', data: '', signature: 'sig123' },\n { type: 'text', text: 'Analysis complete.' },\n {\n type: 'tool_use',\n id: 'call_redacted_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_redacted_1', content: 'files' },\n ],\n },\n ],\n max_tokens: 32,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n // redacted_thinking is recognized as a thinking block; its .data is \"\" and the\n // message carries a tool_call, so it falls back to reasoning_content: \"\"\n expect(assistantWithToolCall?.reasoning_content).toBe('')\n})\n\ntest('DeepSeek: redacted_thinking block with non-empty data propagates data into reasoning_content', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-2',\n model: 'deepseek-chat',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-chat',\n system: 'test',\n messages: [\n { role: 'user', content: 'analyze this' },\n {\n role: 'assistant',\n content: [\n // real redacted_thinking with content in .data\n {\n type: 'redacted_thinking',\n data: 'encrypted_chain_of_thought_payload_v1',\n signature: 'sig456',\n },\n { type: 'text', text: 'Analysis complete.' },\n {\n type: 'tool_use',\n id: 'call_redacted_2',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_redacted_2', content: 'files' },\n ],\n },\n ],\n max_tokens: 32,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n // The real .data payload must be preserved in reasoning_content — this is the\n // case the original test missed (it used a synthetic .thinking field).\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'encrypted_chain_of_thought_payload_v1',\n )\n})\n\ntest('renders tool_reference blocks as text on the chat/completions path', async () => {\n const { __test } = await import('./openaiShim.ts')\n\n const messages = __test.convertMessages(\n [\n {\n role: 'assistant',\n content: [\n { type: 'tool_use', id: 'call_ts1', name: 'ToolSearch', input: { query: 'memory' } },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_ts1',\n content: [\n { type: 'tool_reference', tool_name: 'mcp__example__memory_search' },\n { type: 'tool_reference', tool_name: 'mcp__example__memory_store' },\n ],\n },\n ],\n },\n ],\n undefined,\n )\n\n const toolMsg = messages.find(m => m.role === 'tool')\n expect(toolMsg).toBeDefined()\n // The rendering contract is plain text: text-only parts collapse to a string.\n expect(typeof toolMsg!.content).toBe('string')\n const content = toolMsg!.content as string\n expect(content).toContain('mcp__example__memory_search')\n expect(content).toContain('mcp__example__memory_store')\n})\n"}, "f463d026161babdfab071ace78f9f740c8896bdb": {"t": 1785765445.2049956, "n": 189335, "h": "514399d9729507c1", "c": "import { afterEach, beforeEach, expect, mock, test } from 'bun:test'\nimport { acquireSharedMutationLock, releaseSharedMutationLock } from '../../test/sharedMutationLock.js'\nimport { asMockFetch } from '../../test/typedMocks.js'\nimport { _clearRegistryForTesting, ensureIntegrationsLoaded, registerGateway } from '../../integrations/index.ts'\nimport { applyProviderFlag } from '../../utils/providerFlag.ts'\nimport { applyProviderProfileToProcessEnv } from '../../utils/providerProfiles.ts'\nimport { createOpenAIShimClient } from './openaiShim.ts'\n\ntype FetchType = typeof globalThis.fetch\n\nconst originalEnv = {\n OPENAI_BASE_URL: process.env.OPENAI_BASE_URL,\n OPENAI_API_BASE: process.env.OPENAI_API_BASE,\n OPENAI_API_KEY: process.env.OPENAI_API_KEY,\n OPENAI_MODEL: process.env.OPENAI_MODEL,\n OPENAI_API_FORMAT: process.env.OPENAI_API_FORMAT,\n OPENAI_AUTH_HEADER: process.env.OPENAI_AUTH_HEADER,\n OPENAI_AUTH_SCHEME: process.env.OPENAI_AUTH_SCHEME,\n OPENAI_AUTH_HEADER_VALUE: process.env.OPENAI_AUTH_HEADER_VALUE,\n CLAUDE_CODE_USE_GITHUB: process.env.CLAUDE_CODE_USE_GITHUB,\n GITHUB_TOKEN: process.env.GITHUB_TOKEN,\n GH_TOKEN: process.env.GH_TOKEN,\n CLAUDE_CODE_USE_OPENAI: process.env.CLAUDE_CODE_USE_OPENAI,\n CLAUDE_CODE_USE_GEMINI: process.env.CLAUDE_CODE_USE_GEMINI,\n GEMINI_API_KEY: process.env.GEMINI_API_KEY,\n GOOGLE_API_KEY: process.env.GOOGLE_API_KEY,\n GEMINI_ACCESS_TOKEN: process.env.GEMINI_ACCESS_TOKEN,\n GEMINI_AUTH_MODE: process.env.GEMINI_AUTH_MODE,\n GEMINI_BASE_URL: process.env.GEMINI_BASE_URL,\n GEMINI_MODEL: process.env.GEMINI_MODEL,\n GOOGLE_CLOUD_PROJECT: process.env.GOOGLE_CLOUD_PROJECT,\n ANTHROPIC_CUSTOM_HEADERS: process.env.ANTHROPIC_CUSTOM_HEADERS,\n NVIDIA_API_KEY: process.env.NVIDIA_API_KEY,\n NVIDIA_NIM: process.env.NVIDIA_NIM,\n MINIMAX_API_KEY: process.env.MINIMAX_API_KEY,\n BNKR_API_KEY: process.env.BNKR_API_KEY,\n BANKR_BASE_URL: process.env.BANKR_BASE_URL,\n BANKR_MODEL: process.env.BANKR_MODEL,\n OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY,\n DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,\n MIMO_API_KEY: process.env.MIMO_API_KEY,\n OPENGATEWAY_API_KEY: process.env.OPENGATEWAY_API_KEY,\n OPENGATEWAY_BASE_URL: process.env.OPENGATEWAY_BASE_URL,\n CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED: process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED,\n CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID: process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID,\n}\n\nconst originalFetch = globalThis.fetch\n\nfunction restoreEnv(key: string, value: string | undefined): void {\n if (value === undefined) {\n delete process.env[key]\n } else {\n process.env[key] = value\n }\n}\n\ntype OpenAIShimClient = {\n beta: {\n messages: {\n create: (\n params: Record,\n options?: Record,\n ) => Promise & {\n withResponse: () => Promise<{ data: AsyncIterable> }>\n }\n }\n }\n}\n\nfunction makeSseResponse(lines: string[]): Response {\n const encoder = new TextEncoder()\n return new Response(\n new ReadableStream({\n start(controller) {\n for (const line of lines) {\n controller.enqueue(encoder.encode(line))\n }\n controller.close()\n },\n }),\n {\n headers: {\n 'Content-Type': 'text/event-stream',\n },\n },\n )\n}\n\nfunction makeStreamChunks(chunks: unknown[]): string[] {\n return [\n ...chunks.map(chunk => `data: ${JSON.stringify(chunk)}\\n\\n`),\n 'data: [DONE]\\n\\n',\n ]\n}\n\nfunction makeChatCompletionResponse(model: string): Response {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-test',\n model,\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n}\n\nasync function captureChatCompletionRequest(\n model = 'mimo-v2.5-pro',\n): Promise<{ authorization: string | null; url: string | null }> {\n let authorization: string | null = null\n let url: string | null = null\n\n globalThis.fetch = (async (input, init) => {\n url = String(input)\n const headers = init?.headers as Record | undefined\n authorization = headers?.Authorization ?? headers?.authorization ?? null\n\n return makeChatCompletionResponse(model)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model,\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n return { authorization, url }\n}\n\nbeforeEach(async () => {\n await acquireSharedMutationLock('openaiShim.test.ts')\n process.env.OPENAI_BASE_URL = 'http://example.test/v1'\n delete process.env.OPENAI_API_BASE\n process.env.OPENAI_API_KEY = 'test-key'\n delete process.env.OPENAI_MODEL\n delete process.env.OPENAI_API_FORMAT\n delete process.env.OPENAI_AUTH_HEADER\n delete process.env.OPENAI_AUTH_SCHEME\n delete process.env.OPENAI_AUTH_HEADER_VALUE\n delete process.env.CLAUDE_CODE_USE_GITHUB\n delete process.env.GITHUB_TOKEN\n delete process.env.GH_TOKEN\n delete process.env.CLAUDE_CODE_USE_OPENAI\n delete process.env.CLAUDE_CODE_USE_GEMINI\n delete process.env.GEMINI_API_KEY\n delete process.env.GOOGLE_API_KEY\n delete process.env.GEMINI_ACCESS_TOKEN\n delete process.env.GEMINI_AUTH_MODE\n delete process.env.GEMINI_BASE_URL\n delete process.env.GEMINI_MODEL\n delete process.env.GOOGLE_CLOUD_PROJECT\n delete process.env.ANTHROPIC_CUSTOM_HEADERS\n delete process.env.NVIDIA_API_KEY\n delete process.env.NVIDIA_NIM\n delete process.env.MINIMAX_API_KEY\n delete process.env.BNKR_API_KEY\n delete process.env.BANKR_BASE_URL\n delete process.env.BANKR_MODEL\n delete process.env.OPENROUTER_API_KEY\n delete process.env.DEEPSEEK_API_KEY\n delete process.env.MIMO_API_KEY\n delete process.env.OPENGATEWAY_API_KEY\n delete process.env.OPENGATEWAY_BASE_URL\n delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED\n delete process.env.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID\n})\n\nafterEach(() => {\n try {\n restoreEnv('OPENAI_BASE_URL', originalEnv.OPENAI_BASE_URL)\n restoreEnv('OPENAI_API_BASE', originalEnv.OPENAI_API_BASE)\n restoreEnv('OPENAI_API_KEY', originalEnv.OPENAI_API_KEY)\n restoreEnv('OPENAI_MODEL', originalEnv.OPENAI_MODEL)\n restoreEnv('OPENAI_API_FORMAT', originalEnv.OPENAI_API_FORMAT)\n restoreEnv('OPENAI_AUTH_HEADER', originalEnv.OPENAI_AUTH_HEADER)\n restoreEnv('OPENAI_AUTH_SCHEME', originalEnv.OPENAI_AUTH_SCHEME)\n restoreEnv('OPENAI_AUTH_HEADER_VALUE', originalEnv.OPENAI_AUTH_HEADER_VALUE)\n restoreEnv('CLAUDE_CODE_USE_GITHUB', originalEnv.CLAUDE_CODE_USE_GITHUB)\n restoreEnv('GITHUB_TOKEN', originalEnv.GITHUB_TOKEN)\n restoreEnv('GH_TOKEN', originalEnv.GH_TOKEN)\n restoreEnv('CLAUDE_CODE_USE_OPENAI', originalEnv.CLAUDE_CODE_USE_OPENAI)\n restoreEnv('CLAUDE_CODE_USE_GEMINI', originalEnv.CLAUDE_CODE_USE_GEMINI)\n restoreEnv('GEMINI_API_KEY', originalEnv.GEMINI_API_KEY)\n restoreEnv('GOOGLE_API_KEY', originalEnv.GOOGLE_API_KEY)\n restoreEnv('GEMINI_ACCESS_TOKEN', originalEnv.GEMINI_ACCESS_TOKEN)\n restoreEnv('GEMINI_AUTH_MODE', originalEnv.GEMINI_AUTH_MODE)\n restoreEnv('GEMINI_BASE_URL', originalEnv.GEMINI_BASE_URL)\n restoreEnv('GEMINI_MODEL', originalEnv.GEMINI_MODEL)\n restoreEnv('GOOGLE_CLOUD_PROJECT', originalEnv.GOOGLE_CLOUD_PROJECT)\n restoreEnv('ANTHROPIC_CUSTOM_HEADERS', originalEnv.ANTHROPIC_CUSTOM_HEADERS)\n restoreEnv('NVIDIA_API_KEY', originalEnv.NVIDIA_API_KEY)\n restoreEnv('NVIDIA_NIM', originalEnv.NVIDIA_NIM)\n restoreEnv('MINIMAX_API_KEY', originalEnv.MINIMAX_API_KEY)\n restoreEnv('BNKR_API_KEY', originalEnv.BNKR_API_KEY)\n restoreEnv('BANKR_BASE_URL', originalEnv.BANKR_BASE_URL)\n restoreEnv('BANKR_MODEL', originalEnv.BANKR_MODEL)\n restoreEnv('OPENROUTER_API_KEY', originalEnv.OPENROUTER_API_KEY)\n restoreEnv('DEEPSEEK_API_KEY', originalEnv.DEEPSEEK_API_KEY)\n restoreEnv('MIMO_API_KEY', originalEnv.MIMO_API_KEY)\n restoreEnv('OPENGATEWAY_API_KEY', originalEnv.OPENGATEWAY_API_KEY)\n restoreEnv('OPENGATEWAY_BASE_URL', originalEnv.OPENGATEWAY_BASE_URL)\n restoreEnv('CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED', originalEnv.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED)\n restoreEnv('CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID', originalEnv.CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED_ID)\n globalThis.fetch = originalFetch\n _clearRegistryForTesting()\n ensureIntegrationsLoaded()\n } finally {\n releaseSharedMutationLock()\n }\n})\n\ntest('strips canonical Anthropic headers from direct shim defaultHeaders', async () => {\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n defaultHeaders: {\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\n 'x-anthropic-additional-protection': 'true',\n 'x-claude-remote-session-id': 'remote-123',\n 'x-app': 'cli',\n 'x-client-app': 'sdk',\n 'x-safe-header': 'keep-me',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('anthropic-version')).toBeNull()\n expect(capturedHeaders?.get('anthropic-beta')).toBeNull()\n expect(capturedHeaders?.get('x-anthropic-additional-protection')).toBeNull()\n expect(capturedHeaders?.get('x-claude-remote-session-id')).toBeNull()\n expect(capturedHeaders?.get('x-app')).toBeNull()\n expect(capturedHeaders?.get('x-client-app')).toBeNull()\n expect(capturedHeaders?.get('x-safe-header')).toBe('keep-me')\n})\n\ntest('uses OpenAI-compatible responses endpoint when OPENAI_API_FORMAT=responses', async () => {\n process.env.OPENAI_API_FORMAT = 'responses'\n let capturedUrl = ''\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (input, init) => {\n capturedUrl = String(input)\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'resp-1',\n model: 'gpt-5.4',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n usage: {\n input_tokens: 8,\n output_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedUrl).toBe('http://example.test/v1/responses')\n expect(capturedBody?.model).toBe('gpt-5.4')\n expect(capturedBody?.instructions).toBe('test system')\n expect(capturedBody?.max_output_tokens).toBe(64)\n expect(capturedBody?.store).toBe(false)\n expect(capturedBody?.input).toEqual([\n {\n type: 'message',\n role: 'user',\n content: [{ type: 'input_text', text: 'hello' }],\n },\n ])\n})\n\ntest('uses OpenAI-compatible responses endpoint with text chunk types when OPENAI_API_FORMAT=responses_compat', async () => {\n process.env.OPENAI_API_FORMAT = 'responses_compat'\n let capturedUrl = ''\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (input, init) => {\n capturedUrl = String(input)\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'resp-1',\n model: 'gpt-5.4',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n usage: {\n input_tokens: 8,\n output_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedUrl).toBe('http://example.test/v1/responses')\n expect(capturedBody?.model).toBe('gpt-5.4')\n expect(capturedBody?.instructions).toBe('test system')\n expect(capturedBody?.max_output_tokens).toBe(64)\n expect(capturedBody?.store).toBe(false)\n expect(capturedBody?.input).toEqual([\n {\n type: 'message',\n role: 'user',\n content: [{ type: 'text', text: 'hello' }],\n },\n ])\n})\n\ntest('uses correct empty input fallback schema for standard responses and responses_compat', async () => {\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n return new Response(JSON.stringify({\n id: 'resp-1',\n model: 'test',\n output: [{ type: 'message', role: 'assistant', content: [{ type: 'output_text', text: 'ok' }] }]\n }), { headers: { 'Content-Type': 'application/json' } })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n process.env.OPENAI_API_FORMAT = 'responses'\n await client.beta.messages.create({\n model: 'test',\n max_tokens: 10,\n messages: [{ role: 'user', content: [] }],\n })\n\n expect(capturedBody?.input).toEqual([\n {\n type: 'message',\n role: 'user',\n content: [{ type: 'input_text', text: '' }],\n },\n ])\n\n process.env.OPENAI_API_FORMAT = 'responses_compat'\n await client.beta.messages.create({\n model: 'test',\n max_tokens: 10,\n messages: [{ role: 'user', content: [] }],\n })\n\n expect(capturedBody?.input).toEqual([\n {\n type: 'message',\n role: 'user',\n content: [{ type: 'text', text: '' }],\n },\n ])\n})\n\ntest('strips store from strict OpenAI-compatible responses providers', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.moonshot.ai/v1'\n process.env.OPENAI_API_FORMAT = 'responses'\n let capturedUrl = ''\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (input, init) => {\n capturedUrl = String(input)\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'resp-1',\n model: 'kimi-k2.5',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'kimi-k2.5',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedUrl).toBe('https://api.moonshot.ai/v1/responses')\n expect(capturedBody?.store).toBeUndefined()\n})\n\ntest('strips store when providerOverride routes chat_completions to the Gemini host', async () => {\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-gemini',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n defaultHeaders: {},\n providerOverride: {\n model: 'gemini-3.1-pro',\n baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',\n apiKey: 'gemini-key',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gemini-3.1-pro',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedBody?.store).toBeUndefined()\n})\n\ntest('strips store when providerOverride routes responses API to the Gemini host', async () => {\n process.env.OPENAI_API_FORMAT = 'responses'\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n return new Response(\n JSON.stringify({\n id: 'resp-gemini',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n defaultHeaders: {},\n providerOverride: {\n model: 'gemini-3.1-pro',\n baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',\n apiKey: 'gemini-key',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gemini-3.1-pro',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedBody?.store).toBeUndefined()\n})\n\ntest('uses custom OpenAI-compatible auth header value when configured', async () => {\n process.env.OPENAI_API_KEY = 'generic-key'\n process.env.OPENAI_AUTH_HEADER = 'api-key'\n process.env.OPENAI_AUTH_HEADER_VALUE = 'hicap-header-value'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('api-key')).toBe('hicap-header-value')\n expect(capturedHeaders?.get('authorization')).toBeNull()\n})\n\ntest('uses Hicap api-key auth header for the Hicap route', async () => {\n process.env.OPENAI_API_KEY = 'hicap-live-key'\n process.env.OPENAI_BASE_URL = 'https://api.hicap.ai/v1'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'claude-opus-4.7',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('api-key')).toBe('hicap-live-key')\n expect(capturedHeaders?.get('authorization')).toBeNull()\n})\n\ntest('defaults Authorization custom auth header to bearer scheme', async () => {\n process.env.OPENAI_API_KEY = 'authorization-key'\n process.env.OPENAI_AUTH_HEADER = 'Authorization'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('authorization')).toBe('Bearer authorization-key')\n})\n\ntest('honors bearer scheme for custom OpenAI-compatible auth headers', async () => {\n process.env.OPENAI_API_KEY = 'custom-key'\n process.env.OPENAI_AUTH_HEADER = 'X-Custom-Authorization'\n process.env.OPENAI_AUTH_SCHEME = 'bearer'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('x-custom-authorization')).toBe('Bearer custom-key')\n expect(capturedHeaders?.get('authorization')).toBeNull()\n})\n\ntest('ignores custom auth header value when no custom header is configured', async () => {\n delete process.env.OPENAI_API_KEY\n process.env.OPENAI_AUTH_HEADER_VALUE = 'gateway-header-value'\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers as HeadersInit)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n choices: [{ message: { role: 'assistant', content: 'ok' } }],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('authorization')).toBeNull()\n})\n\ntest('strips canonical Anthropic headers from per-request shim headers too', async () => {\n let capturedHeaders: Headers | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'gpt-4o',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n },\n {\n headers: {\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\n 'x-safe-header': 'keep-me',\n },\n },\n )\n\n expect(capturedHeaders?.get('anthropic-version')).toBeNull()\n expect(capturedHeaders?.get('anthropic-beta')).toBeNull()\n expect(capturedHeaders?.get('x-safe-header')).toBe('keep-me')\n})\n\ntest('applies descriptor static headers before client and request headers', async () => {\n let capturedHeaders: Headers | undefined\n\n registerGateway({\n id: 'shim-header-test',\n label: 'Shim Header Test',\n category: 'hosted',\n defaultBaseUrl: 'https://shim-header-test.example/v1',\n defaultModel: 'shim-test-model',\n setup: {\n requiresAuth: true,\n authMode: 'api-key',\n credentialEnvVars: ['OPENAI_API_KEY'],\n },\n transportConfig: {\n kind: 'openai-compatible',\n openaiShim: {\n headers: {\n 'x-static-header': 'from-descriptor',\n 'x-override-header': 'from-descriptor',\n },\n },\n },\n })\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://shim-header-test.example/v1'\n process.env.OPENAI_MODEL = 'shim-test-model'\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'shim-test-model',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n defaultHeaders: {\n 'x-override-header': 'from-client',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'shim-test-model',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n },\n {\n headers: {\n 'x-override-header': 'from-request',\n },\n },\n )\n\n expect(capturedHeaders?.get('x-static-header')).toBe('from-descriptor')\n expect(capturedHeaders?.get('x-override-header')).toBe('from-request')\n})\n\ntest('opengateway sends Accept-Encoding: identity header on chat requests', async () => {\n let capturedHeaders: Headers | undefined\n\n registerGateway({\n id: 'gitlawb-opengateway-test',\n label: 'Gitlawb Opengateway',\n category: 'aggregating',\n defaultBaseUrl: 'https://opengateway.gitlawb.com/v1/xiaomi-mimo',\n defaultModel: 'mimo-v2.5-pro',\n setup: {\n requiresAuth: false,\n authMode: 'none',\n },\n transportConfig: {\n kind: 'openai-compatible',\n openaiShim: {\n headers: {\n 'Accept-Encoding': 'identity',\n },\n defaultAuthHeader: {\n name: 'api-key',\n scheme: 'raw',\n },\n preserveReasoningContent: true,\n requireReasoningContentOnAssistantMessages: true,\n reasoningContentFallback: '',\n maxTokensField: 'max_completion_tokens',\n supportsApiFormatSelection: false,\n supportsAuthHeaders: false,\n },\n },\n })\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://opengateway.gitlawb.com/v1/xiaomi-mimo'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 3,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'mimo-v2.5-pro',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n },\n {},\n )\n\n expect(capturedHeaders?.get('Accept-Encoding')).toBe('identity')\n})\n\ntest('strips Anthropic-specific headers on GitHub Codex transport requests', async () => {\n let capturedHeaders: Headers | undefined\n\n process.env.CLAUDE_CODE_USE_GITHUB = '1'\n process.env.OPENAI_API_KEY = 'github-test-key'\n delete process.env.OPENAI_BASE_URL\n delete process.env.OPENAI_MODEL\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response('', {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n },\n })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'github:gpt-5-codex',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: true,\n },\n {\n headers: {\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'prompt-caching-2024-07-31',\n 'x-anthropic-additional-protection': 'true',\n 'x-safe-header': 'keep-me',\n },\n },\n )\n\n expect(capturedHeaders?.get('anthropic-version')).toBeNull()\n expect(capturedHeaders?.get('anthropic-beta')).toBeNull()\n expect(capturedHeaders?.get('x-anthropic-additional-protection')).toBeNull()\n expect(capturedHeaders?.get('x-safe-header')).toBe('keep-me')\n expect(capturedHeaders?.get('authorization')).toBe('Bearer github-test-key')\n expect(capturedHeaders?.get('editor-plugin-version')).toBe('copilot-chat/0.26.7')\n})\n\ntest('strips Anthropic-specific headers on GitHub Codex transport with providerOverride API key', async () => {\n let capturedHeaders: Headers | undefined\n\n process.env.CLAUDE_CODE_USE_GITHUB = '1'\n process.env.OPENAI_API_KEY = 'env-should-not-win'\n delete process.env.OPENAI_BASE_URL\n delete process.env.OPENAI_MODEL\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response('', {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n },\n })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n providerOverride: {\n model: 'github:gpt-5-codex',\n baseURL: 'https://api.githubcopilot.com',\n apiKey: 'provider-override-key',\n },\n }) as OpenAIShimClient\n\n await client.beta.messages.create(\n {\n model: 'ignored',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: true,\n },\n {\n headers: {\n 'anthropic-version': '2023-06-01',\n 'x-claude-remote-session-id': 'remote-123',\n 'x-safe-header': 'keep-me',\n },\n },\n )\n\n expect(capturedHeaders?.get('anthropic-version')).toBeNull()\n expect(capturedHeaders?.get('x-claude-remote-session-id')).toBeNull()\n expect(capturedHeaders?.get('x-safe-header')).toBe('keep-me')\n expect(capturedHeaders?.get('authorization')).toBe('Bearer provider-override-key')\n expect(capturedHeaders?.get('editor-plugin-version')).toBe('copilot-chat/0.26.7')\n})\n\ntest('preserves usage from final OpenAI stream chunk with empty choices', async () => {\n globalThis.fetch = (async (_input, init) => {\n const url = typeof _input === 'string' ? _input : _input.url\n expect(url).toBe('http://example.test/v1/chat/completions')\n\n const body = JSON.parse(String(init?.body))\n expect(body.stream).toBe(true)\n expect(body.stream_options).toEqual({ include_usage: true })\n\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'fake-model',\n choices: [\n {\n index: 0,\n delta: { role: 'assistant', content: 'hello world' },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'fake-model',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'fake-model',\n choices: [],\n usage: {\n prompt_tokens: 123,\n completion_tokens: 45,\n total_tokens: 168,\n },\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'fake-model',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const usageEvent = events.find(\n event => event.type === 'message_delta' && typeof event.usage === 'object' && event.usage !== null,\n ) as { usage?: { input_tokens?: number; output_tokens?: number } } | undefined\n\n expect(usageEvent).toBeDefined()\n expect(usageEvent?.usage?.input_tokens).toBe(123)\n expect(usageEvent?.usage?.output_tokens).toBe(45)\n})\n\ntest('uses max_tokens instead of max_completion_tokens for local providers', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n globalThis.fetch = (async (_input, init) => {\n const body = JSON.parse(String(init?.body))\n expect(body.max_tokens).toBe(64)\n expect(body.max_completion_tokens).toBeUndefined()\n expect(body.stream_options).toBeUndefined()\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'llama3.1:8b',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'hello',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 5,\n completion_tokens: 1,\n total_tokens: 6,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'llama3.1:8b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n})\n\ntest('keeps max_completion_tokens for non-local non-github providers', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n\n globalThis.fetch = (async (_input, init) => {\n const body = JSON.parse(String(init?.body))\n expect(body.max_completion_tokens).toBe(64)\n expect(body.max_tokens).toBeUndefined()\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'hello',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 5,\n completion_tokens: 1,\n total_tokens: 6,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n})\n\ntest('uses route-specific credential env vars for descriptor-backed openai-compatible routes', async () => {\n let capturedHeaders: Headers | undefined\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENROUTER_API_KEY = 'or-route-key'\n delete process.env.OPENAI_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = new Headers(init?.headers)\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'openai/gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 5,\n completion_tokens: 1,\n total_tokens: 6,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'openai/gpt-5-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedHeaders?.get('authorization')).toBe('Bearer or-route-key')\n})\n\ntest('preserves Gemini tool call extra_content in follow-up requests', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Use Bash' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'pwd' },\n extra_content: {\n google: {\n thought_signature: 'sig-123',\n },\n },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_1',\n content: 'D:\\\\repo',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n ) as { tool_calls?: Array> } | undefined\n\n expect(assistantWithToolCall?.tool_calls?.[0]).toMatchObject({\n id: 'call_1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: JSON.stringify({ command: 'pwd' }),\n },\n extra_content: {\n google: {\n thought_signature: 'sig-123',\n },\n },\n })\n})\n\ntest('replays Gemini tool signatures for OpenGateway Gemini models', async () => {\n process.env.OPENAI_BASE_URL = 'https://opengateway.gitlawb.com/v1'\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [\n { role: 'user', content: 'Use Write' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Write',\n input: { file_path: 'todo.md', content: 'todo' },\n signature: 'sig-opengateway',\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_1',\n content: 'created',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n ) as { tool_calls?: Array> } | undefined\n\n expect(assistantWithToolCall?.tool_calls?.[0]).toMatchObject({\n id: 'call_1',\n extra_content: {\n google: {\n thought_signature: 'sig-opengateway',\n },\n },\n })\n})\n\ntest('OpenGateway MiMo replays real reasoning_content without adding empty fallback', async () => {\n process.env.OPENAI_BASE_URL = 'https://opengateway.gitlawb.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-opengateway-mimo',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [\n { role: 'user', content: 'Use an agent' },\n {\n role: 'assistant',\n content: [\n {\n type: 'thinking',\n thinking: 'Need to inspect code with an agent.',\n },\n {\n type: 'tool_use',\n id: 'call_agent_1',\n name: 'Agent',\n input: {\n description: 'Inspect code',\n prompt: 'Look at the relevant code',\n subagent_type: 'general-purpose',\n },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_agent_1',\n content: 'Agent finished',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n )\n\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'Need to inspect code with an agent.',\n )\n expect(requestBody).not.toHaveProperty('store')\n})\n\ntest('Xiaomi MiMo replays real reasoning_content without adding empty fallback', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.xiaomimimo.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n process.env.MIMO_API_KEY = 'mimo-test-key'\n delete process.env.OPENAI_API_KEY\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-mimo',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [\n { role: 'user', content: 'Use an agent' },\n {\n role: 'assistant',\n content: [\n {\n type: 'thinking',\n thinking: 'Need to inspect code with an agent.',\n },\n {\n type: 'tool_use',\n id: 'call_agent_1',\n name: 'Agent',\n input: {\n description: 'Inspect code',\n prompt: 'Look at the relevant code',\n subagent_type: 'general-purpose',\n },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_agent_1',\n content: 'Agent finished',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n )\n\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'Need to inspect code with an agent.',\n )\n expect(requestBody).not.toHaveProperty('store')\n})\n\ntest('OpenGateway MiMo does not synthesize empty reasoning_content when missing', async () => {\n process.env.OPENAI_BASE_URL = 'https://opengateway.gitlawb.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-opengateway-mimo',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [\n { role: 'user', content: 'Use an agent' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_agent_1',\n name: 'Agent',\n input: {\n description: 'Inspect code',\n prompt: 'Look at the relevant code',\n subagent_type: 'general-purpose',\n },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_agent_1',\n content: 'Agent finished',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantWithToolCall = (requestBody?.messages as Array>).find(\n message => Array.isArray(message.tool_calls),\n )\n\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall).not.toHaveProperty('reasoning_content')\n expect(requestBody).not.toHaveProperty('store')\n})\n\ntest('strips unsupported stream_options for Xiaomi MiMo streams', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.xiaomimimo.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n process.env.MIMO_API_KEY = 'mimo-test-key'\n delete process.env.OPENAI_API_KEY\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return makeSseResponse(\n makeStreamChunks([\n {\n id: 'chatcmpl-mimo',\n object: 'chat.completion.chunk',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n index: 0,\n delta: { role: 'assistant', content: 'done' },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-mimo',\n object: 'chat.completion.chunk',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ]),\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: true,\n })\n\n expect(requestBody).toMatchObject({\n stream: true,\n max_completion_tokens: 64,\n })\n expect(requestBody).not.toHaveProperty('stream_options')\n expect(requestBody).not.toHaveProperty('store')\n})\n\ntest('preserves Grep tool pattern field in OpenAI-compatible schemas', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-grep-schema',\n model: 'qwen/qwen3.6-plus',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'qwen/qwen3.6-plus',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Grep' }],\n tools: [\n {\n name: 'Grep',\n description: 'Search file contents',\n input_schema: {\n type: 'object',\n properties: {\n pattern: { type: 'string', description: 'Search pattern' },\n path: { type: 'string' },\n },\n required: ['pattern'],\n additionalProperties: false,\n },\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const tools = requestBody?.tools as Array> | undefined\n const grepTool = tools?.find(tool => (tool.function as Record)?.name === 'Grep') as\n | { function?: { parameters?: { properties?: Record; required?: string[] } } }\n | undefined\n\n expect(Object.keys(grepTool?.function?.parameters?.properties ?? {})).toContain('pattern')\n expect(grepTool?.function?.parameters?.required).toContain('pattern')\n})\n\ntest('does not infer Gemini mode from OPENAI_BASE_URL path substrings', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.OPENAI_BASE_URL =\n 'https://evil.example/generativelanguage.googleapis.com/v1beta/openai'\n delete process.env.OPENAI_API_KEY\n process.env.GEMINI_API_KEY = 'gemini-secret'\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'fake-model',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'fake-model',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(capturedAuthorization).toBeNull()\n})\n\ntest('preserves image tool results as placeholders in follow-up requests', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen/qwen3.6-plus',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'qwen/qwen3.6-plus',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Read this screenshot' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_image_1',\n name: 'Read',\n input: { file_path: 'C:\\\\temp\\\\screenshot.png' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_image_1',\n content: [\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: 'ZmFrZQ==',\n },\n },\n ],\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const toolMessage = (requestBody?.messages as Array>).find(\n message => message.role === 'tool',\n ) as {\n content?: Array<{\n type: string\n text?: string\n image_url?: { url: string }\n }> | string\n } | undefined\n\n expect(Array.isArray(toolMessage?.content)).toBe(true)\n const parts = toolMessage?.content as Array<{\n type: string\n text?: string\n image_url?: { url: string }\n }>\n expect(parts).toEqual([\n {\n type: 'image_url',\n image_url: { url: 'data:image/png;base64,ZmFrZQ==' },\n },\n ])\n})\n\ntest('adds text part for image-only user messages', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n system: 'test system',\n messages: [\n {\n role: 'user',\n content: [\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: 'ZmFrZQ==',\n },\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const userMessage = (requestBody?.messages as Array>).find(\n message => message.role === 'user',\n ) as {\n content?: Array<{\n type: string\n text?: string\n image_url?: { url: string }\n }>\n } | undefined\n\n expect(userMessage?.content).toEqual([\n { type: 'text', text: 'Image attached.' },\n {\n type: 'image_url',\n image_url: { url: 'data:image/png;base64,ZmFrZQ==' },\n },\n ])\n})\n\ntest('preserves mixed text and image tool results as multipart content', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Read this screenshot' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_image_2',\n name: 'Read',\n input: { file_path: 'C:\\\\temp\\\\screenshot.png' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_image_2',\n content: [\n { type: 'text', text: 'Screenshot captured' },\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: 'ZmFrZQ==',\n },\n },\n ],\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const toolMessage = (requestBody?.messages as Array>).find(\n message => message.role === 'tool',\n ) as {\n content?: Array<{\n type: string\n text?: string\n image_url?: { url: string }\n }>\n } | undefined\n\n expect(Array.isArray(toolMessage?.content)).toBe(true)\n const parts = toolMessage?.content ?? []\n expect(parts[0]).toEqual({ type: 'text', text: 'Screenshot captured' })\n expect(parts[1]).toEqual({\n type: 'image_url',\n image_url: { url: 'data:image/png;base64,ZmFrZQ==' },\n })\n})\n\ntest('uses GEMINI_ACCESS_TOKEN for Gemini OpenAI-compatible requests', async () => {\n let capturedAuthorization: string | null = null\n let capturedProject: string | null = null\n let requestUrl: string | undefined\n\n process.env.CLAUDE_CODE_USE_GEMINI = '1'\n process.env.GEMINI_AUTH_MODE = 'access-token'\n process.env.GEMINI_ACCESS_TOKEN = 'gemini-access-token'\n process.env.GOOGLE_CLOUD_PROJECT = 'gemini-project'\n process.env.GEMINI_BASE_URL =\n 'https://generativelanguage.googleapis.com/v1beta/openai'\n process.env.GEMINI_MODEL = 'gemini-2.0-flash'\n delete process.env.OPENAI_BASE_URL\n delete process.env.OPENAI_API_KEY\n delete process.env.GEMINI_API_KEY\n delete process.env.GOOGLE_API_KEY\n\n globalThis.fetch = (async (input, init) => {\n requestUrl = typeof input === 'string' ? input : input.url\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n capturedProject =\n headers?.['x-goog-user-project'] ??\n headers?.['X-Goog-User-Project'] ??\n null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-gemini',\n model: 'gemini-2.0-flash',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 3,\n completion_tokens: 1,\n total_tokens: 4,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gemini-2.0-flash',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(requestUrl).toBe(\n 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions',\n )\n // Explicit type argument: TS narrows the closure-assigned variables to\n // their `null` initializer at this point (microsoft/TypeScript#9998).\n expect(capturedAuthorization).toBe('Bearer gemini-access-token')\n expect(capturedProject).toBe('gemini-project')\n})\n\ntest('uses NVIDIA_API_KEY for NVIDIA NIM requests without OPENAI_API_KEY', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.NVIDIA_NIM = '1'\n process.env.OPENAI_BASE_URL = 'https://integrate.api.nvidia.com/v1'\n process.env.OPENAI_MODEL = 'nvidia/llama-3.1-nemotron-70b-instruct'\n process.env.NVIDIA_API_KEY = 'nvidia-live-key'\n delete process.env.OPENAI_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-nvidia',\n model: 'nvidia/llama-3.1-nemotron-70b-instruct',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 3,\n completion_tokens: 1,\n total_tokens: 4,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'nvidia/llama-3.1-nemotron-70b-instruct',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedAuthorization).toBe('Bearer nvidia-live-key')\n})\n\ntest('does not use stale NVIDIA_API_KEY for non-NVIDIA OpenAI-compatible routes', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.NVIDIA_NIM = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENAI_MODEL = 'openai/gpt-5-mini'\n process.env.NVIDIA_API_KEY = 'nvidia-live-key'\n delete process.env.OPENAI_API_KEY\n delete process.env.OPENROUTER_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-openrouter',\n model: 'openai/gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'openai/gpt-5-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedAuthorization).toBeNull()\n})\n\ntest('does not use MINIMAX_API_KEY for non-MiniMax OpenAI-compatible routes', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENAI_MODEL = 'openai/gpt-5-mini'\n process.env.MINIMAX_API_KEY = 'minimax-live-key'\n delete process.env.OPENAI_API_KEY\n delete process.env.OPENROUTER_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-openrouter',\n model: 'openai/gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'openai/gpt-5-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedAuthorization).toBeNull()\n})\n\ntest('xiaomi mimo route uses api-key auth header and max_completion_tokens', async () => {\n let capturedHeaders: Record | undefined\n let capturedBody: Record | undefined\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://api.xiaomimimo.com/v1'\n process.env.OPENAI_MODEL = 'mimo-v2.5-pro'\n process.env.MIMO_API_KEY = 'mimo-live-key'\n delete process.env.OPENAI_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n capturedHeaders = init?.headers as Record\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-mimo',\n model: 'mimo-v2.5-pro',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mimo-v2.5-pro',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedHeaders).toMatchObject({ 'api-key': 'mimo-live-key' })\n expect(capturedHeaders).not.toHaveProperty('Authorization')\n expect(capturedBody).toMatchObject({ max_completion_tokens: 32 })\n expect(capturedBody).not.toHaveProperty('max_tokens')\n})\n\ntest('gitlawb opengateway provider flag sends OPENGATEWAY_API_KEY as bearer auth despite stale generic base URL', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_MODEL = 'gpt-5.5'\n process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'\n delete process.env.OPENAI_API_KEY\n\n const result = applyProviderFlag('gitlawb-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('https://opengateway.gitlawb.com/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('gitlawb opengateway provider flag accepts OPENAI_API_KEY compatibility fallback', async () => {\n delete process.env.OPENAI_BASE_URL\n delete process.env.OPENGATEWAY_API_KEY\n process.env.OPENAI_API_KEY = 'fake-openai-fallback'\n\n const result = applyProviderFlag('gitlawb-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.authorization).toBe('Bearer fake-openai-fallback')\n})\n\ntest('gitlawb opengateway provider flag sends OPENAI_API_KEY fallback despite stale generic base URL', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'fake-openai-fallback'\n delete process.env.OPENGATEWAY_API_KEY\n\n const result = applyProviderFlag('gitlawb-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('https://opengateway.gitlawb.com/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-openai-fallback')\n})\n\ntest('gitlawb opengateway provider flag trims OPENGATEWAY_API_KEY before bearer auth', async () => {\n process.env.OPENGATEWAY_API_KEY = ' fake-ogw-key '\n delete process.env.OPENAI_API_KEY\n\n const result = applyProviderFlag('gitlawb-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('gitlawb opengateway provider flag ignores blank OPENGATEWAY_API_KEY and uses OPENAI_API_KEY fallback', async () => {\n process.env.OPENGATEWAY_API_KEY = ' '\n process.env.OPENAI_API_KEY = 'fake-openai-fallback'\n\n const result = applyProviderFlag('gitlawb-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.authorization).toBe('Bearer fake-openai-fallback')\n})\n\ntest('gitlawb opengateway provider flag sends OPENGATEWAY_API_KEY to OPENGATEWAY_BASE_URL override', async () => {\n process.env.OPENGATEWAY_BASE_URL = 'http://localhost:8181/v1'\n process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'\n delete process.env.OPENAI_API_KEY\n\n const result = applyProviderFlag('gitlawb-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('http://localhost:8181/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('gitlawb opengateway provider flag sends OPENGATEWAY_API_KEY to custom OPENAI_BASE_URL fallback', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:8181/v1'\n process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'\n delete process.env.OPENGATEWAY_BASE_URL\n delete process.env.OPENAI_API_KEY\n\n const result = applyProviderFlag('gitlawb-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('http://localhost:8181/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('gitlawb opengateway provider flag prefers OPENGATEWAY_API_KEY over generic OPENAI_API_KEY for custom base URL', async () => {\n process.env.OPENGATEWAY_BASE_URL = 'http://localhost:8181/v1'\n process.env.OPENGATEWAY_API_KEY = 'fake-ogw-key'\n process.env.OPENAI_API_KEY = 'fake-generic-openai-key'\n\n const result = applyProviderFlag('gitlawb-opengateway', [])\n expect(result.error).toBeUndefined()\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.url).toBe('http://localhost:8181/v1/chat/completions')\n expect(captured.authorization).toBe('Bearer fake-ogw-key')\n})\n\ntest('gitlawb opengateway stored provider profile key becomes bearer auth', async () => {\n delete process.env.OPENAI_API_KEY\n delete process.env.OPENGATEWAY_API_KEY\n\n applyProviderProfileToProcessEnv({\n id: 'stored-opengateway',\n provider: 'gitlawb-opengateway',\n name: 'Gitlawb Opengateway',\n baseUrl: 'https://opengateway.gitlawb.com/v1',\n model: 'mimo-v2.5-pro',\n apiKey: 'fake-profile-key',\n })\n\n const captured = await captureChatCompletionRequest()\n\n expect(captured.authorization).toBe('Bearer fake-profile-key')\n})\n\ntest('openai route still sends OPENAI_API_KEY as bearer auth', async () => {\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_MODEL = 'gpt-5.5'\n process.env.OPENAI_API_KEY = 'fake-openai-key'\n delete process.env.OPENGATEWAY_API_KEY\n\n const captured = await captureChatCompletionRequest('gpt-5.5')\n\n expect(captured.authorization).toBe('Bearer fake-openai-key')\n})\n\ntest('does not use BNKR_API_KEY for non-Bankr OpenAI-compatible routes', async () => {\n let capturedAuthorization: string | null = null\n\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENAI_MODEL = 'openai/gpt-5-mini'\n process.env.BNKR_API_KEY = 'bankr-live-key'\n delete process.env.OPENAI_API_KEY\n delete process.env.OPENROUTER_API_KEY\n\n globalThis.fetch = (async (_input, init) => {\n const headers = init?.headers as Record | undefined\n capturedAuthorization =\n headers?.Authorization ?? headers?.authorization ?? null\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-openrouter',\n model: 'openai/gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'openai/gpt-5-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(capturedAuthorization).toBeNull()\n})\n\ntest('preserves Gemini tool call extra_content from streaming chunks', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n extra_content: {\n google: {\n thought_signature: 'sig-stream',\n },\n },\n function: {\n name: 'Bash',\n arguments: '{\"command\":\"pwd\"}',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const toolStart = events.find(\n event =>\n event.type === 'content_block_start' &&\n typeof event.content_block === 'object' &&\n event.content_block !== null &&\n (event.content_block as Record).type === 'tool_use',\n ) as { content_block?: Record } | undefined\n\n expect(toolStart?.content_block).toMatchObject({\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n extra_content: {\n google: {\n thought_signature: 'sig-stream',\n },\n },\n })\n})\n\ntest('preserves Gemini thought signature from streaming delta extra_content', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n extra_content: {\n google: {\n thought_signature: 'sig-delta',\n },\n },\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Write',\n arguments: '{\"file_path\":\"todo.md\",\"content\":\"todo\"}',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [{ role: 'user', content: 'Use Write' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const toolStart = events.find(\n event =>\n event.type === 'content_block_start' &&\n typeof event.content_block === 'object' &&\n event.content_block !== null &&\n (event.content_block as Record).type === 'tool_use',\n ) as { content_block?: Record } | undefined\n\n expect(toolStart?.content_block).toMatchObject({\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Write',\n extra_content: {\n google: {\n thought_signature: 'sig-delta',\n },\n },\n signature: 'sig-delta',\n })\n})\n\ntest('preserves Gemini thought signature from non-streaming message extra_content', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n message: {\n role: 'assistant',\n extra_content: {\n google: {\n thought_signature: 'sig-message',\n },\n },\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Write',\n arguments: '{\"file_path\":\"todo.md\",\"content\":\"todo\"}',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [{ role: 'user', content: 'Use Write' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content?.[0]).toMatchObject({\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Write',\n extra_content: {\n google: {\n thought_signature: 'sig-message',\n },\n },\n signature: 'sig-message',\n })\n})\n\ntest('converts Gemini raw tool-call text into streaming tool_use blocks', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-raw-tool',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n content: 'Tool calls',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-raw-tool',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {\n content:\n ' requested:\\n- Write({\"file_path\":\"style.css\",\"content\":\"ul { padding: 0; }\"}) [id: call79435b5a26564619b0151197]',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-raw-tool',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [{ role: 'user', content: 'Write CSS' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n expect(\n events.some(\n event =>\n event.type === 'content_block_start' &&\n (event.content_block as Record | undefined)?.type ===\n 'text',\n ),\n ).toBe(false)\n\n const toolStart = events.find(\n event =>\n event.type === 'content_block_start' &&\n (event.content_block as Record | undefined)?.type ===\n 'tool_use',\n ) as { content_block?: Record } | undefined\n expect(toolStart?.content_block).toMatchObject({\n type: 'tool_use',\n id: 'call79435b5a26564619b0151197',\n name: 'Write',\n })\n\n const toolInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n (event.delta as Record | undefined)?.type ===\n 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n expect(JSON.parse(toolInput)).toEqual({\n file_path: 'style.css',\n content: 'ul { padding: 0; }',\n })\n\n const stop = events.find(event => event.type === 'message_delta') as\n | { delta?: Record }\n | undefined\n expect(stop?.delta?.stop_reason).toBe('tool_use')\n})\n\ntest('converts Gemini raw tool-call text into non-streaming tool_use blocks', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-raw-tool',\n model: 'google/gemini-3.1-flash-lite',\n choices: [\n {\n message: {\n role: 'assistant',\n content:\n 'Tool calls requested:\\n- Agent({\"description\":\"Verify the todo list application functionality.\",\"prompt\":\"Check files.\",\"subagent_type\":\"verification\"}) [id: call9a8b7c6d5e4f3a2b1c0d9e8f]',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-flash-lite',\n messages: [{ role: 'user', content: 'Verify' }],\n max_tokens: 64,\n stream: false,\n }) as {\n stop_reason?: string\n content?: Array>\n }\n\n expect(message.stop_reason).toBe('tool_use')\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'call9a8b7c6d5e4f3a2b1c0d9e8f',\n name: 'Agent',\n input: {\n description: 'Verify the todo list application functionality.',\n prompt: 'Check files.',\n subagent_type: 'verification',\n },\n },\n ])\n})\n\ntest('normalizes plain string Bash tool arguments from OpenAI-compatible responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: false,\n }) as {\n stop_reason?: string\n content?: Array>\n }\n\n expect(message.stop_reason).toBe('tool_use')\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n input: { command: 'pwd' },\n },\n ])\n})\n\ntest('normalizes Bash tool arguments that are valid JSON strings', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '\"pwd\"',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n input: { command: 'pwd' },\n },\n ])\n})\n\ntest.each([\n ['false', false],\n ['null', null],\n ['[]', []],\n])(\n 'preserves malformed Bash JSON literals as parsed values in non-streaming responses: %s',\n async (argumentsValue, expectedInput) => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: argumentsValue,\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n input: expectedInput,\n },\n ])\n },\n)\n\ntest('keeps terminal empty Bash tool arguments invalid in non-streaming responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'Bash',\n input: {},\n },\n ])\n})\n\ntest('normalizes plain string Bash tool arguments in streaming responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"pwd\"}')\n})\n\ntest('normalizes plain string Bash tool arguments when streaming starts with an empty chunk', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: 0,\n type: 'function',\n function: {\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"pwd\"}')\n})\n\ntest('normalizes plain string Bash tool arguments when streaming starts with whitespace', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: ' ',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: 0,\n type: 'function',\n function: {\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\" pwd\"}')\n})\n\ntest('keeps terminal whitespace-only Bash arguments invalid in streaming responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: ' ',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{}')\n})\n\ntest('normalizes streaming Bash arguments that begin with bracket syntax', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '[ -f package.json ] && pwd',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"[ -f package.json ] && pwd\"}')\n})\n\ntest('normalizes streaming Bash arguments when the first chunk is only an opening brace', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '{',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: 0,\n type: 'function',\n function: {\n arguments: ' pwd; }',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"{ pwd; }\"}')\n})\n\ntest('repairs truncated structured Bash JSON in streaming responses', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '{\"command\":\"pwd\"',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const normalizedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(normalizedInput).toBe('{\"command\":\"pwd\"}')\n})\n\ntest('does not normalize incomplete streamed Bash commands when finish_reason is length', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: 'rg --fi',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'length',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const streamedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(streamedInput).toBe('rg --fi')\n})\n\ntest('repairs truncated JSON objects even without command field', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n tool_calls: [\n {\n index: 0,\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '{\"cwd\":\"/tmp\"',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use Bash' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const streamedInput = events\n .filter(\n event =>\n event.type === 'content_block_delta' &&\n typeof event.delta === 'object' &&\n event.delta !== null &&\n (event.delta as Record).type === 'input_json_delta',\n )\n .map(event => (event.delta as Record).partial_json)\n .join('')\n\n expect(streamedInput).toBe('{\"cwd\":\"/tmp\"}')\n})\n\ntest('preserves raw input for unknown plain string tool arguments', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'UnknownTool',\n arguments: 'pwd',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use tool' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'UnknownTool',\n input: {},\n },\n ])\n})\n\ntest('preserves parsed string input for unknown JSON string tool arguments', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'google/gemini-3.1-pro-preview',\n choices: [\n {\n message: {\n role: 'assistant',\n tool_calls: [\n {\n id: 'function-call-1',\n type: 'function',\n function: {\n name: 'UnknownTool',\n arguments: '\"pwd\"',\n },\n },\n ],\n },\n finish_reason: 'tool_calls',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const message = await client.beta.messages.create({\n model: 'google/gemini-3.1-pro-preview',\n system: 'test system',\n messages: [{ role: 'user', content: 'Use tool' }],\n max_tokens: 64,\n stream: false,\n }) as {\n content?: Array>\n }\n\n expect(message.content).toEqual([\n {\n type: 'tool_use',\n id: 'function-call-1',\n name: 'UnknownTool',\n input: 'pwd',\n },\n ])\n})\n\ntest('sanitizes malformed MCP tool schemas before sending them to OpenAI', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'ok',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 1,\n total_tokens: 11,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n tools: [\n {\n name: 'mcp__clientry__create_task',\n description: 'Create a task',\n input_schema: {\n type: 'object',\n properties: {\n priority: {\n type: 'integer',\n description: 'Priority: 0=low, 1=medium, 2=high, 3=urgent',\n default: true,\n enum: [false, 0, 1, 2, 3],\n },\n },\n },\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const parameters = (\n requestBody?.tools as Array<{ function?: { parameters?: Record } }>\n )?.[0]?.function?.parameters\n const properties = parameters?.properties as\n | Record\n | undefined\n\n expect(parameters?.additionalProperties).toBe(false)\n // No required[] in the original schema → none added (optional properties must not be forced required)\n expect(parameters?.required).toEqual([])\n expect(properties?.priority?.type).toBe('integer')\n expect(properties?.priority?.enum).toEqual([0, 1, 2, 3])\n expect(properties?.priority).not.toHaveProperty('default')\n})\n\ntest('optional tool properties are not added to required[] — fixes Groq/Azure 400 tool_use_failed', async () => {\n // Regression test for: all optional properties being sent as required in strict mode,\n // causing providers like Groq to reject valid tool calls where the model omits optional args.\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-4',\n model: 'gpt-4o',\n choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }],\n usage: { prompt_tokens: 5, completion_tokens: 2, total_tokens: 7 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'read a file' }],\n tools: [\n {\n name: 'Read',\n description: 'Read a file',\n input_schema: {\n type: 'object',\n properties: {\n file_path: { type: 'string', description: 'Absolute path to file' },\n offset: { type: 'number', description: 'Line to start from' },\n limit: { type: 'number', description: 'Max lines to read' },\n pages: { type: 'string', description: 'Page range for PDFs' },\n },\n required: ['file_path'],\n },\n },\n ],\n max_tokens: 16,\n stream: false,\n })\n\n const parameters = (\n requestBody?.tools as Array<{ function?: { parameters?: Record } }>\n )?.[0]?.function?.parameters\n\n expect(parameters?.required).toEqual(['file_path'])\n\n const required = parameters?.required as string[] | undefined\n expect(required).not.toContain('offset')\n expect(required).not.toContain('limit')\n expect(required).not.toContain('pages')\n expect(parameters?.additionalProperties).toBe(false)\n})\n\n// ---------------------------------------------------------------------------\n// Issue #202 — consecutive role coalescing (Devstral, Mistral strict templates)\n// ---------------------------------------------------------------------------\n\nfunction makeNonStreamResponse(content = 'ok'): Response {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-test',\n model: 'test-model',\n choices: [{ message: { role: 'assistant', content }, finish_reason: 'stop' }],\n usage: { prompt_tokens: 5, completion_tokens: 1, total_tokens: 6 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n}\n\ntest('coalesces consecutive user messages to avoid alternation errors (issue #202)', async () => {\n let sentMessages: Array<{ role: string; content: unknown }> | undefined\n\n globalThis.fetch = (async (_input: unknown, init: RequestInit | undefined) => {\n sentMessages = JSON.parse(String(init?.body)).messages\n return makeNonStreamResponse()\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'test-model',\n system: 'sys',\n messages: [\n { role: 'user', content: 'first message' },\n { role: 'user', content: 'second message' },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n expect(sentMessages?.length).toBe(2)\n expect(sentMessages?.[0]?.role).toBe('system')\n expect(sentMessages?.[1]?.role).toBe('user')\n const userContent = sentMessages?.[1]?.content as string\n expect(userContent).toContain('first message')\n expect(userContent).toContain('second message')\n})\n\ntest('coalesces consecutive assistant messages preserving tool_calls (issue #202)', async () => {\n let sentMessages: Array<{ role: string; content: unknown; tool_calls?: unknown[] }> | undefined\n\n globalThis.fetch = (async (_input: unknown, init: RequestInit | undefined) => {\n sentMessages = JSON.parse(String(init?.body)).messages\n return makeNonStreamResponse()\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'test-model',\n system: 'sys',\n messages: [\n { role: 'user', content: 'go' },\n { role: 'assistant', content: 'thinking...' },\n {\n role: 'assistant',\n content: [{ type: 'tool_use', id: 'call_1', name: 'Bash', input: { command: 'ls' } }],\n },\n { role: 'user', content: [{ type: 'tool_result', tool_use_id: 'call_1', content: 'file.txt' }] },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const assistantMsgs = sentMessages?.filter(m => m.role === 'assistant')\n expect(assistantMsgs?.length).toBe(1)\n expect(assistantMsgs?.[0]?.tool_calls?.length).toBeGreaterThan(0)\n})\n\ntest('non-streaming: reasoning_content emitted as thinking block only when content is null', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'glm-5',\n choices: [\n {\n message: {\n role: 'assistant',\n content: null,\n reasoning_content: 'Let me think about this step by step.',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 20,\n total_tokens: 30,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = (await client.beta.messages.create({\n model: 'glm-5',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })) as { content: Array> }\n\n expect(result.content).toEqual([\n { type: 'thinking', thinking: 'Let me think about this step by step.' },\n ])\n})\n\ntest('non-streaming: empty string content does not fall through to reasoning_content as text', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'glm-5',\n choices: [\n {\n message: {\n role: 'assistant',\n content: '',\n reasoning_content: 'Chain of thought here.',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 20,\n total_tokens: 30,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = (await client.beta.messages.create({\n model: 'glm-5',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })) as { content: Array> }\n\n expect(result.content).toEqual([\n { type: 'thinking', thinking: 'Chain of thought here.' },\n ])\n})\n\ntest('non-streaming: real content takes precedence over reasoning_content', async () => {\n globalThis.fetch = (async (_input, _init) => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'glm-5',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'The answer is 42.',\n reasoning_content: 'I need to calculate this.',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 20,\n total_tokens: 30,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = (await client.beta.messages.create({\n model: 'glm-5',\n system: 'test system',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })) as { content: Array> }\n\n expect(result.content).toEqual([\n { type: 'thinking', thinking: 'I need to calculate this.' },\n { type: 'text', text: 'The answer is 42.' },\n ])\n})\n\ntest('non-streaming: strips tag block from assistant content', async () => {\n globalThis.fetch = asMockFetch(mock(async () => {\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-5-mini',\n choices: [\n {\n message: {\n role: 'assistant',\n content:\n 'user wants a greeting, respond brieflyHey! How can I help you today?',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 10,\n completion_tokens: 20,\n total_tokens: 30,\n },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n const result = (await client.beta.messages.create({\n model: 'gpt-5-mini',\n system: 'test system',\n messages: [{ role: 'user', content: 'hey' }],\n max_tokens: 64,\n stream: false,\n })) as { content: Array> }\n\n expect(result.content).toEqual([\n { type: 'text', text: 'Hey! How can I help you today?' },\n ])\n})\n\ntest('streaming: thinking block closed before tool call', async () => {\n globalThis.fetch = (async (_input, _init) => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'glm-5',\n choices: [\n {\n index: 0,\n delta: { role: 'assistant', reasoning_content: 'Thinking...' },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'glm-5',\n choices: [\n {\n index: 0,\n delta: {\n tool_calls: [\n {\n index: 0,\n id: 'call-1',\n type: 'function',\n function: {\n name: 'Bash',\n arguments: '{\"command\":\"ls\"}',\n },\n },\n ],\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'glm-5',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'tool_calls',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'glm-5',\n system: 'test system',\n messages: [{ role: 'user', content: 'Run ls' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const events: Array> = []\n for await (const event of result.data) {\n events.push(event)\n }\n\n const types = events.map(e => e.type)\n\n const thinkingStartIdx = types.indexOf('content_block_start')\n const firstStopIdx = types.indexOf('content_block_stop')\n const toolStartIdx = types.indexOf(\n 'content_block_start',\n thinkingStartIdx + 1,\n )\n\n expect(thinkingStartIdx).toBeGreaterThanOrEqual(0)\n expect(firstStopIdx).toBeGreaterThan(thinkingStartIdx)\n expect(toolStartIdx).toBeGreaterThan(firstStopIdx)\n\n const thinkingStart = events[thinkingStartIdx] as {\n content_block?: Record\n }\n expect(thinkingStart?.content_block?.type).toBe('thinking')\n})\n\ntest('streaming: strips tag block from assistant content deltas', async () => {\n globalThis.fetch = asMockFetch(mock(async () => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n content:\n 'user wants a greeting, respond brieflyHey! How can I help you today?',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n const result = await client.beta.messages\n .create({\n model: 'gpt-5-mini',\n system: 'test system',\n messages: [{ role: 'user', content: 'hey' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const textDeltas: string[] = []\n for await (const event of result.data) {\n const delta = (event as { delta?: { type?: string; text?: string } }).delta\n if (delta?.type === 'text_delta' && typeof delta.text === 'string') {\n textDeltas.push(delta.text)\n }\n }\n\n expect(textDeltas.join('')).toBe('Hey! How can I help you today?')\n})\n\ntest('streaming: strips tag split across multiple content chunks', async () => {\n globalThis.fetch = asMockFetch(mock(async () => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n content: 'user wants a greeting,',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {\n content: ' respond brieflyHey! How can I help you today?',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n const result = await client.beta.messages\n .create({\n model: 'gpt-5-mini',\n system: 'test system',\n messages: [{ role: 'user', content: 'hey' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const textDeltas: string[] = []\n for await (const event of result.data) {\n const delta = (event as { delta?: { type?: string; text?: string } }).delta\n if (delta?.type === 'text_delta' && typeof delta.text === 'string') {\n textDeltas.push(delta.text)\n }\n }\n\n expect(textDeltas.join('')).toBe('Hey! How can I help you today?')\n})\n\ntest('streaming: preserves prose without tags (no phrase-based false positive)', async () => {\n // Regression: older phrase-based sanitizer would strip \"I should...\" prose.\n // The tag-based approach leaves legitimate assistant output alone.\n globalThis.fetch = asMockFetch(mock(async () => {\n const chunks = makeStreamChunks([\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {\n role: 'assistant',\n content:\n 'I should note that the user role requires a briefly concise friendly response format.',\n },\n finish_reason: null,\n },\n ],\n },\n {\n id: 'chatcmpl-1',\n object: 'chat.completion.chunk',\n model: 'gpt-5-mini',\n choices: [\n {\n index: 0,\n delta: {},\n finish_reason: 'stop',\n },\n ],\n },\n ])\n\n return makeSseResponse(chunks)\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n const result = await client.beta.messages\n .create({\n model: 'gpt-5-mini',\n system: 'test system',\n messages: [{ role: 'user', content: 'hey' }],\n max_tokens: 64,\n stream: true,\n })\n .withResponse()\n\n const textDeltas: string[] = []\n for await (const event of result.data) {\n const delta = (event as { delta?: { type?: string; text?: string } }).delta\n if (delta?.type === 'text_delta' && typeof delta.text === 'string') {\n textDeltas.push(delta.text)\n }\n }\n\n expect(textDeltas.join('')).toBe(\n 'I should note that the user role requires a briefly concise friendly response format.',\n )\n})\n\ntest('strips credentials and query params from URL in fetch network error message', async () => {\n process.env.OPENAI_BASE_URL =\n 'https://user:password@internal.example.test/v1?token=abc123'\n process.env.OPENAI_API_KEY = 'test-key'\n\n globalThis.fetch = asMockFetch(mock(async () => {\n throw new TypeError(\n 'fetch failed https://user:password@internal.example.test/v1?token=abc123/chat/completions',\n )\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n let caught: unknown\n try {\n await client.beta.messages.create({\n model: 'test-model',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n } catch (error) {\n caught = error\n }\n\n const message = (caught as Error).message\n expect(message).toContain('internal.example.test')\n expect(message).toContain('fetch failed')\n expect(message).not.toContain('password')\n expect(message).not.toContain('user:')\n expect(message).not.toContain('token=abc123')\n})\n\ntest('classifies localhost transport failures with actionable category marker', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n const transportError = Object.assign(new TypeError('fetch failed'), {\n code: 'ECONNREFUSED',\n })\n\n globalThis.fetch = asMockFetch(mock(async () => {\n throw transportError\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).rejects.toThrow('openai_category=connection_refused')\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).rejects.toThrow('local server is running')\n})\n\ntest('transport failures are not labeled with HTTP status 503', async () => {\n // Issue #971: ENETDOWN (and other transport errors) are emitted before any\n // HTTP response is received. Reporting them as \"503\" makes users believe the\n // upstream server returned 503 Service Unavailable.\n process.env.OPENAI_BASE_URL = 'https://intranet.example.test/v1'\n\n const transportError = Object.assign(new TypeError('fetch failed'), {\n code: 'ENETDOWN',\n })\n\n globalThis.fetch = asMockFetch(mock(async () => {\n throw transportError\n }))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n let caught: unknown\n try {\n await client.beta.messages.create({\n model: 'gpt-4o-mini',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n } catch (error) {\n caught = error\n }\n\n expect(caught).toBeDefined()\n const err = caught as { status?: number; message: string; constructor: { name: string } }\n expect(err.constructor.name).toBe('APIConnectionError')\n expect(err.status).toBeUndefined()\n expect(err.message).not.toMatch(/^503\\b/)\n expect(err.message).toContain('OpenAI API transport error')\n expect(err.message).toContain('code=ENETDOWN')\n expect(err.message).toContain('openai_category=network_error')\n})\n\ntest('propagates AbortError without wrapping it as transport failure', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n const abortError = new DOMException('The operation was aborted.', 'AbortError')\n globalThis.fetch = asMockFetch(mock(async () => {\n throw abortError\n }))\n\n const controller = new AbortController()\n controller.abort()\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create(\n {\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n },\n { signal: controller.signal },\n ),\n ).rejects.toBe(abortError)\n})\n\ntest('classifies chat-completions endpoint 404 failures with endpoint_not_found marker', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434'\n\n globalThis.fetch = asMockFetch(mock(async () =>\n new Response('Not Found', {\n status: 404,\n headers: {\n 'Content-Type': 'text/plain',\n },\n })))\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).rejects.toThrow('openai_category=endpoint_not_found')\n})\ntest('self-heals localhost resolution failures by retrying local loopback base URL', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n const requestUrls: string[] = []\n globalThis.fetch = (async (input, _init) => {\n const url = typeof input === 'string' ? input : input.url\n requestUrls.push(url)\n\n if (url.includes('localhost')) {\n const error = Object.assign(new TypeError('fetch failed'), {\n code: 'ENOTFOUND',\n })\n throw error\n }\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen2.5-coder:7b',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'hello from loopback',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 4,\n completion_tokens: 3,\n total_tokens: 7,\n },\n }),\n {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).resolves.toBeDefined()\n\n expect(requestUrls[0]).toBe('http://localhost:11434/v1/chat/completions')\n expect(requestUrls).toContain('http://127.0.0.1:11434/v1/chat/completions')\n})\n\ntest('self-heals local endpoint_not_found by retrying with /v1 base URL', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434'\n\n const requestUrls: string[] = []\n globalThis.fetch = (async (input, _init) => {\n const url = typeof input === 'string' ? input : input.url\n requestUrls.push(url)\n\n if (url === 'http://localhost:11434/chat/completions') {\n return new Response('Not Found', {\n status: 404,\n headers: {\n 'Content-Type': 'text/plain',\n },\n })\n }\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen2.5-coder:7b',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'hello from /v1',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 5,\n completion_tokens: 2,\n total_tokens: 7,\n },\n }),\n {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n }),\n ).resolves.toBeDefined()\n\n expect(requestUrls).toEqual([\n 'http://localhost:11434/chat/completions',\n 'http://localhost:11434/v1/chat/completions',\n ])\n})\n\ntest('self-heals tool-call incompatibility by retrying local Ollama requests without tools', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'\n\n const requestBodies: Array> = []\n globalThis.fetch = (async (_input, init) => {\n const requestBody = JSON.parse(String(init?.body)) as Record\n requestBodies.push(requestBody)\n\n if (requestBodies.length === 1) {\n return new Response('tool_calls are not supported', {\n status: 400,\n headers: {\n 'Content-Type': 'text/plain',\n },\n })\n }\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen2.5-coder:7b',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'fallback without tools',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 8,\n completion_tokens: 4,\n total_tokens: 12,\n },\n }),\n {\n status: 200,\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await expect(\n client.beta.messages.create({\n model: 'qwen2.5-coder:7b',\n messages: [{ role: 'user', content: 'hello' }],\n tools: [\n {\n name: 'Read',\n description: 'Read a file',\n input_schema: {\n type: 'object',\n properties: {\n filePath: { type: 'string' },\n },\n required: ['filePath'],\n },\n },\n ],\n max_tokens: 64,\n stream: false,\n }),\n ).resolves.toBeDefined()\n\n expect(requestBodies).toHaveLength(2)\n expect(Array.isArray(requestBodies[0]?.tools)).toBe(true)\n expect(requestBodies[0]?.tool_choice).toBeUndefined()\n expect(\n requestBodies[1]?.tools === undefined ||\n (Array.isArray(requestBodies[1]?.tools) && requestBodies[1]?.tools.length === 0),\n ).toBe(true)\n expect(requestBodies[1]?.tool_choice).toBeUndefined()\n})\n\ntest('preserves valid tool_result and drops orphan tool_result', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'mistral-large-latest',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mistral-large-latest',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Search and then I will interrupt' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'valid_call_1',\n name: 'Search',\n input: { query: 'openclaude' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'valid_call_1',\n content: 'Found it!',\n },\n {\n type: 'tool_result',\n tool_use_id: 'orphan_call_2',\n content: 'Interrupted result',\n },\n {\n role: 'user',\n content: 'What happened?',\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n\n // Should have: system, user, assistant (tool_use), tool (valid_call_1), user\n // Should NOT have: tool (orphan_call_2)\n\n const toolMessages = messages.filter(m => m.role === 'tool')\n expect(toolMessages.length).toBe(1)\n expect(toolMessages[0].tool_call_id).toBe('valid_call_1')\n\n const orphanMessage = toolMessages.find(m => m.tool_call_id === 'orphan_call_2')\n expect(orphanMessage).toBeUndefined()\n \n // Actually, the semantic message IS injected here because the user block with orphan \n // tool result is converted to:\n // 1. Tool result (valid_call_1) -> role 'tool'\n // 2. User content (\"What happened?\") -> role 'user'\n // This triggers the tool -> assistant injection.\n const assistantMessages = messages.filter(m => m.role === 'assistant')\n expect(assistantMessages.some(m => m.content === '[Tool results received]')).toBe(true)\n})\n\ntest('drops empty assistant message when only thinking block was present and stripped', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(JSON.stringify({\n id: 'chatcmpl-1',\n object: 'chat.completion',\n created: 123456789,\n model: 'mistral-large-latest',\n choices: [{ message: { role: 'assistant', content: 'hi' }, finish_reason: 'stop' }],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }\n }), { headers: { 'Content-Type': 'application/json' } })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mistral-large-latest',\n messages: [\n { role: 'user', content: 'Initial' },\n { role: 'assistant', content: [{ type: 'thinking', thinking: 'I am thinking...', signature: 'sig' }] },\n { role: 'user', content: 'Interrupting query' },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n // The assistant msg is dropped because thinking is stripped.\n // The two user messages are coalesced.\n expect(messages.length).toBe(1)\n expect(messages[0].role).toBe('user')\n expect(String(messages[0].content)).toContain('Initial')\n expect(String(messages[0].content)).toContain('Interrupting query')\n})\n\ntest('injects semantic assistant message when tool result is followed by user message', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(JSON.stringify({\n id: 'chatcmpl-2',\n object: 'chat.completion',\n created: 123456789,\n model: 'mistral-large-latest',\n choices: [{ message: { role: 'assistant', content: 'hi' }, finish_reason: 'stop' }],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 }\n }), { headers: { 'Content-Type': 'application/json' } })\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'mistral-large-latest',\n messages: [\n { \n role: 'assistant', \n content: [{ type: 'tool_use', id: 'call_1', name: 'search', input: {} }] \n },\n { \n role: 'user', \n content: [\n { type: 'tool_result', tool_use_id: 'call_1', content: 'Result' }\n ] \n },\n { role: 'user', content: 'Next user query' },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n // Roles should be: assistant (tool_calls) -> tool -> assistant (semantic) -> user\n const roles = messages.map(m => m.role)\n expect(roles).toEqual(['assistant', 'tool', 'assistant', 'user'])\n \n const semanticMsg = messages[2]\n expect(semanticMsg.role).toBe('assistant')\n expect(semanticMsg.content).toBe('[Tool results received]')\n expect(semanticMsg.content).not.toContain('interrupted')\n expect(semanticMsg.content).not.toContain('user')\n})\n\ntest('Moonshot: uses max_tokens (not max_completion_tokens) and strips store', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.moonshot.ai/v1'\n process.env.OPENAI_API_KEY = 'sk-moonshot-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-k2.6',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-k2.6',\n system: 'you are kimi',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.max_tokens).toBe(256)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Cerebras: strips unsupported store on chat_completions (#1023)', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.cerebras.ai/v1'\n process.env.OPENAI_API_KEY = 'csk-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'llama3.1-8b',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'llama3.1-8b',\n system: 'you are cerebras',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Local provider (vLLM/Ollama/etc.): strips unsupported store on chat_completions (#672)', async () => {\n process.env.OPENAI_BASE_URL = 'http://localhost:8000/v1'\n process.env.OPENAI_API_KEY = 'sk-local'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'qwen-3.5-27b',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'qwen-3.5-27b',\n system: 'you are local',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 64,\n stream: false,\n })\n\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Groq: keeps max_completion_tokens and strips unsupported store', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.groq.com/openai/v1'\n process.env.OPENAI_API_KEY = 'gsk-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'llama-3.3-70b-versatile',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'llama-3.3-70b-versatile',\n system: 'you are groq',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.max_completion_tokens).toBe(256)\n expect(requestBody?.max_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Moonshot: echoes reasoning_content on assistant tool-call messages', async () => {\n // Regression for: \"API Error: 400 {\"error\":{\"message\":\"thinking is enabled\n // but reasoning_content is missing in assistant tool call message at index\n // N\"}}\" when the agent sends a prior-turn assistant response back to Kimi.\n // The thinking block captured from the inbound response must round-trip\n // as reasoning_content on the outgoing echoed assistant message.\n process.env.OPENAI_BASE_URL = 'https://api.moonshot.ai/v1'\n process.env.OPENAI_API_KEY = 'sk-moonshot-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-k2.6',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-k2.6',\n system: 'you are kimi',\n messages: [\n { role: 'user', content: 'check the logs' },\n {\n role: 'assistant',\n content: [\n {\n type: 'thinking',\n thinking: 'Need to inspect logs via Bash; running a cat.',\n },\n { type: 'text', text: \"I'll inspect the logs.\" },\n {\n type: 'tool_use',\n id: 'call_bash_1',\n name: 'Bash',\n input: { command: 'cat /tmp/app.log' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_bash_1',\n content: 'log line 1\\nlog line 2',\n },\n ],\n },\n ],\n max_tokens: 256,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'Need to inspect logs via Bash; running a cat.',\n )\n})\n\ntest('DeepSeek echoes reasoning_content on assistant tool-call messages', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-v4-flash',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-v4-flash',\n system: 'test',\n messages: [\n { role: 'user', content: 'hi' },\n {\n role: 'assistant',\n content: [\n { type: 'thinking', thinking: 'thought' },\n { type: 'text', text: 'hello' },\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_1', content: 'files' },\n ],\n },\n ],\n max_tokens: 32,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe('thought')\n})\n\ntest('generic OpenAI-compatible providers do not echo reasoning_content on assistant tool-call messages', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'sk-openai-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test',\n messages: [\n { role: 'user', content: 'hi' },\n {\n role: 'assistant',\n content: [\n { type: 'thinking', thinking: 'thought' },\n { type: 'text', text: 'hello' },\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_1', content: 'files' },\n ],\n },\n ],\n max_tokens: 32,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBeUndefined()\n})\n\ntest('gateway-routed DeepSeek models inherit descriptor-backed reasoning and token shaping', async () => {\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://openrouter.ai/api/v1'\n process.env.OPENAI_API_KEY = 'sk-openrouter-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek/deepseek-reasoner',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n reasoningEffort: 'xhigh',\n }) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek/deepseek-reasoner',\n system: 'test',\n messages: [\n { role: 'user', content: 'hi' },\n {\n role: 'assistant',\n content: [\n { type: 'thinking', thinking: 'thought' },\n { type: 'text', text: 'hello' },\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_1', content: 'files' },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n thinking: { type: 'enabled' },\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n message => message.role === 'assistant' && Array.isArray(message.tool_calls),\n )\n\n expect(assistantWithToolCall?.reasoning_content).toBe('thought')\n expect(requestBody?.thinking).toEqual({ type: 'enabled' })\n expect(requestBody?.reasoning_effort).toBe('max')\n expect(requestBody?.max_tokens).toBe(64)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Moonshot: cn host is also detected', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.moonshot.cn/v1'\n process.env.OPENAI_API_KEY = 'sk-moonshot-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-k2.6',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-k2.6',\n system: 'you are kimi',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Kimi Code endpoint inherits Moonshot max_tokens/store compatibility', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.kimi.com/coding/v1'\n process.env.OPENAI_API_KEY = 'sk-kimi-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-for-coding',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-for-coding',\n system: 'you are kimi code',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.max_tokens).toBe(256)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Kimi Code endpoint echoes reasoning_content on assistant tool-call messages', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.kimi.com/coding/v1'\n process.env.OPENAI_API_KEY = 'sk-kimi-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'kimi-for-coding',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'kimi-for-coding',\n system: 'you are kimi code',\n messages: [\n { role: 'user', content: 'check the logs' },\n {\n role: 'assistant',\n content: [\n {\n type: 'thinking',\n thinking: 'Need to inspect logs via Bash; running a cat.',\n },\n { type: 'text', text: \"I'll inspect the logs.\" },\n {\n type: 'tool_use',\n id: 'call_bash_1',\n name: 'Bash',\n input: { command: 'cat /tmp/app.log' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_bash_1',\n content: 'log line 1\\nlog line 2',\n },\n ],\n },\n ],\n max_tokens: 256,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'Need to inspect logs via Bash; running a cat.',\n )\n})\n\ntest('DeepSeek sends thinking toggle and normalized reasoning effort', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-v4-pro',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n reasoningEffort: 'xhigh',\n }) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-v4-pro',\n system: 'test',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 64,\n stream: false,\n thinking: { type: 'enabled' },\n })\n\n expect(requestBody?.thinking).toEqual({ type: 'enabled' })\n expect(requestBody?.reasoning_effort).toBe('max')\n expect(requestBody?.max_tokens).toBe(64)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('DeepSeek omits thinking controls when the Anthropic-side request does not set them', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-v4-flash',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-v4-flash',\n system: 'test',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 32,\n stream: false,\n })\n\n expect(requestBody?.thinking).toBeUndefined()\n expect(requestBody?.reasoning_effort).toBeUndefined()\n})\n\ntest('DeepSeek forwards an explicit thinking disable toggle for V4 models', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-v4-flash',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-v4-flash',\n system: 'test',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 32,\n stream: false,\n thinking: { type: 'disabled' },\n })\n\n expect(requestBody?.thinking).toEqual({ type: 'disabled' })\n expect(requestBody?.reasoning_effort).toBeUndefined()\n})\n\n\ntest('collapses multiple text blocks in tool_result to string for DeepSeek compatibility (issue #774)', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-reasoner',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'deepseek-reasoner',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Run ls' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_1',\n content: [\n { type: 'text', text: 'line one' },\n { type: 'text', text: 'line two' },\n ],\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const toolMessages = messages.filter(m => m.role === 'tool')\n expect(toolMessages.length).toBe(1)\n expect(toolMessages[0].tool_call_id).toBe('call_1')\n expect(typeof toolMessages[0].content).toBe('string')\n expect(toolMessages[0].content).toBe('line one\\n\\nline two')\n})\n\ntest('collapses multiple text blocks into a single string for DeepSeek compatibility (issue #774)', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-reasoner',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'deepseek-reasoner',\n system: 'test system',\n messages: [\n {\n role: 'user',\n content: [\n { type: 'text', text: 'Hello!' },\n { type: 'text', text: 'How are you?' },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n expect(messages.length).toBe(2) // system + user\n expect(messages[1].role).toBe('user')\n expect(typeof messages[1].content).toBe('string')\n expect(messages[1].content).toBe('Hello!\\n\\nHow are you?')\n})\n\ntest('preserves mixed text and image tool results as multipart content', async () => {\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: {\n role: 'assistant',\n content: 'done',\n },\n finish_reason: 'stop',\n },\n ],\n usage: {\n prompt_tokens: 12,\n completion_tokens: 4,\n total_tokens: 16,\n },\n }),\n {\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: 'test system',\n messages: [\n { role: 'user', content: 'Show me' },\n {\n role: 'assistant',\n content: [\n {\n type: 'tool_use',\n id: 'call_1',\n name: 'Bash',\n input: { command: 'cat image.png' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_1',\n content: [\n { type: 'text', text: 'Here is the image:' },\n {\n type: 'image',\n source: {\n type: 'base64',\n media_type: 'image/png',\n data: 'iVBORw0KGgo=',\n },\n },\n ],\n },\n ],\n },\n ],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const toolMessages = messages.filter(m => m.role === 'tool')\n expect(toolMessages.length).toBe(1)\n expect(Array.isArray(toolMessages[0].content)).toBe(true)\n const content = toolMessages[0].content as Array>\n expect(content.length).toBe(2)\n expect(content[0].type).toBe('text')\n expect(content[1].type).toBe('image_url')\n})\n\ntest('Z.AI: uses max_tokens (not max_completion_tokens) and strips store', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.z.ai/api/coding/paas/v4'\n process.env.OPENAI_API_KEY = 'sk-zai-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'GLM-5.1',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'GLM-5.1',\n system: 'you are glm',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 256,\n stream: false,\n })\n\n expect(requestBody?.max_tokens).toBe(256)\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.store).toBeUndefined()\n})\n\ntest('Z.AI: thinking mode enabled when requested', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.z.ai/api/coding/paas/v4'\n process.env.OPENAI_API_KEY = 'sk-zai-test'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'GLM-5.1',\n choices: [\n {\n message: {\n role: 'assistant',\n content: null,\n reasoning_content: 'Let me think...',\n },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'GLM-5.1',\n system: 'you are glm',\n messages: [{ role: 'user', content: 'think hard' }],\n max_tokens: 1024,\n stream: false,\n thinking: { type: 'enabled', budget_tokens: 1024 },\n })\n\n expect((requestBody?.thinking as Record)?.type).toBe('enabled')\n expect(requestBody?.max_completion_tokens).toBeUndefined()\n expect(requestBody?.max_tokens).toBe(1024)\n})\n\ntest('strips Anthropic attribution header block from chat-completions system prompt (#607)', async () => {\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: { role: 'assistant', content: 'ok' },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 8, completion_tokens: 3, total_tokens: 11 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n system: [\n {\n type: 'text',\n text:\n 'x-anthropic-billing-header: cc_version=0.8.0.abc123; ' +\n 'cc_entrypoint=cli;',\n },\n { type: 'text', text: 'You are Claude Code, helpful assistant.' },\n { type: 'text', text: 'Project context: bun + react.' },\n ],\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n const messages = capturedBody?.messages as Array<{ role: string; content: string }>\n const sysMsg = messages.find(m => m.role === 'system')\n expect(sysMsg).toBeDefined()\n expect(sysMsg?.content).not.toContain('x-anthropic-billing-header')\n expect(sysMsg?.content).not.toContain('cc_version=')\n expect(sysMsg?.content).toContain('You are Claude Code, helpful assistant.')\n expect(sysMsg?.content).toContain('Project context: bun + react.')\n})\n\ntest('strips Anthropic attribution header block from responses-API instructions (#607)', async () => {\n process.env.OPENAI_API_FORMAT = 'responses'\n let capturedBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n capturedBody = JSON.parse(String(init?.body)) as Record\n\n return new Response(\n JSON.stringify({\n id: 'resp-1',\n model: 'gpt-5.4',\n output: [\n {\n type: 'message',\n role: 'assistant',\n content: [{ type: 'output_text', text: 'ok' }],\n },\n ],\n usage: { input_tokens: 8, output_tokens: 3, total_tokens: 11 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({ defaultHeaders: {} }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n system: [\n {\n type: 'text',\n text: 'x-anthropic-billing-header: cc_version=0.8.0.abc123; cc_entrypoint=cli;',\n },\n { type: 'text', text: 'You are Claude Code.' },\n ],\n messages: [{ role: 'user', content: 'hello' }],\n max_tokens: 64,\n stream: false,\n })\n\n const instructions = capturedBody?.instructions as string\n expect(instructions).not.toContain('x-anthropic-billing-header')\n expect(instructions).not.toContain('cc_version=')\n expect(instructions).toContain('You are Claude Code.')\n})\n\ntest('emits reasoning_effort on chat_completions when reasoningEffort is passed', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'test-key'\n\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-5.4',\n choices: [\n {\n message: { role: 'assistant', content: 'ok' },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({\n reasoningEffort: 'xhigh',\n }) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 16,\n stream: false,\n })\n\n expect(requestBody?.reasoning_effort).toBe('xhigh')\n})\n\ntest('omits reasoning_effort on chat_completions when no override and model has no alias default', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'test-key'\n\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-4o',\n choices: [\n {\n message: { role: 'assistant', content: 'ok' },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 16,\n stream: false,\n })\n\n expect(requestBody && 'reasoning_effort' in requestBody).toBe(false)\n})\n\ntest('emits reasoning_effort from codex alias default when no override is passed', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_API_KEY = 'test-key'\n\n let requestBody: Record | undefined\n\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'gpt-5.4',\n choices: [\n {\n message: { role: 'assistant', content: 'ok' },\n finish_reason: 'stop',\n },\n ],\n usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n\n await client.beta.messages.create({\n model: 'gpt-5.4',\n messages: [{ role: 'user', content: 'hi' }],\n max_tokens: 16,\n stream: false,\n })\n\n expect(requestBody?.reasoning_effort).toBe('high')\n})\n\ntest('DeepSeek: redacted_thinking block preserves continuity with reasoning_content: \"\"', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-1',\n model: 'deepseek-chat',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-chat',\n system: 'test',\n messages: [\n { role: 'user', content: 'analyze this' },\n {\n role: 'assistant',\n content: [\n // real redacted_thinking shape: content lives in `.data`, not `.thinking`\n { type: 'redacted_thinking', data: '', signature: 'sig123' },\n { type: 'text', text: 'Analysis complete.' },\n {\n type: 'tool_use',\n id: 'call_redacted_1',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_redacted_1', content: 'files' },\n ],\n },\n ],\n max_tokens: 32,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n // redacted_thinking is recognized as a thinking block; its .data is \"\" and the\n // message carries a tool_call, so it falls back to reasoning_content: \"\"\n expect(assistantWithToolCall?.reasoning_content).toBe('')\n})\n\ntest('DeepSeek: redacted_thinking block with non-empty data propagates data into reasoning_content', async () => {\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_API_KEY = 'sk-deepseek'\n\n let requestBody: Record | undefined\n globalThis.fetch = (async (_input, init) => {\n requestBody = JSON.parse(String(init?.body))\n return new Response(\n JSON.stringify({\n id: 'chatcmpl-2',\n model: 'deepseek-chat',\n choices: [\n { message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },\n ],\n usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },\n }),\n { headers: { 'Content-Type': 'application/json' } },\n )\n }) as unknown as FetchType\n\n const client = createOpenAIShimClient({}) as OpenAIShimClient\n await client.beta.messages.create({\n model: 'deepseek-chat',\n system: 'test',\n messages: [\n { role: 'user', content: 'analyze this' },\n {\n role: 'assistant',\n content: [\n // real redacted_thinking with content in .data\n {\n type: 'redacted_thinking',\n data: 'encrypted_chain_of_thought_payload_v1',\n signature: 'sig456',\n },\n { type: 'text', text: 'Analysis complete.' },\n {\n type: 'tool_use',\n id: 'call_redacted_2',\n name: 'Bash',\n input: { command: 'ls' },\n },\n ],\n },\n {\n role: 'user',\n content: [\n { type: 'tool_result', tool_use_id: 'call_redacted_2', content: 'files' },\n ],\n },\n ],\n max_tokens: 32,\n stream: false,\n })\n\n const messages = requestBody?.messages as Array>\n const assistantWithToolCall = messages.find(\n m => m.role === 'assistant' && Array.isArray(m.tool_calls),\n )\n expect(assistantWithToolCall).toBeDefined()\n // The real .data payload must be preserved in reasoning_content — this is the\n // case the original test missed (it used a synthetic .thinking field).\n expect(assistantWithToolCall?.reasoning_content).toBe(\n 'encrypted_chain_of_thought_payload_v1',\n )\n})\n\ntest('renders tool_reference blocks as text on the chat/completions path', async () => {\n const { __test } = await import('./openaiShim.ts')\n\n const messages = __test.convertMessages(\n [\n {\n role: 'assistant',\n content: [\n { type: 'tool_use', id: 'call_ts1', name: 'ToolSearch', input: { query: 'memory' } },\n ],\n },\n {\n role: 'user',\n content: [\n {\n type: 'tool_result',\n tool_use_id: 'call_ts1',\n content: [\n { type: 'tool_reference', tool_name: 'mcp__example__memory_search' },\n { type: 'tool_reference', tool_name: 'mcp__example__memory_store' },\n ],\n },\n ],\n },\n ],\n undefined,\n )\n\n const toolMsg = messages.find(m => m.role === 'tool')\n expect(toolMsg).toBeDefined()\n // The rendering contract is plain text: text-only parts collapse to a string.\n expect(typeof toolMsg!.content).toBe('string')\n const content = toolMsg!.content as string\n expect(content).toContain('mcp__example__memory_search')\n expect(content).toContain('mcp__example__memory_store')\n})\n"}}, ".env.example": {"b2e10afbdf8cb6ae97322d1ef80e9140f5173378": {"t": 1785747132.474113, "n": 19427, "h": "0e5bff0ddcaf8d66", "c": "# =============================================================================\n# AtusCode Environment Configuration\n# =============================================================================\n# Copy this file to .env and fill in your values:\n# cp .env.example .env\n#\n# Only set the variables for the provider you want to use.\n# All other sections can be left commented out.\n# =============================================================================\n\n# =============================================================================\n# SYSTEM-WIDE SETUP (OPTIONAL)\n# =============================================================================\n# Instead of using a .env file per project, you can set these variables\n# system-wide so AtusCode works from any directory on your machine.\n#\n# STEP 1: Pick your provider variables from the list below.\n# STEP 2: Set them using the method for your OS (see further down).\n#\n# ── Provider variables ───────────────────────────────────────────────\n#\n# Option 1 — Anthropic:\n# ANTHROPIC_API_KEY=sk-ant-your-key-here\n# ANTHROPIC_MODEL=claude-sonnet-4-5 (optional)\n# ANTHROPIC_BASE_URL=https://api.anthropic.com (optional)\n#\n# Option 2 — OpenAI:\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_API_KEY=sk-your-key-here\n# OPENAI_MODEL=gpt-4o\n# OPENAI_BASE_URL=https://api.openai.com/v1 (optional)\n#\n# Option 11 — NEAR AI (unified gateway: Claude, GPT, Gemini + TEE models):\n# NEARAI_API_KEY=your_key_here\n# OPENAI_MODEL=anthropic/claude-sonnet-4-6 (optional, default)\n#\n# Option 3 — Google Gemini:\n# CLAUDE_CODE_USE_GEMINI=1\n# GEMINI_API_KEY=your-gemini-key-here\n# GEMINI_MODEL=gemini-2.0-flash\n# GEMINI_BASE_URL=https://generativelanguage.googleapis.com (optional)\n#\n# Option 4 — GitHub Models:\n# CLAUDE_CODE_USE_GITHUB=1\n# GITHUB_TOKEN=ghp_your-token-here\n#\n# Option 5 — Ollama (local):\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_BASE_URL=http://localhost:11434/v1\n# OPENAI_API_KEY=ollama\n# OPENAI_MODEL=llama3.2\n#\n# Option 6 — LM Studio (local):\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_BASE_URL=http://localhost:1234/v1\n# OPENAI_MODEL=your-model-id-here\n# OPENAI_API_KEY=lmstudio (optional)\n#\n# Option 7 — AWS Bedrock (may also need: aws configure):\n# CLAUDE_CODE_USE_BEDROCK=1\n# AWS_REGION=us-east-1\n# AWS_DEFAULT_REGION=us-east-1\n# AWS_BEARER_TOKEN_BEDROCK=your-bearer-token-here\n# ANTHROPIC_BEDROCK_BASE_URL=https://bedrock-runtime.us-east-1.amazonaws.com\n#\n# Option 8 — Google Vertex AI:\n# CLAUDE_CODE_USE_VERTEX=1\n# ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id\n# CLOUD_ML_REGION=us-east5\n# GOOGLE_CLOUD_PROJECT=your-gcp-project-id\n#\n# ── How to set variables on each OS ──────────────────────────────────\n#\n# macOS (zsh):\n# 1. Open: nano ~/.zshrc\n# 2. Add each variable as: export VAR_NAME=value\n# 3. Save and reload: source ~/.zshrc\n#\n# Linux (bash):\n# 1. Open: nano ~/.bashrc\n# 2. Add each variable as: export VAR_NAME=value\n# 3. Save and reload: source ~/.bashrc\n#\n# Windows (PowerShell):\n# Run for each variable:\n# [System.Environment]::SetEnvironmentVariable('VAR_NAME', 'value', 'User')\n# Then restart your terminal.\n#\n# Windows (Command Prompt):\n# Run for each variable:\n# setx VAR_NAME value\n# Then restart your terminal.\n#\n# Windows (GUI):\n# Settings > System > About > Advanced System Settings >\n# Environment Variables > under \"User variables\" click New,\n# then add each variable.\n#\n# ── Important notes ──────────────────────────────────────────────────\n#\n# LOCAL SERVERS: If using LM Studio or Ollama, the server MUST be\n# running with a model loaded before you launch AtusCode —\n# otherwise you'll get connection errors.\n#\n# SWITCHING PROVIDERS: To temporarily switch, unset the relevant\n# variables in your current terminal session:\n#\n# macOS / Linux:\n# unset VAR_NAME\n# # e.g.: unset CLAUDE_CODE_USE_OPENAI OPENAI_BASE_URL OPENAI_MODEL\n#\n# Windows (PowerShell — current session only):\n# Remove-Item Env:VAR_NAME\n#\n# To permanently remove a variable on Windows:\n# [System.Environment]::SetEnvironmentVariable('VAR_NAME', $null, 'User')\n#\n# LOAD ORDER:\n# Shell and system environment variables are inherited by the process.\n# Project .env files are only used if your launcher or shell loads them\n# before starting AtusCode.\n# COMPATIBILITY:\n# System-wide variables work regardless of how you run AtusCode:\n# npx, global npm install, bun run, or node directly. Any process\n# launched from your terminal inherits your shell's environment.\n#\n# REMINDER: Make sure .env is in your .gitignore to avoid committing secrets.\n# =============================================================================\n\n# =============================================================================\n# PROVIDER SELECTION — uncomment ONE block below\n# =============================================================================\n\n# -----------------------------------------------------------------------------\n# Option 1: Anthropic (default — no provider flag needed)\n# -----------------------------------------------------------------------------\nANTHROPIC_API_KEY=sk-ant-your-key-here\n\n# Override the default model (optional)\n# ANTHROPIC_MODEL=claude-sonnet-4-5\n\n# Use a custom Anthropic-compatible endpoint (optional)\n# ANTHROPIC_BASE_URL=https://api.anthropic.com\n\n\n# -----------------------------------------------------------------------------\n# Option 2: OpenAI\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_API_KEY=sk-your-key-here\n# OPENAI_MODEL=gpt-4o\n# For DeepSeek, set:\n# OPENAI_BASE_URL=https://api.deepseek.com/v1\n# OPENAI_MODEL=deepseek-v4-flash\n# Optional: OPENAI_MODEL=deepseek-v4-pro\n# Legacy aliases also work: deepseek-chat and deepseek-reasoner\n# For Z.AI GLM Coding Plan, set:\n# OPENAI_BASE_URL=https://api.z.ai/api/coding/paas/v4\n# OPENAI_MODEL=GLM-5.1\n# Optional: OPENAI_MODEL=GLM-5-Turbo, GLM-4.7, or GLM-4.5-Air\n# For Hicap, use the OpenAI-compatible route flag above and set:\n# HICAP_API_KEY=your-hicap-key-here\n# OPENAI_BASE_URL=https://api.hicap.ai/v1\n# OPENAI_MODEL=claude-opus-4.7\n\n# Use a custom OpenAI-compatible endpoint (optional — defaults to api.openai.com)\n# OPENAI_BASE_URL=https://api.openai.com/v1\n# Choose the OpenAI-compatible API surface (optional — defaults to chat_completions)\n# Supported: chat_completions, responses\n# OPENAI_API_FORMAT=chat_completions\n# Choose a custom auth header for OpenAI-compatible providers (optional).\n# Authorization defaults to Bearer; custom headers default to the raw API key.\n# Set OPENAI_AUTH_HEADER_VALUE when the header value differs from OPENAI_API_KEY.\n# OPENAI_AUTH_HEADER=api-key\n# OPENAI_AUTH_SCHEME=raw\n# OPENAI_AUTH_HEADER_VALUE=your-header-value-here\n\n# Fallback context window size (tokens) when the model is not found in\n# integration model metadata (default: 128000). Increase this for models with larger\n# context windows (e.g. 200000 for Claude-sized contexts).\n# CLAUDE_CODE_OPENAI_FALLBACK_CONTEXT_WINDOW=128000\n\n# Per-model context window overrides as a JSON object.\n# Takes precedence over integration model metadata, so you can register new or\n# custom models without patching source.\n# Example: CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS={\"my-corp/llm-v3\":262144,\"gpt-4o-mini\":128000}\n# CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS=\n\n# Per-model maximum output token overrides as a JSON object.\n# Use this alongside CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS when your model\n# supports a different output limit than what integration metadata specifies.\n# Example: CLAUDE_CODE_OPENAI_MAX_OUTPUT_TOKENS={\"my-corp/llm-v3\":8192}\n# CLAUDE_CODE_OPENAI_MAX_OUTPUT_TOKENS=\n\n\n# -----------------------------------------------------------------------------\n# Option 3: Google Gemini\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_GEMINI=1\n# GEMINI_API_KEY=your-gemini-key-here\n# GEMINI_MODEL=gemini-2.0-flash\n\n# Use a custom Gemini endpoint (optional)\n# GEMINI_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai\n\n\n# -----------------------------------------------------------------------------\n# Option 4: GitHub Models\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_GITHUB=1\n# GITHUB_TOKEN=ghp_your-token-here\n\n\n# -----------------------------------------------------------------------------\n# Option 5: Ollama (local models)\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_BASE_URL=http://localhost:11434/v1\n# OPENAI_API_KEY=ollama\n# OPENAI_MODEL=llama3.2\n\n# -----------------------------------------------------------------------------\n# Option 6: LM Studio (local models)\n# -----------------------------------------------------------------------------\n# LM Studio exposes an OpenAI-compatible API, so we use the OpenAI provider.\n# Make sure LM Studio is running with the Developer server enabled\n# (Developer tab > toggle server ON).\n#\n# Steps:\n# 1. Download and install LM Studio from https://lmstudio.ai\n# 2. Search for and download a model (e.g. any coding or instruct model)\n# 3. Load the model and start the Developer server\n# 4. Set OPENAI_MODEL to the model ID shown in LM Studio's Developer tab\n#\n# The default server URL is http://localhost:1234 — change the port below\n# if you've configured a different one in LM Studio.\n#\n# OPENAI_API_KEY is optional — LM Studio runs locally and ignores it.\n# Some clients require a non-empty value; if you get auth errors, set it\n# to any dummy value (e.g. \"lmstudio\").\n#\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_BASE_URL=http://localhost:1234/v1\n# OPENAI_MODEL=your-model-id-here\n\n\n# -----------------------------------------------------------------------------\n# Option 7: AWS Bedrock\n# -----------------------------------------------------------------------------\n\n# You may also need AWS CLI credentials configured (run: aws configure)\n# or have AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY set in your\n# environment in addition to the variables below.\n#\n# CLAUDE_CODE_USE_BEDROCK=1\n# AWS_REGION=us-east-1\n# AWS_DEFAULT_REGION=us-east-1\n# AWS_BEARER_TOKEN_BEDROCK=your-bearer-token-here\n# ANTHROPIC_BEDROCK_BASE_URL=https://bedrock-runtime.us-east-1.amazonaws.com\n\n\n# -----------------------------------------------------------------------------\n# Option 8: Google Vertex AI\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_VERTEX=1\n# ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id\n# CLOUD_ML_REGION=us-east5\n# GOOGLE_CLOUD_PROJECT=your-gcp-project-id\n\n\n# -----------------------------------------------------------------------------\n# Option 9: NVIDIA NIM\n# -----------------------------------------------------------------------------\n# NVIDIA NIM provides hosted inference endpoints for NVIDIA models.\n# Get your API key from https://build.nvidia.com/\n#\n# CLAUDE_CODE_USE_OPENAI=1\n# NVIDIA_API_KEY=nvapi-your-key-here\n# OPENAI_BASE_URL=https://integrate.api.nvidia.com/v1\n# OPENAI_MODEL=nvidia/llama-3.1-nemotron-70b-instruct\n\n\n# -----------------------------------------------------------------------------\n# Option 10: MiniMax\n# -----------------------------------------------------------------------------\n# MiniMax API provides text generation models.\n# Get your API key from https://platform.minimax.io/\n#\n# MINIMAX_API_KEY=your-minimax-key-here\n# ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic\n# ANTHROPIC_MODEL=MiniMax-M2.7\n\n\n# -----------------------------------------------------------------------------\n# Option 11: NEAR AI\n# -----------------------------------------------------------------------------\n# NEAR AI is a unified OpenAI-compatible gateway to Claude, GPT, Gemini,\n# and TEE-hosted open models — all under one API key.\n# Get your API key from https://cloud.near.ai/dashboard/organizations\n#\n# NEARAI_API_KEY=your-nearai-key-here\n# OPENAI_BASE_URL=https://cloud-api.near.ai/v1 (optional, default)\n# OPENAI_MODEL=anthropic/claude-sonnet-4-6 (optional, default)\n#\n# For direct TEE completions (lower latency + verifiable privacy):\n# OPENAI_BASE_URL=https://qwen35-122b.completions.near.ai/v1\n\n\n# -----------------------------------------------------------------------------\n# Option 12: Fireworks AI\n# -----------------------------------------------------------------------------\n# Fireworks AI provides a fully OpenAI-compatible endpoint.\n# Get your API key from https://fireworks.ai/\n# Model IDs use the full path format: accounts/fireworks/models/\n#\n# CLAUDE_CODE_USE_OPENAI=1\n# FIREWORKS_API_KEY=fw_your_key_here\n# OPENAI_BASE_URL=https://api.fireworks.ai/inference/v1\n# OPENAI_MODEL=accounts/fireworks/models/llama-v3p1-70b-instruct\n\n\n# =============================================================================\n# OPTIONAL TUNING\n# =============================================================================\n\n# Max number of API retries on failure (default: 10, cap: 100)\n# Set to 0 to disable retries after the initial request\n# Deprecated fallback when ATUSCODE_MAX_RETRIES is unset: CLAUDE_CODE_MAX_RETRIES\n# ATUSCODE_MAX_RETRIES=10\n\n# Base retry delay in milliseconds when the API does not send Retry-After\n# Uses exponential backoff from this value with jitter (default: 500, cap: 60000)\n# ATUSCODE_RETRY_DELAY_MS=500\n\n# Enable persistent retry mode for unattended/CI sessions\n# Retries 429/529 indefinitely with smart backoff\n# CLAUDE_CODE_UNATTENDED_RETRY=1\n\n# Enable extended key reporting (Kitty keyboard protocol)\n# Useful for iTerm2, WezTerm, Ghostty if modifier keys feel off\n# ATUSCODE_ENABLE_EXTENDED_KEYS=1\n\n# Disable \"Co-authored-by\" line in git commits made by AtusCode\n# ATUSCODE_DISABLE_CO_AUTHORED_BY=1\n\n# Disable strict tool schema normalization for non-Gemini providers\n# Useful when MCP tools with complex optional params (e.g. list[dict])\n# trigger \"Extra required key ... supplied\" errors from OpenAI-compatible endpoints\n# ATUSCODE_DISABLE_STRICT_TOOLS=1\n\n# Disable hidden messages injected into tool output\n# Suppresses the file-read cyber-risk reminder and the todo/task tool nudges\n# Useful for users who want full transparency over what the model sees\n# ATUSCODE_DISABLE_TOOL_REMINDERS=1\n\n# Log structured per-request token usage (including cache metrics) to stderr.\n# Useful for auditing cache hit rate / debugging cost spikes outside the REPL.\n# Any truthy value enables it (\"verbose\", \"1\", \"true\").\n#\n# Complements (does NOT replace) CLAUDE_CODE_ENABLE_TOKEN_USAGE_ATTACHMENT —\n# they serve different audiences:\n# - ATUSCODE_LOG_TOKEN_USAGE is user-facing: one JSON line per API\n# request on stderr, intended for humans inspecting cost/caching.\n# - CLAUDE_CODE_ENABLE_TOKEN_USAGE_ATTACHMENT is model-facing: injects\n# a context-usage attachment INTO the prompt so the model can reason\n# about its own remaining context. Does not touch stderr.\n# Turn on whichever audience you're debugging; both can run together.\n# ATUSCODE_LOG_TOKEN_USAGE=verbose\n\n# Custom timeout for API requests in milliseconds (default: varies)\n# API_TIMEOUT_MS=60000\n\n# Enable debug logging\n# CLAUDE_DEBUG=1\n\n\n# =============================================================================\n# WEB SEARCH (OPTIONAL)\n# =============================================================================\n# AtusCode includes a web search tool. By default it uses DuckDuckGo (free)\n# or the provider's native search (Anthropic firstParty / vertex).\n#\n# Set one API key below to enable a provider. That's it.\n\n# ── Provider API keys — set ONE of these ────────────────────────────\n\n# Tavily (AI-optimized search, recommended)\n# TAVILY_API_KEY=tvly-your-key-here\n\n# Exa (neural/semantic search)\n# EXA_API_KEY=your-exa-key-here\n\n# You.com (RAG-ready snippets)\n# YOU_API_KEY=your-you-key-here\n\n# Jina (s.jina.ai endpoint)\n# JINA_API_KEY=your-jina-key-here\n\n# Brave (independent web index, generous free tier)\n# BRAVE_API_KEY=your-brave-key-here\n\n# Bing Web Search\n# BING_API_KEY=your-bing-key-here\n\n# Mojeek (privacy-focused)\n# MOJEEK_API_KEY=your-mojeek-key-here\n\n# Linkup\n# LINKUP_API_KEY=your-linkup-key-here\n\n# Firecrawl (premium, uses @mendable/firecrawl-js)\n# FIRECRAWL_API_KEY=fc-your-key-here\n\n# Self-hosted Firecrawl endpoint (optional — omit to use cloud API)\n# FIRECRAWL_API_URL=https://your-firecrawl-instance.com\n\n# ── Provider selection mode ─────────────────────────────────────────\n#\n# WEB_SEARCH_PROVIDER controls fallback behavior:\n#\n# \"auto\" (default) — try all configured providers, fall through on failure\n# \"custom\" — custom API only, throw on failure (NOT in auto chain)\n# \"firecrawl\" — firecrawl only\n# \"tavily\" — tavily only\n# \"exa\" — exa only\n# \"you\" — you.com only\n# \"jina\" — jina only\n# \"brave\" — brave only\n# \"bing\" — bing only\n# \"mojeek\" — mojeek only\n# \"linkup\" — linkup only\n# \"ddg\" — duckduckgo only\n# \"native\" — anthropic native / codex only\n#\n# Auto mode priority: firecrawl → tavily → exa → you → jina → brave → bing →\n# mojeek → linkup → ddg\n# Note: \"custom\" is NOT in the auto chain. To use the custom API provider,\n# you must explicitly set WEB_SEARCH_PROVIDER=custom.\n#\n# WEB_SEARCH_PROVIDER=auto\n\n# ── Built-in custom API presets ─────────────────────────────────────\n#\n# Use with WEB_KEY for the API key:\n# WEB_PROVIDER=searxng|google|brave|serpapi\n# WEB_KEY=your-api-key-here\n#\n# Google Custom Search additionally requires the Programmable Search Engine ID:\n# WEB_PROVIDER=google\n# WEB_KEY=your-google-api-key\n# GOOGLE_CSE_ID=your-programmable-search-engine-id\n#\n# Note: Google's Custom Search JSON API is closed to new customers and is\n# scheduled for sunset on 2027-01-01. Prefer BRAVE_API_KEY / TAVILY_API_KEY\n# / EXA_API_KEY for new setups.\n\n# ── Custom API endpoint (advanced) ──────────────────────────────────\n#\n# WEB_SEARCH_API — base URL of your search endpoint\n# WEB_QUERY_PARAM — query parameter name (default: \"q\")\n# WEB_METHOD — GET or POST (default: GET)\n# WEB_PARAMS — extra static query params as JSON: {\"lang\":\"en\",\"count\":\"10\"}\n# WEB_URL_TEMPLATE — URL template with {query} for path embedding\n# WEB_BODY_TEMPLATE — custom POST body with {query} placeholder\n# WEB_AUTH_HEADER — header name for API key (default: \"Authorization\")\n# WEB_AUTH_SCHEME — prefix before key (default: \"Bearer\")\n# WEB_HEADERS — extra headers as \"Name: value; Name2: value2\"\n# WEB_JSON_PATH — dot-path to results array in response\n\n# ── Custom API security guardrails ──────────────────────────────────\n#\n# The custom provider enforces security guardrails by default.\n# Override these only if you understand the risks.\n#\n# WEB_CUSTOM_TIMEOUT_SEC=15 — request timeout in seconds (default 15)\n# WEB_CUSTOM_MAX_BODY_KB=300 — max POST body size in KB (default 300)\n# WEB_CUSTOM_ALLOW_ARBITRARY_HEADERS=false — set \"true\" to use non-standard headers\n# WEB_CUSTOM_ALLOW_HTTP=false — set \"true\" to allow http:// URLs\n# WEB_CUSTOM_ALLOW_PRIVATE=false — set \"true\" to target localhost/private IPs\n# (needed for self-hosted SearXNG)\n"}, "b45fea8785f876b796dcd8124fb98f1c7b9c6318": {"t": 1785765442.555429, "n": 19427, "h": "0e5bff0ddcaf8d66", "c": "# =============================================================================\n# AtusCode Environment Configuration\n# =============================================================================\n# Copy this file to .env and fill in your values:\n# cp .env.example .env\n#\n# Only set the variables for the provider you want to use.\n# All other sections can be left commented out.\n# =============================================================================\n\n# =============================================================================\n# SYSTEM-WIDE SETUP (OPTIONAL)\n# =============================================================================\n# Instead of using a .env file per project, you can set these variables\n# system-wide so AtusCode works from any directory on your machine.\n#\n# STEP 1: Pick your provider variables from the list below.\n# STEP 2: Set them using the method for your OS (see further down).\n#\n# ── Provider variables ───────────────────────────────────────────────\n#\n# Option 1 — Anthropic:\n# ANTHROPIC_API_KEY=sk-ant-your-key-here\n# ANTHROPIC_MODEL=claude-sonnet-4-5 (optional)\n# ANTHROPIC_BASE_URL=https://api.anthropic.com (optional)\n#\n# Option 2 — OpenAI:\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_API_KEY=sk-your-key-here\n# OPENAI_MODEL=gpt-4o\n# OPENAI_BASE_URL=https://api.openai.com/v1 (optional)\n#\n# Option 11 — NEAR AI (unified gateway: Claude, GPT, Gemini + TEE models):\n# NEARAI_API_KEY=your_key_here\n# OPENAI_MODEL=anthropic/claude-sonnet-4-6 (optional, default)\n#\n# Option 3 — Google Gemini:\n# CLAUDE_CODE_USE_GEMINI=1\n# GEMINI_API_KEY=your-gemini-key-here\n# GEMINI_MODEL=gemini-2.0-flash\n# GEMINI_BASE_URL=https://generativelanguage.googleapis.com (optional)\n#\n# Option 4 — GitHub Models:\n# CLAUDE_CODE_USE_GITHUB=1\n# GITHUB_TOKEN=ghp_your-token-here\n#\n# Option 5 — Ollama (local):\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_BASE_URL=http://localhost:11434/v1\n# OPENAI_API_KEY=ollama\n# OPENAI_MODEL=llama3.2\n#\n# Option 6 — LM Studio (local):\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_BASE_URL=http://localhost:1234/v1\n# OPENAI_MODEL=your-model-id-here\n# OPENAI_API_KEY=lmstudio (optional)\n#\n# Option 7 — AWS Bedrock (may also need: aws configure):\n# CLAUDE_CODE_USE_BEDROCK=1\n# AWS_REGION=us-east-1\n# AWS_DEFAULT_REGION=us-east-1\n# AWS_BEARER_TOKEN_BEDROCK=your-bearer-token-here\n# ANTHROPIC_BEDROCK_BASE_URL=https://bedrock-runtime.us-east-1.amazonaws.com\n#\n# Option 8 — Google Vertex AI:\n# CLAUDE_CODE_USE_VERTEX=1\n# ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id\n# CLOUD_ML_REGION=us-east5\n# GOOGLE_CLOUD_PROJECT=your-gcp-project-id\n#\n# ── How to set variables on each OS ──────────────────────────────────\n#\n# macOS (zsh):\n# 1. Open: nano ~/.zshrc\n# 2. Add each variable as: export VAR_NAME=value\n# 3. Save and reload: source ~/.zshrc\n#\n# Linux (bash):\n# 1. Open: nano ~/.bashrc\n# 2. Add each variable as: export VAR_NAME=value\n# 3. Save and reload: source ~/.bashrc\n#\n# Windows (PowerShell):\n# Run for each variable:\n# [System.Environment]::SetEnvironmentVariable('VAR_NAME', 'value', 'User')\n# Then restart your terminal.\n#\n# Windows (Command Prompt):\n# Run for each variable:\n# setx VAR_NAME value\n# Then restart your terminal.\n#\n# Windows (GUI):\n# Settings > System > About > Advanced System Settings >\n# Environment Variables > under \"User variables\" click New,\n# then add each variable.\n#\n# ── Important notes ──────────────────────────────────────────────────\n#\n# LOCAL SERVERS: If using LM Studio or Ollama, the server MUST be\n# running with a model loaded before you launch AtusCode —\n# otherwise you'll get connection errors.\n#\n# SWITCHING PROVIDERS: To temporarily switch, unset the relevant\n# variables in your current terminal session:\n#\n# macOS / Linux:\n# unset VAR_NAME\n# # e.g.: unset CLAUDE_CODE_USE_OPENAI OPENAI_BASE_URL OPENAI_MODEL\n#\n# Windows (PowerShell — current session only):\n# Remove-Item Env:VAR_NAME\n#\n# To permanently remove a variable on Windows:\n# [System.Environment]::SetEnvironmentVariable('VAR_NAME', $null, 'User')\n#\n# LOAD ORDER:\n# Shell and system environment variables are inherited by the process.\n# Project .env files are only used if your launcher or shell loads them\n# before starting AtusCode.\n# COMPATIBILITY:\n# System-wide variables work regardless of how you run AtusCode:\n# npx, global npm install, bun run, or node directly. Any process\n# launched from your terminal inherits your shell's environment.\n#\n# REMINDER: Make sure .env is in your .gitignore to avoid committing secrets.\n# =============================================================================\n\n# =============================================================================\n# PROVIDER SELECTION — uncomment ONE block below\n# =============================================================================\n\n# -----------------------------------------------------------------------------\n# Option 1: Anthropic (default — no provider flag needed)\n# -----------------------------------------------------------------------------\nANTHROPIC_API_KEY=sk-ant-your-key-here\n\n# Override the default model (optional)\n# ANTHROPIC_MODEL=claude-sonnet-4-5\n\n# Use a custom Anthropic-compatible endpoint (optional)\n# ANTHROPIC_BASE_URL=https://api.anthropic.com\n\n\n# -----------------------------------------------------------------------------\n# Option 2: OpenAI\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_API_KEY=sk-your-key-here\n# OPENAI_MODEL=gpt-4o\n# For DeepSeek, set:\n# OPENAI_BASE_URL=https://api.deepseek.com/v1\n# OPENAI_MODEL=deepseek-v4-flash\n# Optional: OPENAI_MODEL=deepseek-v4-pro\n# Legacy aliases also work: deepseek-chat and deepseek-reasoner\n# For Z.AI GLM Coding Plan, set:\n# OPENAI_BASE_URL=https://api.z.ai/api/coding/paas/v4\n# OPENAI_MODEL=GLM-5.1\n# Optional: OPENAI_MODEL=GLM-5-Turbo, GLM-4.7, or GLM-4.5-Air\n# For Hicap, use the OpenAI-compatible route flag above and set:\n# HICAP_API_KEY=your-hicap-key-here\n# OPENAI_BASE_URL=https://api.hicap.ai/v1\n# OPENAI_MODEL=claude-opus-4.7\n\n# Use a custom OpenAI-compatible endpoint (optional — defaults to api.openai.com)\n# OPENAI_BASE_URL=https://api.openai.com/v1\n# Choose the OpenAI-compatible API surface (optional — defaults to chat_completions)\n# Supported: chat_completions, responses\n# OPENAI_API_FORMAT=chat_completions\n# Choose a custom auth header for OpenAI-compatible providers (optional).\n# Authorization defaults to Bearer; custom headers default to the raw API key.\n# Set OPENAI_AUTH_HEADER_VALUE when the header value differs from OPENAI_API_KEY.\n# OPENAI_AUTH_HEADER=api-key\n# OPENAI_AUTH_SCHEME=raw\n# OPENAI_AUTH_HEADER_VALUE=your-header-value-here\n\n# Fallback context window size (tokens) when the model is not found in\n# integration model metadata (default: 128000). Increase this for models with larger\n# context windows (e.g. 200000 for Claude-sized contexts).\n# CLAUDE_CODE_OPENAI_FALLBACK_CONTEXT_WINDOW=128000\n\n# Per-model context window overrides as a JSON object.\n# Takes precedence over integration model metadata, so you can register new or\n# custom models without patching source.\n# Example: CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS={\"my-corp/llm-v3\":262144,\"gpt-4o-mini\":128000}\n# CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS=\n\n# Per-model maximum output token overrides as a JSON object.\n# Use this alongside CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS when your model\n# supports a different output limit than what integration metadata specifies.\n# Example: CLAUDE_CODE_OPENAI_MAX_OUTPUT_TOKENS={\"my-corp/llm-v3\":8192}\n# CLAUDE_CODE_OPENAI_MAX_OUTPUT_TOKENS=\n\n\n# -----------------------------------------------------------------------------\n# Option 3: Google Gemini\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_GEMINI=1\n# GEMINI_API_KEY=your-gemini-key-here\n# GEMINI_MODEL=gemini-2.0-flash\n\n# Use a custom Gemini endpoint (optional)\n# GEMINI_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai\n\n\n# -----------------------------------------------------------------------------\n# Option 4: GitHub Models\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_GITHUB=1\n# GITHUB_TOKEN=ghp_your-token-here\n\n\n# -----------------------------------------------------------------------------\n# Option 5: Ollama (local models)\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_BASE_URL=http://localhost:11434/v1\n# OPENAI_API_KEY=ollama\n# OPENAI_MODEL=llama3.2\n\n# -----------------------------------------------------------------------------\n# Option 6: LM Studio (local models)\n# -----------------------------------------------------------------------------\n# LM Studio exposes an OpenAI-compatible API, so we use the OpenAI provider.\n# Make sure LM Studio is running with the Developer server enabled\n# (Developer tab > toggle server ON).\n#\n# Steps:\n# 1. Download and install LM Studio from https://lmstudio.ai\n# 2. Search for and download a model (e.g. any coding or instruct model)\n# 3. Load the model and start the Developer server\n# 4. Set OPENAI_MODEL to the model ID shown in LM Studio's Developer tab\n#\n# The default server URL is http://localhost:1234 — change the port below\n# if you've configured a different one in LM Studio.\n#\n# OPENAI_API_KEY is optional — LM Studio runs locally and ignores it.\n# Some clients require a non-empty value; if you get auth errors, set it\n# to any dummy value (e.g. \"lmstudio\").\n#\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_BASE_URL=http://localhost:1234/v1\n# OPENAI_MODEL=your-model-id-here\n\n\n# -----------------------------------------------------------------------------\n# Option 7: AWS Bedrock\n# -----------------------------------------------------------------------------\n\n# You may also need AWS CLI credentials configured (run: aws configure)\n# or have AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY set in your\n# environment in addition to the variables below.\n#\n# CLAUDE_CODE_USE_BEDROCK=1\n# AWS_REGION=us-east-1\n# AWS_DEFAULT_REGION=us-east-1\n# AWS_BEARER_TOKEN_BEDROCK=your-bearer-token-here\n# ANTHROPIC_BEDROCK_BASE_URL=https://bedrock-runtime.us-east-1.amazonaws.com\n\n\n# -----------------------------------------------------------------------------\n# Option 8: Google Vertex AI\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_VERTEX=1\n# ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id\n# CLOUD_ML_REGION=us-east5\n# GOOGLE_CLOUD_PROJECT=your-gcp-project-id\n\n\n# -----------------------------------------------------------------------------\n# Option 9: NVIDIA NIM\n# -----------------------------------------------------------------------------\n# NVIDIA NIM provides hosted inference endpoints for NVIDIA models.\n# Get your API key from https://build.nvidia.com/\n#\n# CLAUDE_CODE_USE_OPENAI=1\n# NVIDIA_API_KEY=nvapi-your-key-here\n# OPENAI_BASE_URL=https://integrate.api.nvidia.com/v1\n# OPENAI_MODEL=nvidia/llama-3.1-nemotron-70b-instruct\n\n\n# -----------------------------------------------------------------------------\n# Option 10: MiniMax\n# -----------------------------------------------------------------------------\n# MiniMax API provides text generation models.\n# Get your API key from https://platform.minimax.io/\n#\n# MINIMAX_API_KEY=your-minimax-key-here\n# ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic\n# ANTHROPIC_MODEL=MiniMax-M2.7\n\n\n# -----------------------------------------------------------------------------\n# Option 11: NEAR AI\n# -----------------------------------------------------------------------------\n# NEAR AI is a unified OpenAI-compatible gateway to Claude, GPT, Gemini,\n# and TEE-hosted open models — all under one API key.\n# Get your API key from https://cloud.near.ai/dashboard/organizations\n#\n# NEARAI_API_KEY=your-nearai-key-here\n# OPENAI_BASE_URL=https://cloud-api.near.ai/v1 (optional, default)\n# OPENAI_MODEL=anthropic/claude-sonnet-4-6 (optional, default)\n#\n# For direct TEE completions (lower latency + verifiable privacy):\n# OPENAI_BASE_URL=https://qwen35-122b.completions.near.ai/v1\n\n\n# -----------------------------------------------------------------------------\n# Option 12: Fireworks AI\n# -----------------------------------------------------------------------------\n# Fireworks AI provides a fully OpenAI-compatible endpoint.\n# Get your API key from https://fireworks.ai/\n# Model IDs use the full path format: accounts/fireworks/models/\n#\n# CLAUDE_CODE_USE_OPENAI=1\n# FIREWORKS_API_KEY=fw_your_key_here\n# OPENAI_BASE_URL=https://api.fireworks.ai/inference/v1\n# OPENAI_MODEL=accounts/fireworks/models/llama-v3p1-70b-instruct\n\n\n# =============================================================================\n# OPTIONAL TUNING\n# =============================================================================\n\n# Max number of API retries on failure (default: 10, cap: 100)\n# Set to 0 to disable retries after the initial request\n# Deprecated fallback when ATUSCODE_MAX_RETRIES is unset: CLAUDE_CODE_MAX_RETRIES\n# ATUSCODE_MAX_RETRIES=10\n\n# Base retry delay in milliseconds when the API does not send Retry-After\n# Uses exponential backoff from this value with jitter (default: 500, cap: 60000)\n# ATUSCODE_RETRY_DELAY_MS=500\n\n# Enable persistent retry mode for unattended/CI sessions\n# Retries 429/529 indefinitely with smart backoff\n# CLAUDE_CODE_UNATTENDED_RETRY=1\n\n# Enable extended key reporting (Kitty keyboard protocol)\n# Useful for iTerm2, WezTerm, Ghostty if modifier keys feel off\n# ATUSCODE_ENABLE_EXTENDED_KEYS=1\n\n# Disable \"Co-authored-by\" line in git commits made by AtusCode\n# ATUSCODE_DISABLE_CO_AUTHORED_BY=1\n\n# Disable strict tool schema normalization for non-Gemini providers\n# Useful when MCP tools with complex optional params (e.g. list[dict])\n# trigger \"Extra required key ... supplied\" errors from OpenAI-compatible endpoints\n# ATUSCODE_DISABLE_STRICT_TOOLS=1\n\n# Disable hidden messages injected into tool output\n# Suppresses the file-read cyber-risk reminder and the todo/task tool nudges\n# Useful for users who want full transparency over what the model sees\n# ATUSCODE_DISABLE_TOOL_REMINDERS=1\n\n# Log structured per-request token usage (including cache metrics) to stderr.\n# Useful for auditing cache hit rate / debugging cost spikes outside the REPL.\n# Any truthy value enables it (\"verbose\", \"1\", \"true\").\n#\n# Complements (does NOT replace) CLAUDE_CODE_ENABLE_TOKEN_USAGE_ATTACHMENT —\n# they serve different audiences:\n# - ATUSCODE_LOG_TOKEN_USAGE is user-facing: one JSON line per API\n# request on stderr, intended for humans inspecting cost/caching.\n# - CLAUDE_CODE_ENABLE_TOKEN_USAGE_ATTACHMENT is model-facing: injects\n# a context-usage attachment INTO the prompt so the model can reason\n# about its own remaining context. Does not touch stderr.\n# Turn on whichever audience you're debugging; both can run together.\n# ATUSCODE_LOG_TOKEN_USAGE=verbose\n\n# Custom timeout for API requests in milliseconds (default: varies)\n# API_TIMEOUT_MS=60000\n\n# Enable debug logging\n# CLAUDE_DEBUG=1\n\n\n# =============================================================================\n# WEB SEARCH (OPTIONAL)\n# =============================================================================\n# AtusCode includes a web search tool. By default it uses DuckDuckGo (free)\n# or the provider's native search (Anthropic firstParty / vertex).\n#\n# Set one API key below to enable a provider. That's it.\n\n# ── Provider API keys — set ONE of these ────────────────────────────\n\n# Tavily (AI-optimized search, recommended)\n# TAVILY_API_KEY=tvly-your-key-here\n\n# Exa (neural/semantic search)\n# EXA_API_KEY=your-exa-key-here\n\n# You.com (RAG-ready snippets)\n# YOU_API_KEY=your-you-key-here\n\n# Jina (s.jina.ai endpoint)\n# JINA_API_KEY=your-jina-key-here\n\n# Brave (independent web index, generous free tier)\n# BRAVE_API_KEY=your-brave-key-here\n\n# Bing Web Search\n# BING_API_KEY=your-bing-key-here\n\n# Mojeek (privacy-focused)\n# MOJEEK_API_KEY=your-mojeek-key-here\n\n# Linkup\n# LINKUP_API_KEY=your-linkup-key-here\n\n# Firecrawl (premium, uses @mendable/firecrawl-js)\n# FIRECRAWL_API_KEY=fc-your-key-here\n\n# Self-hosted Firecrawl endpoint (optional — omit to use cloud API)\n# FIRECRAWL_API_URL=https://your-firecrawl-instance.com\n\n# ── Provider selection mode ─────────────────────────────────────────\n#\n# WEB_SEARCH_PROVIDER controls fallback behavior:\n#\n# \"auto\" (default) — try all configured providers, fall through on failure\n# \"custom\" — custom API only, throw on failure (NOT in auto chain)\n# \"firecrawl\" — firecrawl only\n# \"tavily\" — tavily only\n# \"exa\" — exa only\n# \"you\" — you.com only\n# \"jina\" — jina only\n# \"brave\" — brave only\n# \"bing\" — bing only\n# \"mojeek\" — mojeek only\n# \"linkup\" — linkup only\n# \"ddg\" — duckduckgo only\n# \"native\" — anthropic native / codex only\n#\n# Auto mode priority: firecrawl → tavily → exa → you → jina → brave → bing →\n# mojeek → linkup → ddg\n# Note: \"custom\" is NOT in the auto chain. To use the custom API provider,\n# you must explicitly set WEB_SEARCH_PROVIDER=custom.\n#\n# WEB_SEARCH_PROVIDER=auto\n\n# ── Built-in custom API presets ─────────────────────────────────────\n#\n# Use with WEB_KEY for the API key:\n# WEB_PROVIDER=searxng|google|brave|serpapi\n# WEB_KEY=your-api-key-here\n#\n# Google Custom Search additionally requires the Programmable Search Engine ID:\n# WEB_PROVIDER=google\n# WEB_KEY=your-google-api-key\n# GOOGLE_CSE_ID=your-programmable-search-engine-id\n#\n# Note: Google's Custom Search JSON API is closed to new customers and is\n# scheduled for sunset on 2027-01-01. Prefer BRAVE_API_KEY / TAVILY_API_KEY\n# / EXA_API_KEY for new setups.\n\n# ── Custom API endpoint (advanced) ──────────────────────────────────\n#\n# WEB_SEARCH_API — base URL of your search endpoint\n# WEB_QUERY_PARAM — query parameter name (default: \"q\")\n# WEB_METHOD — GET or POST (default: GET)\n# WEB_PARAMS — extra static query params as JSON: {\"lang\":\"en\",\"count\":\"10\"}\n# WEB_URL_TEMPLATE — URL template with {query} for path embedding\n# WEB_BODY_TEMPLATE — custom POST body with {query} placeholder\n# WEB_AUTH_HEADER — header name for API key (default: \"Authorization\")\n# WEB_AUTH_SCHEME — prefix before key (default: \"Bearer\")\n# WEB_HEADERS — extra headers as \"Name: value; Name2: value2\"\n# WEB_JSON_PATH — dot-path to results array in response\n\n# ── Custom API security guardrails ──────────────────────────────────\n#\n# The custom provider enforces security guardrails by default.\n# Override these only if you understand the risks.\n#\n# WEB_CUSTOM_TIMEOUT_SEC=15 — request timeout in seconds (default 15)\n# WEB_CUSTOM_MAX_BODY_KB=300 — max POST body size in KB (default 300)\n# WEB_CUSTOM_ALLOW_ARBITRARY_HEADERS=false — set \"true\" to use non-standard headers\n# WEB_CUSTOM_ALLOW_HTTP=false — set \"true\" to allow http:// URLs\n# WEB_CUSTOM_ALLOW_PRIVATE=false — set \"true\" to target localhost/private IPs\n# (needed for self-hosted SearXNG)\n"}, "f463d026161babdfab071ace78f9f740c8896bdb": {"t": 1785765444.0120523, "n": 19459, "h": "a87aeaa80afcd7be", "c": "# =============================================================================\n# OpenClaude Environment Configuration\n# =============================================================================\n# Copy this file to .env and fill in your values:\n# cp .env.example .env\n#\n# Only set the variables for the provider you want to use.\n# All other sections can be left commented out.\n# =============================================================================\n\n# =============================================================================\n# SYSTEM-WIDE SETUP (OPTIONAL)\n# =============================================================================\n# Instead of using a .env file per project, you can set these variables\n# system-wide so OpenClaude works from any directory on your machine.\n#\n# STEP 1: Pick your provider variables from the list below.\n# STEP 2: Set them using the method for your OS (see further down).\n#\n# ── Provider variables ───────────────────────────────────────────────\n#\n# Option 1 — Anthropic:\n# ANTHROPIC_API_KEY=sk-ant-your-key-here\n# ANTHROPIC_MODEL=claude-sonnet-4-5 (optional)\n# ANTHROPIC_BASE_URL=https://api.anthropic.com (optional)\n#\n# Option 2 — OpenAI:\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_API_KEY=sk-your-key-here\n# OPENAI_MODEL=gpt-4o\n# OPENAI_BASE_URL=https://api.openai.com/v1 (optional)\n#\n# Option 11 — NEAR AI (unified gateway: Claude, GPT, Gemini + TEE models):\n# NEARAI_API_KEY=your_key_here\n# OPENAI_MODEL=anthropic/claude-sonnet-4-6 (optional, default)\n#\n# Option 3 — Google Gemini:\n# CLAUDE_CODE_USE_GEMINI=1\n# GEMINI_API_KEY=your-gemini-key-here\n# GEMINI_MODEL=gemini-2.0-flash\n# GEMINI_BASE_URL=https://generativelanguage.googleapis.com (optional)\n#\n# Option 4 — GitHub Models:\n# CLAUDE_CODE_USE_GITHUB=1\n# GITHUB_TOKEN=ghp_your-token-here\n#\n# Option 5 — Ollama (local):\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_BASE_URL=http://localhost:11434/v1\n# OPENAI_API_KEY=ollama\n# OPENAI_MODEL=llama3.2\n#\n# Option 6 — LM Studio (local):\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_BASE_URL=http://localhost:1234/v1\n# OPENAI_MODEL=your-model-id-here\n# OPENAI_API_KEY=lmstudio (optional)\n#\n# Option 7 — AWS Bedrock (may also need: aws configure):\n# CLAUDE_CODE_USE_BEDROCK=1\n# AWS_REGION=us-east-1\n# AWS_DEFAULT_REGION=us-east-1\n# AWS_BEARER_TOKEN_BEDROCK=your-bearer-token-here\n# ANTHROPIC_BEDROCK_BASE_URL=https://bedrock-runtime.us-east-1.amazonaws.com\n#\n# Option 8 — Google Vertex AI:\n# CLAUDE_CODE_USE_VERTEX=1\n# ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id\n# CLOUD_ML_REGION=us-east5\n# GOOGLE_CLOUD_PROJECT=your-gcp-project-id\n#\n# ── How to set variables on each OS ──────────────────────────────────\n#\n# macOS (zsh):\n# 1. Open: nano ~/.zshrc\n# 2. Add each variable as: export VAR_NAME=value\n# 3. Save and reload: source ~/.zshrc\n#\n# Linux (bash):\n# 1. Open: nano ~/.bashrc\n# 2. Add each variable as: export VAR_NAME=value\n# 3. Save and reload: source ~/.bashrc\n#\n# Windows (PowerShell):\n# Run for each variable:\n# [System.Environment]::SetEnvironmentVariable('VAR_NAME', 'value', 'User')\n# Then restart your terminal.\n#\n# Windows (Command Prompt):\n# Run for each variable:\n# setx VAR_NAME value\n# Then restart your terminal.\n#\n# Windows (GUI):\n# Settings > System > About > Advanced System Settings >\n# Environment Variables > under \"User variables\" click New,\n# then add each variable.\n#\n# ── Important notes ──────────────────────────────────────────────────\n#\n# LOCAL SERVERS: If using LM Studio or Ollama, the server MUST be\n# running with a model loaded before you launch OpenClaude —\n# otherwise you'll get connection errors.\n#\n# SWITCHING PROVIDERS: To temporarily switch, unset the relevant\n# variables in your current terminal session:\n#\n# macOS / Linux:\n# unset VAR_NAME\n# # e.g.: unset CLAUDE_CODE_USE_OPENAI OPENAI_BASE_URL OPENAI_MODEL\n#\n# Windows (PowerShell — current session only):\n# Remove-Item Env:VAR_NAME\n#\n# To permanently remove a variable on Windows:\n# [System.Environment]::SetEnvironmentVariable('VAR_NAME', $null, 'User')\n#\n# LOAD ORDER:\n# Shell and system environment variables are inherited by the process.\n# Project .env files are only used if your launcher or shell loads them\n# before starting OpenClaude.\n# COMPATIBILITY:\n# System-wide variables work regardless of how you run OpenClaude:\n# npx, global npm install, bun run, or node directly. Any process\n# launched from your terminal inherits your shell's environment.\n#\n# REMINDER: Make sure .env is in your .gitignore to avoid committing secrets.\n# =============================================================================\n\n# =============================================================================\n# PROVIDER SELECTION — uncomment ONE block below\n# =============================================================================\n\n# -----------------------------------------------------------------------------\n# Option 1: Anthropic (default — no provider flag needed)\n# -----------------------------------------------------------------------------\nANTHROPIC_API_KEY=sk-ant-your-key-here\n\n# Override the default model (optional)\n# ANTHROPIC_MODEL=claude-sonnet-4-5\n\n# Use a custom Anthropic-compatible endpoint (optional)\n# ANTHROPIC_BASE_URL=https://api.anthropic.com\n\n\n# -----------------------------------------------------------------------------\n# Option 2: OpenAI\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_API_KEY=sk-your-key-here\n# OPENAI_MODEL=gpt-4o\n# For DeepSeek, set:\n# OPENAI_BASE_URL=https://api.deepseek.com/v1\n# OPENAI_MODEL=deepseek-v4-flash\n# Optional: OPENAI_MODEL=deepseek-v4-pro\n# Legacy aliases also work: deepseek-chat and deepseek-reasoner\n# For Z.AI GLM Coding Plan, set:\n# OPENAI_BASE_URL=https://api.z.ai/api/coding/paas/v4\n# OPENAI_MODEL=GLM-5.1\n# Optional: OPENAI_MODEL=GLM-5-Turbo, GLM-4.7, or GLM-4.5-Air\n# For Hicap, use the OpenAI-compatible route flag above and set:\n# HICAP_API_KEY=your-hicap-key-here\n# OPENAI_BASE_URL=https://api.hicap.ai/v1\n# OPENAI_MODEL=claude-opus-4.7\n\n# Use a custom OpenAI-compatible endpoint (optional — defaults to api.openai.com)\n# OPENAI_BASE_URL=https://api.openai.com/v1\n# Choose the OpenAI-compatible API surface (optional — defaults to chat_completions)\n# Supported: chat_completions, responses\n# OPENAI_API_FORMAT=chat_completions\n# Choose a custom auth header for OpenAI-compatible providers (optional).\n# Authorization defaults to Bearer; custom headers default to the raw API key.\n# Set OPENAI_AUTH_HEADER_VALUE when the header value differs from OPENAI_API_KEY.\n# OPENAI_AUTH_HEADER=api-key\n# OPENAI_AUTH_SCHEME=raw\n# OPENAI_AUTH_HEADER_VALUE=your-header-value-here\n\n# Fallback context window size (tokens) when the model is not found in\n# integration model metadata (default: 128000). Increase this for models with larger\n# context windows (e.g. 200000 for Claude-sized contexts).\n# CLAUDE_CODE_OPENAI_FALLBACK_CONTEXT_WINDOW=128000\n\n# Per-model context window overrides as a JSON object.\n# Takes precedence over integration model metadata, so you can register new or\n# custom models without patching source.\n# Example: CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS={\"my-corp/llm-v3\":262144,\"gpt-4o-mini\":128000}\n# CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS=\n\n# Per-model maximum output token overrides as a JSON object.\n# Use this alongside CLAUDE_CODE_OPENAI_CONTEXT_WINDOWS when your model\n# supports a different output limit than what integration metadata specifies.\n# Example: CLAUDE_CODE_OPENAI_MAX_OUTPUT_TOKENS={\"my-corp/llm-v3\":8192}\n# CLAUDE_CODE_OPENAI_MAX_OUTPUT_TOKENS=\n\n\n# -----------------------------------------------------------------------------\n# Option 3: Google Gemini\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_GEMINI=1\n# GEMINI_API_KEY=your-gemini-key-here\n# GEMINI_MODEL=gemini-2.0-flash\n\n# Use a custom Gemini endpoint (optional)\n# GEMINI_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai\n\n\n# -----------------------------------------------------------------------------\n# Option 4: GitHub Models\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_GITHUB=1\n# GITHUB_TOKEN=ghp_your-token-here\n\n\n# -----------------------------------------------------------------------------\n# Option 5: Ollama (local models)\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_BASE_URL=http://localhost:11434/v1\n# OPENAI_API_KEY=ollama\n# OPENAI_MODEL=llama3.2\n\n# -----------------------------------------------------------------------------\n# Option 6: LM Studio (local models)\n# -----------------------------------------------------------------------------\n# LM Studio exposes an OpenAI-compatible API, so we use the OpenAI provider.\n# Make sure LM Studio is running with the Developer server enabled\n# (Developer tab > toggle server ON).\n#\n# Steps:\n# 1. Download and install LM Studio from https://lmstudio.ai\n# 2. Search for and download a model (e.g. any coding or instruct model)\n# 3. Load the model and start the Developer server\n# 4. Set OPENAI_MODEL to the model ID shown in LM Studio's Developer tab\n#\n# The default server URL is http://localhost:1234 — change the port below\n# if you've configured a different one in LM Studio.\n#\n# OPENAI_API_KEY is optional — LM Studio runs locally and ignores it.\n# Some clients require a non-empty value; if you get auth errors, set it\n# to any dummy value (e.g. \"lmstudio\").\n#\n# CLAUDE_CODE_USE_OPENAI=1\n# OPENAI_BASE_URL=http://localhost:1234/v1\n# OPENAI_MODEL=your-model-id-here\n\n\n# -----------------------------------------------------------------------------\n# Option 7: AWS Bedrock\n# -----------------------------------------------------------------------------\n\n# You may also need AWS CLI credentials configured (run: aws configure)\n# or have AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY set in your\n# environment in addition to the variables below.\n#\n# CLAUDE_CODE_USE_BEDROCK=1\n# AWS_REGION=us-east-1\n# AWS_DEFAULT_REGION=us-east-1\n# AWS_BEARER_TOKEN_BEDROCK=your-bearer-token-here\n# ANTHROPIC_BEDROCK_BASE_URL=https://bedrock-runtime.us-east-1.amazonaws.com\n\n\n# -----------------------------------------------------------------------------\n# Option 8: Google Vertex AI\n# -----------------------------------------------------------------------------\n# CLAUDE_CODE_USE_VERTEX=1\n# ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id\n# CLOUD_ML_REGION=us-east5\n# GOOGLE_CLOUD_PROJECT=your-gcp-project-id\n\n\n# -----------------------------------------------------------------------------\n# Option 9: NVIDIA NIM\n# -----------------------------------------------------------------------------\n# NVIDIA NIM provides hosted inference endpoints for NVIDIA models.\n# Get your API key from https://build.nvidia.com/\n#\n# CLAUDE_CODE_USE_OPENAI=1\n# NVIDIA_API_KEY=nvapi-your-key-here\n# OPENAI_BASE_URL=https://integrate.api.nvidia.com/v1\n# OPENAI_MODEL=nvidia/llama-3.1-nemotron-70b-instruct\n\n\n# -----------------------------------------------------------------------------\n# Option 10: MiniMax\n# -----------------------------------------------------------------------------\n# MiniMax API provides text generation models.\n# Get your API key from https://platform.minimax.io/\n#\n# MINIMAX_API_KEY=your-minimax-key-here\n# ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic\n# ANTHROPIC_MODEL=MiniMax-M2.7\n\n\n# -----------------------------------------------------------------------------\n# Option 11: NEAR AI\n# -----------------------------------------------------------------------------\n# NEAR AI is a unified OpenAI-compatible gateway to Claude, GPT, Gemini,\n# and TEE-hosted open models — all under one API key.\n# Get your API key from https://cloud.near.ai/dashboard/organizations\n#\n# NEARAI_API_KEY=your-nearai-key-here\n# OPENAI_BASE_URL=https://cloud-api.near.ai/v1 (optional, default)\n# OPENAI_MODEL=anthropic/claude-sonnet-4-6 (optional, default)\n#\n# For direct TEE completions (lower latency + verifiable privacy):\n# OPENAI_BASE_URL=https://qwen35-122b.completions.near.ai/v1\n\n\n# -----------------------------------------------------------------------------\n# Option 12: Fireworks AI\n# -----------------------------------------------------------------------------\n# Fireworks AI provides a fully OpenAI-compatible endpoint.\n# Get your API key from https://fireworks.ai/\n# Model IDs use the full path format: accounts/fireworks/models/\n#\n# CLAUDE_CODE_USE_OPENAI=1\n# FIREWORKS_API_KEY=fw_your_key_here\n# OPENAI_BASE_URL=https://api.fireworks.ai/inference/v1\n# OPENAI_MODEL=accounts/fireworks/models/llama-v3p1-70b-instruct\n\n\n# =============================================================================\n# OPTIONAL TUNING\n# =============================================================================\n\n# Max number of API retries on failure (default: 10, cap: 100)\n# Set to 0 to disable retries after the initial request\n# Deprecated fallback when OPENCLAUDE_MAX_RETRIES is unset: CLAUDE_CODE_MAX_RETRIES\n# OPENCLAUDE_MAX_RETRIES=10\n\n# Base retry delay in milliseconds when the API does not send Retry-After\n# Uses exponential backoff from this value with jitter (default: 500, cap: 60000)\n# OPENCLAUDE_RETRY_DELAY_MS=500\n\n# Enable persistent retry mode for unattended/CI sessions\n# Retries 429/529 indefinitely with smart backoff\n# CLAUDE_CODE_UNATTENDED_RETRY=1\n\n# Enable extended key reporting (Kitty keyboard protocol)\n# Useful for iTerm2, WezTerm, Ghostty if modifier keys feel off\n# OPENCLAUDE_ENABLE_EXTENDED_KEYS=1\n\n# Disable \"Co-authored-by\" line in git commits made by OpenClaude\n# OPENCLAUDE_DISABLE_CO_AUTHORED_BY=1\n\n# Disable strict tool schema normalization for non-Gemini providers\n# Useful when MCP tools with complex optional params (e.g. list[dict])\n# trigger \"Extra required key ... supplied\" errors from OpenAI-compatible endpoints\n# OPENCLAUDE_DISABLE_STRICT_TOOLS=1\n\n# Disable hidden messages injected into tool output\n# Suppresses the file-read cyber-risk reminder and the todo/task tool nudges\n# Useful for users who want full transparency over what the model sees\n# OPENCLAUDE_DISABLE_TOOL_REMINDERS=1\n\n# Log structured per-request token usage (including cache metrics) to stderr.\n# Useful for auditing cache hit rate / debugging cost spikes outside the REPL.\n# Any truthy value enables it (\"verbose\", \"1\", \"true\").\n#\n# Complements (does NOT replace) CLAUDE_CODE_ENABLE_TOKEN_USAGE_ATTACHMENT —\n# they serve different audiences:\n# - OPENCLAUDE_LOG_TOKEN_USAGE is user-facing: one JSON line per API\n# request on stderr, intended for humans inspecting cost/caching.\n# - CLAUDE_CODE_ENABLE_TOKEN_USAGE_ATTACHMENT is model-facing: injects\n# a context-usage attachment INTO the prompt so the model can reason\n# about its own remaining context. Does not touch stderr.\n# Turn on whichever audience you're debugging; both can run together.\n# OPENCLAUDE_LOG_TOKEN_USAGE=verbose\n\n# Custom timeout for API requests in milliseconds (default: varies)\n# API_TIMEOUT_MS=60000\n\n# Enable debug logging\n# CLAUDE_DEBUG=1\n\n\n# =============================================================================\n# WEB SEARCH (OPTIONAL)\n# =============================================================================\n# OpenClaude includes a web search tool. By default it uses DuckDuckGo (free)\n# or the provider's native search (Anthropic firstParty / vertex).\n#\n# Set one API key below to enable a provider. That's it.\n\n# ── Provider API keys — set ONE of these ────────────────────────────\n\n# Tavily (AI-optimized search, recommended)\n# TAVILY_API_KEY=tvly-your-key-here\n\n# Exa (neural/semantic search)\n# EXA_API_KEY=your-exa-key-here\n\n# You.com (RAG-ready snippets)\n# YOU_API_KEY=your-you-key-here\n\n# Jina (s.jina.ai endpoint)\n# JINA_API_KEY=your-jina-key-here\n\n# Brave (independent web index, generous free tier)\n# BRAVE_API_KEY=your-brave-key-here\n\n# Bing Web Search\n# BING_API_KEY=your-bing-key-here\n\n# Mojeek (privacy-focused)\n# MOJEEK_API_KEY=your-mojeek-key-here\n\n# Linkup\n# LINKUP_API_KEY=your-linkup-key-here\n\n# Firecrawl (premium, uses @mendable/firecrawl-js)\n# FIRECRAWL_API_KEY=fc-your-key-here\n\n# Self-hosted Firecrawl endpoint (optional — omit to use cloud API)\n# FIRECRAWL_API_URL=https://your-firecrawl-instance.com\n\n# ── Provider selection mode ─────────────────────────────────────────\n#\n# WEB_SEARCH_PROVIDER controls fallback behavior:\n#\n# \"auto\" (default) — try all configured providers, fall through on failure\n# \"custom\" — custom API only, throw on failure (NOT in auto chain)\n# \"firecrawl\" — firecrawl only\n# \"tavily\" — tavily only\n# \"exa\" — exa only\n# \"you\" — you.com only\n# \"jina\" — jina only\n# \"brave\" — brave only\n# \"bing\" — bing only\n# \"mojeek\" — mojeek only\n# \"linkup\" — linkup only\n# \"ddg\" — duckduckgo only\n# \"native\" — anthropic native / codex only\n#\n# Auto mode priority: firecrawl → tavily → exa → you → jina → brave → bing →\n# mojeek → linkup → ddg\n# Note: \"custom\" is NOT in the auto chain. To use the custom API provider,\n# you must explicitly set WEB_SEARCH_PROVIDER=custom.\n#\n# WEB_SEARCH_PROVIDER=auto\n\n# ── Built-in custom API presets ─────────────────────────────────────\n#\n# Use with WEB_KEY for the API key:\n# WEB_PROVIDER=searxng|google|brave|serpapi\n# WEB_KEY=your-api-key-here\n#\n# Google Custom Search additionally requires the Programmable Search Engine ID:\n# WEB_PROVIDER=google\n# WEB_KEY=your-google-api-key\n# GOOGLE_CSE_ID=your-programmable-search-engine-id\n#\n# Note: Google's Custom Search JSON API is closed to new customers and is\n# scheduled for sunset on 2027-01-01. Prefer BRAVE_API_KEY / TAVILY_API_KEY\n# / EXA_API_KEY for new setups.\n\n# ── Custom API endpoint (advanced) ──────────────────────────────────\n#\n# WEB_SEARCH_API — base URL of your search endpoint\n# WEB_QUERY_PARAM — query parameter name (default: \"q\")\n# WEB_METHOD — GET or POST (default: GET)\n# WEB_PARAMS — extra static query params as JSON: {\"lang\":\"en\",\"count\":\"10\"}\n# WEB_URL_TEMPLATE — URL template with {query} for path embedding\n# WEB_BODY_TEMPLATE — custom POST body with {query} placeholder\n# WEB_AUTH_HEADER — header name for API key (default: \"Authorization\")\n# WEB_AUTH_SCHEME — prefix before key (default: \"Bearer\")\n# WEB_HEADERS — extra headers as \"Name: value; Name2: value2\"\n# WEB_JSON_PATH — dot-path to results array in response\n\n# ── Custom API security guardrails ──────────────────────────────────\n#\n# The custom provider enforces security guardrails by default.\n# Override these only if you understand the risks.\n#\n# WEB_CUSTOM_TIMEOUT_SEC=15 — request timeout in seconds (default 15)\n# WEB_CUSTOM_MAX_BODY_KB=300 — max POST body size in KB (default 300)\n# WEB_CUSTOM_ALLOW_ARBITRARY_HEADERS=false — set \"true\" to use non-standard headers\n# WEB_CUSTOM_ALLOW_HTTP=false — set \"true\" to allow http:// URLs\n# WEB_CUSTOM_ALLOW_PRIVATE=false — set \"true\" to target localhost/private IPs\n# (needed for self-hosted SearXNG)\n"}}, "src/utils/model/agent.test.ts": {"b2e10afbdf8cb6ae97322d1ef80e9140f5173378": {"t": 1786068364.2609353, "n": 20359, "h": "f91a171ccd61e7c3", "c": "import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'\nimport {\n acquireSharedMutationLock,\n releaseSharedMutationLock,\n} from '../../test/sharedMutationLock.js'\n\nconst originalSubagentModel = process.env.CLAUDE_CODE_SUBAGENT_MODEL\nconst originalOpenAIModel = process.env.OPENAI_MODEL\nconst originalDefaultModelEnv = {\n ANTHROPIC_DEFAULT_OPUS_MODEL: process.env.ANTHROPIC_DEFAULT_OPUS_MODEL,\n ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES:\n process.env.ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES,\n ANTHROPIC_DEFAULT_SONNET_MODEL: process.env.ANTHROPIC_DEFAULT_SONNET_MODEL,\n ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES:\n process.env.ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES,\n ANTHROPIC_DEFAULT_HAIKU_MODEL: process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL,\n ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES:\n process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES,\n}\nconst allowedModelsRef: { value?: string[] } = { value: undefined }\n\ntype MockProvider =\n | 'firstParty'\n | 'bedrock'\n | 'vertex'\n | 'foundry'\n | 'openai'\n | 'gemini'\n | 'mistral'\n | 'github'\n | 'nvidia-nim'\n | 'minimax'\n | 'codex'\n\nfunction mockProvider(\n provider: MockProvider,\n isFirstPartyAnthropicBaseUrl = false,\n): void {\n mock.module('./providers.js', () => ({\n getAPIProvider: () => provider,\n getAPIProviderForStatsig: () => provider,\n isFirstPartyAnthropicBaseUrl: () => isFirstPartyAnthropicBaseUrl,\n isGithubNativeAnthropicMode: () => false,\n usesAnthropicAccountFlow: () => provider === 'firstParty',\n }))\n}\n\nfunction setAvailableModelsForTest(availableModels?: string[]): void {\n allowedModelsRef.value = availableModels\n}\n\n// Keep resolver tests independent from process-global settings mocks in other\n// test files; modelAllowlist itself owns the detailed allowlist semantics.\nfunction mockModelAllowlist(): void {\n mock.module('./modelAllowlist.js', () => ({\n isModelAllowed: (model: string) => {\n const availableModels = allowedModelsRef.value\n if (availableModels === undefined) return true\n if (availableModels.length === 0) return false\n\n const normalizedModel = model.trim().toLowerCase()\n return availableModels.some(\n availableModel =>\n availableModel.trim().toLowerCase() === normalizedModel,\n )\n },\n }))\n}\n\nasync function importAgentModule(): Promise {\n const stamp = `${Date.now()}-${Math.random()}`\n return import(`./agent.ts?agent-test=${stamp}`)\n}\n\ndescribe('getAgentModel provider-aware fallback', () => {\n beforeEach(async () => {\n await acquireSharedMutationLock('utils/model/agent.test.ts')\n delete process.env.CLAUDE_CODE_SUBAGENT_MODEL\n delete process.env.OPENAI_MODEL\n for (const key of Object.keys(originalDefaultModelEnv)) {\n delete process.env[key]\n }\n setAvailableModelsForTest()\n mockModelAllowlist()\n })\n\n // Restore all mocks after each test\n afterEach(() => {\n try {\n mock.restore()\n setAvailableModelsForTest()\n if (originalSubagentModel === undefined) {\n delete process.env.CLAUDE_CODE_SUBAGENT_MODEL\n } else {\n process.env.CLAUDE_CODE_SUBAGENT_MODEL = originalSubagentModel\n }\n if (originalOpenAIModel === undefined) {\n delete process.env.OPENAI_MODEL\n } else {\n process.env.OPENAI_MODEL = originalOpenAIModel\n }\n for (const [key, value] of Object.entries(originalDefaultModelEnv)) {\n if (value === undefined) {\n delete process.env[key]\n } else {\n process.env[key] = value\n }\n }\n } finally {\n releaseSharedMutationLock()\n }\n })\n\n describe('Claude-native providers', () => {\n test('haiku alias resolves to haiku model for official Anthropic API', async () => {\n // Mock providers to return firstParty with official URL\n mockProvider('firstParty', true)\n\n // Import after mock is set up\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('haiku', 'claude-sonnet-4-6', undefined, 'default')\n\n // Should resolve haiku alias, not inherit parent\n expect(result).toContain('haiku')\n expect(result).not.toBe('claude-sonnet-4-6')\n })\n\n test('haiku alias resolves for Bedrock provider', async () => {\n mockProvider('bedrock')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('haiku', 'claude-sonnet-4-6', undefined, 'default')\n\n // Should resolve haiku alias for Bedrock\n expect(result).toContain('haiku')\n })\n\n test('haiku alias resolves for Vertex provider', async () => {\n mockProvider('vertex')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('haiku', 'claude-sonnet-4-6', undefined, 'default')\n\n // Should resolve haiku alias for Vertex\n expect(result).toContain('haiku')\n })\n\n test('haiku alias resolves for Foundry provider', async () => {\n mockProvider('foundry')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('haiku', 'claude-sonnet-4-6', undefined, 'default')\n\n // Should resolve haiku alias for Foundry\n expect(result).toContain('haiku')\n })\n })\n\n describe('Non-Claude-native providers', () => {\n test('haiku alias inherits parent model for OpenAI provider', async () => {\n mockProvider('openai')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('haiku', 'gpt-4o-mini', undefined, 'default')\n\n // Should inherit parent model for OpenAI (no haiku concept)\n expect(result).toBe('gpt-4o-mini')\n })\n\n test('haiku alias inherits parent model for Gemini provider', async () => {\n mockProvider('gemini')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('haiku', 'gemini-2.5-pro', undefined, 'default')\n\n // Should inherit parent model for Gemini\n expect(result).toBe('gemini-2.5-pro')\n })\n\n test('haiku alias inherits parent model for custom Anthropic-compatible URL', async () => {\n // firstParty provider but with custom URL (not official Anthropic)\n mockProvider('firstParty')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('haiku', 'claude-sonnet-4-6', undefined, 'default')\n\n // Should inherit parent for custom Anthropic-compatible URL\n expect(result).toBe('claude-sonnet-4-6')\n })\n\n test('sonnet alias inherits parent model for OpenAI provider', async () => {\n mockProvider('openai')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('sonnet', 'gpt-4o-mini', undefined, 'default')\n\n // Should inherit parent model for OpenAI\n expect(result).toBe('gpt-4o-mini')\n })\n\n test('haiku alias inherits parent model for Mistral provider', async () => {\n mockProvider('mistral')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('haiku', 'mistral-small-latest', undefined, 'default')\n\n // Should inherit parent model for Mistral (no haiku concept)\n expect(result).toBe('mistral-small-latest')\n })\n\n test('haiku alias inherits parent model for GitHub Copilot provider', async () => {\n mockProvider('github')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('haiku', 'gpt-4o-mini', undefined, 'default')\n\n // Should inherit parent model for GitHub Copilot\n expect(result).toBe('gpt-4o-mini')\n })\n\n test('haiku alias inherits parent model for NVIDIA NIM provider', async () => {\n mockProvider('nvidia-nim')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('haiku', 'meta/llama-3.1-8b-instruct', undefined, 'default')\n\n // Should inherit parent model for NVIDIA NIM (no haiku concept)\n expect(result).toBe('meta/llama-3.1-8b-instruct')\n })\n\n test('haiku alias inherits parent model for MiniMax provider', async () => {\n mockProvider('minimax')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('haiku', 'MiniMax-M2.5-highspeed', undefined, 'default')\n\n // Should inherit parent model for MiniMax (no haiku concept)\n expect(result).toBe('MiniMax-M2.5-highspeed')\n })\n\n test('haiku alias inherits parent model for Codex provider', async () => {\n mockProvider('codex')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('haiku', 'gpt-5.5-mini', undefined, 'default')\n\n // Should inherit parent model for Codex provider (no haiku concept)\n expect(result).toBe('gpt-5.5-mini')\n })\n })\n\n describe('inherit behavior unchanged', () => {\n test('inherit always returns parent model regardless of provider', async () => {\n mockProvider('openai')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel('inherit', 'gpt-4o', undefined, 'default')\n\n expect(result).toBe('gpt-4o')\n })\n\n test('tool-specified inherit returns the runtime parent model', async () => {\n mockProvider('openai')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel(undefined, 'gpt-4o', 'inherit', 'default')\n\n expect(result).toBe('gpt-4o')\n })\n\n test('tool-specified inherit is case-insensitive and trimmed', async () => {\n mockProvider('openai')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel(undefined, 'gpt-4o', ' InHerit ', 'default')\n\n expect(result).toBe('gpt-4o')\n })\n })\n\n describe('tool-specified model override', () => {\n test('preserves custom provider model IDs unchanged', async () => {\n mockProvider('openai')\n\n const { getAgentModel } = await importAgentModule()\n const customModels = [\n 'gpt-5.5',\n 'mimo-v2.5-pro',\n 'deepseek-v4-flash',\n 'deepseek/deepseek-v4-flash:nitro',\n 'qwen3-coder-next:cloud',\n 'custom_model-v1.2:fast',\n ]\n\n for (const customModel of customModels) {\n expect(\n getAgentModel(undefined, 'claude-sonnet-4-6', customModel, 'default'),\n ).toBe(customModel)\n }\n })\n\n test('trims custom provider model IDs before resolving', async () => {\n mockProvider('openai')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel(\n undefined,\n 'claude-sonnet-4-6',\n ' qwen3-coder-next:cloud ',\n 'default',\n )\n\n expect(result).toBe('qwen3-coder-next:cloud')\n })\n\n test('preserves alias tier matching for tool-specified aliases', async () => {\n mockProvider('firstParty', true)\n\n const { getAgentModel } = await importAgentModule()\n\n expect(\n getAgentModel(undefined, 'claude-sonnet-4-6', 'sonnet', 'default'),\n ).toBe('claude-sonnet-4-6')\n expect(\n getAgentModel(undefined, 'claude-opus-4-6', 'opus', 'default'),\n ).toBe('claude-opus-4-6')\n expect(\n getAgentModel(\n undefined,\n 'claude-3-5-haiku-20241022',\n 'haiku',\n 'default',\n ),\n ).toBe('claude-3-5-haiku-20241022')\n })\n\n test('keeps existing direct alias parsing for non-Claude providers', async () => {\n mockProvider('openai')\n delete process.env.OPENAI_MODEL\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel(undefined, 'gpt-4o', 'haiku', 'default')\n\n expect(result).toBe('gpt-4o-mini')\n })\n\n test('CLAUDE_CODE_SUBAGENT_MODEL overrides tool-specified custom model', async () => {\n mockProvider('openai')\n process.env.CLAUDE_CODE_SUBAGENT_MODEL =\n 'deepseek/deepseek-v4-flash:nitro'\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel(\n 'haiku',\n 'claude-sonnet-4-6',\n 'gpt-5.5',\n 'default',\n )\n\n expect(result).toBe('deepseek/deepseek-v4-flash:nitro')\n })\n\n test('rejects tool-specified custom model IDs outside availableModels', async () => {\n mockProvider('openai')\n setAvailableModelsForTest(['gpt-4o'])\n\n const { getAgentModel } = await importAgentModule()\n\n expect(() =>\n getAgentModel(undefined, 'gpt-4o', 'gpt-5.5', 'default'),\n ).toThrow(\n \"Model 'gpt-5.5' is not available. Your organization restricts model selection.\",\n )\n })\n\n test('allows tool-specified custom model IDs inside availableModels', async () => {\n mockProvider('openai')\n setAvailableModelsForTest(['gpt-5.5'])\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel(undefined, 'gpt-4o', 'gpt-5.5', 'default')\n\n expect(result).toBe('gpt-5.5')\n })\n\n test('allows tool-specified aliases when the alias is in availableModels', async () => {\n mockProvider('openai')\n delete process.env.OPENAI_MODEL\n setAvailableModelsForTest(['haiku'])\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel(undefined, 'gpt-4o', 'haiku', 'default')\n\n expect(result).toBe('gpt-4o-mini')\n })\n\n test('does not let tier-matching aliases bypass availableModels', async () => {\n mockProvider('firstParty', true)\n setAvailableModelsForTest(['claude-3-5-haiku-20241022'])\n\n const { getAgentModel } = await importAgentModule()\n\n expect(() =>\n getAgentModel(\n undefined,\n 'claude-sonnet-4-6',\n 'sonnet',\n 'default',\n ),\n ).toThrow(\n \"Model 'sonnet' is not available. Your organization restricts model selection.\",\n )\n })\n\n test('keeps tier-matching aliases when the effective parent model is allowed', async () => {\n mockProvider('firstParty', true)\n setAvailableModelsForTest(['claude-sonnet-4-6'])\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel(\n undefined,\n 'claude-sonnet-4-6',\n 'sonnet',\n 'default',\n )\n\n expect(result).toBe('claude-sonnet-4-6')\n })\n\n test('keeps tool-specified inherit independent of availableModels', async () => {\n mockProvider('openai')\n setAvailableModelsForTest([])\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel(undefined, 'gpt-4o', 'inherit', 'default')\n\n expect(result).toBe('gpt-4o')\n })\n\n test('inherits Bedrock parent region prefix for non-prefixed Bedrock model IDs', async () => {\n mockProvider('bedrock')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel(\n undefined,\n 'eu.anthropic.claude-sonnet-4-5-20250929-v1:0',\n 'anthropic.claude-3-5-sonnet-20241022-v2:0',\n 'default',\n )\n\n expect(result).toBe('eu.anthropic.claude-3-5-sonnet-20241022-v2:0')\n })\n\n test('allows inherited Bedrock region prefixes when the requested model ID is allowed', async () => {\n mockProvider('bedrock')\n setAvailableModelsForTest(['anthropic.claude-3-5-sonnet-20241022-v2:0'])\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel(\n undefined,\n 'eu.anthropic.claude-sonnet-4-5-20250929-v1:0',\n 'anthropic.claude-3-5-sonnet-20241022-v2:0',\n 'default',\n )\n\n expect(result).toBe('eu.anthropic.claude-3-5-sonnet-20241022-v2:0')\n })\n\n test('preserves explicit Bedrock region prefix on tool-specified model IDs', async () => {\n mockProvider('bedrock')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel(\n undefined,\n 'eu.anthropic.claude-sonnet-4-5-20250929-v1:0',\n 'us.anthropic.claude-3-5-sonnet-20241022-v2:0',\n 'default',\n )\n\n expect(result).toBe('us.anthropic.claude-3-5-sonnet-20241022-v2:0')\n })\n\n test('does not apply Bedrock region prefixes to non-Bedrock custom IDs', async () => {\n mockProvider('bedrock')\n\n const { getAgentModel } = await importAgentModule()\n const result = getAgentModel(\n undefined,\n 'eu.anthropic.claude-sonnet-4-5-20250929-v1:0',\n 'deepseek/deepseek-v4-flash:nitro',\n 'default',\n )\n\n expect(result).toBe('deepseek/deepseek-v4-flash:nitro')\n })\n })\n\n describe('checkIsClaudeNativeProvider helper', () => {\n test('returns true for official Anthropic API', async () => {\n mockProvider('firstParty', true)\n\n const { checkIsClaudeNativeProvider } = await importAgentModule()\n expect(checkIsClaudeNativeProvider()).toBe(true)\n })\n\n test('returns true for Bedrock provider', async () => {\n mockProvider('bedrock')\n\n const { checkIsClaudeNativeProvider } = await importAgentModule()\n expect(checkIsClaudeNativeProvider()).toBe(true)\n })\n\n test('returns true for Vertex provider', async () => {\n mockProvider('vertex')\n\n const { checkIsClaudeNativeProvider } = await importAgentModule()\n expect(checkIsClaudeNativeProvider()).toBe(true)\n })\n\n test('returns true for Foundry provider', async () => {\n mockProvider('foundry')\n\n const { checkIsClaudeNativeProvider } = await importAgentModule()\n expect(checkIsClaudeNativeProvider()).toBe(true)\n })\n\n test('returns false for OpenAI provider', async () => {\n mockProvider('openai')\n\n const { checkIsClaudeNativeProvider } = await importAgentModule()\n expect(checkIsClaudeNativeProvider()).toBe(false)\n })\n\n test('returns false for custom Anthropic URL', async () => {\n mockProvider('firstParty')\n\n const { checkIsClaudeNativeProvider } = await importAgentModule()\n expect(checkIsClaudeNativeProvider()).toBe(false)\n })\n })\n})\n\ndescribe('getAgentModelOptions', () => {\n beforeEach(async () => {\n await acquireSharedMutationLock('utils/model/agent.test.ts')\n })\n\n afterEach(() => {\n releaseSharedMutationLock()\n })\n\n test('returns default options when settings are missing', async () => {\n const { getAgentModelOptions } = await importAgentModule()\n const options = getAgentModelOptions(null)\n expect(options.length).toBe(4)\n expect(options.map(o => o.value)).toEqual(['sonnet', 'opus', 'haiku', 'inherit'])\n })\n\n test('appends configured models from settings', async () => {\n const { getAgentModelOptions } = await importAgentModule()\n const options = getAgentModelOptions({\n agentModels: {\n 'deepseek-chat': { base_url: 'https://api.deepseek.com/v1', api_key: 'sk-ds' },\n 'gpt-4o': { base_url: 'https://api.openai.com/v1', api_key: 'sk-oai' },\n },\n } as any)\n\n expect(options.length).toBe(6)\n expect(options[4]?.value).toBe('deepseek-chat')\n expect(options[5]?.value).toBe('gpt-4o')\n expect(options[4]?.label).toBe('deepseek-chat')\n expect(options[5]?.description).toBe('Configured agent model')\n })\n\n test('deduplicates keys if configured models match built-in aliases', async () => {\n const { getAgentModelOptions } = await importAgentModule()\n const options = getAgentModelOptions({\n agentModels: {\n 'sonnet': { base_url: 'https://api.custom.com/v1', api_key: 'sk-123' },\n 'deepseek-chat': { base_url: 'https://api.deepseek.com/v1', api_key: 'sk-ds' },\n },\n } as any)\n\n expect(options.length).toBe(5)\n // 'sonnet' should not be duplicated\n expect(options.filter(o => o.value === 'sonnet').length).toBe(1)\n expect(options[4]?.value).toBe('deepseek-chat')\n })\n\n test('does not expose configured provider credentials in labels', async () => {\n const { getAgentModelOptions } = await importAgentModule()\n const options = getAgentModelOptions({\n agentModels: {\n 'deepseek-chat': {\n base_url: 'https://api.deepseek.com/v1',\n api_key: 'sk-secret-test-key',\n },\n },\n } as any)\n\n const displayText = options\n .map(option => `${option.label} ${option.description}`)\n .join('\\n')\n\n expect(displayText).not.toContain('sk-secret-test-key')\n expect(displayText).not.toContain('https://api.deepseek.com/v1')\n })\n})\n"}}, "src/utils/conversationRecovery.test.ts": {"b2e10afbdf8cb6ae97322d1ef80e9140f5173378": {"t": 1786068365.001218, "n": 9633, "h": "c96ea6c7d97b35f1", "c": "import { afterEach, beforeEach, expect, mock, test } from 'bun:test'\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n acquireSharedMutationLock,\n releaseSharedMutationLock,\n} from '../test/sharedMutationLock.js'\nimport * as realProviders from './model/providers.js'\n\nconst tempDirs: string[] = []\nconst originalSimple = process.env.CLAUDE_CODE_SIMPLE\nconst providerEnvKeys = [\n 'CLAUDE_CODE_USE_GEMINI',\n 'CLAUDE_CODE_USE_MISTRAL',\n 'CLAUDE_CODE_USE_GITHUB',\n 'CLAUDE_CODE_USE_BEDROCK',\n 'CLAUDE_CODE_USE_VERTEX',\n 'CLAUDE_CODE_USE_OPENAI',\n 'CLAUDE_CODE_USE_FOUNDRY',\n 'OPENAI_MODEL',\n 'OPENAI_BASE_URL',\n 'OPENAI_API_BASE',\n 'NVIDIA_NIM',\n 'MINIMAX_API_KEY',\n 'XAI_API_KEY',\n 'CLAUDE_CODE_PROVIDER_PROFILE_ENV_APPLIED',\n] as const\nconst originalProviderEnv = Object.fromEntries(\n providerEnvKeys.map(key => [key, process.env[key]]),\n) as Record<(typeof providerEnvKeys)[number], string | undefined>\nconst sessionId = '00000000-0000-4000-8000-000000001999'\nconst ts = '2026-04-02T00:00:00.000Z'\n\nfunction id(n: number): string {\n return `00000000-0000-4000-8000-${String(n).padStart(12, '0')}`\n}\n\nfunction user(uuid: string, content: string) {\n return {\n type: 'user',\n uuid,\n parentUuid: null,\n timestamp: ts,\n cwd: '/tmp',\n userType: 'external',\n sessionId,\n version: 'test',\n isSidechain: false,\n isMeta: false,\n message: {\n role: 'user',\n content,\n },\n }\n}\n\nfunction activeGoal(condition = 'resume goal') {\n return {\n id: id(900),\n condition,\n status: 'active',\n createdAt: ts,\n updatedAt: ts,\n startedAt: ts,\n turnCount: 1,\n maxTurns: 50,\n evaluatorFailures: 0,\n }\n}\n\nasync function writeJsonl(entry: unknown): Promise {\n const dir = await mkdtemp(join(tmpdir(), 'atuscode-conversation-recovery-'))\n tempDirs.push(dir)\n const filePath = join(dir, 'resume.jsonl')\n await writeFile(filePath, `${JSON.stringify(entry)}\\n`)\n return filePath\n}\n\nasync function writeJsonlEntries(entries: unknown[]): Promise {\n const dir = await mkdtemp(join(tmpdir(), 'atuscode-conversation-recovery-'))\n tempDirs.push(dir)\n const filePath = join(dir, 'resume.jsonl')\n await writeFile(filePath, entries.map(entry => JSON.stringify(entry)).join('\\n') + '\\n')\n return filePath\n}\n\nbeforeEach(async () => {\n await acquireSharedMutationLock('utils/conversationRecovery.test.ts')\n})\n\nafterEach(async () => {\n try {\n mock.restore()\n mock.module('./model/providers.js', () => realProviders)\n if (originalSimple === undefined) {\n delete process.env.CLAUDE_CODE_SIMPLE\n } else {\n process.env.CLAUDE_CODE_SIMPLE = originalSimple\n }\n for (const key of providerEnvKeys) {\n const value = originalProviderEnv[key]\n if (value === undefined) {\n delete process.env[key]\n } else {\n process.env[key] = value\n }\n }\n await Promise.all(\n tempDirs.splice(0).map(dir => rm(dir, { recursive: true, force: true })),\n )\n } finally {\n releaseSharedMutationLock()\n }\n})\n\nasync function importFreshConversationRecovery() {\n mock.restore()\n mock.module('./model/providers.js', () => ({\n ...realProviders,\n getAPIProvider: () => {\n if (process.env.CLAUDE_CODE_USE_GITHUB) return 'github'\n if (process.env.CLAUDE_CODE_USE_OPENAI) return 'openai'\n if (process.env.CLAUDE_CODE_USE_BEDROCK) return 'bedrock'\n if (process.env.CLAUDE_CODE_USE_VERTEX) return 'vertex'\n if (process.env.CLAUDE_CODE_USE_FOUNDRY) return 'foundry'\n return 'firstParty'\n },\n }))\n const nonce = `${Date.now()}-${Math.random()}`\n return import(`./conversationRecovery.ts?conversationRecoveryTest=${nonce}`)\n}\n\nfunction clearProviderEnv(): void {\n for (const key of providerEnvKeys) {\n delete process.env[key]\n }\n}\n\ntest('loadConversationForResume accepts a small transcript from jsonl path', async () => {\n process.env.CLAUDE_CODE_SIMPLE = '1'\n const path = await writeJsonl(user(id(1), 'hello'))\n const { loadConversationForResume } = await importFreshConversationRecovery()\n\n const result = await loadConversationForResume('fixture', path)\n expect(result).not.toBeNull()\n expect(result?.sessionId).toBe(sessionId)\n expect(result?.messages.length).toBeGreaterThan(0)\n})\n\ntest('loadConversationForResume preserves goal metadata from a loaded log option', async () => {\n process.env.CLAUDE_CODE_SIMPLE = '1'\n const goal = activeGoal('keep going after resume')\n const { loadConversationForResume } = await importFreshConversationRecovery()\n\n const result = await loadConversationForResume(\n {\n date: ts,\n messages: [user(id(10), 'hello')],\n value: 0,\n created: new Date(ts),\n modified: new Date(ts),\n firstPrompt: 'hello',\n messageCount: 1,\n isSidechain: false,\n sessionId,\n goal,\n } as any,\n undefined,\n )\n\n expect(result?.goal).toEqual(goal)\n})\n\ntest('loadConversationForResume preserves goal metadata from jsonl transcript path', async () => {\n process.env.CLAUDE_CODE_SIMPLE = '1'\n const goal = activeGoal('keep going after jsonl resume')\n const path = await writeJsonlEntries([\n {\n type: 'goal-state',\n sessionId,\n goal,\n },\n user(id(11), 'hello'),\n ])\n const { loadConversationForResume } = await importFreshConversationRecovery()\n\n const result = await loadConversationForResume('fixture', path)\n\n expect(result?.goal).toEqual(goal)\n})\n\ntest('loadConversationForResume rejects oversized reconstructed transcripts', async () => {\n process.env.CLAUDE_CODE_SIMPLE = '1'\n const hugeContent = 'x'.repeat(8 * 1024 * 1024 + 32 * 1024)\n const path = await writeJsonl(user(id(2), hugeContent))\n const {\n loadConversationForResume,\n ResumeTranscriptTooLargeError,\n } = await importFreshConversationRecovery()\n\n let caught: unknown\n try {\n await loadConversationForResume('fixture', path)\n } catch (error) {\n caught = error\n }\n\n expect(caught).toBeInstanceOf(ResumeTranscriptTooLargeError)\n expect((caught as Error).message).toContain(\n 'Reconstructed transcript is too large to resume safely',\n )\n})\n\ntest('deserializeMessages preserves thinking blocks for GitHub native Claude transport', async () => {\n clearProviderEnv()\n process.env.CLAUDE_CODE_USE_GITHUB = '1'\n process.env.OPENAI_MODEL = 'claude-sonnet-4-6'\n const { deserializeMessages } = await importFreshConversationRecovery()\n\n const deserialized = deserializeMessages([\n {\n type: 'assistant',\n message: {\n role: 'assistant',\n content: [\n { type: 'thinking', thinking: 'need a plan' },\n { type: 'text', text: 'working on it' },\n ],\n },\n } as any,\n ])\n\n const content = (deserialized[0] as any)?.message?.content as Array<{\n type: string\n }>\n expect(content.some(block => block.type === 'thinking')).toBe(true)\n})\n\ntest('deserializeMessages strips dangerous permission modes from rewindable user messages', async () => {\n clearProviderEnv()\n const { deserializeMessages } = await importFreshConversationRecovery()\n\n const deserialized = deserializeMessages([\n {\n ...user(id(3), 'run it'),\n permissionMode: 'fullAccess',\n } as any,\n ])\n\n expect((deserialized[0] as any)?.permissionMode).toBeUndefined()\n})\n\ntest('deserializeMessages preserves thinking blocks for DeepSeek 3P provider (#957)', async () => {\n // Regression: DeepSeek requires `reasoning_content` echoed back on assistant\n // messages in thinking mode. The shim reads the thinking block to populate\n // that field; stripping it on resume left the shim with no source and the\n // provider 400'd (\"reasoning_content in the thinking mode must be passed\n // back\"). preserveReasoningContent: true (from runtimeMetadata's DeepSeek\n // shim config inference) must opt the provider out of the 3P thinking strip.\n clearProviderEnv()\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'\n process.env.OPENAI_MODEL = 'deepseek-v4-flash'\n const { deserializeMessages } = await importFreshConversationRecovery()\n\n const deserialized = deserializeMessages([\n {\n type: 'assistant',\n message: {\n role: 'assistant',\n content: [\n { type: 'thinking', thinking: 'chain of thought' },\n { type: 'text', text: 'answer' },\n ],\n },\n } as any,\n ])\n\n const content = (deserialized[0] as any)?.message?.content as Array<{\n type: string\n }>\n expect(content.some(block => block.type === 'thinking')).toBe(true)\n})\n\ntest('deserializeMessages still strips thinking blocks for generic OpenAI 3P (no preserveReasoningContent)', async () => {\n // Counter-test: providers that don't set preserveReasoningContent keep the\n // original strip behaviour from #248; thinking blocks were causing 400s\n // there, and the fix for #957 must not regress that path.\n clearProviderEnv()\n process.env.CLAUDE_CODE_USE_OPENAI = '1'\n process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1'\n process.env.OPENAI_MODEL = 'gpt-5-mini'\n const { deserializeMessages } = await importFreshConversationRecovery()\n\n const deserialized = deserializeMessages([\n {\n type: 'assistant',\n message: {\n role: 'assistant',\n content: [\n { type: 'thinking', thinking: 'noise' },\n { type: 'text', text: 'answer' },\n ],\n },\n } as any,\n ])\n\n const content = (deserialized[0] as any)?.message?.content as Array<{\n type: string\n }>\n expect(content.some(block => block.type === 'thinking')).toBe(false)\n})\n"}}}}