@schlessera/brain-ui-server 0.19.0 → 0.21.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 (78) hide show
  1. package/README.md +5 -1
  2. package/data/model-prices.json +63 -0
  3. package/dist/activity/digest.d.ts.map +1 -1
  4. package/dist/activity/digest.js +30 -2
  5. package/dist/activity/digest.js.map +1 -1
  6. package/dist/activity/notify.d.ts.map +1 -1
  7. package/dist/activity/notify.js +26 -4
  8. package/dist/activity/notify.js.map +1 -1
  9. package/dist/activity/query.d.ts.map +1 -1
  10. package/dist/activity/query.js +28 -1
  11. package/dist/activity/query.js.map +1 -1
  12. package/dist/activity/recorder.d.ts +5 -0
  13. package/dist/activity/recorder.d.ts.map +1 -1
  14. package/dist/activity/recorder.js +37 -4
  15. package/dist/activity/recorder.js.map +1 -1
  16. package/dist/activity/runtime.d.ts +7 -1
  17. package/dist/activity/runtime.d.ts.map +1 -1
  18. package/dist/activity/runtime.js +28 -10
  19. package/dist/activity/runtime.js.map +1 -1
  20. package/dist/activity/store.d.ts +51 -2
  21. package/dist/activity/store.d.ts.map +1 -1
  22. package/dist/activity/store.js +236 -19
  23. package/dist/activity/store.js.map +1 -1
  24. package/dist/activity/stream.d.ts +6 -2
  25. package/dist/activity/stream.d.ts.map +1 -1
  26. package/dist/activity/stream.js +42 -1
  27. package/dist/activity/stream.js.map +1 -1
  28. package/dist/agent/backend.d.ts +13 -4
  29. package/dist/agent/backend.d.ts.map +1 -1
  30. package/dist/agent/backend.js +68 -5
  31. package/dist/agent/backend.js.map +1 -1
  32. package/dist/app.d.ts.map +1 -1
  33. package/dist/app.js +18 -3
  34. package/dist/app.js.map +1 -1
  35. package/dist/config/env.d.ts +36 -0
  36. package/dist/config/env.d.ts.map +1 -1
  37. package/dist/config/env.js +66 -2
  38. package/dist/config/env.js.map +1 -1
  39. package/dist/db/settings.d.ts +15 -0
  40. package/dist/db/settings.d.ts.map +1 -1
  41. package/dist/db/settings.js +43 -0
  42. package/dist/db/settings.js.map +1 -1
  43. package/dist/index.d.ts +1 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js.map +1 -1
  46. package/dist/pricing/model-pricing.d.ts +61 -0
  47. package/dist/pricing/model-pricing.d.ts.map +1 -0
  48. package/dist/pricing/model-pricing.js +379 -0
  49. package/dist/pricing/model-pricing.js.map +1 -0
  50. package/dist/routes/activity.d.ts.map +1 -1
  51. package/dist/routes/activity.js +40 -32
  52. package/dist/routes/activity.js.map +1 -1
  53. package/dist/routes/models.d.ts +6 -0
  54. package/dist/routes/models.d.ts.map +1 -1
  55. package/dist/routes/models.js +38 -3
  56. package/dist/routes/models.js.map +1 -1
  57. package/dist/ws/run-session.d.ts.map +1 -1
  58. package/dist/ws/run-session.js +36 -2
  59. package/dist/ws/run-session.js.map +1 -1
  60. package/migrations/009_activity_followups.sql +22 -0
  61. package/migrations/010_effective_cost.sql +21 -0
  62. package/package.json +4 -3
  63. package/src/activity/digest.ts +30 -2
  64. package/src/activity/notify.ts +30 -8
  65. package/src/activity/query.ts +28 -1
  66. package/src/activity/recorder.ts +57 -5
  67. package/src/activity/runtime.ts +41 -13
  68. package/src/activity/store.ts +316 -21
  69. package/src/activity/stream.ts +39 -9
  70. package/src/agent/backend.ts +91 -10
  71. package/src/app.ts +19 -3
  72. package/src/config/env.ts +90 -2
  73. package/src/db/settings.ts +56 -0
  74. package/src/index.ts +1 -0
  75. package/src/pricing/model-pricing.ts +497 -0
  76. package/src/routes/activity.ts +41 -33
  77. package/src/routes/models.ts +57 -6
  78. package/src/ws/run-session.ts +45 -3
@@ -4,6 +4,7 @@ import { createActivityNotifier } from "./notify.js";
4
4
  import { createPushSender } from "./push-sender.js";
5
5
  import { digestRetentionFloor } from "./digest.js";
6
6
  import { runActivityQuery } from "./query.js";
7
+ import { getDetailRetentionDays } from "../db/settings.js";
7
8
  // Activity lifecycle cadence. The stale threshold must comfortably exceed
8
9
  // the cron wrapper's heartbeat interval (~30s) so a live writer is never
9
10
  // swept; the tick doubles as the always-on low-frequency sweep the
@@ -16,8 +17,20 @@ const ACTIVITY_HARD_CEILING_MS = 90 * 24 * 60 * 60 * 1000;
16
17
  const INTENT_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
17
18
  export function createActivityRuntime(db, deps) {
18
19
  const { log } = deps;
19
- const store = createActivityStore(db);
20
+ const store = createActivityStore(db, deps.pricing ? { pricing: deps.pricing } : {});
20
21
  const stream = createActivityStream(store, log);
22
+ // The notifier is created BEFORE the boot sweep: its change cursor starts
23
+ // at the current head, so the terminal changes the sweep writes are above
24
+ // it and the first tick turns restart-interrupted runs into failure
25
+ // intents. Created after, the sweep's changes would sit below the cursor
26
+ // and a turn killed by a restart would never be notified.
27
+ const notifier = createActivityNotifier({
28
+ db,
29
+ store,
30
+ isWatched: (scope) => stream.isWatched(scope),
31
+ log,
32
+ });
33
+ const pushSender = createPushSender(db, { log });
21
34
  // Boot sweep: close this server's orphans from a previous life (interrupted).
22
35
  try {
23
36
  const orphans = store.sweepOwnOrphans();
@@ -36,13 +49,6 @@ export function createActivityRuntime(db, deps) {
36
49
  attributes: { error: err instanceof Error ? err.message : String(err) },
37
50
  });
38
51
  }
39
- const notifier = createActivityNotifier({
40
- db,
41
- store,
42
- isWatched: (scope) => stream.isWatched(scope),
43
- log,
44
- });
45
- const pushSender = createPushSender(db, { log });
46
52
  let delivering = false;
47
53
  let lastPrune = 0;
48
54
  const tick = setInterval(() => {
@@ -74,10 +80,22 @@ export function createActivityRuntime(db, deps) {
74
80
  }
75
81
  if (Date.now() - lastPrune > ACTIVITY_PRUNE_INTERVAL_MS) {
76
82
  lastPrune = Date.now();
83
+ // Self-heal the pricing table on server traffic: without this, a
84
+ // long-lived server rolls up from whatever the boot-time refresh
85
+ // fetched. Single-flight + TTL inside ensureFresh make the hourly
86
+ // call free when fresh. The rollup path itself stays synchronous —
87
+ // never refresh from inside the store.
88
+ const pricing = deps.pricing;
89
+ if (typeof pricing?.ensureFresh === "function") {
90
+ void pricing.ensureFresh().catch(() => { });
91
+ }
77
92
  store.prune({
78
- // Full detail survives until the digest has covered it; the hard
79
- // ceiling bounds growth even if the digest job silently dies.
93
+ // Full detail survives until the digest has covered it AND the
94
+ // retention window has passed; the hard ceiling bounds growth even
95
+ // if the digest job silently dies. The setting is read every pass
96
+ // so a change applies without a restart.
80
97
  digestFloorAt: digestRetentionFloor(db),
98
+ detailRetentionMs: getDetailRetentionDays(db, log) * 24 * 60 * 60 * 1000,
81
99
  hardCeilingMs: ACTIVITY_HARD_CEILING_MS,
82
100
  });
83
101
  notifier.pruneAcknowledged(INTENT_RETENTION_MS);
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/activity/runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/activity/runtime.ts"],"names":[],"mappings":"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"}
@@ -31,6 +31,8 @@
31
31
  * loud one.
32
32
  */
33
33
  import type { Database } from "bun:sqlite";
34
+ import { type BillingMode } from "@schlessera/brain-ui-sdk/protocol";
35
+ import { type PricingRates } from "../pricing/model-pricing.js";
34
36
  export declare const SPAN_OUTCOMES: readonly ["success", "error", "timeout", "cancelled", "denied", "interrupted"];
35
37
  export type SpanOutcome = (typeof SPAN_OUTCOMES)[number];
36
38
  export type SpanKind = "turn" | "tool" | "subagent" | "cron";
@@ -88,11 +90,32 @@ export interface RunRollupRow {
88
90
  cacheReadTokens: number | null;
89
91
  cacheCreationTokens: number | null;
90
92
  costUsd: number | null;
93
+ /**
94
+ * What the run actually cost, frozen at first computation (migration 010):
95
+ * 0 for subscription-billed work regardless of tokens, priced usage for
96
+ * api-billed. NULL = unknown, 0 = genuinely free.
97
+ */
98
+ effectiveCostUsd: number | null;
99
+ /** Billing classification behind `effectiveCostUsd`; NULL = unknown. */
100
+ billingMode: BillingMode | null;
101
+ /** True when the effective cost was computed from estimated rates. */
102
+ pricingEstimate: boolean | null;
91
103
  failureReason: string | null;
92
104
  detailPruned: boolean;
93
105
  }
94
106
  /** The one snake→camel mapper for rollup rows — every reader shares it. */
95
107
  export declare function rowToRunRollup(r: any): RunRollupRow;
108
+ /**
109
+ * Sum-of-KNOWNS over effective costs: unknown (NULL) rows contribute nothing
110
+ * to the sum and are counted instead, so no aggregate can pass an unknown off
111
+ * as $0 (AE3). One definition for every reader that folds rollup rows.
112
+ */
113
+ export declare function sumEffectiveCost(rows: Array<{
114
+ effectiveCostUsd: number | null;
115
+ }>): {
116
+ effectiveCostUsd: number;
117
+ unpricedRuns: number;
118
+ };
96
119
  /** One committed write, as the delta stream sees it. */
97
120
  export type ActivityChange = {
98
121
  changeId: number;
@@ -140,6 +163,10 @@ export interface PruneOptions {
140
163
  /** Spans of runs that ended before this are prunable (digest floor).
141
164
  * Absolute epoch ms, not an offset. */
142
165
  digestFloorAt: number;
166
+ /** Minimum age (ms since ended) a run must reach before the digest floor
167
+ * may prune its detail. 0 reproduces floor-only pruning. The hard
168
+ * ceiling ignores it. */
169
+ detailRetentionMs: number;
143
170
  /** Runs older than this are pruned REGARDLESS of the digest floor. */
144
171
  hardCeilingMs: number;
145
172
  now?: number;
@@ -148,7 +175,6 @@ export interface PruneOptions {
148
175
  }
149
176
  /** A single event payload is capped so no delta can approach the WS frame cap. */
150
177
  export declare const MAX_EVENT_PAYLOAD_BYTES = 16384;
151
- export declare const TRUNCATION_MARKER = "\u2026[truncated]";
152
178
  export interface ActivityStore {
153
179
  readonly writer: string;
154
180
  startSpan(input: StartSpanInput): SpanRow;
@@ -165,7 +191,13 @@ export interface ActivityStore {
165
191
  usage?: SpanUsage;
166
192
  waitUntil?: number;
167
193
  }): boolean;
168
- appendEvent(spanId: string, eventType: string, payload: unknown, ts?: number): SpanEventRow | null;
194
+ /**
195
+ * Append one event; null (nothing written) for an unknown span. `cap`
196
+ * lowers the payload size cap for this call — the store's 16 KB invariant
197
+ * is the ceiling regardless. A capped payload is clipped and flagged
198
+ * `truncated`.
199
+ */
200
+ appendEvent(spanId: string, eventType: string, payload: unknown, ts?: number, cap?: number): SpanEventRow | null;
169
201
  /** External writers touch their root span so staleness is heartbeat-age based. */
170
202
  heartbeat(spanId: string, at?: number): void;
171
203
  /** Close every open span of a run as `outcome` (children first), reason on all. */
@@ -199,9 +231,26 @@ export interface ActivityStore {
199
231
  spansDeleted: number;
200
232
  };
201
233
  }
234
+ /**
235
+ * The pricing dependency rollups price through. `resolve` MUST be synchronous
236
+ * — it is called inside the rollup write transaction, which never awaits.
237
+ */
238
+ export interface RollupPricing {
239
+ resolve(modelId: string): PricingRates | null;
240
+ }
202
241
  export interface CreateActivityStoreOptions {
203
242
  /** Process identity stamped on every span this store writes. */
204
243
  writer?: string;
244
+ /**
245
+ * Pricing seam for rollup-time effective cost. Defaults to an env-derived
246
+ * `createModelPricing` instance (the `$BRAIN_PATH` cache and the bundled
247
+ * snapshot are both synchronous reads), constructed on first rollup — so
248
+ * the cron wrapper's bare `createActivityStore(db)` prices identically to
249
+ * the server with no wiring of its own. Tests inject a fake to stay
250
+ * network- and disk-free (the default is also disabled under NODE_ENV=test,
251
+ * mirroring the discovery flag's default).
252
+ */
253
+ pricing?: RollupPricing;
205
254
  }
206
255
  export declare function createActivityStore(db: Database, options?: CreateActivityStoreOptions): ActivityStore;
207
256
  //# sourceMappingURL=store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/activity/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/activity/store.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,6 @@
1
+ import { isBillingMode } from "@schlessera/brain-ui-sdk/protocol";
2
+ import { resolveAmbientBillingMode, resolveStandalonePricingConfig, } from "../config/env.js";
3
+ import { createModelPricing } from "../pricing/model-pricing.js";
1
4
  export const SPAN_OUTCOMES = [
2
5
  "success",
3
6
  "error",
@@ -24,15 +27,43 @@ export function rowToRunRollup(r) {
24
27
  cacheReadTokens: r.cache_read_tokens,
25
28
  cacheCreationTokens: r.cache_creation_tokens,
26
29
  costUsd: r.cost_usd,
30
+ effectiveCostUsd: r.effective_cost_usd ?? null,
31
+ // Migration 010 CHECKs this column, but a row written around them (older
32
+ // binary, manual edit) must degrade to unknown, never to a wrong mode.
33
+ billingMode: isBillingMode(r.billing_mode) ? r.billing_mode : null,
34
+ pricingEstimate: r.pricing_estimate == null ? null : r.pricing_estimate === 1,
27
35
  failureReason: r.failure_reason,
28
36
  detailPruned: r.detail_pruned === 1,
29
37
  };
30
38
  }
39
+ /**
40
+ * Sum-of-KNOWNS over effective costs: unknown (NULL) rows contribute nothing
41
+ * to the sum and are counted instead, so no aggregate can pass an unknown off
42
+ * as $0 (AE3). One definition for every reader that folds rollup rows.
43
+ */
44
+ export function sumEffectiveCost(rows) {
45
+ let effectiveCostUsd = 0;
46
+ let unpricedRuns = 0;
47
+ for (const row of rows) {
48
+ if (row.effectiveCostUsd === null)
49
+ unpricedRuns += 1;
50
+ else
51
+ effectiveCostUsd += row.effectiveCostUsd;
52
+ }
53
+ return { effectiveCostUsd, unpricedRuns };
54
+ }
31
55
  /** A single event payload is capped so no delta can approach the WS frame cap. */
32
56
  export const MAX_EVENT_PAYLOAD_BYTES = 16_384;
33
- export const TRUNCATION_MARKER = "…[truncated]";
34
57
  export function createActivityStore(db, options = {}) {
35
58
  const writer = options.writer ?? `pid:${process.pid}:${Date.now()}`;
59
+ // Lazy so a store that never rolls up (span-only paths, most tests)
60
+ // touches neither the env nor the disk. The env reads live in
61
+ // config/env.ts — the one chokepoint the env-access gate allows. Every
62
+ // wrapper whose transaction can reach rollupRunInTx calls this BEFORE
63
+ // entering `inWrite`, so the SQLite write lock never covers the two
64
+ // synchronous JSON reads construction costs.
65
+ let pricing = options.pricing;
66
+ const getPricing = () => (pricing ??= createModelPricing(resolveStandalonePricingConfig()));
36
67
  // bun:sqlite transactions: `.immediate` takes the write lock up front, so a
37
68
  // concurrent writer waits (busy_timeout) instead of failing mid-upgrade.
38
69
  const inWrite = (fn) => db.transaction(fn).immediate();
@@ -143,18 +174,88 @@ export function createActivityStore(db, options = {}) {
143
174
  // tree would double-count every fan-out. Verified empirically (plan U3
144
175
  // smoke test).
145
176
  const failure = spans.find((s) => (s.outcome === "error" || s.outcome === "timeout") && s.outcomeReason != null)?.outcomeReason ?? (root.outcome === "interrupted" ? "interrupted" : null);
177
+ // Billing rides the root span when the recorder knew it at run start
178
+ // (session runs, U3). A root without the attr classifies from THIS
179
+ // process's env ONLY for non-session origins: for cron rollups the
180
+ // executing process is the wrapper itself — exactly the credential set
181
+ // the job's agent authenticated under — so classification and reality
182
+ // move together. A SESSION root without the attr (a custom backend the
183
+ // registry could not classify, or a failed profile resolution) stays
184
+ // unknown instead: this process's ambient credentials say nothing about
185
+ // whichever backend ran the turn, and a wrong subscription-$0 would
186
+ // freeze forever where unknown stays honestly unpriced.
187
+ const attrBilling = root.attrs["brain.billing_mode"];
188
+ const billingMode = isBillingMode(attrBilling)
189
+ ? attrBilling
190
+ : root.origin === "session"
191
+ ? null
192
+ : resolveAmbientBillingMode();
193
+ // List-price math runs regardless of billing mode: it gap-fills a
194
+ // missing backend cost_usd (pi without snapshots, cron) AND provides the
195
+ // api-billed effective number. resolve() is synchronous by contract —
196
+ // nothing here may await inside the write transaction.
197
+ const usage = usageForPricing(root, spans);
198
+ const priced = usage.kind === "usage" ? priceUsage(usage.byModel, getPricing()) : null;
199
+ // Effective-cost semantics (NULL = unknown, 0 = genuinely free):
200
+ // subscription → 0 regardless of tokens (AE1); api → the priced sum —
201
+ // 0 when the run verifiably consumed nothing, NULL when usage was never
202
+ // recorded (denied before inference) or any contributing model/rate is
203
+ // missing. The estimate flag qualifies the effective number, so it is
204
+ // NULL exactly when that is, and 0 for a subscription $0 (exact, not
205
+ // estimated).
206
+ // An unclassified run (billingMode null — session root without the
207
+ // attr) prices as unknown: no subscription-zero, no api pricing.
208
+ const effectiveCostUsd = billingMode === null ? null : billingMode === "subscription" ? 0 : (priced?.costUsd ?? null);
209
+ const pricingEstimate = billingMode === null
210
+ ? null
211
+ : billingMode === "subscription"
212
+ ? 0
213
+ : priced
214
+ ? (priced.estimate ? 1 : 0)
215
+ : null;
216
+ const costUsd = root.usage.costUsd ?? priced?.costUsd ?? null;
146
217
  db.query(`INSERT INTO activity_run_rollups
147
218
  (run_id, origin, name, session_id, job_name, started_at, ended_at, outcome,
148
219
  duration_ms, span_count, input_tokens, output_tokens, cache_read_tokens,
149
- cache_creation_tokens, cost_usd, failure_reason, detail_pruned)
150
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)
220
+ cache_creation_tokens, cost_usd, effective_cost_usd, billing_mode,
221
+ pricing_estimate, failure_reason, detail_pruned)
222
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)
151
223
  ON CONFLICT(run_id) DO UPDATE SET
152
224
  ended_at = excluded.ended_at, outcome = excluded.outcome,
153
225
  duration_ms = excluded.duration_ms, span_count = excluded.span_count,
154
226
  input_tokens = excluded.input_tokens, output_tokens = excluded.output_tokens,
155
227
  cache_read_tokens = excluded.cache_read_tokens,
156
228
  cache_creation_tokens = excluded.cache_creation_tokens,
157
- cost_usd = excluded.cost_usd, failure_reason = excluded.failure_reason`).run(runId, root.origin, root.name, root.sessionId, root.jobName, root.startedAt, root.endedAt, root.outcome, root.endedAt !== null ? root.endedAt - root.startedAt : null, spans.length, root.usage.inputTokens ?? null, root.usage.outputTokens ?? null, root.usage.cacheReadTokens ?? null, root.usage.cacheCreationTokens ?? null, root.usage.costUsd ?? null, failure);
229
+ cost_usd = COALESCE(activity_run_rollups.cost_usd, excluded.cost_usd),
230
+ effective_cost_usd = CASE
231
+ WHEN activity_run_rollups.effective_cost_usd IS NOT NULL
232
+ THEN activity_run_rollups.effective_cost_usd
233
+ WHEN activity_run_rollups.billing_mode IS NULL
234
+ OR activity_run_rollups.billing_mode = excluded.billing_mode
235
+ THEN excluded.effective_cost_usd
236
+ ELSE NULL
237
+ END,
238
+ billing_mode = COALESCE(activity_run_rollups.billing_mode, excluded.billing_mode),
239
+ pricing_estimate = CASE
240
+ WHEN activity_run_rollups.pricing_estimate IS NOT NULL
241
+ THEN activity_run_rollups.pricing_estimate
242
+ WHEN activity_run_rollups.billing_mode IS NULL
243
+ OR activity_run_rollups.billing_mode = excluded.billing_mode
244
+ THEN excluded.pricing_estimate
245
+ ELSE NULL
246
+ END,
247
+ failure_reason = excluded.failure_reason`
248
+ // Every cost column is FROZEN at first non-NULL write (AE5): a re-rollup
249
+ // after a pricing refresh must not silently reprice history — only a
250
+ // still-NULL slot may be filled by a later computation. cost_usd gets
251
+ // the plain first-write-wins COALESCE (the terminal endSpan already
252
+ // merged the backend's authoritative number before the first rollup).
253
+ // The effective/estimate pair additionally requires the LATER fill to
254
+ // agree with the frozen classification: billing_mode is first-write-wins,
255
+ // and a slot left NULL under one classification must never be filled by
256
+ // a number computed under a different one (a subscription $0 landing on
257
+ // an api-classified row would fabricate a cross-classified price).
258
+ ).run(runId, root.origin, root.name, root.sessionId, root.jobName, root.startedAt, root.endedAt, root.outcome, root.endedAt !== null ? root.endedAt - root.startedAt : null, spans.length, root.usage.inputTokens ?? null, root.usage.outputTokens ?? null, root.usage.cacheReadTokens ?? null, root.usage.cacheCreationTokens ?? null, costUsd, effectiveCostUsd, billingMode, pricingEstimate, failure);
158
259
  }
159
260
  return {
160
261
  writer,
@@ -195,7 +296,7 @@ export function createActivityStore(db, options = {}) {
195
296
  return true;
196
297
  });
197
298
  },
198
- appendEvent(spanId, eventType, payload, ts) {
299
+ appendEvent(spanId, eventType, payload, ts, cap) {
199
300
  return inWrite(() => {
200
301
  const span = db
201
302
  .query("SELECT run_id FROM activity_spans WHERE span_id = ?")
@@ -205,13 +306,19 @@ export function createActivityStore(db, options = {}) {
205
306
  const next = db
206
307
  .query("SELECT COALESCE(MAX(event_index), -1) + 1 AS idx FROM activity_events WHERE span_id = ?")
207
308
  .get(spanId);
208
- const stored = capPayload(payload);
209
- db.query("INSERT INTO activity_events (span_id, event_index, ts, event_type, payload) VALUES (?, ?, ?, ?, ?)").run(spanId, next.idx, ts ?? Date.now(), eventType, JSON.stringify(stored));
309
+ const stored = capPayload(payload, cap);
310
+ const at = ts ?? Date.now();
311
+ db.query("INSERT INTO activity_events (span_id, event_index, ts, event_type, payload) VALUES (?, ?, ?, ?, ?)").run(spanId, next.idx, at, eventType, JSON.stringify(stored));
210
312
  logChange(span.run_id, nextSeq(span.run_id), spanId, next.idx);
211
- const r = db
212
- .query("SELECT * FROM activity_events WHERE span_id = ? AND event_index = ?")
213
- .get(spanId, next.idx);
214
- return rowToEvent(r);
313
+ // Built from the values just written — no read-back needed.
314
+ return {
315
+ spanId,
316
+ eventIndex: next.idx,
317
+ ts: at,
318
+ eventType,
319
+ payload: stored.v,
320
+ truncated: stored.truncated === true,
321
+ };
215
322
  });
216
323
  },
217
324
  heartbeat(spanId, at) {
@@ -222,6 +329,7 @@ export function createActivityStore(db, options = {}) {
222
329
  });
223
330
  },
224
331
  cascadeClose(runId, outcome, reason) {
332
+ getPricing(); // construct outside the write lock; rollup reuses it
225
333
  return inWrite(() => closeRunInTx(runId, outcome, reason));
226
334
  },
227
335
  sweepOwnOrphans() {
@@ -242,6 +350,7 @@ export function createActivityStore(db, options = {}) {
242
350
  .all();
243
351
  if (open.length === 0)
244
352
  return 0;
353
+ getPricing(); // construct outside the write lock; rollup reuses it
245
354
  return inWrite(() => {
246
355
  let closed = 0;
247
356
  for (const { run_id } of open) {
@@ -262,6 +371,7 @@ export function createActivityStore(db, options = {}) {
262
371
  .all(writer, cutoff);
263
372
  if (staleRoots.length === 0)
264
373
  return 0;
374
+ getPricing(); // construct outside the write lock; rollup reuses it
265
375
  return inWrite(() => {
266
376
  let closed = 0;
267
377
  for (const { run_id } of staleRoots) {
@@ -367,6 +477,7 @@ export function createActivityStore(db, options = {}) {
367
477
  .all().map(rowToSpan);
368
478
  },
369
479
  rollupRun(runId) {
480
+ getPricing(); // construct outside the write lock; rollup reuses it
370
481
  inWrite(() => rollupRunInTx(runId));
371
482
  },
372
483
  prune(options) {
@@ -374,8 +485,15 @@ export function createActivityStore(db, options = {}) {
374
485
  const batch = options.batch ?? 50;
375
486
  // The digest floor gates pruning, but a dead digest job must not freeze
376
487
  // it forever: the hard ceiling prunes regardless (marking the rollup so
377
- // the coverage gap is visible).
488
+ // the coverage gap is visible). The floor alone is NOT enough: it meant
489
+ // "safe to prune once summarized", but drill-in debugging is a second
490
+ // reader of detail with a different clock — nightly cron runs end
491
+ // before the morning digest, day sessions after, so floor-only pruning
492
+ // took cron span trees within the hour while sessions kept theirs (it
493
+ // looked cron-specific; it was clock skew). The retention cutoff ANDs
494
+ // with the floor so covered runs still keep detail for a minimum window.
378
495
  const floor = Math.max(options.digestFloorAt, 0);
496
+ const retentionCutoff = now - options.detailRetentionMs;
379
497
  const ceiling = now - options.hardCeilingMs;
380
498
  return inWrite(() => {
381
499
  // Candidates come from the rollups (they exist for every finished
@@ -384,13 +502,13 @@ export function createActivityStore(db, options = {}) {
384
502
  const candidates = db
385
503
  .query(`SELECT run_id, ended_at AS ended FROM activity_run_rollups r
386
504
  WHERE detail_pruned = 0 AND ended_at IS NOT NULL
387
- AND (ended_at < ? OR ended_at < ?)
505
+ AND ((ended_at < ? AND ended_at < ?) OR ended_at < ?)
388
506
  AND NOT EXISTS (
389
507
  SELECT 1 FROM activity_spans s
390
508
  WHERE s.run_id = r.run_id AND s.outcome IS NULL
391
509
  )
392
510
  LIMIT ?`)
393
- .all(floor, ceiling, batch);
511
+ .all(floor, retentionCutoff, ceiling, batch);
394
512
  let spansDeleted = 0;
395
513
  for (const { run_id, ended } of candidates) {
396
514
  db.query("UPDATE activity_run_rollups SET detail_pruned = 1 WHERE run_id = ?").run(run_id);
@@ -417,6 +535,103 @@ export function createActivityStore(db, options = {}) {
417
535
  },
418
536
  };
419
537
  }
538
+ function tokenCount(value) {
539
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
540
+ }
541
+ /**
542
+ * The usage a run is priced from. Session runs carry the SDK's per-model
543
+ * breakdown as a root attr (result-level accounting, subagents included —
544
+ * the same root-only scope the token rollup uses). Cron runs have no root
545
+ * aggregate: the wrapper's children carry the usage, summed per model here
546
+ * — the sanctioned origin-scoped exception to root-only aggregation, safe
547
+ * because sink children are not double-counted in their root.
548
+ */
549
+ function usageForPricing(root, spans) {
550
+ if (root.origin === "cron") {
551
+ // Null-prototype: model ids are foreign strings — an id like "__proto__"
552
+ // must be an ordinary key, never a prototype write.
553
+ const byModel = Object.create(null);
554
+ let sawUsage = false;
555
+ for (const span of spans) {
556
+ if (span.spanId === root.spanId)
557
+ continue;
558
+ const u = span.usage;
559
+ if (u.inputTokens == null &&
560
+ u.outputTokens == null &&
561
+ u.cacheReadTokens == null &&
562
+ u.cacheCreationTokens == null) {
563
+ continue; // no inference on this span (tool spans etc.) — not "unknown"
564
+ }
565
+ sawUsage = true;
566
+ // Tokens without a model cannot be priced at any rate — guessing one
567
+ // would silently misprice, so the whole run goes unknown.
568
+ if (!u.model)
569
+ return { kind: "unpriceable" };
570
+ const agg = (byModel[u.model] ??= {});
571
+ agg.inputTokens = (agg.inputTokens ?? 0) + (u.inputTokens ?? 0);
572
+ agg.outputTokens = (agg.outputTokens ?? 0) + (u.outputTokens ?? 0);
573
+ agg.cacheReadTokens = (agg.cacheReadTokens ?? 0) + (u.cacheReadTokens ?? 0);
574
+ agg.cacheCreationTokens = (agg.cacheCreationTokens ?? 0) + (u.cacheCreationTokens ?? 0);
575
+ }
576
+ return sawUsage ? { kind: "usage", byModel } : { kind: "none" };
577
+ }
578
+ const perModel = root.attrs["gen_ai.usage.per_model"];
579
+ if (typeof perModel !== "object" || perModel === null || Array.isArray(perModel)) {
580
+ return { kind: "none" };
581
+ }
582
+ const byModel = Object.create(null);
583
+ for (const [model, value] of Object.entries(perModel)) {
584
+ if (typeof value !== "object" || value === null)
585
+ continue;
586
+ const v = value;
587
+ byModel[model] = {
588
+ inputTokens: tokenCount(v.inputTokens) ?? 0,
589
+ outputTokens: tokenCount(v.outputTokens) ?? 0,
590
+ cacheReadTokens: tokenCount(v.cacheReadTokens) ?? 0,
591
+ cacheCreationTokens: tokenCount(v.cacheCreationTokens) ?? 0,
592
+ };
593
+ }
594
+ return Object.keys(byModel).length > 0 ? { kind: "usage", byModel } : { kind: "none" };
595
+ }
596
+ /**
597
+ * Price one run's per-model usage at list rates. Null = unknown: an
598
+ * unresolvable model with consumed tokens, or a consumed token class with no
599
+ * rate, poisons the WHOLE run — cache reads dominate Claude usage, so
600
+ * partial pricing would systematically understate (the binding
601
+ * missing-cache-rate decision). A model with zero consumption contributes
602
+ * nothing and needs no rate. Variant/snapshot fallbacks are the pricing
603
+ * service's job (`resolve()`); the store only propagates the estimate flag.
604
+ */
605
+ function priceUsage(byModel, pricing) {
606
+ let costUsd = 0;
607
+ let estimate = false;
608
+ for (const [model, tokens] of Object.entries(byModel)) {
609
+ const classes = [
610
+ [tokens.inputTokens ?? 0, (r) => r.input],
611
+ [tokens.outputTokens ?? 0, (r) => r.output],
612
+ [tokens.cacheReadTokens ?? 0, (r) => r.cacheRead],
613
+ [tokens.cacheCreationTokens ?? 0, (r) => r.cacheWrite],
614
+ ];
615
+ if (!classes.some(([count]) => count > 0))
616
+ continue;
617
+ const rates = pricing.resolve(model);
618
+ if (!rates)
619
+ return null;
620
+ for (const [count, rateOf] of classes) {
621
+ if (count <= 0)
622
+ continue;
623
+ const perToken = rateOf(rates);
624
+ if (perToken === null)
625
+ return null;
626
+ costUsd += count * perToken;
627
+ }
628
+ if (rates.estimate)
629
+ estimate = true;
630
+ }
631
+ // A non-finite sum (overflowed or poisoned rates) must surface as unknown —
632
+ // once frozen into the rollup it would render as an exact number forever.
633
+ return Number.isFinite(costUsd) ? { costUsd, estimate } : null;
634
+ }
420
635
  function safeParse(text) {
421
636
  if (!text)
422
637
  return undefined;
@@ -427,14 +642,16 @@ function safeParse(text) {
427
642
  return undefined;
428
643
  }
429
644
  }
430
- /** Cap a payload's serialized size, storing an explicit truncation marker. */
431
- function capPayload(payload) {
645
+ /** Cap a payload's serialized size; a clipped payload carries the `truncated`
646
+ * flag (which rides the wire), so no in-text marker is stored. */
647
+ function capPayload(payload, cap) {
648
+ const limit = Math.min(cap ?? MAX_EVENT_PAYLOAD_BYTES, MAX_EVENT_PAYLOAD_BYTES);
432
649
  const json = JSON.stringify(payload ?? null);
433
- if (json.length <= MAX_EVENT_PAYLOAD_BYTES)
650
+ if (json.length <= limit)
434
651
  return { v: payload ?? null };
435
652
  if (typeof payload === "string") {
436
- return { v: payload.slice(0, MAX_EVENT_PAYLOAD_BYTES) + TRUNCATION_MARKER, truncated: true };
653
+ return { v: payload.slice(0, limit), truncated: true };
437
654
  }
438
- return { v: json.slice(0, MAX_EVENT_PAYLOAD_BYTES) + TRUNCATION_MARKER, truncated: true };
655
+ return { v: json.slice(0, limit), truncated: true };
439
656
  }
440
657
  //# sourceMappingURL=store.js.map