@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.cjs
CHANGED
|
@@ -27,6 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
DrizzlePricingStore: () => DrizzlePricingStore,
|
|
28
28
|
agentMessage: () => agentMessage,
|
|
29
29
|
agentModelPricing: () => agentModelPricing,
|
|
30
|
+
agentRun: () => agentRun,
|
|
30
31
|
agentSchema: () => agentSchema,
|
|
31
32
|
agentThread: () => agentThread,
|
|
32
33
|
agentTokenUsage: () => agentTokenUsage,
|
|
@@ -147,12 +148,36 @@ var agentModelPricing = (0, import_sqlite_core.sqliteTable)("agent_model_pricing
|
|
|
147
148
|
mode: "boolean"
|
|
148
149
|
}).notNull()
|
|
149
150
|
});
|
|
151
|
+
var agentRun = (0, import_sqlite_core.sqliteTable)("agent_run", {
|
|
152
|
+
id: (0, import_sqlite_core.text)("id").primaryKey(),
|
|
153
|
+
threadId: (0, import_sqlite_core.text)("thread_id").notNull().references(() => agentThread.id, {
|
|
154
|
+
onDelete: "cascade"
|
|
155
|
+
}),
|
|
156
|
+
actorRef: (0, import_sqlite_core.text)("actor_ref").notNull(),
|
|
157
|
+
agentName: (0, import_sqlite_core.text)("agent_name"),
|
|
158
|
+
status: (0, import_sqlite_core.text)("status").$type().notNull(),
|
|
159
|
+
durationMs: (0, import_sqlite_core.integer)("duration_ms"),
|
|
160
|
+
errorCode: (0, import_sqlite_core.text)("error_code"),
|
|
161
|
+
errorMessage: (0, import_sqlite_core.text)("error_message"),
|
|
162
|
+
retries: (0, import_sqlite_core.integer)("retries").notNull().default(0),
|
|
163
|
+
startedAt: (0, import_sqlite_core.integer)("started_at", {
|
|
164
|
+
mode: "timestamp_ms"
|
|
165
|
+
}).notNull(),
|
|
166
|
+
settledAt: (0, import_sqlite_core.integer)("settled_at", {
|
|
167
|
+
mode: "timestamp_ms"
|
|
168
|
+
}),
|
|
169
|
+
/** sha256 hex of the run's resolved (pre-RAG) system prompt; null for a pre-existing run. */
|
|
170
|
+
promptHash: (0, import_sqlite_core.text)("prompt_hash")
|
|
171
|
+
}, (table) => [
|
|
172
|
+
(0, import_sqlite_core.index)("agent_run_started_idx").on(table.startedAt)
|
|
173
|
+
]);
|
|
150
174
|
var agentSchema = {
|
|
151
175
|
agentThread,
|
|
152
176
|
agentMessage,
|
|
153
177
|
agentToolCall,
|
|
154
178
|
agentTokenUsage,
|
|
155
|
-
agentModelPricing
|
|
179
|
+
agentModelPricing,
|
|
180
|
+
agentRun
|
|
156
181
|
};
|
|
157
182
|
|
|
158
183
|
// src/ensure-schema.ts
|
|
@@ -224,7 +249,23 @@ var statements = [
|
|
|
224
249
|
cache_read_price_per_1m REAL,
|
|
225
250
|
effective_from INTEGER NOT NULL,
|
|
226
251
|
is_current INTEGER NOT NULL
|
|
227
|
-
)
|
|
252
|
+
)`,
|
|
253
|
+
`CREATE TABLE IF NOT EXISTS agent_run (
|
|
254
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
255
|
+
thread_id TEXT NOT NULL REFERENCES agent_thread(id) ON DELETE CASCADE,
|
|
256
|
+
actor_ref TEXT NOT NULL,
|
|
257
|
+
agent_name TEXT,
|
|
258
|
+
status TEXT NOT NULL,
|
|
259
|
+
duration_ms INTEGER,
|
|
260
|
+
error_code TEXT,
|
|
261
|
+
error_message TEXT,
|
|
262
|
+
retries INTEGER NOT NULL DEFAULT 0,
|
|
263
|
+
started_at INTEGER NOT NULL,
|
|
264
|
+
settled_at INTEGER,
|
|
265
|
+
prompt_hash TEXT
|
|
266
|
+
)`,
|
|
267
|
+
`CREATE INDEX IF NOT EXISTS agent_run_started_idx
|
|
268
|
+
ON agent_run (started_at)`
|
|
228
269
|
];
|
|
229
270
|
async function ensureAgentSchema(db) {
|
|
230
271
|
for (const statement of statements) {
|
|
@@ -366,6 +407,47 @@ var DrizzleAgentStore = class {
|
|
|
366
407
|
activeStreamId: runId
|
|
367
408
|
}).where((0, import_drizzle_orm2.eq)(agentThread.id, threadId));
|
|
368
409
|
}
|
|
410
|
+
/** Persist the start of a run (turn). Replay-safe: called under a durable localStep. */
|
|
411
|
+
async recordRunStart(run) {
|
|
412
|
+
const runRow = {
|
|
413
|
+
id: run.runId,
|
|
414
|
+
threadId: run.threadId,
|
|
415
|
+
actorRef: run.actorRef,
|
|
416
|
+
agentName: run.agentName ?? null,
|
|
417
|
+
status: "running",
|
|
418
|
+
durationMs: null,
|
|
419
|
+
errorCode: null,
|
|
420
|
+
errorMessage: null,
|
|
421
|
+
retries: 0,
|
|
422
|
+
startedAt: /* @__PURE__ */ new Date(),
|
|
423
|
+
settledAt: null,
|
|
424
|
+
promptHash: run.promptHash ?? null
|
|
425
|
+
};
|
|
426
|
+
await this.db.insert(agentRun).values(runRow);
|
|
427
|
+
}
|
|
428
|
+
/** Settle a run's outcome. A no-op when the run is unknown (mirrors `setTitle`/`updateThread`). */
|
|
429
|
+
async recordRunEnd(end) {
|
|
430
|
+
const updates = {
|
|
431
|
+
status: end.status,
|
|
432
|
+
settledAt: /* @__PURE__ */ new Date()
|
|
433
|
+
};
|
|
434
|
+
if (end.durationMs !== void 0) {
|
|
435
|
+
updates.durationMs = end.durationMs;
|
|
436
|
+
}
|
|
437
|
+
if (end.errorCode !== void 0) {
|
|
438
|
+
updates.errorCode = end.errorCode;
|
|
439
|
+
}
|
|
440
|
+
if (end.errorMessage !== void 0) {
|
|
441
|
+
updates.errorMessage = end.errorMessage;
|
|
442
|
+
}
|
|
443
|
+
await this.db.update(agentRun).set(updates).where((0, import_drizzle_orm2.eq)(agentRun.id, end.runId));
|
|
444
|
+
}
|
|
445
|
+
/** Bump the run's llm-step retry counter. Atomic `retries = retries + 1`, no read-modify-write. */
|
|
446
|
+
async bumpRunRetries(runId) {
|
|
447
|
+
await this.db.update(agentRun).set({
|
|
448
|
+
retries: import_drizzle_orm2.sql`${agentRun.retries} + 1`
|
|
449
|
+
}).where((0, import_drizzle_orm2.eq)(agentRun.id, runId));
|
|
450
|
+
}
|
|
369
451
|
async appendMessage(input) {
|
|
370
452
|
const [thread] = await this.db.select().from(agentThread).where((0, import_drizzle_orm2.eq)(agentThread.id, input.threadId));
|
|
371
453
|
if (thread === void 0) {
|
|
@@ -504,6 +586,8 @@ var DrizzleAgentStore = class {
|
|
|
504
586
|
// src/drizzle-governance-queries.ts
|
|
505
587
|
var import_nestjs_agent_core = require("@dudousxd/nestjs-agent-core");
|
|
506
588
|
var import_drizzle_orm3 = require("drizzle-orm");
|
|
589
|
+
var UNCLASSIFIED_ERROR_CODE = "unknown";
|
|
590
|
+
var DEFAULT_AGENT_BUCKET = "(default)";
|
|
507
591
|
function toUsageInput(row) {
|
|
508
592
|
return {
|
|
509
593
|
modelId: row.modelId,
|
|
@@ -518,6 +602,14 @@ function toUsageInput(row) {
|
|
|
518
602
|
};
|
|
519
603
|
}
|
|
520
604
|
__name(toUsageInput, "toUsageInput");
|
|
605
|
+
function percentileMs(sortedDurationsMs, p) {
|
|
606
|
+
if (sortedDurationsMs.length === 0) {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
const offset = Math.min(sortedDurationsMs.length - 1, Math.floor(p * sortedDurationsMs.length));
|
|
610
|
+
return sortedDurationsMs[offset] ?? null;
|
|
611
|
+
}
|
|
612
|
+
__name(percentileMs, "percentileMs");
|
|
521
613
|
var DrizzleGovernanceQueries = class {
|
|
522
614
|
static {
|
|
523
615
|
__name(this, "DrizzleGovernanceQueries");
|
|
@@ -623,6 +715,189 @@ var DrizzleGovernanceQueries = class {
|
|
|
623
715
|
}
|
|
624
716
|
return result;
|
|
625
717
|
}
|
|
718
|
+
async runsInRange(range) {
|
|
719
|
+
const { start, end } = (0, import_nestjs_agent_core.dayBoundsUtc)(range);
|
|
720
|
+
return this.db.select().from(agentRun).where((0, import_drizzle_orm3.and)((0, import_drizzle_orm3.gte)(agentRun.startedAt, start), (0, import_drizzle_orm3.lte)(agentRun.startedAt, end)));
|
|
721
|
+
}
|
|
722
|
+
async runMetrics(range) {
|
|
723
|
+
const runs = await this.runsInRange(range);
|
|
724
|
+
let completed = 0;
|
|
725
|
+
let failed = 0;
|
|
726
|
+
let retries = 0;
|
|
727
|
+
const settledDurationsMs = [];
|
|
728
|
+
for (const run of runs) {
|
|
729
|
+
if (run.status === "completed") {
|
|
730
|
+
completed += 1;
|
|
731
|
+
} else if (run.status === "failed") {
|
|
732
|
+
failed += 1;
|
|
733
|
+
}
|
|
734
|
+
retries += run.retries;
|
|
735
|
+
if ((run.status === "completed" || run.status === "failed") && run.durationMs != null) {
|
|
736
|
+
settledDurationsMs.push(run.durationMs);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
settledDurationsMs.sort((left, right) => left - right);
|
|
740
|
+
return {
|
|
741
|
+
runs: runs.length,
|
|
742
|
+
completed,
|
|
743
|
+
failed,
|
|
744
|
+
successRate: runs.length === 0 ? 0 : completed / runs.length,
|
|
745
|
+
retries,
|
|
746
|
+
durationP50Ms: percentileMs(settledDurationsMs, 0.5),
|
|
747
|
+
durationP95Ms: percentileMs(settledDurationsMs, 0.95)
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
async runsByAgent(range) {
|
|
751
|
+
const runs = await this.runsInRange(range);
|
|
752
|
+
const byAgent = /* @__PURE__ */ new Map();
|
|
753
|
+
for (const run of runs) {
|
|
754
|
+
const agentName = run.agentName ?? DEFAULT_AGENT_BUCKET;
|
|
755
|
+
const bucket = byAgent.get(agentName) ?? {
|
|
756
|
+
runs: 0,
|
|
757
|
+
failed: 0,
|
|
758
|
+
retries: 0
|
|
759
|
+
};
|
|
760
|
+
bucket.runs += 1;
|
|
761
|
+
bucket.failed += run.status === "failed" ? 1 : 0;
|
|
762
|
+
bucket.retries += run.retries;
|
|
763
|
+
byAgent.set(agentName, bucket);
|
|
764
|
+
}
|
|
765
|
+
const result = [];
|
|
766
|
+
for (const [agentName, bucket] of byAgent) {
|
|
767
|
+
result.push({
|
|
768
|
+
agentName,
|
|
769
|
+
...bucket
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
result.sort((left, right) => right.runs - left.runs || left.agentName.localeCompare(right.agentName));
|
|
773
|
+
return result;
|
|
774
|
+
}
|
|
775
|
+
async runErrors(range) {
|
|
776
|
+
const { start, end } = (0, import_nestjs_agent_core.dayBoundsUtc)(range);
|
|
777
|
+
const runs = await this.db.select().from(agentRun).where((0, import_drizzle_orm3.and)((0, import_drizzle_orm3.gte)(agentRun.startedAt, start), (0, import_drizzle_orm3.lte)(agentRun.startedAt, end), (0, import_drizzle_orm3.eq)(agentRun.status, "failed")));
|
|
778
|
+
const byError = /* @__PURE__ */ new Map();
|
|
779
|
+
for (const run of runs) {
|
|
780
|
+
const errorCode = run.errorCode ?? UNCLASSIFIED_ERROR_CODE;
|
|
781
|
+
byError.set(errorCode, (byError.get(errorCode) ?? 0) + 1);
|
|
782
|
+
}
|
|
783
|
+
const result = [];
|
|
784
|
+
for (const [errorCode, errorCount] of byError) {
|
|
785
|
+
result.push({
|
|
786
|
+
errorCode,
|
|
787
|
+
count: errorCount
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
result.sort((left, right) => right.count - left.count || left.errorCode.localeCompare(right.errorCode));
|
|
791
|
+
return result;
|
|
792
|
+
}
|
|
793
|
+
async runTrend(range) {
|
|
794
|
+
const runs = await this.runsInRange(range);
|
|
795
|
+
const byDay = /* @__PURE__ */ new Map();
|
|
796
|
+
for (const run of runs) {
|
|
797
|
+
const day = run.startedAt.toISOString().slice(0, 10);
|
|
798
|
+
const bucket = byDay.get(day) ?? {
|
|
799
|
+
runs: 0,
|
|
800
|
+
failed: 0
|
|
801
|
+
};
|
|
802
|
+
bucket.runs += 1;
|
|
803
|
+
bucket.failed += run.status === "failed" ? 1 : 0;
|
|
804
|
+
byDay.set(day, bucket);
|
|
805
|
+
}
|
|
806
|
+
const result = [];
|
|
807
|
+
for (const [day, bucket] of byDay) {
|
|
808
|
+
result.push({
|
|
809
|
+
day,
|
|
810
|
+
...bucket
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
result.sort((left, right) => left.day.localeCompare(right.day));
|
|
814
|
+
return result;
|
|
815
|
+
}
|
|
816
|
+
async recentRuns(limit) {
|
|
817
|
+
const runs = await this.db.select().from(agentRun).orderBy((0, import_drizzle_orm3.desc)(agentRun.startedAt), (0, import_drizzle_orm3.desc)(agentRun.id)).limit(limit);
|
|
818
|
+
return runs.map((run) => ({
|
|
819
|
+
runId: run.id,
|
|
820
|
+
threadId: run.threadId,
|
|
821
|
+
actorRef: run.actorRef,
|
|
822
|
+
agentName: run.agentName ?? null,
|
|
823
|
+
status: run.status,
|
|
824
|
+
durationMs: run.durationMs ?? null,
|
|
825
|
+
errorCode: run.errorCode ?? null,
|
|
826
|
+
errorMessage: run.errorMessage ?? null,
|
|
827
|
+
retries: run.retries,
|
|
828
|
+
startedAt: run.startedAt.toISOString(),
|
|
829
|
+
promptHash: run.promptHash ?? null
|
|
830
|
+
}));
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Tool calls sitting `pending_approval`, oldest first (an inbox drains from the back), joined
|
|
834
|
+
* through their message to the owning thread for title/actorRef and to the message itself for
|
|
835
|
+
* `agentName` (null when the message carries none).
|
|
836
|
+
*/
|
|
837
|
+
async pendingApprovals(limit) {
|
|
838
|
+
const rows = await this.db.select({
|
|
839
|
+
toolCallId: agentToolCall.id,
|
|
840
|
+
toolName: agentToolCall.toolName,
|
|
841
|
+
input: agentToolCall.input,
|
|
842
|
+
threadId: agentThread.id,
|
|
843
|
+
threadTitle: agentThread.title,
|
|
844
|
+
actorRef: agentThread.actorRef,
|
|
845
|
+
agentName: agentMessage.agentName,
|
|
846
|
+
requestedAt: agentToolCall.createdAt
|
|
847
|
+
}).from(agentToolCall).innerJoin(agentMessage, (0, import_drizzle_orm3.eq)(agentToolCall.messageId, agentMessage.id)).innerJoin(agentThread, (0, import_drizzle_orm3.eq)(agentMessage.threadId, agentThread.id)).where((0, import_drizzle_orm3.eq)(agentToolCall.status, "pending_approval")).orderBy(agentToolCall.createdAt, agentToolCall.id).limit(limit);
|
|
848
|
+
return rows.map((row) => ({
|
|
849
|
+
toolCallId: row.toolCallId,
|
|
850
|
+
toolName: row.toolName,
|
|
851
|
+
input: row.input,
|
|
852
|
+
threadId: row.threadId,
|
|
853
|
+
threadTitle: row.threadTitle,
|
|
854
|
+
actorRef: row.actorRef,
|
|
855
|
+
agentName: row.agentName ?? null,
|
|
856
|
+
requestedAt: row.requestedAt.toISOString()
|
|
857
|
+
}));
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Per-tool call/failure/rejection/latency rollup over the range, highest call count first.
|
|
861
|
+
* `p95ExecutionMs` is computed over calls that recorded a non-null `executionMs` (regardless of
|
|
862
|
+
* their final status); `null` when none did.
|
|
863
|
+
*/
|
|
864
|
+
async toolStats(range) {
|
|
865
|
+
const { start, end } = (0, import_nestjs_agent_core.dayBoundsUtc)(range);
|
|
866
|
+
const calls = await this.db.select().from(agentToolCall).where((0, import_drizzle_orm3.and)((0, import_drizzle_orm3.gte)(agentToolCall.createdAt, start), (0, import_drizzle_orm3.lte)(agentToolCall.createdAt, end)));
|
|
867
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
868
|
+
for (const call of calls) {
|
|
869
|
+
const key = `${call.toolName} ${call.toolType}`;
|
|
870
|
+
const bucket = byTool.get(key) ?? {
|
|
871
|
+
toolName: call.toolName,
|
|
872
|
+
toolType: call.toolType,
|
|
873
|
+
calls: 0,
|
|
874
|
+
failed: 0,
|
|
875
|
+
rejected: 0,
|
|
876
|
+
executionMs: []
|
|
877
|
+
};
|
|
878
|
+
bucket.calls += 1;
|
|
879
|
+
bucket.failed += call.status === "failed" ? 1 : 0;
|
|
880
|
+
bucket.rejected += call.status === "rejected" ? 1 : 0;
|
|
881
|
+
if (call.executionMs != null) {
|
|
882
|
+
bucket.executionMs.push(call.executionMs);
|
|
883
|
+
}
|
|
884
|
+
byTool.set(key, bucket);
|
|
885
|
+
}
|
|
886
|
+
const result = [];
|
|
887
|
+
for (const bucket of byTool.values()) {
|
|
888
|
+
bucket.executionMs.sort((left, right) => left - right);
|
|
889
|
+
result.push({
|
|
890
|
+
toolName: bucket.toolName,
|
|
891
|
+
toolType: bucket.toolType,
|
|
892
|
+
calls: bucket.calls,
|
|
893
|
+
failed: bucket.failed,
|
|
894
|
+
rejected: bucket.rejected,
|
|
895
|
+
p95ExecutionMs: percentileMs(bucket.executionMs, 0.95)
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
result.sort((left, right) => right.calls - left.calls || left.toolName.localeCompare(right.toolName));
|
|
899
|
+
return result;
|
|
900
|
+
}
|
|
626
901
|
};
|
|
627
902
|
|
|
628
903
|
// src/drizzle-pricing-store.ts
|
|
@@ -741,6 +1016,7 @@ DrizzleAgentStoreModule = _ts_decorate([
|
|
|
741
1016
|
DrizzlePricingStore,
|
|
742
1017
|
agentMessage,
|
|
743
1018
|
agentModelPricing,
|
|
1019
|
+
agentRun,
|
|
744
1020
|
agentSchema,
|
|
745
1021
|
agentThread,
|
|
746
1022
|
agentTokenUsage,
|