stitchkit 0.110.2 → 0.110.4
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/CHANGELOG.md +16 -0
- package/dist/agent-runtime/archive-projection.d.ts +10 -0
- package/dist/agent-runtime/archive-projection.d.ts.map +1 -0
- package/dist/agent-runtime/branch-messages.d.ts.map +1 -1
- package/dist/agent-runtime/branch-provider.d.ts +123 -8
- package/dist/agent-runtime/branch-provider.d.ts.map +1 -1
- package/dist/agent-runtime/branch-replay.d.ts.map +1 -1
- package/dist/agent-runtime/branch-source.d.ts.map +1 -1
- package/dist/agent-runtime/provider-content.d.ts +183 -0
- package/dist/agent-runtime/provider-content.d.ts.map +1 -0
- package/dist/agent-runtime/provider-journal.d.ts +209 -0
- package/dist/agent-runtime/provider-journal.d.ts.map +1 -0
- package/dist/agent-runtime/run-provider-ledger.d.ts.map +1 -1
- package/dist/agent-runtime/sqlite-events.d.ts.map +1 -1
- package/dist/agent-runtime/store-archive.d.ts.map +1 -1
- package/dist/agent-runtime/store-branch.d.ts.map +1 -1
- package/dist/agent-runtime/store-events.d.ts +1041 -0
- package/dist/agent-runtime/store-events.d.ts.map +1 -1
- package/dist/agent-runtime/store-replay.d.ts +4 -0
- package/dist/agent-runtime/store-replay.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/harness.js +2 -2
- package/dist/entrypoints/agent-runtime/sqlite/bun.js +3 -3
- package/dist/entrypoints/agent-runtime/sqlite/node.js +3 -3
- package/dist/entrypoints/agent-runtime/testing.js +3 -3
- package/dist/entrypoints/agent-runtime.d.ts +1 -1
- package/dist/entrypoints/agent-runtime.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime.js +6 -4
- package/dist/{index-zbssway3.js → index-5pbtbce0.js} +4 -9
- package/dist/{index-79zxnh52.js → index-8jv0trhg.js} +10 -7
- package/dist/{index-e9rbhw1s.js → index-9ket8wzx.js} +2 -2
- package/dist/{index-6qeqdz0z.js → index-jna0ejsa.js} +178 -1
- package/dist/{index-s2bgwsyr.js → index-mpg1t7nv.js} +1058 -1119
- package/llms/agent-runtime.3.txt +1 -0
- package/llms/agent-runtime.txt +23 -2
- package/llms-full.txt +24 -2
- package/llms.txt +1 -1
- package/package.json +1 -1
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
AgentTimestampSchema2,
|
|
12
12
|
AgentMessageSchema2,
|
|
13
13
|
runStateForTerminalReason2,
|
|
14
|
-
AgentUsageSchema2,
|
|
15
14
|
AgentRunSchema2,
|
|
16
15
|
AgentSnapshotSchema2
|
|
17
16
|
} from "./index-8kzct1d6.js";
|
|
@@ -31,12 +30,16 @@ import {
|
|
|
31
30
|
SeedConversationInputSchema2,
|
|
32
31
|
AgentRecoverablePageSchema2,
|
|
33
32
|
AgentStoreTransitionSchema2,
|
|
33
|
+
AgentStoreCheckpointRecordSchema,
|
|
34
34
|
agentStoreEventDraft,
|
|
35
35
|
canonicalAgentJson2,
|
|
36
36
|
encodeAgentConversationArchive2,
|
|
37
37
|
decodeAgentConversationArchive2,
|
|
38
|
-
assistantStatus
|
|
39
|
-
|
|
38
|
+
assistantStatus,
|
|
39
|
+
ProviderJournalMessageSchema,
|
|
40
|
+
ProviderJournalRequestSchema,
|
|
41
|
+
ProviderJournalResponseSchema
|
|
42
|
+
} from "./index-jna0ejsa.js";
|
|
40
43
|
import {
|
|
41
44
|
StepEventPayloadSchema,
|
|
42
45
|
ParkEventPayloadSchema,
|
|
@@ -173,8 +176,15 @@ function emptyHead(conversationId) {
|
|
|
173
176
|
import { z as z15 } from "zod";
|
|
174
177
|
|
|
175
178
|
// src/agent-runtime/store-archive.ts
|
|
179
|
+
import { z as z5 } from "zod";
|
|
180
|
+
|
|
181
|
+
// src/agent-runtime/branch-replay.ts
|
|
182
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
176
183
|
import { z as z4 } from "zod";
|
|
177
184
|
|
|
185
|
+
// src/agent-runtime/store-reducer.ts
|
|
186
|
+
import { createHash } from "node:crypto";
|
|
187
|
+
|
|
178
188
|
// src/agent-runtime/store-snapshot.ts
|
|
179
189
|
function historyPositions(messages) {
|
|
180
190
|
const positions = new Map;
|
|
@@ -295,1136 +305,1074 @@ function referencedRunIds(messages) {
|
|
|
295
305
|
return [...new Set(messages.flatMap((message) => message.runId ? [message.runId] : []))];
|
|
296
306
|
}
|
|
297
307
|
|
|
298
|
-
// src/agent-runtime/store-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
driver.head.load(transaction, conversationId),
|
|
302
|
-
driver.history.load(transaction, conversationId),
|
|
303
|
-
driver.runs.listActive(transaction, conversationId)
|
|
304
|
-
]);
|
|
305
|
-
const head = AgentRuntimeHeadSchema2.parse(stored ?? emptyHead(conversationId));
|
|
306
|
-
const referencedRecords = await driver.runs.loadMany(transaction, {
|
|
307
|
-
conversationId,
|
|
308
|
-
runIds: referencedRunIds(messages)
|
|
309
|
-
});
|
|
310
|
-
return snapshotOf(head, messages, mergeRunRecords(activeRecords, referencedRecords));
|
|
308
|
+
// src/agent-runtime/store-reduce-shared.ts
|
|
309
|
+
function conflict(actualVersion) {
|
|
310
|
+
return { outcome: "conflict", actualVersion };
|
|
311
311
|
}
|
|
312
|
-
function
|
|
313
|
-
|
|
314
|
-
|
|
312
|
+
function applied(current, input, effects) {
|
|
313
|
+
const runs = input.runs ?? current.runs;
|
|
314
|
+
const messages = input.messages ?? current.messages;
|
|
315
|
+
return {
|
|
316
|
+
outcome: "applied",
|
|
317
|
+
snapshot: AgentSnapshotSchema2.parse({
|
|
318
|
+
...current,
|
|
319
|
+
version: current.version + 1,
|
|
320
|
+
runs,
|
|
321
|
+
messages: orderRunMessages(messages, runs)
|
|
322
|
+
}),
|
|
323
|
+
...effects?.runRecords?.length && { runRecords: effects.runRecords },
|
|
324
|
+
...effects?.admissionReceipt && { admissionReceipt: effects.admissionReceipt },
|
|
325
|
+
...effects?.seedReceipt && { seedReceipt: effects.seedReceipt },
|
|
326
|
+
...effects?.historyMutations?.length && { historyMutations: effects.historyMutations }
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/agent-runtime/store-reduce-history.ts
|
|
331
|
+
function reduceAccept(current, input) {
|
|
332
|
+
if (input.expectedVersion !== undefined && input.expectedVersion !== current.version) {
|
|
333
|
+
return conflict(current.version);
|
|
334
|
+
}
|
|
335
|
+
const coalescedRun = input.coalesceIntoRunId ? current.runs.find((candidate) => candidate.id === input.coalesceIntoRunId) : undefined;
|
|
336
|
+
if (input.coalesceIntoRunId !== undefined && (!coalescedRun || coalescedRun.conversationId !== input.input.conversationId || coalescedRun.state !== "queued" || coalescedRun.ownerId !== undefined || coalescedRun.terminalReason !== undefined)) {
|
|
337
|
+
return coalescedRun ? conflict(coalescedRun.revision) : { outcome: "not_found" };
|
|
338
|
+
}
|
|
339
|
+
if (input.run.conversationId !== input.input.conversationId || input.run.inputMessageIds.length !== 1 || input.run.inputMessageIds[0] !== input.input.id || input.run.state !== "queued" || input.run.revision !== 0 || input.run.executionSequence !== undefined || input.run.ownerId !== undefined || input.run.terminalReason !== undefined || input.run.terminalPolicyName !== undefined || input.input.role !== "user" && input.input.role !== "tool" || input.input.status !== "committed" || input.input.runId !== undefined || current.messages.some((message) => message.id === input.input.id) || coalescedRun !== undefined && input.input.id === coalescedRun.assistantMessageId || !coalescedRun && (input.run.assistantMessageId === input.input.id || current.runs.some((candidate) => candidate.id === input.run.id) || current.runs.some((candidate) => candidate.assistantMessageId === input.run.assistantMessageId) || current.messages.some((message) => message.id === input.run.assistantMessageId))) {
|
|
340
|
+
throw new TypeError("Input and queued run do not form one valid assignment");
|
|
341
|
+
}
|
|
342
|
+
const assignedRun = coalescedRun ? AgentRunSchema2.parse({
|
|
343
|
+
...coalescedRun,
|
|
344
|
+
inputMessageIds: [...coalescedRun.inputMessageIds, input.input.id],
|
|
345
|
+
revision: coalescedRun.revision + 1,
|
|
346
|
+
updatedAt: new Date().toISOString()
|
|
347
|
+
}) : input.run;
|
|
348
|
+
const admissionReceipt = AgentAdmissionReceiptSchema2.parse({
|
|
349
|
+
schemaVersion: 1,
|
|
350
|
+
conversationId: input.input.conversationId,
|
|
351
|
+
idempotencyKey: input.idempotencyKey,
|
|
352
|
+
input: input.input,
|
|
353
|
+
runId: assignedRun.id,
|
|
354
|
+
assistantMessageId: assignedRun.assistantMessageId
|
|
355
|
+
});
|
|
356
|
+
return applied(current, {
|
|
357
|
+
messages: [...current.messages, input.input],
|
|
358
|
+
runs: coalescedRun ? replaceRun(current.runs, assignedRun) : [...current.runs, assignedRun]
|
|
359
|
+
}, {
|
|
360
|
+
runRecords: [AgentStoredRunSchema2.parse({ schemaVersion: 1, run: assignedRun })],
|
|
361
|
+
admissionReceipt,
|
|
362
|
+
historyMutations: [{ type: "admit", input: input.input }]
|
|
315
363
|
});
|
|
316
364
|
}
|
|
317
|
-
function
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
return;
|
|
325
|
-
const parsed = AgentStoredRunSchema2.parse(record);
|
|
326
|
-
if (parsed.run.conversationId !== input.conversationId || parsed.run.id !== input.runId) {
|
|
327
|
-
throw new TypeError("Stored run does not match the identity it was loaded by");
|
|
365
|
+
function reduceSeed(current, input) {
|
|
366
|
+
const conversationId = input.conversationId;
|
|
367
|
+
if (current.conversationId !== conversationId)
|
|
368
|
+
return { outcome: "not_found" };
|
|
369
|
+
for (const message of input.inputs) {
|
|
370
|
+
if (message.conversationId !== conversationId || message.role !== "user" || message.status !== "committed" || message.runId !== undefined) {
|
|
371
|
+
throw new TypeError("Seeded conversation input must be committed unowned user messages");
|
|
328
372
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
373
|
+
}
|
|
374
|
+
return applied(current, { messages: [...input.inputs, ...current.messages] }, {
|
|
375
|
+
seedReceipt: AgentSeedReceiptSchema2.parse({
|
|
376
|
+
schemaVersion: 1,
|
|
377
|
+
conversationId,
|
|
378
|
+
seedKey: input.seedKey,
|
|
379
|
+
messageIds: input.inputs.map((message) => message.id)
|
|
380
|
+
}),
|
|
381
|
+
historyMutations: [...input.inputs].reverse().map((message) => ({
|
|
382
|
+
type: "seed",
|
|
383
|
+
message
|
|
384
|
+
}))
|
|
385
|
+
});
|
|
336
386
|
}
|
|
337
|
-
function
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
387
|
+
function reduceCompact(current, input) {
|
|
388
|
+
if (current.conversationId !== input.conversationId)
|
|
389
|
+
return { outcome: "not_found" };
|
|
390
|
+
if (current.version !== input.expectedVersion)
|
|
391
|
+
return conflict(current.version);
|
|
392
|
+
const replaced = new Set(input.replacedMessageIds);
|
|
393
|
+
if (!input.replacedMessageIds.every((id) => current.messages.some((m) => m.id === id))) {
|
|
394
|
+
return { outcome: "not_found" };
|
|
395
|
+
}
|
|
396
|
+
const liveAssistant = current.runs.find((candidate) => isActiveRunState(candidate.state) && replaced.has(candidate.assistantMessageId));
|
|
397
|
+
if (liveAssistant) {
|
|
398
|
+
throw new TypeError(`Compaction may not replace the assistant message of run ${liveAssistant.id}, which has not finished`);
|
|
399
|
+
}
|
|
400
|
+
const positions = current.messages.map((message, index) => replaced.has(message.id) ? index : undefined).filter((index) => index !== undefined);
|
|
401
|
+
const first = positions[0];
|
|
402
|
+
if (first === undefined || positions.some((position, offset) => position !== first + offset) || input.summary.conversationId !== input.conversationId || input.summary.runId !== undefined || input.summary.role !== "summary" || input.summary.status !== "committed" || current.messages.some((message) => message.id === input.summary.id) || current.runs.some((candidate) => candidate.assistantMessageId === input.summary.id)) {
|
|
403
|
+
throw new TypeError("Compaction replacement must be one valid contiguous history range");
|
|
404
|
+
}
|
|
405
|
+
const messages = [
|
|
406
|
+
...current.messages.slice(0, first),
|
|
407
|
+
input.summary,
|
|
408
|
+
...current.messages.slice(first + positions.length)
|
|
409
|
+
];
|
|
410
|
+
return applied(current, { messages }, {
|
|
411
|
+
historyMutations: [
|
|
412
|
+
{
|
|
413
|
+
type: "replace-compacted-range",
|
|
414
|
+
replacedMessageIds: input.replacedMessageIds,
|
|
415
|
+
summary: input.summary
|
|
350
416
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}, { access: "read" });
|
|
417
|
+
]
|
|
418
|
+
});
|
|
354
419
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
420
|
+
|
|
421
|
+
// src/agent-runtime/store-reduce-run.ts
|
|
422
|
+
function reduceAcquire(current, run, input) {
|
|
423
|
+
const runPosition = current.runs.findIndex((candidate) => candidate.id === run.id);
|
|
424
|
+
const acquisitionBlocked = current.runs.some((candidate, index) => candidate.id !== run.id && (candidate.state === "running" || candidate.state === "interrupt_requested" || index < runPosition && candidate.state === "queued"));
|
|
425
|
+
if (run.revision !== input.expectedRevision || run.state !== "queued" || acquisitionBlocked) {
|
|
426
|
+
return conflict(run.revision);
|
|
427
|
+
}
|
|
428
|
+
const next = AgentRunSchema2.parse({
|
|
429
|
+
...run,
|
|
430
|
+
state: "running",
|
|
431
|
+
executionSequence: run.executionSequence ?? current.version + 1,
|
|
432
|
+
ownerId: input.ownerId,
|
|
433
|
+
fencingToken: (run.fencingToken ?? 0) + 1,
|
|
434
|
+
revision: run.revision + 1,
|
|
435
|
+
updatedAt: new Date().toISOString()
|
|
436
|
+
});
|
|
437
|
+
return applied(current, { runs: replaceRun(current.runs, next) }, {
|
|
438
|
+
runRecords: [AgentStoredRunSchema2.parse({ schemaVersion: 1, run: next })]
|
|
439
|
+
});
|
|
358
440
|
}
|
|
359
|
-
|
|
360
|
-
if (
|
|
361
|
-
|
|
441
|
+
function reduceCheckpoint(current, run, input) {
|
|
442
|
+
if (run.revision !== input.expectedRevision || run.state !== "running" || run.ownerId !== input.ownerId || input.fencingToken !== undefined && run.fencingToken !== input.fencingToken || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== "streaming") {
|
|
443
|
+
return conflict(run.revision);
|
|
362
444
|
}
|
|
363
|
-
|
|
445
|
+
const next = AgentRunSchema2.parse({
|
|
446
|
+
...run,
|
|
447
|
+
...input.usage && { usage: input.usage },
|
|
448
|
+
revision: run.revision + 1,
|
|
449
|
+
updatedAt: new Date().toISOString()
|
|
450
|
+
});
|
|
451
|
+
return applied(current, {
|
|
452
|
+
runs: replaceRun(current.runs, next),
|
|
453
|
+
messages: replaceMessage(current.messages, input.assistant)
|
|
454
|
+
}, {
|
|
455
|
+
runRecords: [AgentStoredRunSchema2.parse({ schemaVersion: 1, run: next })],
|
|
456
|
+
historyMutations: [{ type: "upsert-assistant", message: input.assistant }]
|
|
457
|
+
});
|
|
364
458
|
}
|
|
365
|
-
function
|
|
366
|
-
|
|
367
|
-
|
|
459
|
+
function reduceOperation(current, run, input) {
|
|
460
|
+
if (run.revision !== input.expectedRevision || run.state !== "running" && run.state !== "interrupt_requested" || run.ownerId !== input.ownerId || input.fencingToken !== undefined && run.fencingToken !== input.fencingToken) {
|
|
461
|
+
return conflict(run.revision);
|
|
462
|
+
}
|
|
463
|
+
const next = AgentRunSchema2.parse({
|
|
464
|
+
...run,
|
|
465
|
+
lastOperation: input.operation,
|
|
466
|
+
revision: run.revision + 1,
|
|
467
|
+
updatedAt: new Date().toISOString()
|
|
468
|
+
});
|
|
469
|
+
return applied(current, { runs: replaceRun(current.runs, next) }, { runRecords: [AgentStoredRunSchema2.parse({ schemaVersion: 1, run: next })] });
|
|
368
470
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
471
|
+
function reduceInterrupt(current, run, input) {
|
|
472
|
+
if (run.revision !== input.expectedRevision || run.state !== "running") {
|
|
473
|
+
return conflict(run.revision);
|
|
474
|
+
}
|
|
475
|
+
const next = AgentRunSchema2.parse({
|
|
476
|
+
...run,
|
|
477
|
+
state: "interrupt_requested",
|
|
478
|
+
revision: run.revision + 1,
|
|
479
|
+
updatedAt: new Date().toISOString()
|
|
480
|
+
});
|
|
481
|
+
return applied(current, { runs: replaceRun(current.runs, next) }, {
|
|
482
|
+
runRecords: [AgentStoredRunSchema2.parse({ schemaVersion: 1, run: next })]
|
|
483
|
+
});
|
|
375
484
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
})
|
|
415
|
-
});
|
|
416
|
-
if (outcome.outcome !== "applied") {
|
|
417
|
-
throw new TypeError("Conversation archive snapshot target is not empty");
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
for (const run of snapshot.runs) {
|
|
421
|
-
const terminalAssistant = snapshot.messages.find((message) => message.id === run.assistantMessageId);
|
|
422
|
-
await driver.runs.save(transaction, AgentStoredRunSchema2.parse({
|
|
423
|
-
schemaVersion: 1,
|
|
424
|
-
run,
|
|
425
|
-
...terminalAssistant && { terminalAssistant }
|
|
426
|
-
}));
|
|
427
|
-
}
|
|
428
|
-
for (const message of snapshot.messages) {
|
|
429
|
-
await driver.history.apply(transaction, { type: "admit", input: message });
|
|
430
|
-
}
|
|
431
|
-
for (const event of archive.events) {
|
|
432
|
-
if (event.kind !== "runtime/transition")
|
|
433
|
-
continue;
|
|
434
|
-
const transition = AgentStoreTransitionSchema2.parse(event.payload);
|
|
435
|
-
if (transition.type !== "accept")
|
|
436
|
-
continue;
|
|
437
|
-
const assigned = snapshot.runs.find((run) => run.inputMessageIds.includes(transition.input.input.id));
|
|
438
|
-
if (!assigned) {
|
|
439
|
-
throw new TypeError("Conversation archive admission has no assigned run");
|
|
440
|
-
}
|
|
441
|
-
await driver.admissions.create(transaction, AgentAdmissionReceiptSchema2.parse({
|
|
485
|
+
function reduceRecover(current, run, input, retainedAssistant) {
|
|
486
|
+
if (run.revision !== input.expectedRevision || !isActiveRunState(run.state)) {
|
|
487
|
+
return conflict(run.revision);
|
|
488
|
+
}
|
|
489
|
+
if (input.action === "requeue" && run.state !== "queued" && input.replaySafe !== true) {
|
|
490
|
+
throw new TypeError("Recovering an acquired run requires explicit replaySafe evidence");
|
|
491
|
+
}
|
|
492
|
+
const { ownerId: _released, ...carried } = run;
|
|
493
|
+
const next = AgentRunSchema2.parse({
|
|
494
|
+
...carried,
|
|
495
|
+
state: input.action === "requeue" ? "queued" : "abandoned",
|
|
496
|
+
revision: run.revision + 1,
|
|
497
|
+
...input.action === "abandon" && { terminalReason: "abandoned" },
|
|
498
|
+
updatedAt: new Date().toISOString()
|
|
499
|
+
});
|
|
500
|
+
if (input.action === "abandon") {
|
|
501
|
+
if (retainedAssistant && (retainedAssistant.id !== run.assistantMessageId || retainedAssistant.conversationId !== run.conversationId || retainedAssistant.runId !== run.id || retainedAssistant.role !== "assistant" || retainedAssistant.status !== "failed"))
|
|
502
|
+
throw new TypeError("Abandoned assistant does not match its run");
|
|
503
|
+
const existingAssistant = current.messages.find((message) => message.id === run.assistantMessageId);
|
|
504
|
+
const assistant = retainedAssistant ?? AgentMessageSchema2.parse({
|
|
505
|
+
...existingAssistant ?? {
|
|
506
|
+
schemaVersion: 1,
|
|
507
|
+
id: run.assistantMessageId,
|
|
508
|
+
conversationId: run.conversationId,
|
|
509
|
+
runId: run.id,
|
|
510
|
+
role: "assistant",
|
|
511
|
+
parts: [],
|
|
512
|
+
createdAt: run.createdAt
|
|
513
|
+
},
|
|
514
|
+
status: "failed",
|
|
515
|
+
updatedAt: new Date().toISOString()
|
|
516
|
+
});
|
|
517
|
+
return applied(current, {
|
|
518
|
+
runs: replaceRun(current.runs, next),
|
|
519
|
+
messages: replaceMessage(current.messages, assistant)
|
|
520
|
+
}, {
|
|
521
|
+
runRecords: [
|
|
522
|
+
AgentStoredRunSchema2.parse({
|
|
442
523
|
schemaVersion: 1,
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
return { conversationId: archive.conversationId, events: archive.events.length };
|
|
524
|
+
run: next,
|
|
525
|
+
terminalAssistant: assistant
|
|
526
|
+
})
|
|
527
|
+
],
|
|
528
|
+
historyMutations: [{ type: "upsert-assistant", message: assistant }]
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
return applied(current, { runs: replaceRun(current.runs, next) }, {
|
|
532
|
+
runRecords: [AgentStoredRunSchema2.parse({ schemaVersion: 1, run: next })]
|
|
453
533
|
});
|
|
454
534
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
535
|
+
|
|
536
|
+
// src/agent-runtime/store-reduce-terminal.ts
|
|
537
|
+
function reduceTerminal(current, run, input) {
|
|
538
|
+
if (input.reason === "absorbed") {
|
|
539
|
+
throw new TypeError("A run is absorbed by another run, never terminalized as absorbed");
|
|
540
|
+
}
|
|
541
|
+
if (run.revision !== input.expectedRevision || run.state !== "running" && run.state !== "interrupt_requested" || run.ownerId !== input.ownerId || input.fencingToken !== undefined && run.fencingToken !== input.fencingToken || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== assistantStatus(input.reason)) {
|
|
542
|
+
return conflict(run.revision);
|
|
543
|
+
}
|
|
544
|
+
if (input.absorb && runStateForTerminalReason2(input.reason) !== "completed") {
|
|
545
|
+
throw new TypeError("Only a completing run may absorb a queued successor");
|
|
546
|
+
}
|
|
547
|
+
const named = new Set((input.absorb ?? []).map((entry) => entry.runId));
|
|
548
|
+
if (named.size !== (input.absorb ?? []).length || named.has(run.id)) {
|
|
549
|
+
throw new TypeError("An absorption names each successor once, and never the absorbing run");
|
|
550
|
+
}
|
|
551
|
+
const absorbable = (input.absorb ?? []).filter((entry) => {
|
|
552
|
+
const candidate = current.runs.find((item) => item.id === entry.runId);
|
|
553
|
+
return candidate !== undefined && candidate.id !== run.id && candidate.state === "queued" && candidate.conversationId === run.conversationId && candidate.inputMessageIds.length === entry.inputMessageIds.length && candidate.inputMessageIds.every((id, index) => id === entry.inputMessageIds[index]);
|
|
554
|
+
});
|
|
555
|
+
const absorbedIds = new Set(absorbable.flatMap((entry) => entry.inputMessageIds));
|
|
556
|
+
const next = AgentRunSchema2.parse({
|
|
557
|
+
...run,
|
|
558
|
+
state: runStateForTerminalReason2(input.reason),
|
|
559
|
+
terminalReason: input.reason,
|
|
560
|
+
...input.policyName && { terminalPolicyName: input.policyName },
|
|
561
|
+
...input.usage && { usage: input.usage },
|
|
562
|
+
...absorbedIds.size > 0 && {
|
|
563
|
+
inputMessageIds: [
|
|
564
|
+
...run.inputMessageIds,
|
|
565
|
+
...[...absorbedIds].filter((id) => !run.inputMessageIds.includes(id))
|
|
566
|
+
]
|
|
567
|
+
},
|
|
568
|
+
revision: run.revision + 1,
|
|
569
|
+
updatedAt: new Date().toISOString()
|
|
570
|
+
});
|
|
571
|
+
const absorbedRuns = absorbable.map((entry) => {
|
|
572
|
+
const candidate = current.runs.find((item) => item.id === entry.runId);
|
|
573
|
+
if (!candidate)
|
|
574
|
+
throw new TypeError("Absorbed run disappeared inside the reducer");
|
|
575
|
+
return AgentRunSchema2.parse({
|
|
576
|
+
...candidate,
|
|
577
|
+
state: runStateForTerminalReason2("absorbed"),
|
|
578
|
+
terminalReason: "absorbed",
|
|
579
|
+
absorbedIntoRunId: next.id,
|
|
580
|
+
revision: candidate.revision + 1,
|
|
581
|
+
updatedAt: new Date().toISOString()
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
const runs = absorbedRuns.reduce((accumulated, absorbed) => replaceRun(accumulated, absorbed), replaceRun(current.runs, next));
|
|
585
|
+
return applied(current, {
|
|
586
|
+
runs,
|
|
587
|
+
messages: replaceMessage(current.messages, input.assistant)
|
|
588
|
+
}, {
|
|
589
|
+
runRecords: [
|
|
590
|
+
AgentStoredRunSchema2.parse({
|
|
591
|
+
schemaVersion: 1,
|
|
592
|
+
run: next,
|
|
593
|
+
terminalAssistant: input.assistant
|
|
594
|
+
}),
|
|
595
|
+
...absorbedRuns.map((absorbed) => AgentStoredRunSchema2.parse({ schemaVersion: 1, run: absorbed }))
|
|
480
596
|
],
|
|
481
|
-
|
|
597
|
+
historyMutations: [{ type: "upsert-assistant", message: input.assistant }]
|
|
482
598
|
});
|
|
483
599
|
}
|
|
484
600
|
|
|
485
|
-
// src/agent-runtime/store-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
"artifactId"
|
|
508
|
-
]) {
|
|
509
|
-
if (key in value)
|
|
510
|
-
throw new TypeError(`Branch cannot rekey an opaque payload containing ${key}`);
|
|
601
|
+
// src/agent-runtime/store-reducer.ts
|
|
602
|
+
function transitionRecord(operation, reduced) {
|
|
603
|
+
if (operation.type === "recover" && operation.input.action === "abandon") {
|
|
604
|
+
const assistant = reduced?.runRecords?.find((record) => record.run.id === operation.input.runId)?.terminalAssistant;
|
|
605
|
+
if (assistant)
|
|
606
|
+
return { ...operation, assistant };
|
|
607
|
+
}
|
|
608
|
+
if (operation.type !== "checkpoint")
|
|
609
|
+
return operation;
|
|
610
|
+
const { assistant, ...rest } = operation.input;
|
|
611
|
+
const body = canonicalAgentJson2(assistant);
|
|
612
|
+
return AgentStoreCheckpointRecordSchema.parse({
|
|
613
|
+
type: operation.type,
|
|
614
|
+
input: {
|
|
615
|
+
...rest,
|
|
616
|
+
assistant: {
|
|
617
|
+
id: assistant.id,
|
|
618
|
+
status: assistant.status,
|
|
619
|
+
sha256: createHash("sha256").update(body).digest("hex"),
|
|
620
|
+
parts: assistant.parts.length,
|
|
621
|
+
bytes: Buffer.byteLength(body)
|
|
622
|
+
}
|
|
511
623
|
}
|
|
512
|
-
|
|
513
|
-
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
function reduceStore(current, operation) {
|
|
627
|
+
if (operation.type === "accept")
|
|
628
|
+
return reduceAccept(current, operation.input);
|
|
629
|
+
if (operation.type === "seed")
|
|
630
|
+
return reduceSeed(current, operation.input);
|
|
631
|
+
if (operation.type === "compact")
|
|
632
|
+
return reduceCompact(current, operation.input);
|
|
633
|
+
const run = current.runs.find((candidate) => candidate.id === operation.input.runId);
|
|
634
|
+
if (!run)
|
|
635
|
+
return { outcome: "not_found" };
|
|
636
|
+
switch (operation.type) {
|
|
637
|
+
case "acquire":
|
|
638
|
+
return reduceAcquire(current, run, operation.input);
|
|
639
|
+
case "checkpoint":
|
|
640
|
+
return reduceCheckpoint(current, run, operation.input);
|
|
641
|
+
case "operation":
|
|
642
|
+
return reduceOperation(current, run, operation.input);
|
|
643
|
+
case "interrupt":
|
|
644
|
+
return reduceInterrupt(current, run, operation.input);
|
|
645
|
+
case "recover":
|
|
646
|
+
return reduceRecover(current, run, operation.input, operation.assistant);
|
|
647
|
+
case "terminal":
|
|
648
|
+
return reduceTerminal(current, run, operation.input);
|
|
514
649
|
}
|
|
515
|
-
return value;
|
|
516
650
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
651
|
+
function operationConversationId(operation) {
|
|
652
|
+
return operation.type === "accept" ? operation.input.input.conversationId : operation.input.conversationId;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/agent-runtime/branch-replay.ts
|
|
656
|
+
var BranchBaselineSchema = z4.object({
|
|
657
|
+
migratedAt: z4.iso.datetime({ offset: true }),
|
|
658
|
+
asOf: z4.iso.datetime({ offset: true }).optional(),
|
|
659
|
+
head: AgentRuntimeHeadSchema2,
|
|
660
|
+
messages: z4.array(AgentMessageSchema2),
|
|
661
|
+
compacted: z4.array(z4.string().min(1)).optional(),
|
|
662
|
+
runs: z4.array(AgentStoredRunSchema2),
|
|
663
|
+
admissions: z4.array(AgentAdmissionReceiptSchema2)
|
|
521
664
|
}).strict();
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (output && name === AGENT_CODING_TOOL_NAMES.runCommand) {
|
|
544
|
-
const parsed = ShellOutputSchema.parse(value);
|
|
545
|
-
return {
|
|
546
|
-
...parsed,
|
|
547
|
-
...parsed.artifact && {
|
|
548
|
-
artifact: {
|
|
549
|
-
...parsed.artifact,
|
|
550
|
-
reference: ids.reference("spill", parsed.artifact.reference)
|
|
551
|
-
}
|
|
665
|
+
function branchTransition(event) {
|
|
666
|
+
const checkpoint = AgentStoreCheckpointRecordSchema.safeParse(event.payload);
|
|
667
|
+
if (!checkpoint.success)
|
|
668
|
+
return AgentStoreTransitionSchema2.parse(event.payload);
|
|
669
|
+
const input = checkpoint.data.input;
|
|
670
|
+
if (canonicalAgentJson2(checkpoint.data) !== canonicalAgentJson2(event.payload))
|
|
671
|
+
throw new TypeError("Branch checkpoint contains an unknown opaque field");
|
|
672
|
+
return AgentStoreTransitionSchema2.parse({
|
|
673
|
+
type: "checkpoint",
|
|
674
|
+
input: {
|
|
675
|
+
...input,
|
|
676
|
+
assistant: {
|
|
677
|
+
schemaVersion: 1,
|
|
678
|
+
id: input.assistant.id,
|
|
679
|
+
conversationId: input.conversationId,
|
|
680
|
+
runId: input.runId,
|
|
681
|
+
role: "assistant",
|
|
682
|
+
status: "streaming",
|
|
683
|
+
parts: [],
|
|
684
|
+
createdAt: event.occurredAt,
|
|
685
|
+
updatedAt: event.occurredAt
|
|
552
686
|
}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
return branchOpaqueJson(value);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
556
689
|
}
|
|
557
|
-
function
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
690
|
+
function replayBranchEvents(conversationId, events, retainedSnapshot) {
|
|
691
|
+
const result = {
|
|
692
|
+
snapshot: AgentSnapshotSchema2.parse({
|
|
693
|
+
schemaVersion: 1,
|
|
694
|
+
conversationId,
|
|
695
|
+
version: 0,
|
|
696
|
+
messages: [],
|
|
697
|
+
runs: []
|
|
698
|
+
}),
|
|
699
|
+
effects: [],
|
|
700
|
+
unresolvedDrafts: new Set,
|
|
701
|
+
retainedMessages: new Map
|
|
702
|
+
};
|
|
703
|
+
for (const event of events) {
|
|
704
|
+
if (event.kind === "runtime/baseline") {
|
|
705
|
+
if (event.seq !== 1 || result.snapshot.version !== 0)
|
|
706
|
+
throw new TypeError("Branch baseline must be the first event");
|
|
707
|
+
const baseline = BranchBaselineSchema.parse(event.payload);
|
|
708
|
+
if (baseline.head.conversationId !== conversationId)
|
|
709
|
+
throw new TypeError("Branch baseline has a foreign conversation");
|
|
710
|
+
const compacted = new Set(baseline.compacted);
|
|
711
|
+
snapshotOf(baseline.head, baseline.messages, baseline.runs);
|
|
712
|
+
const keys = new Set;
|
|
713
|
+
for (const record of baseline.runs) {
|
|
714
|
+
if (record.run.inputMessageIds.some((id) => !baseline.messages.some((message) => message.id === id)))
|
|
715
|
+
throw new TypeError("Branch baseline run references missing input");
|
|
716
|
+
}
|
|
717
|
+
for (const admission of baseline.admissions) {
|
|
718
|
+
const run = baseline.runs.find((record) => record.run.id === admission.runId)?.run;
|
|
719
|
+
if (keys.has(admission.idempotencyKey) || admission.conversationId !== conversationId || admission.input.conversationId !== conversationId || !run || !run.inputMessageIds.includes(admission.input.id) || run.assistantMessageId !== admission.assistantMessageId || !baseline.messages.some((message) => message.id === admission.input.id && canonicalAgentJson2(message) === canonicalAgentJson2(admission.input)))
|
|
720
|
+
throw new TypeError("Branch baseline admission is inconsistent");
|
|
721
|
+
keys.add(admission.idempotencyKey);
|
|
722
|
+
}
|
|
723
|
+
if (compacted.size !== (baseline.compacted?.length ?? 0) || [...compacted].some((id) => !baseline.messages.some((m) => m.id === id)))
|
|
724
|
+
throw new TypeError("Branch baseline compacted identities are inconsistent");
|
|
725
|
+
result.snapshot = snapshotOf(baseline.head, baseline.messages.filter((m) => !compacted.has(m.id)), baseline.runs);
|
|
726
|
+
result.baseline = baseline;
|
|
727
|
+
continue;
|
|
569
728
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
}
|
|
577
|
-
};
|
|
578
|
-
case "tool-approval-request":
|
|
579
|
-
return {
|
|
580
|
-
type: part.type,
|
|
581
|
-
approvalId: ids.reference("approval", part.approvalId),
|
|
582
|
-
callId: ids.reference("call", part.callId)
|
|
729
|
+
if (event.kind === "runtime/branched") {
|
|
730
|
+
const receipt = AgentConversationBranchReceiptSchema2.parse(event.payload);
|
|
731
|
+
if (receipt.conversationId !== conversationId)
|
|
732
|
+
throw new TypeError("Branch receipt has a foreign conversation");
|
|
733
|
+
result.snapshot = {
|
|
734
|
+
...result.snapshot,
|
|
735
|
+
runs: result.snapshot.runs.map(({ ownerId: _owner, fencingToken: _fence, ...run }) => run)
|
|
583
736
|
};
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
if (event.kind !== "runtime/transition")
|
|
740
|
+
continue;
|
|
741
|
+
let operation = branchTransition(event);
|
|
742
|
+
if (operation.type === "checkpoint" && retainedSnapshot) {
|
|
743
|
+
const record = AgentStoreCheckpointRecordSchema.safeParse(event.payload);
|
|
744
|
+
const assistantId = operation.input.assistant.id;
|
|
745
|
+
const retained = retainedSnapshot.messages.find((message) => message.id === assistantId);
|
|
746
|
+
if (record.success && retained && record.data.input.assistant.sha256 === createHash2("sha256").update(canonicalAgentJson2(retained)).digest("hex")) {
|
|
747
|
+
operation = { ...operation, input: { ...operation.input, assistant: retained } };
|
|
748
|
+
result.retainedMessages.set(event.eventId, retained);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (operation.type === "recover" && operation.input.action === "abandon" && !operation.assistant && retainedSnapshot) {
|
|
752
|
+
const runId = operation.input.runId;
|
|
753
|
+
const run = result.snapshot.runs.find((run) => run.id === runId);
|
|
754
|
+
const retained = retainedSnapshot.messages.find((message) => message.id === run?.assistantMessageId && message.runId === runId && message.role === "assistant" && message.status === "failed");
|
|
755
|
+
if (retained)
|
|
756
|
+
operation = { ...operation, assistant: retained };
|
|
757
|
+
}
|
|
758
|
+
if (operation.type === "recover" && operation.input.action === "abandon" && !operation.assistant)
|
|
759
|
+
throw new TypeError("Branch archive has no exact retained abandoned assistant");
|
|
760
|
+
const inputConversation = operation.type === "accept" ? operation.input.input.conversationId : operation.input.conversationId;
|
|
761
|
+
if (inputConversation !== conversationId)
|
|
762
|
+
throw new TypeError("Branch transition has a foreign conversation");
|
|
763
|
+
const reduced = reduceStore(result.snapshot, operation);
|
|
764
|
+
if (reduced.outcome !== "applied")
|
|
765
|
+
throw new TypeError(`Branch transition at sequence ${event.seq} is inconsistent: ${reduced.outcome}`);
|
|
766
|
+
const runRecords = reduced.runRecords?.map((record) => ({
|
|
767
|
+
...record,
|
|
768
|
+
run: { ...record.run, updatedAt: event.occurredAt }
|
|
769
|
+
}));
|
|
770
|
+
result.snapshot = {
|
|
771
|
+
...reduced.snapshot,
|
|
772
|
+
runs: reduced.snapshot.runs.map((run) => runRecords?.find((record) => record.run.id === run.id)?.run ?? run)
|
|
773
|
+
};
|
|
774
|
+
const { snapshot: _snapshot, outcome: _outcome, ...effects } = reduced;
|
|
775
|
+
result.effects.push({ ...effects, ...runRecords && { runRecords } });
|
|
776
|
+
if (operation.type === "recover" && operation.input.action === "abandon") {
|
|
777
|
+
const runId = operation.input.runId;
|
|
778
|
+
const assistant = reduced.runRecords?.find((record) => record.run.id === runId)?.terminalAssistant;
|
|
779
|
+
if (assistant)
|
|
780
|
+
result.retainedMessages.set(event.eventId, assistant);
|
|
781
|
+
if (operation.assistant)
|
|
782
|
+
result.unresolvedDrafts.delete(runId);
|
|
783
|
+
}
|
|
784
|
+
if (operation.type === "checkpoint" && AgentStoreCheckpointRecordSchema.safeParse(event.payload).success)
|
|
785
|
+
result.unresolvedDrafts.add(operation.input.runId);
|
|
786
|
+
if (operation.type === "terminal")
|
|
787
|
+
result.unresolvedDrafts.delete(operation.input.runId);
|
|
594
788
|
}
|
|
789
|
+
return result;
|
|
595
790
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
791
|
+
|
|
792
|
+
// src/agent-runtime/store-reads.ts
|
|
793
|
+
async function snapshotIn(driver, transaction, conversationId) {
|
|
794
|
+
const [stored, messages, activeRecords] = await Promise.all([
|
|
795
|
+
driver.head.load(transaction, conversationId),
|
|
796
|
+
driver.history.load(transaction, conversationId),
|
|
797
|
+
driver.runs.listActive(transaction, conversationId)
|
|
798
|
+
]);
|
|
799
|
+
const head = AgentRuntimeHeadSchema2.parse(stored ?? emptyHead(conversationId));
|
|
800
|
+
const referencedRecords = await driver.runs.loadMany(transaction, {
|
|
801
|
+
conversationId,
|
|
802
|
+
runIds: referencedRunIds(messages)
|
|
803
|
+
});
|
|
804
|
+
return snapshotOf(head, messages, mergeRunRecords(activeRecords, referencedRecords));
|
|
805
|
+
}
|
|
806
|
+
function loadSnapshot(driver, conversationId) {
|
|
807
|
+
return driver.transaction((transaction) => snapshotIn(driver, transaction, conversationId), {
|
|
808
|
+
access: "read"
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
function loadRun(driver, input) {
|
|
812
|
+
return driver.transaction(async (transaction) => {
|
|
813
|
+
const [stored, record] = await Promise.all([
|
|
814
|
+
driver.head.load(transaction, input.conversationId),
|
|
815
|
+
driver.runs.load(transaction, input)
|
|
816
|
+
]);
|
|
817
|
+
if (!record)
|
|
818
|
+
return;
|
|
819
|
+
const parsed = AgentStoredRunSchema2.parse(record);
|
|
820
|
+
if (parsed.run.conversationId !== input.conversationId || parsed.run.id !== input.runId) {
|
|
821
|
+
throw new TypeError("Stored run does not match the identity it was loaded by");
|
|
607
822
|
}
|
|
608
|
-
|
|
823
|
+
const head = AgentRuntimeHeadSchema2.parse(stored ?? emptyHead(input.conversationId));
|
|
824
|
+
return AgentRunViewSchema2.parse({
|
|
825
|
+
snapshotVersion: head.version,
|
|
826
|
+
run: parsed.run,
|
|
827
|
+
...parsed.terminalAssistant && { assistant: parsed.terminalAssistant }
|
|
828
|
+
});
|
|
829
|
+
}, { access: "read" });
|
|
609
830
|
}
|
|
610
|
-
function
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
...run.absorbedIntoRunId && {
|
|
621
|
-
absorbedIntoRunId: ids.reference("run", run.absorbedIntoRunId)
|
|
622
|
-
},
|
|
623
|
-
...run.lastOperation && {
|
|
624
|
-
lastOperation: {
|
|
625
|
-
...run.lastOperation,
|
|
626
|
-
operationId: ids.reference("operation", run.lastOperation.operationId)
|
|
831
|
+
function listActiveRuns(driver, conversationId) {
|
|
832
|
+
return driver.transaction(async (transaction) => {
|
|
833
|
+
const [records, messages] = await Promise.all([
|
|
834
|
+
driver.runs.listActive(transaction, conversationId),
|
|
835
|
+
driver.history.load(transaction, conversationId)
|
|
836
|
+
]);
|
|
837
|
+
const runs = records.map((record) => AgentStoredRunSchema2.parse(record).run);
|
|
838
|
+
for (const run of runs) {
|
|
839
|
+
if (run.conversationId !== conversationId) {
|
|
840
|
+
throw new TypeError("Active run belongs to another conversation");
|
|
627
841
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
842
|
+
if (!isActiveRunState(run.state)) {
|
|
843
|
+
throw new TypeError("Active run listing returned a terminal run");
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return orderRuns(messages, runs);
|
|
847
|
+
}, { access: "read" });
|
|
632
848
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
version: z6.int().positive(),
|
|
641
|
-
value: z6.json(),
|
|
642
|
-
actor: z6.enum(["agent", "human", "system"])
|
|
643
|
-
}).strict();
|
|
644
|
-
var Retry = z6.object({
|
|
645
|
-
attempt: z6.int().positive(),
|
|
646
|
-
delayMs: z6.number().nonnegative().optional(),
|
|
647
|
-
reason: z6.string().optional()
|
|
648
|
-
}).strict();
|
|
649
|
-
var Spill = z6.object({ reference: Id, bytes: z6.int().nonnegative(), sha256: Hash2.optional() }).strict();
|
|
650
|
-
var Schedule = z6.object({
|
|
651
|
-
id: Id,
|
|
652
|
-
kind: z6.enum(["at", "after", "every"]).optional(),
|
|
653
|
-
nextAt: z6.string().nullable().optional(),
|
|
654
|
-
occurrence: z6.int().nonnegative().optional(),
|
|
655
|
-
lateByMs: z6.number().nonnegative().optional(),
|
|
656
|
-
state: z6.string().optional(),
|
|
657
|
-
message: z6.string().optional(),
|
|
658
|
-
attempts: z6.int().nonnegative().optional(),
|
|
659
|
-
retryAt: z6.string().nullable().optional(),
|
|
660
|
-
terminal: z6.boolean().optional(),
|
|
661
|
-
skipped: z6.boolean().optional()
|
|
662
|
-
}).strict();
|
|
663
|
-
var Child = z6.object({
|
|
664
|
-
childConversationId: Id.optional(),
|
|
665
|
-
parentConversationId: Id.optional(),
|
|
666
|
-
seedUptoSeq: z6.int().nonnegative().optional(),
|
|
667
|
-
budget: z6.json().optional(),
|
|
668
|
-
state: z6.string().optional(),
|
|
669
|
-
resultReference: Id.optional(),
|
|
670
|
-
reason: z6.string().optional(),
|
|
671
|
-
action: z6.enum(["message-sent", "blocking-presented", "blocking-resolved"]).optional(),
|
|
672
|
-
approvalId: Id.optional(),
|
|
673
|
-
childApprovalId: Id.optional(),
|
|
674
|
-
callId: Id.optional(),
|
|
675
|
-
toolName: Id.optional(),
|
|
676
|
-
input: z6.json().optional(),
|
|
677
|
-
batchId: Id.optional(),
|
|
678
|
-
kind: z6.string().optional(),
|
|
679
|
-
presentedAt: z6.string().optional(),
|
|
680
|
-
approved: z6.boolean().optional()
|
|
681
|
-
}).strict();
|
|
682
|
-
var Rejection = z6.object({ approvalId: Id, callId: Id, toolName: Id, reason: z6.string() }).strict();
|
|
683
|
-
function branchObservation(ids, kind, raw) {
|
|
684
|
-
if (kind.startsWith("durability/")) {
|
|
685
|
-
const value = kind === "durability/step" ? StepEventPayloadSchema.parse(raw) : kind === "durability/park" ? ParkEventPayloadSchema.parse(raw) : kind === "durability/event" ? DeliveredEventPayloadSchema.parse(raw) : kind === "durability/effect" ? EffectEventPayloadSchema.parse(raw) : undefined;
|
|
686
|
-
if (!value || canonicalAgentJson2(value) !== canonicalAgentJson2(raw))
|
|
687
|
-
throw new TypeError("Branch cannot rekey opaque durability payload");
|
|
688
|
-
if ("encoded" in value)
|
|
689
|
-
branchOpaqueJson(z6.json().parse(JSON.parse(value.encoded)));
|
|
690
|
-
return {
|
|
691
|
-
...value,
|
|
692
|
-
runId: ids.reference("run", value.runId),
|
|
693
|
-
..."id" in value && { id: ids.define("key", value.id) }
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
if (kind === "sandbox/probed")
|
|
697
|
-
return z6.object({
|
|
698
|
-
result: AgentSandboxGradeSchema2,
|
|
699
|
-
required: z6.array(AgentSandboxRestrictionSchema2),
|
|
700
|
-
missing: z6.array(AgentSandboxRestrictionSchema2)
|
|
701
|
-
}).strict().parse(raw);
|
|
702
|
-
if (kind === "state/set") {
|
|
703
|
-
const value = State.parse(raw);
|
|
704
|
-
branchOpaqueJson(value.value);
|
|
705
|
-
return value;
|
|
706
|
-
}
|
|
707
|
-
if (kind === "retry/scheduled" || kind === "retry/started")
|
|
708
|
-
return Retry.parse(raw);
|
|
709
|
-
if (kind === "spill/created" || kind === "spill/deleted") {
|
|
710
|
-
const value = Spill.parse(raw);
|
|
711
|
-
return { ...value, reference: ids.reference("spill", value.reference) };
|
|
712
|
-
}
|
|
713
|
-
if (kind.startsWith("schedule/")) {
|
|
714
|
-
const value = Schedule.parse(raw);
|
|
715
|
-
return { ...value, id: ids.define("schedule", value.id) };
|
|
849
|
+
function appendEvent(driver, input) {
|
|
850
|
+
const parsed = AppendAgentStoreEventSchema2.parse(input);
|
|
851
|
+
return driver.transaction((transaction) => appendEventIn(driver, transaction, parsed));
|
|
852
|
+
}
|
|
853
|
+
async function appendEventIn(driver, transaction, parsed) {
|
|
854
|
+
if (await driver.conversations?.isPurged(transaction, parsed.conversationId)) {
|
|
855
|
+
throw new AgentConversationPurgedError2;
|
|
716
856
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
857
|
+
return driver.events.append(transaction, agentStoreEventDraft(parsed));
|
|
858
|
+
}
|
|
859
|
+
function readEvents(driver, input) {
|
|
860
|
+
const parsed = ReadAgentStoreEventsSchema2.parse(input);
|
|
861
|
+
return driver.transaction(async (transaction) => AgentStoreEventPageSchema2.parse(await driver.events.list(transaction, parsed)), { access: "read" });
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/agent-runtime/store-replay.ts
|
|
865
|
+
async function persistRuntimeReplay(driver, tx, replay) {
|
|
866
|
+
if (replay.baseline) {
|
|
867
|
+
const baseline = replay.baseline;
|
|
868
|
+
for (const record of baseline.runs)
|
|
869
|
+
await driver.runs.save(tx, record);
|
|
870
|
+
if (driver.history.restore)
|
|
871
|
+
await driver.history.restore(tx, {
|
|
872
|
+
conversationId: baseline.head.conversationId,
|
|
873
|
+
messages: baseline.messages,
|
|
874
|
+
compactedMessageIds: baseline.compacted ?? []
|
|
875
|
+
});
|
|
876
|
+
else
|
|
877
|
+
for (const message of baseline.messages)
|
|
878
|
+
await driver.history.apply(tx, { type: "admit", input: message });
|
|
879
|
+
for (const receipt of baseline.admissions)
|
|
880
|
+
await driver.admissions.create(tx, receipt);
|
|
741
881
|
}
|
|
742
|
-
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
882
|
+
for (const effect of replay.effects) {
|
|
883
|
+
for (const record of effect.runRecords ?? [])
|
|
884
|
+
await driver.runs.save(tx, record);
|
|
885
|
+
if (effect.admissionReceipt)
|
|
886
|
+
await driver.admissions.create(tx, effect.admissionReceipt);
|
|
887
|
+
if (effect.seedReceipt)
|
|
888
|
+
await driver.seeds.create(tx, effect.seedReceipt);
|
|
889
|
+
for (const mutation of effect.historyMutations ?? [])
|
|
890
|
+
await driver.history.apply(tx, mutation);
|
|
749
891
|
}
|
|
750
|
-
throw new TypeError(`Branch cannot rekey opaque event kind ${kind}`);
|
|
751
892
|
}
|
|
752
|
-
var BranchObservationSchema = z6.object({ sourceKind: Id, payload: z6.json() }).strict();
|
|
753
893
|
|
|
754
|
-
// src/agent-runtime/
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
var Call = z7.object({
|
|
760
|
-
type: z7.literal("tool-call"),
|
|
761
|
-
toolCallId: z7.string(),
|
|
762
|
-
toolName: z7.string(),
|
|
763
|
-
input: z7.json()
|
|
764
|
-
}).strict();
|
|
765
|
-
var Result = z7.object({
|
|
766
|
-
type: z7.literal("tool-result"),
|
|
767
|
-
toolCallId: z7.string(),
|
|
768
|
-
toolName: z7.string(),
|
|
769
|
-
output: z7.discriminatedUnion("type", [
|
|
770
|
-
z7.object({ type: z7.enum(["text", "error-text"]), value: z7.string() }).strict(),
|
|
771
|
-
z7.object({ type: z7.enum(["json", "error-json"]), value: z7.json() }).strict()
|
|
772
|
-
])
|
|
773
|
-
}).strict();
|
|
774
|
-
var Message = z7.object({
|
|
775
|
-
role: z7.enum(["user", "assistant", "tool"]),
|
|
776
|
-
content: z7.union([
|
|
777
|
-
z7.string(),
|
|
778
|
-
z7.array(z7.union([
|
|
779
|
-
Text,
|
|
780
|
-
Call,
|
|
781
|
-
Result,
|
|
782
|
-
z7.object({
|
|
783
|
-
type: z7.literal("tool-approval-request"),
|
|
784
|
-
approvalId: z7.string(),
|
|
785
|
-
toolCallId: z7.string(),
|
|
786
|
-
signature: z7.string().optional(),
|
|
787
|
-
isAutomatic: z7.boolean().optional()
|
|
788
|
-
}).strict(),
|
|
789
|
-
z7.object({
|
|
790
|
-
type: z7.literal("tool-approval-response"),
|
|
791
|
-
approvalId: z7.string(),
|
|
792
|
-
approved: z7.boolean(),
|
|
793
|
-
reason: z7.string().optional()
|
|
794
|
-
}).strict(),
|
|
795
|
-
z7.object({
|
|
796
|
-
type: z7.literal("file"),
|
|
797
|
-
data: z7.union([
|
|
798
|
-
z7.string(),
|
|
799
|
-
z7.object({
|
|
800
|
-
binary: z7.literal(true),
|
|
801
|
-
sha256: Hash3,
|
|
802
|
-
bytes: z7.int().nonnegative()
|
|
803
|
-
}).strict()
|
|
804
|
-
]),
|
|
805
|
-
mediaType: z7.string(),
|
|
806
|
-
filename: z7.string().optional()
|
|
807
|
-
}).strict(),
|
|
808
|
-
z7.object({
|
|
809
|
-
type: z7.literal("image"),
|
|
810
|
-
image: z7.union([
|
|
811
|
-
z7.string(),
|
|
812
|
-
z7.object({
|
|
813
|
-
binary: z7.literal(true),
|
|
814
|
-
sha256: Hash3,
|
|
815
|
-
bytes: z7.int().nonnegative()
|
|
816
|
-
}).strict()
|
|
817
|
-
]),
|
|
818
|
-
mediaType: z7.string().optional()
|
|
819
|
-
}).strict()
|
|
820
|
-
]))
|
|
821
|
-
])
|
|
822
|
-
}).strict();
|
|
823
|
-
var ProviderMessage = z7.object({ sha256: Hash3, message: Message }).strict();
|
|
824
|
-
var ProviderRequest = z7.object({
|
|
825
|
-
runId: z7.string(),
|
|
826
|
-
attempt: z7.int().nonnegative(),
|
|
827
|
-
stepNumber: z7.int().nonnegative(),
|
|
828
|
-
modelId: z7.string(),
|
|
829
|
-
instructionsSha256: Hash3,
|
|
830
|
-
messageShas: z7.array(Hash3),
|
|
831
|
-
bodySha256: Hash3
|
|
832
|
-
}).strict();
|
|
833
|
-
var ProviderResponse = z7.object({
|
|
834
|
-
runId: z7.string(),
|
|
835
|
-
attempt: z7.int().nonnegative(),
|
|
836
|
-
stepNumber: z7.int().nonnegative(),
|
|
837
|
-
response: z7.object({ id: z7.string(), provider: z7.string().optional() }).strict()
|
|
838
|
-
}).strict();
|
|
839
|
-
function hash(value) {
|
|
840
|
-
return createHash("sha256").update(canonicalAgentJson2(value)).digest("hex");
|
|
894
|
+
// src/agent-runtime/store-archive.ts
|
|
895
|
+
function exportConversation(driver, conversationId) {
|
|
896
|
+
return driver.transaction(async (transaction) => {
|
|
897
|
+
return exportConversationIn(driver, transaction, conversationId);
|
|
898
|
+
}, { access: "read" });
|
|
841
899
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
const value = ProviderMessage.parse(raw);
|
|
852
|
-
if (hash(value.message) !== value.sha256)
|
|
853
|
-
throw new TypeError("Branch provider message digest is inconsistent");
|
|
854
|
-
const message = {
|
|
855
|
-
...value.message,
|
|
856
|
-
content: typeof value.message.content === "string" ? value.message.content : value.message.content.map((part) => {
|
|
857
|
-
if (part.type === "tool-call")
|
|
858
|
-
return {
|
|
859
|
-
...part,
|
|
860
|
-
toolCallId: this.ids.reference("call", part.toolCallId),
|
|
861
|
-
input: branchToolJson(this.ids, part.toolName, part.input, false)
|
|
862
|
-
};
|
|
863
|
-
if (part.type === "tool-result") {
|
|
864
|
-
if (part.output.type === "error-json")
|
|
865
|
-
branchOpaqueJson(part.output.value);
|
|
866
|
-
return {
|
|
867
|
-
...part,
|
|
868
|
-
toolCallId: this.ids.reference("call", part.toolCallId),
|
|
869
|
-
output: part.output.type === "json" ? {
|
|
870
|
-
...part.output,
|
|
871
|
-
value: branchToolJson(this.ids, part.toolName, part.output.value, true)
|
|
872
|
-
} : part.output
|
|
873
|
-
};
|
|
874
|
-
}
|
|
875
|
-
if (part.type === "tool-approval-request")
|
|
876
|
-
return {
|
|
877
|
-
type: part.type,
|
|
878
|
-
approvalId: this.ids.reference("approval", part.approvalId),
|
|
879
|
-
toolCallId: this.ids.reference("call", part.toolCallId)
|
|
880
|
-
};
|
|
881
|
-
if (part.type === "tool-approval-response")
|
|
882
|
-
return {
|
|
883
|
-
...part,
|
|
884
|
-
approvalId: this.ids.reference("approval", part.approvalId)
|
|
885
|
-
};
|
|
886
|
-
return part;
|
|
887
|
-
})
|
|
888
|
-
};
|
|
889
|
-
const sha256 = hash(message);
|
|
890
|
-
this.digests.set(value.sha256, sha256);
|
|
891
|
-
return { sha256, message };
|
|
900
|
+
async function importConversation(driver, bytes) {
|
|
901
|
+
const archive = decodeAgentConversationArchive2(bytes);
|
|
902
|
+
return driver.transaction(async (transaction) => {
|
|
903
|
+
const existing = await driver.events.list(transaction, {
|
|
904
|
+
conversationId: archive.conversationId,
|
|
905
|
+
limit: 1
|
|
906
|
+
});
|
|
907
|
+
if (existing.items.length > 0) {
|
|
908
|
+
throw new TypeError("Conversation event log must be empty before import");
|
|
892
909
|
}
|
|
893
|
-
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
return target;
|
|
910
|
+
for (const event of archive.events) {
|
|
911
|
+
const appended = await driver.events.append(transaction, {
|
|
912
|
+
schemaVersion: event.schemaVersion,
|
|
913
|
+
eventId: event.eventId,
|
|
914
|
+
conversationId: event.conversationId,
|
|
915
|
+
kind: event.kind,
|
|
916
|
+
occurredAt: event.occurredAt,
|
|
917
|
+
payload: event.payload,
|
|
918
|
+
...event.ignorable && { ignorable: true }
|
|
903
919
|
});
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
messageShas,
|
|
908
|
-
bodySha256: createHash("sha256").update(value.instructionsSha256).update(messageShas.join(",")).digest("hex")
|
|
909
|
-
};
|
|
920
|
+
if (appended.seq !== event.seq) {
|
|
921
|
+
throw new TypeError("Conversation archive event sequence is not contiguous");
|
|
922
|
+
}
|
|
910
923
|
}
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
|
|
924
|
+
const snapshotEntry = archive.projections.find((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) && entry.archiveType === "runtime-snapshot");
|
|
925
|
+
if (snapshotEntry && typeof snapshotEntry === "object" && !Array.isArray(snapshotEntry)) {
|
|
926
|
+
const snapshot = AgentSnapshotSchema2.parse(snapshotEntry.snapshot);
|
|
927
|
+
if (snapshot.conversationId !== archive.conversationId) {
|
|
928
|
+
throw new TypeError("Conversation archive snapshot belongs to another conversation");
|
|
929
|
+
}
|
|
930
|
+
if (snapshot.version > 0 || snapshot.messages.length > 0 || snapshot.runs.length > 0) {
|
|
931
|
+
const outcome = await driver.head.compareAndSwap(transaction, {
|
|
932
|
+
conversationId: snapshot.conversationId,
|
|
933
|
+
expectedVersion: 0,
|
|
934
|
+
next: AgentRuntimeHeadSchema2.parse({
|
|
935
|
+
schemaVersion: 1,
|
|
936
|
+
conversationId: snapshot.conversationId,
|
|
937
|
+
version: snapshot.version
|
|
938
|
+
})
|
|
939
|
+
});
|
|
940
|
+
if (outcome.outcome !== "applied") {
|
|
941
|
+
throw new TypeError("Conversation archive snapshot target is not empty");
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
const replay = replayBranchEvents(archive.conversationId, archive.events, snapshot);
|
|
945
|
+
await persistRuntimeReplay(driver, transaction, replay);
|
|
946
|
+
for (const run of snapshot.runs) {
|
|
947
|
+
const terminalAssistant = snapshot.messages.find((message) => message.id === run.assistantMessageId);
|
|
948
|
+
await driver.runs.save(transaction, AgentStoredRunSchema2.parse({
|
|
949
|
+
schemaVersion: 1,
|
|
950
|
+
run,
|
|
951
|
+
...terminalAssistant && { terminalAssistant }
|
|
952
|
+
}));
|
|
953
|
+
}
|
|
954
|
+
const restoredIds = new Set((await driver.history.load(transaction, archive.conversationId)).map((message) => message.id));
|
|
955
|
+
for (const message of snapshot.messages) {
|
|
956
|
+
if (message.role === "assistant")
|
|
957
|
+
await driver.history.apply(transaction, { type: "upsert-assistant", message });
|
|
958
|
+
else if (!restoredIds.has(message.id))
|
|
959
|
+
await driver.history.apply(transaction, { type: "admit", input: message });
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
await driver.archive?.import(transaction, archive);
|
|
963
|
+
return { conversationId: archive.conversationId, events: archive.events.length };
|
|
964
|
+
});
|
|
914
965
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
// src/agent-runtime/store-reducer.ts
|
|
921
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
922
|
-
|
|
923
|
-
// src/agent-runtime/store-reduce-shared.ts
|
|
924
|
-
function conflict(actualVersion) {
|
|
925
|
-
return { outcome: "conflict", actualVersion };
|
|
926
|
-
}
|
|
927
|
-
function applied(current, input, effects) {
|
|
928
|
-
const runs = input.runs ?? current.runs;
|
|
929
|
-
const messages = input.messages ?? current.messages;
|
|
930
|
-
return {
|
|
931
|
-
outcome: "applied",
|
|
932
|
-
snapshot: AgentSnapshotSchema2.parse({
|
|
933
|
-
...current,
|
|
934
|
-
version: current.version + 1,
|
|
935
|
-
runs,
|
|
936
|
-
messages: orderRunMessages(messages, runs)
|
|
937
|
-
}),
|
|
938
|
-
...effects?.runRecords?.length && { runRecords: effects.runRecords },
|
|
939
|
-
...effects?.admissionReceipt && { admissionReceipt: effects.admissionReceipt },
|
|
940
|
-
...effects?.seedReceipt && { seedReceipt: effects.seedReceipt },
|
|
941
|
-
...effects?.historyMutations?.length && { historyMutations: effects.historyMutations }
|
|
942
|
-
};
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// src/agent-runtime/store-reduce-history.ts
|
|
946
|
-
function reduceAccept(current, input) {
|
|
947
|
-
if (input.expectedVersion !== undefined && input.expectedVersion !== current.version) {
|
|
948
|
-
return conflict(current.version);
|
|
949
|
-
}
|
|
950
|
-
const coalescedRun = input.coalesceIntoRunId ? current.runs.find((candidate) => candidate.id === input.coalesceIntoRunId) : undefined;
|
|
951
|
-
if (input.coalesceIntoRunId !== undefined && (!coalescedRun || coalescedRun.conversationId !== input.input.conversationId || coalescedRun.state !== "queued" || coalescedRun.ownerId !== undefined || coalescedRun.terminalReason !== undefined)) {
|
|
952
|
-
return coalescedRun ? conflict(coalescedRun.revision) : { outcome: "not_found" };
|
|
953
|
-
}
|
|
954
|
-
if (input.run.conversationId !== input.input.conversationId || input.run.inputMessageIds.length !== 1 || input.run.inputMessageIds[0] !== input.input.id || input.run.state !== "queued" || input.run.revision !== 0 || input.run.executionSequence !== undefined || input.run.ownerId !== undefined || input.run.terminalReason !== undefined || input.run.terminalPolicyName !== undefined || input.input.role !== "user" && input.input.role !== "tool" || input.input.status !== "committed" || input.input.runId !== undefined || current.messages.some((message) => message.id === input.input.id) || coalescedRun !== undefined && input.input.id === coalescedRun.assistantMessageId || !coalescedRun && (input.run.assistantMessageId === input.input.id || current.runs.some((candidate) => candidate.id === input.run.id) || current.runs.some((candidate) => candidate.assistantMessageId === input.run.assistantMessageId) || current.messages.some((message) => message.id === input.run.assistantMessageId))) {
|
|
955
|
-
throw new TypeError("Input and queued run do not form one valid assignment");
|
|
956
|
-
}
|
|
957
|
-
const assignedRun = coalescedRun ? AgentRunSchema2.parse({
|
|
958
|
-
...coalescedRun,
|
|
959
|
-
inputMessageIds: [...coalescedRun.inputMessageIds, input.input.id],
|
|
960
|
-
revision: coalescedRun.revision + 1,
|
|
961
|
-
updatedAt: new Date().toISOString()
|
|
962
|
-
}) : input.run;
|
|
963
|
-
const admissionReceipt = AgentAdmissionReceiptSchema2.parse({
|
|
964
|
-
schemaVersion: 1,
|
|
965
|
-
conversationId: input.input.conversationId,
|
|
966
|
-
idempotencyKey: input.idempotencyKey,
|
|
967
|
-
input: input.input,
|
|
968
|
-
runId: assignedRun.id,
|
|
969
|
-
assistantMessageId: assignedRun.assistantMessageId
|
|
970
|
-
});
|
|
971
|
-
return applied(current, {
|
|
972
|
-
messages: [...current.messages, input.input],
|
|
973
|
-
runs: coalescedRun ? replaceRun(current.runs, assignedRun) : [...current.runs, assignedRun]
|
|
974
|
-
}, {
|
|
975
|
-
runRecords: [AgentStoredRunSchema2.parse({ schemaVersion: 1, run: assignedRun })],
|
|
976
|
-
admissionReceipt,
|
|
977
|
-
historyMutations: [{ type: "admit", input: input.input }]
|
|
978
|
-
});
|
|
979
|
-
}
|
|
980
|
-
function reduceSeed(current, input) {
|
|
981
|
-
const conversationId = input.conversationId;
|
|
982
|
-
if (current.conversationId !== conversationId)
|
|
983
|
-
return { outcome: "not_found" };
|
|
984
|
-
for (const message of input.inputs) {
|
|
985
|
-
if (message.conversationId !== conversationId || message.role !== "user" || message.status !== "committed" || message.runId !== undefined) {
|
|
986
|
-
throw new TypeError("Seeded conversation input must be committed unowned user messages");
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
return applied(current, { messages: [...input.inputs, ...current.messages] }, {
|
|
990
|
-
seedReceipt: AgentSeedReceiptSchema2.parse({
|
|
991
|
-
schemaVersion: 1,
|
|
966
|
+
async function exportConversationIn(driver, transaction, conversationId) {
|
|
967
|
+
const events = [];
|
|
968
|
+
let fromSeq;
|
|
969
|
+
do {
|
|
970
|
+
const page = AgentStoreEventPageSchema2.parse(await driver.events.list(transaction, ReadAgentStoreEventsSchema2.parse({
|
|
992
971
|
conversationId,
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
})
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
}
|
|
1015
|
-
const positions = current.messages.map((message, index) => replaced.has(message.id) ? index : undefined).filter((index) => index !== undefined);
|
|
1016
|
-
const first = positions[0];
|
|
1017
|
-
if (first === undefined || positions.some((position, offset) => position !== first + offset) || input.summary.conversationId !== input.conversationId || input.summary.runId !== undefined || input.summary.role !== "summary" || input.summary.status !== "committed" || current.messages.some((message) => message.id === input.summary.id) || current.runs.some((candidate) => candidate.assistantMessageId === input.summary.id)) {
|
|
1018
|
-
throw new TypeError("Compaction replacement must be one valid contiguous history range");
|
|
1019
|
-
}
|
|
1020
|
-
const messages = [
|
|
1021
|
-
...current.messages.slice(0, first),
|
|
1022
|
-
input.summary,
|
|
1023
|
-
...current.messages.slice(first + positions.length)
|
|
1024
|
-
];
|
|
1025
|
-
return applied(current, { messages }, {
|
|
1026
|
-
historyMutations: [
|
|
1027
|
-
{
|
|
1028
|
-
type: "replace-compacted-range",
|
|
1029
|
-
replacedMessageIds: input.replacedMessageIds,
|
|
1030
|
-
summary: input.summary
|
|
1031
|
-
}
|
|
1032
|
-
]
|
|
972
|
+
...fromSeq && { fromSeq },
|
|
973
|
+
limit: 1e4
|
|
974
|
+
})));
|
|
975
|
+
events.push(...page.items);
|
|
976
|
+
fromSeq = page.nextSeq;
|
|
977
|
+
} while (fromSeq !== undefined);
|
|
978
|
+
const durable = await driver.archive?.export(transaction, conversationId) ?? {
|
|
979
|
+
projections: [],
|
|
980
|
+
spills: []
|
|
981
|
+
};
|
|
982
|
+
const snapshot = await snapshotIn(driver, transaction, conversationId);
|
|
983
|
+
return encodeAgentConversationArchive2({
|
|
984
|
+
format: "stitchkit.agent-conversation",
|
|
985
|
+
formatVersion: 1,
|
|
986
|
+
conversationId,
|
|
987
|
+
events,
|
|
988
|
+
projections: [
|
|
989
|
+
{ archiveType: "runtime-snapshot", snapshot: z5.json().parse(snapshot) },
|
|
990
|
+
...durable.projections
|
|
991
|
+
],
|
|
992
|
+
spills: durable.spills
|
|
1033
993
|
});
|
|
1034
994
|
}
|
|
1035
995
|
|
|
1036
|
-
// src/agent-runtime/store-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
});
|
|
1066
|
-
return applied(current, {
|
|
1067
|
-
runs: replaceRun(current.runs, next),
|
|
1068
|
-
messages: replaceMessage(current.messages, input.assistant)
|
|
1069
|
-
}, {
|
|
1070
|
-
runRecords: [AgentStoredRunSchema2.parse({ schemaVersion: 1, run: next })],
|
|
1071
|
-
historyMutations: [{ type: "upsert-assistant", message: input.assistant }]
|
|
1072
|
-
});
|
|
1073
|
-
}
|
|
1074
|
-
function reduceOperation(current, run, input) {
|
|
1075
|
-
if (run.revision !== input.expectedRevision || run.state !== "running" && run.state !== "interrupt_requested" || run.ownerId !== input.ownerId || input.fencingToken !== undefined && run.fencingToken !== input.fencingToken) {
|
|
1076
|
-
return conflict(run.revision);
|
|
1077
|
-
}
|
|
1078
|
-
const next = AgentRunSchema2.parse({
|
|
1079
|
-
...run,
|
|
1080
|
-
lastOperation: input.operation,
|
|
1081
|
-
revision: run.revision + 1,
|
|
1082
|
-
updatedAt: new Date().toISOString()
|
|
1083
|
-
});
|
|
1084
|
-
return applied(current, { runs: replaceRun(current.runs, next) }, { runRecords: [AgentStoredRunSchema2.parse({ schemaVersion: 1, run: next })] });
|
|
1085
|
-
}
|
|
1086
|
-
function reduceInterrupt(current, run, input) {
|
|
1087
|
-
if (run.revision !== input.expectedRevision || run.state !== "running") {
|
|
1088
|
-
return conflict(run.revision);
|
|
1089
|
-
}
|
|
1090
|
-
const next = AgentRunSchema2.parse({
|
|
1091
|
-
...run,
|
|
1092
|
-
state: "interrupt_requested",
|
|
1093
|
-
revision: run.revision + 1,
|
|
1094
|
-
updatedAt: new Date().toISOString()
|
|
1095
|
-
});
|
|
1096
|
-
return applied(current, { runs: replaceRun(current.runs, next) }, {
|
|
1097
|
-
runRecords: [AgentStoredRunSchema2.parse({ schemaVersion: 1, run: next })]
|
|
1098
|
-
});
|
|
1099
|
-
}
|
|
1100
|
-
function reduceRecover(current, run, input, retainedAssistant) {
|
|
1101
|
-
if (run.revision !== input.expectedRevision || !isActiveRunState(run.state)) {
|
|
1102
|
-
return conflict(run.revision);
|
|
1103
|
-
}
|
|
1104
|
-
if (input.action === "requeue" && run.state !== "queued" && input.replaySafe !== true) {
|
|
1105
|
-
throw new TypeError("Recovering an acquired run requires explicit replaySafe evidence");
|
|
1106
|
-
}
|
|
1107
|
-
const { ownerId: _released, ...carried } = run;
|
|
1108
|
-
const next = AgentRunSchema2.parse({
|
|
1109
|
-
...carried,
|
|
1110
|
-
state: input.action === "requeue" ? "queued" : "abandoned",
|
|
1111
|
-
revision: run.revision + 1,
|
|
1112
|
-
...input.action === "abandon" && { terminalReason: "abandoned" },
|
|
1113
|
-
updatedAt: new Date().toISOString()
|
|
1114
|
-
});
|
|
1115
|
-
if (input.action === "abandon") {
|
|
1116
|
-
if (retainedAssistant && (retainedAssistant.id !== run.assistantMessageId || retainedAssistant.conversationId !== run.conversationId || retainedAssistant.runId !== run.id || retainedAssistant.role !== "assistant" || retainedAssistant.status !== "failed"))
|
|
1117
|
-
throw new TypeError("Abandoned assistant does not match its run");
|
|
1118
|
-
const existingAssistant = current.messages.find((message) => message.id === run.assistantMessageId);
|
|
1119
|
-
const assistant = retainedAssistant ?? AgentMessageSchema2.parse({
|
|
1120
|
-
...existingAssistant ?? {
|
|
1121
|
-
schemaVersion: 1,
|
|
1122
|
-
id: run.assistantMessageId,
|
|
1123
|
-
conversationId: run.conversationId,
|
|
1124
|
-
runId: run.id,
|
|
1125
|
-
role: "assistant",
|
|
1126
|
-
parts: [],
|
|
1127
|
-
createdAt: run.createdAt
|
|
1128
|
-
},
|
|
1129
|
-
status: "failed",
|
|
1130
|
-
updatedAt: new Date().toISOString()
|
|
1131
|
-
});
|
|
1132
|
-
return applied(current, {
|
|
1133
|
-
runs: replaceRun(current.runs, next),
|
|
1134
|
-
messages: replaceMessage(current.messages, assistant)
|
|
1135
|
-
}, {
|
|
1136
|
-
runRecords: [
|
|
1137
|
-
AgentStoredRunSchema2.parse({
|
|
1138
|
-
schemaVersion: 1,
|
|
1139
|
-
run: next,
|
|
1140
|
-
terminalAssistant: assistant
|
|
1141
|
-
})
|
|
1142
|
-
],
|
|
1143
|
-
historyMutations: [{ type: "upsert-assistant", message: assistant }]
|
|
1144
|
-
});
|
|
996
|
+
// src/agent-runtime/store-branch.ts
|
|
997
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
998
|
+
import { z as z13 } from "zod";
|
|
999
|
+
|
|
1000
|
+
// src/agent-runtime/branch-events.ts
|
|
1001
|
+
import { z as z9 } from "zod";
|
|
1002
|
+
|
|
1003
|
+
// src/agent-runtime/branch-messages.ts
|
|
1004
|
+
import { z as z6 } from "zod";
|
|
1005
|
+
function branchOpaqueJson(value) {
|
|
1006
|
+
if (Array.isArray(value))
|
|
1007
|
+
return value.map(branchOpaqueJson);
|
|
1008
|
+
if (value !== null && typeof value === "object") {
|
|
1009
|
+
for (const key of [
|
|
1010
|
+
"reference",
|
|
1011
|
+
"conversationId",
|
|
1012
|
+
"runId",
|
|
1013
|
+
"messageId",
|
|
1014
|
+
"callId",
|
|
1015
|
+
"toolCallId",
|
|
1016
|
+
"approvalId",
|
|
1017
|
+
"operationId",
|
|
1018
|
+
"artifactId"
|
|
1019
|
+
]) {
|
|
1020
|
+
if (key in value)
|
|
1021
|
+
throw new TypeError(`Branch cannot rekey an opaque payload containing ${key}`);
|
|
1022
|
+
}
|
|
1023
|
+
for (const entry of Object.values(value))
|
|
1024
|
+
branchOpaqueJson(entry);
|
|
1145
1025
|
}
|
|
1146
|
-
return
|
|
1147
|
-
runRecords: [AgentStoredRunSchema2.parse({ schemaVersion: 1, run: next })]
|
|
1148
|
-
});
|
|
1026
|
+
return value;
|
|
1149
1027
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1028
|
+
var ArtifactReadSchema = z6.object({
|
|
1029
|
+
reference: z6.string(),
|
|
1030
|
+
offset: z6.int().nonnegative(),
|
|
1031
|
+
maxBytes: z6.int().positive().optional()
|
|
1032
|
+
}).strict();
|
|
1033
|
+
var ArtifactSearchSchema = z6.object({
|
|
1034
|
+
reference: z6.string(),
|
|
1035
|
+
query: z6.string(),
|
|
1036
|
+
maxMatches: z6.int().positive().optional()
|
|
1037
|
+
}).strict();
|
|
1038
|
+
var ArtifactReadResultSchema = z6.object({
|
|
1039
|
+
reference: z6.string(),
|
|
1040
|
+
offset: z6.int().nonnegative(),
|
|
1041
|
+
text: z6.string(),
|
|
1042
|
+
bytes: z6.int().nonnegative(),
|
|
1043
|
+
totalBytes: z6.int().nonnegative().optional()
|
|
1044
|
+
}).strict();
|
|
1045
|
+
var ArtifactSearchResultSchema = z6.object({
|
|
1046
|
+
reference: z6.string(),
|
|
1047
|
+
matches: z6.array(z6.object({ line: z6.int().positive(), text: z6.string() }).strict())
|
|
1048
|
+
}).strict();
|
|
1049
|
+
function branchToolJson(ids, name, value, output) {
|
|
1050
|
+
if (name === AGENT_CODING_TOOL_NAMES.readOutput || name === AGENT_CODING_TOOL_NAMES.searchOutput) {
|
|
1051
|
+
const parsed = output ? name === AGENT_CODING_TOOL_NAMES.readOutput ? ArtifactReadResultSchema.parse(value) : ArtifactSearchResultSchema.parse(value) : name === AGENT_CODING_TOOL_NAMES.readOutput ? ArtifactReadSchema.parse(value) : ArtifactSearchSchema.parse(value);
|
|
1052
|
+
return { ...parsed, reference: ids.reference("spill", parsed.reference) };
|
|
1161
1053
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1054
|
+
if (output && name === AGENT_CODING_TOOL_NAMES.runCommand) {
|
|
1055
|
+
const parsed = ShellOutputSchema.parse(value);
|
|
1056
|
+
return {
|
|
1057
|
+
...parsed,
|
|
1058
|
+
...parsed.artifact && {
|
|
1059
|
+
artifact: {
|
|
1060
|
+
...parsed.artifact,
|
|
1061
|
+
reference: ids.reference("spill", parsed.artifact.reference)
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1165
1065
|
}
|
|
1166
|
-
|
|
1167
|
-
const candidate = current.runs.find((item) => item.id === entry.runId);
|
|
1168
|
-
return candidate !== undefined && candidate.id !== run.id && candidate.state === "queued" && candidate.conversationId === run.conversationId && candidate.inputMessageIds.length === entry.inputMessageIds.length && candidate.inputMessageIds.every((id, index) => id === entry.inputMessageIds[index]);
|
|
1169
|
-
});
|
|
1170
|
-
const absorbedIds = new Set(absorbable.flatMap((entry) => entry.inputMessageIds));
|
|
1171
|
-
const next = AgentRunSchema2.parse({
|
|
1172
|
-
...run,
|
|
1173
|
-
state: runStateForTerminalReason2(input.reason),
|
|
1174
|
-
terminalReason: input.reason,
|
|
1175
|
-
...input.policyName && { terminalPolicyName: input.policyName },
|
|
1176
|
-
...input.usage && { usage: input.usage },
|
|
1177
|
-
...absorbedIds.size > 0 && {
|
|
1178
|
-
inputMessageIds: [
|
|
1179
|
-
...run.inputMessageIds,
|
|
1180
|
-
...[...absorbedIds].filter((id) => !run.inputMessageIds.includes(id))
|
|
1181
|
-
]
|
|
1182
|
-
},
|
|
1183
|
-
revision: run.revision + 1,
|
|
1184
|
-
updatedAt: new Date().toISOString()
|
|
1185
|
-
});
|
|
1186
|
-
const absorbedRuns = absorbable.map((entry) => {
|
|
1187
|
-
const candidate = current.runs.find((item) => item.id === entry.runId);
|
|
1188
|
-
if (!candidate)
|
|
1189
|
-
throw new TypeError("Absorbed run disappeared inside the reducer");
|
|
1190
|
-
return AgentRunSchema2.parse({
|
|
1191
|
-
...candidate,
|
|
1192
|
-
state: runStateForTerminalReason2("absorbed"),
|
|
1193
|
-
terminalReason: "absorbed",
|
|
1194
|
-
absorbedIntoRunId: next.id,
|
|
1195
|
-
revision: candidate.revision + 1,
|
|
1196
|
-
updatedAt: new Date().toISOString()
|
|
1197
|
-
});
|
|
1198
|
-
});
|
|
1199
|
-
const runs = absorbedRuns.reduce((accumulated, absorbed) => replaceRun(accumulated, absorbed), replaceRun(current.runs, next));
|
|
1200
|
-
return applied(current, {
|
|
1201
|
-
runs,
|
|
1202
|
-
messages: replaceMessage(current.messages, input.assistant)
|
|
1203
|
-
}, {
|
|
1204
|
-
runRecords: [
|
|
1205
|
-
AgentStoredRunSchema2.parse({
|
|
1206
|
-
schemaVersion: 1,
|
|
1207
|
-
run: next,
|
|
1208
|
-
terminalAssistant: input.assistant
|
|
1209
|
-
}),
|
|
1210
|
-
...absorbedRuns.map((absorbed) => AgentStoredRunSchema2.parse({ schemaVersion: 1, run: absorbed }))
|
|
1211
|
-
],
|
|
1212
|
-
historyMutations: [{ type: "upsert-assistant", message: input.assistant }]
|
|
1213
|
-
});
|
|
1066
|
+
return branchOpaqueJson(value);
|
|
1214
1067
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1068
|
+
function branchPart(ids, part) {
|
|
1069
|
+
switch (part.type) {
|
|
1070
|
+
case "file":
|
|
1071
|
+
return { ...part, reference: ids.reference("spill", part.reference) };
|
|
1072
|
+
case "tool-call": {
|
|
1073
|
+
if (part.provider)
|
|
1074
|
+
branchOpaqueJson(part.provider.data);
|
|
1075
|
+
return {
|
|
1076
|
+
...part,
|
|
1077
|
+
callId: ids.reference("call", part.callId),
|
|
1078
|
+
input: branchToolJson(ids, part.toolName, part.input, false)
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
case "tool-result":
|
|
1082
|
+
return {
|
|
1083
|
+
...part,
|
|
1084
|
+
callId: ids.reference("call", part.callId),
|
|
1085
|
+
...part.output !== undefined && {
|
|
1086
|
+
output: part.outcome === "success" ? branchToolJson(ids, part.toolName, part.output, true) : branchOpaqueJson(part.output)
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
case "tool-approval-request":
|
|
1090
|
+
return {
|
|
1091
|
+
type: part.type,
|
|
1092
|
+
approvalId: ids.reference("approval", part.approvalId),
|
|
1093
|
+
callId: ids.reference("call", part.callId)
|
|
1094
|
+
};
|
|
1095
|
+
case "tool-approval-response":
|
|
1096
|
+
return { ...part, approvalId: ids.reference("approval", part.approvalId) };
|
|
1097
|
+
case "provider":
|
|
1098
|
+
throw new TypeError("Branch cannot rekey an opaque provider part");
|
|
1099
|
+
case "reasoning":
|
|
1100
|
+
if (part.provider)
|
|
1101
|
+
branchOpaqueJson(part.provider.data);
|
|
1102
|
+
return part;
|
|
1103
|
+
default:
|
|
1104
|
+
return part;
|
|
1222
1105
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1106
|
+
}
|
|
1107
|
+
function branchMessage(ids, sourceConversationId, message) {
|
|
1108
|
+
if (message.conversationId !== sourceConversationId)
|
|
1109
|
+
throw new TypeError("Branch message belongs to another conversation");
|
|
1227
1110
|
return {
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
parts: assistant.parts.length,
|
|
1236
|
-
bytes: Buffer.byteLength(body)
|
|
1237
|
-
}
|
|
1111
|
+
...message,
|
|
1112
|
+
id: ids.reference("message", message.id),
|
|
1113
|
+
conversationId: ids.conversationId,
|
|
1114
|
+
...message.runId && { runId: ids.reference("run", message.runId) },
|
|
1115
|
+
parts: message.parts.map((part) => branchPart(ids, part)),
|
|
1116
|
+
...message.metadata && {
|
|
1117
|
+
metadata: z6.record(z6.string(), z6.json()).parse(branchOpaqueJson(message.metadata))
|
|
1238
1118
|
}
|
|
1239
1119
|
};
|
|
1240
1120
|
}
|
|
1241
|
-
function
|
|
1242
|
-
if (
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1121
|
+
function branchRun(ids, sourceConversationId, run, settled) {
|
|
1122
|
+
if (run.conversationId !== sourceConversationId)
|
|
1123
|
+
throw new TypeError("Branch run belongs to another conversation");
|
|
1124
|
+
const { ownerId, fencingToken, ...record } = run;
|
|
1125
|
+
return {
|
|
1126
|
+
...record,
|
|
1127
|
+
id: ids.reference("run", run.id),
|
|
1128
|
+
conversationId: ids.conversationId,
|
|
1129
|
+
inputMessageIds: run.inputMessageIds.map((id) => ids.reference("message", id)),
|
|
1130
|
+
assistantMessageId: ids.reference("message", run.assistantMessageId),
|
|
1131
|
+
...run.absorbedIntoRunId && {
|
|
1132
|
+
absorbedIntoRunId: ids.reference("run", run.absorbedIntoRunId)
|
|
1133
|
+
},
|
|
1134
|
+
...run.lastOperation && {
|
|
1135
|
+
lastOperation: {
|
|
1136
|
+
...run.lastOperation,
|
|
1137
|
+
operationId: ids.reference("operation", run.lastOperation.operationId)
|
|
1138
|
+
}
|
|
1139
|
+
},
|
|
1140
|
+
...!settled && ownerId && { ownerId: ids.define("owner", ownerId) },
|
|
1141
|
+
...!settled && fencingToken !== undefined && { fencingToken }
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// src/agent-runtime/branch-observations.ts
|
|
1146
|
+
import { z as z7 } from "zod";
|
|
1147
|
+
var Id = z7.string().min(1);
|
|
1148
|
+
var Hash2 = z7.string().regex(/^[a-f0-9]{64}$/);
|
|
1149
|
+
var State = z7.object({
|
|
1150
|
+
name: Id,
|
|
1151
|
+
version: z7.int().positive(),
|
|
1152
|
+
value: z7.json(),
|
|
1153
|
+
actor: z7.enum(["agent", "human", "system"])
|
|
1154
|
+
}).strict();
|
|
1155
|
+
var Retry = z7.object({
|
|
1156
|
+
attempt: z7.int().positive(),
|
|
1157
|
+
delayMs: z7.number().nonnegative().optional(),
|
|
1158
|
+
reason: z7.string().optional()
|
|
1159
|
+
}).strict();
|
|
1160
|
+
var Spill = z7.object({ reference: Id, bytes: z7.int().nonnegative(), sha256: Hash2.optional() }).strict();
|
|
1161
|
+
var Schedule = z7.object({
|
|
1162
|
+
id: Id,
|
|
1163
|
+
kind: z7.enum(["at", "after", "every"]).optional(),
|
|
1164
|
+
nextAt: z7.string().nullable().optional(),
|
|
1165
|
+
occurrence: z7.int().nonnegative().optional(),
|
|
1166
|
+
lateByMs: z7.number().nonnegative().optional(),
|
|
1167
|
+
state: z7.string().optional(),
|
|
1168
|
+
message: z7.string().optional(),
|
|
1169
|
+
attempts: z7.int().nonnegative().optional(),
|
|
1170
|
+
retryAt: z7.string().nullable().optional(),
|
|
1171
|
+
terminal: z7.boolean().optional(),
|
|
1172
|
+
skipped: z7.boolean().optional()
|
|
1173
|
+
}).strict();
|
|
1174
|
+
var Child = z7.object({
|
|
1175
|
+
childConversationId: Id.optional(),
|
|
1176
|
+
parentConversationId: Id.optional(),
|
|
1177
|
+
seedUptoSeq: z7.int().nonnegative().optional(),
|
|
1178
|
+
budget: z7.json().optional(),
|
|
1179
|
+
state: z7.string().optional(),
|
|
1180
|
+
resultReference: Id.optional(),
|
|
1181
|
+
reason: z7.string().optional(),
|
|
1182
|
+
action: z7.enum(["message-sent", "blocking-presented", "blocking-resolved"]).optional(),
|
|
1183
|
+
approvalId: Id.optional(),
|
|
1184
|
+
childApprovalId: Id.optional(),
|
|
1185
|
+
callId: Id.optional(),
|
|
1186
|
+
toolName: Id.optional(),
|
|
1187
|
+
input: z7.json().optional(),
|
|
1188
|
+
batchId: Id.optional(),
|
|
1189
|
+
kind: z7.string().optional(),
|
|
1190
|
+
presentedAt: z7.string().optional(),
|
|
1191
|
+
approved: z7.boolean().optional()
|
|
1192
|
+
}).strict();
|
|
1193
|
+
var Rejection = z7.object({ approvalId: Id, callId: Id, toolName: Id, reason: z7.string() }).strict();
|
|
1194
|
+
function branchObservation(ids, kind, raw) {
|
|
1195
|
+
if (kind.startsWith("durability/")) {
|
|
1196
|
+
const value = kind === "durability/step" ? StepEventPayloadSchema.parse(raw) : kind === "durability/park" ? ParkEventPayloadSchema.parse(raw) : kind === "durability/event" ? DeliveredEventPayloadSchema.parse(raw) : kind === "durability/effect" ? EffectEventPayloadSchema.parse(raw) : undefined;
|
|
1197
|
+
if (!value || canonicalAgentJson2(value) !== canonicalAgentJson2(raw))
|
|
1198
|
+
throw new TypeError("Branch cannot rekey opaque durability payload");
|
|
1199
|
+
if ("encoded" in value)
|
|
1200
|
+
branchOpaqueJson(z7.json().parse(JSON.parse(value.encoded)));
|
|
1201
|
+
return {
|
|
1202
|
+
...value,
|
|
1203
|
+
runId: ids.reference("run", value.runId),
|
|
1204
|
+
..."id" in value && { id: ids.define("key", value.id) }
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
if (kind === "sandbox/probed")
|
|
1208
|
+
return z7.object({
|
|
1209
|
+
result: AgentSandboxGradeSchema2,
|
|
1210
|
+
required: z7.array(AgentSandboxRestrictionSchema2),
|
|
1211
|
+
missing: z7.array(AgentSandboxRestrictionSchema2)
|
|
1212
|
+
}).strict().parse(raw);
|
|
1213
|
+
if (kind === "state/set") {
|
|
1214
|
+
const value = State.parse(raw);
|
|
1215
|
+
branchOpaqueJson(value.value);
|
|
1216
|
+
return value;
|
|
1217
|
+
}
|
|
1218
|
+
if (kind === "retry/scheduled" || kind === "retry/started")
|
|
1219
|
+
return Retry.parse(raw);
|
|
1220
|
+
if (kind === "spill/created" || kind === "spill/deleted") {
|
|
1221
|
+
const value = Spill.parse(raw);
|
|
1222
|
+
return { ...value, reference: ids.reference("spill", value.reference) };
|
|
1223
|
+
}
|
|
1224
|
+
if (kind.startsWith("schedule/")) {
|
|
1225
|
+
const value = Schedule.parse(raw);
|
|
1226
|
+
return { ...value, id: ids.define("schedule", value.id) };
|
|
1227
|
+
}
|
|
1228
|
+
if (kind.startsWith("child/")) {
|
|
1229
|
+
const value = Child.parse(raw);
|
|
1230
|
+
if (value.budget)
|
|
1231
|
+
branchOpaqueJson(value.budget);
|
|
1232
|
+
if (value.input)
|
|
1233
|
+
branchOpaqueJson(value.input);
|
|
1234
|
+
return {
|
|
1235
|
+
...value,
|
|
1236
|
+
...value.childConversationId && {
|
|
1237
|
+
childConversationId: value.childConversationId === ids.sourceConversationId ? ids.conversationId : ids.define("child", value.childConversationId)
|
|
1238
|
+
},
|
|
1239
|
+
...value.parentConversationId && {
|
|
1240
|
+
parentConversationId: value.parentConversationId === ids.sourceConversationId ? ids.conversationId : ids.define("child", value.parentConversationId)
|
|
1241
|
+
},
|
|
1242
|
+
...value.resultReference && {
|
|
1243
|
+
resultReference: ids.reference("spill", value.resultReference)
|
|
1244
|
+
},
|
|
1245
|
+
...value.approvalId && { approvalId: ids.define("approval", value.approvalId) },
|
|
1246
|
+
...value.childApprovalId && {
|
|
1247
|
+
childApprovalId: ids.define("approval", value.childApprovalId)
|
|
1248
|
+
},
|
|
1249
|
+
...value.callId && { callId: ids.reference("call", value.callId) },
|
|
1250
|
+
...value.batchId && { batchId: ids.define("key", value.batchId) }
|
|
1251
|
+
};
|
|
1264
1252
|
}
|
|
1253
|
+
if (kind === "approval/response-rejected") {
|
|
1254
|
+
const value = Rejection.parse(raw);
|
|
1255
|
+
return {
|
|
1256
|
+
...value,
|
|
1257
|
+
approvalId: ids.reference("approval", value.approvalId),
|
|
1258
|
+
callId: ids.reference("call", value.callId)
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
throw new TypeError(`Branch cannot rekey opaque event kind ${kind}`);
|
|
1265
1262
|
}
|
|
1266
|
-
|
|
1267
|
-
return operation.type === "accept" ? operation.input.input.conversationId : operation.input.conversationId;
|
|
1268
|
-
}
|
|
1263
|
+
var BranchObservationSchema = z7.object({ sourceKind: Id, payload: z7.json() }).strict();
|
|
1269
1264
|
|
|
1270
|
-
// src/agent-runtime/branch-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
head: AgentRuntimeHeadSchema2,
|
|
1275
|
-
messages: z8.array(AgentMessageSchema2),
|
|
1276
|
-
compacted: z8.array(z8.string().min(1)).optional(),
|
|
1277
|
-
runs: z8.array(AgentStoredRunSchema2),
|
|
1278
|
-
admissions: z8.array(AgentAdmissionReceiptSchema2)
|
|
1279
|
-
}).strict();
|
|
1280
|
-
var CheckpointRecordSchema = z8.object({
|
|
1281
|
-
type: z8.literal("checkpoint"),
|
|
1282
|
-
input: z8.object({
|
|
1283
|
-
conversationId: z8.string().min(1),
|
|
1284
|
-
runId: z8.string().min(1),
|
|
1285
|
-
expectedRevision: z8.int().nonnegative(),
|
|
1286
|
-
ownerId: z8.string().min(1),
|
|
1287
|
-
fencingToken: z8.int().nonnegative().optional(),
|
|
1288
|
-
assistant: z8.object({
|
|
1289
|
-
id: z8.string().min(1),
|
|
1290
|
-
status: z8.literal("streaming"),
|
|
1291
|
-
sha256: z8.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
1292
|
-
sourceSha256: z8.string().regex(/^[a-f0-9]{64}$/).optional(),
|
|
1293
|
-
parts: z8.int().nonnegative(),
|
|
1294
|
-
bytes: z8.int().nonnegative()
|
|
1295
|
-
}).strict(),
|
|
1296
|
-
usage: AgentUsageSchema2.optional()
|
|
1297
|
-
}).strict()
|
|
1298
|
-
}).strict().refine((value) => Boolean(value.input.assistant.sha256) !== Boolean(value.input.assistant.sourceSha256));
|
|
1299
|
-
function branchTransition(event) {
|
|
1300
|
-
const checkpoint = CheckpointRecordSchema.safeParse(event.payload);
|
|
1301
|
-
if (!checkpoint.success)
|
|
1302
|
-
return AgentStoreTransitionSchema2.parse(event.payload);
|
|
1303
|
-
const input = checkpoint.data.input;
|
|
1304
|
-
if (canonicalAgentJson2(checkpoint.data) !== canonicalAgentJson2(event.payload))
|
|
1305
|
-
throw new TypeError("Branch checkpoint contains an unknown opaque field");
|
|
1306
|
-
return AgentStoreTransitionSchema2.parse({
|
|
1307
|
-
type: "checkpoint",
|
|
1308
|
-
input: {
|
|
1309
|
-
...input,
|
|
1310
|
-
assistant: {
|
|
1311
|
-
schemaVersion: 1,
|
|
1312
|
-
id: input.assistant.id,
|
|
1313
|
-
conversationId: input.conversationId,
|
|
1314
|
-
runId: input.runId,
|
|
1315
|
-
role: "assistant",
|
|
1316
|
-
status: "streaming",
|
|
1317
|
-
parts: [],
|
|
1318
|
-
createdAt: event.occurredAt,
|
|
1319
|
-
updatedAt: event.occurredAt
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
});
|
|
1265
|
+
// src/agent-runtime/branch-provider.ts
|
|
1266
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1267
|
+
function hash(value) {
|
|
1268
|
+
return createHash3("sha256").update(canonicalAgentJson2(value)).digest("hex");
|
|
1323
1269
|
}
|
|
1324
|
-
function
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
const
|
|
1342
|
-
if (
|
|
1343
|
-
throw new TypeError("Branch
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
const
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1270
|
+
function checkFileData(value) {
|
|
1271
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
1272
|
+
return;
|
|
1273
|
+
if (value.type === "reference" && "reference" in value)
|
|
1274
|
+
branchOpaqueJson(value.reference);
|
|
1275
|
+
else if (!("type" in value) && value.binary !== true)
|
|
1276
|
+
branchOpaqueJson(value);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
class BranchProvider {
|
|
1280
|
+
ids;
|
|
1281
|
+
digests = new Map;
|
|
1282
|
+
constructor(ids) {
|
|
1283
|
+
this.ids = ids;
|
|
1284
|
+
}
|
|
1285
|
+
rekey(kind, raw) {
|
|
1286
|
+
if (kind === "provider/message") {
|
|
1287
|
+
const value = ProviderJournalMessageSchema.parse(raw);
|
|
1288
|
+
if (hash(value.message) !== value.sha256)
|
|
1289
|
+
throw new TypeError("Branch provider message digest is inconsistent");
|
|
1290
|
+
if (value.message.providerOptions)
|
|
1291
|
+
branchOpaqueJson(value.message.providerOptions);
|
|
1292
|
+
const message = {
|
|
1293
|
+
...value.message,
|
|
1294
|
+
content: typeof value.message.content === "string" ? value.message.content : value.message.content.map((part) => {
|
|
1295
|
+
if ("providerOptions" in part && part.providerOptions)
|
|
1296
|
+
branchOpaqueJson(part.providerOptions);
|
|
1297
|
+
if (part.type === "file" || part.type === "reasoning-file")
|
|
1298
|
+
checkFileData(part.data);
|
|
1299
|
+
if (part.type === "image")
|
|
1300
|
+
checkFileData(part.image);
|
|
1301
|
+
if (part.type === "tool-call")
|
|
1302
|
+
return {
|
|
1303
|
+
...part,
|
|
1304
|
+
toolCallId: this.ids.reference("call", part.toolCallId),
|
|
1305
|
+
input: branchToolJson(this.ids, part.toolName, part.input, false)
|
|
1306
|
+
};
|
|
1307
|
+
if (part.type === "tool-result") {
|
|
1308
|
+
if ("providerOptions" in part.output && part.output.providerOptions)
|
|
1309
|
+
branchOpaqueJson(part.output.providerOptions);
|
|
1310
|
+
if (part.output.type === "error-json")
|
|
1311
|
+
branchOpaqueJson(part.output.value);
|
|
1312
|
+
if (part.output.type === "content")
|
|
1313
|
+
for (const item of part.output.value) {
|
|
1314
|
+
if (item.providerOptions)
|
|
1315
|
+
branchOpaqueJson(item.providerOptions);
|
|
1316
|
+
if (item.type === "file")
|
|
1317
|
+
checkFileData(item.data);
|
|
1318
|
+
if ("providerReference" in item)
|
|
1319
|
+
branchOpaqueJson(item.providerReference);
|
|
1320
|
+
if ("fileId" in item && typeof item.fileId !== "string")
|
|
1321
|
+
branchOpaqueJson(item.fileId);
|
|
1322
|
+
}
|
|
1323
|
+
return {
|
|
1324
|
+
...part,
|
|
1325
|
+
toolCallId: this.ids.reference("call", part.toolCallId),
|
|
1326
|
+
output: part.output.type === "json" ? {
|
|
1327
|
+
...part.output,
|
|
1328
|
+
value: branchToolJson(this.ids, part.toolName, part.output.value, true)
|
|
1329
|
+
} : part.output
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
if (part.type === "tool-approval-request")
|
|
1333
|
+
return {
|
|
1334
|
+
type: part.type,
|
|
1335
|
+
approvalId: this.ids.reference("approval", part.approvalId),
|
|
1336
|
+
toolCallId: this.ids.reference("call", part.toolCallId),
|
|
1337
|
+
...part.reason !== undefined && { reason: part.reason }
|
|
1338
|
+
};
|
|
1339
|
+
if (part.type === "tool-approval-response")
|
|
1340
|
+
return {
|
|
1341
|
+
...part,
|
|
1342
|
+
approvalId: this.ids.reference("approval", part.approvalId)
|
|
1343
|
+
};
|
|
1344
|
+
return part;
|
|
1345
|
+
})
|
|
1370
1346
|
};
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
continue;
|
|
1375
|
-
let operation = branchTransition(event);
|
|
1376
|
-
if (operation.type === "checkpoint" && retainedSnapshot) {
|
|
1377
|
-
const record = CheckpointRecordSchema.safeParse(event.payload);
|
|
1378
|
-
const assistantId = operation.input.assistant.id;
|
|
1379
|
-
const retained = retainedSnapshot.messages.find((message) => message.id === assistantId);
|
|
1380
|
-
if (record.success && retained && record.data.input.assistant.sha256 === createHash3("sha256").update(canonicalAgentJson2(retained)).digest("hex")) {
|
|
1381
|
-
operation = { ...operation, input: { ...operation.input, assistant: retained } };
|
|
1382
|
-
result.retainedMessages.set(event.eventId, retained);
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
if (operation.type === "recover" && operation.input.action === "abandon" && !operation.assistant && retainedSnapshot) {
|
|
1386
|
-
const runId = operation.input.runId;
|
|
1387
|
-
const run = result.snapshot.runs.find((run) => run.id === runId);
|
|
1388
|
-
const retained = retainedSnapshot.messages.find((message) => message.id === run?.assistantMessageId && message.runId === runId && message.role === "assistant" && message.status === "failed");
|
|
1389
|
-
if (retained)
|
|
1390
|
-
operation = { ...operation, assistant: retained };
|
|
1347
|
+
const sha256 = hash(message);
|
|
1348
|
+
this.digests.set(value.sha256, sha256);
|
|
1349
|
+
return { sha256, message };
|
|
1391
1350
|
}
|
|
1392
|
-
if (
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
result.effects.push({ ...effects, ...runRecords && { runRecords } });
|
|
1410
|
-
if (operation.type === "recover" && operation.input.action === "abandon") {
|
|
1411
|
-
const runId = operation.input.runId;
|
|
1412
|
-
const assistant = reduced.runRecords?.find((record) => record.run.id === runId)?.terminalAssistant;
|
|
1413
|
-
if (assistant)
|
|
1414
|
-
result.retainedMessages.set(event.eventId, assistant);
|
|
1415
|
-
if (operation.assistant)
|
|
1416
|
-
result.unresolvedDrafts.delete(runId);
|
|
1351
|
+
if (kind === "provider/request") {
|
|
1352
|
+
const value = ProviderJournalRequestSchema.parse(raw);
|
|
1353
|
+
const sourceHash = createHash3("sha256").update(value.instructionsSha256).update(value.messageShas.join(",")).digest("hex");
|
|
1354
|
+
if (sourceHash !== value.bodySha256)
|
|
1355
|
+
throw new TypeError("Branch provider request digest is inconsistent");
|
|
1356
|
+
const messageShas = value.messageShas.map((sha) => {
|
|
1357
|
+
const target = this.digests.get(sha);
|
|
1358
|
+
if (!target)
|
|
1359
|
+
throw new TypeError("Branch provider request references a missing message");
|
|
1360
|
+
return target;
|
|
1361
|
+
});
|
|
1362
|
+
return {
|
|
1363
|
+
...value,
|
|
1364
|
+
runId: this.ids.reference("run", value.runId),
|
|
1365
|
+
messageShas,
|
|
1366
|
+
bodySha256: createHash3("sha256").update(value.instructionsSha256).update(messageShas.join(",")).digest("hex")
|
|
1367
|
+
};
|
|
1417
1368
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
if (operation.type === "terminal")
|
|
1421
|
-
result.unresolvedDrafts.delete(operation.input.runId);
|
|
1369
|
+
const value = ProviderJournalResponseSchema.parse(raw);
|
|
1370
|
+
return { ...value, runId: this.ids.reference("run", value.runId) };
|
|
1422
1371
|
}
|
|
1423
|
-
return result;
|
|
1424
1372
|
}
|
|
1425
1373
|
|
|
1426
1374
|
// src/agent-runtime/branch-transitions.ts
|
|
1427
|
-
import { z as
|
|
1375
|
+
import { z as z8 } from "zod";
|
|
1428
1376
|
function rekeyBranchTransition(ids, source, operation) {
|
|
1429
1377
|
const message = (value) => branchMessage(ids, source, value);
|
|
1430
1378
|
const input = operation.input;
|
|
@@ -1495,11 +1443,11 @@ function rekeyBranchTransition(ids, source, operation) {
|
|
|
1495
1443
|
}
|
|
1496
1444
|
}
|
|
1497
1445
|
function branchJson(value) {
|
|
1498
|
-
return
|
|
1446
|
+
return z8.json().parse(value);
|
|
1499
1447
|
}
|
|
1500
1448
|
|
|
1501
1449
|
// src/agent-runtime/branch-events.ts
|
|
1502
|
-
var
|
|
1450
|
+
var Hash3 = z9.string().regex(/^[a-f0-9]{64}$/);
|
|
1503
1451
|
function rekeyBranchEvents(ids, source, events, retainedMessages = new Map) {
|
|
1504
1452
|
const hydrated = events.map((event) => {
|
|
1505
1453
|
const assistant = retainedMessages.get(event.eventId);
|
|
@@ -1546,14 +1494,14 @@ function rekeyBranchEvent(ids, source, event, provider) {
|
|
|
1546
1494
|
if (event.kind === "runtime/transition") {
|
|
1547
1495
|
const operation = branchTransition(event);
|
|
1548
1496
|
const rekeyed = rekeyBranchTransition(ids, source, operation);
|
|
1549
|
-
const raw =
|
|
1550
|
-
type:
|
|
1551
|
-
input:
|
|
1552
|
-
assistant:
|
|
1553
|
-
sha256:
|
|
1554
|
-
sourceSha256:
|
|
1555
|
-
bytes:
|
|
1556
|
-
parts:
|
|
1497
|
+
const raw = z9.object({
|
|
1498
|
+
type: z9.string(),
|
|
1499
|
+
input: z9.object({
|
|
1500
|
+
assistant: z9.object({
|
|
1501
|
+
sha256: Hash3.optional(),
|
|
1502
|
+
sourceSha256: Hash3.optional(),
|
|
1503
|
+
bytes: z9.int(),
|
|
1504
|
+
parts: z9.int()
|
|
1557
1505
|
}).passthrough()
|
|
1558
1506
|
}).passthrough()
|
|
1559
1507
|
}).passthrough().safeParse(event.payload);
|
|
@@ -1713,6 +1661,16 @@ class BranchIdentities {
|
|
|
1713
1661
|
import { createHash as createHash6 } from "node:crypto";
|
|
1714
1662
|
import { z as z12 } from "zod";
|
|
1715
1663
|
|
|
1664
|
+
// src/agent-runtime/archive-projection.ts
|
|
1665
|
+
import { z as z10 } from "zod";
|
|
1666
|
+
var ArchiveProjectionSchema = z10.object({
|
|
1667
|
+
name: z10.string(),
|
|
1668
|
+
version: z10.int().positive(),
|
|
1669
|
+
uptoSeq: z10.int().nonnegative(),
|
|
1670
|
+
value: z10.json(),
|
|
1671
|
+
updatedAt: z10.string()
|
|
1672
|
+
}).strict();
|
|
1673
|
+
|
|
1716
1674
|
// src/agent-runtime/archive-spill.ts
|
|
1717
1675
|
import { createHash as createHash5 } from "node:crypto";
|
|
1718
1676
|
import { z as z11 } from "zod";
|
|
@@ -1780,12 +1738,21 @@ function prepareBranchSource(bytes, source) {
|
|
|
1780
1738
|
if (event.seq !== index + 1)
|
|
1781
1739
|
throw new TypeError("Branch source event sequence is incomplete");
|
|
1782
1740
|
}
|
|
1783
|
-
const projections = archive.projections.map((value) => SnapshotProjection.parse(value));
|
|
1741
|
+
const projections = archive.projections.map((value) => z12.union([SnapshotProjection, ArchiveProjectionSchema]).parse(value));
|
|
1784
1742
|
if (projections.some((value, index) => canonicalAgentJson2(value) !== canonicalAgentJson2(archive.projections[index])))
|
|
1785
1743
|
throw new TypeError("Branch archive snapshot contains an unknown opaque field");
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1744
|
+
const snapshots = projections.filter((value) => ("archiveType" in value));
|
|
1745
|
+
if (snapshots.length !== 1)
|
|
1746
|
+
throw new TypeError("Branch archive requires exactly one runtime snapshot");
|
|
1747
|
+
const names = new Set;
|
|
1748
|
+
for (const projection of projections) {
|
|
1749
|
+
if (!("name" in projection))
|
|
1750
|
+
continue;
|
|
1751
|
+
if (names.has(projection.name) || projection.uptoSeq > archive.events.length)
|
|
1752
|
+
throw new TypeError("Branch companion projection has an inconsistent checkpoint");
|
|
1753
|
+
names.add(projection.name);
|
|
1754
|
+
}
|
|
1755
|
+
const expected = snapshots[0]?.snapshot;
|
|
1789
1756
|
if (!expected || expected.conversationId !== archive.conversationId)
|
|
1790
1757
|
throw new TypeError("Branch archive has a foreign snapshot");
|
|
1791
1758
|
const allSpills = archive.spills.map((value) => ArchiveSpillSchema.parse(value));
|
|
@@ -1834,44 +1801,6 @@ function prepareBranchSource(bytes, source) {
|
|
|
1834
1801
|
}
|
|
1835
1802
|
|
|
1836
1803
|
// src/agent-runtime/store-branch.ts
|
|
1837
|
-
async function persistReplay(driver, tx, replay) {
|
|
1838
|
-
if (replay.baseline) {
|
|
1839
|
-
const baseline = replay.baseline;
|
|
1840
|
-
for (const record of baseline.runs)
|
|
1841
|
-
await driver.runs.save(tx, record);
|
|
1842
|
-
if (driver.history.restore)
|
|
1843
|
-
await driver.history.restore(tx, {
|
|
1844
|
-
conversationId: baseline.head.conversationId,
|
|
1845
|
-
messages: baseline.messages,
|
|
1846
|
-
compactedMessageIds: baseline.compacted ?? []
|
|
1847
|
-
});
|
|
1848
|
-
else
|
|
1849
|
-
for (const message of baseline.messages)
|
|
1850
|
-
await driver.history.apply(tx, { type: "admit", input: message });
|
|
1851
|
-
for (const receipt of baseline.admissions)
|
|
1852
|
-
await driver.admissions.create(tx, receipt);
|
|
1853
|
-
}
|
|
1854
|
-
for (const effect of replay.effects) {
|
|
1855
|
-
for (const record of effect.runRecords ?? [])
|
|
1856
|
-
await driver.runs.save(tx, record);
|
|
1857
|
-
if (effect.admissionReceipt)
|
|
1858
|
-
await driver.admissions.create(tx, effect.admissionReceipt);
|
|
1859
|
-
if (effect.seedReceipt)
|
|
1860
|
-
await driver.seeds.create(tx, effect.seedReceipt);
|
|
1861
|
-
for (const mutation of effect.historyMutations ?? [])
|
|
1862
|
-
await driver.history.apply(tx, mutation);
|
|
1863
|
-
}
|
|
1864
|
-
for (const run of replay.snapshot.runs) {
|
|
1865
|
-
const stored = await driver.runs.load(tx, {
|
|
1866
|
-
conversationId: run.conversationId,
|
|
1867
|
-
runId: run.id
|
|
1868
|
-
});
|
|
1869
|
-
if (!stored)
|
|
1870
|
-
throw new TypeError("Branch lost a normalized terminal record");
|
|
1871
|
-
const { ownerId: _owner, fencingToken: _fence, ...settled } = stored.run;
|
|
1872
|
-
await driver.runs.save(tx, { ...stored, run: settled });
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
1804
|
async function destinationEvents(driver, tx, conversationId) {
|
|
1876
1805
|
const events = [];
|
|
1877
1806
|
let fromSeq;
|
|
@@ -1957,7 +1886,17 @@ async function branchConversation(driver, raw) {
|
|
|
1957
1886
|
});
|
|
1958
1887
|
if (cas.outcome !== "applied")
|
|
1959
1888
|
throw new TypeError("Branch destination changed");
|
|
1960
|
-
await
|
|
1889
|
+
await persistRuntimeReplay(driver, tx, replay);
|
|
1890
|
+
for (const run of replay.snapshot.runs) {
|
|
1891
|
+
const stored = await driver.runs.load(tx, {
|
|
1892
|
+
conversationId: run.conversationId,
|
|
1893
|
+
runId: run.id
|
|
1894
|
+
});
|
|
1895
|
+
if (!stored)
|
|
1896
|
+
throw new TypeError("Branch lost a normalized terminal record");
|
|
1897
|
+
const { ownerId: _owner, fencingToken: _fence, ...settled } = stored.run;
|
|
1898
|
+
await driver.runs.save(tx, { ...stored, run: settled });
|
|
1899
|
+
}
|
|
1961
1900
|
for (const event of events) {
|
|
1962
1901
|
const appended = await driver.events.append(tx, event);
|
|
1963
1902
|
if (appended.seq !== event.seq)
|
|
@@ -2277,4 +2216,4 @@ function createAgentRuntimeStore2(driver) {
|
|
|
2277
2216
|
};
|
|
2278
2217
|
}
|
|
2279
2218
|
|
|
2280
|
-
export { BranchAgentConversationInputSchema2, AgentConversationBranchReceiptSchema2, AgentConversationBranchResultSchema2, AgentConversationPurgeInputSchema2, AgentConversationPurgeResultSchema2, AgentConversationPurgedError2, purgeAgentConversation2, AgentRuntimeHeadSchema2, AgentStoredRunSchema2, AgentAdmissionReceiptSchema2, AgentSeedReceiptSchema2, AgentHistoryMutationSchema2, ACTIVE_AGENT_RUN_STATES2, isActiveRunState, replaceMessage, appendEventIn, ArchiveSpillSchema, createAgentRuntimeStore2 };
|
|
2219
|
+
export { BranchAgentConversationInputSchema2, AgentConversationBranchReceiptSchema2, AgentConversationBranchResultSchema2, AgentConversationPurgeInputSchema2, AgentConversationPurgeResultSchema2, AgentConversationPurgedError2, purgeAgentConversation2, AgentRuntimeHeadSchema2, AgentStoredRunSchema2, AgentAdmissionReceiptSchema2, AgentSeedReceiptSchema2, AgentHistoryMutationSchema2, ACTIVE_AGENT_RUN_STATES2, isActiveRunState, replaceMessage, appendEventIn, ArchiveProjectionSchema, ArchiveSpillSchema, createAgentRuntimeStore2 };
|