@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/README.md CHANGED
@@ -124,6 +124,23 @@ ALTER TABLE agent_tool_call ADD COLUMN expires_at INTEGER;
124
124
  ALTER TABLE agent_tool_call ADD COLUMN remember INTEGER;
125
125
  ALTER TABLE agent_tool_call ADD COLUMN decided_via TEXT;
126
126
  CREATE INDEX agent_tool_call_message_idx ON agent_tool_call (message_id);
127
+ -- the chat message queue
128
+ ALTER TABLE agent_thread ADD COLUMN queue_pause TEXT;
129
+ CREATE TABLE agent_queued_message (
130
+ id TEXT PRIMARY KEY NOT NULL,
131
+ thread_id TEXT NOT NULL REFERENCES agent_thread(id) ON DELETE CASCADE,
132
+ actor TEXT NOT NULL,
133
+ content TEXT NOT NULL,
134
+ attachments TEXT,
135
+ agent_name TEXT,
136
+ model TEXT,
137
+ page_context TEXT,
138
+ interrupt INTEGER NOT NULL DEFAULT 0,
139
+ position INTEGER NOT NULL,
140
+ created_at INTEGER NOT NULL,
141
+ updated_at INTEGER NOT NULL
142
+ );
143
+ CREATE INDEX agent_queued_message_thread_position_idx ON agent_queued_message (thread_id, position);
127
144
  ```
128
145
 
129
146
  The index is not optional on Postgres: `agent_tool_call.message_id` carries a foreign key, and
package/dist/index.cjs CHANGED
@@ -31,6 +31,7 @@ __export(index_exports, {
31
31
  agentMemory: () => agentMemory,
32
32
  agentMessage: () => agentMessage,
33
33
  agentModelPricing: () => agentModelPricing,
34
+ agentQueuedMessage: () => agentQueuedMessage,
34
35
  agentRun: () => agentRun,
35
36
  agentSchema: () => agentSchema,
36
37
  agentThread: () => agentThread,
@@ -56,6 +57,10 @@ var agentThread = (0, import_sqlite_core.sqliteTable)("agent_thread", {
56
57
  defaultAgent: (0, import_sqlite_core.text)("default_agent"),
57
58
  /** The model every turn on this thread runs on unless the send names one; `null` → default. */
58
59
  model: (0, import_sqlite_core.text)("model"),
60
+ /** Why the thread's message queue stopped draining; `null` → it drains. */
61
+ queuePause: (0, import_sqlite_core.text)("queue_pause", {
62
+ mode: "json"
63
+ }).$type(),
59
64
  createdAt: (0, import_sqlite_core.integer)("created_at", {
60
65
  mode: "timestamp_ms"
61
66
  }).notNull(),
@@ -149,6 +154,37 @@ var agentToolCall = (0, import_sqlite_core.sqliteTable)("agent_tool_call", {
149
154
  }, (table) => [
150
155
  (0, import_sqlite_core.index)("agent_tool_call_message_idx").on(table.messageId)
151
156
  ]);
157
+ var agentQueuedMessage = (0, import_sqlite_core.sqliteTable)("agent_queued_message", {
158
+ id: (0, import_sqlite_core.text)("id").primaryKey(),
159
+ threadId: (0, import_sqlite_core.text)("thread_id").notNull().references(() => agentThread.id, {
160
+ onDelete: "cascade"
161
+ }),
162
+ /** Who queued it — the turn it starts runs as this actor. */
163
+ actor: (0, import_sqlite_core.text)("actor", {
164
+ mode: "json"
165
+ }).$type().notNull(),
166
+ content: (0, import_sqlite_core.text)("content").notNull(),
167
+ attachments: (0, import_sqlite_core.text)("attachments", {
168
+ mode: "json"
169
+ }).$type(),
170
+ agentName: (0, import_sqlite_core.text)("agent_name"),
171
+ model: (0, import_sqlite_core.text)("model"),
172
+ pageContext: (0, import_sqlite_core.text)("page_context", {
173
+ mode: "json"
174
+ }).$type(),
175
+ interrupt: (0, import_sqlite_core.integer)("interrupt", {
176
+ mode: "boolean"
177
+ }).notNull().default(false),
178
+ position: (0, import_sqlite_core.integer)("position").notNull(),
179
+ createdAt: (0, import_sqlite_core.integer)("created_at", {
180
+ mode: "timestamp_ms"
181
+ }).notNull(),
182
+ updatedAt: (0, import_sqlite_core.integer)("updated_at", {
183
+ mode: "timestamp_ms"
184
+ }).notNull()
185
+ }, (table) => [
186
+ (0, import_sqlite_core.index)("agent_queued_message_thread_position_idx").on(table.threadId, table.position)
187
+ ]);
152
188
  var agentTokenUsage = (0, import_sqlite_core.sqliteTable)("agent_token_usage", {
153
189
  id: (0, import_sqlite_core.text)("id").primaryKey(),
154
190
  threadId: (0, import_sqlite_core.text)("thread_id").notNull().references(() => agentThread.id, {
@@ -274,6 +310,7 @@ var ragIngestionLog = (0, import_sqlite_core.sqliteTable)("rag_ingestion_log", {
274
310
  var agentSchema = {
275
311
  agentThread,
276
312
  agentMessage,
313
+ agentQueuedMessage,
277
314
  agentToolCall,
278
315
  agentTokenUsage,
279
316
  agentModelPricing,
@@ -294,6 +331,7 @@ var statements = [
294
331
  active_stream_id TEXT,
295
332
  default_agent TEXT,
296
333
  model TEXT,
334
+ queue_pause TEXT,
297
335
  created_at INTEGER NOT NULL,
298
336
  updated_at INTEGER NOT NULL,
299
337
  deleted_at INTEGER
@@ -320,6 +358,22 @@ var statements = [
320
358
  )`,
321
359
  `CREATE INDEX IF NOT EXISTS agent_message_thread_created_idx
322
360
  ON agent_message (thread_id, created_at)`,
361
+ `CREATE TABLE IF NOT EXISTS agent_queued_message (
362
+ id TEXT PRIMARY KEY NOT NULL,
363
+ thread_id TEXT NOT NULL REFERENCES agent_thread(id) ON DELETE CASCADE,
364
+ actor TEXT NOT NULL,
365
+ content TEXT NOT NULL,
366
+ attachments TEXT,
367
+ agent_name TEXT,
368
+ model TEXT,
369
+ page_context TEXT,
370
+ interrupt INTEGER NOT NULL DEFAULT 0,
371
+ position INTEGER NOT NULL,
372
+ created_at INTEGER NOT NULL,
373
+ updated_at INTEGER NOT NULL
374
+ )`,
375
+ `CREATE INDEX IF NOT EXISTS agent_queued_message_thread_position_idx
376
+ ON agent_queued_message (thread_id, position)`,
323
377
  `CREATE TABLE IF NOT EXISTS agent_tool_call (
324
378
  id TEXT PRIMARY KEY NOT NULL,
325
379
  message_id TEXT NOT NULL REFERENCES agent_message(id) ON DELETE CASCADE,
@@ -480,6 +534,11 @@ var additiveColumns = [
480
534
  column: "remember",
481
535
  ddl: "ALTER TABLE agent_tool_call ADD COLUMN remember INTEGER"
482
536
  },
537
+ {
538
+ table: "agent_thread",
539
+ column: "queue_pause",
540
+ ddl: "ALTER TABLE agent_thread ADD COLUMN queue_pause TEXT"
541
+ },
483
542
  {
484
543
  table: "agent_tool_call",
485
544
  column: "decided_via",
@@ -531,6 +590,7 @@ var DrizzleAgentStore = class {
531
590
  activeStreamId: null,
532
591
  defaultAgent: null,
533
592
  model: null,
593
+ queuePause: null,
534
594
  createdAt: now,
535
595
  updatedAt: now,
536
596
  deletedAt: null
@@ -637,6 +697,8 @@ var DrizzleAgentStore = class {
637
697
  activeStreamId: null,
638
698
  defaultAgent: source.defaultAgent,
639
699
  model: source.model,
700
+ // A fork is a new conversation: nothing is waiting on it, and nothing is paused.
701
+ queuePause: null,
640
702
  createdAt: now,
641
703
  updatedAt: now,
642
704
  deletedAt: null
@@ -766,6 +828,124 @@ var DrizzleAgentStore = class {
766
828
  updatedAt: /* @__PURE__ */ new Date()
767
829
  }).where((0, import_drizzle_orm2.and)((0, import_drizzle_orm2.eq)(agentThread.id, threadId), (0, import_drizzle_orm2.eq)(agentThread.transient, true)));
768
830
  }
831
+ /** The run streaming the thread, or `null` — a projection of the one column. */
832
+ async activeRunForThread(threadId) {
833
+ const [thread] = await this.db.select({
834
+ activeStreamId: agentThread.activeStreamId
835
+ }).from(agentThread).where((0, import_drizzle_orm2.and)((0, import_drizzle_orm2.eq)(agentThread.id, threadId), (0, import_drizzle_orm2.isNull)(agentThread.deletedAt)));
836
+ return thread?.activeStreamId ?? null;
837
+ }
838
+ /**
839
+ * One conditional UPDATE, so of two racing claims exactly one changes the row: set the holder to
840
+ * `runId` only when there is none, it is already `runId`, or it is `replacing`.
841
+ */
842
+ async claimActiveStream(threadId, runId, options = {}) {
843
+ const holders = [
844
+ (0, import_drizzle_orm2.isNull)(agentThread.activeStreamId),
845
+ (0, import_drizzle_orm2.eq)(agentThread.activeStreamId, runId),
846
+ ...options.replacing !== void 0 ? [
847
+ (0, import_drizzle_orm2.eq)(agentThread.activeStreamId, options.replacing)
848
+ ] : []
849
+ ];
850
+ const claimed = await this.db.update(agentThread).set({
851
+ activeStreamId: runId
852
+ }).where((0, import_drizzle_orm2.and)((0, import_drizzle_orm2.eq)(agentThread.id, threadId), (0, import_drizzle_orm2.or)(...holders))).returning({
853
+ id: agentThread.id
854
+ });
855
+ return claimed.length > 0;
856
+ }
857
+ async releaseActiveStream(threadId, runId) {
858
+ const released = await this.db.update(agentThread).set({
859
+ activeStreamId: null
860
+ }).where((0, import_drizzle_orm2.and)((0, import_drizzle_orm2.eq)(agentThread.id, threadId), (0, import_drizzle_orm2.eq)(agentThread.activeStreamId, runId))).returning({
861
+ id: agentThread.id
862
+ });
863
+ return released.length > 0;
864
+ }
865
+ async enqueueMessage(input) {
866
+ const now = /* @__PURE__ */ new Date();
867
+ const [edge] = await this.db.select({
868
+ first: (0, import_drizzle_orm2.min)(agentQueuedMessage.position),
869
+ last: (0, import_drizzle_orm2.max)(agentQueuedMessage.position)
870
+ }).from(agentQueuedMessage).where((0, import_drizzle_orm2.eq)(agentQueuedMessage.threadId, input.threadId));
871
+ const position = input.at === "head" ? (edge?.first ?? 1) - 1 : (edge?.last ?? -1) + 1;
872
+ const row = {
873
+ id: crypto.randomUUID(),
874
+ threadId: input.threadId,
875
+ actor: input.actor,
876
+ content: input.content,
877
+ attachments: input.attachments !== void 0 && input.attachments.length > 0 ? input.attachments : null,
878
+ agentName: input.agentName ?? null,
879
+ model: input.model ?? null,
880
+ pageContext: input.pageContext ?? null,
881
+ interrupt: input.interrupt === true,
882
+ position,
883
+ createdAt: now,
884
+ updatedAt: now
885
+ };
886
+ await this.db.insert(agentQueuedMessage).values(row);
887
+ return toQueuedMessage(row);
888
+ }
889
+ async listQueue(threadId) {
890
+ const rows = await this.db.select().from(agentQueuedMessage).where((0, import_drizzle_orm2.eq)(agentQueuedMessage.threadId, threadId)).orderBy((0, import_drizzle_orm2.asc)(agentQueuedMessage.position), (0, import_drizzle_orm2.asc)(agentQueuedMessage.createdAt));
891
+ return rows.map(toQueuedMessage);
892
+ }
893
+ async getQueuedMessage(id) {
894
+ const [row] = await this.db.select().from(agentQueuedMessage).where((0, import_drizzle_orm2.eq)(agentQueuedMessage.id, id));
895
+ return row === void 0 ? null : toQueuedMessage(row);
896
+ }
897
+ async updateQueuedMessage(id, patch) {
898
+ const updates = {
899
+ updatedAt: /* @__PURE__ */ new Date()
900
+ };
901
+ if (patch.content !== void 0) {
902
+ updates.content = patch.content;
903
+ }
904
+ if (patch.attachments !== void 0) {
905
+ updates.attachments = patch.attachments === null || patch.attachments.length === 0 ? null : patch.attachments;
906
+ }
907
+ await this.db.update(agentQueuedMessage).set(updates).where((0, import_drizzle_orm2.eq)(agentQueuedMessage.id, id));
908
+ return this.getQueuedMessage(id);
909
+ }
910
+ /** Rewrites the thread's positions as 0..n-1 in the new order — a queue is a handful of rows. */
911
+ async moveQueuedMessage(id, index2) {
912
+ const moving = await this.getQueuedMessage(id);
913
+ if (moving === null) {
914
+ return false;
915
+ }
916
+ const order = (await this.listQueue(moving.threadId)).filter((message) => message.id !== id);
917
+ const target = Math.max(0, Math.min(order.length, Math.trunc(index2)));
918
+ order.splice(target, 0, moving);
919
+ for (const [position, message] of order.entries()) {
920
+ await this.db.update(agentQueuedMessage).set({
921
+ position
922
+ }).where((0, import_drizzle_orm2.eq)(agentQueuedMessage.id, message.id));
923
+ }
924
+ return true;
925
+ }
926
+ async removeQueuedMessage(id) {
927
+ const removed = await this.db.delete(agentQueuedMessage).where((0, import_drizzle_orm2.eq)(agentQueuedMessage.id, id)).returning({
928
+ id: agentQueuedMessage.id
929
+ });
930
+ return removed.length > 0;
931
+ }
932
+ async clearQueue(threadId) {
933
+ const removed = await this.db.delete(agentQueuedMessage).where((0, import_drizzle_orm2.eq)(agentQueuedMessage.threadId, threadId)).returning({
934
+ id: agentQueuedMessage.id
935
+ });
936
+ return removed.length;
937
+ }
938
+ async queuePause(threadId) {
939
+ const [thread] = await this.db.select({
940
+ queuePause: agentThread.queuePause
941
+ }).from(agentThread).where((0, import_drizzle_orm2.eq)(agentThread.id, threadId));
942
+ return thread?.queuePause ?? null;
943
+ }
944
+ async setQueuePause(threadId, pause) {
945
+ await this.db.update(agentThread).set({
946
+ queuePause: pause
947
+ }).where((0, import_drizzle_orm2.eq)(agentThread.id, threadId));
948
+ }
769
949
  async setActiveStream(threadId, runId) {
770
950
  await this.db.update(agentThread).set({
771
951
  activeStreamId: runId
@@ -1089,6 +1269,32 @@ var DrizzleAgentStore = class {
1089
1269
  };
1090
1270
  }
1091
1271
  };
1272
+ function toQueuedMessage(row) {
1273
+ return {
1274
+ id: row.id,
1275
+ threadId: row.threadId,
1276
+ actor: row.actor,
1277
+ content: row.content,
1278
+ ...row.attachments !== null && row.attachments.length > 0 ? {
1279
+ attachments: row.attachments
1280
+ } : {},
1281
+ ...row.agentName !== null ? {
1282
+ agentName: row.agentName
1283
+ } : {},
1284
+ ...row.model !== null ? {
1285
+ model: row.model
1286
+ } : {},
1287
+ ...row.pageContext !== null ? {
1288
+ pageContext: row.pageContext
1289
+ } : {},
1290
+ ...row.interrupt ? {
1291
+ interrupt: true
1292
+ } : {},
1293
+ createdAt: row.createdAt.toISOString(),
1294
+ updatedAt: row.updatedAt.toISOString()
1295
+ };
1296
+ }
1297
+ __name(toQueuedMessage, "toQueuedMessage");
1092
1298
 
1093
1299
  // src/drizzle-governance-queries.ts
1094
1300
  var import_nestjs_agent_core2 = require("@dudousxd/nestjs-agent-core");
@@ -2358,6 +2564,7 @@ DrizzleAgentStoreModule = _ts_decorate2([
2358
2564
  agentMemory,
2359
2565
  agentMessage,
2360
2566
  agentModelPricing,
2567
+ agentQueuedMessage,
2361
2568
  agentRun,
2362
2569
  agentSchema,
2363
2570
  agentThread,