diff --git a/src/backend/src/modules/puterai/ClaudeService.js b/src/backend/src/modules/puterai/ClaudeService.js index b3048df8e..802946a31 100644 --- a/src/backend/src/modules/puterai/ClaudeService.js +++ b/src/backend/src/modules/puterai/ClaudeService.js @@ -370,8 +370,8 @@ class ClaudeService extends BaseService { models_ () { return [ { - id: 'claude-opus-4-5', - aliases: ['claude-opus-4-5-latest', 'claude-opus-4.5'], + id: 'claude-opus-4-5-20251101', + aliases: ['claude-opus-4-5-latest', 'claude-opus-4-5', 'claude-opus-4.5'], name: 'Claude Opus 4.5', cost: { currency: 'usd-cents', diff --git a/src/backend/src/services/MeteringService/MeteringService.ts b/src/backend/src/services/MeteringService/MeteringService.ts index 75d0b2afe..7952b3ada 100644 --- a/src/backend/src/services/MeteringService/MeteringService.ts +++ b/src/backend/src/services/MeteringService/MeteringService.ts @@ -30,7 +30,7 @@ export class MeteringService { this.batchIncrementUsages(actor, Object.entries(trackedUsageObject).map(([usageKind, amount]) => ({ usageType: `${modelPrefix}:${usageKind}`, usageAmount: amount, - costOverride: costsOverrides?.[usageKind as keyof T], + costOverride: costsOverrides?.[usageKind as keyof T] || undefined, }))); } @@ -79,9 +79,11 @@ export class MeteringService { const currentMonth = this.#getMonthYearString(); return this.#superUserService.sudo(async () => { - const totalCost = (costOverride ?? (COST_MAPS[usageType as keyof typeof COST_MAPS] || 0) * usageAmount) || 0; // TODO DS: apply our policy discounts here eventually - if ( COST_MAPS[usageType as keyof typeof COST_MAPS] !== 0 && totalCost === 0 && costOverride === undefined ) { + const mappedCost = COST_MAPS[usageType as keyof typeof COST_MAPS]; + const totalCost = (costOverride ?? ((mappedCost || 0) * usageAmount)); + + if ( totalCost === 0 && mappedCost !== 0 && costOverride !== 0 ) { // could be something is off, there are some models that cost nothing from openrouter, but then our overrides should not be undefined, so will flag this.#alarmService.create('metering-service-0-cost-warning', 'potential abuse vector', { actor, @@ -227,11 +229,12 @@ export class MeteringService { continue; // skip invalid entries } - const totalCost = (costOverride ?? (COST_MAPS[usageType as keyof typeof COST_MAPS] || 0) * usageAmount) || 0; + const mappedCost = COST_MAPS[usageType as keyof typeof COST_MAPS]; + const totalCost = costOverride ?? ((mappedCost || 0) * usageAmount); totalBatchCost += totalCost; // Check for zero cost warning (only flag once per batch) - if ( !hasZeroCostWarning && COST_MAPS[usageType as keyof typeof COST_MAPS] !== 0 && totalCost === 0 && costOverride === undefined ) { + if ( !hasZeroCostWarning && totalCost === 0 && mappedCost !== 0 && costOverride !== 0 ) { hasZeroCostWarning = true; this.#alarmService.create('metering-service-0-cost-warning', 'potential abuse vector', { actor, diff --git a/src/backend/src/services/MeteringService/costMaps/claudeCostMap.ts b/src/backend/src/services/MeteringService/costMaps/claudeCostMap.ts index 9c201ee81..4abe3b2c0 100644 --- a/src/backend/src/services/MeteringService/costMaps/claudeCostMap.ts +++ b/src/backend/src/services/MeteringService/costMaps/claudeCostMap.ts @@ -19,18 +19,19 @@ export const CLAUDE_COST_MAP = { // Claude Opus 4.5 - 'claude:claude-opus-4-5:input_tokens': 500, - 'claude:claude-opus-4-5:ephemeral_5m_input_tokens': 500 * 1.25, - 'claude:claude-opus-4-5:ephemeral_1h_input_tokens': 500 * 2, - 'claude:claude-opus-4-5:cache_read_input_tokens': 500 * 0.1, - 'claude:claude-opus-4-5:output_tokens': 2500, - 'claude:claude-opus-4-5-20251101:input_tokens': 500, 'claude:claude-opus-4-5-20251101:ephemeral_5m_input_tokens': 500 * 1.25, 'claude:claude-opus-4-5-20251101:ephemeral_1h_input_tokens': 500 * 2, 'claude:claude-opus-4-5-20251101:cache_read_input_tokens': 500 * 0.1, 'claude:claude-opus-4-5-20251101:output_tokens': 2500, + // Claude Haiku 4.5 + 'claude:claude-haiku-4-5-20251001:input_tokens': 100, + 'claude:claude-haiku-4-5-20251001:ephemeral_5m_input_tokens': 100 * 1.25, + 'claude:claude-haiku-4-5-20251001:ephemeral_1h_input_tokens': 100 * 2, + 'claude:claude-haiku-4-5-20251001:cache_read_input_tokens': 100 * 0.1, + 'claude:claude-haiku-4-5-20251001:output_tokens': 500, + // Claude Sonnet 4.5 'claude:claude-sonnet-4-5-20250929:input_tokens': 300, 'claude:claude-sonnet-4-5-20250929:ephemeral_5m_input_tokens': 300 * 1.25,