@knpkv/agent-usage 0.1.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 (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/dist/client/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  4. package/dist/client/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  5. package/dist/client/assets/index-CUuLXODk.css +1 -0
  6. package/dist/client/assets/index-DB-VXWqy.js +55 -0
  7. package/dist/client/index.html +15 -0
  8. package/dist/core/Attribution.d.ts +59 -0
  9. package/dist/core/Attribution.js +108 -0
  10. package/dist/core/Attribution.js.map +1 -0
  11. package/dist/core/Balances.d.ts +27 -0
  12. package/dist/core/Balances.js +63 -0
  13. package/dist/core/Balances.js.map +1 -0
  14. package/dist/core/ClaudeLimits.d.ts +58 -0
  15. package/dist/core/ClaudeLimits.js +125 -0
  16. package/dist/core/ClaudeLimits.js.map +1 -0
  17. package/dist/core/ClaudeLimitsLive.d.ts +18 -0
  18. package/dist/core/ClaudeLimitsLive.js +36 -0
  19. package/dist/core/ClaudeLimitsLive.js.map +1 -0
  20. package/dist/core/ClaudeReader.d.ts +27 -0
  21. package/dist/core/ClaudeReader.js +154 -0
  22. package/dist/core/ClaudeReader.js.map +1 -0
  23. package/dist/core/CodexReader.d.ts +91 -0
  24. package/dist/core/CodexReader.js +365 -0
  25. package/dist/core/CodexReader.js.map +1 -0
  26. package/dist/core/Database.d.ts +4 -0
  27. package/dist/core/Database.js +65 -0
  28. package/dist/core/Database.js.map +1 -0
  29. package/dist/core/Ingest.d.ts +55 -0
  30. package/dist/core/Ingest.js +267 -0
  31. package/dist/core/Ingest.js.map +1 -0
  32. package/dist/core/Model.d.ts +160 -0
  33. package/dist/core/Model.js +125 -0
  34. package/dist/core/Model.js.map +1 -0
  35. package/dist/core/Pricing.d.ts +42 -0
  36. package/dist/core/Pricing.js +126 -0
  37. package/dist/core/Pricing.js.map +1 -0
  38. package/dist/core/Readers.d.ts +42 -0
  39. package/dist/core/Readers.js +18 -0
  40. package/dist/core/Readers.js.map +1 -0
  41. package/dist/core/Report.d.ts +53 -0
  42. package/dist/core/Report.js +270 -0
  43. package/dist/core/Report.js.map +1 -0
  44. package/dist/core/Store.d.ts +94 -0
  45. package/dist/core/Store.js +376 -0
  46. package/dist/core/Store.js.map +1 -0
  47. package/dist/core/Tickets.d.ts +42 -0
  48. package/dist/core/Tickets.js +125 -0
  49. package/dist/core/Tickets.js.map +1 -0
  50. package/dist/index.d.ts +14 -0
  51. package/dist/index.js +15 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/main.d.ts +2 -0
  54. package/dist/main.js +65 -0
  55. package/dist/main.js.map +1 -0
  56. package/dist/server/Api.d.ts +304 -0
  57. package/dist/server/Api.js +20 -0
  58. package/dist/server/Api.js.map +1 -0
  59. package/dist/server/Config.d.ts +27 -0
  60. package/dist/server/Config.js +49 -0
  61. package/dist/server/Config.js.map +1 -0
  62. package/dist/server/Handlers.d.ts +5 -0
  63. package/dist/server/Handlers.js +48 -0
  64. package/dist/server/Handlers.js.map +1 -0
  65. package/dist/server/HttpApplication.d.ts +7 -0
  66. package/dist/server/HttpApplication.js +70 -0
  67. package/dist/server/HttpApplication.js.map +1 -0
  68. package/dist/server/OwnerSession.d.ts +60 -0
  69. package/dist/server/OwnerSession.js +206 -0
  70. package/dist/server/OwnerSession.js.map +1 -0
  71. package/dist/server/Runtime.d.ts +57 -0
  72. package/dist/server/Runtime.js +85 -0
  73. package/dist/server/Runtime.js.map +1 -0
  74. package/dist/server/Server.d.ts +15 -0
  75. package/dist/server/Server.js +39 -0
  76. package/dist/server/Server.js.map +1 -0
  77. package/dist/shared/contracts.d.ts +309 -0
  78. package/dist/shared/contracts.js +105 -0
  79. package/dist/shared/contracts.js.map +1 -0
  80. package/dist/tsconfig.tsbuildinfo +1 -0
  81. package/package.json +68 -0
@@ -0,0 +1,376 @@
1
+ /**
2
+ * The per-machine SQLite store: Usage Events, Ingest Cursors, Limit Snapshots, Balance Readings and
3
+ * cached ticket titles.
4
+ *
5
+ * **Mental model**
6
+ *
7
+ * - **Facts only** (ADR 0002). Rows hold token counts and Attribution Inputs; Bookings and costs are
8
+ * derived by callers. No column can hold prompt or response text.
9
+ * - **A chunk commits whole.** A file chunk's events, observations and the cursor past them land in
10
+ * one transaction, so a pass killed mid-backfill neither loses nor double-counts: the next pass
11
+ * resumes from the cursor, and event keys absorb any overlap.
12
+ * - **Every observation is kept; series are compressed when read.** Rollouts are read file by file,
13
+ * so a limit's history arrives interleaved; compressing at write time would lose confirmations a
14
+ * later file needs. Reads keep only the rows where a series' reading changed (Codex echoes one per
15
+ * request), plus each series' last observation, which is what "read … ago" reports.
16
+ * - **Usage is pre-aggregated into 15-minute buckets** by every dimension pricing and Booking need.
17
+ * Every time zone's offset is a multiple of 15 minutes, so callers roll buckets up into local
18
+ * hours, days and weeks without touching raw events.
19
+ *
20
+ * @module
21
+ */
22
+ import { SqliteMigrator } from "@effect/sql-sqlite-node";
23
+ import { Context, Effect, Layer, Option, Schema } from "effect";
24
+ import { SqlClient } from "effect/sql";
25
+ import { Agent, AttributionInputs, BalanceKind, BalanceValue, Count, LimitReading, ObservationSource, Tokens, WindowMinutes } from "./Model.js";
26
+ import { LONG_PROMPT_TOKENS } from "./Pricing.js";
27
+ export class StoreError extends Schema.TaggedError()("StoreError", {
28
+ operation: Schema.String,
29
+ cause: Schema.Defect()
30
+ }) {
31
+ }
32
+ const storeError = (operation) => (cause) => new StoreError({ operation, cause });
33
+ /** How far a source file has been read, and the reader state to resume with (reader-owned JSON). */
34
+ export const Cursor = Schema.Struct({
35
+ /** Device and inode: a file that was replaced reads from 0 again. */
36
+ identity: Schema.String,
37
+ offset: Count,
38
+ state: Schema.String
39
+ });
40
+ /** Usage summed over one 15-minute bucket and every dimension that prices or books it. */
41
+ export const UsageGroup = Schema.Struct({
42
+ bucketStart: Schema.Int,
43
+ agent: Agent,
44
+ model: Schema.String,
45
+ fast: Schema.Boolean,
46
+ longPrompt: Schema.Boolean,
47
+ attribution: AttributionInputs,
48
+ requests: Count,
49
+ tokens: Tokens
50
+ });
51
+ export const TicketTitle = Schema.Struct({
52
+ key: Schema.String,
53
+ /** Null when the lookup ran and found no title. */
54
+ summary: Schema.NullOr(Schema.String),
55
+ fetchedAt: Schema.Int
56
+ });
57
+ export const BUCKET_MILLIS = 15 * 60 * 1000;
58
+ const EVENT_BATCH = 400;
59
+ /**
60
+ * A limit series' identity, as the report draws it: Codex windows by length (a plan change moves the
61
+ * weekly window between its primary and secondary slots), everything else by the provider's key.
62
+ * Compression must use the same identity, or a slot change hides a real transition.
63
+ */
64
+ const seriesOf = (table) => `CASE WHEN ${table}source = 'codex-rollout' AND ${table}window_minutes IS NOT NULL` +
65
+ ` THEN 'w' || ${table}window_minutes ELSE ${table}label END`;
66
+ const GroupRow = Schema.Struct({
67
+ bucket_start: Schema.Int,
68
+ agent: Agent,
69
+ model: Schema.String,
70
+ fast: Schema.Literals([0, 1]),
71
+ long_prompt: Schema.Literals([0, 1]),
72
+ cwd: Schema.String,
73
+ branch: Schema.String,
74
+ active_ticket: Schema.NullOr(Schema.String),
75
+ requests: Count,
76
+ input: Count,
77
+ output: Count,
78
+ reasoning: Count,
79
+ cache_read: Count,
80
+ cache_write_5m: Count,
81
+ cache_write_1h: Count
82
+ });
83
+ const SnapshotRow = Schema.Struct({
84
+ agent: Agent,
85
+ machine: Schema.String,
86
+ source: ObservationSource,
87
+ label: Schema.String,
88
+ window_minutes: WindowMinutes,
89
+ observed_at: Schema.Int,
90
+ reading: Schema.fromJsonString(LimitReading)
91
+ });
92
+ const BalanceRow = Schema.Struct({
93
+ kind: BalanceKind,
94
+ machine: Schema.String,
95
+ observed_at: Schema.Int,
96
+ value: Schema.fromJsonString(BalanceValue)
97
+ });
98
+ const CursorRow = Schema.Struct({ identity: Schema.String, offset: Count, state: Schema.String });
99
+ const TicketRow = Schema.Struct({ key: Schema.String, summary: Schema.NullOr(Schema.String), fetched_at: Schema.Int });
100
+ const ReadingJson = Schema.fromJsonString(LimitReading);
101
+ const BalanceJson = Schema.fromJsonString(BalanceValue);
102
+ const encodeReading = Schema.encodeSync(ReadingJson);
103
+ const encodeBalance = Schema.encodeSync(BalanceJson);
104
+ const migrations = SqliteMigrator.fromRecord({
105
+ "0001_usage": Effect.gen(function* () {
106
+ const sql = yield* SqlClient.SqlClient;
107
+ yield* sql `
108
+ CREATE TABLE usage_events (
109
+ agent TEXT NOT NULL,
110
+ dedupe_key TEXT NOT NULL,
111
+ machine TEXT NOT NULL,
112
+ session_id TEXT NOT NULL,
113
+ occurred_at INTEGER NOT NULL,
114
+ model TEXT NOT NULL,
115
+ fast INTEGER NOT NULL CHECK (fast IN (0, 1)),
116
+ input INTEGER NOT NULL,
117
+ output INTEGER NOT NULL,
118
+ reasoning INTEGER NOT NULL,
119
+ cache_read INTEGER NOT NULL,
120
+ cache_write_5m INTEGER NOT NULL,
121
+ cache_write_1h INTEGER NOT NULL,
122
+ cwd TEXT NOT NULL,
123
+ branch TEXT NOT NULL,
124
+ active_ticket TEXT,
125
+ PRIMARY KEY (agent, dedupe_key)
126
+ )
127
+ `;
128
+ yield* sql `CREATE INDEX usage_events_time ON usage_events (occurred_at)`;
129
+ yield* sql `
130
+ CREATE TABLE ingest_cursors (
131
+ agent TEXT NOT NULL,
132
+ file_key TEXT NOT NULL,
133
+ identity TEXT NOT NULL,
134
+ offset INTEGER NOT NULL,
135
+ state TEXT NOT NULL,
136
+ PRIMARY KEY (agent, file_key)
137
+ )
138
+ `;
139
+ yield* sql `
140
+ CREATE TABLE limit_snapshots (
141
+ agent TEXT NOT NULL,
142
+ machine TEXT NOT NULL,
143
+ source TEXT NOT NULL,
144
+ label TEXT NOT NULL,
145
+ window_minutes INTEGER,
146
+ observed_at INTEGER NOT NULL,
147
+ reading TEXT NOT NULL,
148
+ PRIMARY KEY (machine, agent, source, label, observed_at)
149
+ )
150
+ `;
151
+ yield* sql `CREATE INDEX limit_snapshots_time ON limit_snapshots (machine, observed_at)`;
152
+ yield* sql `
153
+ CREATE TABLE balance_readings (
154
+ kind TEXT NOT NULL,
155
+ machine TEXT NOT NULL,
156
+ observed_at INTEGER NOT NULL,
157
+ value TEXT NOT NULL,
158
+ PRIMARY KEY (kind, machine, observed_at)
159
+ )
160
+ `;
161
+ yield* sql `
162
+ CREATE TABLE tickets (
163
+ key TEXT PRIMARY KEY,
164
+ summary TEXT,
165
+ fetched_at INTEGER NOT NULL
166
+ )
167
+ `;
168
+ })
169
+ });
170
+ export class UsageStore extends Context.Service()("@knpkv/agent-usage/core/Store/UsageStore") {
171
+ static layer = Layer.effect(UsageStore, Effect.gen(function* () {
172
+ const sql = yield* SqlClient.SqlClient;
173
+ yield* SqliteMigrator.run({ loader: migrations }).pipe(Effect.mapError(storeError("migrate")));
174
+ const insertEvents = Effect.fnUntraced(function* (events) {
175
+ let added = 0;
176
+ for (let start = 0; start < events.length; start += EVENT_BATCH) {
177
+ const rows = events.slice(start, start + EVENT_BATCH).map((event) => ({
178
+ agent: event.agent,
179
+ dedupe_key: event.dedupeKey,
180
+ machine: event.machine,
181
+ session_id: event.sessionId,
182
+ occurred_at: event.occurredAt,
183
+ model: event.model,
184
+ fast: event.fast ? 1 : 0,
185
+ input: event.tokens.input,
186
+ output: event.tokens.output,
187
+ reasoning: event.tokens.reasoning,
188
+ cache_read: event.tokens.cacheRead,
189
+ cache_write_5m: event.tokens.cacheWrite5m,
190
+ cache_write_1h: event.tokens.cacheWrite1h,
191
+ cwd: event.attribution.cwd,
192
+ branch: event.attribution.branch,
193
+ active_ticket: event.attribution.activeTicket
194
+ }));
195
+ const inserted = yield* sql `
196
+ INSERT INTO usage_events ${sql.insert(rows)}
197
+ ON CONFLICT DO NOTHING
198
+ RETURNING dedupe_key
199
+ `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ dedupe_key: Schema.String })))));
200
+ added += inserted.length;
201
+ // A Claude request read again with larger counts (its final streamed usage) takes them;
202
+ // its time and attribution stay as first recorded. Codex keys are byte offsets: never.
203
+ const fresh = new Set(inserted.map((row) => row.dedupe_key));
204
+ for (const event of events.slice(start, start + EVENT_BATCH)) {
205
+ if (event.agent !== "claude" || fresh.has(event.dedupeKey))
206
+ continue;
207
+ yield* sql `
208
+ UPDATE usage_events SET
209
+ input = ${event.tokens.input}, output = ${event.tokens.output},
210
+ reasoning = ${event.tokens.reasoning}, cache_read = ${event.tokens.cacheRead},
211
+ cache_write_5m = ${event.tokens.cacheWrite5m}, cache_write_1h = ${event.tokens.cacheWrite1h}
212
+ WHERE agent = 'claude' AND dedupe_key = ${event.dedupeKey} AND output < ${event.tokens.output}
213
+ `;
214
+ }
215
+ }
216
+ return added;
217
+ });
218
+ const insertSnapshots = Effect.fnUntraced(function* (snapshots) {
219
+ for (let start = 0; start < snapshots.length; start += EVENT_BATCH) {
220
+ const rows = snapshots.slice(start, start + EVENT_BATCH).map((snapshot) => ({
221
+ agent: snapshot.agent,
222
+ machine: snapshot.machine,
223
+ source: snapshot.source,
224
+ label: snapshot.label,
225
+ window_minutes: snapshot.windowMinutes,
226
+ observed_at: snapshot.observedAt,
227
+ reading: encodeReading(snapshot.reading)
228
+ }));
229
+ yield* sql `INSERT INTO limit_snapshots ${sql.insert(rows)} ON CONFLICT DO NOTHING`;
230
+ }
231
+ });
232
+ const insertBalances = Effect.fnUntraced(function* (balances) {
233
+ for (let start = 0; start < balances.length; start += EVENT_BATCH) {
234
+ const rows = balances.slice(start, start + EVENT_BATCH).map((balance) => ({
235
+ kind: balance.kind,
236
+ machine: balance.machine,
237
+ observed_at: balance.observedAt,
238
+ value: encodeBalance(balance.value)
239
+ }));
240
+ yield* sql `INSERT INTO balance_readings ${sql.insert(rows)} ON CONFLICT DO NOTHING`;
241
+ }
242
+ });
243
+ const recordObservations = Effect.fn("UsageStore.recordObservations")((snapshots, balances) => sql.withTransaction(Effect.andThen(insertSnapshots(snapshots), insertBalances(balances))).pipe(Effect.mapError(storeError("record-observations"))));
244
+ const commitChunk = Effect.fn("UsageStore.commitChunk")((chunk) => sql.withTransaction(Effect.gen(function* () {
245
+ const eventsAdded = yield* insertEvents(chunk.events);
246
+ yield* insertSnapshots(chunk.snapshots);
247
+ yield* insertBalances(chunk.balances);
248
+ yield* sql `
249
+ INSERT INTO ingest_cursors ${sql.insert({
250
+ agent: chunk.agent,
251
+ file_key: chunk.fileKey,
252
+ identity: chunk.cursor.identity,
253
+ offset: chunk.cursor.offset,
254
+ state: chunk.cursor.state
255
+ })}
256
+ ON CONFLICT (agent, file_key) DO UPDATE SET
257
+ identity = excluded.identity, offset = excluded.offset, state = excluded.state
258
+ `;
259
+ return { eventsAdded };
260
+ })).pipe(Effect.mapError(storeError("commit-chunk"))));
261
+ const cursor = Effect.fn("UsageStore.cursor")((agent, fileKey) => sql `SELECT identity, offset, state FROM ingest_cursors WHERE agent = ${agent} AND file_key = ${fileKey}`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(CursorRow))), Effect.map((rows) => Option.fromNullishOr(rows[0])), Effect.mapError(storeError("read-cursor"))));
262
+ const usageGroups = Effect.fn("UsageStore.usageGroups")((range) => sql `
263
+ SELECT
264
+ (occurred_at - occurred_at % ${BUCKET_MILLIS}) AS bucket_start,
265
+ agent, model, fast,
266
+ (input + cache_read + cache_write_5m + cache_write_1h) > ${LONG_PROMPT_TOKENS} AS long_prompt,
267
+ cwd, branch, active_ticket,
268
+ count(*) AS requests,
269
+ sum(input) AS input, sum(output) AS output, sum(reasoning) AS reasoning,
270
+ sum(cache_read) AS cache_read, sum(cache_write_5m) AS cache_write_5m, sum(cache_write_1h) AS cache_write_1h
271
+ FROM usage_events
272
+ WHERE machine = ${range.machine} AND occurred_at >= ${range.from} AND occurred_at < ${range.to}
273
+ -- Requests with and without cache traffic price apart: a model may lack a cache rate, and
274
+ -- that must leave only those requests unpriced, not the whole bucket.
275
+ GROUP BY bucket_start, agent, model, fast, long_prompt, cache_read > 0,
276
+ cache_write_5m + cache_write_1h > 0, cwd, branch, active_ticket
277
+ ORDER BY bucket_start, agent, model
278
+ `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(GroupRow))), Effect.map((rows) => rows.map((row) => ({
279
+ bucketStart: row.bucket_start,
280
+ agent: row.agent,
281
+ model: row.model,
282
+ fast: row.fast === 1,
283
+ longPrompt: row.long_prompt === 1,
284
+ attribution: { cwd: row.cwd, branch: row.branch, activeTicket: row.active_ticket },
285
+ requests: row.requests,
286
+ tokens: {
287
+ input: row.input,
288
+ output: row.output,
289
+ reasoning: row.reasoning,
290
+ cacheRead: row.cache_read,
291
+ cacheWrite5m: row.cache_write_5m,
292
+ cacheWrite1h: row.cache_write_1h
293
+ }
294
+ }))), Effect.mapError(storeError("usage-groups"))));
295
+ const places = Effect.fn("UsageStore.places")((machine) => sql `SELECT DISTINCT branch, cwd FROM usage_events WHERE machine = ${machine}`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(Schema.Struct({ branch: Schema.String, cwd: Schema.String })))), Effect.mapError(storeError("places"))));
296
+ const limitSnapshots = Effect.fn("UsageStore.limitSnapshots")((range) => sql `
297
+ WITH in_range AS (
298
+ SELECT agent, machine, source, label, window_minutes, observed_at, reading, series,
299
+ coalesce(window_minutes, 'null') || ' ' || reading AS signature,
300
+ lag(coalesce(window_minutes, 'null') || ' ' || reading)
301
+ OVER (PARTITION BY agent, source, series ORDER BY observed_at) AS previous,
302
+ lag(observed_at) OVER (PARTITION BY agent, source, series ORDER BY observed_at) AS previous_at,
303
+ max(CASE WHEN label = '*' THEN observed_at END) OVER (
304
+ PARTITION BY agent, source ORDER BY observed_at ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
305
+ ) AS failed_at,
306
+ max(CASE WHEN label <> '*' THEN observed_at END) OVER (
307
+ PARTITION BY agent, source ORDER BY observed_at ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING
308
+ ) AS succeeded_at,
309
+ row_number() OVER (PARTITION BY agent, source, series ORDER BY observed_at DESC) AS from_end
310
+ FROM (
311
+ SELECT *, ${sql.literal(seriesOf(""))} AS series FROM limit_snapshots
312
+ WHERE machine = ${range.machine} AND observed_at >= ${range.from} AND observed_at < ${range.to}
313
+ )
314
+ ),
315
+ series_before AS (
316
+ SELECT agent, source, ${sql.literal(seriesOf(""))} AS series, max(observed_at) AS observed_at FROM limit_snapshots
317
+ WHERE machine = ${range.machine} AND observed_at < ${range.from}
318
+ GROUP BY agent, source, series
319
+ )
320
+ SELECT snapshot.agent, snapshot.machine, snapshot.source, snapshot.label, snapshot.window_minutes,
321
+ snapshot.observed_at, snapshot.reading
322
+ FROM series_before JOIN limit_snapshots AS snapshot
323
+ ON snapshot.machine = ${range.machine} AND snapshot.agent = series_before.agent
324
+ AND snapshot.source = series_before.source AND snapshot.observed_at = series_before.observed_at
325
+ AND ${sql.literal(seriesOf("snapshot."))} = series_before.series
326
+ UNION ALL
327
+ -- A row starts a new step when its reading changed, or when the other kind of reading (a
328
+ -- source-wide failure for a window, a window for a failure) came since this series' last row.
329
+ SELECT agent, machine, source, label, window_minutes, observed_at, reading FROM in_range
330
+ WHERE from_end = 1 OR previous IS NULL OR previous <> signature
331
+ OR (label <> '*' AND failed_at > previous_at)
332
+ OR (label = '*' AND succeeded_at > previous_at)
333
+ ORDER BY observed_at, agent, label
334
+ `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(SnapshotRow))), Effect.map((rows) => rows.map((row) => ({
335
+ agent: row.agent,
336
+ machine: row.machine,
337
+ source: row.source,
338
+ label: row.label,
339
+ windowMinutes: row.window_minutes,
340
+ observedAt: row.observed_at,
341
+ reading: row.reading
342
+ }))), Effect.mapError(storeError("limit-snapshots"))));
343
+ const latestBalances = Effect.fn("UsageStore.latestBalances")((machine) => sql `
344
+ SELECT kind, machine, observed_at, value FROM balance_readings AS outer_reading
345
+ WHERE machine = ${machine} AND observed_at = (
346
+ SELECT max(observed_at) FROM balance_readings AS inner_reading
347
+ WHERE inner_reading.kind = outer_reading.kind AND inner_reading.machine = outer_reading.machine
348
+ )
349
+ ORDER BY kind, machine
350
+ `.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(BalanceRow))), Effect.map((rows) => rows.map((row) => ({
351
+ kind: row.kind,
352
+ machine: row.machine,
353
+ observedAt: row.observed_at,
354
+ value: row.value
355
+ }))), Effect.mapError(storeError("latest-balances"))));
356
+ const tickets = Effect.fn("UsageStore.tickets")((keys) => keys.length === 0
357
+ ? Effect.succeed([])
358
+ : sql `SELECT key, summary, fetched_at FROM tickets WHERE ${sql.in("key", keys)}`.pipe(Effect.flatMap(Schema.decodeUnknownEffect(Schema.Array(TicketRow))), Effect.map((rows) => rows.map((row) => ({ key: row.key, summary: row.summary, fetchedAt: row.fetched_at }))), Effect.mapError(storeError("tickets"))));
359
+ const saveTicket = Effect.fn("UsageStore.saveTicket")((ticket) => sql `
360
+ INSERT INTO tickets ${sql.insert({ key: ticket.key, summary: ticket.summary, fetched_at: ticket.fetchedAt })}
361
+ ON CONFLICT (key) DO UPDATE SET summary = excluded.summary, fetched_at = excluded.fetched_at
362
+ `.pipe(Effect.asVoid, Effect.mapError(storeError("save-ticket"))));
363
+ return UsageStore.of({
364
+ cursor,
365
+ commitChunk,
366
+ recordObservations,
367
+ usageGroups,
368
+ places,
369
+ limitSnapshots,
370
+ latestBalances,
371
+ tickets,
372
+ saveTicket
373
+ });
374
+ }));
375
+ }
376
+ //# sourceMappingURL=Store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Store.js","sourceRoot":"","sources":["../../src/core/Store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Jira titles for ticket Bookings, looked up with `acli` and cached in the store for a day.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Optional decoration.** A Booking is its key; a title only helps read it. Lookups run in the
7
+ * background after an ingest pass, so a slow or missing `acli` never delays a page.
8
+ * - **Three answers, never blank.** A title is Known, or Unknown because Jira has no such ticket
9
+ * (`NotFound`, cached like a title) or because no lookup has succeeded yet (`NotLookedUp`).
10
+ * - **A failed lookup caches nothing**, so the next pass tries again, and the failure is reported
11
+ * rather than swallowed.
12
+ *
13
+ * @module
14
+ */
15
+ import { Effect } from "effect";
16
+ import { ChildProcessSpawner } from "effect/process";
17
+ import type { TicketTitleValue } from "./Model.js";
18
+ import { type StoreError, UsageStore } from "./Store.js";
19
+ declare const TicketLookupFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
20
+ readonly _tag: "TicketLookupFailed";
21
+ } & Readonly<A>;
22
+ export declare class TicketLookupFailed extends TicketLookupFailed_base<{
23
+ readonly reason: string;
24
+ }> {
25
+ }
26
+ /** Finds the summaries of the given keys; a key Jira does not return is absent from the map. */
27
+ export type TicketSearch = (keys: ReadonlyArray<string>) => Effect.Effect<ReadonlyMap<string, string>, TicketLookupFailed>;
28
+ /** Looks up keys whose cached title is missing or older than a day. Returns the lookups that failed. */
29
+ export declare const refreshTicketTitles: (keys: ReadonlyArray<string>, search: TicketSearch) => Effect.Effect<ReadonlyArray<TicketLookupFailed>, StoreError, UsageStore>;
30
+ /** The cached title of each key. */
31
+ export declare const ticketTitles: (keys: ReadonlyArray<string>) => Effect.Effect<Readonly<Record<string, TicketTitleValue>>, StoreError, UsageStore>;
32
+ /**
33
+ * What one `acli` search said, from its exit code and stdout. A nonzero exit is a failed lookup even
34
+ * when it printed something: a failed search cannot show that a ticket does not exist.
35
+ */
36
+ export declare const searchOutcome: (exitCode: number, stdout: string) => Effect.Effect<ReadonlyMap<string, string>, TicketLookupFailed>;
37
+ /**
38
+ * `acli jira workitem search` for a batch of keys. Keys reach the JQL only after the Booking
39
+ * pattern accepted them, so they cannot carry JQL syntax.
40
+ */
41
+ export declare const acliTicketSearch: Effect.Effect<TicketSearch, never, ChildProcessSpawner.ChildProcessSpawner>;
42
+ export {};
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Jira titles for ticket Bookings, looked up with `acli` and cached in the store for a day.
3
+ *
4
+ * **Mental model**
5
+ *
6
+ * - **Optional decoration.** A Booking is its key; a title only helps read it. Lookups run in the
7
+ * background after an ingest pass, so a slow or missing `acli` never delays a page.
8
+ * - **Three answers, never blank.** A title is Known, or Unknown because Jira has no such ticket
9
+ * (`NotFound`, cached like a title) or because no lookup has succeeded yet (`NotLookedUp`).
10
+ * - **A failed lookup caches nothing**, so the next pass tries again, and the failure is reported
11
+ * rather than swallowed.
12
+ *
13
+ * @module
14
+ */
15
+ import { Clock, Data, Duration, Effect, Schema, Stream } from "effect";
16
+ import { ChildProcess, ChildProcessSpawner } from "effect/process";
17
+ import { UsageStore } from "./Store.js";
18
+ export class TicketLookupFailed extends Data.TaggedError("TicketLookupFailed") {
19
+ }
20
+ const ACLI_TIMEOUT = Duration.seconds(30);
21
+ const FRESH_FOR_MILLIS = 24 * 60 * 60 * 1000;
22
+ const KEYS_PER_SEARCH = 50;
23
+ /** Looks up keys whose cached title is missing or older than a day. Returns the lookups that failed. */
24
+ export const refreshTicketTitles = (keys, search) => Effect.gen(function* () {
25
+ const store = yield* UsageStore;
26
+ const now = yield* Clock.currentTimeMillis;
27
+ const cached = new Map((yield* store.tickets(keys)).map((ticket) => [ticket.key, ticket.fetchedAt]));
28
+ const stale = [...new Set(keys)].filter((key) => {
29
+ const fetchedAt = cached.get(key);
30
+ return fetchedAt === undefined || now - fetchedAt > FRESH_FOR_MILLIS;
31
+ });
32
+ const failures = [];
33
+ for (let start = 0; start < stale.length; start += KEYS_PER_SEARCH) {
34
+ const batch = stale.slice(start, start + KEYS_PER_SEARCH);
35
+ const found = yield* Effect.result(search(batch));
36
+ if (found._tag === "Success") {
37
+ for (const key of batch) {
38
+ yield* store.saveTicket({ key, summary: found.success.get(key) ?? null, fetchedAt: now });
39
+ }
40
+ continue;
41
+ }
42
+ // Jira refuses a whole search when one key in it does not exist. Asking key by key keeps one
43
+ // bad key from leaving the rest untitled; a key that still fails stays uncached and reported.
44
+ if (batch.length === 1) {
45
+ failures.push(found.failure);
46
+ continue;
47
+ }
48
+ for (const key of batch) {
49
+ const single = yield* Effect.result(search([key]));
50
+ if (single._tag === "Failure")
51
+ failures.push(single.failure);
52
+ else
53
+ yield* store.saveTicket({ key, summary: single.success.get(key) ?? null, fetchedAt: now });
54
+ }
55
+ }
56
+ return failures;
57
+ });
58
+ /** The cached title of each key. */
59
+ export const ticketTitles = (keys) => Effect.gen(function* () {
60
+ const store = yield* UsageStore;
61
+ const cached = new Map((yield* store.tickets(keys)).map((ticket) => [ticket.key, ticket.summary]));
62
+ const titles = {};
63
+ for (const key of keys) {
64
+ const summary = cached.get(key);
65
+ titles[key] = summary === undefined
66
+ ? { _tag: "Unknown", reason: "NotLookedUp" }
67
+ : summary === null
68
+ ? { _tag: "Unknown", reason: "NotFound" }
69
+ : { _tag: "Known", summary };
70
+ }
71
+ return titles;
72
+ });
73
+ const SearchResult = Schema.fromJsonString(Schema.Array(Schema.Struct({
74
+ key: Schema.String,
75
+ fields: Schema.Struct({ summary: Schema.optionalKey(Schema.String) })
76
+ })));
77
+ const decodeSearch = Schema.decodeUnknownEffect(SearchResult);
78
+ /**
79
+ * What one `acli` search said, from its exit code and stdout. A nonzero exit is a failed lookup even
80
+ * when it printed something: a failed search cannot show that a ticket does not exist.
81
+ */
82
+ export const searchOutcome = (exitCode, stdout) => exitCode !== 0
83
+ ? Effect.fail(new TicketLookupFailed({ reason: `acli exited with status ${exitCode}` }))
84
+ : decodeSearch(stdout).pipe(Effect.mapError(() => new TicketLookupFailed({ reason: "acli returned an unreadable reply" })), Effect.map((items) => {
85
+ const found = new Map();
86
+ for (const item of items) {
87
+ if (item.fields.summary !== undefined)
88
+ found.set(item.key, item.fields.summary);
89
+ }
90
+ return found;
91
+ }));
92
+ /**
93
+ * `acli jira workitem search` for a batch of keys. Keys reach the JQL only after the Booking
94
+ * pattern accepted them, so they cannot carry JQL syntax.
95
+ */
96
+ export const acliTicketSearch = Effect.gen(function* () {
97
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
98
+ const search = (keys) => Effect.scoped(Effect.gen(function* () {
99
+ const handle = yield* spawner.spawn(ChildProcess.make("acli", [
100
+ "jira",
101
+ "workitem",
102
+ "search",
103
+ "--jql",
104
+ `key in (${keys.join(",")})`,
105
+ "--fields",
106
+ "key,summary",
107
+ "--limit",
108
+ String(keys.length),
109
+ "--json"
110
+ ]));
111
+ const [stdout, exitCode] = yield* Effect.all([Stream.mkString(Stream.decodeText(handle.stdout)), handle.exitCode], { concurrency: 2 });
112
+ return { stdout, exitCode };
113
+ })).pipe(
114
+ // A hung acli (waiting on a login, a dead network) must not hold up the work behind it.
115
+ Effect.timeoutOrElse({
116
+ duration: ACLI_TIMEOUT,
117
+ orElse: () => Effect.fail(new TicketLookupFailed({ reason: "acli did not answer within 30 seconds" }))
118
+ }), Effect.mapError((error) => error._tag === "TicketLookupFailed" ? error : new TicketLookupFailed({
119
+ reason: error.reason._tag === "NotFound"
120
+ ? "ticket titles need acli, which is not installed"
121
+ : `acli could not run (${error.reason._tag})`
122
+ })), Effect.flatMap(({ exitCode, stdout }) => searchOutcome(exitCode, stdout)));
123
+ return search;
124
+ });
125
+ //# sourceMappingURL=Tickets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tickets.js","sourceRoot":"","sources":["../../src/core/Tickets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `@knpkv/agent-usage` — Claude and Codex subscription usage on this Machine: per ticket, over
3
+ * time, and against subscription limits.
4
+ *
5
+ * @module
6
+ */
7
+ export * as Attribution from "./core/Attribution.js";
8
+ export * as Ingest from "./core/Ingest.js";
9
+ export * as Model from "./core/Model.js";
10
+ export * as Pricing from "./core/Pricing.js";
11
+ export * as Report from "./core/Report.js";
12
+ export * as Store from "./core/Store.js";
13
+ export * as Api from "./server/Api.js";
14
+ export * as Server from "./server/Server.js";
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@knpkv/agent-usage` — Claude and Codex subscription usage on this Machine: per ticket, over
3
+ * time, and against subscription limits.
4
+ *
5
+ * @module
6
+ */
7
+ export * as Attribution from "./core/Attribution.js";
8
+ export * as Ingest from "./core/Ingest.js";
9
+ export * as Model from "./core/Model.js";
10
+ export * as Pricing from "./core/Pricing.js";
11
+ export * as Report from "./core/Report.js";
12
+ export * as Store from "./core/Store.js";
13
+ export * as Api from "./server/Api.js";
14
+ export * as Server from "./server/Server.js";
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,WAAW,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAA;AAC1C,OAAO,KAAK,KAAK,MAAM,iBAAiB,CAAA;AACxC,OAAO,KAAK,OAAO,MAAM,mBAAmB,CAAA;AAC5C,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAA;AAC1C,OAAO,KAAK,KAAK,MAAM,iBAAiB,CAAA;AACxC,OAAO,KAAK,GAAG,MAAM,iBAAiB,CAAA;AACtC,OAAO,KAAK,MAAM,MAAM,oBAAoB,CAAA"}
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};