claude-agent-ledger 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,188 +0,0 @@
1
- // Property tests — invariants that must hold for ANY input, not just fixtures.
2
- // (Backend audit recommendation: cover pricing math + aggregator math.)
3
-
4
- import { describe, expect, test } from 'bun:test';
5
- import * as fc from 'fast-check';
6
- import { Aggregator, SubagentGraph } from '../src/aggregator.ts';
7
- import { costFor } from '../src/pricing.ts';
8
- import type { SessionTurn, TokenUsage } from '../src/types.ts';
9
-
10
- // Bounded-positive token counter — Anthropic emits non-negative integers.
11
- const arbTokens = (): fc.Arbitrary<number> => fc.integer({ min: 0, max: 10_000_000 });
12
-
13
- // Realistic Anthropic models that should price correctly.
14
- const arbModel = (): fc.Arbitrary<string> =>
15
- fc.constantFrom(
16
- 'claude-opus-4-7',
17
- 'claude-sonnet-4-6',
18
- 'claude-haiku-4-5-20251001',
19
- 'claude-opus-4-6',
20
- 'claude-sonnet-4-5',
21
- );
22
-
23
- const arbUsage = (): fc.Arbitrary<TokenUsage> =>
24
- fc.record({
25
- input_tokens: arbTokens(),
26
- output_tokens: arbTokens(),
27
- cache_read_input_tokens: fc.option(arbTokens(), { nil: undefined }),
28
- cache_creation: fc.option(
29
- fc.record({
30
- ephemeral_5m_input_tokens: fc.option(arbTokens(), { nil: undefined }),
31
- ephemeral_1h_input_tokens: fc.option(arbTokens(), { nil: undefined }),
32
- }),
33
- { nil: undefined },
34
- ),
35
- });
36
-
37
- const arbAssistantTurn = (): fc.Arbitrary<SessionTurn> =>
38
- fc
39
- .record({
40
- usage: arbUsage(),
41
- model: arbModel(),
42
- sessionId: fc.string({ minLength: 8, maxLength: 36 }),
43
- timestamp: fc.integer({ min: 1735689600000, max: 1798800000000 }).map(
44
- (ms) => new Date(ms).toISOString(),
45
- ),
46
- subagentType: fc.option(fc.string({ minLength: 1, maxLength: 20 }), { nil: undefined }),
47
- projectId: fc.option(fc.string({ minLength: 1, maxLength: 30 }), { nil: undefined }),
48
- parentSessionId: fc.option(fc.string({ minLength: 8, maxLength: 36 }), { nil: undefined }),
49
- toolUses: fc.option(fc.array(fc.string({ minLength: 1, maxLength: 10 }), { maxLength: 5 }), {
50
- nil: undefined,
51
- }),
52
- })
53
- .map((r) => ({
54
- type: 'assistant' as const,
55
- ...r,
56
- raw: r,
57
- }));
58
-
59
- describe('costFor() invariants', () => {
60
- test('cost is non-negative for any valid usage + model', () => {
61
- fc.assert(
62
- fc.property(arbUsage(), arbModel(), (usage, model) => {
63
- const c = costFor(usage, model);
64
- expect(c.totalCost).toBeGreaterThanOrEqual(0);
65
- expect(c.inputCost).toBeGreaterThanOrEqual(0);
66
- expect(c.outputCost).toBeGreaterThanOrEqual(0);
67
- expect(c.cacheReadCost).toBeGreaterThanOrEqual(0);
68
- }),
69
- { numRuns: 200 },
70
- );
71
- });
72
-
73
- test('totalCost equals sum of components', () => {
74
- fc.assert(
75
- fc.property(arbUsage(), arbModel(), (usage, model) => {
76
- const c = costFor(usage, model);
77
- const sum =
78
- c.inputCost +
79
- c.outputCost +
80
- c.cacheCreation5mCost +
81
- c.cacheCreation1hCost +
82
- c.cacheReadCost +
83
- c.serverToolUseCost;
84
- // Floating point — allow tiny epsilon.
85
- expect(Math.abs(c.totalCost - sum)).toBeLessThan(1e-9);
86
- }),
87
- { numRuns: 200 },
88
- );
89
- });
90
-
91
- test('cache_read is always cheaper than the same input as fresh tokens', () => {
92
- fc.assert(
93
- fc.property(arbTokens(), arbModel(), (n, model) => {
94
- const fresh = costFor({ input_tokens: n, output_tokens: 0 }, model).totalCost;
95
- const cached = costFor(
96
- { input_tokens: 0, output_tokens: 0, cache_read_input_tokens: n },
97
- model,
98
- ).totalCost;
99
- // Cached should be ≤ fresh (90% discount).
100
- expect(cached).toBeLessThanOrEqual(fresh + 1e-9);
101
- }),
102
- { numRuns: 100 },
103
- );
104
- });
105
-
106
- test('cache_creation_1h is always more expensive than cache_creation_5m for same tokens', () => {
107
- fc.assert(
108
- fc.property(fc.integer({ min: 1, max: 1_000_000 }), arbModel(), (n, model) => {
109
- const fiveMin = costFor(
110
- {
111
- input_tokens: 0,
112
- output_tokens: 0,
113
- cache_creation: { ephemeral_5m_input_tokens: n },
114
- },
115
- model,
116
- ).totalCost;
117
- const oneH = costFor(
118
- {
119
- input_tokens: 0,
120
- output_tokens: 0,
121
- cache_creation: { ephemeral_1h_input_tokens: n },
122
- },
123
- model,
124
- ).totalCost;
125
- expect(oneH).toBeGreaterThanOrEqual(fiveMin - 1e-9);
126
- }),
127
- { numRuns: 100 },
128
- );
129
- });
130
- });
131
-
132
- describe('Aggregator invariants', () => {
133
- test('total.cost equals sum of row.cost across any group key', () => {
134
- fc.assert(
135
- fc.property(fc.array(arbAssistantTurn(), { minLength: 0, maxLength: 50 }), (turns) => {
136
- const agg = new Aggregator(
137
- 'subagent',
138
- new Date('2025-01-01'),
139
- new Date('2027-01-01'),
140
- );
141
- for (const t of turns) agg.add(t);
142
- const report = agg.finalize();
143
- const rowSum = report.rows.reduce((s, r) => s + r.cost.totalCost, 0);
144
- expect(Math.abs(report.total.cost.totalCost - rowSum)).toBeLessThan(1e-6);
145
- }),
146
- { numRuns: 100 },
147
- );
148
- });
149
-
150
- test('non-assistant turns are ignored (no cost contribution)', () => {
151
- fc.assert(
152
- fc.property(arbUsage(), arbModel(), (usage, model) => {
153
- const userTurn: SessionTurn = {
154
- type: 'user',
155
- usage,
156
- model,
157
- sessionId: 'sess',
158
- timestamp: '2026-04-23T00:00:00Z',
159
- raw: {},
160
- };
161
- const agg = new Aggregator('subagent', new Date('2025-01-01'), new Date('2027-01-01'));
162
- agg.add(userTurn);
163
- const report = agg.finalize();
164
- expect(report.total.cost.totalCost).toBe(0);
165
- }),
166
- { numRuns: 50 },
167
- );
168
- });
169
- });
170
-
171
- describe('SubagentGraph invariants', () => {
172
- test('parent totalCost ≥ self cost (children only add)', () => {
173
- fc.assert(
174
- fc.property(
175
- fc.array(arbAssistantTurn(), { minLength: 1, maxLength: 30 }),
176
- (turns) => {
177
- const graph = new SubagentGraph();
178
- for (const t of turns) graph.add(t);
179
- const roots = graph.finalize(50);
180
- for (const r of roots) {
181
- expect(r.totalCost).toBeGreaterThanOrEqual(r.selfCost - 1e-9);
182
- }
183
- },
184
- ),
185
- { numRuns: 50 },
186
- );
187
- });
188
- });
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "lib": ["ES2022"],
7
- "types": ["bun-types"],
8
- "strict": true,
9
- "noUncheckedIndexedAccess": true,
10
- "noImplicitOverride": true,
11
- "skipLibCheck": true,
12
- "allowImportingTsExtensions": true,
13
- "resolveJsonModule": true,
14
- "isolatedModules": true,
15
- "noEmit": true,
16
- "forceConsistentCasingInFileNames": true,
17
- "verbatimModuleSyntax": true
18
- },
19
- "include": ["src/**/*", "bin/**/*", "test/**/*"]
20
- }