@dudousxd/nestjs-agent-store-drizzle 0.31.0 → 0.33.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
@@ -32,7 +32,9 @@ var agentThread = sqliteTable("agent_thread", {
32
32
  mode: "timestamp_ms"
33
33
  })
34
34
  }, (table) => [
35
- index("agent_thread_actor_updated_idx").on(table.actorRef, table.updatedAt)
35
+ index("agent_thread_actor_updated_idx").on(table.actorRef, table.updatedAt),
36
+ // `ownerOfActiveStream` reads by run id on every stream attach and reconnect.
37
+ index("agent_thread_active_stream_idx").on(table.activeStreamId)
36
38
  ]);
37
39
  var agentMessage = sqliteTable("agent_message", {
38
40
  id: text("id").primaryKey(),
@@ -82,7 +84,9 @@ var agentMessage = sqliteTable("agent_message", {
82
84
  mode: "timestamp_ms"
83
85
  }).notNull()
84
86
  }, (table) => [
85
- index("agent_message_thread_created_idx").on(table.threadId, table.createdAt)
87
+ index("agent_message_thread_created_idx").on(table.threadId, table.createdAt),
88
+ // Transcript reads order a thread's messages by `seq`.
89
+ index("agent_message_thread_seq_idx").on(table.threadId, table.seq)
86
90
  ]);
87
91
  var agentToolCall = sqliteTable("agent_tool_call", {
88
92
  proposalId: text("proposal_id"),
@@ -127,7 +131,11 @@ var agentToolCall = sqliteTable("agent_tool_call", {
127
131
  /** The surface the decision came through (`web`, `slack`, `remembered`, …). */
128
132
  decidedVia: text("decided_via")
129
133
  }, (table) => [
130
- index("agent_tool_call_message_idx").on(table.messageId)
134
+ index("agent_tool_call_message_idx").on(table.messageId),
135
+ // The approvals inbox: `pending_approval`, oldest first.
136
+ index("agent_tool_call_status_created_idx").on(table.status, table.createdAt),
137
+ index("agent_tool_call_run_idx").on(table.runId),
138
+ index("agent_tool_call_created_idx").on(table.createdAt)
131
139
  ]);
132
140
  var agentQueuedMessage = sqliteTable("agent_queued_message", {
133
141
  uiCapabilities: text("ui_capabilities", {
@@ -191,7 +199,9 @@ var agentTokenUsage = sqliteTable("agent_token_usage", {
191
199
  mode: "timestamp_ms"
192
200
  }).notNull()
193
201
  }, (table) => [
194
- index("agent_token_usage_actor_created_idx").on(table.actorRef, table.createdAt)
202
+ index("agent_token_usage_actor_created_idx").on(table.actorRef, table.createdAt),
203
+ index("agent_token_usage_created_idx").on(table.createdAt),
204
+ index("agent_token_usage_thread_idx").on(table.threadId)
195
205
  ]);
196
206
  var agentGenuiCall = sqliteTable("agent_genui_call", {
197
207
  id: text("id").primaryKey(),
@@ -249,6 +259,12 @@ var agentRun = sqliteTable("agent_run", {
249
259
  agentName: text("agent_name"),
250
260
  status: text("status").$type().notNull(),
251
261
  durationMs: integer("duration_ms"),
262
+ /** Turn start to the first `text` frame; null for a run that streamed none or did not complete. */
263
+ ttftMs: integer("ttft_ms"),
264
+ /** The run's known spend (the sum of its model calls with a known cost); null when none had one. */
265
+ costUsd: real("cost_usd"),
266
+ /** The model the run's last model call resolved to. */
267
+ modelId: text("model_id"),
252
268
  errorCode: text("error_code"),
253
269
  errorMessage: text("error_message"),
254
270
  retries: integer("retries").notNull().default(0),
@@ -528,7 +544,9 @@ var agentThread2 = mysqlTable("agent_thread", {
528
544
  updatedAt: ts("updated_at").notNull(),
529
545
  deletedAt: ts("deleted_at")
530
546
  }, (table) => [
531
- index2("agent_thread_actor_updated_idx").on(table.actorRef, table.updatedAt)
547
+ index2("agent_thread_actor_updated_idx").on(table.actorRef, table.updatedAt),
548
+ // `ownerOfActiveStream` reads by run id on every stream attach and reconnect.
549
+ index2("agent_thread_active_stream_idx").on(table.activeStreamId)
532
550
  ]);
533
551
  var agentMessage2 = mysqlTable("agent_message", {
534
552
  id: key("id").primaryKey(),
@@ -554,7 +572,9 @@ var agentMessage2 = mysqlTable("agent_message", {
554
572
  seq: int("seq"),
555
573
  createdAt: ts("created_at").notNull()
556
574
  }, (table) => [
557
- index2("agent_message_thread_created_idx").on(table.threadId, table.createdAt)
575
+ index2("agent_message_thread_created_idx").on(table.threadId, table.createdAt),
576
+ // Transcript reads order a thread's messages by `seq`.
577
+ index2("agent_message_thread_seq_idx").on(table.threadId, table.seq)
558
578
  ]);
559
579
  var agentToolCall2 = mysqlTable("agent_tool_call", {
560
580
  proposalId: key("proposal_id"),
@@ -579,7 +599,11 @@ var agentToolCall2 = mysqlTable("agent_tool_call", {
579
599
  remember: boolean("remember"),
580
600
  decidedVia: key("decided_via")
581
601
  }, (table) => [
582
- index2("agent_tool_call_message_idx").on(table.messageId)
602
+ index2("agent_tool_call_message_idx").on(table.messageId),
603
+ // The approvals inbox: `pending_approval`, oldest first.
604
+ index2("agent_tool_call_status_created_idx").on(table.status, table.createdAt),
605
+ index2("agent_tool_call_run_idx").on(table.runId),
606
+ index2("agent_tool_call_created_idx").on(table.createdAt)
583
607
  ]);
584
608
  var agentQueuedMessage2 = mysqlTable("agent_queued_message", {
585
609
  uiCapabilities: json("ui_capabilities").$type(),
@@ -621,7 +645,9 @@ var agentTokenUsage2 = mysqlTable("agent_token_usage", {
621
645
  costSource: key("cost_source").$type(),
622
646
  createdAt: ts("created_at").notNull()
623
647
  }, (table) => [
624
- index2("agent_token_usage_actor_created_idx").on(table.actorRef, table.createdAt)
648
+ index2("agent_token_usage_actor_created_idx").on(table.actorRef, table.createdAt),
649
+ index2("agent_token_usage_created_idx").on(table.createdAt),
650
+ index2("agent_token_usage_thread_idx").on(table.threadId)
625
651
  ]);
626
652
  var agentGenuiCall2 = mysqlTable("agent_genui_call", {
627
653
  id: key("id").primaryKey(),
@@ -681,6 +707,12 @@ var agentRun2 = mysqlTable("agent_run", {
681
707
  agentName: key("agent_name"),
682
708
  status: key("status").$type().notNull(),
683
709
  durationMs: int("duration_ms"),
710
+ /** Turn start to the first `text` frame; null for a run that streamed none or did not complete. */
711
+ ttftMs: int("ttft_ms"),
712
+ /** The run's known spend (the sum of its model calls with a known cost); null when none had one. */
713
+ costUsd: double("cost_usd"),
714
+ /** The model the run's last model call resolved to. */
715
+ modelId: key("model_id"),
684
716
  errorCode: key("error_code"),
685
717
  errorMessage: longtext("error_message"),
686
718
  retries: int("retries").notNull().default(0),
@@ -974,7 +1006,9 @@ var agentThread3 = pgTable("agent_thread", {
974
1006
  updatedAt: ts2("updated_at").notNull(),
975
1007
  deletedAt: ts2("deleted_at")
976
1008
  }, (table) => [
977
- index3("agent_thread_actor_updated_idx").on(table.actorRef, table.updatedAt)
1009
+ index3("agent_thread_actor_updated_idx").on(table.actorRef, table.updatedAt),
1010
+ // `ownerOfActiveStream` reads by run id on every stream attach and reconnect.
1011
+ index3("agent_thread_active_stream_idx").on(table.activeStreamId)
978
1012
  ]);
979
1013
  var agentMessage3 = pgTable("agent_message", {
980
1014
  id: text2("id").primaryKey(),
@@ -1000,7 +1034,9 @@ var agentMessage3 = pgTable("agent_message", {
1000
1034
  seq: integer2("seq"),
1001
1035
  createdAt: ts2("created_at").notNull()
1002
1036
  }, (table) => [
1003
- index3("agent_message_thread_created_idx").on(table.threadId, table.createdAt)
1037
+ index3("agent_message_thread_created_idx").on(table.threadId, table.createdAt),
1038
+ // Transcript reads order a thread's messages by `seq`.
1039
+ index3("agent_message_thread_seq_idx").on(table.threadId, table.seq)
1004
1040
  ]);
1005
1041
  var agentToolCall3 = pgTable("agent_tool_call", {
1006
1042
  proposalId: text2("proposal_id"),
@@ -1025,7 +1061,11 @@ var agentToolCall3 = pgTable("agent_tool_call", {
1025
1061
  remember: boolean2("remember"),
1026
1062
  decidedVia: text2("decided_via")
1027
1063
  }, (table) => [
1028
- index3("agent_tool_call_message_idx").on(table.messageId)
1064
+ index3("agent_tool_call_message_idx").on(table.messageId),
1065
+ // The approvals inbox: `pending_approval`, oldest first.
1066
+ index3("agent_tool_call_status_created_idx").on(table.status, table.createdAt),
1067
+ index3("agent_tool_call_run_idx").on(table.runId),
1068
+ index3("agent_tool_call_created_idx").on(table.createdAt)
1029
1069
  ]);
1030
1070
  var agentQueuedMessage3 = pgTable("agent_queued_message", {
1031
1071
  uiCapabilities: jsonb("ui_capabilities").$type(),
@@ -1067,7 +1107,9 @@ var agentTokenUsage3 = pgTable("agent_token_usage", {
1067
1107
  costSource: text2("cost_source").$type(),
1068
1108
  createdAt: ts2("created_at").notNull()
1069
1109
  }, (table) => [
1070
- index3("agent_token_usage_actor_created_idx").on(table.actorRef, table.createdAt)
1110
+ index3("agent_token_usage_actor_created_idx").on(table.actorRef, table.createdAt),
1111
+ index3("agent_token_usage_created_idx").on(table.createdAt),
1112
+ index3("agent_token_usage_thread_idx").on(table.threadId)
1071
1113
  ]);
1072
1114
  var agentGenuiCall3 = pgTable("agent_genui_call", {
1073
1115
  id: text2("id").primaryKey(),
@@ -1127,6 +1169,12 @@ var agentRun3 = pgTable("agent_run", {
1127
1169
  agentName: text2("agent_name"),
1128
1170
  status: text2("status").$type().notNull(),
1129
1171
  durationMs: integer2("duration_ms"),
1172
+ /** Turn start to the first `text` frame; null for a run that streamed none or did not complete. */
1173
+ ttftMs: integer2("ttft_ms"),
1174
+ /** The run's known spend (the sum of its model calls with a known cost); null when none had one. */
1175
+ costUsd: doublePrecision("cost_usd"),
1176
+ /** The model the run's last model call resolved to. */
1177
+ modelId: text2("model_id"),
1130
1178
  errorCode: text2("error_code"),
1131
1179
  errorMessage: text2("error_message"),
1132
1180
  retries: integer2("retries").notNull().default(0),
@@ -2340,12 +2388,49 @@ var DrizzleAgentStore = class {
2340
2388
  }
2341
2389
  async listThreads(actorRef, limit = 50) {
2342
2390
  const threads = await this.db.select().from(this.t.agentThread).where(and2(eq2(this.t.agentThread.actorRef, actorRef), eq2(this.t.agentThread.transient, false), isNull2(this.t.agentThread.deletedAt))).orderBy(desc(this.t.agentThread.updatedAt)).limit(limit);
2343
- const summaries = [];
2344
- for (const thread of threads) {
2345
- const [last] = await this.db.select().from(this.t.agentMessage).where(eq2(this.t.agentMessage.threadId, thread.id)).orderBy(...this.messageOrderNewestFirst()).limit(1);
2346
- summaries.push(this.toSummary(thread, last?.content));
2391
+ const previews = await this.lastMessageContents(threads.map((thread) => thread.id));
2392
+ return threads.map((thread) => this.toSummary(thread, previews.get(thread.id)));
2393
+ }
2394
+ /**
2395
+ * The content of each thread's newest message ({@link messageOrderNewestFirst}), in a fixed two
2396
+ * reads for any number of threads: each thread's highest `seq`, then the rows holding it. Only a
2397
+ * thread whose every message predates `seq` is read on its own, by `created_at`.
2398
+ */
2399
+ async lastMessageContents(threadIds) {
2400
+ const contents = /* @__PURE__ */ new Map();
2401
+ if (threadIds.length === 0) {
2402
+ return contents;
2403
+ }
2404
+ const message = this.t.agentMessage;
2405
+ const highest = await this.db.select({
2406
+ threadId: message.threadId,
2407
+ seq: max2(message.seq)
2408
+ }).from(message).where(inArray(message.threadId, [
2409
+ ...threadIds
2410
+ ])).groupBy(message.threadId);
2411
+ const numbered = highest.filter((row) => row.seq !== null);
2412
+ if (numbered.length > 0) {
2413
+ const rows = await this.db.select({
2414
+ threadId: message.threadId,
2415
+ content: message.content
2416
+ }).from(message).where(or2(...numbered.map((row) => and2(eq2(message.threadId, row.threadId), eq2(message.seq, Number(row.seq)))))).orderBy(...this.messageOrderNewestFirst());
2417
+ for (const row of rows) {
2418
+ if (!contents.has(row.threadId)) {
2419
+ contents.set(row.threadId, row.content);
2420
+ }
2421
+ }
2347
2422
  }
2348
- return summaries;
2423
+ for (const row of highest) {
2424
+ if (row.seq === null) {
2425
+ const [last] = await this.db.select({
2426
+ content: message.content
2427
+ }).from(message).where(eq2(message.threadId, row.threadId)).orderBy(...this.messageOrderNewestFirst()).limit(1);
2428
+ if (last !== void 0) {
2429
+ contents.set(row.threadId, last.content);
2430
+ }
2431
+ }
2432
+ }
2433
+ return contents;
2349
2434
  }
2350
2435
  async softDeleteThread(threadId) {
2351
2436
  await this.db.update(this.t.agentThread).set({
@@ -2722,6 +2807,24 @@ var DrizzleAgentStore = class {
2722
2807
  }).from(agentThread).where(and2(eq2(this.t.agentThread.id, threadId), isNull2(this.t.agentThread.deletedAt)));
2723
2808
  return thread?.persona ?? null;
2724
2809
  }
2810
+ async threadSettings(threadId) {
2811
+ const thread = this.t.agentThread;
2812
+ const [row] = await this.db.select({
2813
+ actorRef: thread.actorRef,
2814
+ defaultAgent: thread.defaultAgent,
2815
+ persona: thread.persona,
2816
+ model: thread.model
2817
+ }).from(thread).where(and2(eq2(thread.id, threadId), isNull2(thread.deletedAt)));
2818
+ if (row === void 0) {
2819
+ return null;
2820
+ }
2821
+ return {
2822
+ actorRef: row.actorRef,
2823
+ defaultAgent: row.defaultAgent ?? null,
2824
+ persona: row.persona ?? null,
2825
+ model: row.model ?? null
2826
+ };
2827
+ }
2725
2828
  async defaultAgentForThread(threadId) {
2726
2829
  const [thread] = await this.db.select({
2727
2830
  defaultAgent: this.t.agentThread.defaultAgent
@@ -2742,6 +2845,9 @@ var DrizzleAgentStore = class {
2742
2845
  agentName: run.agentName ?? null,
2743
2846
  status: "running",
2744
2847
  durationMs: null,
2848
+ ttftMs: null,
2849
+ costUsd: null,
2850
+ modelId: null,
2745
2851
  errorCode: null,
2746
2852
  errorMessage: null,
2747
2853
  retries: 0,
@@ -2761,6 +2867,15 @@ var DrizzleAgentStore = class {
2761
2867
  if (end.durationMs !== void 0) {
2762
2868
  updates.durationMs = end.durationMs;
2763
2869
  }
2870
+ if (end.ttftMs !== void 0) {
2871
+ updates.ttftMs = end.ttftMs;
2872
+ }
2873
+ if (end.costUsd !== void 0) {
2874
+ updates.costUsd = end.costUsd;
2875
+ }
2876
+ if (end.modelId !== void 0) {
2877
+ updates.modelId = end.modelId;
2878
+ }
2764
2879
  if (end.errorCode !== void 0) {
2765
2880
  updates.errorCode = end.errorCode;
2766
2881
  }
@@ -3025,10 +3140,43 @@ var DrizzleAgentStore = class {
3025
3140
  return this.usageBetween(actorRef, day, day);
3026
3141
  }
3027
3142
  async usageBetween(actorRef, fromDay, toDay) {
3028
- const start = /* @__PURE__ */ new Date(`${fromDay}T00:00:00.000Z`);
3029
- const end = /* @__PURE__ */ new Date(`${toDay}T23:59:59.999Z`);
3030
- const rows = await this.db.select().from(this.t.agentTokenUsage).where(and2(eq2(this.t.agentTokenUsage.actorRef, actorRef), gte(this.t.agentTokenUsage.createdAt, start), lte2(this.t.agentTokenUsage.createdAt, end)));
3031
- return sumUsage(rows);
3143
+ const [totals] = await this.usageWindows(actorRef, [
3144
+ {
3145
+ fromDay,
3146
+ toDay
3147
+ }
3148
+ ]);
3149
+ return totals ?? sumUsage([]);
3150
+ }
3151
+ /**
3152
+ * Every range summed by the database in one statement, each as a conditional aggregate over the
3153
+ * rows of the smallest span covering them all — the actor's `(actor_ref, created_at)` index serves
3154
+ * it, and no usage row crosses the wire.
3155
+ */
3156
+ async usageWindows(actorRef, ranges) {
3157
+ if (ranges.length === 0) {
3158
+ return [];
3159
+ }
3160
+ const usage = this.t.agentTokenUsage;
3161
+ const bounds = ranges.map(({ fromDay, toDay }) => ({
3162
+ start: /* @__PURE__ */ new Date(`${fromDay}T00:00:00.000Z`),
3163
+ end: /* @__PURE__ */ new Date(`${toDay}T23:59:59.999Z`)
3164
+ }));
3165
+ const within = /* @__PURE__ */ __name((start, end) => and2(gte(usage.createdAt, start), lte2(usage.createdAt, end)), "within");
3166
+ const columns = {};
3167
+ bounds.forEach(({ start, end }, index4) => {
3168
+ columns[`tokens${index4}`] = sql3`sum(case when ${within(start, end)} then ${usage.inputTokens} + ${usage.outputTokens} else 0 end)`;
3169
+ columns[`cost${index4}`] = sql3`sum(case when ${within(start, end)} then ${usage.costUsd} else 0 end)`;
3170
+ columns[`estimated${index4}`] = sql3`sum(case when ${within(start, end)} and ${usage.costSource} = 'estimate' then ${usage.costUsd} else 0 end)`;
3171
+ });
3172
+ const from = new Date(Math.min(...bounds.map(({ start }) => start.getTime())));
3173
+ const to = new Date(Math.max(...bounds.map(({ end }) => end.getTime())));
3174
+ const [row] = await this.db.select(columns).from(usage).where(and2(eq2(usage.actorRef, actorRef), within(from, to)));
3175
+ return bounds.map((_, index4) => ({
3176
+ usedTokens: Number(row?.[`tokens${index4}`] ?? 0),
3177
+ costUsd: Number(row?.[`cost${index4}`] ?? 0),
3178
+ estimatedCostUsd: Number(row?.[`estimated${index4}`] ?? 0)
3179
+ }));
3032
3180
  }
3033
3181
  toSummary(thread, lastContent) {
3034
3182
  return {
@@ -3246,6 +3394,11 @@ function toRecentRunRow(run) {
3246
3394
  agentName: run.agentName ?? null,
3247
3395
  status: run.status,
3248
3396
  durationMs: run.durationMs ?? null,
3397
+ ttftMs: run.ttftMs ?? null,
3398
+ // MySQL's DOUBLE and Postgres' double precision come back as numbers; a driver that hands a
3399
+ // decimal back as a string is normalized here so the SPI row is always a number.
3400
+ costUsd: run.costUsd == null ? null : Number(run.costUsd),
3401
+ modelId: run.modelId ?? null,
3249
3402
  errorCode: run.errorCode ?? null,
3250
3403
  errorMessage: run.errorMessage ?? null,
3251
3404
  retries: run.retries,
@@ -3319,7 +3472,18 @@ var DrizzleGovernanceQueries = class {
3319
3472
  }
3320
3473
  async usageInRange(range) {
3321
3474
  const { start, end } = dayBoundsUtc(range);
3322
- const rows = await this.db.select().from(this.t.agentTokenUsage).where(and3(gte2(this.t.agentTokenUsage.createdAt, start), lte3(this.t.agentTokenUsage.createdAt, end)));
3475
+ const usage = this.t.agentTokenUsage;
3476
+ const rows = await this.db.select({
3477
+ modelId: usage.modelId,
3478
+ actorRef: usage.actorRef,
3479
+ threadId: usage.threadId,
3480
+ createdAt: usage.createdAt,
3481
+ inputTokens: usage.inputTokens,
3482
+ outputTokens: usage.outputTokens,
3483
+ cacheWriteTokens: usage.cacheWriteTokens,
3484
+ cacheReadTokens: usage.cacheReadTokens,
3485
+ costUsd: usage.costUsd
3486
+ }).from(usage).where(and3(gte2(this.t.agentTokenUsage.createdAt, start), lte3(this.t.agentTokenUsage.createdAt, end)));
3323
3487
  return rows.map(toUsageInput);
3324
3488
  }
3325
3489
  async spendByModel(range) {
@@ -3344,7 +3508,11 @@ var DrizzleGovernanceQueries = class {
3344
3508
  if (threadIds.length === 0) {
3345
3509
  return [];
3346
3510
  }
3347
- const threads = await this.db.select().from(this.t.agentThread).where(and3(inArray2(this.t.agentThread.id, threadIds), isNull3(this.t.agentThread.deletedAt)));
3511
+ const threads = await this.db.select({
3512
+ id: this.t.agentThread.id,
3513
+ title: this.t.agentThread.title,
3514
+ actorRef: this.t.agentThread.actorRef
3515
+ }).from(this.t.agentThread).where(and3(inArray2(this.t.agentThread.id, threadIds), isNull3(this.t.agentThread.deletedAt)));
3348
3516
  const threadsById = new Map(threads.map((thread) => [
3349
3517
  thread.id,
3350
3518
  {
@@ -3439,9 +3607,19 @@ var DrizzleGovernanceQueries = class {
3439
3607
  const threads = await this.db.select().from(this.t.agentThread).where(isNull3(this.t.agentThread.deletedAt)).orderBy(desc2(this.t.agentThread.updatedAt), desc2(this.t.agentThread.id)).limit(limit);
3440
3608
  return this.toThreadActivityRows(threads);
3441
3609
  }
3610
+ /** The range's runs, with only the columns the rollups read. */
3442
3611
  async runsInRange(range) {
3443
3612
  const { start, end } = dayBoundsUtc(range);
3444
- return this.db.select().from(this.t.agentRun).where(and3(gte2(this.t.agentRun.startedAt, start), lte3(this.t.agentRun.startedAt, end)));
3613
+ const run = this.t.agentRun;
3614
+ return this.db.select({
3615
+ agentName: run.agentName,
3616
+ status: run.status,
3617
+ retries: run.retries,
3618
+ durationMs: run.durationMs,
3619
+ ttftMs: run.ttftMs,
3620
+ parentRunId: run.parentRunId,
3621
+ startedAt: run.startedAt
3622
+ }).from(run).where(and3(gte2(run.startedAt, start), lte3(run.startedAt, end)));
3445
3623
  }
3446
3624
  async runMetrics(range) {
3447
3625
  const runs = await this.runsInRange(range);
@@ -3449,6 +3627,7 @@ var DrizzleGovernanceQueries = class {
3449
3627
  let failed = 0;
3450
3628
  let retries = 0;
3451
3629
  const settledDurationsMs = [];
3630
+ const ttftsMs = [];
3452
3631
  for (const run of runs) {
3453
3632
  if (run.status === "completed") {
3454
3633
  completed += 1;
@@ -3459,8 +3638,12 @@ var DrizzleGovernanceQueries = class {
3459
3638
  if ((run.status === "completed" || run.status === "failed") && run.durationMs != null) {
3460
3639
  settledDurationsMs.push(run.durationMs);
3461
3640
  }
3641
+ if (run.status === "completed" && run.parentRunId == null && run.ttftMs != null) {
3642
+ ttftsMs.push(run.ttftMs);
3643
+ }
3462
3644
  }
3463
3645
  settledDurationsMs.sort((left, right) => left - right);
3646
+ ttftsMs.sort((left, right) => left - right);
3464
3647
  return {
3465
3648
  runs: runs.length,
3466
3649
  completed,
@@ -3468,7 +3651,9 @@ var DrizzleGovernanceQueries = class {
3468
3651
  successRate: runs.length === 0 ? 0 : completed / runs.length,
3469
3652
  retries,
3470
3653
  durationP50Ms: percentileMs(settledDurationsMs, 0.5),
3471
- durationP95Ms: percentileMs(settledDurationsMs, 0.95)
3654
+ durationP95Ms: percentileMs(settledDurationsMs, 0.95),
3655
+ ttftP50Ms: percentileMs(ttftsMs, 0.5),
3656
+ ttftP95Ms: percentileMs(ttftsMs, 0.95)
3472
3657
  };
3473
3658
  }
3474
3659
  async runsByAgent(range) {
@@ -3498,7 +3683,9 @@ var DrizzleGovernanceQueries = class {
3498
3683
  }
3499
3684
  async runErrors(range) {
3500
3685
  const { start, end } = dayBoundsUtc(range);
3501
- const runs = await this.db.select().from(this.t.agentRun).where(and3(gte2(this.t.agentRun.startedAt, start), lte3(this.t.agentRun.startedAt, end), eq3(this.t.agentRun.status, "failed")));
3686
+ const runs = await this.db.select({
3687
+ errorCode: this.t.agentRun.errorCode
3688
+ }).from(this.t.agentRun).where(and3(gte2(this.t.agentRun.startedAt, start), lte3(this.t.agentRun.startedAt, end), eq3(this.t.agentRun.status, "failed")));
3502
3689
  const byError = /* @__PURE__ */ new Map();
3503
3690
  for (const run of runs) {
3504
3691
  const errorCode = run.errorCode ?? UNCLASSIFIED_ERROR_CODE;
@@ -3579,7 +3766,12 @@ var DrizzleGovernanceQueries = class {
3579
3766
  */
3580
3767
  async toolStats(range) {
3581
3768
  const { start, end } = dayBoundsUtc(range);
3582
- const calls = await this.db.select().from(this.t.agentToolCall).where(and3(gte2(this.t.agentToolCall.createdAt, start), lte3(this.t.agentToolCall.createdAt, end)));
3769
+ const calls = await this.db.select({
3770
+ toolName: this.t.agentToolCall.toolName,
3771
+ toolType: this.t.agentToolCall.toolType,
3772
+ status: this.t.agentToolCall.status,
3773
+ executionMs: this.t.agentToolCall.executionMs
3774
+ }).from(this.t.agentToolCall).where(and3(gte2(this.t.agentToolCall.createdAt, start), lte3(this.t.agentToolCall.createdAt, end)));
3583
3775
  const byTool = /* @__PURE__ */ new Map();
3584
3776
  for (const call of calls) {
3585
3777
  const key2 = `${call.toolName} ${call.toolType}`;