fix: add claude-opus-4-5 pattern to THINKING_TOKEN_MAP (#11457)
* fix: add claude-opus-4-5 pattern to THINKING_TOKEN_MAP
Adds missing regex pattern for claude-opus-4-5 models (e.g., claude-opus-4-5-20251101)
to the THINKING_TOKEN_MAP configuration. Without this pattern, the model was not
recognized, causing findTokenLimit() to return undefined and leading to an
AI_InvalidArgumentError when using Google Vertex AI Anthropic provider.
The fix adds the pattern 'claude-opus-4-5.*$': { min: 1024, max: 64_000 } to
match the existing claude-4 thinking token configuration.
Fixes AI_InvalidArgumentError: invalid anthropic provider options caused by
budgetTokens receiving NaN instead of a number.
Signed-off-by: Shuchen Luo (personal linux) <nemo0806@gmail.com>
* refactor: make THINKING_TOKEN_MAP constant private
* fix(reasoning): update claude model token limit regex patterns
- Consolidate claude model regex patterns to be more consistent
- Add comprehensive test cases for various claude model variants
- Ensure case insensitivity and proper handling of edge cases
* fix: format
* feat(models): extend claude model regex patterns to support AWS and GCP formats
Update regex patterns in THINKING_TOKEN_MAP to support additional Claude model ID formats used in AWS Bedrock and GCP Vertex AI
Add comprehensive test cases for new model ID formats and reorganize test suite
* fix: format
---------
Signed-off-by: Shuchen Luo (personal linux) <nemo0806@gmail.com>
Co-authored-by: icarus <eurfelux@gmail.com>
This commit is contained in:
@@ -800,20 +800,6 @@ describe('getThinkModelType - Comprehensive Coverage', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('Token limit lookup', () => {
|
||||
it.each([
|
||||
['gemini-2.5-flash-lite-latest', { min: 512, max: 24576 }],
|
||||
['qwen-plus-2025-07-14', { min: 0, max: 38912 }],
|
||||
['claude-haiku-4', { min: 1024, max: 64000 }]
|
||||
])('returns configured min/max pairs for %s', (id, expected) => {
|
||||
expect(findTokenLimit(id)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('returns undefined when regex misses', () => {
|
||||
expect(findTokenLimit('unknown-model')).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Gemini Models', () => {
|
||||
describe('isSupportedThinkingTokenGeminiModel', () => {
|
||||
it('should return true for gemini 2.5 models', () => {
|
||||
@@ -1201,6 +1187,19 @@ describe('Gemini Models', () => {
|
||||
})
|
||||
|
||||
describe('findTokenLimit', () => {
|
||||
describe('General token limit lookup', () => {
|
||||
it.each([
|
||||
['gemini-2.5-flash-lite-latest', { min: 512, max: 24576 }],
|
||||
['qwen-plus-2025-07-14', { min: 0, max: 38912 }]
|
||||
])('returns configured min/max pairs for %s', (id, expected) => {
|
||||
expect(findTokenLimit(id)).toEqual(expected)
|
||||
})
|
||||
|
||||
it('returns undefined when regex misses', () => {
|
||||
expect(findTokenLimit('unknown-model')).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
const cases: Array<{ modelId: string; expected: { min: number; max: number } }> = [
|
||||
{ modelId: 'gemini-2.5-flash-lite-exp', expected: { min: 512, max: 24_576 } },
|
||||
{ modelId: 'gemini-1.5-flash', expected: { min: 0, max: 24_576 } },
|
||||
@@ -1216,11 +1215,7 @@ describe('findTokenLimit', () => {
|
||||
{ modelId: 'qwen-plus-ultra', expected: { min: 0, max: 81_920 } },
|
||||
{ modelId: 'qwen-turbo-pro', expected: { min: 0, max: 38_912 } },
|
||||
{ modelId: 'qwen-flash-lite', expected: { min: 0, max: 81_920 } },
|
||||
{ modelId: 'qwen3-7b', expected: { min: 1_024, max: 38_912 } },
|
||||
{ modelId: 'claude-3.7-sonnet-extended', expected: { min: 1_024, max: 64_000 } },
|
||||
{ modelId: 'claude-sonnet-4.1', expected: { min: 1_024, max: 64_000 } },
|
||||
{ modelId: 'claude-sonnet-4-5-20250929', expected: { min: 1_024, max: 64_000 } },
|
||||
{ modelId: 'claude-opus-4-1-extended', expected: { min: 1_024, max: 32_000 } }
|
||||
{ modelId: 'qwen3-7b', expected: { min: 1_024, max: 38_912 } }
|
||||
]
|
||||
|
||||
it.each(cases)('returns correct limits for $modelId', ({ modelId, expected }) => {
|
||||
@@ -1230,6 +1225,207 @@ describe('findTokenLimit', () => {
|
||||
it('returns undefined for unknown models', () => {
|
||||
expect(findTokenLimit('unknown-model')).toBeUndefined()
|
||||
})
|
||||
|
||||
describe('Claude models', () => {
|
||||
describe('Claude 3.7 Sonnet models', () => {
|
||||
it.each([
|
||||
'claude-3.7-sonnet',
|
||||
'claude-3-7-sonnet',
|
||||
'claude-3.7-sonnet-latest',
|
||||
'claude-3-7-sonnet-latest',
|
||||
'claude-3.7-sonnet-20250201',
|
||||
'claude-3-7-sonnet-20250201',
|
||||
// Official Claude API IDs
|
||||
'claude-3-7-sonnet-20250219',
|
||||
// AWS Bedrock format
|
||||
'anthropic.claude-3-7-sonnet-20250219-v1:0',
|
||||
// GCP Vertex AI format
|
||||
'claude-3-7-sonnet@20250219'
|
||||
])('should return { min: 1024, max: 64000 } for %s', (modelId) => {
|
||||
expect(findTokenLimit(modelId)).toEqual({ min: 1024, max: 64_000 })
|
||||
})
|
||||
|
||||
it.each(['CLAUDE-3.7-SONNET', 'Claude-3-7-Sonnet-Latest'])('should be case insensitive for %s', (modelId) => {
|
||||
expect(findTokenLimit(modelId)).toEqual({ min: 1024, max: 64_000 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Claude 4.0 series models', () => {
|
||||
it.each([
|
||||
'claude-sonnet-4',
|
||||
'claude-sonnet-4.0',
|
||||
'claude-sonnet-4-0',
|
||||
'claude-sonnet-4-preview',
|
||||
'claude-sonnet-4.0-preview',
|
||||
'claude-sonnet-4-20250101',
|
||||
// Official Claude API IDs
|
||||
'claude-sonnet-4-20250514',
|
||||
// AWS Bedrock format
|
||||
'anthropic.claude-sonnet-4-20250514-v1:0',
|
||||
// GCP Vertex AI format
|
||||
'claude-sonnet-4@20250514'
|
||||
])('should return { min: 1024, max: 64000 } for Sonnet variant %s', (modelId) => {
|
||||
expect(findTokenLimit(modelId)).toEqual({ min: 1024, max: 64_000 })
|
||||
})
|
||||
|
||||
it.each([
|
||||
'claude-opus-4',
|
||||
'claude-opus-4.0',
|
||||
'claude-opus-4-0',
|
||||
'claude-opus-4-preview',
|
||||
'claude-opus-4.0-preview',
|
||||
'claude-opus-4-20250101',
|
||||
// Official Claude API IDs
|
||||
'claude-opus-4-20250514',
|
||||
// AWS Bedrock format
|
||||
'anthropic.claude-opus-4-20250514-v1:0',
|
||||
// GCP Vertex AI format
|
||||
'claude-opus-4@20250514'
|
||||
])('should return { min: 1024, max: 32000 } for Opus variant %s', (modelId) => {
|
||||
expect(findTokenLimit(modelId)).toEqual({ min: 1024, max: 32_000 })
|
||||
})
|
||||
|
||||
it.each(['CLAUDE-SONNET-4', 'Claude-Opus-4-Preview'])('should be case insensitive for %s', (modelId) => {
|
||||
const expectedSonnet = { min: 1024, max: 64_000 }
|
||||
const expectedOpus = { min: 1024, max: 32_000 }
|
||||
const result = findTokenLimit(modelId)
|
||||
expect(result).toBeDefined()
|
||||
expect([expectedSonnet, expectedOpus]).toContainEqual(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Claude Opus 4.1 models', () => {
|
||||
it.each([
|
||||
'claude-opus-4.1',
|
||||
'claude-opus-4-1',
|
||||
'claude-opus-4.1-preview',
|
||||
'claude-opus-4-1-preview',
|
||||
'claude-opus-4.1-20250120',
|
||||
'claude-opus-4-1-20250120',
|
||||
// Official Claude API IDs
|
||||
'claude-opus-4-1-20250805',
|
||||
// AWS Bedrock format
|
||||
'anthropic.claude-opus-4-1-20250805-v1:0',
|
||||
// GCP Vertex AI format
|
||||
'claude-opus-4-1@20250805'
|
||||
])('should return { min: 1024, max: 32000 } for %s', (modelId) => {
|
||||
expect(findTokenLimit(modelId)).toEqual({ min: 1024, max: 32_000 })
|
||||
})
|
||||
|
||||
it.each(['CLAUDE-OPUS-4.1', 'Claude-Opus-4-1-Preview'])('should be case insensitive for %s', (modelId) => {
|
||||
expect(findTokenLimit(modelId)).toEqual({ min: 1024, max: 32_000 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('Claude 4.5 series models (Haiku, Sonnet, Opus)', () => {
|
||||
it.each([
|
||||
'claude-haiku-4.5',
|
||||
'claude-haiku-4-5',
|
||||
'claude-haiku-4.5-preview',
|
||||
'claude-haiku-4-5-preview',
|
||||
'claude-haiku-4.5-20250929',
|
||||
'claude-haiku-4-5-20250929',
|
||||
// Official Claude API IDs
|
||||
'claude-haiku-4-5-20251001',
|
||||
// AWS Bedrock format
|
||||
'anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
// GCP Vertex AI format
|
||||
'claude-haiku-4-5@20251001'
|
||||
])('should return { min: 1024, max: 64000 } for Haiku variant %s', (modelId) => {
|
||||
expect(findTokenLimit(modelId)).toEqual({ min: 1024, max: 64_000 })
|
||||
})
|
||||
|
||||
it.each([
|
||||
'claude-sonnet-4.5',
|
||||
'claude-sonnet-4-5',
|
||||
'claude-sonnet-4.5-preview',
|
||||
'claude-sonnet-4-5-preview',
|
||||
'claude-sonnet-4.5-20250929',
|
||||
'claude-sonnet-4-5-20250929',
|
||||
// Official Claude API IDs
|
||||
'claude-sonnet-4-5-20250929',
|
||||
// AWS Bedrock format
|
||||
'anthropic.claude-sonnet-4-5-20250929-v1:0',
|
||||
// GCP Vertex AI format
|
||||
'claude-sonnet-4-5@20250929'
|
||||
])('should return { min: 1024, max: 64000 } for Sonnet variant %s', (modelId) => {
|
||||
expect(findTokenLimit(modelId)).toEqual({ min: 1024, max: 64_000 })
|
||||
})
|
||||
|
||||
it.each([
|
||||
'claude-opus-4.5',
|
||||
'claude-opus-4-5',
|
||||
'claude-opus-4.5-preview',
|
||||
'claude-opus-4-5-preview',
|
||||
'claude-opus-4.5-20250929',
|
||||
'claude-opus-4-5-20250929',
|
||||
// Official Claude API IDs
|
||||
'claude-opus-4-5-20251101',
|
||||
// AWS Bedrock format
|
||||
'anthropic.claude-opus-4-5-20251101-v1:0',
|
||||
// GCP Vertex AI format
|
||||
'claude-opus-4-5@20251101'
|
||||
])('should return { min: 1024, max: 64000 } for Opus variant %s', (modelId) => {
|
||||
expect(findTokenLimit(modelId)).toEqual({ min: 1024, max: 64_000 })
|
||||
})
|
||||
|
||||
it.each(['CLAUDE-HAIKU-4.5', 'Claude-Sonnet-4-5-Preview', 'CLAUDE-OPUS-4.5-20250929'])(
|
||||
'should be case insensitive for %s',
|
||||
(modelId) => {
|
||||
expect(findTokenLimit(modelId)).toEqual({ min: 1024, max: 64_000 })
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('Claude models that should NOT match', () => {
|
||||
it.each([
|
||||
'claude-3-opus',
|
||||
'claude-3-sonnet',
|
||||
'claude-3-haiku',
|
||||
'claude-3.5-sonnet',
|
||||
'claude-3-5-sonnet',
|
||||
'claude-2.1',
|
||||
'claude-instant',
|
||||
'claude-haiku-4',
|
||||
'claude-haiku-4.0',
|
||||
'claude-haiku-4-0',
|
||||
'claude-opus-4.2',
|
||||
'claude-opus-4-2',
|
||||
'claude-sonnet-4.2',
|
||||
'claude-sonnet-4-2',
|
||||
// Old Haiku models (no Extended thinking support)
|
||||
'claude-3-5-haiku-20241022',
|
||||
'claude-3-5-haiku-latest',
|
||||
'anthropic.claude-3-5-haiku-20241022-v1:0',
|
||||
'claude-3-5-haiku@20241022',
|
||||
'claude-3-haiku-20240307',
|
||||
'anthropic.claude-3-haiku-20240307-v1:0',
|
||||
'claude-3-haiku@20240307'
|
||||
])('should return undefined for older/unsupported model %s', (modelId) => {
|
||||
expect(findTokenLimit(modelId)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should handle models with custom suffixes', () => {
|
||||
expect(findTokenLimit('claude-3.7-sonnet-custom-variant')).toEqual({ min: 1024, max: 64_000 })
|
||||
expect(findTokenLimit('claude-opus-4.1-custom')).toEqual({ min: 1024, max: 32_000 })
|
||||
expect(findTokenLimit('claude-sonnet-4.5-custom-variant')).toEqual({ min: 1024, max: 64_000 })
|
||||
})
|
||||
|
||||
it('should NOT match non-existent Claude 4.1 variants (only Opus 4.1 exists)', () => {
|
||||
// Claude Sonnet 4.1 and Haiku 4.1 do not exist
|
||||
expect(findTokenLimit('claude-sonnet-4.1')).toBeUndefined()
|
||||
expect(findTokenLimit('claude-haiku-4.1')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should not match partial model names', () => {
|
||||
expect(findTokenLimit('claude-3.7')).toBeUndefined()
|
||||
expect(findTokenLimit('claude-opus')).toBeUndefined()
|
||||
expect(findTokenLimit('claude-4.5')).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('isGemini3ThinkingTokenModel', () => {
|
||||
|
||||
@@ -547,7 +547,7 @@ export function isReasoningModel(model?: Model): boolean {
|
||||
return REASONING_REGEX.test(modelId) || false
|
||||
}
|
||||
|
||||
export const THINKING_TOKEN_MAP: Record<string, { min: number; max: number }> = {
|
||||
const THINKING_TOKEN_MAP: Record<string, { min: number; max: number }> = {
|
||||
// Gemini models
|
||||
'gemini-2\\.5-flash-lite.*$': { min: 512, max: 24576 },
|
||||
'gemini-.*-flash.*$': { min: 0, max: 24576 },
|
||||
@@ -568,10 +568,18 @@ export const THINKING_TOKEN_MAP: Record<string, { min: number; max: number }> =
|
||||
'qwen-flash.*$': { min: 0, max: 81_920 },
|
||||
'qwen3-(?!max).*$': { min: 1024, max: 38_912 },
|
||||
|
||||
// Claude models
|
||||
'claude-3[.-]7.*sonnet.*$': { min: 1024, max: 64_000 },
|
||||
'claude-(:?haiku|sonnet)-4.*$': { min: 1024, max: 64_000 },
|
||||
'claude-opus-4-1.*$': { min: 1024, max: 32_000 }
|
||||
// Claude models (supports AWS Bedrock 'anthropic.' prefix, GCP Vertex AI '@' separator, and '-v1:0' suffix)
|
||||
'(?:anthropic\\.)?claude-3[.-]7.*sonnet.*(?:-v\\d+:\\d+)?$': { min: 1024, max: 64_000 },
|
||||
'(?:anthropic\\.)?claude-(:?haiku|sonnet|opus)-4[.-]5.*(?:-v\\d+:\\d+)?$': { min: 1024, max: 64_000 },
|
||||
'(?:anthropic\\.)?claude-opus-4[.-]1.*(?:-v\\d+:\\d+)?$': { min: 1024, max: 32_000 },
|
||||
'(?:anthropic\\.)?claude-sonnet-4(?:[.-]0)?(?:[@-](?:\\d{4,}|[a-z][\\w-]*))?(?:-v\\d+:\\d+)?$': {
|
||||
min: 1024,
|
||||
max: 64_000
|
||||
},
|
||||
'(?:anthropic\\.)?claude-opus-4(?:[.-]0)?(?:[@-](?:\\d{4,}|[a-z][\\w-]*))?(?:-v\\d+:\\d+)?$': {
|
||||
min: 1024,
|
||||
max: 32_000
|
||||
}
|
||||
}
|
||||
|
||||
export const findTokenLimit = (modelId: string): { min: number; max: number } | undefined => {
|
||||
|
||||
Reference in New Issue
Block a user