@openagentsy/ledger 0.0.0-stage → 0.2.0

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.
Files changed (86) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +20 -2
  3. package/data/pricing/litellm.json +8882 -0
  4. package/data/pricing/models.dev.json +12151 -0
  5. package/data/pricing/openrouter.json +1017 -0
  6. package/dist/db.d.ts +44 -0
  7. package/dist/db.d.ts.map +1 -0
  8. package/dist/db.js +97 -0
  9. package/dist/db.js.map +1 -0
  10. package/dist/export.d.ts +71 -0
  11. package/dist/export.d.ts.map +1 -0
  12. package/dist/export.js +78 -0
  13. package/dist/export.js.map +1 -0
  14. package/dist/footprint.d.ts +24 -0
  15. package/dist/footprint.d.ts.map +1 -0
  16. package/dist/footprint.js +33 -0
  17. package/dist/footprint.js.map +1 -0
  18. package/dist/git.d.ts +44 -0
  19. package/dist/git.d.ts.map +1 -0
  20. package/dist/git.js +126 -0
  21. package/dist/git.js.map +1 -0
  22. package/dist/git.test.d.ts +2 -0
  23. package/dist/git.test.d.ts.map +1 -0
  24. package/dist/git.test.js +101 -0
  25. package/dist/git.test.js.map +1 -0
  26. package/dist/index.d.ts +19 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +19 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/ingest.d.ts +133 -0
  31. package/dist/ingest.d.ts.map +1 -0
  32. package/dist/ingest.js +324 -0
  33. package/dist/ingest.js.map +1 -0
  34. package/dist/ingest.test.d.ts +2 -0
  35. package/dist/ingest.test.d.ts.map +1 -0
  36. package/dist/ingest.test.js +245 -0
  37. package/dist/ingest.test.js.map +1 -0
  38. package/dist/outcomes.d.ts +81 -0
  39. package/dist/outcomes.d.ts.map +1 -0
  40. package/dist/outcomes.js +85 -0
  41. package/dist/outcomes.js.map +1 -0
  42. package/dist/outcomes.test.d.ts +2 -0
  43. package/dist/outcomes.test.d.ts.map +1 -0
  44. package/dist/outcomes.test.js +160 -0
  45. package/dist/outcomes.test.js.map +1 -0
  46. package/dist/policy.d.ts +48 -0
  47. package/dist/policy.d.ts.map +1 -0
  48. package/dist/policy.js +99 -0
  49. package/dist/policy.js.map +1 -0
  50. package/dist/policy.test.d.ts +2 -0
  51. package/dist/policy.test.d.ts.map +1 -0
  52. package/dist/policy.test.js +126 -0
  53. package/dist/policy.test.js.map +1 -0
  54. package/dist/pricing.d.ts +92 -0
  55. package/dist/pricing.d.ts.map +1 -0
  56. package/dist/pricing.js +341 -0
  57. package/dist/pricing.js.map +1 -0
  58. package/dist/pricing.test.d.ts +2 -0
  59. package/dist/pricing.test.d.ts.map +1 -0
  60. package/dist/pricing.test.js +167 -0
  61. package/dist/pricing.test.js.map +1 -0
  62. package/dist/query.d.ts +134 -0
  63. package/dist/query.d.ts.map +1 -0
  64. package/dist/query.js +247 -0
  65. package/dist/query.js.map +1 -0
  66. package/dist/query.test.d.ts +2 -0
  67. package/dist/query.test.d.ts.map +1 -0
  68. package/dist/query.test.js +242 -0
  69. package/dist/query.test.js.map +1 -0
  70. package/dist/schema.d.ts +33 -0
  71. package/dist/schema.d.ts.map +1 -0
  72. package/dist/schema.js +139 -0
  73. package/dist/schema.js.map +1 -0
  74. package/dist/schema.test.d.ts +2 -0
  75. package/dist/schema.test.d.ts.map +1 -0
  76. package/dist/schema.test.js +129 -0
  77. package/dist/schema.test.js.map +1 -0
  78. package/dist/waste.d.ts +116 -0
  79. package/dist/waste.d.ts.map +1 -0
  80. package/dist/waste.js +553 -0
  81. package/dist/waste.js.map +1 -0
  82. package/dist/waste.test.d.ts +2 -0
  83. package/dist/waste.test.d.ts.map +1 -0
  84. package/dist/waste.test.js +278 -0
  85. package/dist/waste.test.js.map +1 -0
  86. package/package.json +52 -4
@@ -0,0 +1,134 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import type { TokenCounts } from './pricing.js';
3
+ export type RoleTotals = {
4
+ role: string;
5
+ messages: number;
6
+ input: number;
7
+ output: number;
8
+ reasoning: number;
9
+ cacheRead: number;
10
+ cacheWrite: number;
11
+ tokens: number;
12
+ usd: number | null;
13
+ unpriced: number;
14
+ };
15
+ export type TaskTotals = {
16
+ taskId: string;
17
+ messages: number;
18
+ tokens: number;
19
+ usd: number | null;
20
+ };
21
+ export type TaskOutcome = {
22
+ taskId: string;
23
+ tokens: number;
24
+ usd: number | null;
25
+ };
26
+ export type CommitOutcome = {
27
+ commitSha: string;
28
+ tokens: number;
29
+ usd: number | null;
30
+ };
31
+ export type WasteTotals = {
32
+ tokens: number;
33
+ percentage: number;
34
+ };
35
+ export type OutcomeTotals = {
36
+ tasksCompleted: number;
37
+ tasksScrapped: number;
38
+ commits: number;
39
+ prsMerged: number;
40
+ };
41
+ export type OffloadRow = {
42
+ messageId: string;
43
+ provider: string;
44
+ model: string;
45
+ tokens: number;
46
+ usd: number | null;
47
+ };
48
+ export type LedgerTotals = {
49
+ messages: number;
50
+ tokens: number;
51
+ input: number;
52
+ output: number;
53
+ reasoning: number;
54
+ cacheRead: number;
55
+ cacheWrite: number;
56
+ usd: number | null;
57
+ unpricedMessages: number;
58
+ };
59
+ /**
60
+ * The totals per resolved role.
61
+ *
62
+ * A backfilled row carries the unresolved agent id in `role_raw`, and it is
63
+ * left out: it cannot be attributed, and a report that guessed would be
64
+ * wrong in a way nobody can see.
65
+ */
66
+ export declare function perRoleTotals(db: Database): RoleTotals[];
67
+ /** The totals per task. */
68
+ export declare function perTaskTotals(db: Database): TaskTotals[];
69
+ /** The tokens of one task, joined to the outcome that closed it. */
70
+ export declare function tokensPerCompletedTask(db: Database, days?: number): TaskOutcome[];
71
+ /** The tokens of one commit, joined to the outcome that recorded it. */
72
+ export declare function tokensPerCommit(db: Database, days?: number): CommitOutcome[];
73
+ /** The waste tokens and the share of the total they are. */
74
+ export declare function wasteTotals(db: Database): WasteTotals;
75
+ export type WasteKindTotals = {
76
+ kind: string;
77
+ count: number;
78
+ tokens: number;
79
+ /** The share of the waste tokens, in percent. */
80
+ percentage: number;
81
+ };
82
+ /**
83
+ * The waste tokens per kind, and the share each kind is of the waste.
84
+ *
85
+ * The kinds are the seven of §6.4, and the ones with no rows are simply
86
+ * absent: a kind that never happened does not need a line in a report.
87
+ */
88
+ export declare function wasteByKind(db: Database): WasteKindTotals[];
89
+ export type ResultTotals = {
90
+ tasksCompleted: number;
91
+ /** The tokens one completed task cost; null when there is no task. */
92
+ tokensPerTask: number | null;
93
+ commits: number;
94
+ /** The tokens one commit cost; null when there is no commit. */
95
+ tokensPerCommit: number | null;
96
+ prsMerged: number;
97
+ };
98
+ /**
99
+ * The exit metrics of §6.5: the tokens per completed task and the tokens per
100
+ * commit.
101
+ *
102
+ * They are built on the two joins that are already there, so the number a
103
+ * report shows and the number the task list shows are the same number.
104
+ */
105
+ export declare function tokensPerResult(db: Database, days?: number): ResultTotals;
106
+ /** How many outcomes of each kind there are. */
107
+ export declare function outcomeTotals(db: Database): OutcomeTotals;
108
+ /** The totals of every message. */
109
+ export declare function ledgerTotals(db: Database): LedgerTotals;
110
+ /** The rows of the offloaded work. */
111
+ export declare function offloadRows(db: Database): OffloadRow[];
112
+ /** The price of some tokens, looked up by the caller. */
113
+ export type PriceLookup = (provider: string, model: string, tokens: TokenCounts) => Promise<number | null>;
114
+ export type OffloadSavings = {
115
+ messages: number;
116
+ tokens: number;
117
+ /** What the local run cost. */
118
+ usd: number | null;
119
+ /** What the same tokens would have cost on the cloud. */
120
+ cloudUsd: number | null;
121
+ /** The difference, when both numbers are known. */
122
+ savedUsd: number | null;
123
+ };
124
+ /**
125
+ * The savings of the offloaded work.
126
+ *
127
+ * The cloud price of a local run is the price the same tokens would have
128
+ * had on the model the job would otherwise have used, so the lookup is the
129
+ * caller's: the ledger knows the token counts, and only the caller knows
130
+ * which cloud model was avoided. A lookup that knows nothing leaves the
131
+ * cloud price empty rather than pretending it was free.
132
+ */
133
+ export declare function offloadSavings(db: Database, price: PriceLookup): Promise<OffloadSavings>;
134
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
package/dist/query.js ADDED
@@ -0,0 +1,247 @@
1
+ /**
2
+ * The queries (§6.5).
3
+ *
4
+ * A token count follows the plan: the tokens of a message are its input, its
5
+ * output, its reasoning and its cache writes. The tokens read from the cache
6
+ * are left out, because they were paid for when they were written and
7
+ * counting them twice would make every number on the board too large.
8
+ */
9
+ const TOKENS = '(input + output + reasoning + cache_write)';
10
+ function n(value) {
11
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
12
+ }
13
+ function optionalNumber(value) {
14
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
15
+ }
16
+ /**
17
+ * The totals per resolved role.
18
+ *
19
+ * A backfilled row carries the unresolved agent id in `role_raw`, and it is
20
+ * left out: it cannot be attributed, and a report that guessed would be
21
+ * wrong in a way nobody can see.
22
+ */
23
+ export function perRoleTotals(db) {
24
+ const rows = db
25
+ .query(`SELECT role,
26
+ COUNT(*) AS messages,
27
+ SUM(input) AS input,
28
+ SUM(output) AS output,
29
+ SUM(reasoning) AS reasoning,
30
+ SUM(cache_read) AS cache_read,
31
+ SUM(cache_write) AS cache_write,
32
+ SUM(${TOKENS}) AS tokens,
33
+ SUM(COALESCE(cost_reported, cost_estimated)) AS usd,
34
+ SUM(CASE WHEN COALESCE(cost_reported, cost_estimated) IS NULL THEN 1 ELSE 0 END) AS unpriced
35
+ FROM usage
36
+ WHERE role IS NOT NULL AND role_raw IS NULL
37
+ GROUP BY role
38
+ ORDER BY tokens DESC`)
39
+ .all();
40
+ return rows.map((row) => ({
41
+ role: String(row.role ?? ''),
42
+ messages: n(row.messages),
43
+ input: n(row.input),
44
+ output: n(row.output),
45
+ reasoning: n(row.reasoning),
46
+ cacheRead: n(row.cache_read),
47
+ cacheWrite: n(row.cache_write),
48
+ tokens: n(row.tokens),
49
+ usd: optionalNumber(row.usd),
50
+ unpriced: n(row.unpriced),
51
+ }));
52
+ }
53
+ /** The totals per task. */
54
+ export function perTaskTotals(db) {
55
+ const rows = db
56
+ .query(`SELECT task_id,
57
+ COUNT(*) AS messages,
58
+ SUM(${TOKENS}) AS tokens,
59
+ SUM(COALESCE(cost_reported, cost_estimated)) AS usd
60
+ FROM usage
61
+ WHERE task_id IS NOT NULL
62
+ GROUP BY task_id
63
+ ORDER BY tokens DESC`)
64
+ .all();
65
+ return rows.map((row) => ({
66
+ taskId: String(row.task_id ?? ''),
67
+ messages: n(row.messages),
68
+ tokens: n(row.tokens),
69
+ usd: optionalNumber(row.usd),
70
+ }));
71
+ }
72
+ /** The tokens of one task, joined to the outcome that closed it. */
73
+ export function tokensPerCompletedTask(db, days = 30) {
74
+ const rows = db
75
+ .query(`SELECT o.task_id, SUM(u.input + u.output + u.reasoning + u.cache_write) AS tokens,
76
+ SUM(COALESCE(u.cost_reported, u.cost_estimated)) AS usd
77
+ FROM outcome o JOIN usage u ON u.task_id = o.task_id
78
+ WHERE o.kind = 'task_completed' AND o.ts > datetime('now', ?)
79
+ GROUP BY o.task_id`)
80
+ .all(`-${days} days`);
81
+ return rows.map((row) => ({
82
+ taskId: String(row.task_id ?? ''),
83
+ tokens: n(row.tokens),
84
+ usd: optionalNumber(row.usd),
85
+ }));
86
+ }
87
+ /** The tokens of one commit, joined to the outcome that recorded it. */
88
+ export function tokensPerCommit(db, days = 30) {
89
+ const rows = db
90
+ .query(`SELECT o.commit_sha, SUM(u.input + u.output + u.reasoning + u.cache_write) AS tokens,
91
+ SUM(COALESCE(u.cost_reported, u.cost_estimated)) AS usd
92
+ FROM outcome o JOIN usage u ON u.task_id = o.task_id
93
+ WHERE o.kind = 'commit' AND o.commit_sha IS NOT NULL AND o.ts > datetime('now', ?)
94
+ GROUP BY o.commit_sha`)
95
+ .all(`-${days} days`);
96
+ return rows.map((row) => ({
97
+ commitSha: String(row.commit_sha ?? ''),
98
+ tokens: n(row.tokens),
99
+ usd: optionalNumber(row.usd),
100
+ }));
101
+ }
102
+ /** The waste tokens and the share of the total they are. */
103
+ export function wasteTotals(db) {
104
+ const row = db
105
+ .query(`SELECT (SELECT COALESCE(SUM(tokens), 0) FROM waste) AS waste_tokens,
106
+ (SELECT COALESCE(SUM(${TOKENS}), 0) FROM usage) AS total_tokens`)
107
+ .get();
108
+ const waste = n(row?.waste_tokens);
109
+ const total = n(row?.total_tokens);
110
+ return { tokens: waste, percentage: total > 0 ? (waste / total) * 100 : 0 };
111
+ }
112
+ /**
113
+ * The waste tokens per kind, and the share each kind is of the waste.
114
+ *
115
+ * The kinds are the seven of §6.4, and the ones with no rows are simply
116
+ * absent: a kind that never happened does not need a line in a report.
117
+ */
118
+ export function wasteByKind(db) {
119
+ const rows = db
120
+ .query(`SELECT kind, COUNT(*) AS count, SUM(tokens) AS tokens
121
+ FROM waste GROUP BY kind ORDER BY tokens DESC, kind`)
122
+ .all();
123
+ const totals = wasteTotals(db);
124
+ return rows.map((row) => {
125
+ const tokens = n(row.tokens);
126
+ return {
127
+ kind: String(row.kind ?? ''),
128
+ count: n(row.count),
129
+ tokens,
130
+ percentage: totals.tokens > 0 ? (tokens / totals.tokens) * 100 : 0,
131
+ };
132
+ });
133
+ }
134
+ /**
135
+ * The exit metrics of §6.5: the tokens per completed task and the tokens per
136
+ * commit.
137
+ *
138
+ * They are built on the two joins that are already there, so the number a
139
+ * report shows and the number the task list shows are the same number.
140
+ */
141
+ export function tokensPerResult(db, days = 30) {
142
+ const tasks = tokensPerCompletedTask(db, days);
143
+ const commits = tokensPerCommit(db, days);
144
+ const outcomes = outcomeTotals(db);
145
+ const taskTokens = tasks.reduce((sum, row) => sum + row.tokens, 0);
146
+ const commitTokens = commits.reduce((sum, row) => sum + row.tokens, 0);
147
+ return {
148
+ tasksCompleted: tasks.length,
149
+ tokensPerTask: tasks.length > 0 ? taskTokens / tasks.length : null,
150
+ commits: commits.length,
151
+ tokensPerCommit: commits.length > 0 ? commitTokens / commits.length : null,
152
+ prsMerged: outcomes.prsMerged,
153
+ };
154
+ }
155
+ /** How many outcomes of each kind there are. */
156
+ export function outcomeTotals(db) {
157
+ const rows = db
158
+ .query('SELECT kind, COUNT(*) AS count FROM outcome GROUP BY kind')
159
+ .all();
160
+ const counts = new Map();
161
+ for (const row of rows) {
162
+ counts.set(String(row.kind ?? ''), n(row.count));
163
+ }
164
+ return {
165
+ tasksCompleted: counts.get('task_completed') ?? 0,
166
+ tasksScrapped: counts.get('task_scrapped') ?? 0,
167
+ commits: counts.get('commit') ?? 0,
168
+ prsMerged: counts.get('pr_merged') ?? 0,
169
+ };
170
+ }
171
+ /** The totals of every message. */
172
+ export function ledgerTotals(db) {
173
+ const row = db
174
+ .query(`SELECT COUNT(*) AS messages,
175
+ SUM(${TOKENS}) AS tokens,
176
+ SUM(input) AS input,
177
+ SUM(output) AS output,
178
+ SUM(reasoning) AS reasoning,
179
+ SUM(cache_read) AS cache_read,
180
+ SUM(cache_write) AS cache_write,
181
+ SUM(COALESCE(cost_reported, cost_estimated)) AS usd,
182
+ SUM(CASE WHEN COALESCE(cost_reported, cost_estimated) IS NULL THEN 1 ELSE 0 END) AS unpriced
183
+ FROM usage`)
184
+ .get();
185
+ return {
186
+ messages: n(row?.messages),
187
+ tokens: n(row?.tokens),
188
+ input: n(row?.input),
189
+ output: n(row?.output),
190
+ reasoning: n(row?.reasoning),
191
+ cacheRead: n(row?.cache_read),
192
+ cacheWrite: n(row?.cache_write),
193
+ usd: optionalNumber(row?.usd),
194
+ unpricedMessages: n(row?.unpriced),
195
+ };
196
+ }
197
+ /** The rows of the offloaded work. */
198
+ export function offloadRows(db) {
199
+ const rows = db
200
+ .query(`SELECT message_id, provider, model, ${TOKENS} AS tokens,
201
+ COALESCE(cost_reported, cost_estimated) AS usd
202
+ FROM usage
203
+ WHERE role = 'offload'
204
+ ORDER BY tokens DESC`)
205
+ .all();
206
+ return rows.map((row) => ({
207
+ messageId: String(row.message_id ?? ''),
208
+ provider: String(row.provider ?? ''),
209
+ model: String(row.model ?? ''),
210
+ tokens: n(row.tokens),
211
+ usd: optionalNumber(row.usd),
212
+ }));
213
+ }
214
+ /**
215
+ * The savings of the offloaded work.
216
+ *
217
+ * The cloud price of a local run is the price the same tokens would have
218
+ * had on the model the job would otherwise have used, so the lookup is the
219
+ * caller's: the ledger knows the token counts, and only the caller knows
220
+ * which cloud model was avoided. A lookup that knows nothing leaves the
221
+ * cloud price empty rather than pretending it was free.
222
+ */
223
+ export async function offloadSavings(db, price) {
224
+ const rows = offloadRows(db);
225
+ let tokens = 0;
226
+ let usd = null;
227
+ let cloudUsd = null;
228
+ for (const row of rows) {
229
+ tokens += row.tokens;
230
+ if (row.usd !== null)
231
+ usd = (usd ?? 0) + row.usd;
232
+ const cloud = await price(row.provider, row.model, {
233
+ input: 0,
234
+ output: row.tokens,
235
+ });
236
+ if (cloud !== null)
237
+ cloudUsd = (cloudUsd ?? 0) + cloud;
238
+ }
239
+ return {
240
+ messages: rows.length,
241
+ tokens,
242
+ usd,
243
+ cloudUsd,
244
+ savedUsd: usd !== null && cloudUsd !== null ? cloudUsd - usd : null,
245
+ };
246
+ }
247
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=query.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.test.d.ts","sourceRoot":"","sources":["../src/query.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,242 @@
1
+ import { mkdtemp, rm } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { estimateRemote, r } from '@openagentsy/footprint';
5
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
+ import { closeLedger, openLedger } from './db.js';
7
+ import { ledgerExport } from './export.js';
8
+ import { FOOTPRINT_METHOD_VERSION, recordFootprint } from './footprint.js';
9
+ import { recordAssistantMessage, recordWaste } from './ingest.js';
10
+ import { ledgerTotals, perRoleTotals, perTaskTotals, tokensPerCompletedTask, wasteTotals, } from './query.js';
11
+ let root;
12
+ beforeEach(async () => {
13
+ root = await mkdtemp(join(tmpdir(), 'openagentsy-query-'));
14
+ });
15
+ afterEach(async () => {
16
+ await rm(root, { recursive: true, force: true });
17
+ });
18
+ const CTX = {
19
+ rootSessionId: 'ses_root',
20
+ role: 'builder',
21
+ ts: '2026-10-26T10:00:00.000Z',
22
+ };
23
+ const BUILDER = {
24
+ id: 'msg_builder',
25
+ sessionID: 'ses_child',
26
+ providerID: 'anthropic',
27
+ modelID: 'claude-sonnet-4-5',
28
+ tokens: { input: 100, output: 40, reasoning: 10, cache: { read: 900, write: 5 } },
29
+ cost: 0.5,
30
+ };
31
+ const LEAD = {
32
+ id: 'msg_lead',
33
+ sessionID: 'ses_root',
34
+ providerID: 'anthropic',
35
+ modelID: 'claude-sonnet-4-5',
36
+ tokens: { input: 10, output: 5, reasoning: 0, cache: { read: 0, write: 0 } },
37
+ };
38
+ const OFFLOAD = {
39
+ id: 'msg_offload',
40
+ sessionID: 'ses_root',
41
+ providerID: 'ollama-local',
42
+ modelID: 'qwen3',
43
+ tokens: { input: 20, output: 10, reasoning: 0, cache: { read: 0, write: 0 } },
44
+ };
45
+ function seed() {
46
+ const db = openLedger(root);
47
+ try {
48
+ recordAssistantMessage(db, BUILDER, { ...CTX, role: 'builder', activeTaskId: 'oa-2b-1' });
49
+ recordAssistantMessage(db, LEAD, { ...CTX, role: 'lead', activeTaskId: 'oa-2b-1' });
50
+ recordAssistantMessage(db, OFFLOAD, { ...CTX, role: 'offload' });
51
+ recordWaste(db, {
52
+ rootSessionId: 'ses_root',
53
+ kind: 'retry',
54
+ tokens: 20,
55
+ detail: 'the same tool twice',
56
+ ts: CTX.ts,
57
+ });
58
+ db.query(`INSERT INTO outcome (root_session_id, task_id, commit_sha, kind, ts)
59
+ VALUES ('ses_root', 'oa-2b-1', 'abc1234', 'task_completed', '2026-10-26T11:00:00.000Z')`).run();
60
+ }
61
+ finally {
62
+ closeLedger(db);
63
+ }
64
+ }
65
+ describe('the queries', () => {
66
+ it('totals the tokens per resolved role', () => {
67
+ seed();
68
+ const db = openLedger(root);
69
+ try {
70
+ const roles = perRoleTotals(db);
71
+ expect(roles.map((row) => row.role)).toEqual(['builder', 'offload', 'lead']);
72
+ const builder = roles[0];
73
+ expect(builder?.messages).toBe(1);
74
+ expect(builder?.tokens).toBe(155);
75
+ expect(builder?.usd).toBe(0.5);
76
+ expect(builder?.unpriced).toBe(0);
77
+ const lead = roles[1];
78
+ expect(lead?.usd).toBe(null);
79
+ expect(lead?.unpriced).toBe(1);
80
+ }
81
+ finally {
82
+ closeLedger(db);
83
+ }
84
+ });
85
+ it('totals the tokens per task', () => {
86
+ seed();
87
+ const db = openLedger(root);
88
+ try {
89
+ const tasks = perTaskTotals(db);
90
+ expect(tasks).toHaveLength(1);
91
+ expect(tasks[0]?.taskId).toBe('oa-2b-1');
92
+ expect(tasks[0]?.messages).toBe(2);
93
+ expect(tasks[0]?.tokens).toBe(170);
94
+ expect(tasks[0]?.usd).toBe(0.5);
95
+ }
96
+ finally {
97
+ closeLedger(db);
98
+ }
99
+ });
100
+ it('joins the outcomes to the tokens of the task', () => {
101
+ seed();
102
+ const db = openLedger(root);
103
+ try {
104
+ const completed = tokensPerCompletedTask(db);
105
+ expect(completed).toHaveLength(1);
106
+ expect(completed[0]?.taskId).toBe('oa-2b-1');
107
+ expect(completed[0]?.tokens).toBe(170);
108
+ expect(completed[0]?.usd).toBe(0.5);
109
+ }
110
+ finally {
111
+ closeLedger(db);
112
+ }
113
+ });
114
+ it('shares the waste of the total', () => {
115
+ seed();
116
+ const db = openLedger(root);
117
+ try {
118
+ const waste = wasteTotals(db);
119
+ expect(waste.tokens).toBe(20);
120
+ expect(waste.percentage).toBeCloseTo(10, 5);
121
+ }
122
+ finally {
123
+ closeLedger(db);
124
+ }
125
+ });
126
+ it('totals every message', () => {
127
+ seed();
128
+ const db = openLedger(root);
129
+ try {
130
+ const totals = ledgerTotals(db);
131
+ expect(totals.messages).toBe(3);
132
+ expect(totals.tokens).toBe(200);
133
+ expect(totals.unpricedMessages).toBe(2);
134
+ }
135
+ finally {
136
+ closeLedger(db);
137
+ }
138
+ });
139
+ });
140
+ describe('the export', () => {
141
+ it('carries the counters and nothing else', () => {
142
+ seed();
143
+ const db = openLedger(root);
144
+ try {
145
+ const exported = ledgerExport(db, { generatedAt: '2026-10-26T12:00:00.000Z' });
146
+ expect(exported.schema).toBe('openagentsy.ledger/v1');
147
+ expect(exported.totals.messages).toBe(3);
148
+ expect(exported.totals.tokens).toBe(200);
149
+ expect(exported.byRole).toHaveLength(3);
150
+ expect(exported.byTask[0]?.taskId).toBe('oa-2b-1');
151
+ expect(exported.waste.percentage).toBeCloseTo(10, 5);
152
+ expect(exported.outcomes.tasksCompleted).toBe(1);
153
+ expect(exported.offload.messages).toBe(1);
154
+ expect(exported.offload.tokens).toBe(30);
155
+ }
156
+ finally {
157
+ closeLedger(db);
158
+ }
159
+ });
160
+ it('carries no prompt, no code and no identifier', () => {
161
+ seed();
162
+ const db = openLedger(root);
163
+ try {
164
+ const exported = ledgerExport(db);
165
+ const forbidden = [
166
+ 'prompt',
167
+ 'prompts',
168
+ 'text',
169
+ 'content',
170
+ 'code',
171
+ 'detail',
172
+ 'body',
173
+ 'parts',
174
+ 'completion',
175
+ 'transcript',
176
+ 'diff',
177
+ 'message_id',
178
+ 'messageId',
179
+ 'session_id',
180
+ 'sessionID',
181
+ 'commit_sha',
182
+ 'commitSha',
183
+ ];
184
+ const keys = new Set();
185
+ const walk = (value) => {
186
+ if (Array.isArray(value)) {
187
+ for (const item of value)
188
+ walk(item);
189
+ return;
190
+ }
191
+ if (typeof value === 'object' && value !== null) {
192
+ for (const [key, child] of Object.entries(value)) {
193
+ keys.add(key);
194
+ walk(child);
195
+ }
196
+ }
197
+ };
198
+ walk(exported);
199
+ expect([...keys].filter((key) => forbidden.includes(key))).toEqual([]);
200
+ const json = JSON.stringify(exported);
201
+ expect(json).not.toContain('msg_builder');
202
+ expect(json).not.toContain('ses_root');
203
+ expect(json).not.toContain('abc1234');
204
+ }
205
+ finally {
206
+ closeLedger(db);
207
+ }
208
+ });
209
+ });
210
+ describe('the footprint rows', () => {
211
+ it('stores the impact of a message with the method that produced it', () => {
212
+ const impact = estimateRemote({
213
+ outputTokens: 1000,
214
+ inputTokens: 500,
215
+ cacheReadTokens: 0,
216
+ cacheWriteTokens: 0,
217
+ }, { totalParamsB: r(300), activeParamsB: r(30), source: 'user' }, {
218
+ pue: r(1.2),
219
+ wueOnSite: r(0.5),
220
+ wueOffSite: r(1.6),
221
+ gridKgCo2PerKWh: r(0.4),
222
+ source: 'test',
223
+ disclosed: true,
224
+ }, { ttftS: r(0), tps: r(60) });
225
+ const db = openLedger(root);
226
+ try {
227
+ recordAssistantMessage(db, BUILDER, CTX);
228
+ recordFootprint(db, BUILDER.id, impact);
229
+ const row = db.query('SELECT * FROM footprint').get();
230
+ expect(row.message_id).toBe(BUILDER.id);
231
+ expect(row.energy_lo).toBe(impact.energyWh[0]);
232
+ expect(row.energy_hi).toBe(impact.energyWh[1]);
233
+ expect(row.energy_lo).toBeLessThanOrEqual(row.energy_hi);
234
+ expect(row.co2_lo).toBeLessThanOrEqual(row.co2_hi);
235
+ expect(row.method_version).toBe(FOOTPRINT_METHOD_VERSION);
236
+ }
237
+ finally {
238
+ closeLedger(db);
239
+ }
240
+ });
241
+ });
242
+ //# sourceMappingURL=query.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.test.js","sourceRoot":"","sources":["../src/query.test.ts"],"names":[],"mappings":"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"}