tnega 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -189
- package/dist/agent.js +2907 -0
- package/dist/agent.js.map +7 -0
- package/dist/bin.js +32512 -4419
- package/dist/bin.js.map +4 -4
- package/dist/cli-runtime.js +33404 -0
- package/dist/cli-runtime.js.map +7 -0
- package/dist/coding-agent.js +19088 -0
- package/dist/coding-agent.js.map +7 -0
- package/dist/core.js +1398 -0
- package/dist/core.js.map +7 -0
- package/dist/eval.js +23905 -0
- package/dist/eval.js.map +7 -0
- package/dist/events.js +1215 -0
- package/dist/events.js.map +7 -0
- package/dist/evolve.js +18303 -0
- package/dist/evolve.js.map +7 -0
- package/dist/index.js +33087 -4450
- package/dist/index.js.map +4 -4
- package/dist/llm.js +1016 -0
- package/dist/llm.js.map +7 -0
- package/dist/services.js +1142 -0
- package/dist/services.js.map +7 -0
- package/dist/session.js +1045 -0
- package/dist/session.js.map +7 -0
- package/dist/tools.js +2577 -0
- package/dist/tools.js.map +7 -0
- package/dist/types/packages/agent/src/definition.d.ts +17 -0
- package/dist/types/packages/agent/src/inbox-durable.d.ts +54 -0
- package/dist/types/packages/agent/src/index.d.ts +7 -0
- package/dist/types/packages/agent/src/live.d.ts +115 -0
- package/dist/types/packages/agent/src/llm-service.d.ts +32 -0
- package/dist/types/packages/agent/src/prompt.d.ts +55 -0
- package/dist/types/packages/agent/src/service.d.ts +68 -0
- package/dist/types/packages/agent/src/types.d.ts +215 -0
- package/dist/types/packages/benchmark/src/bigcodebench.d.ts +6 -0
- package/dist/types/packages/benchmark/src/humaneval.d.ts +11 -0
- package/dist/types/packages/benchmark/src/index.d.ts +9 -0
- package/dist/types/packages/benchmark/src/manifest.d.ts +10 -0
- package/dist/types/packages/benchmark/src/mbpp.d.ts +10 -0
- package/dist/types/packages/benchmark/src/parquet.d.ts +2 -0
- package/dist/types/packages/benchmark/src/stdlib.d.ts +3 -0
- package/dist/types/packages/benchmark/src/swebench.d.ts +2 -0
- package/dist/types/packages/benchmark/src/types.d.ts +58 -0
- package/dist/types/packages/cli/src/bin.d.ts +2 -0
- package/dist/types/packages/cli/src/commands.d.ts +146 -0
- package/dist/types/packages/cli/src/config.d.ts +28 -0
- package/dist/types/packages/cli/src/index.d.ts +9 -0
- package/dist/types/packages/cli/src/profile-file.d.ts +26 -0
- package/dist/types/packages/cli/src/profile.d.ts +9 -0
- package/dist/types/packages/cli/src/server.d.ts +14 -0
- package/dist/types/packages/cli/src/store.d.ts +60 -0
- package/dist/types/packages/cli/src/yaml.d.ts +1 -0
- package/dist/types/packages/coding-agent/src/codingAgent.d.ts +25 -0
- package/dist/types/packages/coding-agent/src/index.d.ts +6 -0
- package/dist/types/packages/coding-agent/src/mcp.d.ts +24 -0
- package/dist/types/packages/coding-agent/src/plan.d.ts +13 -0
- package/dist/types/packages/coding-agent/src/skills.d.ts +12 -0
- package/dist/types/packages/coding-agent/src/slash.d.ts +28 -0
- package/dist/types/packages/coding-agent/src/types.d.ts +41 -0
- package/dist/types/packages/core/src/context.d.ts +28 -0
- package/dist/types/packages/core/src/events.d.ts +57 -0
- package/dist/types/packages/core/src/fiber.d.ts +79 -0
- package/dist/types/packages/core/src/index.d.ts +10 -0
- package/dist/types/packages/core/src/invariant.d.ts +45 -0
- package/dist/types/packages/core/src/logger.d.ts +48 -0
- package/dist/types/packages/core/src/reflect.d.ts +42 -0
- package/dist/types/packages/core/src/registry.d.ts +54 -0
- package/dist/types/packages/core/src/service.d.ts +19 -0
- package/dist/types/packages/core/src/utils.d.ts +54 -0
- package/dist/types/packages/eval/src/codingRuntime.d.ts +18 -0
- package/dist/types/packages/eval/src/index.d.ts +5 -0
- package/dist/types/packages/eval/src/policy.d.ts +8 -0
- package/dist/types/packages/eval/src/runner.d.ts +31 -0
- package/dist/types/packages/eval/src/service.d.ts +23 -0
- package/dist/types/packages/eval/src/strategies.d.ts +58 -0
- package/dist/types/packages/eval/src/trace.d.ts +7 -0
- package/dist/types/packages/eval/src/types.d.ts +281 -0
- package/dist/types/packages/eval/src/utils.d.ts +5 -0
- package/dist/types/packages/eval/src/workspace.d.ts +17 -0
- package/dist/types/packages/evolve/src/diagnose.d.ts +9 -0
- package/dist/types/packages/evolve/src/index.d.ts +7 -0
- package/dist/types/packages/evolve/src/llm.d.ts +39 -0
- package/dist/types/packages/evolve/src/log.d.ts +26 -0
- package/dist/types/packages/evolve/src/select.d.ts +3 -0
- package/dist/types/packages/evolve/src/service.d.ts +29 -0
- package/dist/types/packages/evolve/src/types.d.ts +176 -0
- package/dist/types/packages/llm/src/anthropic-messages.d.ts +4 -0
- package/dist/types/packages/llm/src/errors.d.ts +6 -0
- package/dist/types/packages/llm/src/index.d.ts +7 -0
- package/dist/types/packages/llm/src/models.d.ts +29 -0
- package/dist/types/packages/llm/src/openai.d.ts +4 -0
- package/dist/types/packages/llm/src/provider.d.ts +3 -0
- package/dist/types/packages/llm/src/shared.d.ts +15 -0
- package/dist/types/packages/llm/src/types.d.ts +13 -0
- package/dist/types/packages/session/src/index.d.ts +500 -0
- package/dist/types/packages/session/src/invariant.d.ts +33 -0
- package/dist/types/packages/tools/src/builtins.d.ts +25 -0
- package/dist/types/packages/tools/src/calc.d.ts +4 -0
- package/dist/types/packages/tools/src/execution.d.ts +31 -0
- package/dist/types/packages/tools/src/index.d.ts +107 -0
- package/dist/types/packages/tools/src/path.d.ts +4 -0
- package/dist/types/packages/tools/src/policy.d.ts +14 -0
- package/dist/types/src/events.d.ts +1 -0
- package/dist/types/src/index.d.ts +15 -0
- package/dist/types/src/services.d.ts +4 -0
- package/dist/web/assets/index-45pokiBH.js +270 -0
- package/dist/web/assets/index-DCLL69Kz.css +1 -0
- package/dist/web/index.html +13 -0
- package/package.json +92 -35
package/dist/agent.js
ADDED
|
@@ -0,0 +1,2907 @@
|
|
|
1
|
+
import { createRequire as __tnegaCreateRequire } from 'node:module'; const require = __tnegaCreateRequire(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// packages/agent/src/service.ts
|
|
4
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
5
|
+
|
|
6
|
+
// packages/session/src/index.ts
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
9
|
+
import { dirname, resolve } from "node:path";
|
|
10
|
+
|
|
11
|
+
// packages/session/src/invariant.ts
|
|
12
|
+
function checkBalancedTurns(events) {
|
|
13
|
+
const opens = /* @__PURE__ */ new Map();
|
|
14
|
+
const failures = [];
|
|
15
|
+
for (const event of events) {
|
|
16
|
+
if (event.type === "turn/start") {
|
|
17
|
+
opens.set(event.payload.turn, (opens.get(event.payload.turn) ?? 0) + 1);
|
|
18
|
+
} else if (event.type === "turn/end") {
|
|
19
|
+
const remaining = opens.get(event.payload.turn) ?? 0;
|
|
20
|
+
if (remaining <= 0) {
|
|
21
|
+
failures.push({
|
|
22
|
+
name: "turn/end-without-start",
|
|
23
|
+
detail: `turn/end for turn ${event.payload.turn} has no matching turn/start`
|
|
24
|
+
});
|
|
25
|
+
} else {
|
|
26
|
+
opens.set(event.payload.turn, remaining - 1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const [turn, count] of opens) {
|
|
31
|
+
for (let index = 0; index < count; index += 1) {
|
|
32
|
+
failures.push({
|
|
33
|
+
name: "turn/start-without-end",
|
|
34
|
+
detail: `turn/start ${turn} has no matching turn/end`
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return failures;
|
|
39
|
+
}
|
|
40
|
+
function checkBalancedSteps(events) {
|
|
41
|
+
const open = /* @__PURE__ */ new Set();
|
|
42
|
+
const failures = [];
|
|
43
|
+
for (const event of events) {
|
|
44
|
+
if (event.type === "step/start") {
|
|
45
|
+
const key = `${event.payload.turn}:${event.payload.step}`;
|
|
46
|
+
if (open.has(key)) {
|
|
47
|
+
failures.push({
|
|
48
|
+
name: "duplicate-step-start",
|
|
49
|
+
detail: `step/start for ${key} appears twice without a matching step/end`
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
open.add(key);
|
|
53
|
+
} else if (event.type === "step/end") {
|
|
54
|
+
const key = `${event.payload.turn}:${event.payload.step}`;
|
|
55
|
+
if (!open.delete(key)) {
|
|
56
|
+
failures.push({
|
|
57
|
+
name: "step/end-without-start",
|
|
58
|
+
detail: `step/end for ${key} has no matching step/start`
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const key of open) {
|
|
64
|
+
failures.push({
|
|
65
|
+
name: "step/start-without-end",
|
|
66
|
+
detail: `step/start ${key} has no matching step/end`
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return failures;
|
|
70
|
+
}
|
|
71
|
+
function checkBalancedToolCalls(events) {
|
|
72
|
+
const open = /* @__PURE__ */ new Set();
|
|
73
|
+
const failures = [];
|
|
74
|
+
for (const event of events) {
|
|
75
|
+
if (event.type === "tool/call") {
|
|
76
|
+
open.add(event.payload.id);
|
|
77
|
+
} else if (event.type === "tool/result") {
|
|
78
|
+
const payload = event.payload;
|
|
79
|
+
const id = payload.toolCallId;
|
|
80
|
+
if (!open.delete(id)) {
|
|
81
|
+
failures.push({
|
|
82
|
+
name: "tool/result-without-call",
|
|
83
|
+
detail: `tool/result for call ${id} has no matching tool/call`
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
for (const id of open) {
|
|
89
|
+
failures.push({
|
|
90
|
+
name: "tool/call-without-result",
|
|
91
|
+
detail: `tool/call ${id} has no matching tool/result`
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return failures;
|
|
95
|
+
}
|
|
96
|
+
function checkMonotonicSeq(events) {
|
|
97
|
+
const failures = [];
|
|
98
|
+
for (let index = 1; index < events.length; index += 1) {
|
|
99
|
+
const previous = events[index - 1];
|
|
100
|
+
const current = events[index];
|
|
101
|
+
if (current && previous && current.seq <= previous.seq) {
|
|
102
|
+
failures.push({
|
|
103
|
+
name: "non-monotonic-seq",
|
|
104
|
+
detail: `seq ${current.seq} follows ${previous.seq} out of order at index ${index}`
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return failures;
|
|
109
|
+
}
|
|
110
|
+
function checkSessionInvariants(events) {
|
|
111
|
+
return [
|
|
112
|
+
...checkBalancedTurns(events),
|
|
113
|
+
...checkBalancedSteps(events),
|
|
114
|
+
...checkBalancedToolCalls(events),
|
|
115
|
+
...checkMonotonicSeq(events)
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// packages/session/src/index.ts
|
|
120
|
+
var SESSION_FORMAT_VERSION = 7;
|
|
121
|
+
var SessionFormatError = class extends Error {
|
|
122
|
+
name = "SessionFormatError";
|
|
123
|
+
};
|
|
124
|
+
var liveSessions = /* @__PURE__ */ new Map();
|
|
125
|
+
function sessionFileKey(file) {
|
|
126
|
+
return resolve(file);
|
|
127
|
+
}
|
|
128
|
+
function isMessageEventType(type) {
|
|
129
|
+
return type === "user/message" || type === "assistant/message" || type === "system/message";
|
|
130
|
+
}
|
|
131
|
+
function isMessageEvent(event) {
|
|
132
|
+
return isMessageEventType(event.type);
|
|
133
|
+
}
|
|
134
|
+
var DEFAULT_CONTEXT_LIMIT = 128e3;
|
|
135
|
+
function isSessionEvent(value) {
|
|
136
|
+
if (typeof value !== "object" || value === null) return false;
|
|
137
|
+
const record = value;
|
|
138
|
+
return typeof record.id === "string" && typeof record.seq === "number" && typeof record.ts === "number" && typeof record.type === "string" && "payload" in record;
|
|
139
|
+
}
|
|
140
|
+
function clone(value) {
|
|
141
|
+
return structuredClone(value);
|
|
142
|
+
}
|
|
143
|
+
function previousSurfaceSeq(nodes) {
|
|
144
|
+
return nodes.at(-1);
|
|
145
|
+
}
|
|
146
|
+
function isSurfaceEventType(type) {
|
|
147
|
+
return type === "user/message" || type === "system/message" || type === "assistant/message" || type === "tool/result";
|
|
148
|
+
}
|
|
149
|
+
function checkpointShadowRange(event) {
|
|
150
|
+
if (event.type !== "checkpoint") return void 0;
|
|
151
|
+
const op = event.payload.surfaceOp;
|
|
152
|
+
if (typeof op === "object" && op !== null && op.op === "replace") {
|
|
153
|
+
return { start: op.start, end: op.end };
|
|
154
|
+
}
|
|
155
|
+
return void 0;
|
|
156
|
+
}
|
|
157
|
+
function foldSurface(events) {
|
|
158
|
+
const nodes = [];
|
|
159
|
+
const replacements = [];
|
|
160
|
+
const applyReplace = (seq, start, end) => {
|
|
161
|
+
const startIndex = nodes.indexOf(start);
|
|
162
|
+
if (startIndex < 0) {
|
|
163
|
+
nodes.push(seq);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const endIndex = nodes.indexOf(end, startIndex);
|
|
167
|
+
if (endIndex < 0) {
|
|
168
|
+
nodes.push(seq);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const shadowed = nodes.splice(startIndex, endIndex - startIndex + 1);
|
|
172
|
+
nodes.splice(startIndex, 0, seq);
|
|
173
|
+
replacements.push({ seq, start, end, shadowedSeqs: shadowed });
|
|
174
|
+
};
|
|
175
|
+
for (const event of events) {
|
|
176
|
+
if (event.type === "checkpoint") {
|
|
177
|
+
const range = checkpointShadowRange(event);
|
|
178
|
+
if (!range) {
|
|
179
|
+
nodes.push(event.seq);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
applyReplace(event.seq, range.start, range.end);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (!isSurfaceEventType(event.type)) continue;
|
|
186
|
+
const op = event.surfaceOp ?? "append";
|
|
187
|
+
if (op === "append") {
|
|
188
|
+
nodes.push(event.seq);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
applyReplace(event.seq, op.start, op.end);
|
|
192
|
+
}
|
|
193
|
+
return { nodes, replacements };
|
|
194
|
+
}
|
|
195
|
+
function deriveEventMessage(event) {
|
|
196
|
+
switch (event.type) {
|
|
197
|
+
case "user/message": {
|
|
198
|
+
const message = { role: "user", content: event.payload.content };
|
|
199
|
+
if (event.payload.name) message.name = event.payload.name;
|
|
200
|
+
return message;
|
|
201
|
+
}
|
|
202
|
+
case "system/message": {
|
|
203
|
+
const message = { role: "system", content: event.payload.content };
|
|
204
|
+
if (event.payload.name) message.name = event.payload.name;
|
|
205
|
+
return message;
|
|
206
|
+
}
|
|
207
|
+
case "assistant/message": {
|
|
208
|
+
const toolCalls = event.payload.toolCalls;
|
|
209
|
+
if (!event.payload.content && !(toolCalls && toolCalls.length)) return null;
|
|
210
|
+
const message = { role: "assistant", content: event.payload.content };
|
|
211
|
+
if (event.payload.name) message.name = event.payload.name;
|
|
212
|
+
if (toolCalls?.length) message.tool_calls = clone(toolCalls);
|
|
213
|
+
return message;
|
|
214
|
+
}
|
|
215
|
+
case "tool/result": {
|
|
216
|
+
const failed = !event.payload.ok;
|
|
217
|
+
const message = {
|
|
218
|
+
role: "tool",
|
|
219
|
+
content: failed ? `error: ${event.payload.error?.message ?? "unknown"}` : stringify(event.payload.output),
|
|
220
|
+
tool_call_id: event.payload.toolCallId
|
|
221
|
+
};
|
|
222
|
+
message.name = event.payload.name;
|
|
223
|
+
if (failed) {
|
|
224
|
+
message.toolOk = false;
|
|
225
|
+
if (event.payload.error) message.toolError = event.payload.error;
|
|
226
|
+
}
|
|
227
|
+
return message;
|
|
228
|
+
}
|
|
229
|
+
default:
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function deriveSurfaceMessages(events) {
|
|
234
|
+
const { nodes } = foldSurface(events);
|
|
235
|
+
const bySeq = new Map(events.map((event) => [event.seq, event]));
|
|
236
|
+
const messages = [];
|
|
237
|
+
for (const seq of nodes) {
|
|
238
|
+
const event = bySeq.get(seq);
|
|
239
|
+
if (!event) continue;
|
|
240
|
+
if (event.type === "checkpoint") {
|
|
241
|
+
messages.splice(0, messages.length, ...clone(event.payload.messages));
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const message = deriveEventMessage(event);
|
|
245
|
+
if (message) messages.push(message);
|
|
246
|
+
}
|
|
247
|
+
return messages;
|
|
248
|
+
}
|
|
249
|
+
function foldRequestHeader(events) {
|
|
250
|
+
let header;
|
|
251
|
+
for (const event of events) {
|
|
252
|
+
if (event.type === "request/header") header = clone(event.payload);
|
|
253
|
+
}
|
|
254
|
+
return header;
|
|
255
|
+
}
|
|
256
|
+
function foldRequestContext(events) {
|
|
257
|
+
let context;
|
|
258
|
+
for (const event of events) {
|
|
259
|
+
if (event.type === "request/context") context = clone(event.payload);
|
|
260
|
+
}
|
|
261
|
+
return context;
|
|
262
|
+
}
|
|
263
|
+
function foldSessionMeta(events) {
|
|
264
|
+
const meta = {};
|
|
265
|
+
for (const event of events) {
|
|
266
|
+
if (event.type === "meta") {
|
|
267
|
+
const payload = event.payload;
|
|
268
|
+
if (typeof payload.title === "string") meta.title = payload.title;
|
|
269
|
+
if (payload.agentType === "general" || payload.agentType === "coding") {
|
|
270
|
+
meta.agentType = payload.agentType;
|
|
271
|
+
}
|
|
272
|
+
if (payload.mode === "auto" || payload.mode === "plan" || payload.mode === "execute") {
|
|
273
|
+
meta.mode = payload.mode;
|
|
274
|
+
}
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (event.type === "meta/patch") {
|
|
278
|
+
const patch = event.payload;
|
|
279
|
+
if (patch.fields.includes("title") && typeof patch.title === "string") {
|
|
280
|
+
meta.title = patch.title;
|
|
281
|
+
}
|
|
282
|
+
if (patch.fields.includes("agentType") && patch.agentType !== void 0) {
|
|
283
|
+
meta.agentType = patch.agentType;
|
|
284
|
+
}
|
|
285
|
+
if (patch.fields.includes("mode") && patch.mode !== void 0) {
|
|
286
|
+
meta.mode = patch.mode;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return meta;
|
|
291
|
+
}
|
|
292
|
+
function stringify(value) {
|
|
293
|
+
if (typeof value === "string") return value;
|
|
294
|
+
try {
|
|
295
|
+
return JSON.stringify(value) ?? String(value);
|
|
296
|
+
} catch {
|
|
297
|
+
return String(value);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function estimateMessageTokens(messages) {
|
|
301
|
+
let tokens = 0;
|
|
302
|
+
for (const message of messages) {
|
|
303
|
+
tokens += Math.ceil(message.content.length / 4);
|
|
304
|
+
for (const call of message.tool_calls ?? []) {
|
|
305
|
+
const raw = JSON.stringify(call.arguments ?? {}) ?? "";
|
|
306
|
+
tokens += Math.ceil(raw.length / 4);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return tokens;
|
|
310
|
+
}
|
|
311
|
+
function estimateEventTokens(event) {
|
|
312
|
+
switch (event.type) {
|
|
313
|
+
case "user/message":
|
|
314
|
+
case "system/message":
|
|
315
|
+
return Math.ceil(event.payload.content.length / 4);
|
|
316
|
+
case "assistant/message": {
|
|
317
|
+
let tokens = Math.ceil(event.payload.content.length / 4);
|
|
318
|
+
for (const call of event.payload.toolCalls ?? []) {
|
|
319
|
+
const raw = JSON.stringify(call.arguments ?? {}) ?? "";
|
|
320
|
+
tokens += Math.ceil(raw.length / 4);
|
|
321
|
+
}
|
|
322
|
+
return tokens;
|
|
323
|
+
}
|
|
324
|
+
case "assistant/chunk":
|
|
325
|
+
return Math.ceil(event.payload.content.length / 4);
|
|
326
|
+
case "tool/call": {
|
|
327
|
+
const raw = JSON.stringify(event.payload.arguments ?? {}) ?? "";
|
|
328
|
+
return Math.ceil(raw.length / 4);
|
|
329
|
+
}
|
|
330
|
+
case "tool/result": {
|
|
331
|
+
const raw = event.payload.ok ? stringify(event.payload.output) : event.payload.error?.message ?? "error";
|
|
332
|
+
return Math.ceil(raw.length / 4);
|
|
333
|
+
}
|
|
334
|
+
case "plan":
|
|
335
|
+
return 0;
|
|
336
|
+
case "checkpoint":
|
|
337
|
+
return estimateMessageTokens(event.payload.messages);
|
|
338
|
+
case "compaction/start":
|
|
339
|
+
case "compaction/end":
|
|
340
|
+
case "meta":
|
|
341
|
+
case "meta/patch":
|
|
342
|
+
return 0;
|
|
343
|
+
case "llm/retry":
|
|
344
|
+
case "llm/retry-started":
|
|
345
|
+
return 0;
|
|
346
|
+
case "request/header":
|
|
347
|
+
case "request/context":
|
|
348
|
+
case "agent/inbox/spliced":
|
|
349
|
+
return 0;
|
|
350
|
+
case "turn/start":
|
|
351
|
+
case "turn/end":
|
|
352
|
+
case "step/start":
|
|
353
|
+
case "step/end":
|
|
354
|
+
return 0;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function suffixStartIndexForTokens(events, targetTokens) {
|
|
358
|
+
if (!events.length || targetTokens <= 0) return 0;
|
|
359
|
+
let tokens = 0;
|
|
360
|
+
let candidate = events.length;
|
|
361
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
362
|
+
tokens += estimateEventTokens(events[index]);
|
|
363
|
+
if (tokens >= targetTokens) {
|
|
364
|
+
candidate = index;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (candidate === events.length) return 0;
|
|
369
|
+
let cut = candidate;
|
|
370
|
+
while (cut < events.length) {
|
|
371
|
+
const event = events[cut];
|
|
372
|
+
if (event?.type === "user/message") return cut;
|
|
373
|
+
cut += 1;
|
|
374
|
+
}
|
|
375
|
+
return candidate;
|
|
376
|
+
}
|
|
377
|
+
function repairUnclosed(events) {
|
|
378
|
+
const openCalls = [];
|
|
379
|
+
const openSteps = [];
|
|
380
|
+
const openTurns = [];
|
|
381
|
+
for (const event of events) {
|
|
382
|
+
switch (event.type) {
|
|
383
|
+
case "tool/call":
|
|
384
|
+
openCalls.push(event);
|
|
385
|
+
break;
|
|
386
|
+
case "tool/result": {
|
|
387
|
+
const callIndex = openCalls.findIndex(
|
|
388
|
+
(call) => call.payload.id === event.payload.toolCallId
|
|
389
|
+
);
|
|
390
|
+
if (callIndex >= 0) openCalls.splice(callIndex, 1);
|
|
391
|
+
else openCalls.pop();
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
case "step/start":
|
|
395
|
+
openSteps.push(event);
|
|
396
|
+
break;
|
|
397
|
+
case "step/end":
|
|
398
|
+
openSteps.pop();
|
|
399
|
+
break;
|
|
400
|
+
case "turn/start":
|
|
401
|
+
openTurns.push(event);
|
|
402
|
+
break;
|
|
403
|
+
case "turn/end":
|
|
404
|
+
openTurns.pop();
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (!openCalls.length && !openSteps.length && !openTurns.length) return [];
|
|
409
|
+
const synthetic = [];
|
|
410
|
+
let nextSeq = (events.at(-1)?.seq ?? 0) + 1;
|
|
411
|
+
let nextTs = (events.at(-1)?.ts ?? Date.now()) + 1;
|
|
412
|
+
const push = (type, payload) => {
|
|
413
|
+
synthetic.push({
|
|
414
|
+
id: randomUUID(),
|
|
415
|
+
seq: nextSeq,
|
|
416
|
+
ts: nextTs,
|
|
417
|
+
type,
|
|
418
|
+
payload: clone(payload)
|
|
419
|
+
});
|
|
420
|
+
nextSeq += 1;
|
|
421
|
+
nextTs += 1;
|
|
422
|
+
};
|
|
423
|
+
for (const call of openCalls) {
|
|
424
|
+
push("tool/result", {
|
|
425
|
+
id: call.payload.id,
|
|
426
|
+
toolCallId: call.payload.id,
|
|
427
|
+
name: call.payload.name,
|
|
428
|
+
ok: false,
|
|
429
|
+
error: {
|
|
430
|
+
name: "SessionInterruptedError",
|
|
431
|
+
message: "session interrupted before tool result was recorded"
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
for (const step of openSteps) {
|
|
436
|
+
push("step/end", {
|
|
437
|
+
turn: step.payload.turn,
|
|
438
|
+
step: step.payload.step,
|
|
439
|
+
finishReason: "interrupted",
|
|
440
|
+
interrupted: true
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
for (let i = 0; i < openTurns.length; i++) {
|
|
444
|
+
const turn = openTurns[i].payload.turn;
|
|
445
|
+
push("turn/end", {
|
|
446
|
+
turn,
|
|
447
|
+
finishReason: "interrupted",
|
|
448
|
+
interrupted: true
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
return synthetic;
|
|
452
|
+
}
|
|
453
|
+
function resolveCompactKeep(events, options) {
|
|
454
|
+
if (typeof options.keepTokens === "number" && Number.isFinite(options.keepTokens) && options.keepTokens > 0) {
|
|
455
|
+
return events.length - suffixStartIndexForTokens(events, options.keepTokens);
|
|
456
|
+
}
|
|
457
|
+
const keep = typeof options.keep === "number" && Number.isFinite(options.keep) && options.keep > 0 ? Math.floor(options.keep) : 0;
|
|
458
|
+
return Math.min(keep, events.length);
|
|
459
|
+
}
|
|
460
|
+
function estimateContextUsage(messages, limit = DEFAULT_CONTEXT_LIMIT) {
|
|
461
|
+
const tokens = estimateMessageTokens(messages);
|
|
462
|
+
return {
|
|
463
|
+
tokens,
|
|
464
|
+
limit,
|
|
465
|
+
ratio: limit > 0 ? tokens / limit : 0
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
var SessionLog = class {
|
|
469
|
+
constructor(file, _broadcast) {
|
|
470
|
+
this.file = file;
|
|
471
|
+
this._broadcast = _broadcast;
|
|
472
|
+
}
|
|
473
|
+
file;
|
|
474
|
+
_broadcast;
|
|
475
|
+
_events = [];
|
|
476
|
+
_surface = [];
|
|
477
|
+
_surfaceNodes = [];
|
|
478
|
+
_requestHeader;
|
|
479
|
+
_requestContext;
|
|
480
|
+
_loaded = false;
|
|
481
|
+
_nextSeq = 1;
|
|
482
|
+
_queue = Promise.resolve();
|
|
483
|
+
_writeTail = Promise.resolve();
|
|
484
|
+
_pending = [];
|
|
485
|
+
_drainScheduled = false;
|
|
486
|
+
_writeError;
|
|
487
|
+
init() {
|
|
488
|
+
return this._run(async () => {
|
|
489
|
+
const key = sessionFileKey(this.file);
|
|
490
|
+
if (!liveSessions.has(key)) liveSessions.set(key, this);
|
|
491
|
+
try {
|
|
492
|
+
await this._ensureLoaded();
|
|
493
|
+
await this._drainWrite();
|
|
494
|
+
if (this._writeError) {
|
|
495
|
+
const error = this._writeError;
|
|
496
|
+
this._writeError = void 0;
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
499
|
+
} catch (error) {
|
|
500
|
+
if (liveSessions.get(key) === this) liveSessions.delete(key);
|
|
501
|
+
throw error;
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
append(type, payload) {
|
|
506
|
+
return this._run(async () => {
|
|
507
|
+
await this._ensureLoaded();
|
|
508
|
+
const event = this._buildEvent(type, payload);
|
|
509
|
+
this._commitEvent(event);
|
|
510
|
+
return event;
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
_buildEvent(type, payload) {
|
|
514
|
+
const eventPayload = clone(payload);
|
|
515
|
+
const event = {
|
|
516
|
+
id: randomUUID(),
|
|
517
|
+
seq: this._nextSeq,
|
|
518
|
+
ts: Date.now(),
|
|
519
|
+
type,
|
|
520
|
+
payload: eventPayload
|
|
521
|
+
};
|
|
522
|
+
if (isMessageEventType(type)) {
|
|
523
|
+
for (let index = this._events.length - 1; index >= 0; index -= 1) {
|
|
524
|
+
const previous2 = this._events[index];
|
|
525
|
+
if (previous2 && isMessageEvent(previous2)) {
|
|
526
|
+
const messagePayload = eventPayload;
|
|
527
|
+
messagePayload.parentId ??= previous2.id;
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
event.surfaceOp ??= "append";
|
|
532
|
+
const previous = previousSurfaceSeq(this._surfaceNodes);
|
|
533
|
+
event.sourceEventSeqs ??= previous === void 0 ? [] : [previous];
|
|
534
|
+
} else if (type === "request/header") {
|
|
535
|
+
event.sourceEventSeqs ??= [];
|
|
536
|
+
} else if (type === "request/context") {
|
|
537
|
+
event.sourceEventSeqs ??= [];
|
|
538
|
+
} else if (type === "agent/inbox/spliced") {
|
|
539
|
+
event.sourceEventSeqs ??= [];
|
|
540
|
+
}
|
|
541
|
+
this._nextSeq += 1;
|
|
542
|
+
return event;
|
|
543
|
+
}
|
|
544
|
+
_commitEvent(event) {
|
|
545
|
+
this._events.push(event);
|
|
546
|
+
this._refreshSurface();
|
|
547
|
+
if (event.type === "request/header") this._requestHeader = clone(event.payload);
|
|
548
|
+
if (event.type === "request/context") this._requestContext = clone(event.payload);
|
|
549
|
+
this._broadcast?.("event", event);
|
|
550
|
+
this._enqueue(event);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Rebuild both surface views from the committed log. The nodes come from the
|
|
554
|
+
* positional {@link foldSurface} fold; the messages come from
|
|
555
|
+
* {@link deriveSurfaceMessages}, which walks those nodes in surface order —
|
|
556
|
+
* the model view is a pure function of the surface, never of raw file order.
|
|
557
|
+
*/
|
|
558
|
+
_refreshSurface() {
|
|
559
|
+
this._surfaceNodes = foldSurface(this._events).nodes;
|
|
560
|
+
this._surface = deriveSurfaceMessages(this._events);
|
|
561
|
+
}
|
|
562
|
+
_enqueue(event) {
|
|
563
|
+
this._pending.push(event);
|
|
564
|
+
if (this._drainScheduled) return;
|
|
565
|
+
this._drainScheduled = true;
|
|
566
|
+
setImmediate(() => {
|
|
567
|
+
this._drainScheduled = false;
|
|
568
|
+
void this._drainWrite();
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
_drainWrite() {
|
|
572
|
+
const events = this._pending.splice(0);
|
|
573
|
+
if (!events.length) return this._writeTail;
|
|
574
|
+
const write = this._writeTail.then(() => appendFile(
|
|
575
|
+
this.file,
|
|
576
|
+
`${events.map((event) => JSON.stringify(event)).join("\n")}
|
|
577
|
+
`,
|
|
578
|
+
"utf8"
|
|
579
|
+
));
|
|
580
|
+
this._writeTail = write.catch((error) => {
|
|
581
|
+
this._writeError ??= error;
|
|
582
|
+
});
|
|
583
|
+
return write.then(() => void 0, () => void 0);
|
|
584
|
+
}
|
|
585
|
+
flush() {
|
|
586
|
+
return this._run(async () => {
|
|
587
|
+
await this._ensureLoaded();
|
|
588
|
+
await this._drainWrite();
|
|
589
|
+
if (this._writeError) {
|
|
590
|
+
const error = this._writeError;
|
|
591
|
+
this._writeError = void 0;
|
|
592
|
+
throw error;
|
|
593
|
+
}
|
|
594
|
+
const seq = this._nextSeq - 1;
|
|
595
|
+
this._broadcast?.("flush", { file: this.file, seq });
|
|
596
|
+
return seq;
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
async lineage(messageId) {
|
|
600
|
+
return this._run(async () => {
|
|
601
|
+
await this._ensureLoaded();
|
|
602
|
+
return this._resolveLineage(messageId);
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
/** The next turn number: one more than the highest recorded `turn/start`. */
|
|
606
|
+
async nextTurn() {
|
|
607
|
+
return this._run(async () => {
|
|
608
|
+
await this._ensureLoaded();
|
|
609
|
+
let max = 0;
|
|
610
|
+
for (const event of this._events) {
|
|
611
|
+
if (event.type === "turn/start") max = Math.max(max, event.payload.turn);
|
|
612
|
+
}
|
|
613
|
+
return max + 1;
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
async forkAt(messageId) {
|
|
617
|
+
return this._run(async () => {
|
|
618
|
+
await this._ensureLoaded();
|
|
619
|
+
const lineage = this._resolveLineage(messageId);
|
|
620
|
+
const lineageIds = new Set(lineage.map((event) => event.id));
|
|
621
|
+
const targetIndex = this._events.findIndex(
|
|
622
|
+
(event) => event.id === messageId && isMessageEvent(event)
|
|
623
|
+
);
|
|
624
|
+
if (targetIndex < 0) {
|
|
625
|
+
throw new Error(`message not found: ${messageId}`);
|
|
626
|
+
}
|
|
627
|
+
const selected = [];
|
|
628
|
+
let skippedMessage = false;
|
|
629
|
+
for (let index = 0; index <= targetIndex; index += 1) {
|
|
630
|
+
const event = this._events[index];
|
|
631
|
+
if (isMessageEvent(event)) {
|
|
632
|
+
if (!lineageIds.has(event.id)) {
|
|
633
|
+
skippedMessage = true;
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
skippedMessage = false;
|
|
637
|
+
selected.push(clone(event));
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if (event.type === "meta" || event.type === "llm/retry" || event.type === "llm/retry-started" || event.type.startsWith("turn/") || event.type.startsWith("step/")) {
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
if (event.type === "checkpoint") {
|
|
644
|
+
selected.push(clone(event));
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (!skippedMessage) {
|
|
648
|
+
selected.push(clone(event));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return selected;
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
read() {
|
|
655
|
+
return this._run(async () => {
|
|
656
|
+
await this._ensureLoaded();
|
|
657
|
+
return this._events.map((event) => clone(event));
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
deriveMessages() {
|
|
661
|
+
return this._run(async () => {
|
|
662
|
+
await this._ensureLoaded();
|
|
663
|
+
return clone(this._surface);
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
/** The ordered surface events, after compaction replacements are applied. */
|
|
667
|
+
surfaceEvents() {
|
|
668
|
+
return this._run(async () => {
|
|
669
|
+
await this._ensureLoaded();
|
|
670
|
+
const bySeq = new Map(this._events.map((event) => [event.seq, event]));
|
|
671
|
+
return this._surfaceNodes.map((seq) => bySeq.get(seq)).filter((event) => event !== void 0).map((event) => clone(event));
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
/** The latest `request/header` snapshot, or undefined before the first one. */
|
|
675
|
+
requestHeader() {
|
|
676
|
+
return this._requestHeader ? clone(this._requestHeader) : void 0;
|
|
677
|
+
}
|
|
678
|
+
/** The latest resolved route metadata, or undefined before the first one. */
|
|
679
|
+
requestContext() {
|
|
680
|
+
return this._requestContext ? clone(this._requestContext) : void 0;
|
|
681
|
+
}
|
|
682
|
+
/** Display metadata folded from the durable `meta` + `meta/patch` events. */
|
|
683
|
+
meta() {
|
|
684
|
+
return this._run(async () => {
|
|
685
|
+
await this._ensureLoaded();
|
|
686
|
+
return foldSessionMeta(this._events);
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
estimateContext(limit = DEFAULT_CONTEXT_LIMIT) {
|
|
690
|
+
return this._run(async () => {
|
|
691
|
+
await this._ensureLoaded();
|
|
692
|
+
return estimateContextUsage(this._surface, limit);
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
replay(reduce, initial) {
|
|
696
|
+
return this.read().then((events) => {
|
|
697
|
+
if (!reduce) return events;
|
|
698
|
+
let state = initial;
|
|
699
|
+
let pending = Promise.resolve();
|
|
700
|
+
for (const event of events) {
|
|
701
|
+
pending = pending.then(async () => {
|
|
702
|
+
state = await reduce(state, event);
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
return pending.then(() => state);
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
/** Structural violations in the loaded event stream (empty = balanced). */
|
|
709
|
+
runInvariants() {
|
|
710
|
+
return this._run(async () => {
|
|
711
|
+
await this._ensureLoaded();
|
|
712
|
+
return checkSessionInvariants(this._events);
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Compact the session without ever rewriting history.
|
|
717
|
+
*
|
|
718
|
+
* The surface (not the raw log) is the unit of compaction. A kept suffix —
|
|
719
|
+
* measured in tokens via `keepTokens`, or in nodes via `keep`, and always
|
|
720
|
+
* snapped to start on a fresh `user/message` turn so a tool result never
|
|
721
|
+
* outlives the assistant call it answers — stays visible; every earlier
|
|
722
|
+
* surface node is shadowed by the checkpoint's `surfaceOp.replace` range.
|
|
723
|
+
* The three compaction events are appended like any other event, so seqs
|
|
724
|
+
* stay immutable, live observers see the transition, and a crash can only
|
|
725
|
+
* leave an orphaned `compaction/start` rather than a torn rewrite.
|
|
726
|
+
*
|
|
727
|
+
* Returns the new event count, or the unchanged count when there is no
|
|
728
|
+
* useful surface span to shadow.
|
|
729
|
+
*/
|
|
730
|
+
compact(options = {}) {
|
|
731
|
+
return this._run(async () => {
|
|
732
|
+
await this._ensureLoaded();
|
|
733
|
+
const events = this._events;
|
|
734
|
+
const bySeq = new Map(events.map((event) => [event.seq, event]));
|
|
735
|
+
const surfaceEvents = foldSurface(events).nodes.map((seq) => bySeq.get(seq)).filter((event) => event !== void 0);
|
|
736
|
+
const keep = resolveCompactKeep(surfaceEvents, options);
|
|
737
|
+
let split = Math.max(0, surfaceEvents.length - keep);
|
|
738
|
+
if (split < surfaceEvents.length && surfaceEvents[split].type !== "user/message") {
|
|
739
|
+
let next = split;
|
|
740
|
+
while (next < surfaceEvents.length && surfaceEvents[next].type !== "user/message") next += 1;
|
|
741
|
+
if (next < surfaceEvents.length) {
|
|
742
|
+
split = next;
|
|
743
|
+
} else {
|
|
744
|
+
let lastUser = surfaceEvents.length - 1;
|
|
745
|
+
while (lastUser >= 0 && surfaceEvents[lastUser].type !== "user/message") lastUser -= 1;
|
|
746
|
+
split = lastUser >= 0 ? lastUser : surfaceEvents.length;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
const shadowed = surfaceEvents.slice(0, split);
|
|
750
|
+
if (!options.messages?.length) return this._events.length;
|
|
751
|
+
const range = shadowed.length ? { start: shadowed[0].seq, end: shadowed[shadowed.length - 1].seq } : void 0;
|
|
752
|
+
const boundary = split;
|
|
753
|
+
this._commitEvent(this._buildEvent("compaction/start", {
|
|
754
|
+
...boundary > 0 ? { boundary } : {},
|
|
755
|
+
...options.keep !== void 0 ? { keep: options.keep } : {},
|
|
756
|
+
...options.tokensBefore !== void 0 ? { tokensBefore: options.tokensBefore } : {}
|
|
757
|
+
}));
|
|
758
|
+
const checkpoint = this._buildEvent("checkpoint", {
|
|
759
|
+
messages: clone(options.messages),
|
|
760
|
+
...range ? { surfaceOp: { op: "replace", start: range.start, end: range.end } } : {},
|
|
761
|
+
...options.summary ? { summary: options.summary } : {},
|
|
762
|
+
...options.tokensBefore !== void 0 ? { tokensBefore: options.tokensBefore } : {}
|
|
763
|
+
});
|
|
764
|
+
this._commitEvent(checkpoint);
|
|
765
|
+
this._commitEvent(this._buildEvent("compaction/end", {
|
|
766
|
+
checkpointId: checkpoint.id,
|
|
767
|
+
...options.keep !== void 0 ? { keep: options.keep } : {}
|
|
768
|
+
}));
|
|
769
|
+
return this._events.length;
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
close() {
|
|
773
|
+
return this._queue.then(async () => {
|
|
774
|
+
await this._drainWrite();
|
|
775
|
+
if (this._writeError) {
|
|
776
|
+
const error = this._writeError;
|
|
777
|
+
this._writeError = void 0;
|
|
778
|
+
throw error;
|
|
779
|
+
}
|
|
780
|
+
const key = sessionFileKey(this.file);
|
|
781
|
+
if (liveSessions.get(key) === this) liveSessions.delete(key);
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
_run(task) {
|
|
785
|
+
const next = this._queue.then(task, task);
|
|
786
|
+
this._queue = next.then(() => void 0, () => void 0);
|
|
787
|
+
return next;
|
|
788
|
+
}
|
|
789
|
+
async _ensureLoaded() {
|
|
790
|
+
if (this._loaded) return;
|
|
791
|
+
const key = sessionFileKey(this.file);
|
|
792
|
+
const owner = liveSessions.get(key);
|
|
793
|
+
if (owner && owner !== this && owner._loaded) {
|
|
794
|
+
this._events = owner._events.map((event) => clone(event));
|
|
795
|
+
this._surface = clone(owner._surface);
|
|
796
|
+
this._surfaceNodes = [...owner._surfaceNodes];
|
|
797
|
+
this._requestHeader = owner._requestHeader ? clone(owner._requestHeader) : void 0;
|
|
798
|
+
this._requestContext = owner._requestContext ? clone(owner._requestContext) : void 0;
|
|
799
|
+
this._nextSeq = owner._nextSeq;
|
|
800
|
+
this._loaded = true;
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
await mkdir(dirname(resolve(this.file)), { recursive: true });
|
|
804
|
+
const read = await this._read();
|
|
805
|
+
if (!read.existing) {
|
|
806
|
+
const meta = {
|
|
807
|
+
id: randomUUID(),
|
|
808
|
+
seq: 1,
|
|
809
|
+
ts: Date.now(),
|
|
810
|
+
type: "meta",
|
|
811
|
+
payload: { formatVersion: SESSION_FORMAT_VERSION }
|
|
812
|
+
};
|
|
813
|
+
await writeFile(this.file, `${JSON.stringify(meta)}
|
|
814
|
+
`, "utf8");
|
|
815
|
+
this._events = [meta];
|
|
816
|
+
this._refreshSurface();
|
|
817
|
+
this._nextSeq = 2;
|
|
818
|
+
this._loaded = true;
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
this._events = read.events;
|
|
822
|
+
this._refreshSurface();
|
|
823
|
+
this._requestHeader = foldRequestHeader(this._events);
|
|
824
|
+
this._requestContext = foldRequestContext(this._events);
|
|
825
|
+
this._nextSeq = (this._events.at(-1)?.seq ?? 0) + 1;
|
|
826
|
+
this._loaded = true;
|
|
827
|
+
}
|
|
828
|
+
async _read() {
|
|
829
|
+
let text;
|
|
830
|
+
try {
|
|
831
|
+
text = await readFile(this.file, "utf8");
|
|
832
|
+
} catch (error) {
|
|
833
|
+
if (error.code === "ENOENT") {
|
|
834
|
+
return { existing: false, events: [] };
|
|
835
|
+
}
|
|
836
|
+
throw error;
|
|
837
|
+
}
|
|
838
|
+
if (!text.trim()) return { existing: false, events: [] };
|
|
839
|
+
const lines = text.split("\n");
|
|
840
|
+
const events = [];
|
|
841
|
+
let torn = false;
|
|
842
|
+
for (const line of lines) {
|
|
843
|
+
if (!line.trim()) continue;
|
|
844
|
+
if (torn) continue;
|
|
845
|
+
let parsed;
|
|
846
|
+
try {
|
|
847
|
+
parsed = JSON.parse(line);
|
|
848
|
+
} catch {
|
|
849
|
+
torn = true;
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (!isSessionEvent(parsed)) {
|
|
853
|
+
torn = true;
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
events.push(parsed);
|
|
857
|
+
}
|
|
858
|
+
this._assertFormat(events);
|
|
859
|
+
const key = sessionFileKey(this.file);
|
|
860
|
+
const owner = liveSessions.get(key);
|
|
861
|
+
if (owner && owner !== this) {
|
|
862
|
+
return { existing: true, events };
|
|
863
|
+
}
|
|
864
|
+
const synthetic = repairUnclosed(events);
|
|
865
|
+
if (!torn && !synthetic.length) return { existing: true, events };
|
|
866
|
+
const repaired = [...events, ...synthetic];
|
|
867
|
+
await writeFile(
|
|
868
|
+
this.file,
|
|
869
|
+
`${repaired.map((event) => JSON.stringify(event)).join("\n")}
|
|
870
|
+
`,
|
|
871
|
+
"utf8"
|
|
872
|
+
);
|
|
873
|
+
return { existing: true, events: repaired };
|
|
874
|
+
}
|
|
875
|
+
_assertFormat(events) {
|
|
876
|
+
let version;
|
|
877
|
+
for (const event of events) {
|
|
878
|
+
if (event.type !== "meta") continue;
|
|
879
|
+
const value = event.payload.formatVersion;
|
|
880
|
+
if (typeof value === "number") version = value;
|
|
881
|
+
}
|
|
882
|
+
if (version === void 0) {
|
|
883
|
+
throw new SessionFormatError(
|
|
884
|
+
`session log is missing formatVersion; expected ${SESSION_FORMAT_VERSION}`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
if (version !== SESSION_FORMAT_VERSION) {
|
|
888
|
+
throw new SessionFormatError(
|
|
889
|
+
`unsupported session format version ${version}; expected ${SESSION_FORMAT_VERSION}`
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
_resolveLineage(messageId) {
|
|
894
|
+
const messages = this._events.filter(
|
|
895
|
+
(event) => isMessageEvent(event)
|
|
896
|
+
);
|
|
897
|
+
const byId = new Map(
|
|
898
|
+
messages.map((event) => [event.id, event])
|
|
899
|
+
);
|
|
900
|
+
if (!byId.has(messageId)) {
|
|
901
|
+
throw new Error(`message not found: ${messageId}`);
|
|
902
|
+
}
|
|
903
|
+
const chain = [];
|
|
904
|
+
const seen = /* @__PURE__ */ new Set();
|
|
905
|
+
let cursorId = messageId;
|
|
906
|
+
while (cursorId && byId.has(cursorId) && !seen.has(cursorId)) {
|
|
907
|
+
seen.add(cursorId);
|
|
908
|
+
const event = byId.get(cursorId);
|
|
909
|
+
chain.unshift(clone(event));
|
|
910
|
+
const parentId = event.payload.parentId;
|
|
911
|
+
if (parentId && byId.has(parentId)) {
|
|
912
|
+
cursorId = parentId;
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
const index = messages.findIndex((message) => message.id === cursorId);
|
|
916
|
+
cursorId = index > 0 ? messages[index - 1].id : void 0;
|
|
917
|
+
}
|
|
918
|
+
return chain;
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
// packages/agent/src/service.ts
|
|
923
|
+
var AgentError = class extends Error {
|
|
924
|
+
name = "AgentError";
|
|
925
|
+
};
|
|
926
|
+
var AgentInbox = class {
|
|
927
|
+
_queue = [];
|
|
928
|
+
_steerQueue = [];
|
|
929
|
+
_injected = /* @__PURE__ */ new Map();
|
|
930
|
+
get size() {
|
|
931
|
+
return this._queue.length + this._steerQueue.length;
|
|
932
|
+
}
|
|
933
|
+
get steerSize() {
|
|
934
|
+
return this._steerQueue.length;
|
|
935
|
+
}
|
|
936
|
+
push(input) {
|
|
937
|
+
this._queue.push(input);
|
|
938
|
+
return input;
|
|
939
|
+
}
|
|
940
|
+
followup(input) {
|
|
941
|
+
return this.push(input);
|
|
942
|
+
}
|
|
943
|
+
steer(input) {
|
|
944
|
+
this._steerQueue.push(input);
|
|
945
|
+
return input;
|
|
946
|
+
}
|
|
947
|
+
send(input, options = {}) {
|
|
948
|
+
if (options.mode === "steer") return this.steer(input);
|
|
949
|
+
return this.followup(input);
|
|
950
|
+
}
|
|
951
|
+
claim() {
|
|
952
|
+
return this._steerQueue.shift() ?? this._queue.shift();
|
|
953
|
+
}
|
|
954
|
+
peek() {
|
|
955
|
+
return this._steerQueue[0] ?? this._queue[0];
|
|
956
|
+
}
|
|
957
|
+
inject(key, value) {
|
|
958
|
+
this._injected.set(key, value);
|
|
959
|
+
}
|
|
960
|
+
injected() {
|
|
961
|
+
return new Map(this._injected);
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
function copyMessages(messages) {
|
|
965
|
+
return messages.map((message) => {
|
|
966
|
+
const copy = {
|
|
967
|
+
role: message.role,
|
|
968
|
+
content: message.content
|
|
969
|
+
};
|
|
970
|
+
if (message.name) copy.name = message.name;
|
|
971
|
+
if (message.tool_call_id) copy.tool_call_id = message.tool_call_id;
|
|
972
|
+
if (message.toolOk !== void 0) copy.toolOk = message.toolOk;
|
|
973
|
+
if (message.toolError) copy.toolError = { ...message.toolError };
|
|
974
|
+
if (message.tool_calls) {
|
|
975
|
+
copy.tool_calls = message.tool_calls.map((call) => ({
|
|
976
|
+
id: call.id,
|
|
977
|
+
name: call.name,
|
|
978
|
+
arguments: call.arguments
|
|
979
|
+
}));
|
|
980
|
+
}
|
|
981
|
+
return copy;
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
function isUserOrSystemMessage(message) {
|
|
985
|
+
return message.role === "system" || message.role === "user";
|
|
986
|
+
}
|
|
987
|
+
function stringify2(value) {
|
|
988
|
+
if (typeof value === "string") return value;
|
|
989
|
+
try {
|
|
990
|
+
return JSON.stringify(value) ?? String(value);
|
|
991
|
+
} catch {
|
|
992
|
+
return String(value);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
function copyCompletion(completion) {
|
|
996
|
+
const copy = {
|
|
997
|
+
finishReason: completion.finishReason
|
|
998
|
+
};
|
|
999
|
+
if (completion.content !== void 0) copy.content = completion.content;
|
|
1000
|
+
if (completion.toolCalls) {
|
|
1001
|
+
copy.toolCalls = completion.toolCalls.map((call) => ({
|
|
1002
|
+
id: call.id,
|
|
1003
|
+
name: call.name,
|
|
1004
|
+
arguments: call.arguments
|
|
1005
|
+
}));
|
|
1006
|
+
}
|
|
1007
|
+
return copy;
|
|
1008
|
+
}
|
|
1009
|
+
function isCancelCause(value) {
|
|
1010
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1011
|
+
const record = value;
|
|
1012
|
+
if (record.type === "user" || record.type === "parent" || record.type === "disposed") {
|
|
1013
|
+
return true;
|
|
1014
|
+
}
|
|
1015
|
+
if (record.type === "abort" && (record.message === void 0 || typeof record.message === "string")) {
|
|
1016
|
+
return true;
|
|
1017
|
+
}
|
|
1018
|
+
if (record.type === "timeout" && typeof record.timeoutMs === "number") {
|
|
1019
|
+
return true;
|
|
1020
|
+
}
|
|
1021
|
+
return false;
|
|
1022
|
+
}
|
|
1023
|
+
function cancelCauseFromSignal(signal) {
|
|
1024
|
+
if (!signal?.aborted) return void 0;
|
|
1025
|
+
const reason = signal.reason;
|
|
1026
|
+
if (isCancelCause(reason)) return reason;
|
|
1027
|
+
return { type: "abort" };
|
|
1028
|
+
}
|
|
1029
|
+
function toTurnEndReason(fields) {
|
|
1030
|
+
const { cancelled, turnError, finishReason, cancelCause } = fields;
|
|
1031
|
+
if (cancelled || finishReason === "cancelled") {
|
|
1032
|
+
return { kind: "aborted", cause: cancelCause ?? { type: "user" } };
|
|
1033
|
+
}
|
|
1034
|
+
if (turnError || finishReason === "error") {
|
|
1035
|
+
return {
|
|
1036
|
+
kind: "error",
|
|
1037
|
+
error: turnError ? toToolError(turnError) : { name: "TurnError", message: finishReason }
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
switch (finishReason) {
|
|
1041
|
+
case "length":
|
|
1042
|
+
return { kind: "max-tokens" };
|
|
1043
|
+
case "max_steps":
|
|
1044
|
+
return { kind: "max-steps" };
|
|
1045
|
+
case "max_turns":
|
|
1046
|
+
return { kind: "max-turns" };
|
|
1047
|
+
default:
|
|
1048
|
+
return { kind: "completed" };
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function partialStreamContent(events) {
|
|
1052
|
+
let content = "";
|
|
1053
|
+
for (const event of events) {
|
|
1054
|
+
if (event.type === "message_delta") content += event.delta;
|
|
1055
|
+
}
|
|
1056
|
+
return content;
|
|
1057
|
+
}
|
|
1058
|
+
async function cancellableDelay(ms, signal) {
|
|
1059
|
+
if (ms <= 0) return !signal?.aborted;
|
|
1060
|
+
if (signal?.aborted) return false;
|
|
1061
|
+
return new Promise((resolve2) => {
|
|
1062
|
+
const timer = setTimeout(() => {
|
|
1063
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1064
|
+
resolve2(true);
|
|
1065
|
+
}, ms);
|
|
1066
|
+
const onAbort = () => {
|
|
1067
|
+
clearTimeout(timer);
|
|
1068
|
+
resolve2(false);
|
|
1069
|
+
};
|
|
1070
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
var AgentService = class {
|
|
1074
|
+
constructor(ctx, config = {}) {
|
|
1075
|
+
this.ctx = ctx;
|
|
1076
|
+
this.config = config;
|
|
1077
|
+
this.inbox = config.inbox ?? new AgentInbox();
|
|
1078
|
+
}
|
|
1079
|
+
ctx;
|
|
1080
|
+
config;
|
|
1081
|
+
inbox;
|
|
1082
|
+
_seriesStarted = false;
|
|
1083
|
+
_persistedRequest = false;
|
|
1084
|
+
/** Call when a request begins an explicit new model-message series. */
|
|
1085
|
+
startSeries() {
|
|
1086
|
+
this._seriesStarted = true;
|
|
1087
|
+
}
|
|
1088
|
+
/** Clear any pending series marker after a completed run. */
|
|
1089
|
+
endSeriesBoundary() {
|
|
1090
|
+
this._seriesStarted = false;
|
|
1091
|
+
}
|
|
1092
|
+
/** True when this service instance has already persisted a model-visible step. */
|
|
1093
|
+
get hasPersistedRequest() {
|
|
1094
|
+
return this._persistedRequest;
|
|
1095
|
+
}
|
|
1096
|
+
async run(input, options = {}) {
|
|
1097
|
+
return consumeAgentStream(this._stream(input, options, false));
|
|
1098
|
+
}
|
|
1099
|
+
async *runStream(input, options = {}) {
|
|
1100
|
+
const iterator = this._stream(input, options)[Symbol.asyncIterator]();
|
|
1101
|
+
while (true) {
|
|
1102
|
+
const next = await iterator.next();
|
|
1103
|
+
if (next.done) return next.value;
|
|
1104
|
+
yield next.value;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
async *_stream(input, options = {}, useStream = true) {
|
|
1108
|
+
const claimed = input ?? this.inbox.claim();
|
|
1109
|
+
if (!claimed) throw new AgentError("no agent input available");
|
|
1110
|
+
await this.config.hooks?.beforeRun?.(claimed, options);
|
|
1111
|
+
const session = this._session();
|
|
1112
|
+
const tools = this._tools();
|
|
1113
|
+
const llm = this._llm();
|
|
1114
|
+
if (!llm) throw new AgentError("no LLM adapter available");
|
|
1115
|
+
const maxTurns = options.maxTurns ?? this.config.maxTurns ?? 64;
|
|
1116
|
+
const maxSteps = options.maxSteps ?? this.config.maxSteps ?? 64;
|
|
1117
|
+
const contextBudget = options.contextBudget ?? this.config.contextBudget;
|
|
1118
|
+
const injected = this.inbox.injected();
|
|
1119
|
+
this.ctx.emit("agent/start", { input: claimed, options, injected });
|
|
1120
|
+
let messages = await this._initialMessages(claimed);
|
|
1121
|
+
const steps = [];
|
|
1122
|
+
let output = "";
|
|
1123
|
+
let finishReason = "stop";
|
|
1124
|
+
this.ctx.emit("agent/turn-start", {
|
|
1125
|
+
input: claimed,
|
|
1126
|
+
messages: copyMessages(messages),
|
|
1127
|
+
injected
|
|
1128
|
+
});
|
|
1129
|
+
const turn = await session.nextTurn();
|
|
1130
|
+
await session.append("turn/start", {
|
|
1131
|
+
turn,
|
|
1132
|
+
input: claimed.text ?? claimed,
|
|
1133
|
+
reason: "user"
|
|
1134
|
+
});
|
|
1135
|
+
let index = 0;
|
|
1136
|
+
let finalTurnGranted = false;
|
|
1137
|
+
let currentStepIndex;
|
|
1138
|
+
let turnError;
|
|
1139
|
+
try {
|
|
1140
|
+
while (index < maxTurns + (finalTurnGranted ? 1 : 0)) {
|
|
1141
|
+
if (options.signal?.aborted) {
|
|
1142
|
+
finishReason = "cancelled";
|
|
1143
|
+
break;
|
|
1144
|
+
}
|
|
1145
|
+
if (steps.length >= maxSteps) {
|
|
1146
|
+
finishReason = "max_steps";
|
|
1147
|
+
break;
|
|
1148
|
+
}
|
|
1149
|
+
if (contextBudget) {
|
|
1150
|
+
messages = await this._enforceContextBudget(session, contextBudget, messages);
|
|
1151
|
+
}
|
|
1152
|
+
const stepInput = copyMessages(messages);
|
|
1153
|
+
const preStep = this.ctx.waterfall("agent/pre-step", {
|
|
1154
|
+
index,
|
|
1155
|
+
turn,
|
|
1156
|
+
step: index,
|
|
1157
|
+
messages: stepInput,
|
|
1158
|
+
...options.signal ? { signal: options.signal } : {}
|
|
1159
|
+
}, (payload) => payload);
|
|
1160
|
+
if (!preStep || !Array.isArray(preStep.messages) || !preStep.messages.length) {
|
|
1161
|
+
break;
|
|
1162
|
+
}
|
|
1163
|
+
if (preStep.startsRequestSeries) this._seriesStarted = true;
|
|
1164
|
+
await session.append("step/start", { turn, step: index });
|
|
1165
|
+
currentStepIndex = index;
|
|
1166
|
+
const requestedInput = copyMessages(preStep.messages);
|
|
1167
|
+
this.ctx.emit("agent/step", {
|
|
1168
|
+
index,
|
|
1169
|
+
turn,
|
|
1170
|
+
step: index,
|
|
1171
|
+
input: copyMessages(requestedInput)
|
|
1172
|
+
});
|
|
1173
|
+
const availableTools = finalTurnGranted ? [] : await this._resolveAvailableTools();
|
|
1174
|
+
const completeOptions = {
|
|
1175
|
+
maxSteps: maxSteps - steps.length
|
|
1176
|
+
};
|
|
1177
|
+
if (options.signal) completeOptions.signal = options.signal;
|
|
1178
|
+
const request = this.ctx.waterfall("agent/request", {
|
|
1179
|
+
index,
|
|
1180
|
+
messages: requestedInput,
|
|
1181
|
+
tools: availableTools,
|
|
1182
|
+
options: completeOptions
|
|
1183
|
+
}, (payload) => payload);
|
|
1184
|
+
if (!request || !request.options || !request.tools) {
|
|
1185
|
+
throw new AgentError("agent/request must return a request payload");
|
|
1186
|
+
}
|
|
1187
|
+
let llmMessages = copyMessages(requestedInput);
|
|
1188
|
+
let completion;
|
|
1189
|
+
const streamMethod = useStream ? llm.stream : void 0;
|
|
1190
|
+
let attempt = 0;
|
|
1191
|
+
let persistedStepInput = false;
|
|
1192
|
+
while (true) {
|
|
1193
|
+
if (options.signal?.aborted) {
|
|
1194
|
+
finishReason = "cancelled";
|
|
1195
|
+
break;
|
|
1196
|
+
}
|
|
1197
|
+
const streamEvents = [];
|
|
1198
|
+
try {
|
|
1199
|
+
if (streamMethod) {
|
|
1200
|
+
const streamRequest = {
|
|
1201
|
+
index,
|
|
1202
|
+
messages: llmMessages,
|
|
1203
|
+
tools: request.tools,
|
|
1204
|
+
options: request.options
|
|
1205
|
+
};
|
|
1206
|
+
const stream = await this.ctx.waterfallAsync(
|
|
1207
|
+
"llm/stream",
|
|
1208
|
+
streamRequest,
|
|
1209
|
+
async (payload) => {
|
|
1210
|
+
if (!streamMethod) {
|
|
1211
|
+
throw new AgentError("llm.stream is required for streaming runs");
|
|
1212
|
+
}
|
|
1213
|
+
return streamMethod(payload.messages, payload.tools, payload.options);
|
|
1214
|
+
}
|
|
1215
|
+
);
|
|
1216
|
+
if (!stream || typeof stream[Symbol.asyncIterator] !== "function") {
|
|
1217
|
+
throw new AgentError("llm/stream must return an async iterable stream");
|
|
1218
|
+
}
|
|
1219
|
+
llmMessages = copyMessages(streamRequest.messages);
|
|
1220
|
+
if (!persistedStepInput) {
|
|
1221
|
+
await this._persistStepInput(session, request, llmMessages);
|
|
1222
|
+
if (this.config.assertReplayable) {
|
|
1223
|
+
await this._assertReplayable(session, llmMessages);
|
|
1224
|
+
}
|
|
1225
|
+
persistedStepInput = true;
|
|
1226
|
+
}
|
|
1227
|
+
let chunkIndex = 0;
|
|
1228
|
+
for await (const event of stream) {
|
|
1229
|
+
streamEvents.push(event);
|
|
1230
|
+
if (event.type === "message_delta") {
|
|
1231
|
+
await session.append("assistant/chunk", {
|
|
1232
|
+
id: event.id,
|
|
1233
|
+
content: event.delta,
|
|
1234
|
+
index: chunkIndex
|
|
1235
|
+
});
|
|
1236
|
+
chunkIndex += 1;
|
|
1237
|
+
}
|
|
1238
|
+
yield event;
|
|
1239
|
+
}
|
|
1240
|
+
completion = completionFromStreamEvents(streamEvents);
|
|
1241
|
+
} else {
|
|
1242
|
+
if (!persistedStepInput) {
|
|
1243
|
+
await this._persistStepInput(session, request, llmMessages);
|
|
1244
|
+
if (this.config.assertReplayable) {
|
|
1245
|
+
await this._assertReplayable(session, llmMessages);
|
|
1246
|
+
}
|
|
1247
|
+
persistedStepInput = true;
|
|
1248
|
+
}
|
|
1249
|
+
completion = await llm.complete(llmMessages, request.tools, request.options);
|
|
1250
|
+
}
|
|
1251
|
+
break;
|
|
1252
|
+
} catch (error) {
|
|
1253
|
+
const content = partialStreamContent(streamEvents);
|
|
1254
|
+
if (content) {
|
|
1255
|
+
await session.append("assistant/message", {
|
|
1256
|
+
content,
|
|
1257
|
+
interrupted: true
|
|
1258
|
+
});
|
|
1259
|
+
llmMessages = await session.deriveMessages();
|
|
1260
|
+
}
|
|
1261
|
+
if (options.signal?.aborted) {
|
|
1262
|
+
finishReason = "cancelled";
|
|
1263
|
+
break;
|
|
1264
|
+
}
|
|
1265
|
+
attempt += 1;
|
|
1266
|
+
const failure = toToolError(error);
|
|
1267
|
+
const decision = await this.ctx.waterfallAsync(
|
|
1268
|
+
"agent/request-error",
|
|
1269
|
+
{
|
|
1270
|
+
index,
|
|
1271
|
+
turn,
|
|
1272
|
+
step: index,
|
|
1273
|
+
messages: copyMessages(llmMessages),
|
|
1274
|
+
tools: request.tools,
|
|
1275
|
+
options: request.options,
|
|
1276
|
+
attempt,
|
|
1277
|
+
error,
|
|
1278
|
+
failure,
|
|
1279
|
+
...request.options.provider ? { provider: request.options.provider } : {},
|
|
1280
|
+
...request.options.model ? { model: request.options.model } : {},
|
|
1281
|
+
...options.signal ? { signal: options.signal } : {}
|
|
1282
|
+
},
|
|
1283
|
+
() => void 0
|
|
1284
|
+
);
|
|
1285
|
+
if (options.signal?.aborted) {
|
|
1286
|
+
finishReason = "cancelled";
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1289
|
+
if (decision?.kind !== "retry") throw error;
|
|
1290
|
+
const retryId = randomUUID2();
|
|
1291
|
+
const delayMs = decision.delayMs ?? 0;
|
|
1292
|
+
await session.append("llm/retry", {
|
|
1293
|
+
retryId,
|
|
1294
|
+
retry: attempt,
|
|
1295
|
+
...delayMs > 0 ? { delayMs } : {},
|
|
1296
|
+
failure: toToolError(error)
|
|
1297
|
+
});
|
|
1298
|
+
if (!await cancellableDelay(delayMs, options.signal)) {
|
|
1299
|
+
finishReason = "cancelled";
|
|
1300
|
+
break;
|
|
1301
|
+
}
|
|
1302
|
+
await session.append("llm/retry-started", { retryId, retry: attempt });
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
if (options.signal?.aborted) break;
|
|
1306
|
+
if (!completion) throw new AgentError("LLM adapter did not produce a completion");
|
|
1307
|
+
const toolCalls = completion.toolCalls ?? [];
|
|
1308
|
+
if (finalTurnGranted && toolCalls.length) {
|
|
1309
|
+
finishReason = "max_turns";
|
|
1310
|
+
break;
|
|
1311
|
+
}
|
|
1312
|
+
const toolResults = [];
|
|
1313
|
+
if (toolCalls.length) {
|
|
1314
|
+
await session.append("assistant/message", {
|
|
1315
|
+
content: completion.content ?? "",
|
|
1316
|
+
toolCalls: toolCalls.map((call) => ({
|
|
1317
|
+
id: call.id,
|
|
1318
|
+
name: call.name,
|
|
1319
|
+
arguments: call.arguments
|
|
1320
|
+
}))
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
for (const call of toolCalls) {
|
|
1324
|
+
this.ctx.emit("agent/tool-call", { index, turn, step: index, call });
|
|
1325
|
+
yield { type: "tool/start", index, turn, step: index, call };
|
|
1326
|
+
await session.append("tool/call", {
|
|
1327
|
+
id: call.id,
|
|
1328
|
+
name: call.name,
|
|
1329
|
+
arguments: call.arguments
|
|
1330
|
+
});
|
|
1331
|
+
const toolOptions = { callId: call.id };
|
|
1332
|
+
if (options.signal) toolOptions.signal = options.signal;
|
|
1333
|
+
const startedAt = Date.now();
|
|
1334
|
+
let result;
|
|
1335
|
+
try {
|
|
1336
|
+
result = await tools.execute(call.name, call.arguments, toolOptions);
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
result = {
|
|
1339
|
+
ok: false,
|
|
1340
|
+
name: call.name,
|
|
1341
|
+
callId: call.id,
|
|
1342
|
+
input: call.arguments,
|
|
1343
|
+
error: toToolError(error),
|
|
1344
|
+
startedAt,
|
|
1345
|
+
durationMs: Date.now() - startedAt
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
toolResults.push(result);
|
|
1349
|
+
const toolResultPayload = {
|
|
1350
|
+
id: call.id,
|
|
1351
|
+
toolCallId: call.id,
|
|
1352
|
+
name: call.name,
|
|
1353
|
+
ok: result.ok,
|
|
1354
|
+
durationMs: result.durationMs
|
|
1355
|
+
};
|
|
1356
|
+
if (result.output !== void 0) toolResultPayload.output = result.output;
|
|
1357
|
+
if (result.error) {
|
|
1358
|
+
toolResultPayload.error = {
|
|
1359
|
+
name: result.error.name,
|
|
1360
|
+
message: result.error.message
|
|
1361
|
+
};
|
|
1362
|
+
if (result.error.stack) toolResultPayload.error.stack = result.error.stack;
|
|
1363
|
+
}
|
|
1364
|
+
await session.append("tool/result", toolResultPayload);
|
|
1365
|
+
yield { type: "tool/end", index, turn, step: index, call, result };
|
|
1366
|
+
this.ctx.emit("agent/tool-result", {
|
|
1367
|
+
index,
|
|
1368
|
+
turn,
|
|
1369
|
+
step: index,
|
|
1370
|
+
call,
|
|
1371
|
+
result
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
if (options.signal?.aborted) {
|
|
1375
|
+
finishReason = "cancelled";
|
|
1376
|
+
break;
|
|
1377
|
+
}
|
|
1378
|
+
steps.push({
|
|
1379
|
+
index,
|
|
1380
|
+
input: copyMessages(llmMessages),
|
|
1381
|
+
completion,
|
|
1382
|
+
toolResults
|
|
1383
|
+
});
|
|
1384
|
+
if (completion.content) {
|
|
1385
|
+
output = completion.content;
|
|
1386
|
+
}
|
|
1387
|
+
if (!toolCalls.length) {
|
|
1388
|
+
await session.append("assistant/message", { content: completion.content ?? "" });
|
|
1389
|
+
}
|
|
1390
|
+
await session.append("step/end", {
|
|
1391
|
+
turn,
|
|
1392
|
+
step: index,
|
|
1393
|
+
finishReason: completion.finishReason,
|
|
1394
|
+
toolCalls: toolCalls.length
|
|
1395
|
+
});
|
|
1396
|
+
currentStepIndex = void 0;
|
|
1397
|
+
const nextMessages = this._extendMessages(llmMessages, completion, toolResults);
|
|
1398
|
+
const concludesTurn = toolResults.some((result) => result.concludesTurn === true);
|
|
1399
|
+
if (concludesTurn) {
|
|
1400
|
+
finishReason = "stop";
|
|
1401
|
+
break;
|
|
1402
|
+
}
|
|
1403
|
+
if (toolCalls.length === 0) {
|
|
1404
|
+
finishReason = completion.finishReason === "length" ? "length" : completion.finishReason === "error" ? "error" : "stop";
|
|
1405
|
+
const keepGoing = await this.ctx.serial("agent/turn-stopping", {
|
|
1406
|
+
index,
|
|
1407
|
+
turn,
|
|
1408
|
+
steps: copySteps(steps),
|
|
1409
|
+
finishReason
|
|
1410
|
+
});
|
|
1411
|
+
if (!keepGoing) break;
|
|
1412
|
+
}
|
|
1413
|
+
if (index + 1 >= maxTurns && !finalTurnGranted) {
|
|
1414
|
+
finalTurnGranted = true;
|
|
1415
|
+
} else if (index + 1 >= maxTurns + (finalTurnGranted ? 1 : 0)) {
|
|
1416
|
+
finishReason = "max_turns";
|
|
1417
|
+
break;
|
|
1418
|
+
}
|
|
1419
|
+
if (toolCalls.length) finishReason = "tool_calls";
|
|
1420
|
+
index += 1;
|
|
1421
|
+
messages = nextMessages;
|
|
1422
|
+
}
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
turnError = error;
|
|
1425
|
+
throw error;
|
|
1426
|
+
} finally {
|
|
1427
|
+
const cancelCause = cancelCauseFromSignal(options.signal);
|
|
1428
|
+
if (currentStepIndex !== void 0) {
|
|
1429
|
+
const cancelled = options.signal?.aborted;
|
|
1430
|
+
await session.append("step/end", {
|
|
1431
|
+
turn,
|
|
1432
|
+
step: currentStepIndex,
|
|
1433
|
+
finishReason: cancelled ? "cancelled" : turnError ? "error" : "interrupted",
|
|
1434
|
+
interrupted: true,
|
|
1435
|
+
...cancelCause ? { cancelCause } : {},
|
|
1436
|
+
...turnError ? { error: toToolError(turnError) } : {}
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
await session.append("turn/end", {
|
|
1440
|
+
turn,
|
|
1441
|
+
finishReason: turnError ? options.signal?.aborted ? "cancelled" : "error" : finishReason,
|
|
1442
|
+
reason: toTurnEndReason({
|
|
1443
|
+
cancelled: options.signal?.aborted ?? false,
|
|
1444
|
+
turnError,
|
|
1445
|
+
finishReason: turnError ? options.signal?.aborted ? "cancelled" : "error" : finishReason,
|
|
1446
|
+
...cancelCause ? { cancelCause } : {}
|
|
1447
|
+
}),
|
|
1448
|
+
...cancelCause ? { cancelCause } : {},
|
|
1449
|
+
...output ? { output } : {},
|
|
1450
|
+
...steps.length ? { steps: steps.length } : {},
|
|
1451
|
+
...turnError ? { interrupted: true, error: toToolError(turnError) } : {}
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
const runResult = {
|
|
1455
|
+
input: claimed,
|
|
1456
|
+
output,
|
|
1457
|
+
finishReason,
|
|
1458
|
+
turn,
|
|
1459
|
+
steps,
|
|
1460
|
+
messages: copyMessages(messages)
|
|
1461
|
+
};
|
|
1462
|
+
this.ctx.emit("agent/turn-end", {
|
|
1463
|
+
input: claimed,
|
|
1464
|
+
steps: copySteps(steps),
|
|
1465
|
+
messages: copyMessages(messages),
|
|
1466
|
+
output,
|
|
1467
|
+
finishReason
|
|
1468
|
+
});
|
|
1469
|
+
this.ctx.emit("agent/end", {
|
|
1470
|
+
input: claimed,
|
|
1471
|
+
steps: copySteps(steps),
|
|
1472
|
+
messages: copyMessages(messages),
|
|
1473
|
+
output,
|
|
1474
|
+
finishReason
|
|
1475
|
+
});
|
|
1476
|
+
await this.config.hooks?.afterRun?.(runResult, options);
|
|
1477
|
+
yield { type: "run/end", run: runResult };
|
|
1478
|
+
return runResult;
|
|
1479
|
+
}
|
|
1480
|
+
async _enforceContextBudget(session, budget, messages) {
|
|
1481
|
+
const limit = budget.limit ?? DEFAULT_CONTEXT_LIMIT;
|
|
1482
|
+
const compactRatio = budget.compactRatio ?? 0.9;
|
|
1483
|
+
if (limit <= 0 || compactRatio <= 0 || compactRatio > 1) {
|
|
1484
|
+
throw new AgentError("invalid context budget: limit must be positive and compactRatio must be in (0, 1]");
|
|
1485
|
+
}
|
|
1486
|
+
const usage = estimateContextUsage(messages, limit);
|
|
1487
|
+
if (usage.ratio < compactRatio) return copyMessages(messages);
|
|
1488
|
+
const keepTokens = budget.keepTokens ?? Math.max(1, Math.floor(limit * 0.5));
|
|
1489
|
+
const compactMessages = budget.summarize ? await budget.summarize(messages, usage) : [{ role: "system", content: defaultContextSummary(messages, usage) }];
|
|
1490
|
+
const summary = compactMessages.map((message) => `${message.role}: ${message.content}`).join("\n");
|
|
1491
|
+
await session.compact({
|
|
1492
|
+
keepTokens,
|
|
1493
|
+
summary,
|
|
1494
|
+
tokensBefore: usage.tokens,
|
|
1495
|
+
messages: compactMessages.map((message) => copyMessages([message])[0])
|
|
1496
|
+
});
|
|
1497
|
+
this.ctx.emit("agent/context-compact", {
|
|
1498
|
+
type: "agent/context-compact",
|
|
1499
|
+
messagesBefore: messages.length,
|
|
1500
|
+
tokensBefore: usage.tokens,
|
|
1501
|
+
limit,
|
|
1502
|
+
keepTokens,
|
|
1503
|
+
messagesAfter: compactMessages.length
|
|
1504
|
+
});
|
|
1505
|
+
return compactMessages.map((message) => copyMessages([message])[0]);
|
|
1506
|
+
}
|
|
1507
|
+
async _persistStepInput(session, request, input) {
|
|
1508
|
+
const system = input.find((message) => message.role === "system")?.content;
|
|
1509
|
+
const tools = request.tools.map((tool) => ({
|
|
1510
|
+
name: tool.schema.name,
|
|
1511
|
+
description: tool.schema.description,
|
|
1512
|
+
...tool.schema.parameters ? { parameters: tool.schema.parameters } : {}
|
|
1513
|
+
}));
|
|
1514
|
+
const config = {
|
|
1515
|
+
...request.options.provider ? { provider: request.options.provider } : {},
|
|
1516
|
+
...request.options.model ? { model: request.options.model } : {},
|
|
1517
|
+
...request.options.temperature !== void 0 ? { temperature: request.options.temperature } : {}
|
|
1518
|
+
};
|
|
1519
|
+
const nextHeader = {
|
|
1520
|
+
...Object.keys(config).length ? { config } : {},
|
|
1521
|
+
...system !== void 0 ? { system } : {},
|
|
1522
|
+
...tools.length ? { tools } : {}
|
|
1523
|
+
};
|
|
1524
|
+
const previousHeader = session.requestHeader();
|
|
1525
|
+
const isFirstRequest = !this._persistedRequest;
|
|
1526
|
+
const isResume = isFirstRequest && previousHeader !== void 0;
|
|
1527
|
+
this._persistedRequest = true;
|
|
1528
|
+
const changedHeader = previousHeader && (previousHeader.system !== nextHeader.system || JSON.stringify(previousHeader.tools ?? []) !== JSON.stringify(nextHeader.tools ?? []) || JSON.stringify(previousHeader.config ?? {}) !== JSON.stringify(nextHeader.config ?? {}));
|
|
1529
|
+
if (isFirstRequest) {
|
|
1530
|
+
await session.append("request/header", {
|
|
1531
|
+
reason: this._seriesStarted ? "series" : isResume ? "resume" : "initial",
|
|
1532
|
+
...nextHeader,
|
|
1533
|
+
...this._seriesStarted ? { startsSeries: true } : {}
|
|
1534
|
+
});
|
|
1535
|
+
} else if (changedHeader) {
|
|
1536
|
+
await session.append("request/header", {
|
|
1537
|
+
reason: this._seriesStarted ? "change-series" : "change",
|
|
1538
|
+
...nextHeader,
|
|
1539
|
+
startsSeries: true
|
|
1540
|
+
});
|
|
1541
|
+
} else if (this._seriesStarted) {
|
|
1542
|
+
await session.append("request/header", {
|
|
1543
|
+
reason: "series",
|
|
1544
|
+
...nextHeader,
|
|
1545
|
+
startsSeries: true
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
this._seriesStarted = false;
|
|
1549
|
+
const nextContext = {
|
|
1550
|
+
...request.options.provider ? { provider: request.options.provider } : {},
|
|
1551
|
+
...request.options.model ? { model: request.options.model } : {}
|
|
1552
|
+
};
|
|
1553
|
+
const llmService2 = this.ctx.reflect.get("llm", false);
|
|
1554
|
+
const routeCapacity = llmService2?.routeCapacity?.();
|
|
1555
|
+
if (routeCapacity?.contextWindow !== void 0) {
|
|
1556
|
+
nextContext.contextWindow = routeCapacity.contextWindow;
|
|
1557
|
+
}
|
|
1558
|
+
const previousContext = session.requestContext();
|
|
1559
|
+
if (previousContext?.provider !== nextContext.provider || previousContext?.model !== nextContext.model || previousContext?.contextWindow !== nextContext.contextWindow || previousContext === void 0) {
|
|
1560
|
+
await session.append("request/context", nextContext);
|
|
1561
|
+
}
|
|
1562
|
+
const requested = input.filter(isUserOrSystemMessage);
|
|
1563
|
+
const surface = await session.deriveMessages();
|
|
1564
|
+
const appendDurable = async (messages) => {
|
|
1565
|
+
for (const message of messages) {
|
|
1566
|
+
if (message.role === "user") {
|
|
1567
|
+
await session.append("user/message", {
|
|
1568
|
+
content: message.content,
|
|
1569
|
+
...message.name ? { name: message.name } : {}
|
|
1570
|
+
});
|
|
1571
|
+
} else {
|
|
1572
|
+
await session.append("system/message", {
|
|
1573
|
+
content: message.content,
|
|
1574
|
+
...message.name ? { name: message.name } : {}
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
if (surface.length === 0) {
|
|
1580
|
+
await appendDurable(requested);
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
const durableUsers = surface.filter((message) => message.role === "user").length;
|
|
1584
|
+
let seenUsers = 0;
|
|
1585
|
+
const newTail = [];
|
|
1586
|
+
for (const message of requested) {
|
|
1587
|
+
if (message.role === "user") seenUsers += 1;
|
|
1588
|
+
if (seenUsers <= durableUsers) continue;
|
|
1589
|
+
newTail.push(message);
|
|
1590
|
+
}
|
|
1591
|
+
await appendDurable(newTail);
|
|
1592
|
+
}
|
|
1593
|
+
async _assertReplayable(session, input) {
|
|
1594
|
+
const reconstructed = await session.deriveMessages();
|
|
1595
|
+
const canonical = (messages) => messages.filter((message) => message.role !== "system").map((message) => ({
|
|
1596
|
+
role: message.role,
|
|
1597
|
+
content: message.content,
|
|
1598
|
+
...message.tool_call_id ? { tool_call_id: message.tool_call_id } : {},
|
|
1599
|
+
...message.name ? { name: message.name } : {},
|
|
1600
|
+
...message.tool_calls?.length ? { tool_calls: message.tool_calls.map((call) => ({
|
|
1601
|
+
id: call.id,
|
|
1602
|
+
name: call.name,
|
|
1603
|
+
args: JSON.stringify(call.arguments ?? {})
|
|
1604
|
+
})) } : {}
|
|
1605
|
+
}));
|
|
1606
|
+
const actual = canonical(input);
|
|
1607
|
+
const replay = canonical(reconstructed);
|
|
1608
|
+
if (JSON.stringify(actual) !== JSON.stringify(replay)) {
|
|
1609
|
+
throw new AgentError(
|
|
1610
|
+
`request is not reconstructable from session log: actual=${JSON.stringify(actual)} replay=${JSON.stringify(replay)}`
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
async _initialMessages(input) {
|
|
1615
|
+
const promptService = this.ctx.reflect.get("systemPrompt", false);
|
|
1616
|
+
const assembled = promptService ? (await promptService.assemble()).text.trim() : void 0;
|
|
1617
|
+
const systemPrompt2 = assembled || this.inbox.injected().get("agentSystem");
|
|
1618
|
+
const systemMessage = typeof systemPrompt2 === "string" && systemPrompt2 ? [{ role: "system", content: systemPrompt2 }] : [];
|
|
1619
|
+
if (input.messages?.length) {
|
|
1620
|
+
const first = input.messages[0];
|
|
1621
|
+
if (first?.role === "system") return copyMessages(input.messages);
|
|
1622
|
+
return [...systemMessage, ...copyMessages(input.messages)];
|
|
1623
|
+
}
|
|
1624
|
+
if (input.text) return [...systemMessage, { role: "user", content: input.text }];
|
|
1625
|
+
return systemMessage;
|
|
1626
|
+
}
|
|
1627
|
+
_extendMessages(messages, completion, toolResults) {
|
|
1628
|
+
const next = copyMessages(messages);
|
|
1629
|
+
const assistant = {
|
|
1630
|
+
role: "assistant",
|
|
1631
|
+
content: completion.content ?? ""
|
|
1632
|
+
};
|
|
1633
|
+
const toolCalls = completion.toolCalls ?? [];
|
|
1634
|
+
if (toolCalls.length) {
|
|
1635
|
+
assistant.tool_calls = toolCalls.map((call) => ({
|
|
1636
|
+
id: call.id,
|
|
1637
|
+
name: call.name,
|
|
1638
|
+
arguments: call.arguments
|
|
1639
|
+
}));
|
|
1640
|
+
}
|
|
1641
|
+
next.push(assistant);
|
|
1642
|
+
for (const call of toolCalls) {
|
|
1643
|
+
const result = toolResults.find((candidate) => candidate.callId === call.id);
|
|
1644
|
+
const content = result ? result.ok ? stringify2(result.output) : `error: ${result.error?.message ?? "unknown"}` : "error: missing tool result";
|
|
1645
|
+
const message = {
|
|
1646
|
+
role: "tool",
|
|
1647
|
+
content,
|
|
1648
|
+
tool_call_id: call.id
|
|
1649
|
+
};
|
|
1650
|
+
message.name = result?.name ?? call.name;
|
|
1651
|
+
if (result && !result.ok) {
|
|
1652
|
+
message.toolOk = false;
|
|
1653
|
+
if (result.error) {
|
|
1654
|
+
message.toolError = {
|
|
1655
|
+
name: result.error.name,
|
|
1656
|
+
message: result.error.message
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
next.push(message);
|
|
1661
|
+
}
|
|
1662
|
+
return next;
|
|
1663
|
+
}
|
|
1664
|
+
_session() {
|
|
1665
|
+
if (this.config.session) return this.config.session;
|
|
1666
|
+
const session = this.ctx.session;
|
|
1667
|
+
if (!session) throw new AgentError("session service is required");
|
|
1668
|
+
return session;
|
|
1669
|
+
}
|
|
1670
|
+
_tools() {
|
|
1671
|
+
const tools = this.ctx.tools;
|
|
1672
|
+
if (!tools) throw new AgentError("tools service is required");
|
|
1673
|
+
return tools;
|
|
1674
|
+
}
|
|
1675
|
+
async _resolveAvailableTools() {
|
|
1676
|
+
const executable = this._tools().list();
|
|
1677
|
+
const promptService = this.ctx.reflect.get("systemPrompt", false);
|
|
1678
|
+
if (!promptService) return executable;
|
|
1679
|
+
const schemas = await promptService.toolSchemas();
|
|
1680
|
+
if (!schemas.length) return executable;
|
|
1681
|
+
const byName = new Map(executable.map((tool) => [tool.schema.name, tool]));
|
|
1682
|
+
return schemas.map((schema) => byName.get(schema.name)).filter((tool) => tool !== void 0);
|
|
1683
|
+
}
|
|
1684
|
+
_llm() {
|
|
1685
|
+
if (this.config.llm) return this.config.llm;
|
|
1686
|
+
const service = this.ctx.reflect.get("llm", false);
|
|
1687
|
+
return service?.current();
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
function defaultContextSummary(messages, usage) {
|
|
1691
|
+
const latest = messages.at(-1);
|
|
1692
|
+
const latestText = latest ? `${latest.role}: ${latest.content}` : "none";
|
|
1693
|
+
return [
|
|
1694
|
+
"Earlier context was compacted.",
|
|
1695
|
+
`Messages before compaction: ${messages.length}.`,
|
|
1696
|
+
`Tokens before compaction: ${usage.tokens}.`,
|
|
1697
|
+
`Latest message: ${latestText}`
|
|
1698
|
+
].join(" ");
|
|
1699
|
+
}
|
|
1700
|
+
function copySteps(steps) {
|
|
1701
|
+
return steps.map((step) => ({
|
|
1702
|
+
index: step.index,
|
|
1703
|
+
input: copyMessages(step.input),
|
|
1704
|
+
completion: copyCompletion(step.completion),
|
|
1705
|
+
toolResults: step.toolResults.map((result) => ({ ...result }))
|
|
1706
|
+
}));
|
|
1707
|
+
}
|
|
1708
|
+
function toToolError(error) {
|
|
1709
|
+
if (error instanceof Error) {
|
|
1710
|
+
const result = {
|
|
1711
|
+
name: error.name,
|
|
1712
|
+
message: error.message
|
|
1713
|
+
};
|
|
1714
|
+
if (error.stack) result.stack = error.stack;
|
|
1715
|
+
return result;
|
|
1716
|
+
}
|
|
1717
|
+
return {
|
|
1718
|
+
name: "ToolExecutionError",
|
|
1719
|
+
message: String(error)
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
function completionFromStreamEvents(events) {
|
|
1723
|
+
let content = "";
|
|
1724
|
+
const calls = /* @__PURE__ */ new Map();
|
|
1725
|
+
let finishReason = "error";
|
|
1726
|
+
for (const event of events) {
|
|
1727
|
+
if (event.type === "message_delta") {
|
|
1728
|
+
content += event.delta;
|
|
1729
|
+
} else if (event.type === "toolcall_end") {
|
|
1730
|
+
calls.set(event.index, {
|
|
1731
|
+
id: event.id,
|
|
1732
|
+
name: event.name,
|
|
1733
|
+
arguments: event.arguments
|
|
1734
|
+
});
|
|
1735
|
+
} else if (event.type === "message_stop") {
|
|
1736
|
+
finishReason = event.finishReason;
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
const completion = { finishReason };
|
|
1740
|
+
if (content) completion.content = content;
|
|
1741
|
+
if (calls.size) completion.toolCalls = [...calls.values()];
|
|
1742
|
+
return completion;
|
|
1743
|
+
}
|
|
1744
|
+
async function consumeAgentStream(stream) {
|
|
1745
|
+
const iterator = stream[Symbol.asyncIterator]();
|
|
1746
|
+
while (true) {
|
|
1747
|
+
const next = await iterator.next();
|
|
1748
|
+
if (next.done) return next.value;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
var agent = {
|
|
1752
|
+
name: "agent",
|
|
1753
|
+
inject: ["session", "tools"],
|
|
1754
|
+
apply: (ctx, config = {}) => {
|
|
1755
|
+
const service = new AgentService(ctx, config);
|
|
1756
|
+
ctx.provide("agent", service);
|
|
1757
|
+
ctx.provide("agentLoop", (input, options) => service.run(input, options));
|
|
1758
|
+
ctx.provide("inbox", service.inbox);
|
|
1759
|
+
return () => {
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1763
|
+
var name = "@tnega/agent";
|
|
1764
|
+
|
|
1765
|
+
// packages/agent/src/definition.ts
|
|
1766
|
+
function resolveSystemPrompt(ctx, system) {
|
|
1767
|
+
if (!system) return void 0;
|
|
1768
|
+
return typeof system === "function" ? system(ctx) : system;
|
|
1769
|
+
}
|
|
1770
|
+
function withSystemPrompt(input, system) {
|
|
1771
|
+
if (!system) return input;
|
|
1772
|
+
const messages = [
|
|
1773
|
+
{ role: "system", content: system },
|
|
1774
|
+
...input?.messages ?? []
|
|
1775
|
+
];
|
|
1776
|
+
if (input?.text) messages.push({ role: "user", content: input.text });
|
|
1777
|
+
return { ...input ?? {}, messages };
|
|
1778
|
+
}
|
|
1779
|
+
function defineAgent(definition) {
|
|
1780
|
+
const name2 = definition.name.trim();
|
|
1781
|
+
if (!name2) throw new TypeError("agent definition requires a non-empty name");
|
|
1782
|
+
return {
|
|
1783
|
+
name: `agent:${name2}`,
|
|
1784
|
+
inject: ["session", "tools"],
|
|
1785
|
+
apply: async (ctx, config = {}) => {
|
|
1786
|
+
const dynamic = ctx;
|
|
1787
|
+
const system = resolveSystemPrompt(ctx, definition.system);
|
|
1788
|
+
let agentService;
|
|
1789
|
+
if (definition.loop) {
|
|
1790
|
+
const loop = async (input, options) => {
|
|
1791
|
+
await definition.hooks?.beforeRun?.(input ?? {}, options ?? {});
|
|
1792
|
+
const result = await definition.loop(
|
|
1793
|
+
withSystemPrompt(input, system),
|
|
1794
|
+
options
|
|
1795
|
+
);
|
|
1796
|
+
await definition.hooks?.afterRun?.(result, options ?? {});
|
|
1797
|
+
return result;
|
|
1798
|
+
};
|
|
1799
|
+
ctx.provide("agentLoop", loop);
|
|
1800
|
+
} else {
|
|
1801
|
+
const agentConfig = {};
|
|
1802
|
+
if (definition.hooks) agentConfig.hooks = definition.hooks;
|
|
1803
|
+
if (config.llm) agentConfig.llm = config.llm;
|
|
1804
|
+
if (config.maxTurns !== void 0) agentConfig.maxTurns = config.maxTurns;
|
|
1805
|
+
if (config.maxSteps !== void 0) agentConfig.maxSteps = config.maxSteps;
|
|
1806
|
+
if (config.inbox) agentConfig.inbox = config.inbox;
|
|
1807
|
+
if (config.contextBudget) agentConfig.contextBudget = config.contextBudget;
|
|
1808
|
+
const agentFiber = ctx.plugin(agent, agentConfig);
|
|
1809
|
+
await agentFiber;
|
|
1810
|
+
agentService = ctx.reflect.get("agent");
|
|
1811
|
+
if (system) agentService.inbox.inject("agentSystem", system);
|
|
1812
|
+
}
|
|
1813
|
+
if (system) {
|
|
1814
|
+
const prompt = ctx.reflect.get("systemPrompt");
|
|
1815
|
+
if (prompt) {
|
|
1816
|
+
ctx.fiber.effect(() => prompt.registerSection({
|
|
1817
|
+
name: `${name2}:persona`,
|
|
1818
|
+
content: system
|
|
1819
|
+
}), `ctx.systemPrompt.section(${JSON.stringify(name2)})`);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
for (const tool of definition.tools ?? []) {
|
|
1823
|
+
dynamic.tools.register(tool);
|
|
1824
|
+
}
|
|
1825
|
+
ctx.provide("agentDefinition", definition);
|
|
1826
|
+
ctx.provide("agentSystem", system);
|
|
1827
|
+
ctx.emit("agent/definition", {
|
|
1828
|
+
name: name2,
|
|
1829
|
+
version: definition.version,
|
|
1830
|
+
system,
|
|
1831
|
+
toolCount: (definition.tools ?? []).length
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
};
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
// packages/agent/src/live.ts
|
|
1838
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
1839
|
+
|
|
1840
|
+
// packages/agent/src/inbox-durable.ts
|
|
1841
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
1842
|
+
function contentOf(input) {
|
|
1843
|
+
return input.text ?? "";
|
|
1844
|
+
}
|
|
1845
|
+
function payloadOf(input) {
|
|
1846
|
+
return input.content !== void 0 ? input.content : void 0;
|
|
1847
|
+
}
|
|
1848
|
+
var DurableInbox = class _DurableInbox {
|
|
1849
|
+
constructor(_session) {
|
|
1850
|
+
this._session = _session;
|
|
1851
|
+
}
|
|
1852
|
+
_session;
|
|
1853
|
+
_nextTurn = [];
|
|
1854
|
+
_nextStep = [];
|
|
1855
|
+
get size() {
|
|
1856
|
+
return this._nextTurn.length + this._nextStep.length;
|
|
1857
|
+
}
|
|
1858
|
+
async insert(input, target = "next-turn") {
|
|
1859
|
+
const message = {
|
|
1860
|
+
id: randomUUID3(),
|
|
1861
|
+
...input.text !== void 0 ? { text: input.text } : {},
|
|
1862
|
+
...input.content !== void 0 ? { content: input.content } : {}
|
|
1863
|
+
};
|
|
1864
|
+
const list = target === "next-step" ? this._nextStep : this._nextTurn;
|
|
1865
|
+
list.push(message);
|
|
1866
|
+
await this._session.append("agent/inbox/spliced", {
|
|
1867
|
+
target,
|
|
1868
|
+
index: list.length - 1,
|
|
1869
|
+
inserted: [{
|
|
1870
|
+
id: message.id,
|
|
1871
|
+
content: contentOf(message),
|
|
1872
|
+
...payloadOf(message) !== void 0 ? { payload: payloadOf(message) } : {}
|
|
1873
|
+
}]
|
|
1874
|
+
});
|
|
1875
|
+
return message;
|
|
1876
|
+
}
|
|
1877
|
+
async steer(input) {
|
|
1878
|
+
const message = {
|
|
1879
|
+
id: randomUUID3(),
|
|
1880
|
+
...input.text !== void 0 ? { text: input.text } : {},
|
|
1881
|
+
...input.content !== void 0 ? { content: input.content } : {}
|
|
1882
|
+
};
|
|
1883
|
+
this._nextStep.unshift(message);
|
|
1884
|
+
await this._session.append("agent/inbox/spliced", {
|
|
1885
|
+
target: "next-step",
|
|
1886
|
+
index: 0,
|
|
1887
|
+
inserted: [{
|
|
1888
|
+
id: message.id,
|
|
1889
|
+
content: contentOf(message),
|
|
1890
|
+
...payloadOf(message) !== void 0 ? { payload: payloadOf(message) } : {},
|
|
1891
|
+
mode: "steer"
|
|
1892
|
+
}]
|
|
1893
|
+
});
|
|
1894
|
+
return message;
|
|
1895
|
+
}
|
|
1896
|
+
async claim() {
|
|
1897
|
+
const steered = this._nextStep.shift();
|
|
1898
|
+
if (steered) {
|
|
1899
|
+
await this._session.append("agent/inbox/spliced", {
|
|
1900
|
+
target: "next-step",
|
|
1901
|
+
index: 0,
|
|
1902
|
+
deleteCount: 1
|
|
1903
|
+
});
|
|
1904
|
+
return steered;
|
|
1905
|
+
}
|
|
1906
|
+
const next = this._nextTurn.shift();
|
|
1907
|
+
if (next) {
|
|
1908
|
+
await this._session.append("agent/inbox/spliced", {
|
|
1909
|
+
target: "next-turn",
|
|
1910
|
+
index: 0,
|
|
1911
|
+
deleteCount: 1
|
|
1912
|
+
});
|
|
1913
|
+
return next;
|
|
1914
|
+
}
|
|
1915
|
+
return void 0;
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Claim the complete proposed step batch: every pending next-step input plus
|
|
1919
|
+
* one next-turn message at a turn boundary. Returns an empty array when
|
|
1920
|
+
* nothing is pending.
|
|
1921
|
+
*/
|
|
1922
|
+
async claimBatch() {
|
|
1923
|
+
const claimed = [];
|
|
1924
|
+
if (this._nextStep.length) {
|
|
1925
|
+
const removed = this._nextStep.splice(0);
|
|
1926
|
+
claimed.push(...removed);
|
|
1927
|
+
await this._session.append("agent/inbox/spliced", {
|
|
1928
|
+
target: "next-step",
|
|
1929
|
+
index: 0,
|
|
1930
|
+
deleteCount: removed.length
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
const nextTurn = this._nextTurn.shift();
|
|
1934
|
+
if (nextTurn) {
|
|
1935
|
+
claimed.push(nextTurn);
|
|
1936
|
+
await this._session.append("agent/inbox/spliced", {
|
|
1937
|
+
target: "next-turn",
|
|
1938
|
+
index: 0,
|
|
1939
|
+
deleteCount: 1
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
return claimed;
|
|
1943
|
+
}
|
|
1944
|
+
async clear() {
|
|
1945
|
+
if (this._nextTurn.length) {
|
|
1946
|
+
this._nextTurn = [];
|
|
1947
|
+
await this._session.append("agent/inbox/spliced", {
|
|
1948
|
+
target: "next-turn",
|
|
1949
|
+
index: 0,
|
|
1950
|
+
deleteCount: Number.POSITIVE_INFINITY
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
if (this._nextStep.length) {
|
|
1954
|
+
this._nextStep = [];
|
|
1955
|
+
await this._session.append("agent/inbox/spliced", {
|
|
1956
|
+
target: "next-step",
|
|
1957
|
+
index: 0,
|
|
1958
|
+
deleteCount: Number.POSITIVE_INFINITY
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
/** Insert a message at an explicit target/index boundary. */
|
|
1963
|
+
async insertAt(input, target, index, mode = "followup") {
|
|
1964
|
+
const message = {
|
|
1965
|
+
id: randomUUID3(),
|
|
1966
|
+
...input.text !== void 0 ? { text: input.text } : {},
|
|
1967
|
+
...input.content !== void 0 ? { content: input.content } : {}
|
|
1968
|
+
};
|
|
1969
|
+
const list = target === "next-step" ? this._nextStep : this._nextTurn;
|
|
1970
|
+
const safeIndex = Math.max(0, Math.min(index, list.length));
|
|
1971
|
+
list.splice(safeIndex, 0, message);
|
|
1972
|
+
await this._session.append("agent/inbox/spliced", {
|
|
1973
|
+
target,
|
|
1974
|
+
index: safeIndex,
|
|
1975
|
+
inserted: [{
|
|
1976
|
+
id: message.id,
|
|
1977
|
+
content: contentOf(message),
|
|
1978
|
+
...payloadOf(message) !== void 0 ? { payload: payloadOf(message) } : {},
|
|
1979
|
+
mode
|
|
1980
|
+
}]
|
|
1981
|
+
});
|
|
1982
|
+
return message;
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Replace one pending message by id. The old message is emitted as
|
|
1986
|
+
* discarded and the new one as inserted, matching the DSH inbox contract.
|
|
1987
|
+
*/
|
|
1988
|
+
async replace(messageId, input) {
|
|
1989
|
+
const entry = this._find(messageId);
|
|
1990
|
+
if (!entry) return void 0;
|
|
1991
|
+
const replacement = {
|
|
1992
|
+
id: randomUUID3(),
|
|
1993
|
+
...input.text !== void 0 ? { text: input.text } : {},
|
|
1994
|
+
...input.content !== void 0 ? { content: input.content } : {}
|
|
1995
|
+
};
|
|
1996
|
+
const target = entry.target;
|
|
1997
|
+
const index = entry.index;
|
|
1998
|
+
entry.list.splice(index, 1, replacement);
|
|
1999
|
+
await this._session.append("agent/inbox/spliced", {
|
|
2000
|
+
target,
|
|
2001
|
+
index,
|
|
2002
|
+
deleteCount: 1,
|
|
2003
|
+
inserted: [{
|
|
2004
|
+
id: replacement.id,
|
|
2005
|
+
content: contentOf(replacement),
|
|
2006
|
+
...payloadOf(replacement) !== void 0 ? { payload: payloadOf(replacement) } : {}
|
|
2007
|
+
}]
|
|
2008
|
+
});
|
|
2009
|
+
return replacement;
|
|
2010
|
+
}
|
|
2011
|
+
/** Remove one pending message by id. */
|
|
2012
|
+
async remove(messageId) {
|
|
2013
|
+
const entry = this._find(messageId);
|
|
2014
|
+
if (!entry) return void 0;
|
|
2015
|
+
entry.list.splice(entry.index, 1);
|
|
2016
|
+
await this._session.append("agent/inbox/spliced", {
|
|
2017
|
+
target: entry.target,
|
|
2018
|
+
index: entry.index,
|
|
2019
|
+
deleteCount: 1
|
|
2020
|
+
});
|
|
2021
|
+
return entry.message;
|
|
2022
|
+
}
|
|
2023
|
+
/** Return the first pending message by id across both queues. */
|
|
2024
|
+
get(messageId) {
|
|
2025
|
+
return this._find(messageId)?.message;
|
|
2026
|
+
}
|
|
2027
|
+
snapshot() {
|
|
2028
|
+
return {
|
|
2029
|
+
nextTurn: [...this._nextTurn],
|
|
2030
|
+
nextStep: [...this._nextStep]
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
_find(messageId) {
|
|
2034
|
+
for (const target of ["next-step", "next-turn"]) {
|
|
2035
|
+
const list = target === "next-step" ? this._nextStep : this._nextTurn;
|
|
2036
|
+
const index = list.findIndex((message) => message.id === messageId);
|
|
2037
|
+
if (index >= 0) return { list, target, index, message: list[index] };
|
|
2038
|
+
}
|
|
2039
|
+
return void 0;
|
|
2040
|
+
}
|
|
2041
|
+
static async restore(session) {
|
|
2042
|
+
const inbox = new _DurableInbox(session);
|
|
2043
|
+
inbox._applyAll(await session.read());
|
|
2044
|
+
return inbox;
|
|
2045
|
+
}
|
|
2046
|
+
_applyAll(events) {
|
|
2047
|
+
for (const event of events) {
|
|
2048
|
+
if (event.type !== "agent/inbox/spliced") continue;
|
|
2049
|
+
const payload = event.payload;
|
|
2050
|
+
const list = payload.target === "next-step" ? this._nextStep : this._nextTurn;
|
|
2051
|
+
const count = payload.deleteCount ?? 0;
|
|
2052
|
+
if (count === Number.POSITIVE_INFINITY) {
|
|
2053
|
+
list.length = 0;
|
|
2054
|
+
} else if (count > 0 && payload.index === 0) {
|
|
2055
|
+
list.splice(0, Math.min(count, list.length));
|
|
2056
|
+
} else if (count > 0 && payload.index !== void 0) {
|
|
2057
|
+
list.splice(payload.index, Math.min(count, list.length - payload.index));
|
|
2058
|
+
}
|
|
2059
|
+
let insertedOffset = 0;
|
|
2060
|
+
for (const item of payload.inserted ?? []) {
|
|
2061
|
+
const message = {
|
|
2062
|
+
id: item.id,
|
|
2063
|
+
...item.content !== void 0 ? { text: item.content } : {},
|
|
2064
|
+
...item.payload !== void 0 ? { content: item.payload } : {}
|
|
2065
|
+
};
|
|
2066
|
+
if (item.mode === "steer") {
|
|
2067
|
+
list.unshift(message);
|
|
2068
|
+
} else {
|
|
2069
|
+
const at = payload.index === void 0 ? list.length : Math.min(list.length, payload.index + insertedOffset);
|
|
2070
|
+
list.splice(at, 0, message);
|
|
2071
|
+
insertedOffset += 1;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
2077
|
+
|
|
2078
|
+
// packages/agent/src/live.ts
|
|
2079
|
+
var LiveAgentImpl = class {
|
|
2080
|
+
constructor(_ctx, _agentCtx, _service, id, inbox, session, durable, meta, manualStreaming = false) {
|
|
2081
|
+
this._ctx = _ctx;
|
|
2082
|
+
this._agentCtx = _agentCtx;
|
|
2083
|
+
this._service = _service;
|
|
2084
|
+
this.id = id;
|
|
2085
|
+
this.inbox = inbox;
|
|
2086
|
+
this.session = session;
|
|
2087
|
+
this.meta = meta;
|
|
2088
|
+
this._durable = durable;
|
|
2089
|
+
this._manualStreaming = manualStreaming;
|
|
2090
|
+
}
|
|
2091
|
+
_ctx;
|
|
2092
|
+
_agentCtx;
|
|
2093
|
+
_service;
|
|
2094
|
+
id;
|
|
2095
|
+
inbox;
|
|
2096
|
+
session;
|
|
2097
|
+
meta;
|
|
2098
|
+
_status = "idle";
|
|
2099
|
+
_disposed = false;
|
|
2100
|
+
_controller;
|
|
2101
|
+
_drainPromise;
|
|
2102
|
+
_busy = false;
|
|
2103
|
+
_writeTail = Promise.resolve();
|
|
2104
|
+
_pendingWrite;
|
|
2105
|
+
_sessionStarted = false;
|
|
2106
|
+
_maintenance;
|
|
2107
|
+
_maintenanceBusy = false;
|
|
2108
|
+
_setupDisposers = [];
|
|
2109
|
+
_scopeClosed = false;
|
|
2110
|
+
_durable;
|
|
2111
|
+
_agentSystem;
|
|
2112
|
+
_manualStreaming = false;
|
|
2113
|
+
get agentType() {
|
|
2114
|
+
return this.meta.agentType;
|
|
2115
|
+
}
|
|
2116
|
+
get mode() {
|
|
2117
|
+
return this.meta.mode;
|
|
2118
|
+
}
|
|
2119
|
+
get owner() {
|
|
2120
|
+
return this.meta.owner;
|
|
2121
|
+
}
|
|
2122
|
+
get parentSessionId() {
|
|
2123
|
+
return this.meta.parentSessionId;
|
|
2124
|
+
}
|
|
2125
|
+
get agentCtx() {
|
|
2126
|
+
return this._agentCtx;
|
|
2127
|
+
}
|
|
2128
|
+
trackSetupDispose(dispose) {
|
|
2129
|
+
if (this._scopeClosed) void dispose();
|
|
2130
|
+
else this._setupDisposers.push(dispose);
|
|
2131
|
+
}
|
|
2132
|
+
get status() {
|
|
2133
|
+
return this._status;
|
|
2134
|
+
}
|
|
2135
|
+
get ctx() {
|
|
2136
|
+
return this._ctx;
|
|
2137
|
+
}
|
|
2138
|
+
followup(input) {
|
|
2139
|
+
this._send(input, "followup");
|
|
2140
|
+
}
|
|
2141
|
+
/** Replace one pending inbox message by durable message id. */
|
|
2142
|
+
replaceMessage(messageId, input) {
|
|
2143
|
+
this._mutatePending(async () => {
|
|
2144
|
+
const previous = this._durable.get(messageId);
|
|
2145
|
+
if (!previous) return;
|
|
2146
|
+
const replacement = await this._durable.replace(messageId, {
|
|
2147
|
+
text: input.text ?? "",
|
|
2148
|
+
...input.messages !== void 0 || input.context !== void 0 ? { content: input.messages ?? input.context } : {}
|
|
2149
|
+
});
|
|
2150
|
+
if (!replacement) return;
|
|
2151
|
+
this._ctx.emit("agent/inbox/discarded", {
|
|
2152
|
+
id: this.id,
|
|
2153
|
+
agent: this,
|
|
2154
|
+
message: previous
|
|
2155
|
+
});
|
|
2156
|
+
this._ctx.emit("agent/inbox/inserted", {
|
|
2157
|
+
id: this.id,
|
|
2158
|
+
agent: this,
|
|
2159
|
+
target: this._findTarget(messageId) ?? "next-turn",
|
|
2160
|
+
input: replacement,
|
|
2161
|
+
inboxSeq: this._durable.size
|
|
2162
|
+
});
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
/** Remove one pending inbox message by durable message id. */
|
|
2166
|
+
removeMessage(messageId) {
|
|
2167
|
+
this._mutatePending(async () => {
|
|
2168
|
+
const removed = await this._durable.remove(messageId);
|
|
2169
|
+
if (!removed) return;
|
|
2170
|
+
this._ctx.emit("agent/inbox/discarded", {
|
|
2171
|
+
id: this.id,
|
|
2172
|
+
agent: this,
|
|
2173
|
+
message: removed
|
|
2174
|
+
});
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
steer(input) {
|
|
2178
|
+
this._send(input, "steer");
|
|
2179
|
+
}
|
|
2180
|
+
inject(key, value) {
|
|
2181
|
+
if (key === "agentSystem" && typeof value === "string") {
|
|
2182
|
+
this._agentSystem = value;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
send(input, options = {}) {
|
|
2186
|
+
this._send(input, options.mode === "steer" ? "steer" : "followup");
|
|
2187
|
+
}
|
|
2188
|
+
_send(input, target) {
|
|
2189
|
+
if (this._disposed) throw new Error(`agent disposed: ${this.id}`);
|
|
2190
|
+
const text = input.text ?? "";
|
|
2191
|
+
const content = input.messages ?? input.context;
|
|
2192
|
+
const task = this._writeTail.then(async () => {
|
|
2193
|
+
const message = target === "steer" ? await this._durable.steer({ text, ...content !== void 0 ? { content } : {} }) : await this._durable.insert({ text, ...content !== void 0 ? { content } : {} });
|
|
2194
|
+
this._ctx.emit("agent/inbox/inserted", {
|
|
2195
|
+
id: this.id,
|
|
2196
|
+
target,
|
|
2197
|
+
input: message,
|
|
2198
|
+
inboxSeq: this._durable.size
|
|
2199
|
+
});
|
|
2200
|
+
}).catch((error) => {
|
|
2201
|
+
this._ctx.emit("agent/error", { id: this.id, error });
|
|
2202
|
+
});
|
|
2203
|
+
this._writeTail = task;
|
|
2204
|
+
this._pendingWrite = task;
|
|
2205
|
+
task.finally(() => {
|
|
2206
|
+
if (this._pendingWrite === task) this._pendingWrite = void 0;
|
|
2207
|
+
queueMicrotask(() => this._wake());
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
_mutatePending(task) {
|
|
2211
|
+
if (this._disposed) throw new Error(`agent disposed: ${this.id}`);
|
|
2212
|
+
const chained = this._writeTail.then(task).catch((error) => {
|
|
2213
|
+
this._ctx.emit("agent/error", { id: this.id, error });
|
|
2214
|
+
});
|
|
2215
|
+
this._writeTail = chained;
|
|
2216
|
+
this._pendingWrite = chained;
|
|
2217
|
+
chained.finally(() => {
|
|
2218
|
+
if (this._pendingWrite === chained) this._pendingWrite = void 0;
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2221
|
+
_findTarget(messageId) {
|
|
2222
|
+
const snapshot = this._durable.snapshot();
|
|
2223
|
+
if (snapshot.nextStep.some((message) => message.id === messageId)) return "next-step";
|
|
2224
|
+
if (snapshot.nextTurn.some((message) => message.id === messageId)) return "next-turn";
|
|
2225
|
+
return void 0;
|
|
2226
|
+
}
|
|
2227
|
+
cancel(cause, options = {}) {
|
|
2228
|
+
if (!options.keepInbox) {
|
|
2229
|
+
const task = this._writeTail.then(async () => {
|
|
2230
|
+
const before = this._durable.snapshot();
|
|
2231
|
+
for (const message of [...before.nextStep, ...before.nextTurn]) {
|
|
2232
|
+
this._ctx.emit("agent/inbox/discarded", { id: this.id, message });
|
|
2233
|
+
}
|
|
2234
|
+
await this._durable.clear();
|
|
2235
|
+
}).catch((error) => {
|
|
2236
|
+
this._ctx.emit("agent/error", { id: this.id, error });
|
|
2237
|
+
});
|
|
2238
|
+
this._writeTail = task;
|
|
2239
|
+
this._pendingWrite = task;
|
|
2240
|
+
task.finally(() => {
|
|
2241
|
+
if (this._pendingWrite === task) this._pendingWrite = void 0;
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
this._controller?.abort(cause);
|
|
2245
|
+
this._maintenance?.controller.abort(cause);
|
|
2246
|
+
}
|
|
2247
|
+
async whenIdle() {
|
|
2248
|
+
while (this._pendingWrite) await this._pendingWrite;
|
|
2249
|
+
while (true) {
|
|
2250
|
+
const maintenance = this._maintenance;
|
|
2251
|
+
if (maintenance) {
|
|
2252
|
+
await maintenance.promise.catch(() => void 0);
|
|
2253
|
+
continue;
|
|
2254
|
+
}
|
|
2255
|
+
const drain = this._drainPromise;
|
|
2256
|
+
if (drain) {
|
|
2257
|
+
await drain.catch(() => void 0);
|
|
2258
|
+
continue;
|
|
2259
|
+
}
|
|
2260
|
+
if (!this._durable.size) break;
|
|
2261
|
+
await new Promise((resolve2) => setTimeout(resolve2, 0));
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
/** Run one non-turn maintenance task from the true idle phase. */
|
|
2265
|
+
runMaintenance(task) {
|
|
2266
|
+
if (this._disposed) throw new Error(`agent disposed: ${this.id}`);
|
|
2267
|
+
if (this._maintenanceBusy || this._drainPromise || this._pendingWrite) {
|
|
2268
|
+
throw new Error("agent is busy; maintenance can only run while idle");
|
|
2269
|
+
}
|
|
2270
|
+
this._maintenanceBusy = true;
|
|
2271
|
+
const controller = new AbortController();
|
|
2272
|
+
const result = Promise.resolve().then(async () => {
|
|
2273
|
+
const value = await task(controller.signal);
|
|
2274
|
+
return value;
|
|
2275
|
+
}).finally(() => {
|
|
2276
|
+
this._maintenanceBusy = false;
|
|
2277
|
+
if (this._maintenance?.controller === controller) {
|
|
2278
|
+
this._maintenance = void 0;
|
|
2279
|
+
}
|
|
2280
|
+
queueMicrotask(() => this._wake());
|
|
2281
|
+
});
|
|
2282
|
+
this._maintenance = { promise: result.then(() => void 0, () => void 0), controller };
|
|
2283
|
+
return result;
|
|
2284
|
+
}
|
|
2285
|
+
async dispose() {
|
|
2286
|
+
if (this._disposed) return;
|
|
2287
|
+
this._disposed = true;
|
|
2288
|
+
this.cancel({ type: "disposed" });
|
|
2289
|
+
await this._writeTail.catch(() => void 0);
|
|
2290
|
+
await this._drainPromise?.catch(() => void 0);
|
|
2291
|
+
if (this._maintenance) this._maintenance.controller.abort({ type: "disposed" });
|
|
2292
|
+
await this._maintenance?.promise.catch(() => void 0);
|
|
2293
|
+
if (!this._scopeClosed) {
|
|
2294
|
+
this._scopeClosed = true;
|
|
2295
|
+
await Promise.all([...this._setupDisposers].reverse().map((dispose) => dispose()));
|
|
2296
|
+
}
|
|
2297
|
+
const writeTail = this._writeTail;
|
|
2298
|
+
if (writeTail !== this._drainPromise && this._pendingWrite) {
|
|
2299
|
+
this._pendingWrite = void 0;
|
|
2300
|
+
await writeTail.catch(() => void 0);
|
|
2301
|
+
}
|
|
2302
|
+
this._setStatus("idle");
|
|
2303
|
+
}
|
|
2304
|
+
/** Publish the agent lifecycle session-start notification once. */
|
|
2305
|
+
async publishSessionStart(source = "startup") {
|
|
2306
|
+
if (this._sessionStarted) return;
|
|
2307
|
+
this._sessionStarted = true;
|
|
2308
|
+
this._ctx.emit("agent/session-start", {
|
|
2309
|
+
id: this.id,
|
|
2310
|
+
agent: this,
|
|
2311
|
+
source
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
/** Current durable turn number for a newly claimed inbox item. */
|
|
2315
|
+
async _nextTurnNumber() {
|
|
2316
|
+
const events = await this.session.read();
|
|
2317
|
+
let max = 0;
|
|
2318
|
+
for (const event of events) {
|
|
2319
|
+
if (event.type === "turn/start") max = Math.max(max, event.payload.turn);
|
|
2320
|
+
}
|
|
2321
|
+
return max + 1;
|
|
2322
|
+
}
|
|
2323
|
+
/** Start draining currently pending durable work without a new input. */
|
|
2324
|
+
wakeNow() {
|
|
2325
|
+
this._wake();
|
|
2326
|
+
}
|
|
2327
|
+
_wake() {
|
|
2328
|
+
if (this._disposed || this._manualStreaming || this._drainPromise || this._busy || this._maintenanceBusy) return;
|
|
2329
|
+
const task = this._drainAll();
|
|
2330
|
+
this._drainPromise = task;
|
|
2331
|
+
task.finally(() => {
|
|
2332
|
+
if (this._drainPromise === task) this._drainPromise = void 0;
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
async _drainAll() {
|
|
2336
|
+
await this._writeTail.catch(() => void 0);
|
|
2337
|
+
if (this._durable.size) {
|
|
2338
|
+
for await (const _ of this._streamTurns()) void _;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
/** Public drain over queued durable work (manual-streaming mode). */
|
|
2342
|
+
async *runTurns(signal) {
|
|
2343
|
+
if (this._manualStreaming) await this._writeTail.catch(() => void 0);
|
|
2344
|
+
if (this._busy || this._disposed) return;
|
|
2345
|
+
yield* this._streamTurns(signal);
|
|
2346
|
+
}
|
|
2347
|
+
async *_streamTurns(signal) {
|
|
2348
|
+
if (this._busy || this._disposed) return;
|
|
2349
|
+
this._busy = true;
|
|
2350
|
+
this._setStatus("running");
|
|
2351
|
+
try {
|
|
2352
|
+
while (!this._disposed) {
|
|
2353
|
+
const batch = await this._durable.claimBatch();
|
|
2354
|
+
if (!batch.length) break;
|
|
2355
|
+
const input = await this._inputForBatch(batch);
|
|
2356
|
+
const turn = await this._nextTurnNumber();
|
|
2357
|
+
for (const message of batch) {
|
|
2358
|
+
this._ctx.emit("agent/inbox/claimed", {
|
|
2359
|
+
id: this.id,
|
|
2360
|
+
agent: this,
|
|
2361
|
+
message,
|
|
2362
|
+
input: { text: message.text ?? "" },
|
|
2363
|
+
turn
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
const controller = new AbortController();
|
|
2367
|
+
this._controller = controller;
|
|
2368
|
+
const forward = () => controller.abort(signal?.reason);
|
|
2369
|
+
if (signal?.aborted) controller.abort(signal.reason);
|
|
2370
|
+
else signal?.addEventListener("abort", forward, { once: true });
|
|
2371
|
+
try {
|
|
2372
|
+
if (this._manualStreaming) {
|
|
2373
|
+
yield* this._service.runStream(input, { signal: controller.signal });
|
|
2374
|
+
} else {
|
|
2375
|
+
await this._service.run(input, { signal: controller.signal });
|
|
2376
|
+
}
|
|
2377
|
+
} catch (error) {
|
|
2378
|
+
if (!controller.signal.aborted) {
|
|
2379
|
+
const step = await this._failedStep();
|
|
2380
|
+
this._ctx.emit("agent/error", {
|
|
2381
|
+
id: this.id,
|
|
2382
|
+
agent: this,
|
|
2383
|
+
error,
|
|
2384
|
+
...step !== void 0 ? { turn, step } : {}
|
|
2385
|
+
});
|
|
2386
|
+
if (this._manualStreaming) throw error;
|
|
2387
|
+
}
|
|
2388
|
+
} finally {
|
|
2389
|
+
signal?.removeEventListener("abort", forward);
|
|
2390
|
+
if (this._controller === controller) this._controller = void 0;
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
} finally {
|
|
2394
|
+
this._busy = false;
|
|
2395
|
+
this._setStatus("idle");
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
async _inputForBatch(batch) {
|
|
2399
|
+
const history = await this.session.deriveMessages();
|
|
2400
|
+
const structured = batch.filter((message) => message.content !== void 0);
|
|
2401
|
+
const plain = batch.filter((message) => message.content === void 0);
|
|
2402
|
+
const plainMessages = plain.filter((message) => message.text !== void 0).map((message) => ({ role: "user", content: message.text ?? "" }));
|
|
2403
|
+
if (structured.length) {
|
|
2404
|
+
return {
|
|
2405
|
+
messages: [
|
|
2406
|
+
...history,
|
|
2407
|
+
...structured.flatMap((message) => Array.isArray(message.content) ? message.content : [{ role: "user", content: String(message.content ?? "") }])
|
|
2408
|
+
]
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
if (history.length || batch.length > 1) {
|
|
2412
|
+
return { messages: [...history, ...plainMessages] };
|
|
2413
|
+
}
|
|
2414
|
+
return { text: plainMessages[0]?.content ?? "" };
|
|
2415
|
+
}
|
|
2416
|
+
_setStatus(status) {
|
|
2417
|
+
const previous = this._status;
|
|
2418
|
+
if (previous === status) return;
|
|
2419
|
+
this._status = status;
|
|
2420
|
+
this._ctx.emit("agent/status", { id: this.id, status, previous });
|
|
2421
|
+
}
|
|
2422
|
+
async _failedStep() {
|
|
2423
|
+
const events = await this.session.read();
|
|
2424
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
2425
|
+
const event = events[index];
|
|
2426
|
+
if (event?.type === "step/end" && (event.payload.finishReason === "error" || event.payload.interrupted === true)) {
|
|
2427
|
+
return event.payload.step;
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
return void 0;
|
|
2431
|
+
}
|
|
2432
|
+
};
|
|
2433
|
+
var AgentRegistry = class {
|
|
2434
|
+
constructor(_ctx) {
|
|
2435
|
+
this._ctx = _ctx;
|
|
2436
|
+
}
|
|
2437
|
+
_ctx;
|
|
2438
|
+
_agents = /* @__PURE__ */ new Map();
|
|
2439
|
+
_owners = /* @__PURE__ */ new Map();
|
|
2440
|
+
_factory;
|
|
2441
|
+
setFactory(factory) {
|
|
2442
|
+
this._factory = factory;
|
|
2443
|
+
return () => {
|
|
2444
|
+
if (this._factory === factory) this._factory = void 0;
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
async create(options) {
|
|
2448
|
+
if (!this._factory) throw new Error("no agent factory registered");
|
|
2449
|
+
const id = options.id ?? options.sessionId ?? randomUUID4();
|
|
2450
|
+
if (this._agents.has(id)) throw new Error(`agent already registered: ${id}`);
|
|
2451
|
+
const handle = await this._factory.create({ ...options, id });
|
|
2452
|
+
this._agents.set(id, handle.agent);
|
|
2453
|
+
this._owners.set(id, handle.agent.owner);
|
|
2454
|
+
this._ctx.emit("agent/created", { id, agent: handle.agent });
|
|
2455
|
+
return {
|
|
2456
|
+
agent: handle.agent,
|
|
2457
|
+
dispose: () => this._dispose(id, handle)
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
2460
|
+
async resume(options) {
|
|
2461
|
+
if (!this._factory) throw new Error("no agent factory registered");
|
|
2462
|
+
if (!options.id && !options.sessionId) {
|
|
2463
|
+
throw new AgentError("agent resume requires a stable session id");
|
|
2464
|
+
}
|
|
2465
|
+
const id = options.id ?? options.sessionId;
|
|
2466
|
+
if (this._agents.has(id)) throw new Error(`agent already registered: ${id}`);
|
|
2467
|
+
const handle = await this._factory.resume({ ...options, id });
|
|
2468
|
+
this._agents.set(id, handle.agent);
|
|
2469
|
+
this._owners.set(id, handle.agent.owner);
|
|
2470
|
+
this._ctx.emit("agent/created", { id, agent: handle.agent });
|
|
2471
|
+
return {
|
|
2472
|
+
agent: handle.agent,
|
|
2473
|
+
dispose: () => this._dispose(id, handle)
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
get(id) {
|
|
2477
|
+
return this._agents.get(id);
|
|
2478
|
+
}
|
|
2479
|
+
list() {
|
|
2480
|
+
return [...this._agents.values()];
|
|
2481
|
+
}
|
|
2482
|
+
roots() {
|
|
2483
|
+
return this.list().filter((agent2) => this._owners.get(agent2.id) === void 0);
|
|
2484
|
+
}
|
|
2485
|
+
isOwnedBy(id, owner) {
|
|
2486
|
+
return this._owners.get(id) === owner;
|
|
2487
|
+
}
|
|
2488
|
+
async _dispose(id, handle) {
|
|
2489
|
+
const agent2 = this._agents.get(id);
|
|
2490
|
+
if (!agent2) return;
|
|
2491
|
+
await handle.dispose();
|
|
2492
|
+
this._agents.delete(id);
|
|
2493
|
+
this._owners.delete(id);
|
|
2494
|
+
this._ctx.emit("agent/disposed", { id });
|
|
2495
|
+
}
|
|
2496
|
+
};
|
|
2497
|
+
async function buildHandle(ctx, options, resume = false) {
|
|
2498
|
+
const fileMeta = await readAgentMeta(options.file);
|
|
2499
|
+
if (resume && !fileMeta) {
|
|
2500
|
+
throw new AgentError(`no agent session meta found for resume: ${options.file}`);
|
|
2501
|
+
}
|
|
2502
|
+
const requestedId = options.id ?? options.sessionId ?? randomUUID4();
|
|
2503
|
+
if (fileMeta?.agentId && fileMeta.agentId !== requestedId) {
|
|
2504
|
+
throw new AgentError(
|
|
2505
|
+
`agent id mismatch: session belongs to ${fileMeta.agentId}, requested ${requestedId}`
|
|
2506
|
+
);
|
|
2507
|
+
}
|
|
2508
|
+
const agentId = fileMeta?.agentId ?? requestedId;
|
|
2509
|
+
if (!agentId) throw new AgentError("agent requires a stable identity");
|
|
2510
|
+
const log = new SessionLog(
|
|
2511
|
+
options.file,
|
|
2512
|
+
(type, payload) => {
|
|
2513
|
+
if (type === "event") ctx.emit("session/event", payload);
|
|
2514
|
+
else ctx.emit("session/flush", payload);
|
|
2515
|
+
}
|
|
2516
|
+
);
|
|
2517
|
+
await log.init();
|
|
2518
|
+
const fileEvents = await log.read();
|
|
2519
|
+
const durableMeta = readDurableAgentMeta(fileEvents);
|
|
2520
|
+
if (durableMeta) {
|
|
2521
|
+
if (durableMeta.agentId !== agentId) {
|
|
2522
|
+
throw new AgentError(
|
|
2523
|
+
`agent id mismatch: session belongs to ${durableMeta.agentId}, requested ${agentId}`
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2526
|
+
if (options.agentType && durableMeta.agentType !== options.agentType) {
|
|
2527
|
+
throw new AgentError(
|
|
2528
|
+
`agent type mismatch: session is ${durableMeta.agentType ?? "untyped"}, requested ${options.agentType}`
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
if (options.mode && durableMeta.mode !== options.mode) {
|
|
2532
|
+
throw new AgentError(
|
|
2533
|
+
`agent mode mismatch: session is ${durableMeta.mode ?? "unset"}, requested ${options.mode}`
|
|
2534
|
+
);
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
const boundMeta = {
|
|
2538
|
+
...fileMeta,
|
|
2539
|
+
...durableMeta ?? {},
|
|
2540
|
+
...options.title ? { title: options.title } : {},
|
|
2541
|
+
...options.owner ? { owner: options.owner } : {},
|
|
2542
|
+
...options.parentSessionId ? { parentSessionId: options.parentSessionId } : {},
|
|
2543
|
+
...options.forkedAtMessageId ? { forkedAtMessageId: options.forkedAtMessageId } : {},
|
|
2544
|
+
agentId
|
|
2545
|
+
};
|
|
2546
|
+
const resolvedAgentType = options.agentType ?? durableMeta?.agentType;
|
|
2547
|
+
if (resolvedAgentType) boundMeta.agentType = resolvedAgentType;
|
|
2548
|
+
const resolvedMode = options.mode ?? durableMeta?.mode;
|
|
2549
|
+
if (resolvedMode) boundMeta.mode = resolvedMode;
|
|
2550
|
+
if (!durableMeta) {
|
|
2551
|
+
await log.append("meta", {
|
|
2552
|
+
kind: "agent",
|
|
2553
|
+
agentId,
|
|
2554
|
+
...boundMeta.agentType ? { agentType: boundMeta.agentType } : {},
|
|
2555
|
+
...boundMeta.mode ? { mode: boundMeta.mode } : {},
|
|
2556
|
+
...boundMeta.title ? { title: boundMeta.title } : {},
|
|
2557
|
+
...boundMeta.owner ? { owner: boundMeta.owner } : {},
|
|
2558
|
+
...boundMeta.parentSessionId ? { parentSessionId: boundMeta.parentSessionId } : {},
|
|
2559
|
+
...boundMeta.forkedAtMessageId ? { forkedAtMessageId: boundMeta.forkedAtMessageId } : {},
|
|
2560
|
+
...boundMeta.createdAt !== void 0 ? { createdAt: boundMeta.createdAt } : {}
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
const durable = resume ? await DurableInbox.restore(log) : new DurableInbox(log);
|
|
2564
|
+
const injected = new AgentInbox();
|
|
2565
|
+
const inboxView = {
|
|
2566
|
+
get size() {
|
|
2567
|
+
return durable.size;
|
|
2568
|
+
},
|
|
2569
|
+
snapshot: () => durable.snapshot()
|
|
2570
|
+
};
|
|
2571
|
+
const service = new AgentService(ctx, {
|
|
2572
|
+
session: log,
|
|
2573
|
+
inbox: injected,
|
|
2574
|
+
...options.llm ? { llm: options.llm } : {},
|
|
2575
|
+
...options.maxTurns !== void 0 ? { maxTurns: options.maxTurns } : {},
|
|
2576
|
+
...options.maxSteps !== void 0 ? { maxSteps: options.maxSteps } : {},
|
|
2577
|
+
...options.contextBudget ? { contextBudget: options.contextBudget } : {},
|
|
2578
|
+
...options.hooks ? { hooks: options.hooks } : {}
|
|
2579
|
+
});
|
|
2580
|
+
if (options.system) injected.inject("agentSystem", options.system);
|
|
2581
|
+
const agentScope = await ctx.inject([], (scopeCtx) => {
|
|
2582
|
+
scopeCtx.isolate("agentScope").provide("agentScope", scopeCtx);
|
|
2583
|
+
});
|
|
2584
|
+
await agentScope;
|
|
2585
|
+
const agentCtx = agentScope.ctx;
|
|
2586
|
+
const agent2 = new LiveAgentImpl(
|
|
2587
|
+
ctx,
|
|
2588
|
+
agentCtx,
|
|
2589
|
+
service,
|
|
2590
|
+
agentId,
|
|
2591
|
+
inboxView,
|
|
2592
|
+
log,
|
|
2593
|
+
durable,
|
|
2594
|
+
boundMeta,
|
|
2595
|
+
options.manualStreaming === true
|
|
2596
|
+
);
|
|
2597
|
+
agent2.trackSetupDispose(() => agentScope.dispose());
|
|
2598
|
+
if (options.system) agent2.inject("agentSystem", options.system);
|
|
2599
|
+
let setupResult;
|
|
2600
|
+
try {
|
|
2601
|
+
setupResult = await options.setup?.(agentCtx);
|
|
2602
|
+
setupResult?.commit();
|
|
2603
|
+
} catch (error) {
|
|
2604
|
+
await agent2.dispose().catch(() => void 0);
|
|
2605
|
+
await log.close().catch(() => void 0);
|
|
2606
|
+
throw error;
|
|
2607
|
+
}
|
|
2608
|
+
await agent2.publishSessionStart(resume ? "resume" : "startup");
|
|
2609
|
+
for (const input of options.initial ?? []) {
|
|
2610
|
+
if (!resume) await durable.insert({ text: input.text ?? "" });
|
|
2611
|
+
}
|
|
2612
|
+
if (resume && durable.size && options.manualStreaming !== true) {
|
|
2613
|
+
setTimeout(() => agent2.wakeNow(), 0);
|
|
2614
|
+
}
|
|
2615
|
+
return { agent: agent2, dispose: () => agent2.dispose() };
|
|
2616
|
+
}
|
|
2617
|
+
async function readAgentMeta(file) {
|
|
2618
|
+
const lines = await import("node:fs/promises").then((fs) => fs.readFile(file, "utf8")).catch((error) => {
|
|
2619
|
+
if (error.code === "ENOENT") return void 0;
|
|
2620
|
+
throw error;
|
|
2621
|
+
});
|
|
2622
|
+
if (lines === void 0) return void 0;
|
|
2623
|
+
for (const line of lines.split("\n")) {
|
|
2624
|
+
if (!line.trim()) continue;
|
|
2625
|
+
let record;
|
|
2626
|
+
try {
|
|
2627
|
+
const parsed = JSON.parse(line);
|
|
2628
|
+
record = parsed && typeof parsed === "object" ? parsed : {};
|
|
2629
|
+
} catch {
|
|
2630
|
+
continue;
|
|
2631
|
+
}
|
|
2632
|
+
if (record.type !== "meta") continue;
|
|
2633
|
+
const payload = record.payload;
|
|
2634
|
+
if (!payload || payload.kind !== "agent") continue;
|
|
2635
|
+
return {
|
|
2636
|
+
...typeof payload.agentId === "string" ? { agentId: payload.agentId } : {},
|
|
2637
|
+
...typeof payload.createdAt === "number" ? { createdAt: payload.createdAt } : {}
|
|
2638
|
+
};
|
|
2639
|
+
}
|
|
2640
|
+
return void 0;
|
|
2641
|
+
}
|
|
2642
|
+
function readDurableAgentMeta(events) {
|
|
2643
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
2644
|
+
const event = events[index];
|
|
2645
|
+
if (event?.type !== "meta") continue;
|
|
2646
|
+
const payload = event.payload;
|
|
2647
|
+
if (payload.kind !== "agent") continue;
|
|
2648
|
+
const meta = {
|
|
2649
|
+
agentId: typeof payload.agentId === "string" ? payload.agentId : ""
|
|
2650
|
+
};
|
|
2651
|
+
if (payload.agentType === "general" || payload.agentType === "coding") {
|
|
2652
|
+
meta.agentType = payload.agentType;
|
|
2653
|
+
}
|
|
2654
|
+
if (payload.mode === "auto" || payload.mode === "plan" || payload.mode === "execute") {
|
|
2655
|
+
meta.mode = payload.mode;
|
|
2656
|
+
}
|
|
2657
|
+
if (typeof payload.title === "string") meta.title = payload.title;
|
|
2658
|
+
if (typeof payload.owner === "string") meta.owner = payload.owner;
|
|
2659
|
+
if (typeof payload.parentSessionId === "string") {
|
|
2660
|
+
meta.parentSessionId = payload.parentSessionId;
|
|
2661
|
+
}
|
|
2662
|
+
if (typeof payload.forkedAtMessageId === "string") {
|
|
2663
|
+
meta.forkedAtMessageId = payload.forkedAtMessageId;
|
|
2664
|
+
}
|
|
2665
|
+
if (typeof payload.createdAt === "number") meta.createdAt = payload.createdAt;
|
|
2666
|
+
return meta;
|
|
2667
|
+
}
|
|
2668
|
+
return void 0;
|
|
2669
|
+
}
|
|
2670
|
+
var agents = {
|
|
2671
|
+
name: "agents",
|
|
2672
|
+
inject: ["tools"],
|
|
2673
|
+
apply: (ctx) => {
|
|
2674
|
+
const registry = new AgentRegistry(ctx);
|
|
2675
|
+
ctx.provide("agents", registry);
|
|
2676
|
+
registry.setFactory({
|
|
2677
|
+
create: (options) => buildHandle(ctx, options),
|
|
2678
|
+
resume: (options) => buildHandle(ctx, options, true)
|
|
2679
|
+
});
|
|
2680
|
+
return () => {
|
|
2681
|
+
for (const agent2 of registry.list()) {
|
|
2682
|
+
void agent2.dispose();
|
|
2683
|
+
}
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
};
|
|
2687
|
+
|
|
2688
|
+
// packages/agent/src/prompt.ts
|
|
2689
|
+
var SystemPromptService = class {
|
|
2690
|
+
_sections = /* @__PURE__ */ new Map();
|
|
2691
|
+
_toolProviders = [];
|
|
2692
|
+
_variables = /* @__PURE__ */ new Map();
|
|
2693
|
+
_contexts = [];
|
|
2694
|
+
_ctx;
|
|
2695
|
+
constructor(ctx) {
|
|
2696
|
+
this._ctx = ctx;
|
|
2697
|
+
}
|
|
2698
|
+
registerSection(section) {
|
|
2699
|
+
const name2 = section.name.trim();
|
|
2700
|
+
if (!name2) throw new TypeError("prompt section requires a non-empty name");
|
|
2701
|
+
if (this._sections.has(name2)) {
|
|
2702
|
+
throw new Error(`prompt section already registered: ${name2}`);
|
|
2703
|
+
}
|
|
2704
|
+
this._sections.set(name2, { ...section, name: name2 });
|
|
2705
|
+
this._emitChange();
|
|
2706
|
+
return () => {
|
|
2707
|
+
this._sections.delete(name2);
|
|
2708
|
+
this._emitChange();
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
sections() {
|
|
2712
|
+
return [...this._sections.values()].sort((left, right) => (left.order ?? 0) - (right.order ?? 0));
|
|
2713
|
+
}
|
|
2714
|
+
registerTools(provider) {
|
|
2715
|
+
if (typeof provider !== "function") throw new TypeError("tool provider must be a function");
|
|
2716
|
+
this._toolProviders.push(provider);
|
|
2717
|
+
this._emitChange();
|
|
2718
|
+
return () => {
|
|
2719
|
+
const index = this._toolProviders.indexOf(provider);
|
|
2720
|
+
if (index >= 0) this._toolProviders.splice(index, 1);
|
|
2721
|
+
this._emitChange();
|
|
2722
|
+
};
|
|
2723
|
+
}
|
|
2724
|
+
async toolSchemas(options = {}) {
|
|
2725
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2726
|
+
const tools = [];
|
|
2727
|
+
for (const provider of this._toolProviders) {
|
|
2728
|
+
for (const schema of await provider(options)) {
|
|
2729
|
+
if (!seen.has(schema.name)) {
|
|
2730
|
+
seen.add(schema.name);
|
|
2731
|
+
tools.push(schema);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
return tools;
|
|
2736
|
+
}
|
|
2737
|
+
registerVariable(name2, provider) {
|
|
2738
|
+
const key = name2.trim();
|
|
2739
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(key)) {
|
|
2740
|
+
throw new TypeError(`invalid prompt variable name: ${name2}`);
|
|
2741
|
+
}
|
|
2742
|
+
if (typeof provider !== "function") throw new TypeError("variable provider must be a function");
|
|
2743
|
+
if (this._variables.has(key)) throw new Error(`prompt variable already registered: ${key}`);
|
|
2744
|
+
this._variables.set(key, provider);
|
|
2745
|
+
this._emitChange();
|
|
2746
|
+
return () => {
|
|
2747
|
+
this._variables.delete(key);
|
|
2748
|
+
this._emitChange();
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
registerContext(context) {
|
|
2752
|
+
const name2 = context.name.trim();
|
|
2753
|
+
if (!name2) throw new TypeError("prompt context requires a non-empty name");
|
|
2754
|
+
if (this._contexts.some((entry) => entry.name === name2)) {
|
|
2755
|
+
throw new Error(`prompt context already registered: ${name2}`);
|
|
2756
|
+
}
|
|
2757
|
+
this._contexts.push({ ...context, name: name2 });
|
|
2758
|
+
this._emitChange();
|
|
2759
|
+
return () => {
|
|
2760
|
+
this._contexts = this._contexts.filter((entry) => entry.name !== name2);
|
|
2761
|
+
this._emitChange();
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
async assemble(options = {}, ctx) {
|
|
2765
|
+
const activeCtx = ctx ?? this._ctx;
|
|
2766
|
+
const sections = this.sections();
|
|
2767
|
+
const variables = await this._resolveVariables(options);
|
|
2768
|
+
const renderedSections = sections.map((section) => ({
|
|
2769
|
+
...section,
|
|
2770
|
+
content: renderTemplate(section.content, variables)
|
|
2771
|
+
}));
|
|
2772
|
+
const dynamicContexts = await this._resolveContexts(options);
|
|
2773
|
+
const contextText = dynamicContexts.map((context) => renderTemplate(context.content, variables)).filter(Boolean);
|
|
2774
|
+
const base = {
|
|
2775
|
+
sections: renderedSections,
|
|
2776
|
+
text: [
|
|
2777
|
+
...renderedSections.map((section) => section.content).filter(Boolean),
|
|
2778
|
+
...contextText
|
|
2779
|
+
].join("\n\n"),
|
|
2780
|
+
...Object.keys(variables).length ? { variables } : {},
|
|
2781
|
+
...Object.keys(options).length ? { context: options } : {}
|
|
2782
|
+
};
|
|
2783
|
+
const tools = await this.toolSchemas(options);
|
|
2784
|
+
if (tools.length) base.tools = tools;
|
|
2785
|
+
if (!activeCtx) return base;
|
|
2786
|
+
const resolved = await activeCtx.waterfallAsync(
|
|
2787
|
+
"system-prompt/assemble",
|
|
2788
|
+
base,
|
|
2789
|
+
async (assembly) => assembly
|
|
2790
|
+
);
|
|
2791
|
+
return resolved ?? base;
|
|
2792
|
+
}
|
|
2793
|
+
async _resolveVariables(options) {
|
|
2794
|
+
const result = {};
|
|
2795
|
+
for (const [name2, provider] of this._variables) {
|
|
2796
|
+
const value = await provider(options);
|
|
2797
|
+
if (value !== void 0) result[name2] = value;
|
|
2798
|
+
}
|
|
2799
|
+
return result;
|
|
2800
|
+
}
|
|
2801
|
+
async _resolveContexts(options) {
|
|
2802
|
+
const contexts = this._contexts.slice().sort((left, right) => (left.order ?? 0) - (right.order ?? 0));
|
|
2803
|
+
const result = [];
|
|
2804
|
+
for (const context of contexts) {
|
|
2805
|
+
const value = await context.content(options);
|
|
2806
|
+
if (value) result.push({ name: context.name, content: value });
|
|
2807
|
+
}
|
|
2808
|
+
return result;
|
|
2809
|
+
}
|
|
2810
|
+
_emitChange() {
|
|
2811
|
+
this._ctx?.emit("system-prompt/change");
|
|
2812
|
+
}
|
|
2813
|
+
};
|
|
2814
|
+
function renderTemplate(text, variables) {
|
|
2815
|
+
return text.replace(/\$\{([a-zA-Z][a-zA-Z0-9_]*)\}/g, (match, name2) => {
|
|
2816
|
+
return variables[name2] ?? match;
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
var systemPrompt = {
|
|
2820
|
+
name: "system-prompt",
|
|
2821
|
+
apply: (ctx) => {
|
|
2822
|
+
const service = new SystemPromptService(ctx);
|
|
2823
|
+
ctx.provide("systemPrompt", service);
|
|
2824
|
+
return () => {
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2827
|
+
};
|
|
2828
|
+
|
|
2829
|
+
// packages/agent/src/llm-service.ts
|
|
2830
|
+
var LlmService = class {
|
|
2831
|
+
_adapters = /* @__PURE__ */ new Map();
|
|
2832
|
+
_current;
|
|
2833
|
+
_defaultName;
|
|
2834
|
+
register(name2, adapter) {
|
|
2835
|
+
const key = name2.trim();
|
|
2836
|
+
if (!key) throw new TypeError("llm provider requires a non-empty name");
|
|
2837
|
+
if (this._adapters.has(key)) throw new Error(`llm provider already registered: ${key}`);
|
|
2838
|
+
this._adapters.set(key, adapter);
|
|
2839
|
+
if (!this._current) {
|
|
2840
|
+
this._current = adapter;
|
|
2841
|
+
this._defaultName = key;
|
|
2842
|
+
}
|
|
2843
|
+
return () => {
|
|
2844
|
+
this._adapters.delete(key);
|
|
2845
|
+
if (this._defaultName === key) {
|
|
2846
|
+
this._defaultName = void 0;
|
|
2847
|
+
this._current = this._adapters.values().next().value;
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2851
|
+
setCurrent(name2) {
|
|
2852
|
+
const adapter = this._adapters.get(name2.trim());
|
|
2853
|
+
if (!adapter) throw new Error(`llm provider not found: ${name2}`);
|
|
2854
|
+
this._current = adapter;
|
|
2855
|
+
this._defaultName = name2.trim();
|
|
2856
|
+
return adapter;
|
|
2857
|
+
}
|
|
2858
|
+
current() {
|
|
2859
|
+
return this._current;
|
|
2860
|
+
}
|
|
2861
|
+
list() {
|
|
2862
|
+
return [...this._adapters.entries()].map(([name2, adapter]) => ({ name: name2, adapter }));
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Report provider/model/contextWindow for the current route. The adapter
|
|
2866
|
+
* may be registered with an optional capacity provider by callers that own
|
|
2867
|
+
* the model catalog (for example the CLI runtime).
|
|
2868
|
+
*/
|
|
2869
|
+
routeCapacity() {
|
|
2870
|
+
if (!this._current || !this._defaultName) return void 0;
|
|
2871
|
+
const provider = this._defaultName;
|
|
2872
|
+
const model = this._current.model;
|
|
2873
|
+
const contextWindow = this._capacityFor?.(model);
|
|
2874
|
+
const result = { provider, model: model ?? "" };
|
|
2875
|
+
if (contextWindow !== void 0) result.contextWindow = contextWindow;
|
|
2876
|
+
return result;
|
|
2877
|
+
}
|
|
2878
|
+
_capacityFor;
|
|
2879
|
+
/** Install a catalog-backed contextWindow resolver (optional). */
|
|
2880
|
+
setCapacityResolver(resolver) {
|
|
2881
|
+
this._capacityFor = resolver;
|
|
2882
|
+
}
|
|
2883
|
+
};
|
|
2884
|
+
var llmService = {
|
|
2885
|
+
name: "llm-service",
|
|
2886
|
+
apply: (ctx) => {
|
|
2887
|
+
ctx.provide("llm", new LlmService());
|
|
2888
|
+
return () => {
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
};
|
|
2892
|
+
export {
|
|
2893
|
+
AgentError,
|
|
2894
|
+
AgentInbox,
|
|
2895
|
+
AgentRegistry,
|
|
2896
|
+
AgentService,
|
|
2897
|
+
DurableInbox,
|
|
2898
|
+
LlmService,
|
|
2899
|
+
SystemPromptService,
|
|
2900
|
+
agent,
|
|
2901
|
+
agents,
|
|
2902
|
+
defineAgent,
|
|
2903
|
+
llmService,
|
|
2904
|
+
name,
|
|
2905
|
+
systemPrompt
|
|
2906
|
+
};
|
|
2907
|
+
//# sourceMappingURL=agent.js.map
|