| import { describe, expect, it } from 'vitest'; |
|
|
| import { getProvider } from '@/lib/ai/providers'; |
| import { |
| getDefaultThinkingConfig, |
| getThinkingDisplayValue, |
| normalizeThinkingConfig, |
| supportsConfigurableThinking, |
| } from '@/lib/ai/thinking-config'; |
| import type { ProviderId } from '@/lib/types/provider'; |
|
|
| function getThinking(providerId: ProviderId, modelId: string) { |
| const model = getProvider(providerId)?.models.find((item) => item.id === modelId); |
| return model?.capabilities?.thinking; |
| } |
|
|
| describe('thinking config metadata', () => { |
| it('marks configurable models with adapter-backed thinking capabilities', () => { |
| const thinking = getThinking('qwen', 'qwen3.6-plus'); |
|
|
| expect(supportsConfigurableThinking(thinking)).toBe(true); |
| expect(thinking?.control).toBe('toggle-budget'); |
| expect(thinking?.requestAdapter).toBe('qwen'); |
| }); |
|
|
| it('does not expose fixed thinking models as configurable', () => { |
| const thinking = getThinking('grok', 'grok-4.20-reasoning'); |
| const minimaxThinking = getThinking('minimax', 'MiniMax-M2.7'); |
|
|
| expect(thinking?.control).toBe('none'); |
| expect(supportsConfigurableThinking(thinking)).toBe(false); |
| expect(minimaxThinking?.control).toBe('none'); |
| expect(supportsConfigurableThinking(minimaxThinking)).toBe(false); |
| }); |
|
|
| it('exposes Claude Haiku 4.5 thinking as budget-only, not effort', () => { |
| const thinking = getThinking('anthropic', 'claude-haiku-4-5'); |
|
|
| expect(supportsConfigurableThinking(thinking)).toBe(true); |
| expect(thinking?.control).toBe('toggle-budget'); |
| expect(thinking?.requestAdapter).toBe('anthropic'); |
| expect(thinking?.effortValues).toBeUndefined(); |
| expect(getDefaultThinkingConfig(thinking)).toEqual({ |
| mode: 'disabled', |
| budgetTokens: 1024, |
| }); |
| expect(normalizeThinkingConfig(thinking, { mode: 'enabled', budgetTokens: 4096 })).toEqual({ |
| mode: 'enabled', |
| budgetTokens: 4096, |
| }); |
| }); |
|
|
| it('removes deprecated and legacy models from the built-in catalog', () => { |
| const openaiModels = getProvider('openai')?.models.map((item) => item.id); |
| const glmModels = getProvider('glm')?.models.map((item) => item.id); |
| const googleModels = getProvider('google')?.models.map((item) => item.id); |
| const deepseekModels = getProvider('deepseek')?.models.map((item) => item.id); |
| const hunyuanModels = getProvider('tencent-hunyuan')?.models.map((item) => item.id); |
| const minimaxModels = getProvider('minimax')?.models.map((item) => item.id); |
| const siliconflowModels = getProvider('siliconflow')?.models.map((item) => item.id); |
|
|
| expect(openaiModels).not.toContain('o3-mini'); |
| expect(openaiModels).not.toContain('o3'); |
| expect(openaiModels).not.toContain('o4-mini'); |
| expect(openaiModels).not.toContain('gpt-5.2'); |
| expect(openaiModels).not.toContain('gpt-5.1'); |
| expect(openaiModels).not.toContain('gpt-5'); |
| expect(openaiModels).not.toContain('gpt-4o'); |
| expect(glmModels).not.toContain('glm-4.5-air'); |
| expect(glmModels).not.toContain('glm-4.5-airx'); |
| expect(glmModels).not.toContain('glm-4.5-flash'); |
| expect(googleModels).toContain('gemini-3.1-pro-preview'); |
| expect(googleModels).not.toContain('gemini-3-pro-preview'); |
| expect(deepseekModels).toEqual(['deepseek-v4-pro', 'deepseek-v4-flash']); |
| expect(hunyuanModels).toEqual(['hy3-preview']); |
| expect(minimaxModels).toEqual(['MiniMax-M2.7']); |
| expect(siliconflowModels).not.toContain('MiniMaxAI/MiniMax-M2'); |
| }); |
| }); |
|
|
| describe('thinking config normalization', () => { |
| it('normalizes OpenAI effort defaults and selected effort values', () => { |
| const thinking = getThinking('openai', 'gpt-5.4'); |
|
|
| expect(getDefaultThinkingConfig(thinking)).toEqual({ |
| mode: 'disabled', |
| effort: 'none', |
| }); |
| expect(normalizeThinkingConfig(thinking, { effort: 'high' })).toEqual({ |
| mode: 'enabled', |
| effort: 'high', |
| }); |
| }); |
|
|
| it('normalizes GPT-5.5 as non-toggleable effort levels', () => { |
| const thinking = getThinking('openai', 'gpt-5.5'); |
|
|
| expect(getDefaultThinkingConfig(thinking)).toEqual({ |
| mode: 'enabled', |
| effort: 'medium', |
| }); |
| expect(normalizeThinkingConfig(thinking, { mode: 'disabled' })).toEqual({ |
| mode: 'enabled', |
| effort: 'low', |
| }); |
| expect(thinking?.effortValues).toEqual(['low', 'medium', 'high', 'xhigh']); |
| }); |
|
|
| it('normalizes Claude 4.5+ thinking as effort levels', () => { |
| const thinking = getThinking('anthropic', 'claude-sonnet-4-6'); |
| const opus47Thinking = getThinking('anthropic', 'claude-opus-4-7'); |
|
|
| expect(getDefaultThinkingConfig(thinking)).toEqual({ |
| mode: 'enabled', |
| effort: 'medium', |
| }); |
| expect(normalizeThinkingConfig(thinking, { effort: 'max' })).toEqual({ |
| mode: 'enabled', |
| effort: 'max', |
| }); |
| expect(normalizeThinkingConfig(thinking, { mode: 'disabled' })).toEqual({ |
| mode: 'disabled', |
| effort: 'none', |
| }); |
| expect(opus47Thinking?.effortValues).toEqual(['none', 'low', 'medium', 'high', 'xhigh', 'max']); |
| }); |
|
|
| it('normalizes DeepSeek V4 thinking as high/max effort levels', () => { |
| const thinking = getThinking('deepseek', 'deepseek-v4-pro'); |
|
|
| expect(getDefaultThinkingConfig(thinking)).toEqual({ |
| mode: 'enabled', |
| effort: 'high', |
| }); |
| expect(normalizeThinkingConfig(thinking, { effort: 'max' })).toEqual({ |
| mode: 'enabled', |
| effort: 'max', |
| }); |
| }); |
|
|
| it('normalizes Tencent HY3 thinking as no_think/low/high effort levels', () => { |
| const thinking = getThinking('tencent-hunyuan', 'hy3-preview'); |
|
|
| expect(getDefaultThinkingConfig(thinking)).toEqual({ |
| mode: 'disabled', |
| effort: 'none', |
| }); |
| expect(normalizeThinkingConfig(thinking, { effort: 'high' })).toEqual({ |
| mode: 'enabled', |
| effort: 'high', |
| }); |
| expect(thinking?.effortValues).toEqual(['none', 'low', 'high']); |
| }); |
|
|
| it('normalizes Doubao Seed 2.0 thinking as reasoning effort levels', () => { |
| const thinking = getThinking('doubao', 'doubao-seed-2-0-pro-260215'); |
|
|
| expect(getDefaultThinkingConfig(thinking)).toEqual({ |
| mode: 'enabled', |
| effort: 'medium', |
| }); |
| expect(normalizeThinkingConfig(thinking, { effort: 'high' })).toEqual({ |
| mode: 'enabled', |
| effort: 'high', |
| }); |
| expect(thinking?.effortValues).toEqual(['minimal', 'low', 'medium', 'high']); |
| }); |
|
|
| it('preserves dynamic Gemini budgets and display labels', () => { |
| const thinking = getThinking('google', 'gemini-2.5-flash'); |
|
|
| expect(getDefaultThinkingConfig(thinking)).toEqual({ |
| mode: 'enabled', |
| budgetTokens: -1, |
| }); |
| expect(getThinkingDisplayValue(thinking, undefined)).toBe('auto'); |
| expect(getThinkingDisplayValue(thinking, { mode: 'enabled', budgetTokens: 8192 })).toBe('8192'); |
| }); |
| }); |
|
|