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:
Waleed
2026-07-10 09:08:00 -07:00
committed by GitHub
parent 2cfa040f23
commit 7d1c927ab1
6 changed files with 189 additions and 40 deletions
+21 -5
View File
@@ -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`
+16 -4
View File
@@ -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
+59 -1
View File
@@ -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', () => {
+41
View File
@@ -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
*/
+28 -4
View File
@@ -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',
+24 -26
View File
@@ -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')