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