@dudousxd/nestjs-agent-store-drizzle 0.18.3 → 0.19.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
@@ -16,6 +16,10 @@ var agentThread = sqliteTable("agent_thread", {
16
16
  defaultAgent: text("default_agent"),
17
17
  /** The model every turn on this thread runs on unless the send names one; `null` → default. */
18
18
  model: text("model"),
19
+ /** Why the thread's message queue stopped draining; `null` → it drains. */
20
+ queuePause: text("queue_pause", {
21
+ mode: "json"
22
+ }).$type(),
19
23
  createdAt: integer("created_at", {
20
24
  mode: "timestamp_ms"
21
25
  }).notNull(),
@@ -109,6 +113,37 @@ var agentToolCall = sqliteTable("agent_tool_call", {
109
113
  }, (table) => [
110
114
  index("agent_tool_call_message_idx").on(table.messageId)
111
115
  ]);
116
+ var agentQueuedMessage = sqliteTable("agent_queued_message", {
117
+ id: text("id").primaryKey(),
118
+ threadId: text("thread_id").notNull().references(() => agentThread.id, {
119
+ onDelete: "cascade"
120
+ }),
121
+ /** Who queued it — the turn it starts runs as this actor. */
122
+ actor: text("actor", {
123
+ mode: "json"
124
+ }).$type().notNull(),
125
+ content: text("content").notNull(),
126
+ attachments: text("attachments", {
127
+ mode: "json"
128
+ }).$type(),
129
+ agentName: text("agent_name"),
130
+ model: text("model"),
131
+ pageContext: text("page_context", {
132
+ mode: "json"
133
+ }).$type(),
134
+ interrupt: integer("interrupt", {
135
+ mode: "boolean"
136
+ }).notNull().default(false),
137
+ position: integer("position").notNull(),
138
+ createdAt: integer("created_at", {
139
+ mode: "timestamp_ms"
140
+ }).notNull(),
141
+ updatedAt: integer("updated_at", {
142
+ mode: "timestamp_ms"
143
+ }).notNull()
144
+ }, (table) => [
145
+ index("agent_queued_message_thread_position_idx").on(table.threadId, table.position)
146
+ ]);
112
147
  var agentTokenUsage = sqliteTable("agent_token_usage", {
113
148
  id: text("id").primaryKey(),
114
149
  threadId: text("thread_id").notNull().references(() => agentThread.id, {
@@ -234,6 +269,7 @@ var ragIngestionLog = sqliteTable("rag_ingestion_log", {
234
269
  var agentSchema = {
235
270
  agentThread,
236
271
  agentMessage,
272
+ agentQueuedMessage,
237
273
  agentToolCall,
238
274
  agentTokenUsage,
239
275
  agentModelPricing,
@@ -254,6 +290,7 @@ var statements = [
254
290
  active_stream_id TEXT,
255
291
  default_agent TEXT,
256
292
  model TEXT,
293
+ queue_pause TEXT,
257
294
  created_at INTEGER NOT NULL,
258
295
  updated_at INTEGER NOT NULL,
259
296
  deleted_at INTEGER
@@ -280,6 +317,22 @@ var statements = [
280
317
  )`,
281
318
  `CREATE INDEX IF NOT EXISTS agent_message_thread_created_idx
282
319
  ON agent_message (thread_id, created_at)`,
320
+ `CREATE TABLE IF NOT EXISTS agent_queued_message (
321
+ id TEXT PRIMARY KEY NOT NULL,
322
+ thread_id TEXT NOT NULL REFERENCES agent_thread(id) ON DELETE CASCADE,
323
+ actor TEXT NOT NULL,
324
+ content TEXT NOT NULL,
325
+ attachments TEXT,
326
+ agent_name TEXT,
327
+ model TEXT,
328
+ page_context TEXT,
329
+ interrupt INTEGER NOT NULL DEFAULT 0,
330
+ position INTEGER NOT NULL,
331
+ created_at INTEGER NOT NULL,
332
+ updated_at INTEGER NOT NULL
333
+ )`,
334
+ `CREATE INDEX IF NOT EXISTS agent_queued_message_thread_position_idx
335
+ ON agent_queued_message (thread_id, position)`,
283
336
  `CREATE TABLE IF NOT EXISTS agent_tool_call (
284
337
  id TEXT PRIMARY KEY NOT NULL,
285
338
  message_id TEXT NOT NULL REFERENCES agent_message(id) ON DELETE CASCADE,
@@ -440,6 +493,11 @@ var additiveColumns = [
440
493
  column: "remember",
441
494
  ddl: "ALTER TABLE agent_tool_call ADD COLUMN remember INTEGER"
442
495
  },
496
+ {
497
+ table: "agent_thread",
498
+ column: "queue_pause",
499
+ ddl: "ALTER TABLE agent_thread ADD COLUMN queue_pause TEXT"
500
+ },
443
501
  {
444
502
  table: "agent_tool_call",
445
503
  column: "decided_via",
@@ -461,7 +519,7 @@ __name(ensureAgentSchema, "ensureAgentSchema");
461
519
 
462
520
  // src/drizzle-agent-store.ts
463
521
  import { toolCallApprovalFromRow } from "@dudousxd/nestjs-agent-core";
464
- import { and, asc, desc, eq, gte, inArray, isNotNull, isNull, lte, sql as sql2 } from "drizzle-orm";
522
+ import { and, asc, desc, eq, gte, inArray, isNotNull, isNull, lte, max, min, or, sql as sql2 } from "drizzle-orm";
465
523
  var TURN_MESSAGE_COLUMNS = {
466
524
  id: agentMessage.id,
467
525
  role: agentMessage.role,
@@ -491,6 +549,7 @@ var DrizzleAgentStore = class {
491
549
  activeStreamId: null,
492
550
  defaultAgent: null,
493
551
  model: null,
552
+ queuePause: null,
494
553
  createdAt: now,
495
554
  updatedAt: now,
496
555
  deletedAt: null
@@ -597,6 +656,8 @@ var DrizzleAgentStore = class {
597
656
  activeStreamId: null,
598
657
  defaultAgent: source.defaultAgent,
599
658
  model: source.model,
659
+ // A fork is a new conversation: nothing is waiting on it, and nothing is paused.
660
+ queuePause: null,
600
661
  createdAt: now,
601
662
  updatedAt: now,
602
663
  deletedAt: null
@@ -726,6 +787,124 @@ var DrizzleAgentStore = class {
726
787
  updatedAt: /* @__PURE__ */ new Date()
727
788
  }).where(and(eq(agentThread.id, threadId), eq(agentThread.transient, true)));
728
789
  }
790
+ /** The run streaming the thread, or `null` — a projection of the one column. */
791
+ async activeRunForThread(threadId) {
792
+ const [thread] = await this.db.select({
793
+ activeStreamId: agentThread.activeStreamId
794
+ }).from(agentThread).where(and(eq(agentThread.id, threadId), isNull(agentThread.deletedAt)));
795
+ return thread?.activeStreamId ?? null;
796
+ }
797
+ /**
798
+ * One conditional UPDATE, so of two racing claims exactly one changes the row: set the holder to
799
+ * `runId` only when there is none, it is already `runId`, or it is `replacing`.
800
+ */
801
+ async claimActiveStream(threadId, runId, options = {}) {
802
+ const holders = [
803
+ isNull(agentThread.activeStreamId),
804
+ eq(agentThread.activeStreamId, runId),
805
+ ...options.replacing !== void 0 ? [
806
+ eq(agentThread.activeStreamId, options.replacing)
807
+ ] : []
808
+ ];
809
+ const claimed = await this.db.update(agentThread).set({
810
+ activeStreamId: runId
811
+ }).where(and(eq(agentThread.id, threadId), or(...holders))).returning({
812
+ id: agentThread.id
813
+ });
814
+ return claimed.length > 0;
815
+ }
816
+ async releaseActiveStream(threadId, runId) {
817
+ const released = await this.db.update(agentThread).set({
818
+ activeStreamId: null
819
+ }).where(and(eq(agentThread.id, threadId), eq(agentThread.activeStreamId, runId))).returning({
820
+ id: agentThread.id
821
+ });
822
+ return released.length > 0;
823
+ }
824
+ async enqueueMessage(input) {
825
+ const now = /* @__PURE__ */ new Date();
826
+ const [edge] = await this.db.select({
827
+ first: min(agentQueuedMessage.position),
828
+ last: max(agentQueuedMessage.position)
829
+ }).from(agentQueuedMessage).where(eq(agentQueuedMessage.threadId, input.threadId));
830
+ const position = input.at === "head" ? (edge?.first ?? 1) - 1 : (edge?.last ?? -1) + 1;
831
+ const row = {
832
+ id: crypto.randomUUID(),
833
+ threadId: input.threadId,
834
+ actor: input.actor,
835
+ content: input.content,
836
+ attachments: input.attachments !== void 0 && input.attachments.length > 0 ? input.attachments : null,
837
+ agentName: input.agentName ?? null,
838
+ model: input.model ?? null,
839
+ pageContext: input.pageContext ?? null,
840
+ interrupt: input.interrupt === true,
841
+ position,
842
+ createdAt: now,
843
+ updatedAt: now
844
+ };
845
+ await this.db.insert(agentQueuedMessage).values(row);
846
+ return toQueuedMessage(row);
847
+ }
848
+ async listQueue(threadId) {
849
+ const rows = await this.db.select().from(agentQueuedMessage).where(eq(agentQueuedMessage.threadId, threadId)).orderBy(asc(agentQueuedMessage.position), asc(agentQueuedMessage.createdAt));
850
+ return rows.map(toQueuedMessage);
851
+ }
852
+ async getQueuedMessage(id) {
853
+ const [row] = await this.db.select().from(agentQueuedMessage).where(eq(agentQueuedMessage.id, id));
854
+ return row === void 0 ? null : toQueuedMessage(row);
855
+ }
856
+ async updateQueuedMessage(id, patch) {
857
+ const updates = {
858
+ updatedAt: /* @__PURE__ */ new Date()
859
+ };
860
+ if (patch.content !== void 0) {
861
+ updates.content = patch.content;
862
+ }
863
+ if (patch.attachments !== void 0) {
864
+ updates.attachments = patch.attachments === null || patch.attachments.length === 0 ? null : patch.attachments;
865
+ }
866
+ await this.db.update(agentQueuedMessage).set(updates).where(eq(agentQueuedMessage.id, id));
867
+ return this.getQueuedMessage(id);
868
+ }
869
+ /** Rewrites the thread's positions as 0..n-1 in the new order — a queue is a handful of rows. */
870
+ async moveQueuedMessage(id, index2) {
871
+ const moving = await this.getQueuedMessage(id);
872
+ if (moving === null) {
873
+ return false;
874
+ }
875
+ const order = (await this.listQueue(moving.threadId)).filter((message) => message.id !== id);
876
+ const target = Math.max(0, Math.min(order.length, Math.trunc(index2)));
877
+ order.splice(target, 0, moving);
878
+ for (const [position, message] of order.entries()) {
879
+ await this.db.update(agentQueuedMessage).set({
880
+ position
881
+ }).where(eq(agentQueuedMessage.id, message.id));
882
+ }
883
+ return true;
884
+ }
885
+ async removeQueuedMessage(id) {
886
+ const removed = await this.db.delete(agentQueuedMessage).where(eq(agentQueuedMessage.id, id)).returning({
887
+ id: agentQueuedMessage.id
888
+ });
889
+ return removed.length > 0;
890
+ }
891
+ async clearQueue(threadId) {
892
+ const removed = await this.db.delete(agentQueuedMessage).where(eq(agentQueuedMessage.threadId, threadId)).returning({
893
+ id: agentQueuedMessage.id
894
+ });
895
+ return removed.length;
896
+ }
897
+ async queuePause(threadId) {
898
+ const [thread] = await this.db.select({
899
+ queuePause: agentThread.queuePause
900
+ }).from(agentThread).where(eq(agentThread.id, threadId));
901
+ return thread?.queuePause ?? null;
902
+ }
903
+ async setQueuePause(threadId, pause) {
904
+ await this.db.update(agentThread).set({
905
+ queuePause: pause
906
+ }).where(eq(agentThread.id, threadId));
907
+ }
729
908
  async setActiveStream(threadId, runId) {
730
909
  await this.db.update(agentThread).set({
731
910
  activeStreamId: runId
@@ -1049,6 +1228,32 @@ var DrizzleAgentStore = class {
1049
1228
  };
1050
1229
  }
1051
1230
  };
1231
+ function toQueuedMessage(row) {
1232
+ return {
1233
+ id: row.id,
1234
+ threadId: row.threadId,
1235
+ actor: row.actor,
1236
+ content: row.content,
1237
+ ...row.attachments !== null && row.attachments.length > 0 ? {
1238
+ attachments: row.attachments
1239
+ } : {},
1240
+ ...row.agentName !== null ? {
1241
+ agentName: row.agentName
1242
+ } : {},
1243
+ ...row.model !== null ? {
1244
+ model: row.model
1245
+ } : {},
1246
+ ...row.pageContext !== null ? {
1247
+ pageContext: row.pageContext
1248
+ } : {},
1249
+ ...row.interrupt ? {
1250
+ interrupt: true
1251
+ } : {},
1252
+ createdAt: row.createdAt.toISOString(),
1253
+ updatedAt: row.updatedAt.toISOString()
1254
+ };
1255
+ }
1256
+ __name(toQueuedMessage, "toQueuedMessage");
1052
1257
 
1053
1258
  // src/drizzle-governance-queries.ts
1054
1259
  import { bucketByActor, bucketByModel, bucketByThread, bucketUsageTrend, dayBoundsUtc, rollupThreadUsage, truncateDetailContent } from "@dudousxd/nestjs-agent-core";
@@ -1889,7 +2094,7 @@ var DrizzlePricingStore = class {
1889
2094
  // src/drizzle-rag-ingestion-log.ts
1890
2095
  import { subscribe, unsubscribe } from "diagnostics_channel";
1891
2096
  import { Injectable, Logger } from "@nestjs/common";
1892
- import { and as and5, asc as asc3, count as count2, desc as desc3, eq as eq5, gt, lt, or } from "drizzle-orm";
2097
+ import { and as and5, asc as asc3, count as count2, desc as desc3, eq as eq5, gt, lt, or as or2 } from "drizzle-orm";
1893
2098
  function _ts_decorate(decorators, target, key, desc4) {
1894
2099
  var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
1895
2100
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -2140,7 +2345,7 @@ var DrizzleRagIngestionLog = class _DrizzleRagIngestionLog {
2140
2345
  if (cursor === void 0) {
2141
2346
  return base;
2142
2347
  }
2143
- return and5(base, or(lt(ragIngestionLog.updatedAt, cursor.updatedAt), and5(eq5(ragIngestionLog.updatedAt, cursor.updatedAt), gt(ragIngestionLog.documentId, cursor.documentId))));
2348
+ return and5(base, or2(lt(ragIngestionLog.updatedAt, cursor.updatedAt), and5(eq5(ragIngestionLog.updatedAt, cursor.updatedAt), gt(ragIngestionLog.documentId, cursor.documentId))));
2144
2349
  }
2145
2350
  /**
2146
2351
  * Upsert one outcome. Keyed by document id so the row always reflects the *current* state — a
@@ -2317,6 +2522,7 @@ export {
2317
2522
  agentMemory,
2318
2523
  agentMessage,
2319
2524
  agentModelPricing,
2525
+ agentQueuedMessage,
2320
2526
  agentRun,
2321
2527
  agentSchema,
2322
2528
  agentThread,