@dudousxd/nestjs-agent-store-drizzle 0.5.1 → 0.7.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
@@ -72,7 +72,9 @@ var agentToolCall = sqliteTable("agent_tool_call", {
72
72
  }).notNull(),
73
73
  executedAt: integer("executed_at", {
74
74
  mode: "timestamp_ms"
75
- })
75
+ }),
76
+ /** The run (turn) this call belongs to, for a trace deep-link; null for a pre-rollout row. */
77
+ runId: text("run_id")
76
78
  });
77
79
  var agentTokenUsage = sqliteTable("agent_token_usage", {
78
80
  id: text("id").primaryKey(),
@@ -187,7 +189,8 @@ var statements = [
187
189
  execution_ms INTEGER,
188
190
  error TEXT,
189
191
  created_at INTEGER NOT NULL,
190
- executed_at INTEGER
192
+ executed_at INTEGER,
193
+ run_id TEXT
191
194
  )`,
192
195
  `CREATE TABLE IF NOT EXISTS agent_token_usage (
193
196
  id TEXT PRIMARY KEY NOT NULL,
@@ -460,7 +463,8 @@ var DrizzleAgentStore = class {
460
463
  executionMs: null,
461
464
  error: null,
462
465
  createdAt: /* @__PURE__ */ new Date(),
463
- executedAt: null
466
+ executedAt: null,
467
+ runId: input.runId ?? null
464
468
  });
465
469
  }
466
470
  async updateToolCall(input) {
@@ -550,9 +554,61 @@ var DrizzleAgentStore = class {
550
554
 
551
555
  // src/drizzle-governance-queries.ts
552
556
  import { bucketByActor, bucketByModel, bucketByThread, bucketUsageTrend, dayBoundsUtc } from "@dudousxd/nestjs-agent-core";
553
- import { and as and2, count, desc as desc2, eq as eq2, gte as gte2, inArray as inArray2, isNull as isNull2, lte as lte2 } from "drizzle-orm";
557
+ import { and as and2, count, desc as desc2, eq as eq2, gte as gte2, inArray as inArray2, isNull as isNull2, like, lte as lte2, sql as sql3 } from "drizzle-orm";
554
558
  var UNCLASSIFIED_ERROR_CODE = "unknown";
555
559
  var DEFAULT_AGENT_BUCKET = "(default)";
560
+ var TOOL_CALL_STATUSES = [
561
+ "auto_executed",
562
+ "pending_approval",
563
+ "executed",
564
+ "rejected",
565
+ "failed"
566
+ ];
567
+ function isToolCallStatus(value) {
568
+ return TOOL_CALL_STATUSES.includes(value);
569
+ }
570
+ __name(isToolCallStatus, "isToolCallStatus");
571
+ var TOOL_KINDS = [
572
+ "read",
573
+ "action",
574
+ "agent"
575
+ ];
576
+ function isToolKind(value) {
577
+ return TOOL_KINDS.includes(value);
578
+ }
579
+ __name(isToolKind, "isToolKind");
580
+ var RUN_STATUSES = [
581
+ "running",
582
+ "completed",
583
+ "failed"
584
+ ];
585
+ function isRunStatus(value) {
586
+ return RUN_STATUSES.includes(value);
587
+ }
588
+ __name(isRunStatus, "isRunStatus");
589
+ function dayStartUtc(day) {
590
+ return dayBoundsUtc({
591
+ fromDay: day,
592
+ toDay: day
593
+ }).start;
594
+ }
595
+ __name(dayStartUtc, "dayStartUtc");
596
+ function dayEndUtc(day) {
597
+ return dayBoundsUtc({
598
+ fromDay: day,
599
+ toDay: day
600
+ }).end;
601
+ }
602
+ __name(dayEndUtc, "dayEndUtc");
603
+ function emptyPage(query) {
604
+ return {
605
+ rows: [],
606
+ total: 0,
607
+ page: query.page,
608
+ pageSize: query.pageSize
609
+ };
610
+ }
611
+ __name(emptyPage, "emptyPage");
556
612
  function toUsageInput(row) {
557
613
  return {
558
614
  modelId: row.modelId,
@@ -649,7 +705,8 @@ var DrizzleGovernanceQueries = class {
649
705
  toolType: agentToolCall.toolType,
650
706
  status: agentToolCall.status,
651
707
  threadId: agentMessage.threadId,
652
- createdAt: agentToolCall.createdAt
708
+ createdAt: agentToolCall.createdAt,
709
+ runId: agentToolCall.runId
653
710
  }).from(agentToolCall).innerJoin(agentMessage, eq2(agentToolCall.messageId, agentMessage.id)).orderBy(desc2(agentToolCall.createdAt), desc2(agentToolCall.id)).limit(limit);
654
711
  return rows.map((row) => ({
655
712
  toolCallId: row.toolCallId,
@@ -657,7 +714,8 @@ var DrizzleGovernanceQueries = class {
657
714
  toolType: row.toolType,
658
715
  status: row.status,
659
716
  threadId: row.threadId,
660
- createdAt: row.createdAt.toISOString()
717
+ createdAt: row.createdAt.toISOString(),
718
+ runId: row.runId
661
719
  }));
662
720
  }
663
721
  async recentThreads(limit) {
@@ -808,7 +866,8 @@ var DrizzleGovernanceQueries = class {
808
866
  threadTitle: agentThread.title,
809
867
  actorRef: agentThread.actorRef,
810
868
  agentName: agentMessage.agentName,
811
- requestedAt: agentToolCall.createdAt
869
+ requestedAt: agentToolCall.createdAt,
870
+ runId: agentToolCall.runId
812
871
  }).from(agentToolCall).innerJoin(agentMessage, eq2(agentToolCall.messageId, agentMessage.id)).innerJoin(agentThread, eq2(agentMessage.threadId, agentThread.id)).where(eq2(agentToolCall.status, "pending_approval")).orderBy(agentToolCall.createdAt, agentToolCall.id).limit(limit);
813
872
  return rows.map((row) => ({
814
873
  toolCallId: row.toolCallId,
@@ -818,7 +877,8 @@ var DrizzleGovernanceQueries = class {
818
877
  threadTitle: row.threadTitle,
819
878
  actorRef: row.actorRef,
820
879
  agentName: row.agentName ?? null,
821
- requestedAt: row.requestedAt.toISOString()
880
+ requestedAt: row.requestedAt.toISOString(),
881
+ runId: row.runId
822
882
  }));
823
883
  }
824
884
  /**
@@ -863,6 +923,117 @@ var DrizzleGovernanceQueries = class {
863
923
  result.sort((left, right) => right.calls - left.calls || left.toolName.localeCompare(right.toolName));
864
924
  return result;
865
925
  }
926
+ /**
927
+ * Paged, filterable tool-call activity, newest-first (same ordering as {@link recentToolCalls}).
928
+ * `toolType`/`status` values outside the known literals can never match a row — short-circuits to
929
+ * an empty page rather than issuing a query. Joins `agent_message` unconditionally (like
930
+ * `recentToolCalls`) to resolve `threadId` and to support the `threadId` filter.
931
+ */
932
+ async toolCallsPage(query) {
933
+ const filters = query.where;
934
+ if (filters?.status !== void 0 && !isToolCallStatus(filters.status)) {
935
+ return emptyPage(query);
936
+ }
937
+ if (filters?.toolType !== void 0 && !isToolKind(filters.toolType)) {
938
+ return emptyPage(query);
939
+ }
940
+ const whereClause = and2(filters?.toolName !== void 0 ? eq2(agentToolCall.toolName, filters.toolName) : void 0, filters?.toolType !== void 0 && isToolKind(filters.toolType) ? eq2(agentToolCall.toolType, filters.toolType) : void 0, filters?.status !== void 0 && isToolCallStatus(filters.status) ? eq2(agentToolCall.status, filters.status) : void 0, filters?.threadId !== void 0 ? eq2(agentMessage.threadId, filters.threadId) : void 0, filters?.fromDay !== void 0 ? gte2(agentToolCall.createdAt, dayStartUtc(filters.fromDay)) : void 0, filters?.toDay !== void 0 ? lte2(agentToolCall.createdAt, dayEndUtc(filters.toDay)) : void 0);
941
+ const [totalRow] = await this.db.select({
942
+ value: count()
943
+ }).from(agentToolCall).innerJoin(agentMessage, eq2(agentToolCall.messageId, agentMessage.id)).where(whereClause);
944
+ const rows = await this.db.select({
945
+ toolCallId: agentToolCall.id,
946
+ toolName: agentToolCall.toolName,
947
+ toolType: agentToolCall.toolType,
948
+ status: agentToolCall.status,
949
+ threadId: agentMessage.threadId,
950
+ createdAt: agentToolCall.createdAt,
951
+ runId: agentToolCall.runId
952
+ }).from(agentToolCall).innerJoin(agentMessage, eq2(agentToolCall.messageId, agentMessage.id)).where(whereClause).orderBy(desc2(agentToolCall.createdAt), desc2(agentToolCall.id)).limit(query.pageSize).offset((query.page - 1) * query.pageSize);
953
+ return {
954
+ rows: rows.map((row) => ({
955
+ toolCallId: row.toolCallId,
956
+ toolName: row.toolName,
957
+ toolType: row.toolType,
958
+ status: row.status,
959
+ threadId: row.threadId,
960
+ createdAt: row.createdAt.toISOString(),
961
+ runId: row.runId
962
+ })),
963
+ total: totalRow?.value ?? 0,
964
+ page: query.page,
965
+ pageSize: query.pageSize
966
+ };
967
+ }
968
+ /**
969
+ * Paged, filterable thread activity, newest-first (same ordering as {@link recentThreads}).
970
+ * `title` is a case-insensitive substring match via `lower(...)` on both sides (portable across
971
+ * dialects, regardless of the underlying column collation). Soft-deleted threads are excluded,
972
+ * mirroring `recentThreads`.
973
+ */
974
+ async threadsPage(query) {
975
+ const filters = query.where;
976
+ const whereClause = and2(isNull2(agentThread.deletedAt), filters?.actorRef !== void 0 ? eq2(agentThread.actorRef, filters.actorRef) : void 0, filters?.title !== void 0 ? like(sql3`lower(${agentThread.title})`, `%${filters.title.toLowerCase()}%`) : void 0, filters?.fromDay !== void 0 ? gte2(agentThread.updatedAt, dayStartUtc(filters.fromDay)) : void 0, filters?.toDay !== void 0 ? lte2(agentThread.updatedAt, dayEndUtc(filters.toDay)) : void 0);
977
+ const [totalRow] = await this.db.select({
978
+ value: count()
979
+ }).from(agentThread).where(whereClause);
980
+ const threads = await this.db.select().from(agentThread).where(whereClause).orderBy(desc2(agentThread.updatedAt), desc2(agentThread.id)).limit(query.pageSize).offset((query.page - 1) * query.pageSize);
981
+ const rows = [];
982
+ for (const thread of threads) {
983
+ const [messageCountRow] = await this.db.select({
984
+ value: count()
985
+ }).from(agentMessage).where(eq2(agentMessage.threadId, thread.id));
986
+ const usageRows = await this.db.select().from(agentTokenUsage).where(eq2(agentTokenUsage.threadId, thread.id));
987
+ const totalTokens = usageRows.reduce((sum, row) => sum + row.inputTokens + row.outputTokens, 0);
988
+ rows.push({
989
+ threadId: thread.id,
990
+ title: thread.title,
991
+ actorRef: thread.actorRef,
992
+ messageCount: messageCountRow?.value ?? 0,
993
+ totalTokens,
994
+ lastActivityAt: thread.updatedAt.toISOString()
995
+ });
996
+ }
997
+ return {
998
+ rows,
999
+ total: totalRow?.value ?? 0,
1000
+ page: query.page,
1001
+ pageSize: query.pageSize
1002
+ };
1003
+ }
1004
+ /**
1005
+ * Paged, filterable run activity, newest-first (same ordering as {@link recentRuns}). `status`
1006
+ * values outside the known literals can never match a row — short-circuits to an empty page.
1007
+ */
1008
+ async runsPage(query) {
1009
+ const filters = query.where;
1010
+ if (filters?.status !== void 0 && !isRunStatus(filters.status)) {
1011
+ return emptyPage(query);
1012
+ }
1013
+ const whereClause = and2(filters?.agentName !== void 0 ? eq2(agentRun.agentName, filters.agentName) : void 0, filters?.status !== void 0 && isRunStatus(filters.status) ? eq2(agentRun.status, filters.status) : void 0, filters?.errorCode !== void 0 ? eq2(agentRun.errorCode, filters.errorCode) : void 0, filters?.threadId !== void 0 ? eq2(agentRun.threadId, filters.threadId) : void 0, filters?.fromDay !== void 0 ? gte2(agentRun.startedAt, dayStartUtc(filters.fromDay)) : void 0, filters?.toDay !== void 0 ? lte2(agentRun.startedAt, dayEndUtc(filters.toDay)) : void 0);
1014
+ const [totalRow] = await this.db.select({
1015
+ value: count()
1016
+ }).from(agentRun).where(whereClause);
1017
+ const runs = await this.db.select().from(agentRun).where(whereClause).orderBy(desc2(agentRun.startedAt), desc2(agentRun.id)).limit(query.pageSize).offset((query.page - 1) * query.pageSize);
1018
+ return {
1019
+ rows: runs.map((run) => ({
1020
+ runId: run.id,
1021
+ threadId: run.threadId,
1022
+ actorRef: run.actorRef,
1023
+ agentName: run.agentName ?? null,
1024
+ status: run.status,
1025
+ durationMs: run.durationMs ?? null,
1026
+ errorCode: run.errorCode ?? null,
1027
+ errorMessage: run.errorMessage ?? null,
1028
+ retries: run.retries,
1029
+ startedAt: run.startedAt.toISOString(),
1030
+ promptHash: run.promptHash ?? null
1031
+ })),
1032
+ total: totalRow?.value ?? 0,
1033
+ page: query.page,
1034
+ pageSize: query.pageSize
1035
+ };
1036
+ }
866
1037
  };
867
1038
 
868
1039
  // src/drizzle-pricing-store.ts