mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(models): correct model catalog data and Gemini thinking-config wire format (#5559)
* fix(models): correct model catalog data and Gemini thinking-config wire format - OpenAI: remove fabricated 'max' reasoning-effort value from gpt-5.6 family - Anthropic: fix claude-sonnet-4-6 maxOutputTokens (64k -> 128k); remove 3 fully retired models (claude-opus-4-0, claude-sonnet-4-0, claude-3-haiku-20240307); fix budget_tokens/max_tokens clamp that could send budget_tokens >= max_tokens for claude-opus-4-1 at its default thinking level - Google/Vertex: un-deprecate gemini-3-flash-preview (no shutdown date announced); add thinking capability to gemini-2.5-pro/flash/flash-lite (google + vertex) - Fix gemini/core.ts to send thinkingBudget (not thinkingLevel) for Gemini 2.5-series models, which reject thinkingLevel entirely - only Gemini 3.x supports it - Bedrock: mark claude-opus-4-1 deprecated per AWS's own Bedrock lifecycle schedule (Legacy since Jul 8 2026, separate from Anthropic's direct-API date) * fix(models): restore retired Claude entries as deprecated instead of removing Greptile caught a real backward-compat regression: fully removing claude-opus-4-0/claude-sonnet-4-0/claude-3-haiku-20240307 dropped them from getHostedModels()/shouldBillModelUsage(), so saved workflows still referencing them would fail on a missing-API-key error instead of Anthropic's actual "model retired" error. deprecated:true isn't consumed by the model picker (only copilot's serializer reads it), so restoring them this way costs nothing on hiding from new selection while preserving hosted-key resolution for existing references. * fix(models): restore Sol-exclusive 'max' reasoning value; fix Gemini 2.5 disable-thinking gap Found during a final per-model audit round with independent 2-3 source verification on every changed model: - gpt-5.6-sol: restore 'max' reasoning-effort value. Multiple independent sources (OpenAI's own model-guidance docs page, launch announcement, and press coverage) confirm 'max' is a real, newly-launched value exclusive to Sol - not fabricated as originally assessed. Terra and Luna correctly do NOT get 'max' (confirmed Sol-exclusive), so they're unchanged. - gemini-2.5-flash / gemini-2.5-flash-lite (google + vertex): selecting 'none' for thinking level was sending no thinkingConfig at all, which falls back to the API's dynamic default (thinking stays ON for flash) - not actually disabling it, even though both models explicitly support thinkingBudget:0. Now sends an explicit budget of 0 for these two models specifically (gemini-2.5-pro is correctly excluded - it cannot disable thinking at all, floor is 128 not 0). * chore(models): tighten inline comments in anthropic/gemini core Trim verbose multi-line comments to single concise lines and remove duplication with the TSDoc already on the ANTHROPIC_MIN_BUDGET_TOKENS/ ANTHROPIC_THINKING_OUTPUT_HEADROOM constants. No behavior change - verified against the full test suite (811 files, 11141 tests, all passing).
This commit is contained in:
@@ -82,6 +82,12 @@ const THINKING_BUDGET_TOKENS: Record<string, number> = {
|
||||
high: 32768,
|
||||
}
|
||||
|
||||
/** Anthropic's documented floor for `budget_tokens` (Messages API reference: "Must be >=1024 and less than max_tokens"). */
|
||||
const ANTHROPIC_MIN_BUDGET_TOKENS = 1024
|
||||
|
||||
/** Headroom reserved for text output above the thinking budget when computing max_tokens. */
|
||||
const ANTHROPIC_THINKING_OUTPUT_HEADROOM = 4096
|
||||
|
||||
/**
|
||||
* Checks if a model supports adaptive thinking (thinking.type: "adaptive").
|
||||
* Fable 5 supports ONLY adaptive thinking (always on; type: "disabled" is rejected).
|
||||
@@ -338,16 +344,26 @@ export async function executeAnthropicProviderRequest(
|
||||
payload.output_config = thinkingConfig.outputConfig
|
||||
}
|
||||
|
||||
// Per Anthropic docs: budget_tokens must be less than max_tokens.
|
||||
// Ensure max_tokens leaves room for both thinking and text output.
|
||||
// Keep budget_tokens < max_tokens (see constants above) by shrinking the budget
|
||||
// itself when the model's output cap is too tight — clamping max_tokens alone
|
||||
// can leave budget_tokens >= max_tokens.
|
||||
if (
|
||||
thinkingConfig.thinking.type === 'enabled' &&
|
||||
'budget_tokens' in thinkingConfig.thinking
|
||||
) {
|
||||
const budgetTokens = thinkingConfig.thinking.budget_tokens
|
||||
const minMaxTokens = budgetTokens + 4096
|
||||
const modelMax = getMaxOutputTokensForModel(request.model)
|
||||
let budgetTokens = thinkingConfig.thinking.budget_tokens
|
||||
|
||||
if (budgetTokens + ANTHROPIC_THINKING_OUTPUT_HEADROOM > modelMax) {
|
||||
budgetTokens = Math.max(
|
||||
ANTHROPIC_MIN_BUDGET_TOKENS,
|
||||
modelMax - ANTHROPIC_THINKING_OUTPUT_HEADROOM
|
||||
)
|
||||
thinkingConfig.thinking.budget_tokens = budgetTokens
|
||||
}
|
||||
|
||||
const minMaxTokens = budgetTokens + ANTHROPIC_THINKING_OUTPUT_HEADROOM
|
||||
if (payload.max_tokens < minMaxTokens) {
|
||||
const modelMax = getMaxOutputTokensForModel(request.model)
|
||||
payload.max_tokens = Math.min(minMaxTokens, modelMax)
|
||||
logger.info(
|
||||
`Adjusted max_tokens to ${payload.max_tokens} to satisfy budget_tokens (${budgetTokens}) constraint`
|
||||
|
||||
@@ -24,7 +24,9 @@ import {
|
||||
ensureStructResponse,
|
||||
extractAllFunctionCallParts,
|
||||
extractTextContent,
|
||||
mapToThinkingBudget,
|
||||
mapToThinkingLevel,
|
||||
supportsDisablingGemini25Thinking,
|
||||
} from '@/providers/google/utils'
|
||||
import { enrichLastModelSegment } from '@/providers/trace-enrichment'
|
||||
import type {
|
||||
@@ -952,13 +954,23 @@ export async function executeGeminiRequest(
|
||||
)
|
||||
}
|
||||
|
||||
// Configure thinking only when the user explicitly selects a thinking level
|
||||
// Gemini 3.x takes thinkingLevel directly; Gemini 2.5-series rejects it and needs thinkingBudget.
|
||||
if (request.thinkingLevel && request.thinkingLevel !== 'none') {
|
||||
const thinkingConfig: ThinkingConfig = {
|
||||
includeThoughts: false,
|
||||
thinkingLevel: mapToThinkingLevel(request.thinkingLevel),
|
||||
const thinkingConfig: ThinkingConfig = { includeThoughts: false }
|
||||
if (isGemini3Model(model)) {
|
||||
thinkingConfig.thinkingLevel = mapToThinkingLevel(request.thinkingLevel)
|
||||
} else {
|
||||
thinkingConfig.thinkingBudget = mapToThinkingBudget(model, request.thinkingLevel)
|
||||
}
|
||||
geminiConfig.thinkingConfig = thinkingConfig
|
||||
} else if (
|
||||
request.thinkingLevel === 'none' &&
|
||||
!isGemini3Model(model) &&
|
||||
supportsDisablingGemini25Thinking(model)
|
||||
) {
|
||||
// Omitting thinkingConfig falls back to the API's dynamic default (ON for gemini-2.5-flash),
|
||||
// so disabling requires an explicit budget of 0.
|
||||
geminiConfig.thinkingConfig = { includeThoughts: false, thinkingBudget: 0 }
|
||||
}
|
||||
|
||||
// Prepare tools
|
||||
|
||||
@@ -2,9 +2,67 @@
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { convertToGeminiFormat, ensureStructResponse } from '@/providers/google/utils'
|
||||
import {
|
||||
convertToGeminiFormat,
|
||||
ensureStructResponse,
|
||||
mapToThinkingBudget,
|
||||
supportsDisablingGemini25Thinking,
|
||||
} from '@/providers/google/utils'
|
||||
import type { ProviderRequest } from '@/providers/types'
|
||||
|
||||
describe('mapToThinkingBudget', () => {
|
||||
it('maps named levels to a within-range budget for gemini-2.5-pro (128-32768, cannot disable)', () => {
|
||||
expect(mapToThinkingBudget('gemini-2.5-pro', 'low')).toBeGreaterThanOrEqual(128)
|
||||
expect(mapToThinkingBudget('gemini-2.5-pro', 'high')).toBeLessThanOrEqual(32768)
|
||||
})
|
||||
|
||||
it('maps named levels to a within-range budget for gemini-2.5-flash (0-24576)', () => {
|
||||
expect(mapToThinkingBudget('gemini-2.5-flash', 'low')).toBeLessThanOrEqual(24576)
|
||||
expect(mapToThinkingBudget('gemini-2.5-flash', 'high')).toBeLessThanOrEqual(24576)
|
||||
})
|
||||
|
||||
it('maps named levels to a within-range budget for gemini-2.5-flash-lite (512-24576)', () => {
|
||||
expect(mapToThinkingBudget('gemini-2.5-flash-lite', 'low')).toBeGreaterThanOrEqual(512)
|
||||
expect(mapToThinkingBudget('gemini-2.5-flash-lite', 'high')).toBeLessThanOrEqual(24576)
|
||||
})
|
||||
|
||||
it('strips the vertex/ prefix before looking up the model', () => {
|
||||
expect(mapToThinkingBudget('vertex/gemini-2.5-flash', 'medium')).toBe(
|
||||
mapToThinkingBudget('gemini-2.5-flash', 'medium')
|
||||
)
|
||||
})
|
||||
|
||||
it('falls back to dynamic budget (-1) for models with no explicit mapping', () => {
|
||||
expect(mapToThinkingBudget('gemini-2.0-flash', 'medium')).toBe(-1)
|
||||
})
|
||||
|
||||
it('falls back to the high budget for an unrecognized level on a mapped model', () => {
|
||||
expect(mapToThinkingBudget('gemini-2.5-flash', 'unknown-level')).toBe(
|
||||
mapToThinkingBudget('gemini-2.5-flash', 'high')
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('supportsDisablingGemini25Thinking', () => {
|
||||
it('returns true for gemini-2.5-flash and gemini-2.5-flash-lite', () => {
|
||||
expect(supportsDisablingGemini25Thinking('gemini-2.5-flash')).toBe(true)
|
||||
expect(supportsDisablingGemini25Thinking('gemini-2.5-flash-lite')).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false for gemini-2.5-pro, which cannot disable thinking', () => {
|
||||
expect(supportsDisablingGemini25Thinking('gemini-2.5-pro')).toBe(false)
|
||||
})
|
||||
|
||||
it('strips the vertex/ prefix before checking', () => {
|
||||
expect(supportsDisablingGemini25Thinking('vertex/gemini-2.5-flash')).toBe(true)
|
||||
expect(supportsDisablingGemini25Thinking('vertex/gemini-2.5-pro')).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for models with no explicit mapping', () => {
|
||||
expect(supportsDisablingGemini25Thinking('gemini-3.5-flash')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ensureStructResponse', () => {
|
||||
describe('should return objects unchanged', () => {
|
||||
it('should return plain object unchanged', () => {
|
||||
|
||||
@@ -350,6 +350,47 @@ export function mapToThinkingLevel(level: string): ThinkingLevel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-model thinkingBudget ranges for Gemini 2.5-series models. Unlike Gemini 3.x, these
|
||||
* models reject `thinkingLevel` entirely (Gemini API docs: "Gemini 2.5 series models don't
|
||||
* support thinkingLevel; use thinkingBudget instead") and require a numeric token budget
|
||||
* within each model's own documented range.
|
||||
*/
|
||||
const GEMINI_25_THINKING_BUDGETS: Record<string, Record<string, number>> = {
|
||||
'gemini-2.5-pro': { low: 2048, medium: 8192, high: 32768 }, // valid range 128-32768, cannot disable
|
||||
'gemini-2.5-flash': { low: 2048, medium: 8192, high: 24576 }, // valid range 0-24576
|
||||
'gemini-2.5-flash-lite': { low: 1024, medium: 8192, high: 24576 }, // valid range 512-24576
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a named thinking level to a `thinkingBudget` token count for Gemini 2.5-series models.
|
||||
* Falls back to -1 (dynamic/automatic budget) for any model not in the explicit table above,
|
||||
* rather than guessing a number that could fall outside an unmapped model's valid range.
|
||||
*/
|
||||
export function mapToThinkingBudget(model: string, level: string): number {
|
||||
const normalized = model.toLowerCase().replace(/^vertex\//, '')
|
||||
const budgets = GEMINI_25_THINKING_BUDGETS[normalized]
|
||||
if (!budgets) return -1
|
||||
return budgets[level.toLowerCase()] ?? budgets.high
|
||||
}
|
||||
|
||||
/**
|
||||
* Gemini 2.5-series models that accept `thinkingBudget: 0` to explicitly disable thinking.
|
||||
* gemini-2.5-pro cannot disable thinking at all (its documented budget floor is 128, not 0),
|
||||
* so it's deliberately excluded here.
|
||||
*/
|
||||
const GEMINI_25_MODELS_SUPPORTING_DISABLE = new Set(['gemini-2.5-flash', 'gemini-2.5-flash-lite'])
|
||||
|
||||
/**
|
||||
* Whether this Gemini 2.5-series model supports explicitly disabling thinking via budget=0.
|
||||
* Omitting thinkingConfig entirely (the 'none' no-op path) falls back to the API's own
|
||||
* dynamic default, which is ON for gemini-2.5-flash — not the same as actually disabling it.
|
||||
*/
|
||||
export function supportsDisablingGemini25Thinking(model: string): boolean {
|
||||
const normalized = model.toLowerCase().replace(/^vertex\//, '')
|
||||
return GEMINI_25_MODELS_SUPPORTING_DISABLE.has(normalized)
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of checking forced tool usage
|
||||
*/
|
||||
|
||||
@@ -315,7 +315,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
},
|
||||
capabilities: {
|
||||
reasoningEffort: {
|
||||
values: ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
|
||||
values: ['none', 'low', 'medium', 'high', 'xhigh'],
|
||||
},
|
||||
verbosity: {
|
||||
values: ['low', 'medium', 'high'],
|
||||
@@ -335,7 +335,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
},
|
||||
capabilities: {
|
||||
reasoningEffort: {
|
||||
values: ['none', 'low', 'medium', 'high', 'xhigh', 'max'],
|
||||
values: ['none', 'low', 'medium', 'high', 'xhigh'],
|
||||
},
|
||||
verbosity: {
|
||||
values: ['low', 'medium', 'high'],
|
||||
@@ -841,7 +841,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
capabilities: {
|
||||
temperature: { min: 0, max: 1 },
|
||||
nativeStructuredOutputs: true,
|
||||
maxOutputTokens: 64000,
|
||||
maxOutputTokens: 128000,
|
||||
thinking: {
|
||||
levels: ['low', 'medium', 'high', 'max'],
|
||||
default: 'high',
|
||||
@@ -1509,7 +1509,6 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
},
|
||||
contextWindow: 1048576,
|
||||
releaseDate: '2025-12-17',
|
||||
deprecated: true,
|
||||
},
|
||||
{
|
||||
id: 'gemini-2.5-pro',
|
||||
@@ -1521,6 +1520,10 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
},
|
||||
capabilities: {
|
||||
temperature: { min: 0, max: 2 },
|
||||
thinking: {
|
||||
levels: ['low', 'medium', 'high'],
|
||||
default: 'high',
|
||||
},
|
||||
maxOutputTokens: 65536,
|
||||
},
|
||||
contextWindow: 1048576,
|
||||
@@ -1536,6 +1539,10 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
},
|
||||
capabilities: {
|
||||
temperature: { min: 0, max: 2 },
|
||||
thinking: {
|
||||
levels: ['low', 'medium', 'high'],
|
||||
default: 'medium',
|
||||
},
|
||||
maxOutputTokens: 65536,
|
||||
},
|
||||
contextWindow: 1048576,
|
||||
@@ -1551,6 +1558,10 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
},
|
||||
capabilities: {
|
||||
temperature: { min: 0, max: 2 },
|
||||
thinking: {
|
||||
levels: ['low', 'medium', 'high'],
|
||||
default: 'low',
|
||||
},
|
||||
maxOutputTokens: 65536,
|
||||
},
|
||||
contextWindow: 1048576,
|
||||
@@ -1725,6 +1736,10 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
},
|
||||
capabilities: {
|
||||
temperature: { min: 0, max: 2 },
|
||||
thinking: {
|
||||
levels: ['low', 'medium', 'high'],
|
||||
default: 'high',
|
||||
},
|
||||
maxOutputTokens: 65535,
|
||||
},
|
||||
contextWindow: 1048576,
|
||||
@@ -1740,6 +1755,10 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
},
|
||||
capabilities: {
|
||||
temperature: { min: 0, max: 2 },
|
||||
thinking: {
|
||||
levels: ['low', 'medium', 'high'],
|
||||
default: 'medium',
|
||||
},
|
||||
maxOutputTokens: 65535,
|
||||
},
|
||||
contextWindow: 1048576,
|
||||
@@ -1755,6 +1774,10 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
},
|
||||
capabilities: {
|
||||
temperature: { min: 0, max: 2 },
|
||||
thinking: {
|
||||
levels: ['low', 'medium', 'high'],
|
||||
default: 'low',
|
||||
},
|
||||
maxOutputTokens: 65535,
|
||||
},
|
||||
contextWindow: 1048576,
|
||||
@@ -2876,6 +2899,7 @@ export const PROVIDER_DEFINITIONS: Record<string, ProviderDefinition> = {
|
||||
},
|
||||
contextWindow: 200000,
|
||||
releaseDate: '2025-08-05',
|
||||
deprecated: true,
|
||||
},
|
||||
{
|
||||
id: 'bedrock/amazon.nova-2-pro-v1:0',
|
||||
|
||||
@@ -196,8 +196,8 @@ describe('Model Capabilities', () => {
|
||||
'gpt-5-chat-latest',
|
||||
'azure/gpt-5-chat',
|
||||
'gemini-2.5-flash',
|
||||
'claude-sonnet-4-0',
|
||||
'claude-opus-4-0',
|
||||
'claude-sonnet-4-5',
|
||||
'claude-opus-4-1',
|
||||
'grok-3-latest',
|
||||
'grok-3-fast-latest',
|
||||
'deepseek-v3',
|
||||
@@ -243,7 +243,7 @@ describe('Model Capabilities', () => {
|
||||
|
||||
it.concurrent('should be case insensitive', () => {
|
||||
expect(supportsTemperature('GPT-4O')).toBe(true)
|
||||
expect(supportsTemperature('claude-sonnet-4-0')).toBe(true)
|
||||
expect(supportsTemperature('claude-sonnet-4-5')).toBe(true)
|
||||
})
|
||||
|
||||
it.concurrent(
|
||||
@@ -277,7 +277,7 @@ describe('Model Capabilities', () => {
|
||||
})
|
||||
|
||||
it.concurrent('should return 1 for models with temperature range 0-1', () => {
|
||||
const modelsRange01 = ['claude-sonnet-4-0', 'claude-opus-4-0']
|
||||
const modelsRange01 = ['claude-sonnet-4-5', 'claude-opus-4-1']
|
||||
|
||||
for (const model of modelsRange01) {
|
||||
expect(getMaxTemperature(model)).toBe(1)
|
||||
@@ -316,7 +316,7 @@ describe('Model Capabilities', () => {
|
||||
|
||||
it.concurrent('should be case insensitive', () => {
|
||||
expect(getMaxTemperature('GPT-4O')).toBe(2)
|
||||
expect(getMaxTemperature('CLAUDE-SONNET-4-0')).toBe(1)
|
||||
expect(getMaxTemperature('CLAUDE-SONNET-4-5')).toBe(1)
|
||||
})
|
||||
|
||||
it.concurrent(
|
||||
@@ -413,7 +413,7 @@ describe('Model Capabilities', () => {
|
||||
expect(supportsThinking('claude-opus-4-6')).toBe(true)
|
||||
expect(supportsThinking('claude-opus-4-5')).toBe(true)
|
||||
expect(supportsThinking('claude-sonnet-4-5')).toBe(true)
|
||||
expect(supportsThinking('claude-sonnet-4-0')).toBe(true)
|
||||
expect(supportsThinking('claude-sonnet-4-5')).toBe(true)
|
||||
expect(supportsThinking('claude-haiku-4-5')).toBe(true)
|
||||
expect(supportsThinking('gemini-3-flash-preview')).toBe(true)
|
||||
})
|
||||
@@ -438,11 +438,11 @@ describe('Model Capabilities', () => {
|
||||
expect(MODELS_TEMP_RANGE_0_2).toContain('gemini-2.5-flash')
|
||||
expect(MODELS_TEMP_RANGE_0_2).toContain('deepseek-v3')
|
||||
expect(MODELS_TEMP_RANGE_0_2).toContain('grok-3-latest')
|
||||
expect(MODELS_TEMP_RANGE_0_2).not.toContain('claude-sonnet-4-0')
|
||||
expect(MODELS_TEMP_RANGE_0_2).not.toContain('claude-sonnet-4-5')
|
||||
})
|
||||
|
||||
it.concurrent('should have correct models in MODELS_TEMP_RANGE_0_1', () => {
|
||||
expect(MODELS_TEMP_RANGE_0_1).toContain('claude-sonnet-4-0')
|
||||
expect(MODELS_TEMP_RANGE_0_1).toContain('claude-sonnet-4-5')
|
||||
expect(MODELS_TEMP_RANGE_0_1).not.toContain('grok-3-latest')
|
||||
expect(MODELS_TEMP_RANGE_0_1).not.toContain('gpt-4o')
|
||||
})
|
||||
@@ -464,7 +464,7 @@ describe('Model Capabilities', () => {
|
||||
MODELS_TEMP_RANGE_0_1.length
|
||||
)
|
||||
expect(MODELS_WITH_TEMPERATURE_SUPPORT).toContain('gpt-4o')
|
||||
expect(MODELS_WITH_TEMPERATURE_SUPPORT).toContain('claude-sonnet-4-0')
|
||||
expect(MODELS_WITH_TEMPERATURE_SUPPORT).toContain('claude-sonnet-4-5')
|
||||
}
|
||||
)
|
||||
|
||||
@@ -496,7 +496,7 @@ describe('Model Capabilities', () => {
|
||||
expect(MODELS_WITH_REASONING_EFFORT).not.toContain('azure/gpt-5-chat')
|
||||
|
||||
expect(MODELS_WITH_REASONING_EFFORT).not.toContain('gpt-4o')
|
||||
expect(MODELS_WITH_REASONING_EFFORT).not.toContain('claude-sonnet-4-0')
|
||||
expect(MODELS_WITH_REASONING_EFFORT).not.toContain('claude-sonnet-4-5')
|
||||
})
|
||||
|
||||
it.concurrent('should have correct models in MODELS_WITH_VERBOSITY', () => {
|
||||
@@ -525,16 +525,14 @@ describe('Model Capabilities', () => {
|
||||
expect(MODELS_WITH_VERBOSITY).not.toContain('o4-mini')
|
||||
|
||||
expect(MODELS_WITH_VERBOSITY).not.toContain('gpt-4o')
|
||||
expect(MODELS_WITH_VERBOSITY).not.toContain('claude-sonnet-4-0')
|
||||
expect(MODELS_WITH_VERBOSITY).not.toContain('claude-sonnet-4-5')
|
||||
})
|
||||
|
||||
it.concurrent('should have correct models in MODELS_WITH_THINKING', () => {
|
||||
expect(MODELS_WITH_THINKING).toContain('claude-opus-4-6')
|
||||
expect(MODELS_WITH_THINKING).toContain('claude-opus-4-5')
|
||||
expect(MODELS_WITH_THINKING).toContain('claude-opus-4-1')
|
||||
expect(MODELS_WITH_THINKING).toContain('claude-opus-4-0')
|
||||
expect(MODELS_WITH_THINKING).toContain('claude-sonnet-4-5')
|
||||
expect(MODELS_WITH_THINKING).toContain('claude-sonnet-4-0')
|
||||
|
||||
expect(MODELS_WITH_THINKING).toContain('gemini-3-flash-preview')
|
||||
|
||||
@@ -659,7 +657,7 @@ describe('Model Capabilities', () => {
|
||||
})
|
||||
|
||||
it.concurrent('should return correct levels for other Claude models (budget_tokens)', () => {
|
||||
for (const model of ['claude-opus-4-5', 'claude-sonnet-4-5', 'claude-sonnet-4-0']) {
|
||||
for (const model of ['claude-opus-4-5', 'claude-sonnet-4-5', 'claude-haiku-4-5']) {
|
||||
const levels = getThinkingLevelsForModel(model)
|
||||
expect(levels).toBeDefined()
|
||||
expect(levels).toContain('low')
|
||||
@@ -713,7 +711,7 @@ describe('Max Output Tokens', () => {
|
||||
})
|
||||
|
||||
it.concurrent('should return updated max for Claude Sonnet 4.6', () => {
|
||||
expect(getMaxOutputTokensForModel('claude-sonnet-4-6')).toBe(64000)
|
||||
expect(getMaxOutputTokensForModel('claude-sonnet-4-6')).toBe(128000)
|
||||
})
|
||||
|
||||
it.concurrent('should return published max for Gemini 2.5 Pro', () => {
|
||||
@@ -873,8 +871,8 @@ describe('getHostedModels', () => {
|
||||
expect(hostedModels).toContain('gpt-4o')
|
||||
expect(hostedModels).toContain('o1')
|
||||
|
||||
expect(hostedModels).toContain('claude-sonnet-4-0')
|
||||
expect(hostedModels).toContain('claude-opus-4-0')
|
||||
expect(hostedModels).toContain('claude-sonnet-4-5')
|
||||
expect(hostedModels).toContain('claude-opus-4-1')
|
||||
|
||||
expect(hostedModels).toContain('gemini-2.5-pro')
|
||||
expect(hostedModels).toContain('gemini-2.5-flash')
|
||||
@@ -899,8 +897,8 @@ describe('shouldBillModelUsage', () => {
|
||||
expect(shouldBillModelUsage('gpt-4o')).toBe(true)
|
||||
expect(shouldBillModelUsage('o1')).toBe(true)
|
||||
|
||||
expect(shouldBillModelUsage('claude-sonnet-4-0')).toBe(true)
|
||||
expect(shouldBillModelUsage('claude-opus-4-0')).toBe(true)
|
||||
expect(shouldBillModelUsage('claude-sonnet-4-5')).toBe(true)
|
||||
expect(shouldBillModelUsage('claude-opus-4-1')).toBe(true)
|
||||
|
||||
expect(shouldBillModelUsage('gemini-2.5-pro')).toBe(true)
|
||||
expect(shouldBillModelUsage('gemini-2.5-flash')).toBe(true)
|
||||
@@ -921,7 +919,7 @@ describe('shouldBillModelUsage', () => {
|
||||
|
||||
it.concurrent('should be case insensitive', () => {
|
||||
expect(shouldBillModelUsage('GPT-4O')).toBe(true)
|
||||
expect(shouldBillModelUsage('Claude-Sonnet-4-0')).toBe(true)
|
||||
expect(shouldBillModelUsage('Claude-Sonnet-4-5')).toBe(true)
|
||||
expect(shouldBillModelUsage('GEMINI-2.5-PRO')).toBe(true)
|
||||
})
|
||||
|
||||
@@ -936,7 +934,7 @@ describe('Provider Management', () => {
|
||||
describe('getProviderFromModel', () => {
|
||||
it.concurrent('should return correct provider for known models', () => {
|
||||
expect(getProviderFromModel('gpt-4o')).toBe('openai')
|
||||
expect(getProviderFromModel('claude-sonnet-4-0')).toBe('anthropic')
|
||||
expect(getProviderFromModel('claude-sonnet-4-5')).toBe('anthropic')
|
||||
expect(getProviderFromModel('gemini-2.5-pro')).toBe('google')
|
||||
expect(getProviderFromModel('azure/gpt-4o')).toBe('azure-openai')
|
||||
})
|
||||
@@ -985,7 +983,7 @@ describe('Provider Management', () => {
|
||||
expect(config).toBeDefined()
|
||||
expect(config?.id).toBe('openai')
|
||||
|
||||
const anthropicConfig = getProviderConfigFromModel('claude-sonnet-4-0')
|
||||
const anthropicConfig = getProviderConfigFromModel('claude-sonnet-4-5')
|
||||
expect(anthropicConfig).toBeDefined()
|
||||
expect(anthropicConfig?.id).toBe('anthropic')
|
||||
})
|
||||
@@ -998,7 +996,7 @@ describe('Provider Management', () => {
|
||||
expect(allModels.length).toBeGreaterThan(0)
|
||||
|
||||
expect(allModels).toContain('gpt-4o')
|
||||
expect(allModels).toContain('claude-sonnet-4-0')
|
||||
expect(allModels).toContain('claude-sonnet-4-5')
|
||||
expect(allModels).toContain('gemini-2.5-pro')
|
||||
})
|
||||
})
|
||||
@@ -1022,8 +1020,8 @@ describe('Provider Management', () => {
|
||||
expect(openaiModels).toContain('o1')
|
||||
|
||||
const anthropicModels = getProviderModels('anthropic')
|
||||
expect(anthropicModels).toContain('claude-sonnet-4-0')
|
||||
expect(anthropicModels).toContain('claude-opus-4-0')
|
||||
expect(anthropicModels).toContain('claude-sonnet-4-5')
|
||||
expect(anthropicModels).toContain('claude-opus-4-1')
|
||||
})
|
||||
|
||||
it.concurrent('should return empty array for unknown providers', () => {
|
||||
@@ -1037,7 +1035,7 @@ describe('Provider Management', () => {
|
||||
const allProviders = getAllModelProviders()
|
||||
expect(typeof allProviders).toBe('object')
|
||||
expect(allProviders['gpt-4o']).toBe('openai')
|
||||
expect(allProviders['claude-sonnet-4-0']).toBe('anthropic')
|
||||
expect(allProviders['claude-sonnet-4-5']).toBe('anthropic')
|
||||
|
||||
const baseProviders = getBaseModelProviders()
|
||||
expect(typeof baseProviders).toBe('object')
|
||||
|
||||
Reference in New Issue
Block a user