@dudousxd/nestjs-agent-store-drizzle 0.3.4 → 0.5.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.cjs +278 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +508 -4
- package/dist/index.d.ts +508 -4
- package/dist/index.js +278 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -113,12 +113,36 @@ var agentModelPricing = sqliteTable("agent_model_pricing", {
|
|
|
113
113
|
mode: "boolean"
|
|
114
114
|
}).notNull()
|
|
115
115
|
});
|
|
116
|
+
var agentRun = sqliteTable("agent_run", {
|
|
117
|
+
id: text("id").primaryKey(),
|
|
118
|
+
threadId: text("thread_id").notNull().references(() => agentThread.id, {
|
|
119
|
+
onDelete: "cascade"
|
|
120
|
+
}),
|
|
121
|
+
actorRef: text("actor_ref").notNull(),
|
|
122
|
+
agentName: text("agent_name"),
|
|
123
|
+
status: text("status").$type().notNull(),
|
|
124
|
+
durationMs: integer("duration_ms"),
|
|
125
|
+
errorCode: text("error_code"),
|
|
126
|
+
errorMessage: text("error_message"),
|
|
127
|
+
retries: integer("retries").notNull().default(0),
|
|
128
|
+
startedAt: integer("started_at", {
|
|
129
|
+
mode: "timestamp_ms"
|
|
130
|
+
}).notNull(),
|
|
131
|
+
settledAt: integer("settled_at", {
|
|
132
|
+
mode: "timestamp_ms"
|
|
133
|
+
}),
|
|
134
|
+
/** sha256 hex of the run's resolved (pre-RAG) system prompt; null for a pre-existing run. */
|
|
135
|
+
promptHash: text("prompt_hash")
|
|
136
|
+
}, (table) => [
|
|
137
|
+
index("agent_run_started_idx").on(table.startedAt)
|
|
138
|
+
]);
|
|
116
139
|
var agentSchema = {
|
|
117
140
|
agentThread,
|
|
118
141
|
agentMessage,
|
|
119
142
|
agentToolCall,
|
|
120
143
|
agentTokenUsage,
|
|
121
|
-
agentModelPricing
|
|
144
|
+
agentModelPricing,
|
|
145
|
+
agentRun
|
|
122
146
|
};
|
|
123
147
|
|
|
124
148
|
// src/ensure-schema.ts
|
|
@@ -190,7 +214,23 @@ var statements = [
|
|
|
190
214
|
cache_read_price_per_1m REAL,
|
|
191
215
|
effective_from INTEGER NOT NULL,
|
|
192
216
|
is_current INTEGER NOT NULL
|
|
193
|
-
)
|
|
217
|
+
)`,
|
|
218
|
+
`CREATE TABLE IF NOT EXISTS agent_run (
|
|
219
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
220
|
+
thread_id TEXT NOT NULL REFERENCES agent_thread(id) ON DELETE CASCADE,
|
|
221
|
+
actor_ref TEXT NOT NULL,
|
|
222
|
+
agent_name TEXT,
|
|
223
|
+
status TEXT NOT NULL,
|
|
224
|
+
duration_ms INTEGER,
|
|
225
|
+
error_code TEXT,
|
|
226
|
+
error_message TEXT,
|
|
227
|
+
retries INTEGER NOT NULL DEFAULT 0,
|
|
228
|
+
started_at INTEGER NOT NULL,
|
|
229
|
+
settled_at INTEGER,
|
|
230
|
+
prompt_hash TEXT
|
|
231
|
+
)`,
|
|
232
|
+
`CREATE INDEX IF NOT EXISTS agent_run_started_idx
|
|
233
|
+
ON agent_run (started_at)`
|
|
194
234
|
];
|
|
195
235
|
async function ensureAgentSchema(db) {
|
|
196
236
|
for (const statement of statements) {
|
|
@@ -200,7 +240,7 @@ async function ensureAgentSchema(db) {
|
|
|
200
240
|
__name(ensureAgentSchema, "ensureAgentSchema");
|
|
201
241
|
|
|
202
242
|
// src/drizzle-agent-store.ts
|
|
203
|
-
import { and, asc, desc, eq, gte, inArray, isNull, lte } from "drizzle-orm";
|
|
243
|
+
import { and, asc, desc, eq, gte, inArray, isNull, lte, sql as sql2 } from "drizzle-orm";
|
|
204
244
|
var DrizzleAgentStore = class {
|
|
205
245
|
static {
|
|
206
246
|
__name(this, "DrizzleAgentStore");
|
|
@@ -332,6 +372,47 @@ var DrizzleAgentStore = class {
|
|
|
332
372
|
activeStreamId: runId
|
|
333
373
|
}).where(eq(agentThread.id, threadId));
|
|
334
374
|
}
|
|
375
|
+
/** Persist the start of a run (turn). Replay-safe: called under a durable localStep. */
|
|
376
|
+
async recordRunStart(run) {
|
|
377
|
+
const runRow = {
|
|
378
|
+
id: run.runId,
|
|
379
|
+
threadId: run.threadId,
|
|
380
|
+
actorRef: run.actorRef,
|
|
381
|
+
agentName: run.agentName ?? null,
|
|
382
|
+
status: "running",
|
|
383
|
+
durationMs: null,
|
|
384
|
+
errorCode: null,
|
|
385
|
+
errorMessage: null,
|
|
386
|
+
retries: 0,
|
|
387
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
388
|
+
settledAt: null,
|
|
389
|
+
promptHash: run.promptHash ?? null
|
|
390
|
+
};
|
|
391
|
+
await this.db.insert(agentRun).values(runRow);
|
|
392
|
+
}
|
|
393
|
+
/** Settle a run's outcome. A no-op when the run is unknown (mirrors `setTitle`/`updateThread`). */
|
|
394
|
+
async recordRunEnd(end) {
|
|
395
|
+
const updates = {
|
|
396
|
+
status: end.status,
|
|
397
|
+
settledAt: /* @__PURE__ */ new Date()
|
|
398
|
+
};
|
|
399
|
+
if (end.durationMs !== void 0) {
|
|
400
|
+
updates.durationMs = end.durationMs;
|
|
401
|
+
}
|
|
402
|
+
if (end.errorCode !== void 0) {
|
|
403
|
+
updates.errorCode = end.errorCode;
|
|
404
|
+
}
|
|
405
|
+
if (end.errorMessage !== void 0) {
|
|
406
|
+
updates.errorMessage = end.errorMessage;
|
|
407
|
+
}
|
|
408
|
+
await this.db.update(agentRun).set(updates).where(eq(agentRun.id, end.runId));
|
|
409
|
+
}
|
|
410
|
+
/** Bump the run's llm-step retry counter. Atomic `retries = retries + 1`, no read-modify-write. */
|
|
411
|
+
async bumpRunRetries(runId) {
|
|
412
|
+
await this.db.update(agentRun).set({
|
|
413
|
+
retries: sql2`${agentRun.retries} + 1`
|
|
414
|
+
}).where(eq(agentRun.id, runId));
|
|
415
|
+
}
|
|
335
416
|
async appendMessage(input) {
|
|
336
417
|
const [thread] = await this.db.select().from(agentThread).where(eq(agentThread.id, input.threadId));
|
|
337
418
|
if (thread === void 0) {
|
|
@@ -470,6 +551,8 @@ var DrizzleAgentStore = class {
|
|
|
470
551
|
// src/drizzle-governance-queries.ts
|
|
471
552
|
import { bucketByActor, bucketByModel, bucketByThread, bucketUsageTrend, dayBoundsUtc } from "@dudousxd/nestjs-agent-core";
|
|
472
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";
|
|
554
|
+
var UNCLASSIFIED_ERROR_CODE = "unknown";
|
|
555
|
+
var DEFAULT_AGENT_BUCKET = "(default)";
|
|
473
556
|
function toUsageInput(row) {
|
|
474
557
|
return {
|
|
475
558
|
modelId: row.modelId,
|
|
@@ -484,6 +567,14 @@ function toUsageInput(row) {
|
|
|
484
567
|
};
|
|
485
568
|
}
|
|
486
569
|
__name(toUsageInput, "toUsageInput");
|
|
570
|
+
function percentileMs(sortedDurationsMs, p) {
|
|
571
|
+
if (sortedDurationsMs.length === 0) {
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
const offset = Math.min(sortedDurationsMs.length - 1, Math.floor(p * sortedDurationsMs.length));
|
|
575
|
+
return sortedDurationsMs[offset] ?? null;
|
|
576
|
+
}
|
|
577
|
+
__name(percentileMs, "percentileMs");
|
|
487
578
|
var DrizzleGovernanceQueries = class {
|
|
488
579
|
static {
|
|
489
580
|
__name(this, "DrizzleGovernanceQueries");
|
|
@@ -589,6 +680,189 @@ var DrizzleGovernanceQueries = class {
|
|
|
589
680
|
}
|
|
590
681
|
return result;
|
|
591
682
|
}
|
|
683
|
+
async runsInRange(range) {
|
|
684
|
+
const { start, end } = dayBoundsUtc(range);
|
|
685
|
+
return this.db.select().from(agentRun).where(and2(gte2(agentRun.startedAt, start), lte2(agentRun.startedAt, end)));
|
|
686
|
+
}
|
|
687
|
+
async runMetrics(range) {
|
|
688
|
+
const runs = await this.runsInRange(range);
|
|
689
|
+
let completed = 0;
|
|
690
|
+
let failed = 0;
|
|
691
|
+
let retries = 0;
|
|
692
|
+
const settledDurationsMs = [];
|
|
693
|
+
for (const run of runs) {
|
|
694
|
+
if (run.status === "completed") {
|
|
695
|
+
completed += 1;
|
|
696
|
+
} else if (run.status === "failed") {
|
|
697
|
+
failed += 1;
|
|
698
|
+
}
|
|
699
|
+
retries += run.retries;
|
|
700
|
+
if ((run.status === "completed" || run.status === "failed") && run.durationMs != null) {
|
|
701
|
+
settledDurationsMs.push(run.durationMs);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
settledDurationsMs.sort((left, right) => left - right);
|
|
705
|
+
return {
|
|
706
|
+
runs: runs.length,
|
|
707
|
+
completed,
|
|
708
|
+
failed,
|
|
709
|
+
successRate: runs.length === 0 ? 0 : completed / runs.length,
|
|
710
|
+
retries,
|
|
711
|
+
durationP50Ms: percentileMs(settledDurationsMs, 0.5),
|
|
712
|
+
durationP95Ms: percentileMs(settledDurationsMs, 0.95)
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
async runsByAgent(range) {
|
|
716
|
+
const runs = await this.runsInRange(range);
|
|
717
|
+
const byAgent = /* @__PURE__ */ new Map();
|
|
718
|
+
for (const run of runs) {
|
|
719
|
+
const agentName = run.agentName ?? DEFAULT_AGENT_BUCKET;
|
|
720
|
+
const bucket = byAgent.get(agentName) ?? {
|
|
721
|
+
runs: 0,
|
|
722
|
+
failed: 0,
|
|
723
|
+
retries: 0
|
|
724
|
+
};
|
|
725
|
+
bucket.runs += 1;
|
|
726
|
+
bucket.failed += run.status === "failed" ? 1 : 0;
|
|
727
|
+
bucket.retries += run.retries;
|
|
728
|
+
byAgent.set(agentName, bucket);
|
|
729
|
+
}
|
|
730
|
+
const result = [];
|
|
731
|
+
for (const [agentName, bucket] of byAgent) {
|
|
732
|
+
result.push({
|
|
733
|
+
agentName,
|
|
734
|
+
...bucket
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
result.sort((left, right) => right.runs - left.runs || left.agentName.localeCompare(right.agentName));
|
|
738
|
+
return result;
|
|
739
|
+
}
|
|
740
|
+
async runErrors(range) {
|
|
741
|
+
const { start, end } = dayBoundsUtc(range);
|
|
742
|
+
const runs = await this.db.select().from(agentRun).where(and2(gte2(agentRun.startedAt, start), lte2(agentRun.startedAt, end), eq2(agentRun.status, "failed")));
|
|
743
|
+
const byError = /* @__PURE__ */ new Map();
|
|
744
|
+
for (const run of runs) {
|
|
745
|
+
const errorCode = run.errorCode ?? UNCLASSIFIED_ERROR_CODE;
|
|
746
|
+
byError.set(errorCode, (byError.get(errorCode) ?? 0) + 1);
|
|
747
|
+
}
|
|
748
|
+
const result = [];
|
|
749
|
+
for (const [errorCode, errorCount] of byError) {
|
|
750
|
+
result.push({
|
|
751
|
+
errorCode,
|
|
752
|
+
count: errorCount
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
result.sort((left, right) => right.count - left.count || left.errorCode.localeCompare(right.errorCode));
|
|
756
|
+
return result;
|
|
757
|
+
}
|
|
758
|
+
async runTrend(range) {
|
|
759
|
+
const runs = await this.runsInRange(range);
|
|
760
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
761
|
+
for (const run of runs) {
|
|
762
|
+
const day = run.startedAt.toISOString().slice(0, 10);
|
|
763
|
+
const bucket = byDay.get(day) ?? {
|
|
764
|
+
runs: 0,
|
|
765
|
+
failed: 0
|
|
766
|
+
};
|
|
767
|
+
bucket.runs += 1;
|
|
768
|
+
bucket.failed += run.status === "failed" ? 1 : 0;
|
|
769
|
+
byDay.set(day, bucket);
|
|
770
|
+
}
|
|
771
|
+
const result = [];
|
|
772
|
+
for (const [day, bucket] of byDay) {
|
|
773
|
+
result.push({
|
|
774
|
+
day,
|
|
775
|
+
...bucket
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
result.sort((left, right) => left.day.localeCompare(right.day));
|
|
779
|
+
return result;
|
|
780
|
+
}
|
|
781
|
+
async recentRuns(limit) {
|
|
782
|
+
const runs = await this.db.select().from(agentRun).orderBy(desc2(agentRun.startedAt), desc2(agentRun.id)).limit(limit);
|
|
783
|
+
return runs.map((run) => ({
|
|
784
|
+
runId: run.id,
|
|
785
|
+
threadId: run.threadId,
|
|
786
|
+
actorRef: run.actorRef,
|
|
787
|
+
agentName: run.agentName ?? null,
|
|
788
|
+
status: run.status,
|
|
789
|
+
durationMs: run.durationMs ?? null,
|
|
790
|
+
errorCode: run.errorCode ?? null,
|
|
791
|
+
errorMessage: run.errorMessage ?? null,
|
|
792
|
+
retries: run.retries,
|
|
793
|
+
startedAt: run.startedAt.toISOString(),
|
|
794
|
+
promptHash: run.promptHash ?? null
|
|
795
|
+
}));
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Tool calls sitting `pending_approval`, oldest first (an inbox drains from the back), joined
|
|
799
|
+
* through their message to the owning thread for title/actorRef and to the message itself for
|
|
800
|
+
* `agentName` (null when the message carries none).
|
|
801
|
+
*/
|
|
802
|
+
async pendingApprovals(limit) {
|
|
803
|
+
const rows = await this.db.select({
|
|
804
|
+
toolCallId: agentToolCall.id,
|
|
805
|
+
toolName: agentToolCall.toolName,
|
|
806
|
+
input: agentToolCall.input,
|
|
807
|
+
threadId: agentThread.id,
|
|
808
|
+
threadTitle: agentThread.title,
|
|
809
|
+
actorRef: agentThread.actorRef,
|
|
810
|
+
agentName: agentMessage.agentName,
|
|
811
|
+
requestedAt: agentToolCall.createdAt
|
|
812
|
+
}).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
|
+
return rows.map((row) => ({
|
|
814
|
+
toolCallId: row.toolCallId,
|
|
815
|
+
toolName: row.toolName,
|
|
816
|
+
input: row.input,
|
|
817
|
+
threadId: row.threadId,
|
|
818
|
+
threadTitle: row.threadTitle,
|
|
819
|
+
actorRef: row.actorRef,
|
|
820
|
+
agentName: row.agentName ?? null,
|
|
821
|
+
requestedAt: row.requestedAt.toISOString()
|
|
822
|
+
}));
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Per-tool call/failure/rejection/latency rollup over the range, highest call count first.
|
|
826
|
+
* `p95ExecutionMs` is computed over calls that recorded a non-null `executionMs` (regardless of
|
|
827
|
+
* their final status); `null` when none did.
|
|
828
|
+
*/
|
|
829
|
+
async toolStats(range) {
|
|
830
|
+
const { start, end } = dayBoundsUtc(range);
|
|
831
|
+
const calls = await this.db.select().from(agentToolCall).where(and2(gte2(agentToolCall.createdAt, start), lte2(agentToolCall.createdAt, end)));
|
|
832
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
833
|
+
for (const call of calls) {
|
|
834
|
+
const key = `${call.toolName} ${call.toolType}`;
|
|
835
|
+
const bucket = byTool.get(key) ?? {
|
|
836
|
+
toolName: call.toolName,
|
|
837
|
+
toolType: call.toolType,
|
|
838
|
+
calls: 0,
|
|
839
|
+
failed: 0,
|
|
840
|
+
rejected: 0,
|
|
841
|
+
executionMs: []
|
|
842
|
+
};
|
|
843
|
+
bucket.calls += 1;
|
|
844
|
+
bucket.failed += call.status === "failed" ? 1 : 0;
|
|
845
|
+
bucket.rejected += call.status === "rejected" ? 1 : 0;
|
|
846
|
+
if (call.executionMs != null) {
|
|
847
|
+
bucket.executionMs.push(call.executionMs);
|
|
848
|
+
}
|
|
849
|
+
byTool.set(key, bucket);
|
|
850
|
+
}
|
|
851
|
+
const result = [];
|
|
852
|
+
for (const bucket of byTool.values()) {
|
|
853
|
+
bucket.executionMs.sort((left, right) => left - right);
|
|
854
|
+
result.push({
|
|
855
|
+
toolName: bucket.toolName,
|
|
856
|
+
toolType: bucket.toolType,
|
|
857
|
+
calls: bucket.calls,
|
|
858
|
+
failed: bucket.failed,
|
|
859
|
+
rejected: bucket.rejected,
|
|
860
|
+
p95ExecutionMs: percentileMs(bucket.executionMs, 0.95)
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
result.sort((left, right) => right.calls - left.calls || left.toolName.localeCompare(right.toolName));
|
|
864
|
+
return result;
|
|
865
|
+
}
|
|
592
866
|
};
|
|
593
867
|
|
|
594
868
|
// src/drizzle-pricing-store.ts
|
|
@@ -706,6 +980,7 @@ export {
|
|
|
706
980
|
DrizzlePricingStore,
|
|
707
981
|
agentMessage,
|
|
708
982
|
agentModelPricing,
|
|
983
|
+
agentRun,
|
|
709
984
|
agentSchema,
|
|
710
985
|
agentThread,
|
|
711
986
|
agentTokenUsage,
|