Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/types/src/providers/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,28 @@ export const anthropicModels = {
},
],
},
"claude-sonnet-4-5-20250929": {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Duplicated model metadata for 'claude-sonnet-4-5' and 'claude-sonnet-4-5-20250929' increases maintenance overhead and the risk of drift. Consider defining a single source of truth (e.g., keep the snapshot entry and alias the short ID at the UI layer or via a mapping table) to avoid pricing/context discrepancies in future updates.

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'
Expand Down
50 changes: 50 additions & 0 deletions src/api/providers/__tests__/anthropic.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Tests claim coverage for countTokens mapping in the PR description, but there is no test exercising AnthropicHandler.countTokens. Please add a unit test that sets apiModelId='claude-sonnet-4-5', invokes countTokens, and verifies the SDK is called with model 'claude-sonnet-4-5-20250929'. You will need to extend the @anthropic-ai/sdk mock with messages.countTokens.

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)
})
})
})
28 changes: 22 additions & 6 deletions src/api/providers/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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":
Expand All @@ -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" }],
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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 }],
})

Expand Down