@dudousxd/nestjs-agent-store-drizzle 0.8.1 → 0.9.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 +52 -3
- package/dist/index.cjs +206 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +231 -16
- package/dist/index.d.ts +231 -16
- package/dist/index.js +207 -9
- package/dist/index.js.map +1 -1
- package/package.json +8 -6
package/dist/index.js
CHANGED
|
@@ -12,6 +12,8 @@ var agentThread = sqliteTable("agent_thread", {
|
|
|
12
12
|
mode: "boolean"
|
|
13
13
|
}).notNull().default(false),
|
|
14
14
|
activeStreamId: text("active_stream_id"),
|
|
15
|
+
/** The agent a new turn on this thread defaults to when the caller names none. */
|
|
16
|
+
defaultAgent: text("default_agent"),
|
|
15
17
|
createdAt: integer("created_at", {
|
|
16
18
|
mode: "timestamp_ms"
|
|
17
19
|
}).notNull(),
|
|
@@ -37,6 +39,9 @@ var agentMessage = sqliteTable("agent_message", {
|
|
|
37
39
|
toolResults: text("tool_results", {
|
|
38
40
|
mode: "json"
|
|
39
41
|
}).$type(),
|
|
42
|
+
attachments: text("attachments", {
|
|
43
|
+
mode: "json"
|
|
44
|
+
}).$type(),
|
|
40
45
|
followUps: text("follow_ups", {
|
|
41
46
|
mode: "json"
|
|
42
47
|
}).$type(),
|
|
@@ -44,6 +49,7 @@ var agentMessage = sqliteTable("agent_message", {
|
|
|
44
49
|
mode: "json"
|
|
45
50
|
}).$type(),
|
|
46
51
|
agentName: text("agent_name"),
|
|
52
|
+
runId: text("run_id"),
|
|
47
53
|
createdAt: integer("created_at", {
|
|
48
54
|
mode: "timestamp_ms"
|
|
49
55
|
}).notNull()
|
|
@@ -75,7 +81,9 @@ var agentToolCall = sqliteTable("agent_tool_call", {
|
|
|
75
81
|
}),
|
|
76
82
|
/** The run (turn) this call belongs to, for a trace deep-link; null for a pre-rollout row. */
|
|
77
83
|
runId: text("run_id")
|
|
78
|
-
})
|
|
84
|
+
}, (table) => [
|
|
85
|
+
index("agent_tool_call_message_idx").on(table.messageId)
|
|
86
|
+
]);
|
|
79
87
|
var agentTokenUsage = sqliteTable("agent_token_usage", {
|
|
80
88
|
id: text("id").primaryKey(),
|
|
81
89
|
threadId: text("thread_id").notNull().references(() => agentThread.id, {
|
|
@@ -134,7 +142,15 @@ var agentRun = sqliteTable("agent_run", {
|
|
|
134
142
|
mode: "timestamp_ms"
|
|
135
143
|
}),
|
|
136
144
|
/** sha256 hex of the run's resolved (pre-RAG) system prompt; null for a pre-existing run. */
|
|
137
|
-
promptHash: text("prompt_hash")
|
|
145
|
+
promptHash: text("prompt_hash"),
|
|
146
|
+
/**
|
|
147
|
+
* The run that delegated this one; null for a turn nobody delegated. The durable runtime's
|
|
148
|
+
* journal holds the same edge, but only there — this column is what lets a reader of run ROWS
|
|
149
|
+
* build the delegation tree and roll a child's cost up to the turn that asked for it, including
|
|
150
|
+
* for a detached child, which outlives its parent's turn and so is paired by nothing in the
|
|
151
|
+
* transcript.
|
|
152
|
+
*/
|
|
153
|
+
parentRunId: text("parent_run_id")
|
|
138
154
|
}, (table) => [
|
|
139
155
|
index("agent_run_started_idx").on(table.startedAt)
|
|
140
156
|
]);
|
|
@@ -157,6 +173,7 @@ var statements = [
|
|
|
157
173
|
title TEXT NOT NULL,
|
|
158
174
|
transient INTEGER NOT NULL DEFAULT 0,
|
|
159
175
|
active_stream_id TEXT,
|
|
176
|
+
default_agent TEXT,
|
|
160
177
|
created_at INTEGER NOT NULL,
|
|
161
178
|
updated_at INTEGER NOT NULL,
|
|
162
179
|
deleted_at INTEGER
|
|
@@ -170,9 +187,11 @@ var statements = [
|
|
|
170
187
|
content TEXT NOT NULL,
|
|
171
188
|
tool_calls TEXT,
|
|
172
189
|
tool_results TEXT,
|
|
190
|
+
attachments TEXT,
|
|
173
191
|
follow_ups TEXT,
|
|
174
192
|
usage TEXT,
|
|
175
193
|
agent_name TEXT,
|
|
194
|
+
run_id TEXT,
|
|
176
195
|
created_at INTEGER NOT NULL
|
|
177
196
|
)`,
|
|
178
197
|
`CREATE INDEX IF NOT EXISTS agent_message_thread_created_idx
|
|
@@ -192,6 +211,8 @@ var statements = [
|
|
|
192
211
|
executed_at INTEGER,
|
|
193
212
|
run_id TEXT
|
|
194
213
|
)`,
|
|
214
|
+
`CREATE INDEX IF NOT EXISTS agent_tool_call_message_idx
|
|
215
|
+
ON agent_tool_call (message_id)`,
|
|
195
216
|
`CREATE TABLE IF NOT EXISTS agent_token_usage (
|
|
196
217
|
id TEXT PRIMARY KEY NOT NULL,
|
|
197
218
|
thread_id TEXT NOT NULL REFERENCES agent_thread(id) ON DELETE CASCADE,
|
|
@@ -230,20 +251,59 @@ var statements = [
|
|
|
230
251
|
retries INTEGER NOT NULL DEFAULT 0,
|
|
231
252
|
started_at INTEGER NOT NULL,
|
|
232
253
|
settled_at INTEGER,
|
|
233
|
-
prompt_hash TEXT
|
|
254
|
+
prompt_hash TEXT,
|
|
255
|
+
parent_run_id TEXT
|
|
234
256
|
)`,
|
|
235
257
|
`CREATE INDEX IF NOT EXISTS agent_run_started_idx
|
|
236
258
|
ON agent_run (started_at)`
|
|
237
259
|
];
|
|
260
|
+
var additiveColumns = [
|
|
261
|
+
{
|
|
262
|
+
table: "agent_message",
|
|
263
|
+
column: "run_id",
|
|
264
|
+
ddl: "ALTER TABLE agent_message ADD COLUMN run_id TEXT"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
table: "agent_message",
|
|
268
|
+
column: "attachments",
|
|
269
|
+
ddl: "ALTER TABLE agent_message ADD COLUMN attachments TEXT"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
table: "agent_thread",
|
|
273
|
+
column: "default_agent",
|
|
274
|
+
ddl: "ALTER TABLE agent_thread ADD COLUMN default_agent TEXT"
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
table: "agent_run",
|
|
278
|
+
column: "parent_run_id",
|
|
279
|
+
ddl: "ALTER TABLE agent_run ADD COLUMN parent_run_id TEXT"
|
|
280
|
+
}
|
|
281
|
+
];
|
|
238
282
|
async function ensureAgentSchema(db) {
|
|
239
283
|
for (const statement of statements) {
|
|
240
284
|
await db.run(sql.raw(statement));
|
|
241
285
|
}
|
|
286
|
+
for (const { table, column, ddl } of additiveColumns) {
|
|
287
|
+
const existing = await db.all(sql.raw(`PRAGMA table_info(${table})`));
|
|
288
|
+
if (!existing.some((row) => row.name === column)) {
|
|
289
|
+
await db.run(sql.raw(ddl));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
242
292
|
}
|
|
243
293
|
__name(ensureAgentSchema, "ensureAgentSchema");
|
|
244
294
|
|
|
245
295
|
// src/drizzle-agent-store.ts
|
|
246
|
-
import { and, asc, desc, eq, gte, inArray, isNull, lte, sql as sql2 } from "drizzle-orm";
|
|
296
|
+
import { and, asc, desc, eq, gte, inArray, isNotNull, isNull, lte, sql as sql2 } from "drizzle-orm";
|
|
297
|
+
var TURN_MESSAGE_COLUMNS = {
|
|
298
|
+
id: agentMessage.id,
|
|
299
|
+
role: agentMessage.role,
|
|
300
|
+
content: agentMessage.content,
|
|
301
|
+
agentName: agentMessage.agentName,
|
|
302
|
+
toolCalls: agentMessage.toolCalls,
|
|
303
|
+
toolResults: agentMessage.toolResults,
|
|
304
|
+
attachments: agentMessage.attachments,
|
|
305
|
+
createdAt: agentMessage.createdAt
|
|
306
|
+
};
|
|
247
307
|
var DrizzleAgentStore = class {
|
|
248
308
|
static {
|
|
249
309
|
__name(this, "DrizzleAgentStore");
|
|
@@ -261,6 +321,7 @@ var DrizzleAgentStore = class {
|
|
|
261
321
|
title: input.title ?? "New chat",
|
|
262
322
|
transient: input.transient ?? false,
|
|
263
323
|
activeStreamId: null,
|
|
324
|
+
defaultAgent: null,
|
|
264
325
|
createdAt: now,
|
|
265
326
|
updatedAt: now,
|
|
266
327
|
deletedAt: null
|
|
@@ -283,6 +344,50 @@ var DrizzleAgentStore = class {
|
|
|
283
344
|
} : {}
|
|
284
345
|
};
|
|
285
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* The window a turn reads off a thread: its newest `messageLimit` messages (oldest first), the
|
|
349
|
+
* title, the default agent, and whether the thread has EVER been answered.
|
|
350
|
+
*
|
|
351
|
+
* {@link getThread} is the wrong read for a turn. It materializes the transcript — every message,
|
|
352
|
+
* every attachment, every tool output the thread ever recorded — and the run then journals what it
|
|
353
|
+
* loaded, so a long thread pays for its whole history on every turn and again on every replay.
|
|
354
|
+
*
|
|
355
|
+
* `hasAssistantMessage` is answered over the WHOLE thread, not the returned page: it answers "has
|
|
356
|
+
* this conversation been answered before?", and a thread whose window happens to hold only the
|
|
357
|
+
* user's last questions has still been answered. `null` when the thread is unknown or
|
|
358
|
+
* soft-deleted, same as {@link getThread}.
|
|
359
|
+
*/
|
|
360
|
+
async loadThreadForTurn(query) {
|
|
361
|
+
const [thread] = await this.db.select({
|
|
362
|
+
title: agentThread.title,
|
|
363
|
+
defaultAgent: agentThread.defaultAgent
|
|
364
|
+
}).from(agentThread).where(and(eq(agentThread.id, query.threadId), isNull(agentThread.deletedAt)));
|
|
365
|
+
if (thread === void 0) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const messages = await this.loadTurnWindow(query.threadId, query.messageLimit);
|
|
369
|
+
const [answered] = await this.db.select({
|
|
370
|
+
id: agentMessage.id
|
|
371
|
+
}).from(agentMessage).where(and(eq(agentMessage.threadId, query.threadId), eq(agentMessage.role, "assistant"))).limit(1);
|
|
372
|
+
return {
|
|
373
|
+
title: thread.title,
|
|
374
|
+
defaultAgent: thread.defaultAgent,
|
|
375
|
+
hasAssistantMessage: answered !== void 0,
|
|
376
|
+
messages
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* The newest `limit` messages of a thread, oldest first, projected to the columns a model turn
|
|
381
|
+
* reads. Read newest-first so the LIMIT is the database's job, then reversed for the prompt.
|
|
382
|
+
*/
|
|
383
|
+
async loadTurnWindow(threadId, limit) {
|
|
384
|
+
if (limit !== void 0 && limit <= 0) {
|
|
385
|
+
return [];
|
|
386
|
+
}
|
|
387
|
+
const window = this.db.select(TURN_MESSAGE_COLUMNS).from(agentMessage).where(eq(agentMessage.threadId, threadId)).orderBy(desc(agentMessage.createdAt), desc(agentMessage.id));
|
|
388
|
+
const rows = limit === void 0 ? await window : await window.limit(limit);
|
|
389
|
+
return rows.reverse().map((row) => this.toStoredMessage(row));
|
|
390
|
+
}
|
|
286
391
|
async listThreads(actorRef, limit = 50) {
|
|
287
392
|
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);
|
|
288
393
|
const summaries = [];
|
|
@@ -313,6 +418,7 @@ var DrizzleAgentStore = class {
|
|
|
313
418
|
title: source.title,
|
|
314
419
|
transient: false,
|
|
315
420
|
activeStreamId: null,
|
|
421
|
+
defaultAgent: source.defaultAgent,
|
|
316
422
|
createdAt: now,
|
|
317
423
|
updatedAt: now,
|
|
318
424
|
deletedAt: null
|
|
@@ -326,9 +432,14 @@ var DrizzleAgentStore = class {
|
|
|
326
432
|
content: message.content,
|
|
327
433
|
toolCalls: message.toolCalls,
|
|
328
434
|
toolResults: message.toolResults,
|
|
435
|
+
attachments: message.attachments,
|
|
329
436
|
followUps: message.followUps,
|
|
330
437
|
usage: message.usage,
|
|
331
438
|
agentName: message.agentName,
|
|
439
|
+
// The copy is the same message, so it keeps the run that wrote it. A reader asking which
|
|
440
|
+
// turn produced this text gets the truthful answer; the run's own thread is still the
|
|
441
|
+
// original, so a run-scoped read never picks the fork's rows up.
|
|
442
|
+
runId: message.runId,
|
|
332
443
|
createdAt: message.createdAt
|
|
333
444
|
})));
|
|
334
445
|
}
|
|
@@ -348,9 +459,10 @@ var DrizzleAgentStore = class {
|
|
|
348
459
|
}
|
|
349
460
|
async runForToolCall(toolCallId) {
|
|
350
461
|
const [row] = await this.db.select({
|
|
462
|
+
runId: agentToolCall.runId,
|
|
351
463
|
activeStreamId: agentThread.activeStreamId
|
|
352
464
|
}).from(agentToolCall).innerJoin(agentMessage, eq(agentToolCall.messageId, agentMessage.id)).innerJoin(agentThread, eq(agentMessage.threadId, agentThread.id)).where(eq(agentToolCall.id, toolCallId));
|
|
353
|
-
return row?.activeStreamId ?? null;
|
|
465
|
+
return row?.runId ?? row?.activeStreamId ?? null;
|
|
354
466
|
}
|
|
355
467
|
async ownerOfActiveStream(runId) {
|
|
356
468
|
const [thread] = await this.db.select({
|
|
@@ -375,7 +487,43 @@ var DrizzleAgentStore = class {
|
|
|
375
487
|
activeStreamId: runId
|
|
376
488
|
}).where(eq(agentThread.id, threadId));
|
|
377
489
|
}
|
|
378
|
-
/**
|
|
490
|
+
/**
|
|
491
|
+
* Patch a thread's `title` and/or `defaultAgent`. Each field is only touched when PRESENT in
|
|
492
|
+
* `patch` — `defaultAgent: null` clears it (falls back to the app default), while an omitted
|
|
493
|
+
* `defaultAgent` leaves whatever is stored untouched. A no-op (including `updatedAt`) when the
|
|
494
|
+
* patch is empty or the thread doesn't exist.
|
|
495
|
+
*/
|
|
496
|
+
async updateThread(threadId, patch) {
|
|
497
|
+
const updates = {};
|
|
498
|
+
if (patch.title !== void 0) {
|
|
499
|
+
updates.title = patch.title;
|
|
500
|
+
}
|
|
501
|
+
if (patch.defaultAgent !== void 0) {
|
|
502
|
+
updates.defaultAgent = patch.defaultAgent;
|
|
503
|
+
}
|
|
504
|
+
if (Object.keys(updates).length === 0) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
updates.updatedAt = /* @__PURE__ */ new Date();
|
|
508
|
+
await this.db.update(agentThread).set(updates).where(eq(agentThread.id, threadId));
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* The thread's default agent, projected — the caller wants one nullable scalar to decide which
|
|
512
|
+
* agent answers the next turn, and {@link getThread} would materialize the whole transcript to
|
|
513
|
+
* hand it over. `null` when the thread is unknown, soft-deleted, or has no default set.
|
|
514
|
+
*/
|
|
515
|
+
async defaultAgentForThread(threadId) {
|
|
516
|
+
const [thread] = await this.db.select({
|
|
517
|
+
defaultAgent: agentThread.defaultAgent
|
|
518
|
+
}).from(agentThread).where(and(eq(agentThread.id, threadId), isNull(agentThread.deletedAt)));
|
|
519
|
+
return thread?.defaultAgent ?? null;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Persist the start of a run (turn). Replay-safe: called under a durable localStep.
|
|
523
|
+
*
|
|
524
|
+
* Takes the SPI's own {@link RecordRunStartInput} rather than a hand-copied shape: a field added
|
|
525
|
+
* to the input is otherwise accepted and dropped, silently, by every adapter that re-declares it.
|
|
526
|
+
*/
|
|
379
527
|
async recordRunStart(run) {
|
|
380
528
|
const runRow = {
|
|
381
529
|
id: run.runId,
|
|
@@ -389,7 +537,8 @@ var DrizzleAgentStore = class {
|
|
|
389
537
|
retries: 0,
|
|
390
538
|
startedAt: /* @__PURE__ */ new Date(),
|
|
391
539
|
settledAt: null,
|
|
392
|
-
promptHash: run.promptHash ?? null
|
|
540
|
+
promptHash: run.promptHash ?? null,
|
|
541
|
+
parentRunId: run.parentRunId ?? null
|
|
393
542
|
};
|
|
394
543
|
await this.db.insert(agentRun).values(runRow);
|
|
395
544
|
}
|
|
@@ -429,9 +578,11 @@ var DrizzleAgentStore = class {
|
|
|
429
578
|
content: input.content,
|
|
430
579
|
toolCalls: input.toolCalls ?? null,
|
|
431
580
|
toolResults: input.toolResults ?? null,
|
|
581
|
+
attachments: input.attachments ?? null,
|
|
432
582
|
followUps: input.followUps ?? null,
|
|
433
583
|
usage: input.usage ?? null,
|
|
434
584
|
agentName: input.agentName ?? null,
|
|
585
|
+
runId: input.runId ?? null,
|
|
435
586
|
createdAt: now
|
|
436
587
|
};
|
|
437
588
|
await this.db.insert(agentMessage).values(message);
|
|
@@ -440,6 +591,11 @@ var DrizzleAgentStore = class {
|
|
|
440
591
|
}).where(eq(agentThread.id, input.threadId));
|
|
441
592
|
return this.toStoredMessage(message);
|
|
442
593
|
}
|
|
594
|
+
async setMessageToolResults(messageId, results) {
|
|
595
|
+
await this.db.update(agentMessage).set({
|
|
596
|
+
toolResults: results
|
|
597
|
+
}).where(eq(agentMessage.id, messageId));
|
|
598
|
+
}
|
|
443
599
|
async truncateFrom(threadId, messageId) {
|
|
444
600
|
const messages = await this.db.select().from(agentMessage).where(eq(agentMessage.threadId, threadId)).orderBy(asc(agentMessage.createdAt), asc(agentMessage.id));
|
|
445
601
|
const cutoff = messages.findIndex((message) => message.id === messageId);
|
|
@@ -488,6 +644,39 @@ var DrizzleAgentStore = class {
|
|
|
488
644
|
}
|
|
489
645
|
await this.db.update(agentToolCall).set(updates).where(eq(agentToolCall.id, input.toolCallId));
|
|
490
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Of `mediaIds`, the ones a surviving message in one of this actor's threads still carries.
|
|
649
|
+
*
|
|
650
|
+
* Reads the `attachments` JSON back out and matches in memory rather than pushing the match into
|
|
651
|
+
* SQL: the column holds an array of objects, and every dialect this adapter's sibling targets
|
|
652
|
+
* spells that query differently (`jsonb` containment, `json_table`, `json_each`), while none of
|
|
653
|
+
* them can use an index for it anyway. The scan is bounded by ONE actor's attachment-bearing
|
|
654
|
+
* messages, which is a small set — attachments are rare, and a message without one is excluded
|
|
655
|
+
* by the `is not null` before any JSON is parsed.
|
|
656
|
+
*
|
|
657
|
+
* Soft-deleted threads are deliberately included: their message rows survive, so the bytes they
|
|
658
|
+
* point at are still reachable from stored state and are not garbage.
|
|
659
|
+
*/
|
|
660
|
+
async referencedMediaIds(actorRef, mediaIds) {
|
|
661
|
+
if (mediaIds.length === 0) {
|
|
662
|
+
return [];
|
|
663
|
+
}
|
|
664
|
+
const rows = await this.db.select({
|
|
665
|
+
attachments: agentMessage.attachments
|
|
666
|
+
}).from(agentMessage).innerJoin(agentThread, eq(agentMessage.threadId, agentThread.id)).where(and(eq(agentThread.actorRef, actorRef), isNotNull(agentMessage.attachments)));
|
|
667
|
+
const wanted = new Set(mediaIds);
|
|
668
|
+
const found = /* @__PURE__ */ new Set();
|
|
669
|
+
for (const row of rows) {
|
|
670
|
+
for (const attachment of row.attachments ?? []) {
|
|
671
|
+
if (wanted.has(attachment.mediaId)) {
|
|
672
|
+
found.add(attachment.mediaId);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return [
|
|
677
|
+
...wanted
|
|
678
|
+
].filter((mediaId) => found.has(mediaId));
|
|
679
|
+
}
|
|
491
680
|
async recordUsage(input) {
|
|
492
681
|
await this.db.insert(agentTokenUsage).values({
|
|
493
682
|
id: crypto.randomUUID(),
|
|
@@ -522,6 +711,7 @@ var DrizzleAgentStore = class {
|
|
|
522
711
|
transient: thread.transient,
|
|
523
712
|
createdAt: thread.createdAt.toISOString(),
|
|
524
713
|
updatedAt: thread.updatedAt.toISOString(),
|
|
714
|
+
defaultAgent: thread.defaultAgent,
|
|
525
715
|
...lastContent !== void 0 ? {
|
|
526
716
|
lastMessagePreview: lastContent.slice(0, 120)
|
|
527
717
|
} : {}
|
|
@@ -539,6 +729,9 @@ var DrizzleAgentStore = class {
|
|
|
539
729
|
...message.toolResults != null ? {
|
|
540
730
|
toolResults: message.toolResults
|
|
541
731
|
} : {},
|
|
732
|
+
...message.attachments != null ? {
|
|
733
|
+
attachments: message.attachments
|
|
734
|
+
} : {},
|
|
542
735
|
...message.followUps != null ? {
|
|
543
736
|
followUps: message.followUps
|
|
544
737
|
} : {},
|
|
@@ -547,6 +740,9 @@ var DrizzleAgentStore = class {
|
|
|
547
740
|
} : {},
|
|
548
741
|
...message.agentName != null ? {
|
|
549
742
|
agentName: message.agentName
|
|
743
|
+
} : {},
|
|
744
|
+
...message.runId != null ? {
|
|
745
|
+
runId: message.runId
|
|
550
746
|
} : {}
|
|
551
747
|
};
|
|
552
748
|
}
|
|
@@ -580,7 +776,8 @@ __name(isToolKind, "isToolKind");
|
|
|
580
776
|
var RUN_STATUSES = [
|
|
581
777
|
"running",
|
|
582
778
|
"completed",
|
|
583
|
-
"failed"
|
|
779
|
+
"failed",
|
|
780
|
+
"cancelled"
|
|
584
781
|
];
|
|
585
782
|
function isRunStatus(value) {
|
|
586
783
|
return RUN_STATUSES.includes(value);
|
|
@@ -621,7 +818,8 @@ function toRecentRunRow(run) {
|
|
|
621
818
|
errorMessage: run.errorMessage ?? null,
|
|
622
819
|
retries: run.retries,
|
|
623
820
|
startedAt: run.startedAt.toISOString(),
|
|
624
|
-
promptHash: run.promptHash ?? null
|
|
821
|
+
promptHash: run.promptHash ?? null,
|
|
822
|
+
parentRunId: run.parentRunId ?? null
|
|
625
823
|
};
|
|
626
824
|
}
|
|
627
825
|
__name(toRecentRunRow, "toRecentRunRow");
|