@dudousxd/nestjs-agent-store-drizzle 0.34.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.cjs CHANGED
@@ -240,7 +240,7 @@ var agentQueuedMessage = (0, import_sqlite_core.sqliteTable)("agent_queued_messa
240
240
  ]);
241
241
  var agentTokenUsage = (0, import_sqlite_core.sqliteTable)("agent_token_usage", {
242
242
  id: (0, import_sqlite_core.text)("id").primaryKey(),
243
- threadId: (0, import_sqlite_core.text)("thread_id").notNull().references(() => agentThread.id, {
243
+ threadId: (0, import_sqlite_core.text)("thread_id").references(() => agentThread.id, {
244
244
  onDelete: "cascade"
245
245
  }),
246
246
  actorRef: (0, import_sqlite_core.text)("actor_ref").notNull(),
@@ -257,13 +257,28 @@ var agentTokenUsage = (0, import_sqlite_core.sqliteTable)("agent_token_usage", {
257
257
  costUsd: (0, import_sqlite_core.real)("cost_usd"),
258
258
  /** Where `cost_usd` came from: `'provider'` (reported) or `'estimate'` (tokens × the price row); null on rows from before it existed (reported). */
259
259
  costSource: (0, import_sqlite_core.text)("cost_source").$type(),
260
+ /** The run the row belongs to. */
261
+ runId: (0, import_sqlite_core.text)("run_id"),
262
+ /** What the spend was for: `agent:<name>` for an agent turn, the app's own otherwise; null → `agent`. */
263
+ feature: (0, import_sqlite_core.text)("feature"),
264
+ provider: (0, import_sqlite_core.text)("provider"),
265
+ tenantRef: (0, import_sqlite_core.text)("tenant_ref"),
266
+ /** Units a row bills by when they are not tokens. */
267
+ audioSeconds: (0, import_sqlite_core.real)("audio_seconds"),
268
+ characters: (0, import_sqlite_core.integer)("characters"),
269
+ requests: (0, import_sqlite_core.integer)("requests"),
270
+ /** The caller's own JSON, never read by the library. */
271
+ metadata: (0, import_sqlite_core.text)("metadata", {
272
+ mode: "json"
273
+ }).$type(),
260
274
  createdAt: (0, import_sqlite_core.integer)("created_at", {
261
275
  mode: "timestamp_ms"
262
276
  }).notNull()
263
277
  }, (table) => [
264
278
  (0, import_sqlite_core.index)("agent_token_usage_actor_created_idx").on(table.actorRef, table.createdAt),
265
279
  (0, import_sqlite_core.index)("agent_token_usage_created_idx").on(table.createdAt),
266
- (0, import_sqlite_core.index)("agent_token_usage_thread_idx").on(table.threadId)
280
+ (0, import_sqlite_core.index)("agent_token_usage_thread_idx").on(table.threadId),
281
+ (0, import_sqlite_core.index)("agent_token_usage_feature_created_idx").on(table.feature, table.createdAt)
267
282
  ]);
268
283
  var agentGenuiCall = (0, import_sqlite_core.sqliteTable)("agent_genui_call", {
269
284
  id: (0, import_sqlite_core.text)("id").primaryKey(),
@@ -345,7 +360,17 @@ var agentRun = (0, import_sqlite_core.sqliteTable)("agent_run", {
345
360
  * for a detached child, which outlives its parent's turn and so is paired by nothing in the
346
361
  * transcript.
347
362
  */
348
- parentRunId: (0, import_sqlite_core.text)("parent_run_id")
363
+ parentRunId: (0, import_sqlite_core.text)("parent_run_id"),
364
+ /** Enqueue → body start (ms); null when not read. */
365
+ queueMs: (0, import_sqlite_core.integer)("queue_ms"),
366
+ /** Where the completed run's time went: model and tool totals, counts, and the per-step list. */
367
+ modelMs: (0, import_sqlite_core.integer)("model_ms"),
368
+ toolsMs: (0, import_sqlite_core.integer)("tools_ms"),
369
+ modelCalls: (0, import_sqlite_core.integer)("model_calls"),
370
+ toolCalls: (0, import_sqlite_core.integer)("tool_calls"),
371
+ timings: (0, import_sqlite_core.text)("timings", {
372
+ mode: "json"
373
+ }).$type()
349
374
  }, (table) => [
350
375
  (0, import_sqlite_core.index)("agent_run_started_idx").on(table.startedAt)
351
376
  ]);
@@ -691,7 +716,7 @@ var agentQueuedMessage2 = (0, import_mysql_core.mysqlTable)("agent_queued_messag
691
716
  ]);
692
717
  var agentTokenUsage2 = (0, import_mysql_core.mysqlTable)("agent_token_usage", {
693
718
  id: key("id").primaryKey(),
694
- threadId: key("thread_id").notNull().references(() => agentThread2.id, {
719
+ threadId: key("thread_id").references(() => agentThread2.id, {
695
720
  onDelete: "cascade"
696
721
  }),
697
722
  actorRef: key("actor_ref").notNull(),
@@ -705,11 +730,26 @@ var agentTokenUsage2 = (0, import_mysql_core.mysqlTable)("agent_token_usage", {
705
730
  costUsd: (0, import_mysql_core.double)("cost_usd"),
706
731
  /** Where `cost_usd` came from: `'provider'` (reported) or `'estimate'` (tokens × the price row); null on rows from before it existed (reported). */
707
732
  costSource: key("cost_source").$type(),
733
+ /** The run the row belongs to. */
734
+ runId: key("run_id"),
735
+ /** What the spend was for: `agent:<name>` for an agent turn, the app's own otherwise; null → `agent`. */
736
+ feature: key("feature"),
737
+ provider: key("provider"),
738
+ tenantRef: key("tenant_ref"),
739
+ /** Units a row bills by when they are not tokens. */
740
+ audioSeconds: (0, import_mysql_core.double)("audio_seconds"),
741
+ characters: (0, import_mysql_core.bigint)("characters", {
742
+ mode: "number"
743
+ }),
744
+ requests: (0, import_mysql_core.int)("requests"),
745
+ /** The caller's own JSON, never read by the library. */
746
+ metadata: (0, import_mysql_core.json)("metadata").$type(),
708
747
  createdAt: ts("created_at").notNull()
709
748
  }, (table) => [
710
749
  (0, import_mysql_core.index)("agent_token_usage_actor_created_idx").on(table.actorRef, table.createdAt),
711
750
  (0, import_mysql_core.index)("agent_token_usage_created_idx").on(table.createdAt),
712
- (0, import_mysql_core.index)("agent_token_usage_thread_idx").on(table.threadId)
751
+ (0, import_mysql_core.index)("agent_token_usage_thread_idx").on(table.threadId),
752
+ (0, import_mysql_core.index)("agent_token_usage_feature_created_idx").on(table.feature, table.createdAt)
713
753
  ]);
714
754
  var agentGenuiCall2 = (0, import_mysql_core.mysqlTable)("agent_genui_call", {
715
755
  id: key("id").primaryKey(),
@@ -781,7 +821,15 @@ var agentRun2 = (0, import_mysql_core.mysqlTable)("agent_run", {
781
821
  startedAt: ts("started_at").notNull(),
782
822
  settledAt: ts("settled_at"),
783
823
  promptHash: key("prompt_hash"),
784
- parentRunId: key("parent_run_id")
824
+ parentRunId: key("parent_run_id"),
825
+ /** Enqueue → body start (ms); null when not read. */
826
+ queueMs: (0, import_mysql_core.int)("queue_ms"),
827
+ /** Where the completed run's time went: model and tool totals, counts, and the per-step list. */
828
+ modelMs: (0, import_mysql_core.int)("model_ms"),
829
+ toolsMs: (0, import_mysql_core.int)("tools_ms"),
830
+ modelCalls: (0, import_mysql_core.int)("model_calls"),
831
+ toolCalls: (0, import_mysql_core.int)("tool_calls"),
832
+ timings: (0, import_mysql_core.json)("timings").$type()
785
833
  }, (table) => [
786
834
  (0, import_mysql_core.index)("agent_run_started_idx").on(table.startedAt)
787
835
  ]);
@@ -1153,7 +1201,7 @@ var agentQueuedMessage3 = (0, import_pg_core.pgTable)("agent_queued_message", {
1153
1201
  ]);
1154
1202
  var agentTokenUsage3 = (0, import_pg_core.pgTable)("agent_token_usage", {
1155
1203
  id: (0, import_pg_core.text)("id").primaryKey(),
1156
- threadId: (0, import_pg_core.text)("thread_id").notNull().references(() => agentThread3.id, {
1204
+ threadId: (0, import_pg_core.text)("thread_id").references(() => agentThread3.id, {
1157
1205
  onDelete: "cascade"
1158
1206
  }),
1159
1207
  actorRef: (0, import_pg_core.text)("actor_ref").notNull(),
@@ -1167,11 +1215,26 @@ var agentTokenUsage3 = (0, import_pg_core.pgTable)("agent_token_usage", {
1167
1215
  costUsd: (0, import_pg_core.doublePrecision)("cost_usd"),
1168
1216
  /** Where `cost_usd` came from: `'provider'` (reported) or `'estimate'` (tokens × the price row); null on rows from before it existed (reported). */
1169
1217
  costSource: (0, import_pg_core.text)("cost_source").$type(),
1218
+ /** The run the row belongs to. */
1219
+ runId: (0, import_pg_core.text)("run_id"),
1220
+ /** What the spend was for: `agent:<name>` for an agent turn, the app's own otherwise; null → `agent`. */
1221
+ feature: (0, import_pg_core.text)("feature"),
1222
+ provider: (0, import_pg_core.text)("provider"),
1223
+ tenantRef: (0, import_pg_core.text)("tenant_ref"),
1224
+ /** Units a row bills by when they are not tokens. */
1225
+ audioSeconds: (0, import_pg_core.doublePrecision)("audio_seconds"),
1226
+ characters: (0, import_pg_core.bigint)("characters", {
1227
+ mode: "number"
1228
+ }),
1229
+ requests: (0, import_pg_core.integer)("requests"),
1230
+ /** The caller's own JSON, never read by the library. */
1231
+ metadata: (0, import_pg_core.jsonb)("metadata").$type(),
1170
1232
  createdAt: ts2("created_at").notNull()
1171
1233
  }, (table) => [
1172
1234
  (0, import_pg_core.index)("agent_token_usage_actor_created_idx").on(table.actorRef, table.createdAt),
1173
1235
  (0, import_pg_core.index)("agent_token_usage_created_idx").on(table.createdAt),
1174
- (0, import_pg_core.index)("agent_token_usage_thread_idx").on(table.threadId)
1236
+ (0, import_pg_core.index)("agent_token_usage_thread_idx").on(table.threadId),
1237
+ (0, import_pg_core.index)("agent_token_usage_feature_created_idx").on(table.feature, table.createdAt)
1175
1238
  ]);
1176
1239
  var agentGenuiCall3 = (0, import_pg_core.pgTable)("agent_genui_call", {
1177
1240
  id: (0, import_pg_core.text)("id").primaryKey(),
@@ -1243,7 +1306,15 @@ var agentRun3 = (0, import_pg_core.pgTable)("agent_run", {
1243
1306
  startedAt: ts2("started_at").notNull(),
1244
1307
  settledAt: ts2("settled_at"),
1245
1308
  promptHash: (0, import_pg_core.text)("prompt_hash"),
1246
- parentRunId: (0, import_pg_core.text)("parent_run_id")
1309
+ parentRunId: (0, import_pg_core.text)("parent_run_id"),
1310
+ /** Enqueue → body start (ms); null when not read. */
1311
+ queueMs: (0, import_pg_core.integer)("queue_ms"),
1312
+ /** Where the completed run's time went: model and tool totals, counts, and the per-step list. */
1313
+ modelMs: (0, import_pg_core.integer)("model_ms"),
1314
+ toolsMs: (0, import_pg_core.integer)("tools_ms"),
1315
+ modelCalls: (0, import_pg_core.integer)("model_calls"),
1316
+ toolCalls: (0, import_pg_core.integer)("tool_calls"),
1317
+ timings: (0, import_pg_core.jsonb)("timings").$type()
1247
1318
  }, (table) => [
1248
1319
  (0, import_pg_core.index)("agent_run_started_idx").on(table.startedAt)
1249
1320
  ]);
@@ -1701,10 +1772,60 @@ function specs(dialect) {
1701
1772
  }
1702
1773
  __name(specs, "specs");
1703
1774
  async function existingColumns(db, dialect, table) {
1704
- const rows = dialect === "sqlite" ? await querySql(db, dialect, import_drizzle_orm2.sql.raw(`PRAGMA table_info("${table}")`)) : await querySql(db, dialect, import_drizzle_orm2.sql`select column_name as name from information_schema.columns where table_schema = ${import_drizzle_orm2.sql.raw(dialect === "mysql" ? "database()" : "current_schema()")} and table_name = ${table}`);
1705
- return new Set(rows.map((row) => String(row.name)));
1775
+ return new Set((await columnNullability(db, dialect, table)).keys());
1706
1776
  }
1707
1777
  __name(existingColumns, "existingColumns");
1778
+ async function columnNullability(db, dialect, table) {
1779
+ if (dialect === "sqlite") {
1780
+ const rows2 = await querySql(db, dialect, import_drizzle_orm2.sql.raw(`PRAGMA table_info("${table}")`));
1781
+ return new Map(rows2.map((row) => [
1782
+ String(row.name),
1783
+ Number(row.notnull) === 0
1784
+ ]));
1785
+ }
1786
+ const rows = await querySql(db, dialect, import_drizzle_orm2.sql`select column_name as name, is_nullable as nullable from information_schema.columns where table_schema = ${import_drizzle_orm2.sql.raw(dialect === "mysql" ? "database()" : "current_schema()")} and table_name = ${table}`);
1787
+ return new Map(rows.map((row) => [
1788
+ String(row.name ?? row.NAME),
1789
+ String(row.nullable ?? row.NULLABLE).toUpperCase() === "YES"
1790
+ ]));
1791
+ }
1792
+ __name(columnNullability, "columnNullability");
1793
+ async function relaxNotNull(db, dialect, spec, column, all) {
1794
+ const q = /* @__PURE__ */ __name((identifier) => quote(dialect, identifier), "q");
1795
+ if (dialect === "postgres") {
1796
+ await runSql(db, dialect, import_drizzle_orm2.sql.raw(`ALTER TABLE ${q(spec.name)} ALTER COLUMN ${q(column.name)} DROP NOT NULL`));
1797
+ return;
1798
+ }
1799
+ if (dialect === "mysql") {
1800
+ await runSql(db, dialect, import_drizzle_orm2.sql.raw(`ALTER TABLE ${q(spec.name)} MODIFY ${columnDefinition(dialect, column)}`));
1801
+ return;
1802
+ }
1803
+ if (all.some((other) => other.foreignKeys.some((key2) => key2.table === spec.name && other !== spec))) {
1804
+ throw new Error(`agent schema: cannot make ${spec.name}.${column.name} nullable on SQLite \u2014 other tables reference ${spec.name}`);
1805
+ }
1806
+ const scratch = `${spec.name}__rebuild`;
1807
+ const present = [
1808
+ ...(await columnNullability(db, dialect, spec.name)).keys()
1809
+ ];
1810
+ const list = present.map(q).join(", ");
1811
+ await runSql(db, dialect, import_drizzle_orm2.sql.raw("SAVEPOINT agent_schema_rebuild"));
1812
+ try {
1813
+ await runSql(db, dialect, import_drizzle_orm2.sql.raw(`DROP TABLE IF EXISTS ${q(scratch)}`));
1814
+ await runSql(db, dialect, import_drizzle_orm2.sql.raw(createTableSql(dialect, {
1815
+ ...spec,
1816
+ name: scratch
1817
+ })));
1818
+ await runSql(db, dialect, import_drizzle_orm2.sql.raw(`INSERT INTO ${q(scratch)} (${list}) SELECT ${list} FROM ${q(spec.name)}`));
1819
+ await runSql(db, dialect, import_drizzle_orm2.sql.raw(`DROP TABLE ${q(spec.name)}`));
1820
+ await runSql(db, dialect, import_drizzle_orm2.sql.raw(`ALTER TABLE ${q(scratch)} RENAME TO ${q(spec.name)}`));
1821
+ await runSql(db, dialect, import_drizzle_orm2.sql.raw("RELEASE SAVEPOINT agent_schema_rebuild"));
1822
+ } catch (error) {
1823
+ await runSql(db, dialect, import_drizzle_orm2.sql.raw("ROLLBACK TO SAVEPOINT agent_schema_rebuild")).catch(() => void 0);
1824
+ await runSql(db, dialect, import_drizzle_orm2.sql.raw("RELEASE SAVEPOINT agent_schema_rebuild")).catch(() => void 0);
1825
+ throw error;
1826
+ }
1827
+ }
1828
+ __name(relaxNotNull, "relaxNotNull");
1708
1829
  async function indexExists(db, dialect, table, index4) {
1709
1830
  let query;
1710
1831
  if (dialect === "sqlite") {
@@ -1718,7 +1839,8 @@ async function indexExists(db, dialect, table, index4) {
1718
1839
  }
1719
1840
  __name(indexExists, "indexExists");
1720
1841
  async function heal(db, dialect) {
1721
- for (const spec of specs(dialect)) {
1842
+ const all = specs(dialect);
1843
+ for (const spec of all) {
1722
1844
  await runSql(db, dialect, import_drizzle_orm2.sql.raw(createTableSql(dialect, spec)));
1723
1845
  const present = await existingColumns(db, dialect, spec.name);
1724
1846
  for (const column of spec.columns) {
@@ -1726,6 +1848,13 @@ async function heal(db, dialect) {
1726
1848
  await runSql(db, dialect, import_drizzle_orm2.sql.raw(`ALTER TABLE ${quote(dialect, spec.name)} ADD COLUMN ${columnDefinition(dialect, column)}`));
1727
1849
  }
1728
1850
  }
1851
+ const nullable3 = await columnNullability(db, dialect, spec.name);
1852
+ for (const column of spec.columns) {
1853
+ if (column.notNull || spec.primaryKey.includes(column.name)) continue;
1854
+ if (nullable3.get(column.name) === false) {
1855
+ await relaxNotNull(db, dialect, spec, column, all);
1856
+ }
1857
+ }
1729
1858
  for (const index4 of spec.indexes) {
1730
1859
  if (!await indexExists(db, dialect, spec.name, index4.name)) {
1731
1860
  await runSql(db, dialect, import_drizzle_orm2.sql.raw(createIndexSql(dialect, spec.name, index4)));
@@ -2916,7 +3045,13 @@ var DrizzleAgentStore = class {
2916
3045
  startedAt: /* @__PURE__ */ new Date(),
2917
3046
  settledAt: null,
2918
3047
  promptHash: run.promptHash ?? null,
2919
- parentRunId: run.parentRunId ?? null
3048
+ parentRunId: run.parentRunId ?? null,
3049
+ queueMs: run.queueMs !== void 0 ? Math.max(0, Math.round(run.queueMs)) : null,
3050
+ modelMs: null,
3051
+ toolsMs: null,
3052
+ modelCalls: null,
3053
+ toolCalls: null,
3054
+ timings: null
2920
3055
  };
2921
3056
  await this.db.insert(this.t.agentRun).values(runRow);
2922
3057
  }
@@ -2944,6 +3079,13 @@ var DrizzleAgentStore = class {
2944
3079
  if (end.errorMessage !== void 0) {
2945
3080
  updates.errorMessage = end.errorMessage;
2946
3081
  }
3082
+ if (end.timings !== void 0) {
3083
+ updates.modelMs = Math.round(end.timings.modelMs);
3084
+ updates.toolsMs = Math.round(end.timings.toolsMs);
3085
+ updates.modelCalls = end.timings.modelCalls;
3086
+ updates.toolCalls = end.timings.toolCalls;
3087
+ updates.timings = end.timings.steps;
3088
+ }
2947
3089
  await this.db.update(this.t.agentRun).set(updates).where((0, import_drizzle_orm4.eq)(this.t.agentRun.id, end.runId));
2948
3090
  }
2949
3091
  /** Bump the run's llm-step retry counter. Atomic `retries = retries + 1`, no read-modify-write. */
@@ -3146,9 +3288,17 @@ var DrizzleAgentStore = class {
3146
3288
  async recordUsage(input) {
3147
3289
  await this.db.insert(this.t.agentTokenUsage).values({
3148
3290
  id: crypto.randomUUID(),
3149
- threadId: input.threadId,
3291
+ threadId: input.threadId ?? null,
3150
3292
  actorRef: input.actorRef,
3151
3293
  messageId: input.messageId ?? null,
3294
+ runId: input.runId ?? null,
3295
+ feature: input.feature ?? null,
3296
+ provider: input.provider ?? null,
3297
+ tenantRef: input.tenantRef ?? null,
3298
+ audioSeconds: input.audioSeconds ?? null,
3299
+ characters: input.characters ?? null,
3300
+ requests: input.requests ?? null,
3301
+ metadata: input.metadata ?? null,
3152
3302
  modelId: input.modelId,
3153
3303
  purpose: input.purpose,
3154
3304
  inputTokens: input.usage.inputTokens,
@@ -3466,10 +3616,26 @@ function toRecentRunRow(run) {
3466
3616
  retries: run.retries,
3467
3617
  startedAt: run.startedAt.toISOString(),
3468
3618
  promptHash: run.promptHash ?? null,
3469
- parentRunId: run.parentRunId ?? null
3619
+ parentRunId: run.parentRunId ?? null,
3620
+ queueMs: run.queueMs ?? null,
3621
+ modelMs: run.modelMs ?? null,
3622
+ toolsMs: run.toolsMs ?? null,
3623
+ modelCalls: run.modelCalls ?? null,
3624
+ toolCalls: run.toolCalls ?? null
3470
3625
  };
3471
3626
  }
3472
3627
  __name(toRecentRunRow, "toRecentRunRow");
3628
+ function toRunTimings(run) {
3629
+ if (!Array.isArray(run.timings)) return null;
3630
+ return {
3631
+ modelMs: run.modelMs ?? 0,
3632
+ toolsMs: run.toolsMs ?? 0,
3633
+ modelCalls: run.modelCalls ?? 0,
3634
+ toolCalls: run.toolCalls ?? 0,
3635
+ steps: run.timings
3636
+ };
3637
+ }
3638
+ __name(toRunTimings, "toRunTimings");
3473
3639
  function toRunToolCallRow(toolCall) {
3474
3640
  return {
3475
3641
  toolCallId: toolCall.id,
@@ -3485,6 +3651,12 @@ function toRunToolCallRow(toolCall) {
3485
3651
  __name(toRunToolCallRow, "toRunToolCallRow");
3486
3652
  function toUsageInput(row) {
3487
3653
  return {
3654
+ feature: row.feature ?? null,
3655
+ purpose: row.purpose ?? null,
3656
+ costSource: row.costSource ?? null,
3657
+ audioSeconds: row.audioSeconds == null ? null : Number(row.audioSeconds),
3658
+ characters: row.characters == null ? null : Number(row.characters),
3659
+ requests: row.requests ?? null,
3488
3660
  modelId: row.modelId,
3489
3661
  actorRef: row.actorRef,
3490
3662
  threadId: row.threadId,
@@ -3532,7 +3704,12 @@ var DrizzleGovernanceQueries = class {
3532
3704
  }
3533
3705
  return pricing;
3534
3706
  }
3535
- async usageInRange(range) {
3707
+ /**
3708
+ * The usage rows in range, with only what the bucketers read: `requests` always (a recorded row
3709
+ * counts as its own requests in every rollup), and the feature/unit columns only for the
3710
+ * per-feature reads that group by them.
3711
+ */
3712
+ async usageInRange(range, withFeature = false) {
3536
3713
  const { start, end } = (0, import_nestjs_agent_core5.dayBoundsUtc)(range);
3537
3714
  const usage = this.t.agentTokenUsage;
3538
3715
  const rows = await this.db.select({
@@ -3544,7 +3721,15 @@ var DrizzleGovernanceQueries = class {
3544
3721
  outputTokens: usage.outputTokens,
3545
3722
  cacheWriteTokens: usage.cacheWriteTokens,
3546
3723
  cacheReadTokens: usage.cacheReadTokens,
3547
- costUsd: usage.costUsd
3724
+ costUsd: usage.costUsd,
3725
+ requests: usage.requests,
3726
+ ...withFeature ? {
3727
+ feature: usage.feature,
3728
+ purpose: usage.purpose,
3729
+ costSource: usage.costSource,
3730
+ audioSeconds: usage.audioSeconds,
3731
+ characters: usage.characters
3732
+ } : {}
3548
3733
  }).from(usage).where((0, import_drizzle_orm5.and)((0, import_drizzle_orm5.gte)(this.t.agentTokenUsage.createdAt, start), (0, import_drizzle_orm5.lte)(this.t.agentTokenUsage.createdAt, end)));
3549
3734
  return rows.map(toUsageInput);
3550
3735
  }
@@ -3552,6 +3737,14 @@ var DrizzleGovernanceQueries = class {
3552
3737
  const pricing = await this.loadPricing();
3553
3738
  return (0, import_nestjs_agent_core5.bucketByModel)(await this.usageInRange(range), pricing);
3554
3739
  }
3740
+ async spendByFeature(range) {
3741
+ const pricing = await this.loadPricing();
3742
+ return (0, import_nestjs_agent_core5.bucketByFeature)(await this.usageInRange(range, true), pricing);
3743
+ }
3744
+ async usageBreakdown(range, filter) {
3745
+ const pricing = await this.loadPricing();
3746
+ return (0, import_nestjs_agent_core5.bucketUsageBreakdown)(await this.usageInRange(range, true), pricing, filter);
3747
+ }
3555
3748
  async spendByActor(range) {
3556
3749
  const pricing = await this.loadPricing();
3557
3750
  return (0, import_nestjs_agent_core5.bucketByActor)(await this.usageInRange(range), pricing);
@@ -3565,7 +3758,9 @@ var DrizzleGovernanceQueries = class {
3565
3758
  const pricing = await this.loadPricing();
3566
3759
  const rows = await this.usageInRange(range);
3567
3760
  const threadIds = [
3568
- ...new Set(rows.map((row) => row.threadId))
3761
+ ...new Set(rows.flatMap((row) => row.threadId === null ? [] : [
3762
+ row.threadId
3763
+ ]))
3569
3764
  ];
3570
3765
  if (threadIds.length === 0) {
3571
3766
  return [];
@@ -3643,6 +3838,7 @@ var DrizzleGovernanceQueries = class {
3643
3838
  outputTokens: (0, import_drizzle_orm5.sum)(this.t.agentTokenUsage.outputTokens)
3644
3839
  }).from(this.t.agentTokenUsage).where((0, import_drizzle_orm5.inArray)(this.t.agentTokenUsage.threadId, threadIds)).groupBy(this.t.agentTokenUsage.threadId);
3645
3840
  for (const row of usageTotals) {
3841
+ if (row.threadId === null) continue;
3646
3842
  const bucket = rollups.get(row.threadId);
3647
3843
  if (bucket !== void 0) {
3648
3844
  bucket.totalTokens = Number(row.inputTokens ?? 0) + Number(row.outputTokens ?? 0);
@@ -4019,7 +4215,8 @@ var DrizzleGovernanceQueries = class {
4019
4215
  actorRef: row.thread.actorRef,
4020
4216
  deleted: row.thread.deletedAt != null
4021
4217
  },
4022
- toolCalls: toolCalls.map(toRunToolCallRow)
4218
+ toolCalls: toolCalls.map(toRunToolCallRow),
4219
+ timings: toRunTimings(row.run)
4023
4220
  };
4024
4221
  }
4025
4222
  /**