@dudousxd/nestjs-agent-store-drizzle 0.1.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 ADDED
@@ -0,0 +1,749 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/schema.ts
5
+ import { index, integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core";
6
+ var agentThread = sqliteTable("agent_thread", {
7
+ id: text("id").primaryKey(),
8
+ actorRef: text("actor_ref").notNull(),
9
+ tenantRef: text("tenant_ref"),
10
+ title: text("title").notNull(),
11
+ persona: text("persona").notNull().default("default"),
12
+ transient: integer("transient", {
13
+ mode: "boolean"
14
+ }).notNull().default(false),
15
+ activeStreamId: text("active_stream_id"),
16
+ createdAt: integer("created_at", {
17
+ mode: "timestamp_ms"
18
+ }).notNull(),
19
+ updatedAt: integer("updated_at", {
20
+ mode: "timestamp_ms"
21
+ }).notNull(),
22
+ deletedAt: integer("deleted_at", {
23
+ mode: "timestamp_ms"
24
+ })
25
+ }, (table) => [
26
+ index("agent_thread_actor_updated_idx").on(table.actorRef, table.updatedAt)
27
+ ]);
28
+ var agentMessage = sqliteTable("agent_message", {
29
+ id: text("id").primaryKey(),
30
+ threadId: text("thread_id").notNull().references(() => agentThread.id, {
31
+ onDelete: "cascade"
32
+ }),
33
+ role: text("role").$type().notNull(),
34
+ content: text("content").notNull(),
35
+ toolCalls: text("tool_calls", {
36
+ mode: "json"
37
+ }).$type(),
38
+ toolResults: text("tool_results", {
39
+ mode: "json"
40
+ }).$type(),
41
+ followUps: text("follow_ups", {
42
+ mode: "json"
43
+ }).$type(),
44
+ usage: text("usage", {
45
+ mode: "json"
46
+ }).$type(),
47
+ persona: text("persona"),
48
+ createdAt: integer("created_at", {
49
+ mode: "timestamp_ms"
50
+ }).notNull()
51
+ }, (table) => [
52
+ index("agent_message_thread_created_idx").on(table.threadId, table.createdAt)
53
+ ]);
54
+ var agentToolCall = sqliteTable("agent_tool_call", {
55
+ id: text("id").primaryKey(),
56
+ messageId: text("message_id").notNull().references(() => agentMessage.id, {
57
+ onDelete: "cascade"
58
+ }),
59
+ toolName: text("tool_name").notNull(),
60
+ toolType: text("tool_type").$type().notNull(),
61
+ input: text("input", {
62
+ mode: "json"
63
+ }),
64
+ output: text("output", {
65
+ mode: "json"
66
+ }),
67
+ status: text("status").$type().notNull(),
68
+ executedByRef: text("executed_by_ref"),
69
+ executionMs: integer("execution_ms"),
70
+ error: text("error"),
71
+ createdAt: integer("created_at", {
72
+ mode: "timestamp_ms"
73
+ }).notNull(),
74
+ executedAt: integer("executed_at", {
75
+ mode: "timestamp_ms"
76
+ })
77
+ });
78
+ var agentTokenUsage = sqliteTable("agent_token_usage", {
79
+ id: text("id").primaryKey(),
80
+ threadId: text("thread_id").notNull().references(() => agentThread.id, {
81
+ onDelete: "cascade"
82
+ }),
83
+ actorRef: text("actor_ref").notNull(),
84
+ messageId: text("message_id"),
85
+ modelId: text("model_id").notNull(),
86
+ purpose: text("purpose").$type().notNull(),
87
+ inputTokens: integer("input_tokens").notNull(),
88
+ outputTokens: integer("output_tokens").notNull(),
89
+ /** Subset of `inputTokens` written to the prompt cache this turn; null when not reported. */
90
+ cacheWriteTokens: integer("cache_write_tokens"),
91
+ /** Subset of `inputTokens` served from the prompt cache this turn; null when not reported. */
92
+ cacheReadTokens: integer("cache_read_tokens"),
93
+ /** Provider-reported actual USD cost for the turn; null when only tokens were reported. */
94
+ costUsd: real("cost_usd"),
95
+ createdAt: integer("created_at", {
96
+ mode: "timestamp_ms"
97
+ }).notNull()
98
+ }, (table) => [
99
+ index("agent_token_usage_actor_created_idx").on(table.actorRef, table.createdAt)
100
+ ]);
101
+ var agentModelPricing = sqliteTable("agent_model_pricing", {
102
+ id: text("id").primaryKey(),
103
+ modelId: text("model_id").notNull(),
104
+ inputPricePer1m: real("input_price_per_1m").notNull(),
105
+ outputPricePer1m: real("output_price_per_1m").notNull(),
106
+ /** Per-1M price for cache-write input tokens; null → price them at the input rate. */
107
+ cacheWritePricePer1m: real("cache_write_price_per_1m"),
108
+ /** Per-1M price for cache-read input tokens; null → price them at the input rate. */
109
+ cacheReadPricePer1m: real("cache_read_price_per_1m"),
110
+ effectiveFrom: integer("effective_from", {
111
+ mode: "timestamp_ms"
112
+ }).notNull(),
113
+ isCurrent: integer("is_current", {
114
+ mode: "boolean"
115
+ }).notNull()
116
+ });
117
+ var agentSchema = {
118
+ agentThread,
119
+ agentMessage,
120
+ agentToolCall,
121
+ agentTokenUsage,
122
+ agentModelPricing
123
+ };
124
+
125
+ // src/ensure-schema.ts
126
+ import { sql } from "drizzle-orm";
127
+ var statements = [
128
+ `CREATE TABLE IF NOT EXISTS agent_thread (
129
+ id TEXT PRIMARY KEY NOT NULL,
130
+ actor_ref TEXT NOT NULL,
131
+ tenant_ref TEXT,
132
+ title TEXT NOT NULL,
133
+ persona TEXT NOT NULL DEFAULT 'default',
134
+ transient INTEGER NOT NULL DEFAULT 0,
135
+ active_stream_id TEXT,
136
+ created_at INTEGER NOT NULL,
137
+ updated_at INTEGER NOT NULL,
138
+ deleted_at INTEGER
139
+ )`,
140
+ `CREATE INDEX IF NOT EXISTS agent_thread_actor_updated_idx
141
+ ON agent_thread (actor_ref, updated_at)`,
142
+ `CREATE TABLE IF NOT EXISTS agent_message (
143
+ id TEXT PRIMARY KEY NOT NULL,
144
+ thread_id TEXT NOT NULL REFERENCES agent_thread(id) ON DELETE CASCADE,
145
+ role TEXT NOT NULL,
146
+ content TEXT NOT NULL,
147
+ tool_calls TEXT,
148
+ tool_results TEXT,
149
+ follow_ups TEXT,
150
+ usage TEXT,
151
+ persona TEXT,
152
+ created_at INTEGER NOT NULL
153
+ )`,
154
+ `CREATE INDEX IF NOT EXISTS agent_message_thread_created_idx
155
+ ON agent_message (thread_id, created_at)`,
156
+ `CREATE TABLE IF NOT EXISTS agent_tool_call (
157
+ id TEXT PRIMARY KEY NOT NULL,
158
+ message_id TEXT NOT NULL REFERENCES agent_message(id) ON DELETE CASCADE,
159
+ tool_name TEXT NOT NULL,
160
+ tool_type TEXT NOT NULL,
161
+ input TEXT,
162
+ output TEXT,
163
+ status TEXT NOT NULL,
164
+ executed_by_ref TEXT,
165
+ execution_ms INTEGER,
166
+ error TEXT,
167
+ created_at INTEGER NOT NULL,
168
+ executed_at INTEGER
169
+ )`,
170
+ `CREATE TABLE IF NOT EXISTS agent_token_usage (
171
+ id TEXT PRIMARY KEY NOT NULL,
172
+ thread_id TEXT NOT NULL REFERENCES agent_thread(id) ON DELETE CASCADE,
173
+ actor_ref TEXT NOT NULL,
174
+ message_id TEXT,
175
+ model_id TEXT NOT NULL,
176
+ purpose TEXT NOT NULL,
177
+ input_tokens INTEGER NOT NULL,
178
+ output_tokens INTEGER NOT NULL,
179
+ cache_write_tokens INTEGER,
180
+ cache_read_tokens INTEGER,
181
+ cost_usd REAL,
182
+ created_at INTEGER NOT NULL
183
+ )`,
184
+ `CREATE INDEX IF NOT EXISTS agent_token_usage_actor_created_idx
185
+ ON agent_token_usage (actor_ref, created_at)`,
186
+ `CREATE TABLE IF NOT EXISTS agent_model_pricing (
187
+ id TEXT PRIMARY KEY NOT NULL,
188
+ model_id TEXT NOT NULL,
189
+ input_price_per_1m REAL NOT NULL,
190
+ output_price_per_1m REAL NOT NULL,
191
+ cache_write_price_per_1m REAL,
192
+ cache_read_price_per_1m REAL,
193
+ effective_from INTEGER NOT NULL,
194
+ is_current INTEGER NOT NULL
195
+ )`
196
+ ];
197
+ async function ensureAgentSchema(db) {
198
+ for (const statement of statements) {
199
+ await db.run(sql.raw(statement));
200
+ }
201
+ }
202
+ __name(ensureAgentSchema, "ensureAgentSchema");
203
+
204
+ // src/drizzle-agent-store.ts
205
+ import { and, asc, desc, eq, gte, inArray, isNull, lte } from "drizzle-orm";
206
+ var DrizzleAgentStore = class {
207
+ static {
208
+ __name(this, "DrizzleAgentStore");
209
+ }
210
+ db;
211
+ constructor(db) {
212
+ this.db = db;
213
+ }
214
+ async createThread(input) {
215
+ const now = /* @__PURE__ */ new Date();
216
+ const thread = {
217
+ id: crypto.randomUUID(),
218
+ actorRef: input.actor.id,
219
+ tenantRef: input.actor.tenantRef ?? null,
220
+ title: input.title ?? "New chat",
221
+ persona: input.persona,
222
+ transient: input.transient ?? false,
223
+ activeStreamId: null,
224
+ createdAt: now,
225
+ updatedAt: now,
226
+ deletedAt: null
227
+ };
228
+ await this.db.insert(agentThread).values(thread);
229
+ return this.toSummary(thread);
230
+ }
231
+ async getThread(threadId) {
232
+ const [thread] = await this.db.select().from(agentThread).where(and(eq(agentThread.id, threadId), isNull(agentThread.deletedAt)));
233
+ if (thread === void 0) {
234
+ return null;
235
+ }
236
+ const messages = await this.db.select().from(agentMessage).where(eq(agentMessage.threadId, threadId)).orderBy(asc(agentMessage.createdAt), asc(agentMessage.id));
237
+ const last = messages[messages.length - 1];
238
+ return {
239
+ ...this.toSummary(thread, last?.content),
240
+ messages: messages.map((message) => this.toStoredMessage(message)),
241
+ ...thread.activeStreamId != null ? {
242
+ activeStreamId: thread.activeStreamId
243
+ } : {}
244
+ };
245
+ }
246
+ async listThreads(actorRef, limit = 50) {
247
+ const threads = await this.db.select().from(agentThread).where(and(eq(agentThread.actorRef, actorRef), eq(agentThread.transient, false), isNull(agentThread.deletedAt))).orderBy(desc(agentThread.updatedAt)).limit(limit);
248
+ const summaries = [];
249
+ for (const thread of threads) {
250
+ const [last] = await this.db.select().from(agentMessage).where(eq(agentMessage.threadId, thread.id)).orderBy(desc(agentMessage.createdAt), desc(agentMessage.id)).limit(1);
251
+ summaries.push(this.toSummary(thread, last?.content));
252
+ }
253
+ return summaries;
254
+ }
255
+ async softDeleteThread(threadId) {
256
+ await this.db.update(agentThread).set({
257
+ deletedAt: /* @__PURE__ */ new Date()
258
+ }).where(eq(agentThread.id, threadId));
259
+ }
260
+ async forkThread(threadId, fromMessageId) {
261
+ const [source] = await this.db.select().from(agentThread).where(eq(agentThread.id, threadId));
262
+ if (source === void 0) {
263
+ throw new Error(`thread ${threadId} not found`);
264
+ }
265
+ const messages = await this.db.select().from(agentMessage).where(eq(agentMessage.threadId, threadId)).orderBy(asc(agentMessage.createdAt), asc(agentMessage.id));
266
+ const cutoff = messages.findIndex((message) => message.id === fromMessageId);
267
+ const kept = cutoff >= 0 ? messages.slice(0, cutoff + 1) : messages;
268
+ const now = /* @__PURE__ */ new Date();
269
+ const fork = {
270
+ id: crypto.randomUUID(),
271
+ actorRef: source.actorRef,
272
+ tenantRef: source.tenantRef,
273
+ title: source.title,
274
+ persona: source.persona,
275
+ transient: false,
276
+ activeStreamId: null,
277
+ createdAt: now,
278
+ updatedAt: now,
279
+ deletedAt: null
280
+ };
281
+ await this.db.insert(agentThread).values(fork);
282
+ if (kept.length > 0) {
283
+ await this.db.insert(agentMessage).values(kept.map((message) => ({
284
+ id: crypto.randomUUID(),
285
+ threadId: fork.id,
286
+ role: message.role,
287
+ content: message.content,
288
+ toolCalls: message.toolCalls,
289
+ toolResults: message.toolResults,
290
+ followUps: message.followUps,
291
+ usage: message.usage,
292
+ persona: message.persona,
293
+ createdAt: message.createdAt
294
+ })));
295
+ }
296
+ return this.toSummary(fork);
297
+ }
298
+ async ownerOfThread(threadId) {
299
+ const [thread] = await this.db.select({
300
+ actorRef: agentThread.actorRef
301
+ }).from(agentThread).where(and(eq(agentThread.id, threadId), isNull(agentThread.deletedAt)));
302
+ return thread?.actorRef ?? null;
303
+ }
304
+ async ownerOfToolCall(toolCallId) {
305
+ const [row] = await this.db.select({
306
+ actorRef: agentThread.actorRef
307
+ }).from(agentToolCall).innerJoin(agentMessage, eq(agentToolCall.messageId, agentMessage.id)).innerJoin(agentThread, eq(agentMessage.threadId, agentThread.id)).where(eq(agentToolCall.id, toolCallId));
308
+ return row?.actorRef ?? null;
309
+ }
310
+ async runForToolCall(toolCallId) {
311
+ const [row] = await this.db.select({
312
+ activeStreamId: agentThread.activeStreamId
313
+ }).from(agentToolCall).innerJoin(agentMessage, eq(agentToolCall.messageId, agentMessage.id)).innerJoin(agentThread, eq(agentMessage.threadId, agentThread.id)).where(eq(agentToolCall.id, toolCallId));
314
+ return row?.activeStreamId ?? null;
315
+ }
316
+ async ownerOfActiveStream(runId) {
317
+ const [thread] = await this.db.select({
318
+ actorRef: agentThread.actorRef
319
+ }).from(agentThread).where(and(eq(agentThread.activeStreamId, runId), isNull(agentThread.deletedAt)));
320
+ return thread?.actorRef ?? null;
321
+ }
322
+ async setTitle(threadId, title) {
323
+ await this.db.update(agentThread).set({
324
+ title,
325
+ updatedAt: /* @__PURE__ */ new Date()
326
+ }).where(eq(agentThread.id, threadId));
327
+ }
328
+ async setActiveStream(threadId, runId) {
329
+ await this.db.update(agentThread).set({
330
+ activeStreamId: runId
331
+ }).where(eq(agentThread.id, threadId));
332
+ }
333
+ async appendMessage(input) {
334
+ const [thread] = await this.db.select().from(agentThread).where(eq(agentThread.id, input.threadId));
335
+ if (thread === void 0) {
336
+ throw new Error(`thread ${input.threadId} not found`);
337
+ }
338
+ const now = /* @__PURE__ */ new Date();
339
+ const message = {
340
+ id: crypto.randomUUID(),
341
+ threadId: input.threadId,
342
+ role: input.role,
343
+ content: input.content,
344
+ toolCalls: input.toolCalls ?? null,
345
+ toolResults: input.toolResults ?? null,
346
+ followUps: input.followUps ?? null,
347
+ usage: input.usage ?? null,
348
+ persona: input.persona ?? null,
349
+ createdAt: now
350
+ };
351
+ await this.db.insert(agentMessage).values(message);
352
+ await this.db.update(agentThread).set({
353
+ updatedAt: now
354
+ }).where(eq(agentThread.id, input.threadId));
355
+ return this.toStoredMessage(message);
356
+ }
357
+ async truncateFrom(threadId, messageId) {
358
+ const messages = await this.db.select().from(agentMessage).where(eq(agentMessage.threadId, threadId)).orderBy(asc(agentMessage.createdAt), asc(agentMessage.id));
359
+ const cutoff = messages.findIndex((message) => message.id === messageId);
360
+ if (cutoff < 0) {
361
+ return;
362
+ }
363
+ const doomedIds = messages.slice(cutoff).map((message) => message.id);
364
+ await this.db.delete(agentToolCall).where(inArray(agentToolCall.messageId, doomedIds));
365
+ await this.db.delete(agentMessage).where(inArray(agentMessage.id, doomedIds));
366
+ }
367
+ async recordToolCall(input) {
368
+ await this.db.insert(agentToolCall).values({
369
+ id: input.toolCallId,
370
+ messageId: input.messageId,
371
+ toolName: input.toolName,
372
+ toolType: input.toolType,
373
+ input: input.input,
374
+ output: null,
375
+ status: input.status,
376
+ executedByRef: null,
377
+ executionMs: null,
378
+ error: null,
379
+ createdAt: /* @__PURE__ */ new Date(),
380
+ executedAt: null
381
+ });
382
+ }
383
+ async updateToolCall(input) {
384
+ const updates = {
385
+ status: input.status
386
+ };
387
+ if (input.output !== void 0) {
388
+ updates.output = input.output;
389
+ }
390
+ if (input.error !== void 0) {
391
+ updates.error = input.error;
392
+ }
393
+ if (input.executionMs !== void 0) {
394
+ updates.executionMs = input.executionMs;
395
+ }
396
+ if (input.executedByRef !== void 0) {
397
+ updates.executedByRef = input.executedByRef;
398
+ }
399
+ if (input.status === "executed" || input.status === "auto_executed") {
400
+ updates.executedAt = /* @__PURE__ */ new Date();
401
+ }
402
+ await this.db.update(agentToolCall).set(updates).where(eq(agentToolCall.id, input.toolCallId));
403
+ }
404
+ async recordUsage(input) {
405
+ await this.db.insert(agentTokenUsage).values({
406
+ id: crypto.randomUUID(),
407
+ threadId: input.threadId,
408
+ actorRef: input.actorRef,
409
+ messageId: input.messageId ?? null,
410
+ modelId: input.modelId,
411
+ purpose: input.purpose,
412
+ inputTokens: input.usage.inputTokens,
413
+ outputTokens: input.usage.outputTokens,
414
+ cacheWriteTokens: input.usage.cacheWriteTokens ?? null,
415
+ cacheReadTokens: input.usage.cacheReadTokens ?? null,
416
+ costUsd: input.costUsd ?? null,
417
+ createdAt: /* @__PURE__ */ new Date()
418
+ });
419
+ }
420
+ async quotaToday(actorRef, day) {
421
+ const start = /* @__PURE__ */ new Date(`${day}T00:00:00.000Z`);
422
+ const end = /* @__PURE__ */ new Date(`${day}T23:59:59.999Z`);
423
+ const rows = await this.db.select().from(agentTokenUsage).where(and(eq(agentTokenUsage.actorRef, actorRef), gte(agentTokenUsage.createdAt, start), lte(agentTokenUsage.createdAt, end)));
424
+ const usedTokens = rows.reduce((sum, row) => sum + row.inputTokens + row.outputTokens, 0);
425
+ return {
426
+ usedTokens
427
+ };
428
+ }
429
+ toSummary(thread, lastContent) {
430
+ return {
431
+ id: thread.id,
432
+ title: thread.title,
433
+ persona: thread.persona,
434
+ transient: thread.transient,
435
+ createdAt: thread.createdAt.toISOString(),
436
+ updatedAt: thread.updatedAt.toISOString(),
437
+ ...lastContent !== void 0 ? {
438
+ lastMessagePreview: lastContent.slice(0, 120)
439
+ } : {}
440
+ };
441
+ }
442
+ toStoredMessage(message) {
443
+ return {
444
+ id: message.id,
445
+ role: message.role,
446
+ content: message.content,
447
+ createdAt: message.createdAt.toISOString(),
448
+ ...message.toolCalls != null ? {
449
+ toolCalls: message.toolCalls
450
+ } : {},
451
+ ...message.toolResults != null ? {
452
+ toolResults: message.toolResults
453
+ } : {},
454
+ ...message.followUps != null ? {
455
+ followUps: message.followUps
456
+ } : {},
457
+ ...message.usage != null ? {
458
+ usage: message.usage
459
+ } : {}
460
+ };
461
+ }
462
+ };
463
+
464
+ // src/drizzle-governance-queries.ts
465
+ import { and as and2, count, desc as desc2, eq as eq2, gte as gte2, isNull as isNull2, lte as lte2 } from "drizzle-orm";
466
+ function estimateFromTokens(pricing, row) {
467
+ const price = pricing.get(row.modelId);
468
+ if (price === void 0) {
469
+ return 0;
470
+ }
471
+ const cacheWriteTokens = row.cacheWriteTokens ?? 0;
472
+ const cacheReadTokens = row.cacheReadTokens ?? 0;
473
+ const uncachedInputTokens = row.inputTokens - cacheWriteTokens - cacheReadTokens;
474
+ return uncachedInputTokens / 1e6 * price.inputPricePer1m + cacheWriteTokens / 1e6 * (price.cacheWritePricePer1m ?? price.inputPricePer1m) + cacheReadTokens / 1e6 * (price.cacheReadPricePer1m ?? price.inputPricePer1m) + row.outputTokens / 1e6 * price.outputPricePer1m;
475
+ }
476
+ __name(estimateFromTokens, "estimateFromTokens");
477
+ function rowCost(pricing, row) {
478
+ return row.costUsd ?? estimateFromTokens(pricing, row);
479
+ }
480
+ __name(rowCost, "rowCost");
481
+ function dayBounds(range) {
482
+ return {
483
+ start: /* @__PURE__ */ new Date(`${range.fromDay}T00:00:00.000Z`),
484
+ end: /* @__PURE__ */ new Date(`${range.toDay}T23:59:59.999Z`)
485
+ };
486
+ }
487
+ __name(dayBounds, "dayBounds");
488
+ var DrizzleGovernanceQueries = class {
489
+ static {
490
+ __name(this, "DrizzleGovernanceQueries");
491
+ }
492
+ db;
493
+ constructor(db) {
494
+ this.db = db;
495
+ }
496
+ async loadPricing() {
497
+ const rows = await this.db.select().from(agentModelPricing).where(eq2(agentModelPricing.isCurrent, true));
498
+ const pricing = /* @__PURE__ */ new Map();
499
+ for (const row of rows) {
500
+ pricing.set(row.modelId, {
501
+ inputPricePer1m: row.inputPricePer1m,
502
+ outputPricePer1m: row.outputPricePer1m,
503
+ cacheWritePricePer1m: row.cacheWritePricePer1m,
504
+ cacheReadPricePer1m: row.cacheReadPricePer1m
505
+ });
506
+ }
507
+ return pricing;
508
+ }
509
+ async usageInRange(range) {
510
+ const { start, end } = dayBounds(range);
511
+ const rows = await this.db.select().from(agentTokenUsage).where(and2(gte2(agentTokenUsage.createdAt, start), lte2(agentTokenUsage.createdAt, end)));
512
+ return rows;
513
+ }
514
+ async spendByModel(range) {
515
+ const pricing = await this.loadPricing();
516
+ const rows = await this.usageInRange(range);
517
+ const byModel = /* @__PURE__ */ new Map();
518
+ for (const row of rows) {
519
+ const bucket = byModel.get(row.modelId) ?? {
520
+ requests: 0,
521
+ inputTokens: 0,
522
+ outputTokens: 0,
523
+ costUsd: 0
524
+ };
525
+ bucket.requests += 1;
526
+ bucket.inputTokens += row.inputTokens;
527
+ bucket.outputTokens += row.outputTokens;
528
+ bucket.costUsd += rowCost(pricing, row);
529
+ byModel.set(row.modelId, bucket);
530
+ }
531
+ const result = [];
532
+ for (const [modelId, bucket] of byModel) {
533
+ result.push({
534
+ modelId,
535
+ requests: bucket.requests,
536
+ inputTokens: bucket.inputTokens,
537
+ outputTokens: bucket.outputTokens,
538
+ costUsd: bucket.costUsd
539
+ });
540
+ }
541
+ result.sort((left, right) => right.costUsd - left.costUsd || left.modelId.localeCompare(right.modelId));
542
+ return result;
543
+ }
544
+ async spendByActor(range) {
545
+ const pricing = await this.loadPricing();
546
+ const rows = await this.usageInRange(range);
547
+ const byActor = /* @__PURE__ */ new Map();
548
+ for (const row of rows) {
549
+ const bucket = byActor.get(row.actorRef) ?? {
550
+ requests: 0,
551
+ totalTokens: 0,
552
+ costUsd: 0
553
+ };
554
+ bucket.requests += 1;
555
+ bucket.totalTokens += row.inputTokens + row.outputTokens;
556
+ bucket.costUsd += rowCost(pricing, row);
557
+ byActor.set(row.actorRef, bucket);
558
+ }
559
+ const result = [];
560
+ for (const [actorRef, bucket] of byActor) {
561
+ result.push({
562
+ actorRef,
563
+ requests: bucket.requests,
564
+ totalTokens: bucket.totalTokens,
565
+ costUsd: bucket.costUsd
566
+ });
567
+ }
568
+ result.sort((left, right) => right.costUsd - left.costUsd || left.actorRef.localeCompare(right.actorRef));
569
+ return result;
570
+ }
571
+ async usageTrend(range) {
572
+ const pricing = await this.loadPricing();
573
+ const rows = await this.usageInRange(range);
574
+ const byDay = /* @__PURE__ */ new Map();
575
+ for (const row of rows) {
576
+ const day = row.createdAt.toISOString().slice(0, 10);
577
+ const bucket = byDay.get(day) ?? {
578
+ totalTokens: 0,
579
+ costUsd: 0
580
+ };
581
+ bucket.totalTokens += row.inputTokens + row.outputTokens;
582
+ bucket.costUsd += rowCost(pricing, row);
583
+ byDay.set(day, bucket);
584
+ }
585
+ const result = [];
586
+ for (const [day, bucket] of byDay) {
587
+ result.push({
588
+ day,
589
+ totalTokens: bucket.totalTokens,
590
+ costUsd: bucket.costUsd
591
+ });
592
+ }
593
+ result.sort((left, right) => left.day.localeCompare(right.day));
594
+ return result;
595
+ }
596
+ async recentToolCalls(limit) {
597
+ const rows = await this.db.select({
598
+ toolCallId: agentToolCall.id,
599
+ toolName: agentToolCall.toolName,
600
+ toolType: agentToolCall.toolType,
601
+ status: agentToolCall.status,
602
+ threadId: agentMessage.threadId,
603
+ createdAt: agentToolCall.createdAt
604
+ }).from(agentToolCall).innerJoin(agentMessage, eq2(agentToolCall.messageId, agentMessage.id)).orderBy(desc2(agentToolCall.createdAt), desc2(agentToolCall.id)).limit(limit);
605
+ return rows.map((row) => ({
606
+ toolCallId: row.toolCallId,
607
+ toolName: row.toolName,
608
+ toolType: row.toolType,
609
+ status: row.status,
610
+ threadId: row.threadId,
611
+ createdAt: row.createdAt.toISOString()
612
+ }));
613
+ }
614
+ async recentThreads(limit) {
615
+ const threads = await this.db.select().from(agentThread).where(isNull2(agentThread.deletedAt)).orderBy(desc2(agentThread.updatedAt), desc2(agentThread.id)).limit(limit);
616
+ const result = [];
617
+ for (const thread of threads) {
618
+ const [messageCountRow] = await this.db.select({
619
+ value: count()
620
+ }).from(agentMessage).where(eq2(agentMessage.threadId, thread.id));
621
+ const usageRows = await this.db.select().from(agentTokenUsage).where(eq2(agentTokenUsage.threadId, thread.id));
622
+ const totalTokens = usageRows.reduce((sum, row) => sum + row.inputTokens + row.outputTokens, 0);
623
+ result.push({
624
+ threadId: thread.id,
625
+ title: thread.title,
626
+ actorRef: thread.actorRef,
627
+ messageCount: messageCountRow?.value ?? 0,
628
+ totalTokens,
629
+ lastActivityAt: thread.updatedAt.toISOString()
630
+ });
631
+ }
632
+ return result;
633
+ }
634
+ };
635
+
636
+ // src/drizzle-pricing-store.ts
637
+ import { and as and3, eq as eq3 } from "drizzle-orm";
638
+ var DrizzlePricingStore = class {
639
+ static {
640
+ __name(this, "DrizzlePricingStore");
641
+ }
642
+ db;
643
+ constructor(db) {
644
+ this.db = db;
645
+ }
646
+ async upsertModelPrice(input) {
647
+ await this.db.update(agentModelPricing).set({
648
+ isCurrent: false
649
+ }).where(and3(eq3(agentModelPricing.modelId, input.modelId), eq3(agentModelPricing.isCurrent, true)));
650
+ await this.db.insert(agentModelPricing).values({
651
+ id: crypto.randomUUID(),
652
+ modelId: input.modelId,
653
+ inputPricePer1m: input.inputPricePer1m,
654
+ outputPricePer1m: input.outputPricePer1m,
655
+ cacheWritePricePer1m: input.cacheWritePricePer1m ?? null,
656
+ cacheReadPricePer1m: input.cacheReadPricePer1m ?? null,
657
+ effectiveFrom: /* @__PURE__ */ new Date(),
658
+ isCurrent: true
659
+ });
660
+ }
661
+ async listCurrentPrices() {
662
+ const rows = await this.db.select().from(agentModelPricing).where(eq3(agentModelPricing.isCurrent, true));
663
+ return rows.map((row) => ({
664
+ modelId: row.modelId,
665
+ inputPricePer1m: row.inputPricePer1m,
666
+ outputPricePer1m: row.outputPricePer1m,
667
+ effectiveFrom: row.effectiveFrom.toISOString(),
668
+ ...row.cacheWritePricePer1m != null ? {
669
+ cacheWritePricePer1m: row.cacheWritePricePer1m
670
+ } : {},
671
+ ...row.cacheReadPricePer1m != null ? {
672
+ cacheReadPricePer1m: row.cacheReadPricePer1m
673
+ } : {}
674
+ }));
675
+ }
676
+ };
677
+
678
+ // src/drizzle-agent-store.module.ts
679
+ import { AGENT_GOVERNANCE_QUERIES, AGENT_PRICING_STORE, AGENT_STORE } from "@dudousxd/nestjs-agent-core";
680
+ import { Module } from "@nestjs/common";
681
+ function _ts_decorate(decorators, target, key, desc3) {
682
+ var c = arguments.length, r = c < 3 ? target : desc3 === null ? desc3 = Object.getOwnPropertyDescriptor(target, key) : desc3, d;
683
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc3);
684
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
685
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
686
+ }
687
+ __name(_ts_decorate, "_ts_decorate");
688
+ var DrizzleAgentStoreModule = class _DrizzleAgentStoreModule {
689
+ static {
690
+ __name(this, "DrizzleAgentStoreModule");
691
+ }
692
+ static forRoot(options) {
693
+ return {
694
+ module: _DrizzleAgentStoreModule,
695
+ global: true,
696
+ providers: [
697
+ {
698
+ provide: DrizzleAgentStore,
699
+ useFactory: /* @__PURE__ */ __name(() => new DrizzleAgentStore(options.db), "useFactory")
700
+ },
701
+ {
702
+ provide: AGENT_STORE,
703
+ useExisting: DrizzleAgentStore
704
+ },
705
+ {
706
+ provide: DrizzleGovernanceQueries,
707
+ useFactory: /* @__PURE__ */ __name(() => new DrizzleGovernanceQueries(options.db), "useFactory")
708
+ },
709
+ {
710
+ provide: AGENT_GOVERNANCE_QUERIES,
711
+ useExisting: DrizzleGovernanceQueries
712
+ },
713
+ {
714
+ provide: DrizzlePricingStore,
715
+ useFactory: /* @__PURE__ */ __name(() => new DrizzlePricingStore(options.db), "useFactory")
716
+ },
717
+ {
718
+ provide: AGENT_PRICING_STORE,
719
+ useExisting: DrizzlePricingStore
720
+ }
721
+ ],
722
+ exports: [
723
+ DrizzleAgentStore,
724
+ AGENT_STORE,
725
+ DrizzleGovernanceQueries,
726
+ AGENT_GOVERNANCE_QUERIES,
727
+ DrizzlePricingStore,
728
+ AGENT_PRICING_STORE
729
+ ]
730
+ };
731
+ }
732
+ };
733
+ DrizzleAgentStoreModule = _ts_decorate([
734
+ Module({})
735
+ ], DrizzleAgentStoreModule);
736
+ export {
737
+ DrizzleAgentStore,
738
+ DrizzleAgentStoreModule,
739
+ DrizzleGovernanceQueries,
740
+ DrizzlePricingStore,
741
+ agentMessage,
742
+ agentModelPricing,
743
+ agentSchema,
744
+ agentThread,
745
+ agentTokenUsage,
746
+ agentToolCall,
747
+ ensureAgentSchema
748
+ };
749
+ //# sourceMappingURL=index.js.map