fix: map claude-sonnet-4-5 alias to full snapshot name claude-sonnet-4-5-20250929

- Add claude-sonnet-4-5-20250929 model definition to anthropic.ts
- Update AnthropicHandler to map the alias to snapshot name for API calls
- Add tests to verify the mapping works correctly
- Fixes issue where custom base URLs that require exact snapshot IDs were failing

Fixes #8545
This commit is contained in:
Roo Code 2025-10-07 06:13:57 +00:00
parent 5a3f911321
commit 5d890d8b77
3 changed files with 94 additions and 6 deletions

View file

@ -28,6 +28,28 @@ export const anthropicModels = {
},
],
},
"claude-sonnet-4-5-20250929": {
maxTokens: 64_000, // Overridden to 8k if `enableReasoningEffort` is false.
contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07'
supportsImages: true,
supportsComputerUse: true,
supportsPromptCache: true,
inputPrice: 3.0, // $3 per million input tokens (≤200K context)
outputPrice: 15.0, // $15 per million output tokens (≤200K context)
cacheWritesPrice: 3.75, // $3.75 per million tokens
cacheReadsPrice: 0.3, // $0.30 per million tokens
supportsReasoningBudget: true,
// Tiered pricing for extended context (requires beta flag 'context-1m-2025-08-07')
tiers: [
{
contextWindow: 1_000_000, // 1M tokens with beta flag
inputPrice: 6.0, // $6 per million input tokens (>200K context)
outputPrice: 22.5, // $22.50 per million output tokens (>200K context)
cacheWritesPrice: 7.5, // $7.50 per million tokens (>200K context)
cacheReadsPrice: 0.6, // $0.60 per million tokens (>200K context)
},
],
},
"claude-sonnet-4-20250514": {
maxTokens: 64_000, // Overridden to 8k if `enableReasoningEffort` is false.
contextWindow: 200_000, // Default 200K, extendable to 1M with beta flag 'context-1m-2025-08-07'

View file

@ -288,5 +288,55 @@ describe("AnthropicHandler", () => {
expect(model.info.inputPrice).toBe(6.0)
expect(model.info.outputPrice).toBe(22.5)
})
it("should map claude-sonnet-4-5 to claude-sonnet-4-5-20250929 for API calls", async () => {
const handler = new AnthropicHandler({
apiKey: "test-api-key",
apiModelId: "claude-sonnet-4-5",
})
// Test createMessage
const stream = handler.createMessage("Test system", [{ role: "user", content: "Test message" }])
// Consume the stream to trigger the API call
for await (const chunk of stream) {
// Just consume the stream
}
// Verify the API was called with the full snapshot name
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: "claude-sonnet-4-5-20250929",
stream: true,
}),
expect.any(Object),
)
// Clear mock for next test
mockCreate.mockClear()
// Test completePrompt
await handler.completePrompt("Test prompt")
// Verify the API was called with the full snapshot name
expect(mockCreate).toHaveBeenCalledWith(
expect.objectContaining({
model: "claude-sonnet-4-5-20250929",
stream: false,
}),
)
})
it("should handle claude-sonnet-4-5-20250929 model directly", () => {
const handler = new AnthropicHandler({
apiKey: "test-api-key",
apiModelId: "claude-sonnet-4-5-20250929",
})
const model = handler.getModel()
expect(model.id).toBe("claude-sonnet-4-5-20250929")
expect(model.info.maxTokens).toBe(64000)
expect(model.info.contextWindow).toBe(200000)
expect(model.info.supportsReasoningBudget).toBe(true)
})
})
})

View file

@ -47,14 +47,20 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
// Add 1M context beta flag if enabled for Claude Sonnet 4 and 4.5
if (
(modelId === "claude-sonnet-4-20250514" || modelId === "claude-sonnet-4-5") &&
(modelId === "claude-sonnet-4-20250514" ||
modelId === "claude-sonnet-4-5" ||
modelId === "claude-sonnet-4-5-20250929") &&
this.options.anthropicBeta1MContext
) {
betas.push("context-1m-2025-08-07")
}
// Map the alias to the full snapshot name for API calls
const apiModelId = modelId === "claude-sonnet-4-5" ? "claude-sonnet-4-5-20250929" : modelId
switch (modelId) {
case "claude-sonnet-4-5":
case "claude-sonnet-4-5-20250929":
case "claude-sonnet-4-20250514":
case "claude-opus-4-1-20250805":
case "claude-opus-4-20250514":
@ -83,7 +89,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
stream = await this.client.messages.create(
{
model: modelId,
model: apiModelId,
max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS,
temperature,
thinking,
@ -115,6 +121,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
// Then check for models that support prompt caching
switch (modelId) {
case "claude-sonnet-4-5":
case "claude-sonnet-4-5-20250929":
case "claude-sonnet-4-20250514":
case "claude-opus-4-1-20250805":
case "claude-opus-4-20250514":
@ -134,7 +141,7 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
}
default: {
stream = (await this.client.messages.create({
model: modelId,
model: apiModelId,
max_tokens: maxTokens ?? ANTHROPIC_DEFAULT_MAX_TOKENS,
temperature,
system: [{ text: systemPrompt, type: "text" }],
@ -249,7 +256,10 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
let info: ModelInfo = anthropicModels[id]
// If 1M context beta is enabled for Claude Sonnet 4 or 4.5, update the model info
if ((id === "claude-sonnet-4-20250514" || id === "claude-sonnet-4-5") && this.options.anthropicBeta1MContext) {
if (
(id === "claude-sonnet-4-20250514" || id === "claude-sonnet-4-5" || id === "claude-sonnet-4-5-20250929") &&
this.options.anthropicBeta1MContext
) {
// Use the tier pricing for 1M context
const tier = info.tiers?.[0]
if (tier) {
@ -286,8 +296,11 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
async completePrompt(prompt: string) {
let { id: model, temperature } = this.getModel()
// Map the alias to the full snapshot name for API calls
const apiModel = model === "claude-sonnet-4-5" ? "claude-sonnet-4-5-20250929" : model
const message = await this.client.messages.create({
model,
model: apiModel,
max_tokens: ANTHROPIC_DEFAULT_MAX_TOKENS,
thinking: undefined,
temperature,
@ -310,8 +323,11 @@ export class AnthropicHandler extends BaseProvider implements SingleCompletionHa
// Use the current model
const { id: model } = this.getModel()
// Map the alias to the full snapshot name for API calls
const apiModel = model === "claude-sonnet-4-5" ? "claude-sonnet-4-5-20250929" : model
const response = await this.client.messages.countTokens({
model,
model: apiModel,
messages: [{ role: "user", content: content }],
})