blob: b31c0a3b88e108f17da36003132df30c6a485bee [file]
/**
* Session Inspector trace projection (#1625).
*
* Run: `npm --workspace @maka/runtime run test`
*/
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import {
MODEL_CALL_ATTEMPT_SCHEMA_VERSION,
type ModelCallAttempt,
} from '@maka/core/model-call-attempt';
import type { RuntimeEvent } from '@maka/core/runtime-event';
import { projectSessionTrace } from '../session-trace-projection.js';
function attempt(overrides: Partial<ModelCallAttempt> = {}): ModelCallAttempt {
return {
schemaVersion: MODEL_CALL_ATTEMPT_SCHEMA_VERSION,
logicalCallId: 'call-1',
attemptId: 'attempt-1',
traceId: 'trace-1',
sessionId: 'session-1',
runId: 'run-1',
turnId: 'turn-1',
step: 0,
attempt: 0,
callKind: 'main',
providerId: 'anthropic',
modelId: 'claude-test',
startedAt: 1_000,
completedAt: 1_500,
latencyMs: 500,
status: 'completed',
usageBasis: 'reported',
inputTokens: 10,
outputTokens: 5,
costBasis: 'priced',
costUsd: 0.002,
...overrides,
};
}
function event(overrides: Partial<RuntimeEvent> = {}): RuntimeEvent {
return {
id: 'event-1',
invocationId: 'invocation-1',
runId: 'run-1',
sessionId: 'session-1',
turnId: 'turn-1',
ts: 1_000,
partial: false,
role: 'model',
author: 'agent',
...overrides,
};
}
describe('session trace projection', () => {
test('groups retries of one logical call into a single step', () => {
// Retries share a `logicalCallId` by contract, so "this call was retried"
// is a grouping rather than something the reader has to reconstruct.
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [],
modelCallAttempts: [
attempt({
attemptId: 'a-0',
attempt: 0,
status: 'failed',
costUsd: undefined,
costBasis: 'unpriced',
}),
attempt({ attemptId: 'a-1', attempt: 1, startedAt: 1_600, completedAt: 2_000 }),
],
});
assert.equal(trace.turns.length, 1);
const steps = trace.turns[0]!.steps;
assert.equal(steps.length, 1, 'one logical call is one step');
const call = steps[0]!;
assert.equal(call.kind, 'model_call');
if (call.kind !== 'model_call') return;
assert.equal(call.attempts.length, 2);
assert.equal(call.status, 'completed', 'the step carries the last attempt outcome');
assert.equal(trace.totals.modelAttempts, 2);
assert.equal(trace.totals.retries, 1, 'attempts beyond the first are retries');
// Only the second attempt was priced, so the step's cost is that one.
assert.equal(call.costUsd, 0.002);
assert.equal(trace.totals.unpricedAttempts, 1);
});
test('a session of entirely unpriced calls totals to no price, not to zero', () => {
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [],
modelCallAttempts: [attempt({ costUsd: undefined, costBasis: 'unpriced' })],
});
assert.equal(trace.totals.costUsd, undefined, 'absent price is not a zero price');
assert.equal(trace.totals.unpricedAttempts, 1);
assert.equal(trace.turns[0]?.steps[0]?.kind, 'model_call');
});
test('counts compaction calls made inside a turn', () => {
// The compaction kinds settle through the same seam as the send they
// interrupt (#1877), so a compacted turn shows what compacting cost.
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [],
modelCallAttempts: [
attempt({ logicalCallId: 'call-main', attemptId: 'a-main' }),
attempt({
logicalCallId: 'call-compact',
attemptId: 'a-compact',
callKind: 'history_compact',
startedAt: 1_600,
completedAt: 1_900,
costUsd: 0.001,
}),
],
});
const totals = trace.turns[0]!.totals;
assert.equal(totals.compactions, 1);
assert.equal(totals.modelAttempts, 2);
assert.equal(totals.costUsd, 0.003, 'compaction cost belongs to the turn it interrupted');
});
test('pairs a tool dispatch with the result that answers it', () => {
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [
event({
id: 'dispatch-1',
ts: 1_000,
actions: {
toolDispatch: {
protocol: 't1_after_preflight_v1',
operationId: 'op-1',
providerToolCallId: 'tool-call-1',
toolName: 'Bash',
canonicalArgsHash: 'hash',
recoveryMode: 'replay_safe',
},
},
}),
event({
id: 'response-1',
ts: 1_800,
role: 'tool',
author: 'tool',
content: {
kind: 'function_response',
id: 'tool-call-1',
name: 'Bash',
result: 'boom',
isError: true,
},
}),
],
modelCallAttempts: [],
});
const steps = trace.turns[0]!.steps;
assert.equal(steps.length, 1, 'a call and its result are one step to a reader');
const tool = steps[0]!;
assert.equal(tool.kind, 'tool');
if (tool.kind !== 'tool') return;
assert.equal(tool.toolName, 'Bash');
assert.equal(tool.status, 'failed');
assert.equal(tool.durationMs, 800);
});
test('attributes a turn failure to what failed first, not to the terminal error', () => {
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [
event({
id: 'dispatch-1',
ts: 1_000,
actions: {
toolDispatch: {
protocol: 't1_after_preflight_v1',
operationId: 'op-1',
providerToolCallId: 'tool-call-1',
toolName: 'Bash',
canonicalArgsHash: 'hash',
recoveryMode: 'replay_safe',
},
},
}),
event({
id: 'response-1',
ts: 1_200,
role: 'tool',
author: 'tool',
content: {
kind: 'function_response',
id: 'tool-call-1',
name: 'Bash',
result: 'boom',
isError: true,
},
}),
event({
id: 'error-1',
ts: 2_000,
content: { kind: 'error', message: 'turn ended after tool failure' },
}),
],
modelCallAttempts: [],
});
const failure = trace.turns[0]!.failure;
assert.ok(failure);
assert.equal(failure.code, 'tool_failed');
assert.equal(failure.attributedToStepId, 'dispatch-1', 'the symptom is not the cause');
assert.equal(failure.message, 'turn ended after tool failure');
});
test('reports a backend that emits no canonical records instead of rendering an idle session', () => {
// The pi backend emits `token_usage` and no `ModelCallAttempt` at all. An
// empty timeline would be indistinguishable from a session that did nothing.
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [
event({
id: 'usage-1',
ts: 1_000,
actions: { tokenUsage: { input: 100, output: 20, total: 120 } },
}),
],
modelCallAttempts: [],
});
assert.equal(trace.coverage.modelCalls, 'absent');
assert.deepEqual(trace.coverage.turnsMissingModelCalls, ['turn-1']);
assert.equal(trace.totals.modelAttempts, 0);
assert.equal(trace.totals.costUsd, undefined);
});
test('distinguishes a partially covered session from a wholly uncovered one', () => {
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [
event({
id: 'usage-1',
turnId: 'turn-1',
ts: 1_000,
actions: { tokenUsage: { input: 100, output: 20, total: 120 } },
}),
event({
id: 'usage-2',
turnId: 'turn-2',
ts: 3_000,
actions: { tokenUsage: { input: 50, output: 10, total: 60 } },
}),
],
modelCallAttempts: [attempt({ turnId: 'turn-2', startedAt: 3_000, completedAt: 3_200 })],
});
assert.equal(trace.coverage.modelCalls, 'partial');
assert.deepEqual(trace.coverage.turnsMissingModelCalls, ['turn-1']);
assert.equal(trace.turns.map((turn) => turn.turnId).join(','), 'turn-1,turn-2');
});
test('a session with no model activity is uncovered rather than incomplete', () => {
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [event({ content: { kind: 'text', text: 'hi' } })],
modelCallAttempts: [],
});
assert.equal(trace.coverage.modelCalls, 'none');
assert.deepEqual(trace.coverage.turnsMissingModelCalls, []);
});
test('ignores partial streaming events', () => {
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [
event({ id: 'partial-1', partial: true, content: { kind: 'error', message: 'transient' } }),
],
modelCallAttempts: [attempt()],
});
const steps = trace.turns[0]!.steps;
assert.equal(steps.length, 1);
assert.equal(steps[0]?.kind, 'model_call');
assert.equal(trace.turns[0]?.failure, undefined);
});
test('collapses a re-appended settlement instead of inventing a retry', () => {
// A provisional abort and its later settlement are appended under one
// `attemptId`. The ledger dedupes on write; a stream read does not, so
// without collapsing them the trace shows a retry that never happened and
// bills the priced settlement twice.
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [],
modelCallAttempts: [
attempt({ attemptId: 'a-0', status: 'aborted', costUsd: undefined, costBasis: 'unpriced' }),
attempt({ attemptId: 'a-0', status: 'completed', costUsd: 0.002 }),
],
});
const call = trace.turns[0]!.steps[0]!;
assert.equal(call.kind, 'model_call');
if (call.kind !== 'model_call') return;
assert.equal(call.attempts.length, 1, 'one attempt id is one attempt');
assert.equal(call.status, 'completed', 'the later settlement wins');
assert.equal(trace.totals.retries, 0);
assert.equal(trace.totals.costUsd, 0.002, 'not double-counted against Settings → Usage');
assert.equal(trace.totals.unpricedAttempts, 0);
});
test('reports a shortfall when usage stands for more steps than there are calls', () => {
// `runtimeSteps` says how many tool-loop steps one aggregate usage event
// represents, and each is a main call. Fewer on record is a gap the two
// ledgers disagree about — a floor on what is missing, not a count.
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [
event({
id: 'usage-1',
ts: 2_000,
actions: { tokenUsage: { input: 100, output: 20, total: 120, runtimeSteps: 2 } },
}),
],
modelCallAttempts: [attempt()],
});
assert.equal(trace.coverage.modelCalls, 'partial');
assert.deepEqual(trace.coverage.turnsWithFewerModelCallsThanSteps, ['turn-1']);
assert.deepEqual(trace.coverage.turnsMissingModelCalls, []);
});
test('a matching turn is reported as no known gap rather than as proven complete', () => {
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [
event({
id: 'usage-1',
ts: 2_000,
actions: { tokenUsage: { input: 100, output: 20, total: 120, runtimeSteps: 1 } },
}),
],
modelCallAttempts: [attempt()],
});
assert.equal(trace.coverage.modelCalls, 'no_known_gap');
assert.deepEqual(trace.coverage.turnsWithFewerModelCallsThanSteps, []);
});
test('a turn with no projected steps still has finite bounds', () => {
// Usage-only and text-only turns project nothing. Folding an empty list
// gives ±Infinity, which JSON serializes to null — a turn that renders as
// having no time at all.
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [
event({ id: 'text-1', ts: 1_000, content: { kind: 'text', text: 'hello' } }),
event({
id: 'usage-1',
ts: 2_500,
actions: { tokenUsage: { input: 10, output: 2, total: 12 } },
}),
],
modelCallAttempts: [],
});
const turn = trace.turns[0]!;
assert.equal(turn.steps.length, 0);
assert.equal(Number.isFinite(turn.startedAt), true);
assert.equal(Number.isFinite(turn.endedAt), true);
assert.equal(turn.startedAt, 1_000);
assert.equal(turn.endedAt, 2_500);
assert.equal(turn.durationMs, 1_500);
assert.equal(JSON.parse(JSON.stringify(turn)).startedAt, 1_000);
});
test('a tool failure the turn recovered from does not fail the turn', () => {
// The ledger's terminal verdict decides whether the turn failed; the failed
// step only locates a cause once that is established.
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [
event({
id: 'dispatch-1',
ts: 1_000,
actions: {
toolDispatch: {
protocol: 't1_after_preflight_v1',
operationId: 'op-1',
providerToolCallId: 'tool-call-1',
toolName: 'Bash',
canonicalArgsHash: 'hash',
recoveryMode: 'replay_safe',
},
},
}),
event({
id: 'response-1',
ts: 1_200,
role: 'tool',
author: 'tool',
content: {
kind: 'function_response',
id: 'tool-call-1',
name: 'Bash',
result: 'boom',
isError: true,
},
}),
event({
id: 'final-1',
ts: 2_000,
status: 'completed',
content: { kind: 'text', text: 'recovered and finished' },
}),
],
modelCallAttempts: [],
});
assert.equal(trace.turns[0]?.failure, undefined, 'a handled failure is not a failed turn');
const tool = trace.turns[0]!.steps.find((step) => step.kind === 'tool');
assert.equal(
tool?.kind === 'tool' ? tool.status : undefined,
'failed',
'the step still failed',
);
});
test('emits the written compaction boundary, which is not the summarizer call', () => {
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [
event({
id: 'history-compact:checkpoint-7',
turnId: 'history-compact:12',
runId: 'history-compact:checkpoint-7',
ts: 900,
role: 'user',
author: 'system',
content: { kind: 'text', text: '<maka_history_compact_checkpoint>' },
}),
],
modelCallAttempts: [attempt({ callKind: 'history_compact', logicalCallId: 'call-compact' })],
});
const boundary = trace.turns
.flatMap((turn) => turn.steps)
.find((step) => step.kind === 'compaction');
assert.ok(boundary, 'the checkpoint is a step in its own right');
assert.equal(
boundary.kind === 'compaction' ? boundary.checkpointId : undefined,
'checkpoint-7',
);
// The summarizer request is still its own model call, counted as spend.
assert.equal(trace.totals.compactions, 1);
assert.equal(trace.totals.modelAttempts, 1);
});
test('separates the declared recovery policy from a recovery that happened', () => {
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [
event({
id: 'dispatch-1',
ts: 1_000,
actions: {
toolDispatch: {
protocol: 't1_after_preflight_v1',
operationId: 'op-1',
providerToolCallId: 'tool-call-1',
toolName: 'Bash',
canonicalArgsHash: 'hash',
recoveryMode: 'reconcile',
},
},
}),
event({
id: 'recovery-1',
ts: 1_400,
actions: {
toolRecovery: {
kind: 'maka.tool.recovery_decision',
version: 1,
payload: {
protocol: 'tool_recovery_v1',
operationId: 'op-1',
disposition: 'parked',
reasonCode: 'reconcile_diverged',
evidenceEventIds: ['dispatch-1'],
},
},
},
}),
],
modelCallAttempts: [],
});
const tool = trace.turns[0]!.steps.find((step) => step.kind === 'tool');
assert.ok(tool && tool.kind === 'tool');
assert.equal(tool.recoveryPolicy, 'reconcile', 'the policy every dispatch declares');
assert.deepEqual(
tool.recovered,
{ disposition: 'parked', reasonCode: 'reconcile_diverged' },
'and the decision that was actually recorded',
);
});
test('an unreadable record is a known gap even with no other evidence of one', () => {
// The reader counts what it could not decode; the projection has to carry
// that through, or spend nobody can see reads as a clean session.
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [],
modelCallAttempts: [attempt()],
unreadableRecords: 2,
});
assert.equal(trace.coverage.unreadableRecords, 2);
assert.equal(trace.coverage.modelCalls, 'partial', 'records nobody can read are a gap');
});
test('a session that is only unreadable records is still a covered session', () => {
const trace = projectSessionTrace({
sessionId: 'session-1',
runtimeEvents: [],
modelCallAttempts: [],
unreadableRecords: 1,
});
assert.equal(trace.coverage.modelCalls, 'partial');
assert.equal(trace.coverage.unreadableRecords, 1);
assert.notEqual(trace.coverage.modelCalls, 'none', 'not "nothing happened"');
});
});