import { beforeEach, describe, expect, it, vi } from 'vitest'; const openAiMock = vi.hoisted(() => ({ chat: vi.fn((modelId: string) => ({ endpoint: 'chat', modelId })), responses: vi.fn((modelId: string) => ({ endpoint: 'responses', modelId })), createOpenAI: vi.fn(), })); vi.mock('@ai-sdk/openai', () => ({ createOpenAI: openAiMock.createOpenAI, })); import { getModel, getModelInfo } from '@/lib/ai/providers'; import type { ProviderId } from '@/lib/types/provider'; async function captureInjectedRequestBody( providerId: ProviderId, modelId: string, thinkingConfig: Record, ) { const originalFetch = globalThis.fetch; const globalRecord = globalThis as Record; const originalThinkingContext = globalRecord.__thinkingContext; const fetchMock = vi.fn(async (_url: RequestInfo | URL, _init?: RequestInit) => { return new Response(JSON.stringify({ ok: true }), { status: 200, headers: { 'content-type': 'application/json' }, }); }); try { globalThis.fetch = fetchMock as typeof fetch; globalRecord.__thinkingContext = { getStore: () => thinkingConfig, }; getModel({ providerId, modelId, apiKey: 'sk-test', }); const lastCall = openAiMock.createOpenAI.mock.calls.at(-1); const options = lastCall?.[0] as { fetch?: typeof fetch } | undefined; await options?.fetch?.('https://example.test/v1/chat/completions', { method: 'POST', body: JSON.stringify({ model: modelId, messages: [{ role: 'user', content: 'hi' }], }), }); const init = fetchMock.mock.calls[0]?.[1] as RequestInit; return JSON.parse(init.body as string); } finally { globalThis.fetch = originalFetch; if (originalThinkingContext === undefined) { delete globalRecord.__thinkingContext; } else { globalRecord.__thinkingContext = originalThinkingContext; } } } describe('OpenAI provider defaults', () => { beforeEach(() => { openAiMock.chat.mockClear(); openAiMock.responses.mockClear(); openAiMock.createOpenAI.mockReset(); openAiMock.createOpenAI.mockReturnValue({ chat: openAiMock.chat, responses: openAiMock.responses, }); }); it('includes GPT-5.5 as a built-in OpenAI model', () => { expect(getModelInfo('openai', 'gpt-5.5')).toMatchObject({ id: 'gpt-5.5', name: 'GPT-5.5', contextWindow: 1050000, outputWindow: 128000, capabilities: { streaming: true, tools: true, vision: true, thinking: { toggleable: false, budgetAdjustable: true, defaultEnabled: true, }, }, }); }); it('routes GPT-5.5 through the OpenAI Responses API', () => { const { model } = getModel({ providerId: 'openai', modelId: 'gpt-5.5', apiKey: 'sk-test', }); expect(openAiMock.responses).toHaveBeenCalledWith('gpt-5.5'); expect(openAiMock.chat).not.toHaveBeenCalled(); expect(model).toEqual({ endpoint: 'responses', modelId: 'gpt-5.5' }); }); it.each([ ['kimi', 'kimi-k2.6', { mode: 'disabled' }, { thinking: { type: 'disabled' } }], ['glm', 'glm-5.1', { mode: 'enabled' }, { thinking: { type: 'enabled' } }], ['xiaomi', 'mimo-v2.5', { mode: 'disabled' }, { thinking: { type: 'disabled' } }], [ 'deepseek', 'deepseek-v4-pro', { mode: 'enabled', effort: 'max' }, { thinking: { type: 'enabled' }, reasoning_effort: 'max' }, ], [ 'qwen', 'qwen3.6-plus', { mode: 'enabled', budgetTokens: 4096 }, { enable_thinking: true, thinking_budget: 4096 }, ], [ 'siliconflow', 'deepseek-ai/DeepSeek-R1', { mode: 'enabled', budgetTokens: 2048 }, { thinking_budget: 2048 }, ], [ 'doubao', 'doubao-seed-2-0-pro-260215', { mode: 'enabled', effort: 'high' }, { reasoning_effort: 'high' }, ], [ 'openrouter', 'deepseek/deepseek-v4-pro', { mode: 'enabled', effort: 'high' }, { reasoning: { enabled: true, effort: 'high' } }, ], [ 'tencent-hunyuan', 'hy3-preview', { mode: 'enabled', effort: 'high' }, { chat_template_kwargs: { reasoning_effort: 'high' } }, ], ] as const)( 'injects %s thinking params into the OpenAI-compatible request body', async (providerId, modelId, thinkingConfig, expected) => { const body = await captureInjectedRequestBody(providerId, modelId, thinkingConfig); expect(body).toMatchObject(expected); }, ); });