@dudousxd/nestjs-agent-store-drizzle 0.18.3 → 0.19.1

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,127 @@ 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
+ if (patch.interrupt !== void 0) {
867
+ updates.interrupt = patch.interrupt;
868
+ }
869
+ await this.db.update(agentQueuedMessage).set(updates).where(eq(agentQueuedMessage.id, id));
870
+ return this.getQueuedMessage(id);
871
+ }
872
+ /** Rewrites the thread's positions as 0..n-1 in the new order — a queue is a handful of rows. */
873
+ async moveQueuedMessage(id, index2) {
874
+ const moving = await this.getQueuedMessage(id);
875
+ if (moving === null) {
876
+ return false;
877
+ }
878
+ const order = (await this.listQueue(moving.threadId)).filter((message) => message.id !== id);
879
+ const target = Math.max(0, Math.min(order.length, Math.trunc(index2)));
880
+ order.splice(target, 0, moving);
881
+ for (const [position, message] of order.entries()) {
882
+ await this.db.update(agentQueuedMessage).set({
883
+ position
884
+ }).where(eq(agentQueuedMessage.id, message.id));
885
+ }
886
+ return true;
887
+ }
888
+ async removeQueuedMessage(id) {
889
+ const removed = await this.db.delete(agentQueuedMessage).where(eq(agentQueuedMessage.id, id)).returning({
890
+ id: agentQueuedMessage.id
891
+ });
892
+ return removed.length > 0;
893
+ }
894
+ async clearQueue(threadId) {
895
+ const removed = await this.db.delete(agentQueuedMessage).where(eq(agentQueuedMessage.threadId, threadId)).returning({
896
+ id: agentQueuedMessage.id
897
+ });
898
+ return removed.length;
899
+ }
900
+ async queuePause(threadId) {
901
+ const [thread] = await this.db.select({
902
+ queuePause: agentThread.queuePause
903
+ }).from(agentThread).where(eq(agentThread.id, threadId));
904
+ return thread?.queuePause ?? null;
905
+ }
906
+ async setQueuePause(threadId, pause) {
907
+ await this.db.update(agentThread).set({
908
+ queuePause: pause
909
+ }).where(eq(agentThread.id, threadId));
910
+ }
729
911
  async setActiveStream(threadId, runId) {
730
912
  await this.db.update(agentThread).set({
731
913
  activeStreamId: runId
@@ -1049,6 +1231,32 @@ var DrizzleAgentStore = class {
1049
1231
  };
1050
1232
  }
1051
1233
  };
1234
+ function toQueuedMessage(row) {
1235
+ return {
1236
+ id: row.id,
1237
+ threadId: row.threadId,
1238
+ actor: row.actor,
1239
+ content: row.content,
1240
+ ...row.attachments !== null && row.attachments.length > 0 ? {
1241
+ attachments: row.attachments
1242
+ } : {},
1243
+ ...row.agentName !== null ? {
1244
+ agentName: row.agentName
1245
+ } : {},
1246
+ ...row.model !== null ? {
1247
+ model: row.model
1248
+ } : {},
1249
+ ...row.pageContext !== null ? {
1250
+ pageContext: row.pageContext
1251
+ } : {},
1252
+ ...row.interrupt ? {
1253
+ interrupt: true
1254
+ } : {},
1255
+ createdAt: row.createdAt.toISOString(),
1256
+ updatedAt: row.updatedAt.toISOString()
1257
+ };
1258
+ }
1259
+ __name(toQueuedMessage, "toQueuedMessage");
1052
1260
 
1053
1261
  // src/drizzle-governance-queries.ts
1054
1262
  import { bucketByActor, bucketByModel, bucketByThread, bucketUsageTrend, dayBoundsUtc, rollupThreadUsage, truncateDetailContent } from "@dudousxd/nestjs-agent-core";
@@ -1889,7 +2097,7 @@ var DrizzlePricingStore = class {
1889
2097
  // src/drizzle-rag-ingestion-log.ts
1890
2098
  import { subscribe, unsubscribe } from "diagnostics_channel";
1891
2099
  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";
2100
+ 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
2101
  function _ts_decorate(decorators, target, key, desc4) {
1894
2102
  var c = arguments.length, r = c < 3 ? target : desc4 === null ? desc4 = Object.getOwnPropertyDescriptor(target, key) : desc4, d;
1895
2103
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -2140,7 +2348,7 @@ var DrizzleRagIngestionLog = class _DrizzleRagIngestionLog {
2140
2348
  if (cursor === void 0) {
2141
2349
  return base;
2142
2350
  }
2143
- return and5(base, or(lt(ragIngestionLog.updatedAt, cursor.updatedAt), and5(eq5(ragIngestionLog.updatedAt, cursor.updatedAt), gt(ragIngestionLog.documentId, cursor.documentId))));
2351
+ return and5(base, or2(lt(ragIngestionLog.updatedAt, cursor.updatedAt), and5(eq5(ragIngestionLog.updatedAt, cursor.updatedAt), gt(ragIngestionLog.documentId, cursor.documentId))));
2144
2352
  }
2145
2353
  /**
2146
2354
  * Upsert one outcome. Keyed by document id so the row always reflects the *current* state — a
@@ -2317,6 +2525,7 @@ export {
2317
2525
  agentMemory,
2318
2526
  agentMessage,
2319
2527
  agentModelPricing,
2528
+ agentQueuedMessage,
2320
2529
  agentRun,
2321
2530
  agentSchema,
2322
2531
  agentThread,