@dudousxd/nestjs-agent-store-drizzle 0.33.0 → 0.35.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.
package/dist/index.js CHANGED
@@ -178,7 +178,7 @@ var agentQueuedMessage = sqliteTable("agent_queued_message", {
178
178
  ]);
179
179
  var agentTokenUsage = sqliteTable("agent_token_usage", {
180
180
  id: text("id").primaryKey(),
181
- threadId: text("thread_id").notNull().references(() => agentThread.id, {
181
+ threadId: text("thread_id").references(() => agentThread.id, {
182
182
  onDelete: "cascade"
183
183
  }),
184
184
  actorRef: text("actor_ref").notNull(),
@@ -195,13 +195,28 @@ var agentTokenUsage = sqliteTable("agent_token_usage", {
195
195
  costUsd: real("cost_usd"),
196
196
  /** Where `cost_usd` came from: `'provider'` (reported) or `'estimate'` (tokens × the price row); null on rows from before it existed (reported). */
197
197
  costSource: text("cost_source").$type(),
198
+ /** The run the row belongs to. */
199
+ runId: text("run_id"),
200
+ /** What the spend was for: `agent:<name>` for an agent turn, the app's own otherwise; null → `agent`. */
201
+ feature: text("feature"),
202
+ provider: text("provider"),
203
+ tenantRef: text("tenant_ref"),
204
+ /** Units a row bills by when they are not tokens. */
205
+ audioSeconds: real("audio_seconds"),
206
+ characters: integer("characters"),
207
+ requests: integer("requests"),
208
+ /** The caller's own JSON, never read by the library. */
209
+ metadata: text("metadata", {
210
+ mode: "json"
211
+ }).$type(),
198
212
  createdAt: integer("created_at", {
199
213
  mode: "timestamp_ms"
200
214
  }).notNull()
201
215
  }, (table) => [
202
216
  index("agent_token_usage_actor_created_idx").on(table.actorRef, table.createdAt),
203
217
  index("agent_token_usage_created_idx").on(table.createdAt),
204
- index("agent_token_usage_thread_idx").on(table.threadId)
218
+ index("agent_token_usage_thread_idx").on(table.threadId),
219
+ index("agent_token_usage_feature_created_idx").on(table.feature, table.createdAt)
205
220
  ]);
206
221
  var agentGenuiCall = sqliteTable("agent_genui_call", {
207
222
  id: text("id").primaryKey(),
@@ -283,7 +298,17 @@ var agentRun = sqliteTable("agent_run", {
283
298
  * for a detached child, which outlives its parent's turn and so is paired by nothing in the
284
299
  * transcript.
285
300
  */
286
- parentRunId: text("parent_run_id")
301
+ parentRunId: text("parent_run_id"),
302
+ /** Enqueue → body start (ms); null when not read. */
303
+ queueMs: integer("queue_ms"),
304
+ /** Where the completed run's time went: model and tool totals, counts, and the per-step list. */
305
+ modelMs: integer("model_ms"),
306
+ toolsMs: integer("tools_ms"),
307
+ modelCalls: integer("model_calls"),
308
+ toolCalls: integer("tool_calls"),
309
+ timings: text("timings", {
310
+ mode: "json"
311
+ }).$type()
287
312
  }, (table) => [
288
313
  index("agent_run_started_idx").on(table.startedAt)
289
314
  ]);
@@ -629,7 +654,7 @@ var agentQueuedMessage2 = mysqlTable("agent_queued_message", {
629
654
  ]);
630
655
  var agentTokenUsage2 = mysqlTable("agent_token_usage", {
631
656
  id: key("id").primaryKey(),
632
- threadId: key("thread_id").notNull().references(() => agentThread2.id, {
657
+ threadId: key("thread_id").references(() => agentThread2.id, {
633
658
  onDelete: "cascade"
634
659
  }),
635
660
  actorRef: key("actor_ref").notNull(),
@@ -643,11 +668,26 @@ var agentTokenUsage2 = mysqlTable("agent_token_usage", {
643
668
  costUsd: double("cost_usd"),
644
669
  /** Where `cost_usd` came from: `'provider'` (reported) or `'estimate'` (tokens × the price row); null on rows from before it existed (reported). */
645
670
  costSource: key("cost_source").$type(),
671
+ /** The run the row belongs to. */
672
+ runId: key("run_id"),
673
+ /** What the spend was for: `agent:<name>` for an agent turn, the app's own otherwise; null → `agent`. */
674
+ feature: key("feature"),
675
+ provider: key("provider"),
676
+ tenantRef: key("tenant_ref"),
677
+ /** Units a row bills by when they are not tokens. */
678
+ audioSeconds: double("audio_seconds"),
679
+ characters: bigint("characters", {
680
+ mode: "number"
681
+ }),
682
+ requests: int("requests"),
683
+ /** The caller's own JSON, never read by the library. */
684
+ metadata: json("metadata").$type(),
646
685
  createdAt: ts("created_at").notNull()
647
686
  }, (table) => [
648
687
  index2("agent_token_usage_actor_created_idx").on(table.actorRef, table.createdAt),
649
688
  index2("agent_token_usage_created_idx").on(table.createdAt),
650
- index2("agent_token_usage_thread_idx").on(table.threadId)
689
+ index2("agent_token_usage_thread_idx").on(table.threadId),
690
+ index2("agent_token_usage_feature_created_idx").on(table.feature, table.createdAt)
651
691
  ]);
652
692
  var agentGenuiCall2 = mysqlTable("agent_genui_call", {
653
693
  id: key("id").primaryKey(),
@@ -719,7 +759,15 @@ var agentRun2 = mysqlTable("agent_run", {
719
759
  startedAt: ts("started_at").notNull(),
720
760
  settledAt: ts("settled_at"),
721
761
  promptHash: key("prompt_hash"),
722
- parentRunId: key("parent_run_id")
762
+ parentRunId: key("parent_run_id"),
763
+ /** Enqueue → body start (ms); null when not read. */
764
+ queueMs: int("queue_ms"),
765
+ /** Where the completed run's time went: model and tool totals, counts, and the per-step list. */
766
+ modelMs: int("model_ms"),
767
+ toolsMs: int("tools_ms"),
768
+ modelCalls: int("model_calls"),
769
+ toolCalls: int("tool_calls"),
770
+ timings: json("timings").$type()
723
771
  }, (table) => [
724
772
  index2("agent_run_started_idx").on(table.startedAt)
725
773
  ]);
@@ -1091,7 +1139,7 @@ var agentQueuedMessage3 = pgTable("agent_queued_message", {
1091
1139
  ]);
1092
1140
  var agentTokenUsage3 = pgTable("agent_token_usage", {
1093
1141
  id: text2("id").primaryKey(),
1094
- threadId: text2("thread_id").notNull().references(() => agentThread3.id, {
1142
+ threadId: text2("thread_id").references(() => agentThread3.id, {
1095
1143
  onDelete: "cascade"
1096
1144
  }),
1097
1145
  actorRef: text2("actor_ref").notNull(),
@@ -1105,11 +1153,26 @@ var agentTokenUsage3 = pgTable("agent_token_usage", {
1105
1153
  costUsd: doublePrecision("cost_usd"),
1106
1154
  /** Where `cost_usd` came from: `'provider'` (reported) or `'estimate'` (tokens × the price row); null on rows from before it existed (reported). */
1107
1155
  costSource: text2("cost_source").$type(),
1156
+ /** The run the row belongs to. */
1157
+ runId: text2("run_id"),
1158
+ /** What the spend was for: `agent:<name>` for an agent turn, the app's own otherwise; null → `agent`. */
1159
+ feature: text2("feature"),
1160
+ provider: text2("provider"),
1161
+ tenantRef: text2("tenant_ref"),
1162
+ /** Units a row bills by when they are not tokens. */
1163
+ audioSeconds: doublePrecision("audio_seconds"),
1164
+ characters: bigint2("characters", {
1165
+ mode: "number"
1166
+ }),
1167
+ requests: integer2("requests"),
1168
+ /** The caller's own JSON, never read by the library. */
1169
+ metadata: jsonb("metadata").$type(),
1108
1170
  createdAt: ts2("created_at").notNull()
1109
1171
  }, (table) => [
1110
1172
  index3("agent_token_usage_actor_created_idx").on(table.actorRef, table.createdAt),
1111
1173
  index3("agent_token_usage_created_idx").on(table.createdAt),
1112
- index3("agent_token_usage_thread_idx").on(table.threadId)
1174
+ index3("agent_token_usage_thread_idx").on(table.threadId),
1175
+ index3("agent_token_usage_feature_created_idx").on(table.feature, table.createdAt)
1113
1176
  ]);
1114
1177
  var agentGenuiCall3 = pgTable("agent_genui_call", {
1115
1178
  id: text2("id").primaryKey(),
@@ -1181,7 +1244,15 @@ var agentRun3 = pgTable("agent_run", {
1181
1244
  startedAt: ts2("started_at").notNull(),
1182
1245
  settledAt: ts2("settled_at"),
1183
1246
  promptHash: text2("prompt_hash"),
1184
- parentRunId: text2("parent_run_id")
1247
+ parentRunId: text2("parent_run_id"),
1248
+ /** Enqueue → body start (ms); null when not read. */
1249
+ queueMs: integer2("queue_ms"),
1250
+ /** Where the completed run's time went: model and tool totals, counts, and the per-step list. */
1251
+ modelMs: integer2("model_ms"),
1252
+ toolsMs: integer2("tools_ms"),
1253
+ modelCalls: integer2("model_calls"),
1254
+ toolCalls: integer2("tool_calls"),
1255
+ timings: jsonb("timings").$type()
1185
1256
  }, (table) => [
1186
1257
  index3("agent_run_started_idx").on(table.startedAt)
1187
1258
  ]);
@@ -1639,10 +1710,60 @@ function specs(dialect) {
1639
1710
  }
1640
1711
  __name(specs, "specs");
1641
1712
  async function existingColumns(db, dialect, table) {
1642
- const rows = dialect === "sqlite" ? await querySql(db, dialect, sql.raw(`PRAGMA table_info("${table}")`)) : await querySql(db, dialect, sql`select column_name as name from information_schema.columns where table_schema = ${sql.raw(dialect === "mysql" ? "database()" : "current_schema()")} and table_name = ${table}`);
1643
- return new Set(rows.map((row) => String(row.name)));
1713
+ return new Set((await columnNullability(db, dialect, table)).keys());
1644
1714
  }
1645
1715
  __name(existingColumns, "existingColumns");
1716
+ async function columnNullability(db, dialect, table) {
1717
+ if (dialect === "sqlite") {
1718
+ const rows2 = await querySql(db, dialect, sql.raw(`PRAGMA table_info("${table}")`));
1719
+ return new Map(rows2.map((row) => [
1720
+ String(row.name),
1721
+ Number(row.notnull) === 0
1722
+ ]));
1723
+ }
1724
+ const rows = await querySql(db, dialect, sql`select column_name as name, is_nullable as nullable from information_schema.columns where table_schema = ${sql.raw(dialect === "mysql" ? "database()" : "current_schema()")} and table_name = ${table}`);
1725
+ return new Map(rows.map((row) => [
1726
+ String(row.name ?? row.NAME),
1727
+ String(row.nullable ?? row.NULLABLE).toUpperCase() === "YES"
1728
+ ]));
1729
+ }
1730
+ __name(columnNullability, "columnNullability");
1731
+ async function relaxNotNull(db, dialect, spec, column, all) {
1732
+ const q = /* @__PURE__ */ __name((identifier) => quote(dialect, identifier), "q");
1733
+ if (dialect === "postgres") {
1734
+ await runSql(db, dialect, sql.raw(`ALTER TABLE ${q(spec.name)} ALTER COLUMN ${q(column.name)} DROP NOT NULL`));
1735
+ return;
1736
+ }
1737
+ if (dialect === "mysql") {
1738
+ await runSql(db, dialect, sql.raw(`ALTER TABLE ${q(spec.name)} MODIFY ${columnDefinition(dialect, column)}`));
1739
+ return;
1740
+ }
1741
+ if (all.some((other) => other.foreignKeys.some((key2) => key2.table === spec.name && other !== spec))) {
1742
+ throw new Error(`agent schema: cannot make ${spec.name}.${column.name} nullable on SQLite \u2014 other tables reference ${spec.name}`);
1743
+ }
1744
+ const scratch = `${spec.name}__rebuild`;
1745
+ const present = [
1746
+ ...(await columnNullability(db, dialect, spec.name)).keys()
1747
+ ];
1748
+ const list = present.map(q).join(", ");
1749
+ await runSql(db, dialect, sql.raw("SAVEPOINT agent_schema_rebuild"));
1750
+ try {
1751
+ await runSql(db, dialect, sql.raw(`DROP TABLE IF EXISTS ${q(scratch)}`));
1752
+ await runSql(db, dialect, sql.raw(createTableSql(dialect, {
1753
+ ...spec,
1754
+ name: scratch
1755
+ })));
1756
+ await runSql(db, dialect, sql.raw(`INSERT INTO ${q(scratch)} (${list}) SELECT ${list} FROM ${q(spec.name)}`));
1757
+ await runSql(db, dialect, sql.raw(`DROP TABLE ${q(spec.name)}`));
1758
+ await runSql(db, dialect, sql.raw(`ALTER TABLE ${q(scratch)} RENAME TO ${q(spec.name)}`));
1759
+ await runSql(db, dialect, sql.raw("RELEASE SAVEPOINT agent_schema_rebuild"));
1760
+ } catch (error) {
1761
+ await runSql(db, dialect, sql.raw("ROLLBACK TO SAVEPOINT agent_schema_rebuild")).catch(() => void 0);
1762
+ await runSql(db, dialect, sql.raw("RELEASE SAVEPOINT agent_schema_rebuild")).catch(() => void 0);
1763
+ throw error;
1764
+ }
1765
+ }
1766
+ __name(relaxNotNull, "relaxNotNull");
1646
1767
  async function indexExists(db, dialect, table, index4) {
1647
1768
  let query;
1648
1769
  if (dialect === "sqlite") {
@@ -1656,7 +1777,8 @@ async function indexExists(db, dialect, table, index4) {
1656
1777
  }
1657
1778
  __name(indexExists, "indexExists");
1658
1779
  async function heal(db, dialect) {
1659
- for (const spec of specs(dialect)) {
1780
+ const all = specs(dialect);
1781
+ for (const spec of all) {
1660
1782
  await runSql(db, dialect, sql.raw(createTableSql(dialect, spec)));
1661
1783
  const present = await existingColumns(db, dialect, spec.name);
1662
1784
  for (const column of spec.columns) {
@@ -1664,6 +1786,13 @@ async function heal(db, dialect) {
1664
1786
  await runSql(db, dialect, sql.raw(`ALTER TABLE ${quote(dialect, spec.name)} ADD COLUMN ${columnDefinition(dialect, column)}`));
1665
1787
  }
1666
1788
  }
1789
+ const nullable3 = await columnNullability(db, dialect, spec.name);
1790
+ for (const column of spec.columns) {
1791
+ if (column.notNull || spec.primaryKey.includes(column.name)) continue;
1792
+ if (nullable3.get(column.name) === false) {
1793
+ await relaxNotNull(db, dialect, spec, column, all);
1794
+ }
1795
+ }
1667
1796
  for (const index4 of spec.indexes) {
1668
1797
  if (!await indexExists(db, dialect, spec.name, index4.name)) {
1669
1798
  await runSql(db, dialect, sql.raw(createIndexSql(dialect, spec.name, index4)));
@@ -2437,7 +2566,7 @@ var DrizzleAgentStore = class {
2437
2566
  deletedAt: /* @__PURE__ */ new Date()
2438
2567
  }).where(eq2(this.t.agentThread.id, threadId));
2439
2568
  }
2440
- async forkThread(threadId, fromMessageId) {
2569
+ async forkThread(threadId, fromMessageId, options = {}) {
2441
2570
  const [source] = await this.db.select().from(this.t.agentThread).where(eq2(this.t.agentThread.id, threadId));
2442
2571
  if (source === void 0) {
2443
2572
  throw new Error(`thread ${threadId} not found`);
@@ -2448,8 +2577,8 @@ var DrizzleAgentStore = class {
2448
2577
  const now = /* @__PURE__ */ new Date();
2449
2578
  const fork = {
2450
2579
  id: crypto.randomUUID(),
2451
- actorRef: source.actorRef,
2452
- tenantRef: source.tenantRef,
2580
+ actorRef: options.actorRef ?? source.actorRef,
2581
+ tenantRef: options.tenantRef !== void 0 ? options.tenantRef : source.tenantRef,
2453
2582
  title: source.title,
2454
2583
  transient: false,
2455
2584
  activeStreamId: null,
@@ -2854,7 +2983,13 @@ var DrizzleAgentStore = class {
2854
2983
  startedAt: /* @__PURE__ */ new Date(),
2855
2984
  settledAt: null,
2856
2985
  promptHash: run.promptHash ?? null,
2857
- parentRunId: run.parentRunId ?? null
2986
+ parentRunId: run.parentRunId ?? null,
2987
+ queueMs: run.queueMs !== void 0 ? Math.max(0, Math.round(run.queueMs)) : null,
2988
+ modelMs: null,
2989
+ toolsMs: null,
2990
+ modelCalls: null,
2991
+ toolCalls: null,
2992
+ timings: null
2858
2993
  };
2859
2994
  await this.db.insert(this.t.agentRun).values(runRow);
2860
2995
  }
@@ -2882,6 +3017,13 @@ var DrizzleAgentStore = class {
2882
3017
  if (end.errorMessage !== void 0) {
2883
3018
  updates.errorMessage = end.errorMessage;
2884
3019
  }
3020
+ if (end.timings !== void 0) {
3021
+ updates.modelMs = Math.round(end.timings.modelMs);
3022
+ updates.toolsMs = Math.round(end.timings.toolsMs);
3023
+ updates.modelCalls = end.timings.modelCalls;
3024
+ updates.toolCalls = end.timings.toolCalls;
3025
+ updates.timings = end.timings.steps;
3026
+ }
2885
3027
  await this.db.update(this.t.agentRun).set(updates).where(eq2(this.t.agentRun.id, end.runId));
2886
3028
  }
2887
3029
  /** Bump the run's llm-step retry counter. Atomic `retries = retries + 1`, no read-modify-write. */
@@ -3084,9 +3226,17 @@ var DrizzleAgentStore = class {
3084
3226
  async recordUsage(input) {
3085
3227
  await this.db.insert(this.t.agentTokenUsage).values({
3086
3228
  id: crypto.randomUUID(),
3087
- threadId: input.threadId,
3229
+ threadId: input.threadId ?? null,
3088
3230
  actorRef: input.actorRef,
3089
3231
  messageId: input.messageId ?? null,
3232
+ runId: input.runId ?? null,
3233
+ feature: input.feature ?? null,
3234
+ provider: input.provider ?? null,
3235
+ tenantRef: input.tenantRef ?? null,
3236
+ audioSeconds: input.audioSeconds ?? null,
3237
+ characters: input.characters ?? null,
3238
+ requests: input.requests ?? null,
3239
+ metadata: input.metadata ?? null,
3090
3240
  modelId: input.modelId,
3091
3241
  purpose: input.purpose,
3092
3242
  inputTokens: input.usage.inputTokens,
@@ -3282,7 +3432,7 @@ function toQueuedMessage(row) {
3282
3432
  __name(toQueuedMessage, "toQueuedMessage");
3283
3433
 
3284
3434
  // src/drizzle-governance-queries.ts
3285
- import { bucketByActor, bucketByModel, bucketByThread, bucketUsageTrend, dayBoundsUtc, rollupThreadUsage, summarizeGenuiCalls, truncateDetailContent } from "@dudousxd/nestjs-agent-core";
3435
+ import { bucketByActor, bucketByFeature, bucketByModel, bucketByThread, bucketUsageBreakdown, bucketUsageTrend, dayBoundsUtc, rollupThreadUsage, summarizeGenuiCalls, truncateDetailContent } from "@dudousxd/nestjs-agent-core";
3286
3436
  import { and as and3, asc as asc3, count, desc as desc2, eq as eq3, gte as gte2, inArray as inArray2, isNull as isNull3, like, lte as lte3, sql as sql4, sum } from "drizzle-orm";
3287
3437
  var UNCLASSIFIED_ERROR_CODE = "unknown";
3288
3438
  var DEFAULT_AGENT_BUCKET = "(default)";
@@ -3404,10 +3554,26 @@ function toRecentRunRow(run) {
3404
3554
  retries: run.retries,
3405
3555
  startedAt: run.startedAt.toISOString(),
3406
3556
  promptHash: run.promptHash ?? null,
3407
- parentRunId: run.parentRunId ?? null
3557
+ parentRunId: run.parentRunId ?? null,
3558
+ queueMs: run.queueMs ?? null,
3559
+ modelMs: run.modelMs ?? null,
3560
+ toolsMs: run.toolsMs ?? null,
3561
+ modelCalls: run.modelCalls ?? null,
3562
+ toolCalls: run.toolCalls ?? null
3408
3563
  };
3409
3564
  }
3410
3565
  __name(toRecentRunRow, "toRecentRunRow");
3566
+ function toRunTimings(run) {
3567
+ if (!Array.isArray(run.timings)) return null;
3568
+ return {
3569
+ modelMs: run.modelMs ?? 0,
3570
+ toolsMs: run.toolsMs ?? 0,
3571
+ modelCalls: run.modelCalls ?? 0,
3572
+ toolCalls: run.toolCalls ?? 0,
3573
+ steps: run.timings
3574
+ };
3575
+ }
3576
+ __name(toRunTimings, "toRunTimings");
3411
3577
  function toRunToolCallRow(toolCall) {
3412
3578
  return {
3413
3579
  toolCallId: toolCall.id,
@@ -3423,6 +3589,12 @@ function toRunToolCallRow(toolCall) {
3423
3589
  __name(toRunToolCallRow, "toRunToolCallRow");
3424
3590
  function toUsageInput(row) {
3425
3591
  return {
3592
+ feature: row.feature ?? null,
3593
+ purpose: row.purpose ?? null,
3594
+ costSource: row.costSource ?? null,
3595
+ audioSeconds: row.audioSeconds == null ? null : Number(row.audioSeconds),
3596
+ characters: row.characters == null ? null : Number(row.characters),
3597
+ requests: row.requests ?? null,
3426
3598
  modelId: row.modelId,
3427
3599
  actorRef: row.actorRef,
3428
3600
  threadId: row.threadId,
@@ -3470,7 +3642,12 @@ var DrizzleGovernanceQueries = class {
3470
3642
  }
3471
3643
  return pricing;
3472
3644
  }
3473
- async usageInRange(range) {
3645
+ /**
3646
+ * The usage rows in range, with only what the bucketers read: `requests` always (a recorded row
3647
+ * counts as its own requests in every rollup), and the feature/unit columns only for the
3648
+ * per-feature reads that group by them.
3649
+ */
3650
+ async usageInRange(range, withFeature = false) {
3474
3651
  const { start, end } = dayBoundsUtc(range);
3475
3652
  const usage = this.t.agentTokenUsage;
3476
3653
  const rows = await this.db.select({
@@ -3482,7 +3659,15 @@ var DrizzleGovernanceQueries = class {
3482
3659
  outputTokens: usage.outputTokens,
3483
3660
  cacheWriteTokens: usage.cacheWriteTokens,
3484
3661
  cacheReadTokens: usage.cacheReadTokens,
3485
- costUsd: usage.costUsd
3662
+ costUsd: usage.costUsd,
3663
+ requests: usage.requests,
3664
+ ...withFeature ? {
3665
+ feature: usage.feature,
3666
+ purpose: usage.purpose,
3667
+ costSource: usage.costSource,
3668
+ audioSeconds: usage.audioSeconds,
3669
+ characters: usage.characters
3670
+ } : {}
3486
3671
  }).from(usage).where(and3(gte2(this.t.agentTokenUsage.createdAt, start), lte3(this.t.agentTokenUsage.createdAt, end)));
3487
3672
  return rows.map(toUsageInput);
3488
3673
  }
@@ -3490,6 +3675,14 @@ var DrizzleGovernanceQueries = class {
3490
3675
  const pricing = await this.loadPricing();
3491
3676
  return bucketByModel(await this.usageInRange(range), pricing);
3492
3677
  }
3678
+ async spendByFeature(range) {
3679
+ const pricing = await this.loadPricing();
3680
+ return bucketByFeature(await this.usageInRange(range, true), pricing);
3681
+ }
3682
+ async usageBreakdown(range, filter) {
3683
+ const pricing = await this.loadPricing();
3684
+ return bucketUsageBreakdown(await this.usageInRange(range, true), pricing, filter);
3685
+ }
3493
3686
  async spendByActor(range) {
3494
3687
  const pricing = await this.loadPricing();
3495
3688
  return bucketByActor(await this.usageInRange(range), pricing);
@@ -3503,7 +3696,9 @@ var DrizzleGovernanceQueries = class {
3503
3696
  const pricing = await this.loadPricing();
3504
3697
  const rows = await this.usageInRange(range);
3505
3698
  const threadIds = [
3506
- ...new Set(rows.map((row) => row.threadId))
3699
+ ...new Set(rows.flatMap((row) => row.threadId === null ? [] : [
3700
+ row.threadId
3701
+ ]))
3507
3702
  ];
3508
3703
  if (threadIds.length === 0) {
3509
3704
  return [];
@@ -3581,6 +3776,7 @@ var DrizzleGovernanceQueries = class {
3581
3776
  outputTokens: sum(this.t.agentTokenUsage.outputTokens)
3582
3777
  }).from(this.t.agentTokenUsage).where(inArray2(this.t.agentTokenUsage.threadId, threadIds)).groupBy(this.t.agentTokenUsage.threadId);
3583
3778
  for (const row of usageTotals) {
3779
+ if (row.threadId === null) continue;
3584
3780
  const bucket = rollups.get(row.threadId);
3585
3781
  if (bucket !== void 0) {
3586
3782
  bucket.totalTokens = Number(row.inputTokens ?? 0) + Number(row.outputTokens ?? 0);
@@ -3957,7 +4153,8 @@ var DrizzleGovernanceQueries = class {
3957
4153
  actorRef: row.thread.actorRef,
3958
4154
  deleted: row.thread.deletedAt != null
3959
4155
  },
3960
- toolCalls: toolCalls.map(toRunToolCallRow)
4156
+ toolCalls: toolCalls.map(toRunToolCallRow),
4157
+ timings: toRunTimings(row.run)
3961
4158
  };
3962
4159
  }
3963
4160
  /**