blob: 4a7d1f8f93f3d67f69e7d83950e252778ff10b0a [file]
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import { computeCost } from '../telemetry/cost.js';
import { recordLlmCall } from '../telemetry/record-llm-call.js';
import { recordToolInvocation } from '../telemetry/record-tool-invocation.js';
import type { PersistedLlmCallRecord, PersistedToolInvocationRecord } from '../telemetry/types.js';
describe('computeCost', () => {
test('charges full input price only for cache-miss input', () => {
const cost = computeCost(
{
inputTokens: 100,
outputTokens: 50,
cacheHitInputTokens: 30,
cacheMissInputTokens: 60,
cacheWriteInputTokens: 10,
},
{
modelKey: 'deepseek:deepseek-chat',
inputUsdPer1M: 1,
outputUsdPer1M: 2,
cacheReadUsdPer1M: 0.25,
cacheWriteUsdPer1M: 1.5,
},
);
assert.equal(cost.inputCost, 0.00006);
assert.equal(cost.cacheReadCost, 0.0000075);
assert.ok(Math.abs(cost.cacheWriteCost - 0.000015) < 1e-12);
assert.equal(cost.outputCost, 0.0001);
assert.ok(Math.abs(cost.totalCost - 0.0001825) < 1e-12);
});
test('derives cache miss from total input when explicit miss is absent', () => {
const cost = computeCost(
{
inputTokens: 100,
outputTokens: 0,
cachedInputTokens: 40,
cacheWriteInputTokens: 10,
},
{
modelKey: 'deepseek:deepseek-chat',
inputUsdPer1M: 1,
outputUsdPer1M: 2,
cacheReadUsdPer1M: 0.25,
cacheWriteUsdPer1M: 1.5,
},
);
assert.equal(cost.inputCost, 0.00005);
assert.equal(cost.cacheReadCost, 0.00001);
assert.ok(Math.abs(cost.cacheWriteCost - 0.000015) < 1e-12);
assert.ok(Math.abs(cost.totalCost - 0.000075) < 1e-12);
});
test('treats all input as fresh when no cache data exists', () => {
const cost = computeCost(
{
inputTokens: 100,
outputTokens: 0,
},
{
modelKey: 'deepseek:deepseek-chat',
inputUsdPer1M: 1,
outputUsdPer1M: 2,
cacheReadUsdPer1M: 0.25,
},
);
assert.equal(cost.inputCost, 0.0001);
assert.equal(cost.cacheReadCost, 0);
assert.equal(cost.totalCost, 0.0001);
});
});
describe('recordLlmCall', () => {
test('preserves a runtime-provided cost fact instead of recomputing it', async () => {
const inserted: PersistedLlmCallRecord[] = [];
await recordLlmCall(
{
repo: {
insertLlmCall: async (record) => {
inserted.push(record);
},
},
lookupPricing: () => {
throw new Error('lookup should not run when costUsd is already present');
},
},
{
turnId: 'turn-1',
providerId: 'deepseek',
modelId: 'deepseek-chat',
inputTokens: 10,
outputTokens: 5,
costUsd: 0.123,
latencyMs: 7,
status: 'success',
startedAt: 100,
},
);
assert.equal(inserted[0]?.costUsd, 0.123);
});
test('settles only after publication and preserves main token fields and diagnostics', async () => {
let publish!: () => void;
const publication = new Promise<void>((resolve) => {
publish = resolve;
});
const inserted: PersistedLlmCallRecord[] = [];
let settled = false;
const recording = recordLlmCall(
{
repo: {
insertLlmCall: (record) => {
inserted.push(record);
return publication;
},
},
lookupPricing: () => null,
},
{
sessionId: 'session-1',
turnId: 'turn-1',
callKind: 'semantic_compact',
callId: 'compact-1',
connectionSlug: 'connection-1',
providerId: 'anthropic',
modelId: 'claude',
inputTokens: 20,
outputTokens: 8,
cacheHitInputTokens: 7,
cachedInputTokens: 6,
cacheWriteInputTokens: 3,
reasoningTokens: 2,
totalTokens: 30,
rawFinishReason: 'stop',
rawUsage: {
prompt_tokens: 20,
completion_tokens: 8,
total_tokens: 28,
prompt_cache_hit_tokens: 7,
prompt_cache_miss_tokens: 10,
prompt_tokens_details: { cached_tokens: 7 },
completion_tokens_details: { reasoning_tokens: 2 },
},
latencyMs: 5,
status: 'success',
startedAt: 100,
systemPromptHash: 'system-hash',
prefixHash: 'prefix-hash',
prefixChangeReason: 'first_turn',
requestShapeHash: 'request-hash',
requestShapeChangeReason: 'tool_schema_changed',
toolSchemaChangeReason: 'tool_source_enabled',
promptSegments: [{ kind: 'system_prompt', chars: 40, estimatedTokens: 10 }],
contextBudget: {
enabled: true,
estimatedTokensBefore: 30,
estimatedTokensAfter: 20,
keptTurns: 2,
droppedTurns: 1,
keptEvents: 4,
droppedEvents: 2,
},
},
);
const settlement = recording.then(() => {
settled = true;
});
await Promise.resolve();
assert.equal(settled, false);
assert.equal(inserted[0]?.id, 'usage_compact-1');
assert.equal(inserted[0]?.cacheHitInputTokens, 7);
assert.equal(inserted[0]?.cachedInputTokens, 7);
assert.equal(inserted[0]?.cacheMissInputTokens, 10);
assert.equal(inserted[0]?.cacheMissInputSource, 'derived');
assert.equal(inserted[0]?.cacheWriteInputTokens, 3);
assert.equal(inserted[0]?.reasoningTokens, 2);
assert.equal(inserted[0]?.totalTokens, 30);
assert.deepEqual(inserted[0]?.rawUsage, {
prompt_tokens: 20,
completion_tokens: 8,
total_tokens: 28,
prompt_cache_hit_tokens: 7,
prompt_cache_miss_tokens: 10,
prompt_tokens_details: { cached_tokens: 7 },
completion_tokens_details: { reasoning_tokens: 2 },
});
assert.deepEqual(inserted[0]?.promptSegments, [
{ kind: 'system_prompt', chars: 40, estimatedTokens: 10 },
]);
assert.equal(inserted[0]?.contextBudget?.droppedEvents, 2);
assert.equal(inserted[0]?.toolSchemaChangeReason, 'tool_source_enabled');
publish();
await settlement;
assert.equal(settled, true);
});
test('contains writer failures at the best-effort telemetry boundary', async () => {
const messages: string[] = [];
const originalConsoleError = console.error;
console.error = (message?: unknown) => {
messages.push(String(message));
};
try {
await assert.doesNotReject(() =>
recordLlmCall(
{
repo: {
insertLlmCall: async () => {
throw new Error('publication failed');
},
},
lookupPricing: () => null,
},
{
providerId: 'openai',
modelId: 'gpt-5',
inputTokens: 1,
outputTokens: 1,
latencyMs: 1,
status: 'success',
startedAt: 1,
},
),
);
} finally {
console.error = originalConsoleError;
}
assert.equal(messages.length, 1);
assert.match(messages[0] ?? '', /^\[telemetry\] recordLlmCall failed:/);
});
});
describe('recordToolInvocation', () => {
test('settles only after normalized record publication completes', async () => {
let publish!: () => void;
const publication = new Promise<void>((resolve) => {
publish = resolve;
});
const inserted: PersistedToolInvocationRecord[] = [];
let settled = false;
const recording = recordToolInvocation(
{
repo: {
insertToolInvocation: (record) => {
inserted.push(record);
return publication;
},
},
},
{
toolCallId: 'call-1',
toolName: 'Bash',
durationMs: 5,
status: 'success',
resultSummary: { kind: 'shell', status: 'completed', itemCount: 1 },
startedAt: 100,
},
);
const settlement = recording.then(() => {
settled = true;
});
await Promise.resolve();
assert.equal(settled, false);
assert.equal(inserted[0]?.id, 'tool_call-1');
assert.equal(inserted[0]?.ts, 105);
assert.equal(inserted[0]?.bytesIn, 0);
assert.equal(inserted[0]?.bytesOut, 0);
assert.deepEqual(inserted[0]?.resultSummary, {
kind: 'shell',
status: 'completed',
itemCount: 1,
});
publish();
await settlement;
assert.equal(settled, true);
});
test('contains writer failures at the best-effort telemetry boundary', async () => {
const messages: string[] = [];
const originalConsoleError = console.error;
console.error = (message?: unknown) => {
messages.push(String(message));
};
try {
await assert.doesNotReject(() =>
recordToolInvocation(
{
repo: {
insertToolInvocation: async () => {
throw new Error('publication failed');
},
},
},
{
toolCallId: 'call-failed',
toolName: 'Read',
durationMs: 2,
status: 'error',
startedAt: 100,
},
),
);
} finally {
console.error = originalConsoleError;
}
assert.equal(messages.length, 1);
assert.match(messages[0] ?? '', /^\[telemetry\] recordToolInvocation failed:/);
});
});