diff --git a/src/backend/src/services/ai/chat/providers/ClaudeProvider/ClaudeProvider.test.ts b/src/backend/src/services/ai/chat/providers/ClaudeProvider/ClaudeProvider.test.ts index 98a8e51cd..ea10f17b3 100644 --- a/src/backend/src/services/ai/chat/providers/ClaudeProvider/ClaudeProvider.test.ts +++ b/src/backend/src/services/ai/chat/providers/ClaudeProvider/ClaudeProvider.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it, test } from 'vitest'; import { createTestKernel } from '../../../../../../tools/test.mjs'; -import { COST_MAPS } from '../../../../MeteringService/costMaps/index.js'; import { SUService } from '../../../../SUService.js'; import { ClaudeProvider } from './ClaudeProvider.js'; @@ -21,12 +20,14 @@ describe('ClaudeProvider ', async () => { const target = new ClaudeProvider(testKernel.services!.get('meteringService'), { apiKey: process.env.PUTER_CLAUDE_API_KEY || '' }, testKernel.services?.get('error-service')); const su = testKernel.services!.get('su') as SUService; - it('should have all models mapped in cost maps', async () => { + it('should have all models have cost in models json', async () => { const models = target.models(); for ( const model of models ) { - const entry = Object.entries(COST_MAPS).find(([key, _value]) => key.startsWith('claude') && key.includes(model.id)); - expect(entry, `Model ${model.id} is missing in cost maps`).toBeDefined(); + expect(model.input_cost_key).toBeTruthy(); + expect(model.costs[model.input_cost_key!]).not.toBeNullable(); + expect(model.output_cost_key).toBeTruthy(); + expect(model.costs[model.output_cost_key!]).not.toBeNullable(); } }); diff --git a/src/backend/src/services/ai/chat/providers/ClaudeProvider/models.ts b/src/backend/src/services/ai/chat/providers/ClaudeProvider/models.ts index 6207fe60f..abb467192 100644 --- a/src/backend/src/services/ai/chat/providers/ClaudeProvider/models.ts +++ b/src/backend/src/services/ai/chat/providers/ClaudeProvider/models.ts @@ -2,6 +2,30 @@ import { IChatModel } from '../types'; // Hardcoded from https://models.dev/api.json export const CLAUDE_MODELS: IChatModel[] = [ + { + puterId: 'anthropic:anthropic/claude-sonnet-4-6', + id: 'claude-sonnet-4-6', + modalities: { 'input': ['text', 'image', 'pdf'], 'output': ['text'] }, + open_weights: false, + tool_call: true, + knowledge: '2025-08', + release_date: '2026-02-17', + aliases: ['claude-sonnet-latest', 'claude-sonnet', 'claude-sonnet-4-6-latest', 'claude-sonnet-4.6', 'claude-sonnet-4-6', 'anthropic/claude-sonnet-4-6'], + name: 'Claude Sonnet 4.6', + costs_currency: 'usd-cents', + input_cost_key: 'input_tokens', + output_cost_key: 'output_tokens', + costs: { + tokens: 1_000_000, + input_tokens: 300, + ephemeral_5m_input_tokens: 300 * 1.25, + ephemeral_1h_input_tokens: 300 * 2, + cache_read_input_tokens: 300 * 0.1, + output_tokens: 1500, + }, + context: 200000, + max_tokens: 64000, + }, { puterId: 'anthropic:anthropic/claude-opus-4-6', id: 'claude-opus-4-6', @@ -10,7 +34,7 @@ export const CLAUDE_MODELS: IChatModel[] = [ tool_call: true, knowledge: '2025-05', release_date: '2026-02-05', - aliases: ['claude-opus-4-6-latest', 'claude-opus-4.6', 'claude-opus-4-6', 'anthropic/claude-opus-4-6'], + aliases: ['claude-opus', 'claude-opus-latest', 'claude-opus-4-6-latest', 'claude-opus-4.6', 'claude-opus-4-6', 'anthropic/claude-opus-4-6'], name: 'Claude Opus 4.6', costs_currency: 'usd-cents', input_cost_key: 'input_tokens', @@ -58,7 +82,7 @@ export const CLAUDE_MODELS: IChatModel[] = [ tool_call: true, knowledge: '2025-02-28', release_date: '2025-10-15', - aliases: ['claude-haiku-4.5', 'claude-haiku-4-5', 'claude-4-5-haiku', 'anthropic/claude-haiku-4-5'], + aliases: ['claude-haiku', 'claude-haiku-latest', 'claude-haiku-4.5-latest', 'claude-haiku-4.5', 'claude-haiku-4-5', 'claude-4-5-haiku', 'anthropic/claude-haiku-4-5'], name: 'Claude Haiku 4.5', costs_currency: 'usd-cents', input_cost_key: 'input_tokens',