stitchkit 0.68.7 → 0.68.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/agent-runtime/sqlite.d.ts +32 -0
- package/dist/agent-runtime/sqlite.d.ts.map +1 -0
- package/dist/agent-runtime-sqlite-bun.d.ts +11 -0
- package/dist/agent-runtime-sqlite-bun.d.ts.map +1 -0
- package/dist/agent-runtime-sqlite-bun.js +40 -0
- package/dist/agent-runtime-sqlite-node.d.ts +11 -0
- package/dist/agent-runtime-sqlite-node.d.ts.map +1 -0
- package/dist/agent-runtime-sqlite-node.js +42 -0
- package/dist/agent-runtime.js +26 -898
- package/dist/index-8vpzxg55.js +403 -0
- package/dist/index-hqza5nde.js +914 -0
- package/llms-full.txt +183 -4
- package/package.json +10 -2
|
@@ -0,0 +1,914 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AgentMessageSchema,
|
|
3
|
+
AgentRecordIdSchema,
|
|
4
|
+
AgentRecordVersionSchema,
|
|
5
|
+
AgentRunSchema,
|
|
6
|
+
AgentSnapshotSchema,
|
|
7
|
+
AgentTerminalReasonSchema,
|
|
8
|
+
AgentUsageSchema,
|
|
9
|
+
runStateForTerminalReason
|
|
10
|
+
} from "./index-ysphyxax.js";
|
|
11
|
+
|
|
12
|
+
// src/agent-runtime/store.ts
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
var AgentStoreConflictSchema = z.object({
|
|
15
|
+
outcome: z.literal("conflict"),
|
|
16
|
+
actualVersion: AgentRecordVersionSchema
|
|
17
|
+
});
|
|
18
|
+
var AgentStoreNotFoundSchema = z.object({ outcome: z.literal("not_found") });
|
|
19
|
+
var AgentStoreAppliedSchema = z.object({
|
|
20
|
+
outcome: z.literal("applied"),
|
|
21
|
+
snapshot: AgentSnapshotSchema
|
|
22
|
+
});
|
|
23
|
+
var AgentStoreDuplicateSchema = z.object({
|
|
24
|
+
outcome: z.literal("duplicate"),
|
|
25
|
+
input: AgentMessageSchema,
|
|
26
|
+
inputMessageId: AgentRecordIdSchema,
|
|
27
|
+
runId: AgentRecordIdSchema,
|
|
28
|
+
assistantMessageId: AgentRecordIdSchema,
|
|
29
|
+
run: AgentRunSchema,
|
|
30
|
+
assistant: AgentMessageSchema.optional(),
|
|
31
|
+
snapshot: AgentSnapshotSchema
|
|
32
|
+
});
|
|
33
|
+
var AgentStoreMutationResultSchema = z.discriminatedUnion("outcome", [
|
|
34
|
+
AgentStoreAppliedSchema,
|
|
35
|
+
AgentStoreDuplicateSchema,
|
|
36
|
+
AgentStoreConflictSchema,
|
|
37
|
+
AgentStoreNotFoundSchema
|
|
38
|
+
]);
|
|
39
|
+
var AgentRunViewSchema = z.object({
|
|
40
|
+
snapshotVersion: AgentRecordVersionSchema,
|
|
41
|
+
run: AgentRunSchema,
|
|
42
|
+
assistant: AgentMessageSchema.optional()
|
|
43
|
+
});
|
|
44
|
+
var AcceptInputAndAssignRunSchema = z.object({
|
|
45
|
+
idempotencyKey: z.string().min(1),
|
|
46
|
+
expectedVersion: AgentRecordVersionSchema.optional(),
|
|
47
|
+
input: AgentMessageSchema,
|
|
48
|
+
run: AgentRunSchema,
|
|
49
|
+
coalesceIntoRunId: AgentRecordIdSchema.optional()
|
|
50
|
+
});
|
|
51
|
+
var AcquireAgentRunSchema = z.object({
|
|
52
|
+
conversationId: AgentRecordIdSchema,
|
|
53
|
+
runId: AgentRecordIdSchema,
|
|
54
|
+
expectedRevision: AgentRecordVersionSchema,
|
|
55
|
+
ownerId: z.string().min(1)
|
|
56
|
+
});
|
|
57
|
+
var CheckpointRunAssistantSchema = z.object({
|
|
58
|
+
conversationId: AgentRecordIdSchema,
|
|
59
|
+
runId: AgentRecordIdSchema,
|
|
60
|
+
expectedRevision: AgentRecordVersionSchema,
|
|
61
|
+
ownerId: z.string().min(1),
|
|
62
|
+
fencingToken: AgentRecordVersionSchema.optional(),
|
|
63
|
+
assistant: AgentMessageSchema,
|
|
64
|
+
usage: AgentUsageSchema.optional()
|
|
65
|
+
});
|
|
66
|
+
var CommitRunTerminalSchema = z.object({
|
|
67
|
+
conversationId: AgentRecordIdSchema,
|
|
68
|
+
runId: AgentRecordIdSchema,
|
|
69
|
+
expectedRevision: AgentRecordVersionSchema,
|
|
70
|
+
ownerId: z.string().min(1),
|
|
71
|
+
fencingToken: AgentRecordVersionSchema.optional(),
|
|
72
|
+
assistant: AgentMessageSchema,
|
|
73
|
+
reason: AgentTerminalReasonSchema,
|
|
74
|
+
policyName: z.string().min(1).optional(),
|
|
75
|
+
usage: AgentUsageSchema.optional(),
|
|
76
|
+
absorb: z.array(z.object({
|
|
77
|
+
runId: AgentRecordIdSchema,
|
|
78
|
+
inputMessageIds: z.array(AgentRecordIdSchema).min(1)
|
|
79
|
+
})).min(1).optional()
|
|
80
|
+
});
|
|
81
|
+
var RequestRunInterruptSchema = z.object({
|
|
82
|
+
conversationId: AgentRecordIdSchema,
|
|
83
|
+
runId: AgentRecordIdSchema,
|
|
84
|
+
expectedRevision: AgentRecordVersionSchema
|
|
85
|
+
});
|
|
86
|
+
var RecoverAgentRunSchema = z.object({
|
|
87
|
+
conversationId: AgentRecordIdSchema,
|
|
88
|
+
runId: AgentRecordIdSchema,
|
|
89
|
+
expectedRevision: AgentRecordVersionSchema,
|
|
90
|
+
action: z.enum(["requeue", "abandon"]),
|
|
91
|
+
replaySafe: z.boolean().optional()
|
|
92
|
+
});
|
|
93
|
+
var ReplaceCompactedRangeSchema = z.object({
|
|
94
|
+
conversationId: AgentRecordIdSchema,
|
|
95
|
+
expectedVersion: AgentRecordVersionSchema,
|
|
96
|
+
replacedMessageIds: z.array(AgentRecordIdSchema).min(1),
|
|
97
|
+
summary: AgentMessageSchema
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// src/agent-runtime/store-driver.ts
|
|
101
|
+
import { z as z2 } from "zod";
|
|
102
|
+
|
|
103
|
+
// src/agent-runtime/terminal-status.ts
|
|
104
|
+
function isSpeakableAssistantStatus(status) {
|
|
105
|
+
return status === "completed" || status === "interrupted" || status === "committed";
|
|
106
|
+
}
|
|
107
|
+
function assistantStatus(reason) {
|
|
108
|
+
if (reason === "success" || reason === "policy_stop" || reason === "provider_stop") {
|
|
109
|
+
return "completed";
|
|
110
|
+
}
|
|
111
|
+
if (reason === "superseded")
|
|
112
|
+
return "superseded";
|
|
113
|
+
if (reason === "absorbed")
|
|
114
|
+
return "superseded";
|
|
115
|
+
if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
|
|
116
|
+
return "interrupted";
|
|
117
|
+
}
|
|
118
|
+
return "failed";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/agent-runtime/store-driver.ts
|
|
122
|
+
var AgentRuntimeHeadSchema = z2.object({
|
|
123
|
+
schemaVersion: z2.literal(1),
|
|
124
|
+
conversationId: AgentRecordIdSchema,
|
|
125
|
+
version: AgentRecordVersionSchema
|
|
126
|
+
});
|
|
127
|
+
var AgentStoredRunSchema = z2.object({
|
|
128
|
+
schemaVersion: z2.literal(1),
|
|
129
|
+
run: AgentRunSchema,
|
|
130
|
+
terminalAssistant: AgentMessageSchema.optional()
|
|
131
|
+
});
|
|
132
|
+
var AgentAdmissionReceiptSchema = z2.object({
|
|
133
|
+
schemaVersion: z2.literal(1),
|
|
134
|
+
conversationId: AgentRecordIdSchema,
|
|
135
|
+
idempotencyKey: z2.string().min(1),
|
|
136
|
+
input: AgentMessageSchema,
|
|
137
|
+
runId: AgentRecordIdSchema,
|
|
138
|
+
assistantMessageId: AgentRecordIdSchema
|
|
139
|
+
});
|
|
140
|
+
var AgentHistoryMutationSchema = z2.discriminatedUnion("type", [
|
|
141
|
+
z2.object({ type: z2.literal("admit"), input: AgentMessageSchema }),
|
|
142
|
+
z2.object({
|
|
143
|
+
type: z2.literal("upsert-assistant"),
|
|
144
|
+
message: AgentMessageSchema
|
|
145
|
+
}),
|
|
146
|
+
z2.object({
|
|
147
|
+
type: z2.literal("replace-compacted-range"),
|
|
148
|
+
replacedMessageIds: z2.array(AgentRecordIdSchema).min(1),
|
|
149
|
+
summary: AgentMessageSchema
|
|
150
|
+
})
|
|
151
|
+
]);
|
|
152
|
+
var AgentRecoverableDescriptorSchema = z2.object({
|
|
153
|
+
conversationId: AgentRecordIdSchema,
|
|
154
|
+
run: AgentRunSchema
|
|
155
|
+
});
|
|
156
|
+
var AgentRecoverablePageSchema = z2.object({
|
|
157
|
+
items: z2.array(AgentRecoverableDescriptorSchema),
|
|
158
|
+
nextCursor: z2.string().min(1).optional()
|
|
159
|
+
});
|
|
160
|
+
var AgentRecoverableScanInputSchema = z2.object({
|
|
161
|
+
cursor: z2.string().min(1).optional(),
|
|
162
|
+
limit: z2.number().int().min(1).max(1000)
|
|
163
|
+
});
|
|
164
|
+
function emptyHead(conversationId) {
|
|
165
|
+
return AgentRuntimeHeadSchema.parse({
|
|
166
|
+
schemaVersion: 1,
|
|
167
|
+
conversationId,
|
|
168
|
+
version: 0
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function historyPositions(messages) {
|
|
172
|
+
const positions = new Map;
|
|
173
|
+
messages.forEach((message, index) => {
|
|
174
|
+
if (!positions.has(message.id))
|
|
175
|
+
positions.set(message.id, index);
|
|
176
|
+
});
|
|
177
|
+
return (run) => {
|
|
178
|
+
let earliest = Number.MAX_SAFE_INTEGER;
|
|
179
|
+
for (const id of [...run.inputMessageIds, run.assistantMessageId]) {
|
|
180
|
+
const position = positions.get(id);
|
|
181
|
+
if (position !== undefined && position < earliest)
|
|
182
|
+
earliest = position;
|
|
183
|
+
}
|
|
184
|
+
return earliest;
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function orderRuns(messages, runs) {
|
|
188
|
+
const positionOf = historyPositions(messages);
|
|
189
|
+
const fallback = (left, right) => left.createdAt.localeCompare(right.createdAt) || positionOf(left) - positionOf(right) || left.id.localeCompare(right.id);
|
|
190
|
+
return [...runs].sort((left, right) => {
|
|
191
|
+
if (left.executionSequence !== undefined && right.executionSequence !== undefined && left.executionSequence !== right.executionSequence) {
|
|
192
|
+
return left.executionSequence - right.executionSequence;
|
|
193
|
+
}
|
|
194
|
+
const leftStarted = left.executionSequence !== undefined || left.state !== "queued";
|
|
195
|
+
const rightStarted = right.executionSequence !== undefined || right.state !== "queued";
|
|
196
|
+
if (leftStarted !== rightStarted)
|
|
197
|
+
return leftStarted ? -1 : 1;
|
|
198
|
+
if (!leftStarted && !rightStarted) {
|
|
199
|
+
const priority = Number(right.queuePriority !== undefined) - Number(left.queuePriority !== undefined);
|
|
200
|
+
if (priority !== 0)
|
|
201
|
+
return priority;
|
|
202
|
+
}
|
|
203
|
+
return fallback(left, right);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function orderRunMessages(messages, runs) {
|
|
207
|
+
const byId = new Map(messages.map((message) => [message.id, message]));
|
|
208
|
+
const ownedIds = new Set(runs.flatMap((run) => [...run.inputMessageIds, run.assistantMessageId]));
|
|
209
|
+
const orderedOwned = runs.flatMap((run) => [...run.inputMessageIds, run.assistantMessageId].flatMap((id) => {
|
|
210
|
+
const message = byId.get(id);
|
|
211
|
+
return message ? [message] : [];
|
|
212
|
+
}));
|
|
213
|
+
let ownedIndex = 0;
|
|
214
|
+
return messages.map((message) => {
|
|
215
|
+
if (!ownedIds.has(message.id))
|
|
216
|
+
return message;
|
|
217
|
+
const ordered = orderedOwned[ownedIndex];
|
|
218
|
+
if (!ordered)
|
|
219
|
+
throw new TypeError("Stored agent history lost a run-owned message");
|
|
220
|
+
ownedIndex += 1;
|
|
221
|
+
return ordered;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function snapshotOf(head, messages, records) {
|
|
225
|
+
validateSnapshot(head, messages, records);
|
|
226
|
+
const runs = orderRuns(messages, records.map((record) => record.run));
|
|
227
|
+
return AgentSnapshotSchema.parse({
|
|
228
|
+
schemaVersion: 1,
|
|
229
|
+
conversationId: head.conversationId,
|
|
230
|
+
version: head.version,
|
|
231
|
+
messages: orderRunMessages(messages, runs),
|
|
232
|
+
runs
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
function validateSnapshot(head, messages, records) {
|
|
236
|
+
const runIds = new Set;
|
|
237
|
+
const assistantIds = new Set;
|
|
238
|
+
const messageIds = new Set;
|
|
239
|
+
for (const record of records) {
|
|
240
|
+
const run = record.run;
|
|
241
|
+
if (run.conversationId !== head.conversationId || runIds.has(run.id) || assistantIds.has(run.assistantMessageId)) {
|
|
242
|
+
throw new TypeError("Stored agent runs contain inconsistent identities");
|
|
243
|
+
}
|
|
244
|
+
if (record.terminalAssistant && (record.terminalAssistant.id !== run.assistantMessageId || record.terminalAssistant.conversationId !== run.conversationId || record.terminalAssistant.runId !== run.id || record.terminalAssistant.role !== "assistant" || run.terminalReason === undefined)) {
|
|
245
|
+
throw new TypeError("Retained terminal assistant does not match its run");
|
|
246
|
+
}
|
|
247
|
+
runIds.add(run.id);
|
|
248
|
+
assistantIds.add(run.assistantMessageId);
|
|
249
|
+
}
|
|
250
|
+
for (const message of messages) {
|
|
251
|
+
if (message.conversationId !== head.conversationId || messageIds.has(message.id)) {
|
|
252
|
+
throw new TypeError("Stored agent history contains inconsistent message identities");
|
|
253
|
+
}
|
|
254
|
+
messageIds.add(message.id);
|
|
255
|
+
if (assistantIds.has(message.id) && message.runId === undefined) {
|
|
256
|
+
throw new TypeError("Stored history occupies a reserved assistant identity");
|
|
257
|
+
}
|
|
258
|
+
if (message.runId !== undefined) {
|
|
259
|
+
const run = records.find((candidate) => candidate.run.id === message.runId)?.run;
|
|
260
|
+
if (!run || message.role !== "assistant" || run.assistantMessageId !== message.id) {
|
|
261
|
+
throw new TypeError("Stored assistant history does not match its reserved run identity");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
var RecoverableCursorSchema = z2.tuple([AgentRecordIdSchema, AgentRecordIdSchema]);
|
|
267
|
+
function recoverableCursor(input) {
|
|
268
|
+
return JSON.stringify([input.conversationId, input.run.id]);
|
|
269
|
+
}
|
|
270
|
+
function parseRecoverableCursor(cursor) {
|
|
271
|
+
return RecoverableCursorSchema.parse(JSON.parse(cursor));
|
|
272
|
+
}
|
|
273
|
+
function replaceRun(runs, next) {
|
|
274
|
+
return runs.map((run) => run.id === next.id ? next : run);
|
|
275
|
+
}
|
|
276
|
+
function replaceMessage(messages, next) {
|
|
277
|
+
return messages.some((message) => message.id === next.id) ? messages.map((message) => message.id === next.id ? next : message) : [...messages, next];
|
|
278
|
+
}
|
|
279
|
+
var ACTIVE_AGENT_RUN_STATES = [
|
|
280
|
+
"queued",
|
|
281
|
+
"running",
|
|
282
|
+
"interrupt_requested"
|
|
283
|
+
];
|
|
284
|
+
function isActiveRunState(state) {
|
|
285
|
+
return ACTIVE_AGENT_RUN_STATES.includes(state);
|
|
286
|
+
}
|
|
287
|
+
function conflict(actualVersion) {
|
|
288
|
+
return { outcome: "conflict", actualVersion };
|
|
289
|
+
}
|
|
290
|
+
function applied(current, input, effects) {
|
|
291
|
+
const runs = input.runs ?? current.runs;
|
|
292
|
+
const messages = input.messages ?? current.messages;
|
|
293
|
+
return {
|
|
294
|
+
outcome: "applied",
|
|
295
|
+
snapshot: AgentSnapshotSchema.parse({
|
|
296
|
+
...current,
|
|
297
|
+
version: current.version + 1,
|
|
298
|
+
runs,
|
|
299
|
+
messages: orderRunMessages(messages, runs)
|
|
300
|
+
}),
|
|
301
|
+
...effects?.runRecords?.length && { runRecords: effects.runRecords },
|
|
302
|
+
...effects?.admissionReceipt && { admissionReceipt: effects.admissionReceipt },
|
|
303
|
+
...effects?.historyMutations?.length && { historyMutations: effects.historyMutations }
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function reduceStore(current, operation) {
|
|
307
|
+
if (operation.type === "accept") {
|
|
308
|
+
const input = operation.input;
|
|
309
|
+
if (input.expectedVersion !== undefined && input.expectedVersion !== current.version) {
|
|
310
|
+
return conflict(current.version);
|
|
311
|
+
}
|
|
312
|
+
const coalescedRun = input.coalesceIntoRunId ? current.runs.find((candidate) => candidate.id === input.coalesceIntoRunId) : undefined;
|
|
313
|
+
if (input.coalesceIntoRunId !== undefined && (!coalescedRun || coalescedRun.conversationId !== input.input.conversationId || coalescedRun.state !== "queued" || coalescedRun.ownerId !== undefined || coalescedRun.terminalReason !== undefined)) {
|
|
314
|
+
return coalescedRun ? conflict(coalescedRun.revision) : { outcome: "not_found" };
|
|
315
|
+
}
|
|
316
|
+
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.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))) {
|
|
317
|
+
throw new TypeError("Input and queued run do not form one valid assignment");
|
|
318
|
+
}
|
|
319
|
+
const assignedRun = coalescedRun ? AgentRunSchema.parse({
|
|
320
|
+
...coalescedRun,
|
|
321
|
+
inputMessageIds: [...coalescedRun.inputMessageIds, input.input.id],
|
|
322
|
+
revision: coalescedRun.revision + 1,
|
|
323
|
+
updatedAt: new Date().toISOString()
|
|
324
|
+
}) : input.run;
|
|
325
|
+
const admissionReceipt = AgentAdmissionReceiptSchema.parse({
|
|
326
|
+
schemaVersion: 1,
|
|
327
|
+
conversationId: input.input.conversationId,
|
|
328
|
+
idempotencyKey: input.idempotencyKey,
|
|
329
|
+
input: input.input,
|
|
330
|
+
runId: assignedRun.id,
|
|
331
|
+
assistantMessageId: assignedRun.assistantMessageId
|
|
332
|
+
});
|
|
333
|
+
return applied(current, {
|
|
334
|
+
messages: [...current.messages, input.input],
|
|
335
|
+
runs: coalescedRun ? replaceRun(current.runs, assignedRun) : [...current.runs, assignedRun]
|
|
336
|
+
}, {
|
|
337
|
+
runRecords: [AgentStoredRunSchema.parse({ schemaVersion: 1, run: assignedRun })],
|
|
338
|
+
admissionReceipt,
|
|
339
|
+
historyMutations: [{ type: "admit", input: input.input }]
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
const conversationId = operation.input.conversationId;
|
|
343
|
+
const run = operation.type === "compact" ? undefined : current.runs.find((candidate) => candidate.id === operation.input.runId);
|
|
344
|
+
if (operation.type !== "compact" && !run)
|
|
345
|
+
return { outcome: "not_found" };
|
|
346
|
+
if (operation.type === "acquire" && run) {
|
|
347
|
+
const runPosition = current.runs.findIndex((candidate) => candidate.id === run.id);
|
|
348
|
+
const acquisitionBlocked = current.runs.some((candidate, index) => candidate.id !== run.id && (candidate.state === "running" || candidate.state === "interrupt_requested" || index < runPosition && candidate.state === "queued"));
|
|
349
|
+
if (run.revision !== operation.input.expectedRevision || run.state !== "queued" || acquisitionBlocked) {
|
|
350
|
+
return conflict(run.revision);
|
|
351
|
+
}
|
|
352
|
+
const next = AgentRunSchema.parse({
|
|
353
|
+
...run,
|
|
354
|
+
state: "running",
|
|
355
|
+
executionSequence: run.executionSequence ?? current.version + 1,
|
|
356
|
+
ownerId: operation.input.ownerId,
|
|
357
|
+
fencingToken: (run.fencingToken ?? 0) + 1,
|
|
358
|
+
revision: run.revision + 1,
|
|
359
|
+
updatedAt: new Date().toISOString()
|
|
360
|
+
});
|
|
361
|
+
return applied(current, { runs: replaceRun(current.runs, next) }, {
|
|
362
|
+
runRecords: [AgentStoredRunSchema.parse({ schemaVersion: 1, run: next })]
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
if (operation.type === "checkpoint" && run) {
|
|
366
|
+
const input = operation.input;
|
|
367
|
+
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") {
|
|
368
|
+
return conflict(run.revision);
|
|
369
|
+
}
|
|
370
|
+
const next = AgentRunSchema.parse({
|
|
371
|
+
...run,
|
|
372
|
+
...input.usage && { usage: input.usage },
|
|
373
|
+
revision: run.revision + 1,
|
|
374
|
+
updatedAt: new Date().toISOString()
|
|
375
|
+
});
|
|
376
|
+
return applied(current, {
|
|
377
|
+
runs: replaceRun(current.runs, next),
|
|
378
|
+
messages: replaceMessage(current.messages, input.assistant)
|
|
379
|
+
}, {
|
|
380
|
+
runRecords: [AgentStoredRunSchema.parse({ schemaVersion: 1, run: next })],
|
|
381
|
+
historyMutations: [{ type: "upsert-assistant", message: input.assistant }]
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
if (operation.type === "interrupt" && run) {
|
|
385
|
+
if (run.revision !== operation.input.expectedRevision || run.state !== "running") {
|
|
386
|
+
return conflict(run.revision);
|
|
387
|
+
}
|
|
388
|
+
const next = AgentRunSchema.parse({
|
|
389
|
+
...run,
|
|
390
|
+
state: "interrupt_requested",
|
|
391
|
+
revision: run.revision + 1,
|
|
392
|
+
updatedAt: new Date().toISOString()
|
|
393
|
+
});
|
|
394
|
+
return applied(current, { runs: replaceRun(current.runs, next) }, {
|
|
395
|
+
runRecords: [AgentStoredRunSchema.parse({ schemaVersion: 1, run: next })]
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
if (operation.type === "recover" && run) {
|
|
399
|
+
const input = operation.input;
|
|
400
|
+
if (run.revision !== input.expectedRevision || !isActiveRunState(run.state)) {
|
|
401
|
+
return conflict(run.revision);
|
|
402
|
+
}
|
|
403
|
+
if (input.action === "requeue" && run.state !== "queued" && input.replaySafe !== true) {
|
|
404
|
+
throw new TypeError("Recovering an acquired run requires explicit replaySafe evidence");
|
|
405
|
+
}
|
|
406
|
+
const { ownerId: _released, ...carried } = run;
|
|
407
|
+
const next = AgentRunSchema.parse({
|
|
408
|
+
...carried,
|
|
409
|
+
state: input.action === "requeue" ? "queued" : "abandoned",
|
|
410
|
+
revision: run.revision + 1,
|
|
411
|
+
...input.action === "abandon" && { terminalReason: "abandoned" },
|
|
412
|
+
updatedAt: new Date().toISOString()
|
|
413
|
+
});
|
|
414
|
+
if (input.action === "abandon") {
|
|
415
|
+
const existingAssistant = current.messages.find((message) => message.id === run.assistantMessageId);
|
|
416
|
+
const assistant = AgentMessageSchema.parse({
|
|
417
|
+
...existingAssistant ?? {
|
|
418
|
+
schemaVersion: 1,
|
|
419
|
+
id: run.assistantMessageId,
|
|
420
|
+
conversationId: run.conversationId,
|
|
421
|
+
runId: run.id,
|
|
422
|
+
role: "assistant",
|
|
423
|
+
parts: [],
|
|
424
|
+
createdAt: run.createdAt
|
|
425
|
+
},
|
|
426
|
+
status: "failed",
|
|
427
|
+
updatedAt: new Date().toISOString()
|
|
428
|
+
});
|
|
429
|
+
return applied(current, {
|
|
430
|
+
runs: replaceRun(current.runs, next),
|
|
431
|
+
messages: replaceMessage(current.messages, assistant)
|
|
432
|
+
}, {
|
|
433
|
+
runRecords: [
|
|
434
|
+
AgentStoredRunSchema.parse({
|
|
435
|
+
schemaVersion: 1,
|
|
436
|
+
run: next,
|
|
437
|
+
terminalAssistant: assistant
|
|
438
|
+
})
|
|
439
|
+
],
|
|
440
|
+
historyMutations: [{ type: "upsert-assistant", message: assistant }]
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
return applied(current, { runs: replaceRun(current.runs, next) }, {
|
|
444
|
+
runRecords: [AgentStoredRunSchema.parse({ schemaVersion: 1, run: next })]
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
if (operation.type === "terminal" && run) {
|
|
448
|
+
const input = operation.input;
|
|
449
|
+
if (input.reason === "absorbed") {
|
|
450
|
+
throw new TypeError("A run is absorbed by another run, never terminalized as absorbed");
|
|
451
|
+
}
|
|
452
|
+
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)) {
|
|
453
|
+
return conflict(run.revision);
|
|
454
|
+
}
|
|
455
|
+
if (input.absorb && runStateForTerminalReason(input.reason) !== "completed") {
|
|
456
|
+
throw new TypeError("Only a completing run may absorb a queued successor");
|
|
457
|
+
}
|
|
458
|
+
const named = new Set((input.absorb ?? []).map((entry) => entry.runId));
|
|
459
|
+
if (named.size !== (input.absorb ?? []).length || named.has(run.id)) {
|
|
460
|
+
throw new TypeError("An absorption names each successor once, and never the absorbing run");
|
|
461
|
+
}
|
|
462
|
+
const absorbable = (input.absorb ?? []).filter((entry) => {
|
|
463
|
+
const candidate = current.runs.find((item) => item.id === entry.runId);
|
|
464
|
+
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]);
|
|
465
|
+
});
|
|
466
|
+
const absorbedIds = new Set(absorbable.flatMap((entry) => entry.inputMessageIds));
|
|
467
|
+
const next = AgentRunSchema.parse({
|
|
468
|
+
...run,
|
|
469
|
+
state: runStateForTerminalReason(input.reason),
|
|
470
|
+
terminalReason: input.reason,
|
|
471
|
+
...input.policyName && { terminalPolicyName: input.policyName },
|
|
472
|
+
...input.usage && { usage: input.usage },
|
|
473
|
+
...absorbedIds.size > 0 && {
|
|
474
|
+
inputMessageIds: [
|
|
475
|
+
...run.inputMessageIds,
|
|
476
|
+
...[...absorbedIds].filter((id) => !run.inputMessageIds.includes(id))
|
|
477
|
+
]
|
|
478
|
+
},
|
|
479
|
+
revision: run.revision + 1,
|
|
480
|
+
updatedAt: new Date().toISOString()
|
|
481
|
+
});
|
|
482
|
+
const absorbedRuns = absorbable.map((entry) => {
|
|
483
|
+
const candidate = current.runs.find((item) => item.id === entry.runId);
|
|
484
|
+
if (!candidate)
|
|
485
|
+
throw new TypeError("Absorbed run disappeared inside the reducer");
|
|
486
|
+
return AgentRunSchema.parse({
|
|
487
|
+
...candidate,
|
|
488
|
+
state: runStateForTerminalReason("absorbed"),
|
|
489
|
+
terminalReason: "absorbed",
|
|
490
|
+
absorbedIntoRunId: next.id,
|
|
491
|
+
revision: candidate.revision + 1,
|
|
492
|
+
updatedAt: new Date().toISOString()
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
const runs = absorbedRuns.reduce((accumulated, absorbed) => replaceRun(accumulated, absorbed), replaceRun(current.runs, next));
|
|
496
|
+
return applied(current, {
|
|
497
|
+
runs,
|
|
498
|
+
messages: replaceMessage(current.messages, input.assistant)
|
|
499
|
+
}, {
|
|
500
|
+
runRecords: [
|
|
501
|
+
AgentStoredRunSchema.parse({
|
|
502
|
+
schemaVersion: 1,
|
|
503
|
+
run: next,
|
|
504
|
+
terminalAssistant: input.assistant
|
|
505
|
+
}),
|
|
506
|
+
...absorbedRuns.map((absorbed) => AgentStoredRunSchema.parse({ schemaVersion: 1, run: absorbed }))
|
|
507
|
+
],
|
|
508
|
+
historyMutations: [{ type: "upsert-assistant", message: input.assistant }]
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
if (operation.type === "compact") {
|
|
512
|
+
const input = operation.input;
|
|
513
|
+
if (current.conversationId !== conversationId)
|
|
514
|
+
return { outcome: "not_found" };
|
|
515
|
+
if (current.version !== input.expectedVersion)
|
|
516
|
+
return conflict(current.version);
|
|
517
|
+
const replaced = new Set(input.replacedMessageIds);
|
|
518
|
+
if (!input.replacedMessageIds.every((id) => current.messages.some((m) => m.id === id))) {
|
|
519
|
+
return { outcome: "not_found" };
|
|
520
|
+
}
|
|
521
|
+
const liveAssistant = current.runs.find((candidate) => isActiveRunState(candidate.state) && replaced.has(candidate.assistantMessageId));
|
|
522
|
+
if (liveAssistant) {
|
|
523
|
+
throw new TypeError(`Compaction may not replace the assistant message of run ${liveAssistant.id}, which has not finished`);
|
|
524
|
+
}
|
|
525
|
+
const positions = current.messages.map((message, index) => replaced.has(message.id) ? index : undefined).filter((index) => index !== undefined);
|
|
526
|
+
const first = positions[0];
|
|
527
|
+
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)) {
|
|
528
|
+
throw new TypeError("Compaction replacement must be one valid contiguous history range");
|
|
529
|
+
}
|
|
530
|
+
const messages = [
|
|
531
|
+
...current.messages.slice(0, first),
|
|
532
|
+
input.summary,
|
|
533
|
+
...current.messages.slice(first + positions.length)
|
|
534
|
+
];
|
|
535
|
+
return applied(current, { messages }, {
|
|
536
|
+
historyMutations: [
|
|
537
|
+
{
|
|
538
|
+
type: "replace-compacted-range",
|
|
539
|
+
replacedMessageIds: input.replacedMessageIds,
|
|
540
|
+
summary: input.summary
|
|
541
|
+
}
|
|
542
|
+
]
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return { outcome: "not_found" };
|
|
546
|
+
}
|
|
547
|
+
function operationConversationId(operation) {
|
|
548
|
+
return operation.type === "accept" ? operation.input.input.conversationId : operation.input.conversationId;
|
|
549
|
+
}
|
|
550
|
+
function mergeRunRecords(...groups) {
|
|
551
|
+
const records = new Map;
|
|
552
|
+
for (const group of groups) {
|
|
553
|
+
for (const rawRecord of group) {
|
|
554
|
+
const record = AgentStoredRunSchema.parse(rawRecord);
|
|
555
|
+
const previous = records.get(record.run.id);
|
|
556
|
+
if (previous && previous.run.assistantMessageId !== record.run.assistantMessageId) {
|
|
557
|
+
throw new TypeError("Stored agent run identity changed across normalized records");
|
|
558
|
+
}
|
|
559
|
+
records.set(record.run.id, record);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return [...records.values()];
|
|
563
|
+
}
|
|
564
|
+
function referencedRunIds(messages) {
|
|
565
|
+
return [...new Set(messages.flatMap((message) => message.runId ? [message.runId] : []))];
|
|
566
|
+
}
|
|
567
|
+
function validateAdmissionReceipt(receipt, record, conversationId) {
|
|
568
|
+
const input = receipt.input;
|
|
569
|
+
const run = record.run;
|
|
570
|
+
if (receipt.conversationId !== conversationId || input.conversationId !== conversationId || input.role !== "user" || input.status !== "committed" || input.runId !== undefined || receipt.runId !== run.id || receipt.assistantMessageId !== run.assistantMessageId || !run.inputMessageIds.includes(input.id)) {
|
|
571
|
+
throw new TypeError("Admission receipt does not match its canonical run assignment");
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function createAgentRuntimeStore(driver) {
|
|
575
|
+
const loadSnapshot = (conversationId) => driver.transaction(async (transaction) => {
|
|
576
|
+
const [stored, messages, activeRecords] = await Promise.all([
|
|
577
|
+
driver.head.load(transaction, conversationId),
|
|
578
|
+
driver.history.load(transaction, conversationId),
|
|
579
|
+
driver.runs.listActive(transaction, conversationId)
|
|
580
|
+
]);
|
|
581
|
+
const head = AgentRuntimeHeadSchema.parse(stored ?? emptyHead(conversationId));
|
|
582
|
+
const referencedRecords = await driver.runs.loadMany(transaction, {
|
|
583
|
+
conversationId,
|
|
584
|
+
runIds: referencedRunIds(messages)
|
|
585
|
+
});
|
|
586
|
+
return snapshotOf(head, messages, mergeRunRecords(activeRecords, referencedRecords));
|
|
587
|
+
});
|
|
588
|
+
const loadRun = (input) => driver.transaction(async (transaction) => {
|
|
589
|
+
const [stored, record] = await Promise.all([
|
|
590
|
+
driver.head.load(transaction, input.conversationId),
|
|
591
|
+
driver.runs.load(transaction, input)
|
|
592
|
+
]);
|
|
593
|
+
if (!record)
|
|
594
|
+
return;
|
|
595
|
+
const parsed = AgentStoredRunSchema.parse(record);
|
|
596
|
+
if (parsed.run.conversationId !== input.conversationId || parsed.run.id !== input.runId) {
|
|
597
|
+
throw new TypeError("Stored run does not match the identity it was loaded by");
|
|
598
|
+
}
|
|
599
|
+
const head = AgentRuntimeHeadSchema.parse(stored ?? emptyHead(input.conversationId));
|
|
600
|
+
return AgentRunViewSchema.parse({
|
|
601
|
+
snapshotVersion: head.version,
|
|
602
|
+
run: parsed.run,
|
|
603
|
+
...parsed.terminalAssistant && { assistant: parsed.terminalAssistant }
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
const listActiveRuns = (conversationId) => driver.transaction(async (transaction) => {
|
|
607
|
+
const [records, messages] = await Promise.all([
|
|
608
|
+
driver.runs.listActive(transaction, conversationId),
|
|
609
|
+
driver.history.load(transaction, conversationId)
|
|
610
|
+
]);
|
|
611
|
+
const runs = records.map((record) => AgentStoredRunSchema.parse(record).run);
|
|
612
|
+
for (const run of runs) {
|
|
613
|
+
if (run.conversationId !== conversationId) {
|
|
614
|
+
throw new TypeError("Active run belongs to another conversation");
|
|
615
|
+
}
|
|
616
|
+
if (!isActiveRunState(run.state)) {
|
|
617
|
+
throw new TypeError("Active run listing returned a terminal run");
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return orderRuns(messages, runs);
|
|
621
|
+
});
|
|
622
|
+
const mutate = (operation) => driver.transaction(async (transaction) => {
|
|
623
|
+
const conversationId = operationConversationId(operation);
|
|
624
|
+
const operationRunId = operation.type === "accept" ? operation.input.coalesceIntoRunId : operation.type === "compact" ? undefined : operation.input.runId;
|
|
625
|
+
const [stored, messages, activeRecords, operationRecord, duplicateReceipt] = await Promise.all([
|
|
626
|
+
driver.head.load(transaction, conversationId),
|
|
627
|
+
driver.history.load(transaction, conversationId),
|
|
628
|
+
driver.runs.listActive(transaction, conversationId),
|
|
629
|
+
operationRunId ? driver.runs.load(transaction, { conversationId, runId: operationRunId }) : undefined,
|
|
630
|
+
operation.type === "accept" ? driver.admissions.load(transaction, {
|
|
631
|
+
conversationId,
|
|
632
|
+
idempotencyKey: operation.input.idempotencyKey
|
|
633
|
+
}) : undefined
|
|
634
|
+
]);
|
|
635
|
+
const head = AgentRuntimeHeadSchema.parse(stored ?? emptyHead(conversationId));
|
|
636
|
+
const referencedRecords = await driver.runs.loadMany(transaction, {
|
|
637
|
+
conversationId,
|
|
638
|
+
runIds: referencedRunIds(messages)
|
|
639
|
+
});
|
|
640
|
+
const records = mergeRunRecords(activeRecords, referencedRecords, operationRecord ? [operationRecord] : []);
|
|
641
|
+
const current = snapshotOf(head, messages, records);
|
|
642
|
+
if (duplicateReceipt) {
|
|
643
|
+
const duplicateRecord = await driver.runs.load(transaction, {
|
|
644
|
+
conversationId,
|
|
645
|
+
runId: duplicateReceipt.runId
|
|
646
|
+
});
|
|
647
|
+
if (!duplicateRecord) {
|
|
648
|
+
throw new TypeError("Admission receipt points to a missing canonical run");
|
|
649
|
+
}
|
|
650
|
+
validateAdmissionReceipt(duplicateReceipt, duplicateRecord, conversationId);
|
|
651
|
+
const answering = duplicateRecord.run.absorbedIntoRunId ? await driver.runs.load(transaction, {
|
|
652
|
+
conversationId,
|
|
653
|
+
runId: duplicateRecord.run.absorbedIntoRunId
|
|
654
|
+
}) : undefined;
|
|
655
|
+
if (duplicateRecord.run.absorbedIntoRunId && !answering) {
|
|
656
|
+
throw new TypeError("Absorbed run points to a missing absorbing run");
|
|
657
|
+
}
|
|
658
|
+
const canonical = answering ?? duplicateRecord;
|
|
659
|
+
return {
|
|
660
|
+
outcome: "duplicate",
|
|
661
|
+
input: duplicateReceipt.input,
|
|
662
|
+
inputMessageId: duplicateReceipt.input.id,
|
|
663
|
+
runId: canonical.run.id,
|
|
664
|
+
assistantMessageId: canonical.run.assistantMessageId,
|
|
665
|
+
run: canonical.run,
|
|
666
|
+
...canonical.terminalAssistant && {
|
|
667
|
+
assistant: canonical.terminalAssistant
|
|
668
|
+
},
|
|
669
|
+
snapshot: snapshotOf(head, messages, mergeRunRecords(records, [duplicateRecord], answering ? [answering] : []))
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
if (operation.type === "accept") {
|
|
673
|
+
const inputCollision = await driver.admissions.loadByInputMessageId(transaction, {
|
|
674
|
+
conversationId,
|
|
675
|
+
inputMessageId: operation.input.input.id
|
|
676
|
+
});
|
|
677
|
+
if (inputCollision) {
|
|
678
|
+
throw new TypeError("Input message identity is already assigned to an admission");
|
|
679
|
+
}
|
|
680
|
+
if (!operation.input.coalesceIntoRunId) {
|
|
681
|
+
const [runCollision, assistantCollision] = await Promise.all([
|
|
682
|
+
driver.runs.load(transaction, {
|
|
683
|
+
conversationId,
|
|
684
|
+
runId: operation.input.run.id
|
|
685
|
+
}),
|
|
686
|
+
driver.runs.loadByAssistantMessageId(transaction, {
|
|
687
|
+
conversationId,
|
|
688
|
+
assistantMessageId: operation.input.run.assistantMessageId
|
|
689
|
+
})
|
|
690
|
+
]);
|
|
691
|
+
if (runCollision || assistantCollision) {
|
|
692
|
+
throw new TypeError("Queued run identities are already reserved");
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
const reduced = reduceStore(current, operation);
|
|
697
|
+
if (reduced.outcome !== "applied")
|
|
698
|
+
return reduced;
|
|
699
|
+
const nextHead = AgentRuntimeHeadSchema.parse({
|
|
700
|
+
schemaVersion: 1,
|
|
701
|
+
conversationId,
|
|
702
|
+
version: reduced.snapshot.version
|
|
703
|
+
});
|
|
704
|
+
const outcome = await driver.head.compareAndSwap(transaction, {
|
|
705
|
+
conversationId,
|
|
706
|
+
expectedVersion: current.version,
|
|
707
|
+
next: nextHead
|
|
708
|
+
});
|
|
709
|
+
if (outcome.outcome === "conflict")
|
|
710
|
+
return conflict(outcome.actualVersion);
|
|
711
|
+
for (const record of reduced.runRecords ?? []) {
|
|
712
|
+
await driver.runs.save(transaction, record);
|
|
713
|
+
}
|
|
714
|
+
if (reduced.admissionReceipt) {
|
|
715
|
+
await driver.admissions.create(transaction, reduced.admissionReceipt);
|
|
716
|
+
}
|
|
717
|
+
for (const mutation of reduced.historyMutations ?? []) {
|
|
718
|
+
await driver.history.apply(transaction, mutation);
|
|
719
|
+
}
|
|
720
|
+
return { outcome: "applied", snapshot: reduced.snapshot };
|
|
721
|
+
});
|
|
722
|
+
return {
|
|
723
|
+
loadSnapshot,
|
|
724
|
+
loadRun,
|
|
725
|
+
listActiveRuns,
|
|
726
|
+
acceptInputAndAssignRun: (input) => mutate({
|
|
727
|
+
type: "accept",
|
|
728
|
+
input: AcceptInputAndAssignRunSchema.parse(input)
|
|
729
|
+
}),
|
|
730
|
+
acquireRun: (input) => mutate({ type: "acquire", input: AcquireAgentRunSchema.parse(input) }),
|
|
731
|
+
checkpointRunAssistant: (input) => mutate({
|
|
732
|
+
type: "checkpoint",
|
|
733
|
+
input: CheckpointRunAssistantSchema.parse(input)
|
|
734
|
+
}),
|
|
735
|
+
requestRunInterrupt: (input) => mutate({
|
|
736
|
+
type: "interrupt",
|
|
737
|
+
input: RequestRunInterruptSchema.parse(input)
|
|
738
|
+
}),
|
|
739
|
+
recoverRun: (input) => mutate({ type: "recover", input: RecoverAgentRunSchema.parse(input) }),
|
|
740
|
+
commitRunTerminal: (input) => mutate({ type: "terminal", input: CommitRunTerminalSchema.parse(input) }),
|
|
741
|
+
replaceCompactedRange: (input) => mutate({
|
|
742
|
+
type: "compact",
|
|
743
|
+
input: ReplaceCompactedRangeSchema.parse(input)
|
|
744
|
+
}),
|
|
745
|
+
async scanRecoverable(input) {
|
|
746
|
+
const parsed = AgentRecoverableScanInputSchema.parse(input);
|
|
747
|
+
return AgentRecoverablePageSchema.parse(await driver.scanRecoverable(parsed));
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
function cloneHeadMap(source) {
|
|
752
|
+
return new Map([...source].map(([key, value]) => [
|
|
753
|
+
key,
|
|
754
|
+
AgentRuntimeHeadSchema.parse(structuredClone(value))
|
|
755
|
+
]));
|
|
756
|
+
}
|
|
757
|
+
function cloneNestedMap(source, clone) {
|
|
758
|
+
return new Map([...source].map(([outerKey, values]) => [
|
|
759
|
+
outerKey,
|
|
760
|
+
new Map([...values].map(([innerKey, value]) => [innerKey, clone(value)]))
|
|
761
|
+
]));
|
|
762
|
+
}
|
|
763
|
+
function cloneHistoryMap(source) {
|
|
764
|
+
return new Map([...source].map(([key, value]) => [
|
|
765
|
+
key,
|
|
766
|
+
value.map((message) => AgentMessageSchema.parse(structuredClone(message)))
|
|
767
|
+
]));
|
|
768
|
+
}
|
|
769
|
+
function createMemoryAgentRuntimeStore() {
|
|
770
|
+
let heads = new Map;
|
|
771
|
+
let runs = new Map;
|
|
772
|
+
let admissions = new Map;
|
|
773
|
+
let histories = new Map;
|
|
774
|
+
let transactionTail = Promise.resolve();
|
|
775
|
+
const driver = {
|
|
776
|
+
async transaction(work) {
|
|
777
|
+
const previous = transactionTail;
|
|
778
|
+
const release = Promise.withResolvers();
|
|
779
|
+
transactionTail = previous.catch(() => {
|
|
780
|
+
return;
|
|
781
|
+
}).then(() => release.promise);
|
|
782
|
+
await previous.catch(() => {
|
|
783
|
+
return;
|
|
784
|
+
});
|
|
785
|
+
const transaction = {
|
|
786
|
+
heads: cloneHeadMap(heads),
|
|
787
|
+
runs: cloneNestedMap(runs, (record) => AgentStoredRunSchema.parse(structuredClone(record))),
|
|
788
|
+
admissions: cloneNestedMap(admissions, (receipt) => AgentAdmissionReceiptSchema.parse(structuredClone(receipt))),
|
|
789
|
+
histories: cloneHistoryMap(histories)
|
|
790
|
+
};
|
|
791
|
+
try {
|
|
792
|
+
const result = await work(transaction);
|
|
793
|
+
heads = transaction.heads;
|
|
794
|
+
runs = transaction.runs;
|
|
795
|
+
admissions = transaction.admissions;
|
|
796
|
+
histories = transaction.histories;
|
|
797
|
+
return result;
|
|
798
|
+
} finally {
|
|
799
|
+
release.resolve();
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
head: {
|
|
803
|
+
async load(transaction, conversationId) {
|
|
804
|
+
const head = transaction.heads.get(conversationId);
|
|
805
|
+
return head ? AgentRuntimeHeadSchema.parse(structuredClone(head)) : undefined;
|
|
806
|
+
},
|
|
807
|
+
async compareAndSwap(transaction, input) {
|
|
808
|
+
const current = transaction.heads.get(input.conversationId);
|
|
809
|
+
const actualVersion = current?.version ?? 0;
|
|
810
|
+
if (actualVersion !== input.expectedVersion) {
|
|
811
|
+
return { outcome: "conflict", actualVersion };
|
|
812
|
+
}
|
|
813
|
+
transaction.heads.set(input.conversationId, AgentRuntimeHeadSchema.parse(structuredClone(input.next)));
|
|
814
|
+
return { outcome: "applied" };
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
runs: {
|
|
818
|
+
async load(transaction, input) {
|
|
819
|
+
const record = transaction.runs.get(input.conversationId)?.get(input.runId);
|
|
820
|
+
return record ? AgentStoredRunSchema.parse(structuredClone(record)) : undefined;
|
|
821
|
+
},
|
|
822
|
+
async loadByAssistantMessageId(transaction, input) {
|
|
823
|
+
const record = [...transaction.runs.get(input.conversationId)?.values() ?? []].find((candidate) => candidate.run.assistantMessageId === input.assistantMessageId);
|
|
824
|
+
return record ? AgentStoredRunSchema.parse(structuredClone(record)) : undefined;
|
|
825
|
+
},
|
|
826
|
+
async loadMany(transaction, input) {
|
|
827
|
+
const records = transaction.runs.get(input.conversationId);
|
|
828
|
+
return input.runIds.flatMap((runId) => {
|
|
829
|
+
const record = records?.get(runId);
|
|
830
|
+
return record ? [AgentStoredRunSchema.parse(structuredClone(record))] : [];
|
|
831
|
+
});
|
|
832
|
+
},
|
|
833
|
+
async listActive(transaction, conversationId) {
|
|
834
|
+
return [...transaction.runs.get(conversationId)?.values() ?? []].filter((record) => isActiveRunState(record.run.state)).map((record) => AgentStoredRunSchema.parse(structuredClone(record)));
|
|
835
|
+
},
|
|
836
|
+
async save(transaction, rawRecord) {
|
|
837
|
+
const record = AgentStoredRunSchema.parse(structuredClone(rawRecord));
|
|
838
|
+
const conversationRuns = transaction.runs.get(record.run.conversationId) ?? new Map;
|
|
839
|
+
const collision = [...conversationRuns.values()].find((candidate) => candidate.run.id !== record.run.id && candidate.run.assistantMessageId === record.run.assistantMessageId);
|
|
840
|
+
if (collision)
|
|
841
|
+
throw new TypeError("Assistant message identity is already reserved");
|
|
842
|
+
conversationRuns.set(record.run.id, record);
|
|
843
|
+
transaction.runs.set(record.run.conversationId, conversationRuns);
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
admissions: {
|
|
847
|
+
async load(transaction, input) {
|
|
848
|
+
const receipt = transaction.admissions.get(input.conversationId)?.get(input.idempotencyKey);
|
|
849
|
+
return receipt ? AgentAdmissionReceiptSchema.parse(structuredClone(receipt)) : undefined;
|
|
850
|
+
},
|
|
851
|
+
async loadByInputMessageId(transaction, input) {
|
|
852
|
+
const receipt = [
|
|
853
|
+
...transaction.admissions.get(input.conversationId)?.values() ?? []
|
|
854
|
+
].find((candidate) => candidate.input.id === input.inputMessageId);
|
|
855
|
+
return receipt ? AgentAdmissionReceiptSchema.parse(structuredClone(receipt)) : undefined;
|
|
856
|
+
},
|
|
857
|
+
async create(transaction, rawReceipt) {
|
|
858
|
+
const receipt = AgentAdmissionReceiptSchema.parse(structuredClone(rawReceipt));
|
|
859
|
+
const conversationAdmissions = transaction.admissions.get(receipt.conversationId) ?? new Map;
|
|
860
|
+
if (conversationAdmissions.has(receipt.idempotencyKey) || [...conversationAdmissions.values()].some((candidate) => candidate.input.id === receipt.input.id)) {
|
|
861
|
+
throw new TypeError("Admission identity is already reserved");
|
|
862
|
+
}
|
|
863
|
+
conversationAdmissions.set(receipt.idempotencyKey, receipt);
|
|
864
|
+
transaction.admissions.set(receipt.conversationId, conversationAdmissions);
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
history: {
|
|
868
|
+
async load(transaction, conversationId) {
|
|
869
|
+
return (transaction.histories.get(conversationId) ?? []).map((message) => AgentMessageSchema.parse(structuredClone(message)));
|
|
870
|
+
},
|
|
871
|
+
async apply(transaction, rawMutation) {
|
|
872
|
+
const mutation = AgentHistoryMutationSchema.parse(rawMutation);
|
|
873
|
+
const conversationId = mutation.type === "admit" ? mutation.input.conversationId : mutation.type === "upsert-assistant" ? mutation.message.conversationId : mutation.summary.conversationId;
|
|
874
|
+
const current = transaction.histories.get(conversationId) ?? [];
|
|
875
|
+
if (mutation.type === "admit") {
|
|
876
|
+
transaction.histories.set(conversationId, [...current, mutation.input]);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
if (mutation.type === "upsert-assistant") {
|
|
880
|
+
transaction.histories.set(conversationId, replaceMessage(current, mutation.message));
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
const replaced = new Set(mutation.replacedMessageIds);
|
|
884
|
+
const positions = current.map((message, index) => replaced.has(message.id) ? index : undefined).filter((index) => index !== undefined);
|
|
885
|
+
const first = positions[0];
|
|
886
|
+
if (first === undefined)
|
|
887
|
+
throw new Error("Compaction history range disappeared");
|
|
888
|
+
transaction.histories.set(conversationId, [
|
|
889
|
+
...current.slice(0, first),
|
|
890
|
+
mutation.summary,
|
|
891
|
+
...current.slice(first + positions.length)
|
|
892
|
+
]);
|
|
893
|
+
}
|
|
894
|
+
},
|
|
895
|
+
async scanRecoverable(input) {
|
|
896
|
+
const descriptors = [...runs].flatMap(([conversationId, conversationRuns]) => [...conversationRuns.values()].filter((record) => isActiveRunState(record.run.state)).map((record) => ({ conversationId, run: record.run }))).sort((left, right) => left.conversationId.localeCompare(right.conversationId) || left.run.id.localeCompare(right.run.id));
|
|
897
|
+
const cursorTuple = input.cursor ? parseRecoverableCursor(input.cursor) : undefined;
|
|
898
|
+
const start = cursorTuple ? descriptors.findIndex((item) => item.conversationId.localeCompare(cursorTuple[0]) > 0 || item.conversationId === cursorTuple[0] && item.run.id.localeCompare(cursorTuple[1]) > 0) : 0;
|
|
899
|
+
if (start === -1) {
|
|
900
|
+
return AgentRecoverablePageSchema.parse({ items: [] });
|
|
901
|
+
}
|
|
902
|
+
const items = descriptors.slice(start, start + input.limit);
|
|
903
|
+
const last = items.at(-1);
|
|
904
|
+
const hasMore = start + items.length < descriptors.length;
|
|
905
|
+
return AgentRecoverablePageSchema.parse({
|
|
906
|
+
items,
|
|
907
|
+
...hasMore && last && { nextCursor: recoverableCursor(last) }
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
};
|
|
911
|
+
return createAgentRuntimeStore(driver);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
export { isSpeakableAssistantStatus, assistantStatus, AgentStoreConflictSchema, AgentStoreNotFoundSchema, AgentStoreAppliedSchema, AgentStoreDuplicateSchema, AgentStoreMutationResultSchema, AgentRunViewSchema, AcceptInputAndAssignRunSchema, AcquireAgentRunSchema, CheckpointRunAssistantSchema, CommitRunTerminalSchema, RequestRunInterruptSchema, RecoverAgentRunSchema, ReplaceCompactedRangeSchema, AgentRuntimeHeadSchema, AgentStoredRunSchema, AgentAdmissionReceiptSchema, AgentHistoryMutationSchema, AgentRecoverableDescriptorSchema, AgentRecoverablePageSchema, ACTIVE_AGENT_RUN_STATES, createAgentRuntimeStore, createMemoryAgentRuntimeStore };
|