fix: metering service alerts + missing claude costs (#2058)
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test (20.x) (push) Has been cancelled
test / test (22.x) (push) Has been cancelled
test / backend (node env, api-test) (22.x) (push) Has been cancelled
test / puterjs (browser env, playwright) (22.x) (push) Has been cancelled
test / puterjs (node env, vitest) (22.x) (push) Has been cancelled

* fix: missing haiku costs, updating opus 4.5 id to match documentation

* fix: improve alert logic for metering service misses
This commit is contained in:
Daniel Salazar
2025-11-27 11:45:56 -08:00
committed by GitHub
parent c6af50ea98
commit f120bc9c58
3 changed files with 17 additions and 13 deletions
@@ -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',
@@ -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,
@@ -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,