tnega 0.1.1 → 0.4.1
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 -100
- package/dist/agent.js +4221 -151
- package/dist/agent.js.map +4 -4
- package/dist/bin.js +31173 -5140
- package/dist/bin.js.map +4 -4
- package/dist/cli-runtime.js +31170 -5100
- package/dist/cli-runtime.js.map +4 -4
- package/dist/coding-agent.js +20549 -0
- package/dist/coding-agent.js.map +7 -0
- package/dist/core.js +74 -1
- package/dist/core.js.map +4 -4
- package/dist/eval.js +23423 -1990
- package/dist/eval.js.map +4 -4
- package/dist/events.js +22 -1
- package/dist/events.js.map +2 -2
- package/dist/evolve.js +17814 -1812
- package/dist/evolve.js.map +4 -4
- package/dist/index.js +32340 -6086
- package/dist/index.js.map +4 -4
- package/dist/llm.js +55 -3
- package/dist/llm.js.map +3 -3
- package/dist/services.js +12 -1
- package/dist/services.js.map +2 -2
- package/dist/session.js +879 -122
- package/dist/session.js.map +4 -4
- package/dist/tools.js +242 -135
- package/dist/tools.js.map +4 -4
- package/dist/types/packages/agent/src/inbox-durable.d.ts +59 -0
- package/dist/types/packages/agent/src/index.d.ts +4 -0
- package/dist/types/packages/agent/src/live.d.ts +131 -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 +31 -1
- package/dist/types/packages/agent/src/types.d.ts +114 -2
- 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/commands.d.ts +26 -2
- package/dist/types/packages/cli/src/config.d.ts +4 -0
- package/dist/types/packages/cli/src/index.d.ts +5 -2
- 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 +2 -0
- package/dist/types/packages/cli/src/store.d.ts +10 -9
- 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/events.d.ts +4 -1
- package/dist/types/packages/core/src/index.d.ts +1 -0
- package/dist/types/packages/core/src/invariant.d.ts +45 -0
- package/dist/types/packages/eval/src/codingRuntime.d.ts +18 -0
- package/dist/types/packages/eval/src/policy.d.ts +8 -0
- package/dist/types/packages/eval/src/runner.d.ts +4 -0
- package/dist/types/packages/eval/src/strategies.d.ts +10 -0
- package/dist/types/packages/eval/src/trace.d.ts +7 -0
- package/dist/types/packages/eval/src/types.d.ts +97 -1
- package/dist/types/packages/eval/src/workspace.d.ts +17 -0
- package/dist/types/packages/llm/src/types.d.ts +4 -0
- package/dist/types/packages/session/src/index.d.ts +458 -12
- package/dist/types/packages/session/src/invariant.d.ts +35 -0
- package/dist/types/packages/tools/src/builtins.d.ts +2 -0
- package/dist/types/packages/tools/src/execution.d.ts +31 -0
- package/dist/types/packages/tools/src/index.d.ts +17 -0
- package/dist/types/src/index.d.ts +5 -5
- package/dist/web/assets/index-Bq0i29_u.js +270 -0
- package/dist/web/assets/index-DCLL69Kz.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +92 -88
- package/dist/web/assets/index-C3jFlR4V.css +0 -1
- package/dist/web/assets/index-DeFGh2h4.js +0 -268
package/dist/agent.js
CHANGED
|
@@ -1,5 +1,373 @@
|
|
|
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 isRecord(value) {
|
|
111
|
+
return typeof value === "object" && value !== null && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
112
|
+
}
|
|
113
|
+
function isNonNegativeInteger(value) {
|
|
114
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
115
|
+
}
|
|
116
|
+
function isJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
117
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
118
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
119
|
+
if (typeof value !== "object" || ancestors.has(value)) return false;
|
|
120
|
+
if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return false;
|
|
121
|
+
ancestors.add(value);
|
|
122
|
+
const valid = (Array.isArray(value) ? Array.from(value) : Object.values(value)).every((member) => isJsonValue(member, ancestors));
|
|
123
|
+
ancestors.delete(value);
|
|
124
|
+
return valid;
|
|
125
|
+
}
|
|
126
|
+
function isStreamRecord(value) {
|
|
127
|
+
if (!isRecord(value) || !isNonNegativeInteger(value.time) || !isRecord(value.chunk)) return false;
|
|
128
|
+
const chunk = value.chunk;
|
|
129
|
+
if (chunk.type === "stream_error") {
|
|
130
|
+
return isRecord(chunk.error) && typeof chunk.error.message === "string" && (chunk.error.name === void 0 || typeof chunk.error.name === "string") && (chunk.error.stack === void 0 || typeof chunk.error.stack === "string");
|
|
131
|
+
}
|
|
132
|
+
if (typeof chunk.id !== "string") return false;
|
|
133
|
+
switch (chunk.type) {
|
|
134
|
+
case "message_start":
|
|
135
|
+
return chunk.model === void 0 || typeof chunk.model === "string";
|
|
136
|
+
case "message_delta":
|
|
137
|
+
return typeof chunk.delta === "string";
|
|
138
|
+
case "toolcall_start":
|
|
139
|
+
case "toolcall_end":
|
|
140
|
+
return isNonNegativeInteger(chunk.index) && typeof chunk.name === "string" && (chunk.type === "toolcall_start" || isJsonValue(chunk.arguments));
|
|
141
|
+
case "message_stop":
|
|
142
|
+
return chunk.finishReason === "stop" || chunk.finishReason === "tool_calls" || chunk.finishReason === "length" || chunk.finishReason === "max_turns" || chunk.finishReason === "max_steps" || chunk.finishReason === "error" || chunk.finishReason === "cancelled";
|
|
143
|
+
default:
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function checkAssistantAttempts(events) {
|
|
148
|
+
const turns = /* @__PURE__ */ new Set();
|
|
149
|
+
const steps = /* @__PURE__ */ new Map();
|
|
150
|
+
const failures = [];
|
|
151
|
+
for (const event of events) {
|
|
152
|
+
if (event.type === "turn/start") turns.add(event.payload.turn);
|
|
153
|
+
if (event.type === "turn/end") {
|
|
154
|
+
turns.delete(event.payload.turn);
|
|
155
|
+
for (const [key, turn] of steps) {
|
|
156
|
+
if (turn === event.payload.turn) steps.delete(key);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (event.type === "step/start" && turns.has(event.payload.turn)) {
|
|
160
|
+
steps.set(`${event.payload.turn}:${event.payload.step}`, event.payload.turn);
|
|
161
|
+
}
|
|
162
|
+
if (event.type === "step/end") steps.delete(`${event.payload.turn}:${event.payload.step}`);
|
|
163
|
+
if (event.type !== "assistant/attempt") continue;
|
|
164
|
+
const payload = event.payload;
|
|
165
|
+
if (!isRecord(payload) || !isNonNegativeInteger(payload.turn) || !isNonNegativeInteger(payload.step) || !turns.has(payload.turn) || !steps.has(`${payload.turn}:${payload.step}`)) {
|
|
166
|
+
failures.push({
|
|
167
|
+
name: "assistant/attempt-without-open-step",
|
|
168
|
+
detail: `assistant/attempt at seq ${event.seq} has no matching open turn and step`
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (!isRecord(payload) || !Array.isArray(payload.stream) || !payload.stream.every(isStreamRecord)) {
|
|
172
|
+
failures.push({
|
|
173
|
+
name: "assistant/attempt-invalid-stream",
|
|
174
|
+
detail: `assistant/attempt at seq ${event.seq} must contain an ordered array of normalized stream records`
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return failures;
|
|
179
|
+
}
|
|
180
|
+
function checkSessionInvariants(events) {
|
|
181
|
+
return [
|
|
182
|
+
...checkBalancedTurns(events),
|
|
183
|
+
...checkBalancedSteps(events),
|
|
184
|
+
...checkBalancedToolCalls(events),
|
|
185
|
+
...checkMonotonicSeq(events),
|
|
186
|
+
...checkAssistantAttempts(events)
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
|
|
1
190
|
// packages/session/src/index.ts
|
|
191
|
+
var SESSION_FORMAT_VERSION = 10;
|
|
192
|
+
var SessionFormatError = class extends Error {
|
|
193
|
+
name = "SessionFormatError";
|
|
194
|
+
};
|
|
195
|
+
var liveSessions = /* @__PURE__ */ new Map();
|
|
196
|
+
function sessionFileKey(file) {
|
|
197
|
+
return resolve(file);
|
|
198
|
+
}
|
|
199
|
+
function isMessageEventType(type) {
|
|
200
|
+
return type === "user/message" || type === "assistant/message" || type === "system/message";
|
|
201
|
+
}
|
|
202
|
+
function isMessageEvent(event) {
|
|
203
|
+
return isMessageEventType(event.type);
|
|
204
|
+
}
|
|
2
205
|
var DEFAULT_CONTEXT_LIMIT = 128e3;
|
|
206
|
+
function isSessionEvent(value) {
|
|
207
|
+
if (typeof value !== "object" || value === null) return false;
|
|
208
|
+
const record = value;
|
|
209
|
+
return typeof record.id === "string" && typeof record.seq === "number" && typeof record.ts === "number" && typeof record.type === "string" && "payload" in record;
|
|
210
|
+
}
|
|
211
|
+
function clone(value) {
|
|
212
|
+
return structuredClone(value);
|
|
213
|
+
}
|
|
214
|
+
function previousSurfaceSeq(nodes) {
|
|
215
|
+
return nodes.at(-1);
|
|
216
|
+
}
|
|
217
|
+
function isSurfaceEventType(type) {
|
|
218
|
+
return type === "user/message" || type === "system/message" || type === "assistant/message" || type === "tool/result";
|
|
219
|
+
}
|
|
220
|
+
function checkpointShadowRange(event) {
|
|
221
|
+
if (event.type !== "checkpoint") return void 0;
|
|
222
|
+
const op = event.payload.surfaceOp;
|
|
223
|
+
if (typeof op === "object" && op !== null && op.op === "replace") {
|
|
224
|
+
return { start: op.start, end: op.end };
|
|
225
|
+
}
|
|
226
|
+
return void 0;
|
|
227
|
+
}
|
|
228
|
+
function foldSurface(events) {
|
|
229
|
+
const nodes = [];
|
|
230
|
+
const replacements = [];
|
|
231
|
+
const applyReplace = (seq, start, end) => {
|
|
232
|
+
const startIndex = nodes.indexOf(start);
|
|
233
|
+
if (startIndex < 0) {
|
|
234
|
+
nodes.push(seq);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const endIndex = nodes.indexOf(end, startIndex);
|
|
238
|
+
if (endIndex < 0) {
|
|
239
|
+
nodes.push(seq);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const shadowed = nodes.splice(startIndex, endIndex - startIndex + 1);
|
|
243
|
+
nodes.splice(startIndex, 0, seq);
|
|
244
|
+
replacements.push({ seq, start, end, shadowedSeqs: shadowed });
|
|
245
|
+
};
|
|
246
|
+
for (const event of events) {
|
|
247
|
+
if (event.type === "checkpoint") {
|
|
248
|
+
const range = checkpointShadowRange(event);
|
|
249
|
+
if (!range) {
|
|
250
|
+
nodes.push(event.seq);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
applyReplace(event.seq, range.start, range.end);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (!isSurfaceEventType(event.type)) continue;
|
|
257
|
+
const op = event.surfaceOp ?? "append";
|
|
258
|
+
if (op === "append") {
|
|
259
|
+
nodes.push(event.seq);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
applyReplace(event.seq, op.start, op.end);
|
|
263
|
+
}
|
|
264
|
+
return { nodes, replacements };
|
|
265
|
+
}
|
|
266
|
+
function deriveEventMessage(event) {
|
|
267
|
+
switch (event.type) {
|
|
268
|
+
case "user/message": {
|
|
269
|
+
const message = { role: "user", content: event.payload.content };
|
|
270
|
+
if (event.payload.name) message.name = event.payload.name;
|
|
271
|
+
return message;
|
|
272
|
+
}
|
|
273
|
+
case "system/message": {
|
|
274
|
+
const message = { role: "system", content: event.payload.content };
|
|
275
|
+
if (event.payload.name) message.name = event.payload.name;
|
|
276
|
+
return message;
|
|
277
|
+
}
|
|
278
|
+
case "assistant/message": {
|
|
279
|
+
const toolCalls = event.payload.toolCalls;
|
|
280
|
+
if (!event.payload.content && !(toolCalls && toolCalls.length)) return null;
|
|
281
|
+
const message = { role: "assistant", content: event.payload.content };
|
|
282
|
+
if (event.payload.name) message.name = event.payload.name;
|
|
283
|
+
if (toolCalls?.length) message.tool_calls = clone(toolCalls);
|
|
284
|
+
return message;
|
|
285
|
+
}
|
|
286
|
+
case "tool/result": {
|
|
287
|
+
const failed = !event.payload.ok;
|
|
288
|
+
const message = {
|
|
289
|
+
role: "tool",
|
|
290
|
+
content: failed ? `error: ${event.payload.error?.message ?? "unknown"}` : stringify(event.payload.output),
|
|
291
|
+
tool_call_id: event.payload.toolCallId
|
|
292
|
+
};
|
|
293
|
+
message.name = event.payload.name;
|
|
294
|
+
if (failed) {
|
|
295
|
+
message.toolOk = false;
|
|
296
|
+
if (event.payload.error) message.toolError = event.payload.error;
|
|
297
|
+
}
|
|
298
|
+
return message;
|
|
299
|
+
}
|
|
300
|
+
default:
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function deriveSurfaceMessages(events) {
|
|
305
|
+
const { nodes } = foldSurface(events);
|
|
306
|
+
const bySeq = new Map(events.map((event) => [event.seq, event]));
|
|
307
|
+
const messages = [];
|
|
308
|
+
for (const seq of nodes) {
|
|
309
|
+
const event = bySeq.get(seq);
|
|
310
|
+
if (!event) continue;
|
|
311
|
+
if (event.type === "checkpoint") {
|
|
312
|
+
messages.splice(0, messages.length, ...clone(event.payload.messages));
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const message = deriveEventMessage(event);
|
|
316
|
+
if (message) messages.push(message);
|
|
317
|
+
}
|
|
318
|
+
return messages;
|
|
319
|
+
}
|
|
320
|
+
function foldRequestHeader(events) {
|
|
321
|
+
let header;
|
|
322
|
+
for (const event of events) {
|
|
323
|
+
if (event.type === "request/header") header = clone(event.payload);
|
|
324
|
+
}
|
|
325
|
+
return header;
|
|
326
|
+
}
|
|
327
|
+
function foldRequestContext(events) {
|
|
328
|
+
let context;
|
|
329
|
+
for (const event of events) {
|
|
330
|
+
if (event.type === "request/context") context = clone(event.payload);
|
|
331
|
+
}
|
|
332
|
+
return context;
|
|
333
|
+
}
|
|
334
|
+
function foldSessionMeta(events) {
|
|
335
|
+
const meta = {};
|
|
336
|
+
for (const event of events) {
|
|
337
|
+
if (event.type === "meta") {
|
|
338
|
+
const payload = event.payload;
|
|
339
|
+
if (typeof payload.title === "string") meta.title = payload.title;
|
|
340
|
+
if (payload.agentType === "general" || payload.agentType === "coding") {
|
|
341
|
+
meta.agentType = payload.agentType;
|
|
342
|
+
}
|
|
343
|
+
if (payload.mode === "auto" || payload.mode === "plan" || payload.mode === "execute") {
|
|
344
|
+
meta.mode = payload.mode;
|
|
345
|
+
}
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (event.type === "meta/patch") {
|
|
349
|
+
const patch = event.payload;
|
|
350
|
+
if (patch.fields.includes("title") && typeof patch.title === "string") {
|
|
351
|
+
meta.title = patch.title;
|
|
352
|
+
}
|
|
353
|
+
if (patch.fields.includes("agentType") && patch.agentType !== void 0) {
|
|
354
|
+
meta.agentType = patch.agentType;
|
|
355
|
+
}
|
|
356
|
+
if (patch.fields.includes("mode") && patch.mode !== void 0) {
|
|
357
|
+
meta.mode = patch.mode;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return meta;
|
|
362
|
+
}
|
|
363
|
+
function stringify(value) {
|
|
364
|
+
if (typeof value === "string") return value;
|
|
365
|
+
try {
|
|
366
|
+
return JSON.stringify(value) ?? String(value);
|
|
367
|
+
} catch {
|
|
368
|
+
return String(value);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
3
371
|
function estimateMessageTokens(messages) {
|
|
4
372
|
let tokens = 0;
|
|
5
373
|
for (const message of messages) {
|
|
@@ -11,6 +379,156 @@ function estimateMessageTokens(messages) {
|
|
|
11
379
|
}
|
|
12
380
|
return tokens;
|
|
13
381
|
}
|
|
382
|
+
function estimateEventTokens(event) {
|
|
383
|
+
switch (event.type) {
|
|
384
|
+
case "user/message":
|
|
385
|
+
case "system/message":
|
|
386
|
+
return Math.ceil(event.payload.content.length / 4);
|
|
387
|
+
case "assistant/message": {
|
|
388
|
+
let tokens = Math.ceil(event.payload.content.length / 4);
|
|
389
|
+
for (const call of event.payload.toolCalls ?? []) {
|
|
390
|
+
const raw = JSON.stringify(call.arguments ?? {}) ?? "";
|
|
391
|
+
tokens += Math.ceil(raw.length / 4);
|
|
392
|
+
}
|
|
393
|
+
return tokens;
|
|
394
|
+
}
|
|
395
|
+
case "assistant/chunk":
|
|
396
|
+
return Math.ceil(event.payload.content.length / 4);
|
|
397
|
+
case "tool/call": {
|
|
398
|
+
const raw = JSON.stringify(event.payload.arguments ?? {}) ?? "";
|
|
399
|
+
return Math.ceil(raw.length / 4);
|
|
400
|
+
}
|
|
401
|
+
case "tool/result": {
|
|
402
|
+
const raw = event.payload.ok ? stringify(event.payload.output) : event.payload.error?.message ?? "error";
|
|
403
|
+
return Math.ceil(raw.length / 4);
|
|
404
|
+
}
|
|
405
|
+
case "plan":
|
|
406
|
+
case "assistant/attempt":
|
|
407
|
+
return 0;
|
|
408
|
+
case "checkpoint":
|
|
409
|
+
return estimateMessageTokens(event.payload.messages);
|
|
410
|
+
case "compaction/start":
|
|
411
|
+
case "compaction/end":
|
|
412
|
+
case "meta":
|
|
413
|
+
case "meta/patch":
|
|
414
|
+
return 0;
|
|
415
|
+
case "llm/retry":
|
|
416
|
+
case "llm/retry-started":
|
|
417
|
+
return 0;
|
|
418
|
+
case "request/header":
|
|
419
|
+
case "request/context":
|
|
420
|
+
case "agent/inbox/spliced":
|
|
421
|
+
return 0;
|
|
422
|
+
case "turn/start":
|
|
423
|
+
case "turn/end":
|
|
424
|
+
case "step/start":
|
|
425
|
+
case "step/end":
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function suffixStartIndexForTokens(events, targetTokens) {
|
|
430
|
+
if (!events.length || targetTokens <= 0) return 0;
|
|
431
|
+
let tokens = 0;
|
|
432
|
+
let candidate = events.length;
|
|
433
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
434
|
+
tokens += estimateEventTokens(events[index]);
|
|
435
|
+
if (tokens >= targetTokens) {
|
|
436
|
+
candidate = index;
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (candidate === events.length) return 0;
|
|
441
|
+
let cut = candidate;
|
|
442
|
+
while (cut < events.length) {
|
|
443
|
+
const event = events[cut];
|
|
444
|
+
if (event?.type === "user/message") return cut;
|
|
445
|
+
cut += 1;
|
|
446
|
+
}
|
|
447
|
+
return candidate;
|
|
448
|
+
}
|
|
449
|
+
function repairUnclosed(events) {
|
|
450
|
+
const openCalls = [];
|
|
451
|
+
const openSteps = [];
|
|
452
|
+
const openTurns = [];
|
|
453
|
+
for (const event of events) {
|
|
454
|
+
switch (event.type) {
|
|
455
|
+
case "tool/call":
|
|
456
|
+
openCalls.push(event);
|
|
457
|
+
break;
|
|
458
|
+
case "tool/result": {
|
|
459
|
+
const callIndex = openCalls.findIndex(
|
|
460
|
+
(call) => call.payload.id === event.payload.toolCallId
|
|
461
|
+
);
|
|
462
|
+
if (callIndex >= 0) openCalls.splice(callIndex, 1);
|
|
463
|
+
else openCalls.pop();
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
case "step/start":
|
|
467
|
+
openSteps.push(event);
|
|
468
|
+
break;
|
|
469
|
+
case "step/end":
|
|
470
|
+
openSteps.pop();
|
|
471
|
+
break;
|
|
472
|
+
case "turn/start":
|
|
473
|
+
openTurns.push(event);
|
|
474
|
+
break;
|
|
475
|
+
case "turn/end":
|
|
476
|
+
openTurns.pop();
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (!openCalls.length && !openSteps.length && !openTurns.length) return [];
|
|
481
|
+
const synthetic = [];
|
|
482
|
+
let nextSeq = (events.at(-1)?.seq ?? 0) + 1;
|
|
483
|
+
let nextTs = (events.at(-1)?.ts ?? Date.now()) + 1;
|
|
484
|
+
const push = (type, payload) => {
|
|
485
|
+
synthetic.push({
|
|
486
|
+
id: randomUUID(),
|
|
487
|
+
seq: nextSeq,
|
|
488
|
+
ts: nextTs,
|
|
489
|
+
type,
|
|
490
|
+
payload: clone(payload)
|
|
491
|
+
});
|
|
492
|
+
nextSeq += 1;
|
|
493
|
+
nextTs += 1;
|
|
494
|
+
};
|
|
495
|
+
for (const call of openCalls) {
|
|
496
|
+
push("tool/result", {
|
|
497
|
+
id: call.payload.id,
|
|
498
|
+
toolCallId: call.payload.id,
|
|
499
|
+
name: call.payload.name,
|
|
500
|
+
ok: false,
|
|
501
|
+
error: {
|
|
502
|
+
name: "SessionInterruptedError",
|
|
503
|
+
message: "session interrupted before tool result was recorded"
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
for (const step of openSteps) {
|
|
508
|
+
push("step/end", {
|
|
509
|
+
turn: step.payload.turn,
|
|
510
|
+
step: step.payload.step,
|
|
511
|
+
finishReason: "interrupted",
|
|
512
|
+
interrupted: true
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
for (let i = 0; i < openTurns.length; i++) {
|
|
516
|
+
const turn = openTurns[i].payload.turn;
|
|
517
|
+
push("turn/end", {
|
|
518
|
+
turn,
|
|
519
|
+
finishReason: "interrupted",
|
|
520
|
+
interrupted: true
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
return synthetic;
|
|
524
|
+
}
|
|
525
|
+
function resolveCompactKeep(events, options) {
|
|
526
|
+
if (typeof options.keepTokens === "number" && Number.isFinite(options.keepTokens) && options.keepTokens > 0) {
|
|
527
|
+
return events.length - suffixStartIndexForTokens(events, options.keepTokens);
|
|
528
|
+
}
|
|
529
|
+
const keep = typeof options.keep === "number" && Number.isFinite(options.keep) && options.keep > 0 ? Math.floor(options.keep) : 0;
|
|
530
|
+
return Math.min(keep, events.length);
|
|
531
|
+
}
|
|
14
532
|
function estimateContextUsage(messages, limit = DEFAULT_CONTEXT_LIMIT) {
|
|
15
533
|
const tokens = estimateMessageTokens(messages);
|
|
16
534
|
return {
|
|
@@ -19,6 +537,465 @@ function estimateContextUsage(messages, limit = DEFAULT_CONTEXT_LIMIT) {
|
|
|
19
537
|
ratio: limit > 0 ? tokens / limit : 0
|
|
20
538
|
};
|
|
21
539
|
}
|
|
540
|
+
var SessionLog = class {
|
|
541
|
+
constructor(file, _broadcast, publicationContext) {
|
|
542
|
+
this.file = file;
|
|
543
|
+
this._broadcast = _broadcast;
|
|
544
|
+
if (!this._broadcast && publicationContext) {
|
|
545
|
+
this._broadcast = (type, payload) => {
|
|
546
|
+
if (type === "event") publicationContext.emit("session/event", payload);
|
|
547
|
+
else publicationContext.emit("session/flush", payload);
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
file;
|
|
552
|
+
_broadcast;
|
|
553
|
+
_events = [];
|
|
554
|
+
_surface = [];
|
|
555
|
+
_surfaceNodes = [];
|
|
556
|
+
_requestHeader;
|
|
557
|
+
_requestContext;
|
|
558
|
+
_loaded = false;
|
|
559
|
+
_nextSeq = 1;
|
|
560
|
+
_queue = Promise.resolve();
|
|
561
|
+
_writeTail = Promise.resolve();
|
|
562
|
+
_pending = [];
|
|
563
|
+
_drainScheduled = false;
|
|
564
|
+
_writeError;
|
|
565
|
+
init() {
|
|
566
|
+
return this._run(async () => {
|
|
567
|
+
const key = sessionFileKey(this.file);
|
|
568
|
+
if (!liveSessions.has(key)) liveSessions.set(key, this);
|
|
569
|
+
try {
|
|
570
|
+
await this._ensureLoaded();
|
|
571
|
+
await this._drainWrite();
|
|
572
|
+
if (this._writeError) {
|
|
573
|
+
const error = this._writeError;
|
|
574
|
+
this._writeError = void 0;
|
|
575
|
+
throw error;
|
|
576
|
+
}
|
|
577
|
+
} catch (error) {
|
|
578
|
+
if (liveSessions.get(key) === this) liveSessions.delete(key);
|
|
579
|
+
throw error;
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
append(type, payload) {
|
|
584
|
+
return this._run(async () => {
|
|
585
|
+
await this._ensureLoaded();
|
|
586
|
+
const event = this._buildEvent(type, payload);
|
|
587
|
+
this._commitEvent(event);
|
|
588
|
+
return event;
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
_buildEvent(type, payload) {
|
|
592
|
+
const eventPayload = clone(payload);
|
|
593
|
+
const event = {
|
|
594
|
+
id: randomUUID(),
|
|
595
|
+
seq: this._nextSeq,
|
|
596
|
+
ts: Date.now(),
|
|
597
|
+
type,
|
|
598
|
+
payload: eventPayload
|
|
599
|
+
};
|
|
600
|
+
if (isMessageEventType(type)) {
|
|
601
|
+
for (let index = this._events.length - 1; index >= 0; index -= 1) {
|
|
602
|
+
const previous2 = this._events[index];
|
|
603
|
+
if (previous2 && isMessageEvent(previous2)) {
|
|
604
|
+
const messagePayload = eventPayload;
|
|
605
|
+
messagePayload.parentId ??= previous2.id;
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
event.surfaceOp ??= "append";
|
|
610
|
+
const previous = previousSurfaceSeq(this._surfaceNodes);
|
|
611
|
+
event.sourceEventSeqs ??= previous === void 0 ? [] : [previous];
|
|
612
|
+
} else if (type === "request/header") {
|
|
613
|
+
event.sourceEventSeqs ??= [];
|
|
614
|
+
} else if (type === "request/context") {
|
|
615
|
+
event.sourceEventSeqs ??= [];
|
|
616
|
+
} else if (type === "agent/inbox/spliced") {
|
|
617
|
+
event.sourceEventSeqs ??= [];
|
|
618
|
+
}
|
|
619
|
+
this._nextSeq += 1;
|
|
620
|
+
return event;
|
|
621
|
+
}
|
|
622
|
+
_commitEvent(event) {
|
|
623
|
+
this._events.push(event);
|
|
624
|
+
this._refreshSurface();
|
|
625
|
+
if (event.type === "request/header") this._requestHeader = clone(event.payload);
|
|
626
|
+
if (event.type === "request/context") this._requestContext = clone(event.payload);
|
|
627
|
+
this._broadcast?.("event", event);
|
|
628
|
+
this._enqueue(event);
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Rebuild both surface views from the committed log. The nodes come from the
|
|
632
|
+
* positional {@link foldSurface} fold; the messages come from
|
|
633
|
+
* {@link deriveSurfaceMessages}, which walks those nodes in surface order —
|
|
634
|
+
* the model view is a pure function of the surface, never of raw file order.
|
|
635
|
+
*/
|
|
636
|
+
_refreshSurface() {
|
|
637
|
+
this._surfaceNodes = foldSurface(this._events).nodes;
|
|
638
|
+
this._surface = deriveSurfaceMessages(this._events);
|
|
639
|
+
}
|
|
640
|
+
_enqueue(event) {
|
|
641
|
+
this._pending.push(event);
|
|
642
|
+
if (this._drainScheduled) return;
|
|
643
|
+
this._drainScheduled = true;
|
|
644
|
+
setImmediate(() => {
|
|
645
|
+
this._drainScheduled = false;
|
|
646
|
+
void this._drainWrite();
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
_drainWrite() {
|
|
650
|
+
const events = this._pending.splice(0);
|
|
651
|
+
if (!events.length) return this._writeTail;
|
|
652
|
+
const write = this._writeTail.then(() => appendFile(
|
|
653
|
+
this.file,
|
|
654
|
+
`${events.map((event) => JSON.stringify(event)).join("\n")}
|
|
655
|
+
`,
|
|
656
|
+
"utf8"
|
|
657
|
+
));
|
|
658
|
+
this._writeTail = write.catch((error) => {
|
|
659
|
+
this._writeError ??= error;
|
|
660
|
+
});
|
|
661
|
+
return write.then(() => void 0, () => void 0);
|
|
662
|
+
}
|
|
663
|
+
flush() {
|
|
664
|
+
return this._run(async () => {
|
|
665
|
+
await this._ensureLoaded();
|
|
666
|
+
await this._drainWrite();
|
|
667
|
+
if (this._writeError) {
|
|
668
|
+
const error = this._writeError;
|
|
669
|
+
this._writeError = void 0;
|
|
670
|
+
throw error;
|
|
671
|
+
}
|
|
672
|
+
const seq = this._nextSeq - 1;
|
|
673
|
+
this._broadcast?.("flush", { file: this.file, seq });
|
|
674
|
+
return seq;
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
async lineage(messageId) {
|
|
678
|
+
return this._run(async () => {
|
|
679
|
+
await this._ensureLoaded();
|
|
680
|
+
return this._resolveLineage(messageId);
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
/** The next turn number: one more than the highest recorded `turn/start`. */
|
|
684
|
+
async nextTurn() {
|
|
685
|
+
return this._run(async () => {
|
|
686
|
+
await this._ensureLoaded();
|
|
687
|
+
let max = 0;
|
|
688
|
+
for (const event of this._events) {
|
|
689
|
+
if (event.type === "turn/start") max = Math.max(max, event.payload.turn);
|
|
690
|
+
}
|
|
691
|
+
return max + 1;
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
async forkAt(messageId) {
|
|
695
|
+
return this._run(async () => {
|
|
696
|
+
await this._ensureLoaded();
|
|
697
|
+
const lineage = this._resolveLineage(messageId);
|
|
698
|
+
const lineageIds = new Set(lineage.map((event) => event.id));
|
|
699
|
+
const targetIndex = this._events.findIndex(
|
|
700
|
+
(event) => event.id === messageId && isMessageEvent(event)
|
|
701
|
+
);
|
|
702
|
+
if (targetIndex < 0) {
|
|
703
|
+
throw new Error(`message not found: ${messageId}`);
|
|
704
|
+
}
|
|
705
|
+
const selected = [];
|
|
706
|
+
let skippedMessage = false;
|
|
707
|
+
for (let index = 0; index <= targetIndex; index += 1) {
|
|
708
|
+
const event = this._events[index];
|
|
709
|
+
if (isMessageEvent(event)) {
|
|
710
|
+
if (!lineageIds.has(event.id)) {
|
|
711
|
+
skippedMessage = true;
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
skippedMessage = false;
|
|
715
|
+
selected.push(clone(event));
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
if (event.type === "meta" || event.type === "llm/retry" || event.type === "llm/retry-started" || event.type === "assistant/attempt" || event.type.startsWith("turn/") || event.type.startsWith("step/")) {
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
if (event.type === "checkpoint") {
|
|
722
|
+
selected.push(clone(event));
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
if (!skippedMessage) {
|
|
726
|
+
selected.push(clone(event));
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return selected;
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
read() {
|
|
733
|
+
return this._run(async () => {
|
|
734
|
+
await this._ensureLoaded();
|
|
735
|
+
return this._events.map((event) => clone(event));
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
deriveMessages() {
|
|
739
|
+
return this._run(async () => {
|
|
740
|
+
await this._ensureLoaded();
|
|
741
|
+
return clone(this._surface);
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
/** The ordered surface events, after compaction replacements are applied. */
|
|
745
|
+
surfaceEvents() {
|
|
746
|
+
return this._run(async () => {
|
|
747
|
+
await this._ensureLoaded();
|
|
748
|
+
const bySeq = new Map(this._events.map((event) => [event.seq, event]));
|
|
749
|
+
return this._surfaceNodes.map((seq) => bySeq.get(seq)).filter((event) => event !== void 0).map((event) => clone(event));
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
/** The latest `request/header` snapshot, or undefined before the first one. */
|
|
753
|
+
requestHeader() {
|
|
754
|
+
return this._requestHeader ? clone(this._requestHeader) : void 0;
|
|
755
|
+
}
|
|
756
|
+
/** The latest resolved route metadata, or undefined before the first one. */
|
|
757
|
+
requestContext() {
|
|
758
|
+
return this._requestContext ? clone(this._requestContext) : void 0;
|
|
759
|
+
}
|
|
760
|
+
/** Display metadata folded from the durable `meta` + `meta/patch` events. */
|
|
761
|
+
meta() {
|
|
762
|
+
return this._run(async () => {
|
|
763
|
+
await this._ensureLoaded();
|
|
764
|
+
return foldSessionMeta(this._events);
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
estimateContext(limit = DEFAULT_CONTEXT_LIMIT) {
|
|
768
|
+
return this._run(async () => {
|
|
769
|
+
await this._ensureLoaded();
|
|
770
|
+
return estimateContextUsage(this._surface, limit);
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
replay(reduce, initial) {
|
|
774
|
+
return this.read().then((events) => {
|
|
775
|
+
if (!reduce) return events;
|
|
776
|
+
let state = initial;
|
|
777
|
+
let pending = Promise.resolve();
|
|
778
|
+
for (const event of events) {
|
|
779
|
+
pending = pending.then(async () => {
|
|
780
|
+
state = await reduce(state, event);
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
return pending.then(() => state);
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
/** Structural violations in the loaded event stream (empty = balanced). */
|
|
787
|
+
runInvariants() {
|
|
788
|
+
return this._run(async () => {
|
|
789
|
+
await this._ensureLoaded();
|
|
790
|
+
return checkSessionInvariants(this._events);
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Compact the session without ever rewriting history.
|
|
795
|
+
*
|
|
796
|
+
* The surface (not the raw log) is the unit of compaction. A kept suffix —
|
|
797
|
+
* measured in tokens via `keepTokens`, or in nodes via `keep`, and always
|
|
798
|
+
* snapped to start on a fresh `user/message` turn so a tool result never
|
|
799
|
+
* outlives the assistant call it answers — stays visible; every earlier
|
|
800
|
+
* surface node is shadowed by the checkpoint's `surfaceOp.replace` range.
|
|
801
|
+
* The three compaction events are appended like any other event, so seqs
|
|
802
|
+
* stay immutable, live observers see the transition, and a crash can only
|
|
803
|
+
* leave an orphaned `compaction/start` rather than a torn rewrite.
|
|
804
|
+
*
|
|
805
|
+
* Returns the new event count, or the unchanged count when there is no
|
|
806
|
+
* useful surface span to shadow.
|
|
807
|
+
*/
|
|
808
|
+
compact(options = {}) {
|
|
809
|
+
return this._run(async () => {
|
|
810
|
+
await this._ensureLoaded();
|
|
811
|
+
const events = this._events;
|
|
812
|
+
const bySeq = new Map(events.map((event) => [event.seq, event]));
|
|
813
|
+
const surfaceEvents = foldSurface(events).nodes.map((seq) => bySeq.get(seq)).filter((event) => event !== void 0);
|
|
814
|
+
const keep = resolveCompactKeep(surfaceEvents, options);
|
|
815
|
+
let split = Math.max(0, surfaceEvents.length - keep);
|
|
816
|
+
if (split < surfaceEvents.length && surfaceEvents[split].type !== "user/message") {
|
|
817
|
+
let next = split;
|
|
818
|
+
while (next < surfaceEvents.length && surfaceEvents[next].type !== "user/message") next += 1;
|
|
819
|
+
if (next < surfaceEvents.length) {
|
|
820
|
+
split = next;
|
|
821
|
+
} else {
|
|
822
|
+
let lastUser = surfaceEvents.length - 1;
|
|
823
|
+
while (lastUser >= 0 && surfaceEvents[lastUser].type !== "user/message") lastUser -= 1;
|
|
824
|
+
split = lastUser >= 0 ? lastUser : surfaceEvents.length;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const shadowed = surfaceEvents.slice(0, split);
|
|
828
|
+
if (!options.messages?.length) return this._events.length;
|
|
829
|
+
const range = shadowed.length ? { start: shadowed[0].seq, end: shadowed[shadowed.length - 1].seq } : void 0;
|
|
830
|
+
const boundary = split;
|
|
831
|
+
this._commitEvent(this._buildEvent("compaction/start", {
|
|
832
|
+
...boundary > 0 ? { boundary } : {},
|
|
833
|
+
...options.keep !== void 0 ? { keep: options.keep } : {},
|
|
834
|
+
...options.tokensBefore !== void 0 ? { tokensBefore: options.tokensBefore } : {}
|
|
835
|
+
}));
|
|
836
|
+
const checkpoint = this._buildEvent("checkpoint", {
|
|
837
|
+
messages: clone(options.messages),
|
|
838
|
+
...range ? { surfaceOp: { op: "replace", start: range.start, end: range.end } } : {},
|
|
839
|
+
...options.summary ? { summary: options.summary } : {},
|
|
840
|
+
...options.tokensBefore !== void 0 ? { tokensBefore: options.tokensBefore } : {}
|
|
841
|
+
});
|
|
842
|
+
this._commitEvent(checkpoint);
|
|
843
|
+
this._commitEvent(this._buildEvent("compaction/end", {
|
|
844
|
+
checkpointId: checkpoint.id,
|
|
845
|
+
...options.keep !== void 0 ? { keep: options.keep } : {}
|
|
846
|
+
}));
|
|
847
|
+
return this._events.length;
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
close() {
|
|
851
|
+
return this._queue.then(async () => {
|
|
852
|
+
await this._drainWrite();
|
|
853
|
+
if (this._writeError) {
|
|
854
|
+
const error = this._writeError;
|
|
855
|
+
this._writeError = void 0;
|
|
856
|
+
throw error;
|
|
857
|
+
}
|
|
858
|
+
const key = sessionFileKey(this.file);
|
|
859
|
+
if (liveSessions.get(key) === this) liveSessions.delete(key);
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
_run(task) {
|
|
863
|
+
const next = this._queue.then(task, task);
|
|
864
|
+
this._queue = next.then(() => void 0, () => void 0);
|
|
865
|
+
return next;
|
|
866
|
+
}
|
|
867
|
+
async _ensureLoaded() {
|
|
868
|
+
if (this._loaded) return;
|
|
869
|
+
const key = sessionFileKey(this.file);
|
|
870
|
+
const owner = liveSessions.get(key);
|
|
871
|
+
if (owner && owner !== this && owner._loaded) {
|
|
872
|
+
this._events = owner._events.map((event) => clone(event));
|
|
873
|
+
this._surface = clone(owner._surface);
|
|
874
|
+
this._surfaceNodes = [...owner._surfaceNodes];
|
|
875
|
+
this._requestHeader = owner._requestHeader ? clone(owner._requestHeader) : void 0;
|
|
876
|
+
this._requestContext = owner._requestContext ? clone(owner._requestContext) : void 0;
|
|
877
|
+
this._nextSeq = owner._nextSeq;
|
|
878
|
+
this._loaded = true;
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
await mkdir(dirname(resolve(this.file)), { recursive: true });
|
|
882
|
+
const read = await this._read();
|
|
883
|
+
if (!read.existing) {
|
|
884
|
+
const meta = {
|
|
885
|
+
id: randomUUID(),
|
|
886
|
+
seq: 1,
|
|
887
|
+
ts: Date.now(),
|
|
888
|
+
type: "meta",
|
|
889
|
+
payload: { formatVersion: SESSION_FORMAT_VERSION }
|
|
890
|
+
};
|
|
891
|
+
await writeFile(this.file, `${JSON.stringify(meta)}
|
|
892
|
+
`, "utf8");
|
|
893
|
+
this._events = [meta];
|
|
894
|
+
this._refreshSurface();
|
|
895
|
+
this._nextSeq = 2;
|
|
896
|
+
this._loaded = true;
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
this._events = read.events;
|
|
900
|
+
this._refreshSurface();
|
|
901
|
+
this._requestHeader = foldRequestHeader(this._events);
|
|
902
|
+
this._requestContext = foldRequestContext(this._events);
|
|
903
|
+
this._nextSeq = (this._events.at(-1)?.seq ?? 0) + 1;
|
|
904
|
+
this._loaded = true;
|
|
905
|
+
}
|
|
906
|
+
async _read() {
|
|
907
|
+
let text;
|
|
908
|
+
try {
|
|
909
|
+
text = await readFile(this.file, "utf8");
|
|
910
|
+
} catch (error) {
|
|
911
|
+
if (error.code === "ENOENT") {
|
|
912
|
+
return { existing: false, events: [] };
|
|
913
|
+
}
|
|
914
|
+
throw error;
|
|
915
|
+
}
|
|
916
|
+
if (!text.trim()) return { existing: false, events: [] };
|
|
917
|
+
const lines = text.split("\n");
|
|
918
|
+
const events = [];
|
|
919
|
+
let torn = false;
|
|
920
|
+
for (const line of lines) {
|
|
921
|
+
if (!line.trim()) continue;
|
|
922
|
+
if (torn) continue;
|
|
923
|
+
let parsed;
|
|
924
|
+
try {
|
|
925
|
+
parsed = JSON.parse(line);
|
|
926
|
+
} catch {
|
|
927
|
+
torn = true;
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
if (!isSessionEvent(parsed)) {
|
|
931
|
+
torn = true;
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
events.push(parsed);
|
|
935
|
+
}
|
|
936
|
+
this._assertFormat(events);
|
|
937
|
+
const key = sessionFileKey(this.file);
|
|
938
|
+
const owner = liveSessions.get(key);
|
|
939
|
+
if (owner && owner !== this) {
|
|
940
|
+
return { existing: true, events };
|
|
941
|
+
}
|
|
942
|
+
const synthetic = repairUnclosed(events);
|
|
943
|
+
if (!torn && !synthetic.length) return { existing: true, events };
|
|
944
|
+
const repaired = [...events, ...synthetic];
|
|
945
|
+
await writeFile(
|
|
946
|
+
this.file,
|
|
947
|
+
`${repaired.map((event) => JSON.stringify(event)).join("\n")}
|
|
948
|
+
`,
|
|
949
|
+
"utf8"
|
|
950
|
+
);
|
|
951
|
+
return { existing: true, events: repaired };
|
|
952
|
+
}
|
|
953
|
+
_assertFormat(events) {
|
|
954
|
+
let version;
|
|
955
|
+
for (const event of events) {
|
|
956
|
+
if (event.type !== "meta") continue;
|
|
957
|
+
const value = event.payload.formatVersion;
|
|
958
|
+
if (typeof value === "number") version = value;
|
|
959
|
+
}
|
|
960
|
+
if (version === void 0) {
|
|
961
|
+
throw new SessionFormatError(
|
|
962
|
+
`session log is missing formatVersion; expected ${SESSION_FORMAT_VERSION}`
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
if (version !== SESSION_FORMAT_VERSION) {
|
|
966
|
+
throw new SessionFormatError(
|
|
967
|
+
`unsupported session format version ${version}; expected ${SESSION_FORMAT_VERSION}`
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
_resolveLineage(messageId) {
|
|
972
|
+
const messages = this._events.filter(
|
|
973
|
+
(event) => isMessageEvent(event)
|
|
974
|
+
);
|
|
975
|
+
const byId = new Map(
|
|
976
|
+
messages.map((event) => [event.id, event])
|
|
977
|
+
);
|
|
978
|
+
if (!byId.has(messageId)) {
|
|
979
|
+
throw new Error(`message not found: ${messageId}`);
|
|
980
|
+
}
|
|
981
|
+
const chain = [];
|
|
982
|
+
const seen = /* @__PURE__ */ new Set();
|
|
983
|
+
let cursorId = messageId;
|
|
984
|
+
while (cursorId && byId.has(cursorId) && !seen.has(cursorId)) {
|
|
985
|
+
seen.add(cursorId);
|
|
986
|
+
const event = byId.get(cursorId);
|
|
987
|
+
chain.unshift(clone(event));
|
|
988
|
+
const parentId = event.payload.parentId;
|
|
989
|
+
if (parentId && byId.has(parentId)) {
|
|
990
|
+
cursorId = parentId;
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
const index = messages.findIndex((message) => message.id === cursorId);
|
|
994
|
+
cursorId = index > 0 ? messages[index - 1].id : void 0;
|
|
995
|
+
}
|
|
996
|
+
return chain;
|
|
997
|
+
}
|
|
998
|
+
};
|
|
22
999
|
|
|
23
1000
|
// packages/agent/src/service.ts
|
|
24
1001
|
var AgentError = class extends Error {
|
|
@@ -26,19 +1003,34 @@ var AgentError = class extends Error {
|
|
|
26
1003
|
};
|
|
27
1004
|
var AgentInbox = class {
|
|
28
1005
|
_queue = [];
|
|
1006
|
+
_steerQueue = [];
|
|
29
1007
|
_injected = /* @__PURE__ */ new Map();
|
|
30
1008
|
get size() {
|
|
31
|
-
return this._queue.length;
|
|
1009
|
+
return this._queue.length + this._steerQueue.length;
|
|
1010
|
+
}
|
|
1011
|
+
get steerSize() {
|
|
1012
|
+
return this._steerQueue.length;
|
|
32
1013
|
}
|
|
33
1014
|
push(input) {
|
|
34
1015
|
this._queue.push(input);
|
|
35
1016
|
return input;
|
|
36
1017
|
}
|
|
1018
|
+
followup(input) {
|
|
1019
|
+
return this.push(input);
|
|
1020
|
+
}
|
|
1021
|
+
steer(input) {
|
|
1022
|
+
this._steerQueue.push(input);
|
|
1023
|
+
return input;
|
|
1024
|
+
}
|
|
1025
|
+
send(input, options = {}) {
|
|
1026
|
+
if (options.mode === "steer") return this.steer(input);
|
|
1027
|
+
return this.followup(input);
|
|
1028
|
+
}
|
|
37
1029
|
claim() {
|
|
38
|
-
return this._queue.shift();
|
|
1030
|
+
return this._steerQueue.shift() ?? this._queue.shift();
|
|
39
1031
|
}
|
|
40
1032
|
peek() {
|
|
41
|
-
return this._queue[0];
|
|
1033
|
+
return this._steerQueue[0] ?? this._queue[0];
|
|
42
1034
|
}
|
|
43
1035
|
inject(key, value) {
|
|
44
1036
|
this._injected.set(key, value);
|
|
@@ -61,13 +1053,16 @@ function copyMessages(messages) {
|
|
|
61
1053
|
copy.tool_calls = message.tool_calls.map((call) => ({
|
|
62
1054
|
id: call.id,
|
|
63
1055
|
name: call.name,
|
|
64
|
-
arguments: call.arguments
|
|
1056
|
+
arguments: structuredClone(call.arguments)
|
|
65
1057
|
}));
|
|
66
1058
|
}
|
|
67
1059
|
return copy;
|
|
68
1060
|
});
|
|
69
1061
|
}
|
|
70
|
-
function
|
|
1062
|
+
function isUserOrSystemMessage(message) {
|
|
1063
|
+
return message.role === "system" || message.role === "user";
|
|
1064
|
+
}
|
|
1065
|
+
function stringify2(value) {
|
|
71
1066
|
if (typeof value === "string") return value;
|
|
72
1067
|
try {
|
|
73
1068
|
return JSON.stringify(value) ?? String(value);
|
|
@@ -89,27 +1084,268 @@ function copyCompletion(completion) {
|
|
|
89
1084
|
}
|
|
90
1085
|
return copy;
|
|
91
1086
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
1087
|
+
function canonicalMessages(messages) {
|
|
1088
|
+
return JSON.stringify(messages.map((message) => ({
|
|
1089
|
+
role: message.role,
|
|
1090
|
+
content: message.content,
|
|
1091
|
+
...message.name ? { name: message.name } : {},
|
|
1092
|
+
...message.tool_call_id ? { tool_call_id: message.tool_call_id } : {},
|
|
1093
|
+
...message.tool_calls?.length ? { tool_calls: message.tool_calls } : {},
|
|
1094
|
+
...message.toolOk !== void 0 ? { toolOk: message.toolOk } : {},
|
|
1095
|
+
...message.toolError ? {
|
|
1096
|
+
toolError: { name: message.toolError.name, message: message.toolError.message }
|
|
1097
|
+
} : {}
|
|
1098
|
+
})));
|
|
1099
|
+
}
|
|
1100
|
+
function freezeRequestData(data) {
|
|
1101
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1102
|
+
const freeze = (value) => {
|
|
1103
|
+
if (typeof value !== "object" || value === null || visited.has(value)) return;
|
|
1104
|
+
visited.add(value);
|
|
1105
|
+
for (const child of Object.values(value)) freeze(child);
|
|
1106
|
+
Object.freeze(value);
|
|
1107
|
+
};
|
|
1108
|
+
freeze(data);
|
|
1109
|
+
}
|
|
1110
|
+
function lockStreamRequest(request) {
|
|
1111
|
+
freezeRequestData(request.messages);
|
|
1112
|
+
request.tools = Object.freeze(request.tools.map((tool) => {
|
|
1113
|
+
const schema = structuredClone(tool.schema);
|
|
1114
|
+
freezeRequestData(schema);
|
|
1115
|
+
return Object.freeze({ ...tool, schema });
|
|
1116
|
+
}));
|
|
1117
|
+
Object.freeze(request.options);
|
|
1118
|
+
Object.freeze(request);
|
|
1119
|
+
}
|
|
1120
|
+
function requestHeaderFor(request, input) {
|
|
1121
|
+
const system = input.find((message) => message.role === "system")?.content;
|
|
1122
|
+
const tools = request.tools.map((tool) => ({
|
|
1123
|
+
name: tool.schema.name,
|
|
1124
|
+
description: tool.schema.description,
|
|
1125
|
+
...tool.schema.parameters ? { parameters: tool.schema.parameters } : {}
|
|
1126
|
+
}));
|
|
1127
|
+
const config = {
|
|
1128
|
+
...request.options.provider ? { provider: request.options.provider } : {},
|
|
1129
|
+
...request.options.model ? { model: request.options.model } : {},
|
|
1130
|
+
...request.options.temperature !== void 0 ? { temperature: request.options.temperature } : {}
|
|
1131
|
+
};
|
|
1132
|
+
return {
|
|
1133
|
+
...Object.keys(config).length ? { config } : {},
|
|
1134
|
+
...system !== void 0 ? { system } : {},
|
|
1135
|
+
...tools.length ? { tools } : {}
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
async function* completeAsStream(llm, messages, tools, options) {
|
|
1139
|
+
const id = randomUUID2();
|
|
1140
|
+
yield { type: "message_start", id };
|
|
1141
|
+
const completion = await llm.complete(messages, tools, options);
|
|
1142
|
+
if (completion.content) yield { type: "message_delta", id, delta: completion.content };
|
|
1143
|
+
for (let index = 0; index < (completion.toolCalls?.length ?? 0); index += 1) {
|
|
1144
|
+
const call = completion.toolCalls[index];
|
|
1145
|
+
yield { type: "toolcall_start", id: call.id, index, name: call.name };
|
|
1146
|
+
yield {
|
|
1147
|
+
type: "toolcall_end",
|
|
1148
|
+
id: call.id,
|
|
1149
|
+
index,
|
|
1150
|
+
name: call.name,
|
|
1151
|
+
arguments: call.arguments
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
yield { type: "message_stop", id, finishReason: completion.finishReason };
|
|
1155
|
+
}
|
|
1156
|
+
function isCancelCause(value) {
|
|
1157
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1158
|
+
const record = value;
|
|
1159
|
+
if (record.type === "user" || record.type === "parent" || record.type === "disposed") {
|
|
1160
|
+
return true;
|
|
1161
|
+
}
|
|
1162
|
+
if (record.type === "abort" && (record.message === void 0 || typeof record.message === "string")) {
|
|
1163
|
+
return true;
|
|
1164
|
+
}
|
|
1165
|
+
if (record.type === "timeout" && typeof record.timeoutMs === "number") {
|
|
1166
|
+
return true;
|
|
1167
|
+
}
|
|
1168
|
+
return false;
|
|
1169
|
+
}
|
|
1170
|
+
function cancelCauseFromSignal(signal) {
|
|
1171
|
+
if (!signal?.aborted) return void 0;
|
|
1172
|
+
const reason = signal.reason;
|
|
1173
|
+
if (isCancelCause(reason)) return reason;
|
|
1174
|
+
return { type: "abort" };
|
|
1175
|
+
}
|
|
1176
|
+
function abortedToolError(signal) {
|
|
1177
|
+
const cause = cancelCauseFromSignal(signal);
|
|
1178
|
+
return {
|
|
1179
|
+
name: "AbortError",
|
|
1180
|
+
message: `tool call aborted: ${cause?.type ?? "abort"}`
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
function toTurnEndReason(fields) {
|
|
1184
|
+
const { cancelled, turnError, finishReason, cancelCause } = fields;
|
|
1185
|
+
if (cancelled || finishReason === "cancelled") {
|
|
1186
|
+
return { kind: "aborted", cause: cancelCause ?? { type: "user" } };
|
|
1187
|
+
}
|
|
1188
|
+
if (turnError || finishReason === "error") {
|
|
1189
|
+
return {
|
|
1190
|
+
kind: "error",
|
|
1191
|
+
error: turnError ? toToolError(turnError) : { name: "TurnError", message: finishReason }
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
switch (finishReason) {
|
|
1195
|
+
case "length":
|
|
1196
|
+
return { kind: "max-tokens" };
|
|
1197
|
+
case "max_steps":
|
|
1198
|
+
return { kind: "max-steps" };
|
|
1199
|
+
case "max_turns":
|
|
1200
|
+
return { kind: "max-turns" };
|
|
1201
|
+
default:
|
|
1202
|
+
return { kind: "completed" };
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
function partialStreamContent(events) {
|
|
1206
|
+
let content = "";
|
|
1207
|
+
for (const event of events) {
|
|
1208
|
+
if (event.type === "message_delta") content += event.delta;
|
|
1209
|
+
}
|
|
1210
|
+
return content;
|
|
1211
|
+
}
|
|
1212
|
+
async function cancellableDelay(ms, signal) {
|
|
1213
|
+
if (ms <= 0) return !signal?.aborted;
|
|
1214
|
+
if (signal?.aborted) return false;
|
|
1215
|
+
return new Promise((resolve2) => {
|
|
1216
|
+
const timer = setTimeout(() => {
|
|
1217
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1218
|
+
resolve2(true);
|
|
1219
|
+
}, ms);
|
|
1220
|
+
const onAbort = () => {
|
|
1221
|
+
clearTimeout(timer);
|
|
1222
|
+
resolve2(false);
|
|
1223
|
+
};
|
|
1224
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
var AssistantStreamAttempt = class {
|
|
1228
|
+
constructor(session, turn, step, nextRevision) {
|
|
1229
|
+
this.session = session;
|
|
1230
|
+
this.turn = turn;
|
|
1231
|
+
this.step = step;
|
|
1232
|
+
this.nextRevision = nextRevision;
|
|
1233
|
+
}
|
|
1234
|
+
session;
|
|
1235
|
+
turn;
|
|
1236
|
+
step;
|
|
1237
|
+
nextRevision;
|
|
1238
|
+
attemptId = randomUUID2();
|
|
1239
|
+
records = [];
|
|
1240
|
+
ended = false;
|
|
1241
|
+
start() {
|
|
1242
|
+
return { type: "assistant/stream", frame: {
|
|
1243
|
+
type: "start",
|
|
1244
|
+
attemptId: this.attemptId,
|
|
1245
|
+
revision: this.nextRevision(),
|
|
1246
|
+
turn: this.turn,
|
|
1247
|
+
step: this.step
|
|
1248
|
+
} };
|
|
1249
|
+
}
|
|
1250
|
+
push(chunk) {
|
|
1251
|
+
if (this.ended) throw new AgentError("assistant attempt is already settled");
|
|
1252
|
+
const record = { time: Date.now(), chunk: structuredClone(chunk) };
|
|
1253
|
+
const index = this.records.length;
|
|
1254
|
+
this.records.push(record);
|
|
1255
|
+
return { type: "assistant/stream", frame: {
|
|
1256
|
+
type: "chunk",
|
|
1257
|
+
attemptId: this.attemptId,
|
|
1258
|
+
revision: this.nextRevision(),
|
|
1259
|
+
index,
|
|
1260
|
+
...structuredClone(record)
|
|
1261
|
+
} };
|
|
1262
|
+
}
|
|
1263
|
+
async settle(message) {
|
|
1264
|
+
if (this.ended) throw new AgentError("assistant attempt is already settled");
|
|
1265
|
+
const eventType = message ? "assistant/message" : "assistant/attempt";
|
|
1266
|
+
let event;
|
|
1267
|
+
try {
|
|
1268
|
+
event = message ? await this.session.append("assistant/message", { ...message, stream: this.records }) : await this.session.append("assistant/attempt", { turn: this.turn, step: this.step, stream: this.records });
|
|
1269
|
+
} catch (cause) {
|
|
1270
|
+
this.ended = true;
|
|
1271
|
+
const frame = { type: "assistant/stream", frame: {
|
|
1272
|
+
type: "end",
|
|
1273
|
+
attemptId: this.attemptId,
|
|
1274
|
+
revision: this.nextRevision(),
|
|
1275
|
+
index: this.records.length,
|
|
1276
|
+
outcome: { kind: "abandoned" }
|
|
1277
|
+
} };
|
|
1278
|
+
throw new AttemptSettlementError(cause, frame);
|
|
1279
|
+
}
|
|
1280
|
+
this.ended = true;
|
|
1281
|
+
return { type: "assistant/stream", frame: {
|
|
1282
|
+
type: "end",
|
|
1283
|
+
attemptId: this.attemptId,
|
|
1284
|
+
revision: this.nextRevision(),
|
|
1285
|
+
index: this.records.length,
|
|
1286
|
+
outcome: { kind: "committed", eventType, seq: event.seq }
|
|
1287
|
+
} };
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
var AttemptSettlementError = class extends Error {
|
|
1291
|
+
constructor(cause, frame) {
|
|
1292
|
+
super("assistant attempt settlement failed");
|
|
1293
|
+
this.cause = cause;
|
|
1294
|
+
this.frame = frame;
|
|
1295
|
+
}
|
|
1296
|
+
cause;
|
|
1297
|
+
frame;
|
|
1298
|
+
};
|
|
1299
|
+
var AgentService = class {
|
|
1300
|
+
constructor(ctx, config = {}) {
|
|
1301
|
+
this.ctx = ctx;
|
|
1302
|
+
this.config = config;
|
|
1303
|
+
this.inbox = config.inbox ?? new AgentInbox();
|
|
1304
|
+
}
|
|
1305
|
+
ctx;
|
|
1306
|
+
config;
|
|
1307
|
+
async *settleAttempt(attempt, message) {
|
|
1308
|
+
try {
|
|
1309
|
+
yield await attempt.settle(message);
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
if (error instanceof AttemptSettlementError) {
|
|
1312
|
+
yield error.frame;
|
|
1313
|
+
throw error.cause;
|
|
1314
|
+
}
|
|
1315
|
+
throw error;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
inbox;
|
|
1319
|
+
_seriesStarted = false;
|
|
1320
|
+
_persistedRequest = false;
|
|
1321
|
+
_streamRevision = 0;
|
|
1322
|
+
_attempt(session, turn, step) {
|
|
1323
|
+
return new AssistantStreamAttempt(session, turn, step, () => ++this._streamRevision);
|
|
1324
|
+
}
|
|
1325
|
+
/** Call when a request begins an explicit new model-message series. */
|
|
1326
|
+
startSeries() {
|
|
1327
|
+
this._seriesStarted = true;
|
|
1328
|
+
}
|
|
1329
|
+
/** Clear any pending series marker after a completed run. */
|
|
1330
|
+
endSeriesBoundary() {
|
|
1331
|
+
this._seriesStarted = false;
|
|
1332
|
+
}
|
|
1333
|
+
/** True when this service instance has already persisted a model-visible step. */
|
|
1334
|
+
get hasPersistedRequest() {
|
|
1335
|
+
return this._persistedRequest;
|
|
1336
|
+
}
|
|
1337
|
+
async run(input, options = {}) {
|
|
1338
|
+
return consumeAgentStream(this._stream(input, options));
|
|
1339
|
+
}
|
|
1340
|
+
async *runStream(input, options = {}) {
|
|
1341
|
+
const iterator = this._stream(input, options)[Symbol.asyncIterator]();
|
|
106
1342
|
while (true) {
|
|
107
1343
|
const next = await iterator.next();
|
|
108
1344
|
if (next.done) return next.value;
|
|
109
1345
|
yield next.value;
|
|
110
1346
|
}
|
|
111
1347
|
}
|
|
112
|
-
async *_stream(input, options = {}
|
|
1348
|
+
async *_stream(input, options = {}) {
|
|
113
1349
|
const claimed = input ?? this.inbox.claim();
|
|
114
1350
|
if (!claimed) throw new AgentError("no agent input available");
|
|
115
1351
|
await this.config.hooks?.beforeRun?.(claimed, options);
|
|
@@ -117,162 +1353,377 @@ var AgentService = class {
|
|
|
117
1353
|
const tools = this._tools();
|
|
118
1354
|
const llm = this._llm();
|
|
119
1355
|
if (!llm) throw new AgentError("no LLM adapter available");
|
|
1356
|
+
const streamAdapter = llm.stream;
|
|
1357
|
+
const persistChunks = streamAdapter !== void 0;
|
|
120
1358
|
const maxTurns = options.maxTurns ?? this.config.maxTurns ?? 64;
|
|
121
1359
|
const maxSteps = options.maxSteps ?? this.config.maxSteps ?? 64;
|
|
122
1360
|
const contextBudget = options.contextBudget ?? this.config.contextBudget;
|
|
123
1361
|
const injected = this.inbox.injected();
|
|
124
1362
|
this.ctx.emit("agent/start", { input: claimed, options, injected });
|
|
125
|
-
let messages = this._initialMessages(claimed);
|
|
126
|
-
|
|
127
|
-
if (typeof systemPrompt === "string" && systemPrompt) {
|
|
128
|
-
await session.append("message", { role: "system", content: systemPrompt });
|
|
129
|
-
}
|
|
130
|
-
if (claimed.text) {
|
|
131
|
-
await session.append("message", { role: "user", content: claimed.text });
|
|
132
|
-
}
|
|
1363
|
+
let messages = await this._initialMessages(claimed);
|
|
1364
|
+
let claimedMessages = messages.filter((message) => message.role === "user");
|
|
133
1365
|
const steps = [];
|
|
134
1366
|
let output = "";
|
|
135
1367
|
let finishReason = "stop";
|
|
1368
|
+
let lengthReached = false;
|
|
136
1369
|
this.ctx.emit("agent/turn-start", {
|
|
137
1370
|
input: claimed,
|
|
138
1371
|
messages: copyMessages(messages),
|
|
139
1372
|
injected
|
|
140
1373
|
});
|
|
1374
|
+
const turn = options.turn ?? await session.nextTurn();
|
|
1375
|
+
if (options.turn === void 0) {
|
|
1376
|
+
await session.append("turn/start", {
|
|
1377
|
+
turn,
|
|
1378
|
+
input: claimed.text ?? claimed,
|
|
1379
|
+
reason: "user"
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
141
1382
|
let index = 0;
|
|
142
1383
|
let finalTurnGranted = false;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
1384
|
+
let currentStepIndex;
|
|
1385
|
+
let turnError;
|
|
1386
|
+
try {
|
|
1387
|
+
while (index < maxTurns + (finalTurnGranted ? 1 : 0)) {
|
|
1388
|
+
if (options.signal?.aborted) {
|
|
1389
|
+
finishReason = "cancelled";
|
|
1390
|
+
break;
|
|
1391
|
+
}
|
|
1392
|
+
if (steps.length >= maxSteps) {
|
|
1393
|
+
finishReason = "max_steps";
|
|
1394
|
+
break;
|
|
1395
|
+
}
|
|
1396
|
+
if (contextBudget) {
|
|
1397
|
+
messages = await this._enforceContextBudget(session, contextBudget, messages);
|
|
1398
|
+
}
|
|
1399
|
+
const stepInput = copyMessages(messages);
|
|
1400
|
+
const preStep = await this.ctx.waterfallAsync("agent/pre-step", {
|
|
1401
|
+
index,
|
|
1402
|
+
turn,
|
|
1403
|
+
step: index,
|
|
1404
|
+
messages: stepInput,
|
|
1405
|
+
claimedMessages: copyMessages(claimedMessages),
|
|
1406
|
+
...options.signal ? { signal: options.signal } : {}
|
|
1407
|
+
}, (payload) => payload);
|
|
1408
|
+
if (!preStep || preStep.admission === "reject" || !Array.isArray(preStep.messages) || !preStep.messages.length) {
|
|
1409
|
+
break;
|
|
1410
|
+
}
|
|
1411
|
+
if (preStep.startsRequestSeries) this._seriesStarted = true;
|
|
1412
|
+
await session.append("step/start", { turn, step: index });
|
|
1413
|
+
currentStepIndex = index;
|
|
1414
|
+
const requestedInput = copyMessages(preStep.messages);
|
|
1415
|
+
const admittedHistory = await session.deriveMessages();
|
|
1416
|
+
this.ctx.emit("agent/step", {
|
|
1417
|
+
index,
|
|
1418
|
+
turn,
|
|
1419
|
+
step: index,
|
|
1420
|
+
input: copyMessages(requestedInput)
|
|
1421
|
+
});
|
|
1422
|
+
const availableTools = finalTurnGranted ? [] : await this._resolveAvailableTools();
|
|
1423
|
+
const completeOptions = {
|
|
1424
|
+
maxSteps: maxSteps - steps.length
|
|
1425
|
+
};
|
|
1426
|
+
if (options.signal) completeOptions.signal = options.signal;
|
|
1427
|
+
let llmMessages = copyMessages(requestedInput);
|
|
1428
|
+
let completion;
|
|
1429
|
+
let activeAttempt;
|
|
1430
|
+
let attempt = 0;
|
|
1431
|
+
while (true) {
|
|
1432
|
+
if (options.signal?.aborted) {
|
|
1433
|
+
finishReason = "cancelled";
|
|
1434
|
+
break;
|
|
167
1435
|
}
|
|
168
|
-
|
|
169
|
-
|
|
1436
|
+
const streamEvents = [];
|
|
1437
|
+
activeAttempt = this._attempt(session, turn, index);
|
|
1438
|
+
yield activeAttempt.start();
|
|
1439
|
+
const streamRequest = {
|
|
1440
|
+
index,
|
|
1441
|
+
messages: copyMessages(requestedInput),
|
|
1442
|
+
tools: [...availableTools],
|
|
1443
|
+
options: { ...completeOptions }
|
|
1444
|
+
};
|
|
1445
|
+
try {
|
|
1446
|
+
const request = await this.ctx.waterfallAsync("agent/request", {
|
|
1447
|
+
index,
|
|
1448
|
+
messages: copyMessages(requestedInput),
|
|
1449
|
+
tools: streamRequest.tools,
|
|
1450
|
+
options: streamRequest.options
|
|
1451
|
+
}, (payload) => payload);
|
|
1452
|
+
if (!request || !request.options || !request.tools) {
|
|
1453
|
+
throw new AgentError("agent/request must return a request payload");
|
|
1454
|
+
}
|
|
1455
|
+
streamRequest.tools = request.tools;
|
|
1456
|
+
streamRequest.options = request.options;
|
|
1457
|
+
let preparation;
|
|
1458
|
+
const prepareRequest = () => preparation ??= (async () => {
|
|
1459
|
+
lockStreamRequest(streamRequest);
|
|
1460
|
+
llmMessages = copyMessages(streamRequest.messages);
|
|
1461
|
+
await this._persistStepInput(session, streamRequest, llmMessages, admittedHistory);
|
|
1462
|
+
await this._assertReplayable(session, streamRequest, llmMessages);
|
|
1463
|
+
})();
|
|
1464
|
+
const stream = await this.ctx.waterfallAsync(
|
|
1465
|
+
"llm/stream",
|
|
1466
|
+
streamRequest,
|
|
1467
|
+
(payload) => (async function* () {
|
|
1468
|
+
await prepareRequest();
|
|
1469
|
+
yield* streamAdapter ? streamAdapter.call(llm, payload.messages, payload.tools, payload.options) : completeAsStream(llm, payload.messages, payload.tools, payload.options);
|
|
1470
|
+
})()
|
|
1471
|
+
);
|
|
1472
|
+
if (!stream || typeof stream[Symbol.asyncIterator] !== "function") {
|
|
1473
|
+
throw new AgentError("llm/stream must return an async iterable stream");
|
|
1474
|
+
}
|
|
1475
|
+
await prepareRequest();
|
|
1476
|
+
let chunkIndex = 0;
|
|
1477
|
+
for await (const event of stream) {
|
|
1478
|
+
streamEvents.push(structuredClone(event));
|
|
1479
|
+
yield activeAttempt.push(event);
|
|
1480
|
+
if (persistChunks && event.type === "message_delta") {
|
|
1481
|
+
await session.append("assistant/chunk", {
|
|
1482
|
+
id: event.id,
|
|
1483
|
+
content: event.delta,
|
|
1484
|
+
index: chunkIndex
|
|
1485
|
+
});
|
|
1486
|
+
chunkIndex += 1;
|
|
1487
|
+
}
|
|
1488
|
+
yield event;
|
|
1489
|
+
}
|
|
1490
|
+
completion = completionFromStreamEvents(streamEvents);
|
|
1491
|
+
if (completion.finishReason === "error" || completion.finishReason === "cancelled") {
|
|
1492
|
+
const terminalReason = completion.finishReason;
|
|
1493
|
+
completion = void 0;
|
|
1494
|
+
throw new AgentError(`LLM stream finished with ${terminalReason}`);
|
|
1495
|
+
}
|
|
1496
|
+
break;
|
|
1497
|
+
} catch (error) {
|
|
1498
|
+
yield activeAttempt.push({ type: "stream_error", error: toToolError(error) });
|
|
1499
|
+
if (options.signal?.aborted) {
|
|
1500
|
+
const content = partialStreamContent(streamEvents);
|
|
1501
|
+
yield* this.settleAttempt(activeAttempt, content ? { content, interrupted: true } : void 0);
|
|
1502
|
+
finishReason = "cancelled";
|
|
1503
|
+
break;
|
|
1504
|
+
}
|
|
1505
|
+
yield* this.settleAttempt(activeAttempt);
|
|
1506
|
+
attempt += 1;
|
|
1507
|
+
const failure = toToolError(error);
|
|
1508
|
+
const decision = await this.ctx.waterfallAsync(
|
|
1509
|
+
"agent/request-error",
|
|
1510
|
+
{
|
|
1511
|
+
index,
|
|
1512
|
+
turn,
|
|
1513
|
+
step: index,
|
|
1514
|
+
messages: copyMessages(streamRequest.messages),
|
|
1515
|
+
tools: [...streamRequest.tools],
|
|
1516
|
+
options: { ...streamRequest.options },
|
|
1517
|
+
attempt,
|
|
1518
|
+
error,
|
|
1519
|
+
failure,
|
|
1520
|
+
...streamRequest.options.provider ? { provider: streamRequest.options.provider } : {},
|
|
1521
|
+
...streamRequest.options.model ? { model: streamRequest.options.model } : {},
|
|
1522
|
+
...options.signal ? { signal: options.signal } : {}
|
|
1523
|
+
},
|
|
1524
|
+
() => void 0
|
|
1525
|
+
);
|
|
1526
|
+
if (options.signal?.aborted) {
|
|
1527
|
+
finishReason = "cancelled";
|
|
1528
|
+
break;
|
|
1529
|
+
}
|
|
1530
|
+
if (decision?.kind !== "retry") throw error;
|
|
1531
|
+
const retryId = randomUUID2();
|
|
1532
|
+
const delayMs = decision.delayMs ?? 0;
|
|
1533
|
+
await session.append("llm/retry", {
|
|
1534
|
+
retryId,
|
|
1535
|
+
retry: attempt,
|
|
1536
|
+
...delayMs > 0 ? { delayMs } : {},
|
|
1537
|
+
failure: toToolError(error)
|
|
1538
|
+
});
|
|
1539
|
+
if (!await cancellableDelay(delayMs, options.signal)) {
|
|
1540
|
+
finishReason = "cancelled";
|
|
1541
|
+
break;
|
|
1542
|
+
}
|
|
1543
|
+
await session.append("llm/retry-started", { retryId, retry: attempt });
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
if (!completion || !activeAttempt) {
|
|
1547
|
+
if (options.signal?.aborted) {
|
|
1548
|
+
finishReason = "cancelled";
|
|
1549
|
+
break;
|
|
1550
|
+
}
|
|
1551
|
+
throw new AgentError("LLM adapter did not produce a completion");
|
|
1552
|
+
}
|
|
1553
|
+
const toolCalls = completion.toolCalls ?? [];
|
|
1554
|
+
if (completion.finishReason === "length") lengthReached = true;
|
|
1555
|
+
if (options.signal?.aborted && toolCalls.length === 0) {
|
|
1556
|
+
yield activeAttempt.push({ type: "stream_error", error: { name: "AbortError", message: "stream aborted" } });
|
|
1557
|
+
yield* this.settleAttempt(activeAttempt);
|
|
170
1558
|
finishReason = "cancelled";
|
|
171
|
-
|
|
1559
|
+
break;
|
|
172
1560
|
}
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
1561
|
+
if (finalTurnGranted && toolCalls.length && !options.signal?.aborted) {
|
|
1562
|
+
yield activeAttempt.push({ type: "stream_error", error: { name: "AgentError", message: "maximum turns reached" } });
|
|
1563
|
+
yield* this.settleAttempt(activeAttempt);
|
|
1564
|
+
finishReason = "max_turns";
|
|
1565
|
+
break;
|
|
1566
|
+
}
|
|
1567
|
+
const toolResults = [];
|
|
1568
|
+
if (toolCalls.length) {
|
|
1569
|
+
yield* this.settleAttempt(activeAttempt, {
|
|
1570
|
+
content: completion.content ?? "",
|
|
1571
|
+
toolCalls: toolCalls.map((call) => ({
|
|
1572
|
+
id: call.id,
|
|
1573
|
+
name: call.name,
|
|
1574
|
+
arguments: call.arguments
|
|
1575
|
+
}))
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
for (const call of toolCalls) {
|
|
1579
|
+
this.ctx.emit("agent/tool-call", { index, turn, step: index, call });
|
|
1580
|
+
yield { type: "tool/start", index, turn, step: index, call };
|
|
1581
|
+
await session.append("tool/call", {
|
|
1582
|
+
id: call.id,
|
|
1583
|
+
name: call.name,
|
|
1584
|
+
arguments: call.arguments
|
|
1585
|
+
});
|
|
1586
|
+
const toolOptions = { callId: call.id };
|
|
1587
|
+
if (options.signal) toolOptions.signal = options.signal;
|
|
1588
|
+
const startedAt = Date.now();
|
|
1589
|
+
let result;
|
|
1590
|
+
if (options.signal?.aborted) {
|
|
1591
|
+
result = {
|
|
1592
|
+
ok: false,
|
|
1593
|
+
name: call.name,
|
|
1594
|
+
callId: call.id,
|
|
1595
|
+
input: call.arguments,
|
|
1596
|
+
error: abortedToolError(options.signal),
|
|
1597
|
+
startedAt,
|
|
1598
|
+
durationMs: Date.now() - startedAt
|
|
1599
|
+
};
|
|
1600
|
+
} else {
|
|
1601
|
+
try {
|
|
1602
|
+
result = await tools.execute(call.name, call.arguments, toolOptions);
|
|
1603
|
+
} catch (error) {
|
|
1604
|
+
result = {
|
|
1605
|
+
ok: false,
|
|
1606
|
+
name: call.name,
|
|
1607
|
+
callId: call.id,
|
|
1608
|
+
input: call.arguments,
|
|
1609
|
+
error: toToolError(error),
|
|
1610
|
+
startedAt,
|
|
1611
|
+
durationMs: Date.now() - startedAt
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
toolResults.push(result);
|
|
1616
|
+
const toolResultPayload = {
|
|
1617
|
+
id: call.id,
|
|
1618
|
+
toolCallId: call.id,
|
|
1619
|
+
name: call.name,
|
|
1620
|
+
ok: result.ok,
|
|
1621
|
+
durationMs: result.durationMs
|
|
1622
|
+
};
|
|
1623
|
+
if (result.output !== void 0) toolResultPayload.output = result.output;
|
|
1624
|
+
if (result.error) {
|
|
1625
|
+
toolResultPayload.error = {
|
|
1626
|
+
name: result.error.name,
|
|
1627
|
+
message: result.error.message
|
|
1628
|
+
};
|
|
1629
|
+
if (result.error.stack) toolResultPayload.error.stack = result.error.stack;
|
|
1630
|
+
}
|
|
1631
|
+
await session.append("tool/result", toolResultPayload);
|
|
1632
|
+
yield { type: "tool/end", index, turn, step: index, call, result };
|
|
1633
|
+
this.ctx.emit("agent/tool-result", {
|
|
1634
|
+
index,
|
|
1635
|
+
turn,
|
|
1636
|
+
step: index,
|
|
1637
|
+
call,
|
|
1638
|
+
result
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
if (options.signal?.aborted) {
|
|
180
1642
|
finishReason = "cancelled";
|
|
181
1643
|
break;
|
|
182
1644
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
const toolResults = [];
|
|
190
|
-
if (toolCalls.length) {
|
|
191
|
-
await session.append("message", {
|
|
192
|
-
role: "assistant",
|
|
193
|
-
content: completion.content ?? ""
|
|
1645
|
+
steps.push({
|
|
1646
|
+
index,
|
|
1647
|
+
input: copyMessages(llmMessages),
|
|
1648
|
+
completion,
|
|
1649
|
+
toolResults
|
|
194
1650
|
});
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
1651
|
+
if (completion.content) {
|
|
1652
|
+
output = completion.content;
|
|
1653
|
+
}
|
|
1654
|
+
if (!toolCalls.length) {
|
|
1655
|
+
yield* this.settleAttempt(activeAttempt, { content: completion.content ?? "" });
|
|
1656
|
+
}
|
|
1657
|
+
await session.append("step/end", {
|
|
1658
|
+
turn,
|
|
1659
|
+
step: index,
|
|
1660
|
+
finishReason: completion.finishReason,
|
|
1661
|
+
toolCalls: toolCalls.length
|
|
203
1662
|
});
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
let
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
1663
|
+
currentStepIndex = void 0;
|
|
1664
|
+
const nextMessages = this._extendMessages(llmMessages, completion, toolResults);
|
|
1665
|
+
const concludesTurn = toolResults.some((result) => result.concludesTurn === true);
|
|
1666
|
+
let nextStepMessages = await this._claimNextStepMessages();
|
|
1667
|
+
if ((toolCalls.length === 0 || concludesTurn) && nextStepMessages.length === 0) {
|
|
1668
|
+
finishReason = lengthReached ? "length" : completion.finishReason === "error" ? "error" : "stop";
|
|
1669
|
+
const keepGoing = await this.ctx.serial("agent/turn-stopping", {
|
|
1670
|
+
index,
|
|
1671
|
+
turn,
|
|
1672
|
+
steps: copySteps(steps),
|
|
1673
|
+
finishReason
|
|
1674
|
+
});
|
|
1675
|
+
if (!keepGoing) {
|
|
1676
|
+
nextStepMessages = await this._claimNextStepMessages();
|
|
1677
|
+
if (nextStepMessages.length === 0) break;
|
|
1678
|
+
}
|
|
220
1679
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
ok: result.ok,
|
|
227
|
-
durationMs: result.durationMs
|
|
228
|
-
};
|
|
229
|
-
if (result.output !== void 0) toolResultPayload.output = result.output;
|
|
230
|
-
if (result.error) {
|
|
231
|
-
toolResultPayload.error = {
|
|
232
|
-
name: result.error.name,
|
|
233
|
-
message: result.error.message
|
|
234
|
-
};
|
|
235
|
-
if (result.error.stack) toolResultPayload.error.stack = result.error.stack;
|
|
1680
|
+
if (index + 1 >= maxTurns && !finalTurnGranted) {
|
|
1681
|
+
finalTurnGranted = true;
|
|
1682
|
+
} else if (index + 1 >= maxTurns + (finalTurnGranted ? 1 : 0)) {
|
|
1683
|
+
finishReason = "max_turns";
|
|
1684
|
+
break;
|
|
236
1685
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
1686
|
+
if (toolCalls.length) finishReason = "tool_calls";
|
|
1687
|
+
index += 1;
|
|
1688
|
+
messages = [...nextMessages, ...nextStepMessages];
|
|
1689
|
+
claimedMessages = nextStepMessages.filter((message) => message.role === "user");
|
|
240
1690
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
turnError = error;
|
|
1693
|
+
throw error;
|
|
1694
|
+
} finally {
|
|
1695
|
+
const cancelCause = cancelCauseFromSignal(options.signal);
|
|
1696
|
+
if (currentStepIndex !== void 0) {
|
|
1697
|
+
const cancelled = options.signal?.aborted;
|
|
1698
|
+
await session.append("step/end", {
|
|
1699
|
+
turn,
|
|
1700
|
+
step: currentStepIndex,
|
|
1701
|
+
finishReason: cancelled ? "cancelled" : turnError ? "error" : "interrupted",
|
|
1702
|
+
interrupted: true,
|
|
1703
|
+
...cancelCause ? { cancelCause } : {},
|
|
1704
|
+
...turnError ? { error: toToolError(turnError) } : {}
|
|
1705
|
+
});
|
|
244
1706
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
1707
|
+
await session.append("turn/end", {
|
|
1708
|
+
turn,
|
|
1709
|
+
finishReason: turnError ? options.signal?.aborted ? "cancelled" : "error" : finishReason,
|
|
1710
|
+
reason: toTurnEndReason({
|
|
1711
|
+
cancelled: options.signal?.aborted ?? false,
|
|
1712
|
+
turnError,
|
|
1713
|
+
finishReason: turnError ? options.signal?.aborted ? "cancelled" : "error" : finishReason,
|
|
1714
|
+
...cancelCause ? { cancelCause } : {}
|
|
1715
|
+
}),
|
|
1716
|
+
...cancelCause ? { cancelCause } : {},
|
|
1717
|
+
...output ? { output } : {},
|
|
1718
|
+
...steps.length ? { steps: steps.length } : {},
|
|
1719
|
+
...turnError ? { interrupted: true, error: toToolError(turnError) } : {}
|
|
250
1720
|
});
|
|
251
|
-
if (completion.content) {
|
|
252
|
-
output = completion.content;
|
|
253
|
-
}
|
|
254
|
-
if (!toolCalls.length) {
|
|
255
|
-
await session.append("message", { role: "assistant", content: completion.content ?? "" });
|
|
256
|
-
}
|
|
257
|
-
const nextMessages = this._extendMessages(messages, completion, toolResults);
|
|
258
|
-
if (toolCalls.length === 0) {
|
|
259
|
-
finishReason = completion.finishReason === "length" ? "length" : completion.finishReason === "error" ? "error" : "stop";
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
if (index + 1 >= maxTurns && !finalTurnGranted) {
|
|
263
|
-
finalTurnGranted = true;
|
|
264
|
-
} else if (index + 1 >= maxTurns + (finalTurnGranted ? 1 : 0)) {
|
|
265
|
-
finishReason = "max_turns";
|
|
266
|
-
break;
|
|
267
|
-
}
|
|
268
|
-
finishReason = "tool_calls";
|
|
269
|
-
index += 1;
|
|
270
|
-
messages = nextMessages;
|
|
271
1721
|
}
|
|
272
1722
|
const runResult = {
|
|
273
1723
|
input: claimed,
|
|
274
1724
|
output,
|
|
275
1725
|
finishReason,
|
|
1726
|
+
turn,
|
|
276
1727
|
steps,
|
|
277
1728
|
messages: copyMessages(messages)
|
|
278
1729
|
};
|
|
@@ -321,9 +1772,113 @@ var AgentService = class {
|
|
|
321
1772
|
});
|
|
322
1773
|
return compactMessages.map((message) => copyMessages([message])[0]);
|
|
323
1774
|
}
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
const
|
|
1775
|
+
async _persistStepInput(session, request, input, admittedHistory) {
|
|
1776
|
+
const surface = await session.deriveMessages();
|
|
1777
|
+
const owned = admittedHistory.filter((message) => !isUserOrSystemMessage(message));
|
|
1778
|
+
let ownerIndex = 0;
|
|
1779
|
+
for (const message of input) {
|
|
1780
|
+
if (isUserOrSystemMessage(message)) continue;
|
|
1781
|
+
const actual = canonicalMessages([message]);
|
|
1782
|
+
while (ownerIndex < owned.length && canonicalMessages([owned[ownerIndex]]) !== actual) {
|
|
1783
|
+
ownerIndex += 1;
|
|
1784
|
+
}
|
|
1785
|
+
if (ownerIndex === owned.length) {
|
|
1786
|
+
throw new AgentError("request is not reconstructable from session log: unowned or reordered assistant/tool message");
|
|
1787
|
+
}
|
|
1788
|
+
ownerIndex += 1;
|
|
1789
|
+
}
|
|
1790
|
+
const nextHeader = requestHeaderFor(request, input);
|
|
1791
|
+
const previousHeader = session.requestHeader();
|
|
1792
|
+
const isFirstRequest = !this._persistedRequest;
|
|
1793
|
+
const isResume = isFirstRequest && previousHeader !== void 0;
|
|
1794
|
+
this._persistedRequest = true;
|
|
1795
|
+
const changedHeader = previousHeader && (previousHeader.system !== nextHeader.system || JSON.stringify(previousHeader.tools ?? []) !== JSON.stringify(nextHeader.tools ?? []) || JSON.stringify(previousHeader.config ?? {}) !== JSON.stringify(nextHeader.config ?? {}));
|
|
1796
|
+
if (isFirstRequest) {
|
|
1797
|
+
await session.append("request/header", {
|
|
1798
|
+
reason: this._seriesStarted ? "series" : isResume ? "resume" : "initial",
|
|
1799
|
+
...nextHeader,
|
|
1800
|
+
...this._seriesStarted ? { startsSeries: true } : {}
|
|
1801
|
+
});
|
|
1802
|
+
} else if (changedHeader) {
|
|
1803
|
+
await session.append("request/header", {
|
|
1804
|
+
reason: this._seriesStarted ? "change-series" : "change",
|
|
1805
|
+
...nextHeader,
|
|
1806
|
+
startsSeries: true
|
|
1807
|
+
});
|
|
1808
|
+
} else if (this._seriesStarted) {
|
|
1809
|
+
await session.append("request/header", {
|
|
1810
|
+
reason: "series",
|
|
1811
|
+
...nextHeader,
|
|
1812
|
+
startsSeries: true
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
this._seriesStarted = false;
|
|
1816
|
+
const nextContext = {
|
|
1817
|
+
...request.options.provider ? { provider: request.options.provider } : {},
|
|
1818
|
+
...request.options.model ? { model: request.options.model } : {}
|
|
1819
|
+
};
|
|
1820
|
+
if (request.options.contextWindow !== void 0) {
|
|
1821
|
+
nextContext.contextWindow = request.options.contextWindow;
|
|
1822
|
+
}
|
|
1823
|
+
const llmService2 = this.ctx.reflect.get("llm", false);
|
|
1824
|
+
const routeCapacity = llmService2?.routeCapacity?.();
|
|
1825
|
+
if (nextContext.contextWindow === void 0 && routeCapacity?.contextWindow !== void 0) {
|
|
1826
|
+
nextContext.contextWindow = routeCapacity.contextWindow;
|
|
1827
|
+
}
|
|
1828
|
+
const previousContext = session.requestContext();
|
|
1829
|
+
if (previousContext?.provider !== nextContext.provider || previousContext?.model !== nextContext.model || previousContext?.contextWindow !== nextContext.contextWindow || previousContext === void 0) {
|
|
1830
|
+
await session.append("request/context", nextContext);
|
|
1831
|
+
}
|
|
1832
|
+
const isSurfacePrefix = (messages) => canonicalMessages(messages.slice(0, surface.length)) === canonicalMessages(surface);
|
|
1833
|
+
const requested = surface.length > 0 && input[0]?.role === "system" && input[0].content === nextHeader.system && isSurfacePrefix(input.slice(1)) ? input.slice(1) : input;
|
|
1834
|
+
const tail = requested.slice(surface.length);
|
|
1835
|
+
if (isSurfacePrefix(requested) && tail.every(isUserOrSystemMessage)) {
|
|
1836
|
+
for (const message of tail) {
|
|
1837
|
+
if (message.role === "user") {
|
|
1838
|
+
await session.append("user/message", {
|
|
1839
|
+
content: message.content,
|
|
1840
|
+
...message.name ? { name: message.name } : {}
|
|
1841
|
+
});
|
|
1842
|
+
} else {
|
|
1843
|
+
await session.append("system/message", {
|
|
1844
|
+
content: message.content,
|
|
1845
|
+
...message.name ? { name: message.name } : {}
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
const nodes = await session.surfaceEvents();
|
|
1852
|
+
await session.append("checkpoint", {
|
|
1853
|
+
messages: copyMessages(requested),
|
|
1854
|
+
surfaceOp: { op: "replace", start: nodes[0].seq, end: nodes.at(-1).seq }
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
async _assertReplayable(session, request, input) {
|
|
1858
|
+
const reconstructed = await session.deriveMessages();
|
|
1859
|
+
const actual = canonicalMessages(input);
|
|
1860
|
+
const replay = canonicalMessages(reconstructed);
|
|
1861
|
+
const header = session.requestHeader();
|
|
1862
|
+
const headerOwnsPrefix = input[0]?.role === "system" && input[0].content === header?.system && canonicalMessages(input.slice(1)) === replay;
|
|
1863
|
+
if (actual !== replay && !headerOwnsPrefix) {
|
|
1864
|
+
throw new AgentError(
|
|
1865
|
+
`request is not reconstructable from session log: actual=${actual} replay=${replay}`
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
const expected = requestHeaderFor(request, input);
|
|
1869
|
+
if (header?.system !== expected.system || JSON.stringify(header?.tools ?? []) !== JSON.stringify(expected.tools ?? []) || JSON.stringify(header?.config ?? {}) !== JSON.stringify(expected.config ?? {})) {
|
|
1870
|
+
throw new AgentError("request envelope is not reconstructable from session log");
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
async _claimNextStepMessages() {
|
|
1874
|
+
const inputs = await this.config.claimNextStep?.() ?? [];
|
|
1875
|
+
return inputs.flatMap((input) => input.messages?.length ? copyMessages(input.messages) : input.text ? [{ role: "user", content: input.text }] : input.context !== void 0 ? [{ role: "user", content: stringify2(input.context) }] : []);
|
|
1876
|
+
}
|
|
1877
|
+
async _initialMessages(input) {
|
|
1878
|
+
const promptService = this.ctx.reflect.get("systemPrompt", false);
|
|
1879
|
+
const assembled = promptService ? (await promptService.assemble({}, this.ctx)).text.trim() : void 0;
|
|
1880
|
+
const systemPrompt2 = assembled || this.inbox.injected().get("agentSystem");
|
|
1881
|
+
const systemMessage = typeof systemPrompt2 === "string" && systemPrompt2 ? [{ role: "system", content: systemPrompt2 }] : [];
|
|
327
1882
|
if (input.messages?.length) {
|
|
328
1883
|
const first = input.messages[0];
|
|
329
1884
|
if (first?.role === "system") return copyMessages(input.messages);
|
|
@@ -346,10 +1901,10 @@ var AgentService = class {
|
|
|
346
1901
|
arguments: call.arguments
|
|
347
1902
|
}));
|
|
348
1903
|
}
|
|
349
|
-
next.push(assistant);
|
|
1904
|
+
if (assistant.content || toolCalls.length) next.push(assistant);
|
|
350
1905
|
for (const call of toolCalls) {
|
|
351
1906
|
const result = toolResults.find((candidate) => candidate.callId === call.id);
|
|
352
|
-
const content = result ? result.ok ?
|
|
1907
|
+
const content = result ? result.ok ? stringify2(result.output) : `error: ${result.error?.message ?? "unknown"}` : "error: missing tool result";
|
|
353
1908
|
const message = {
|
|
354
1909
|
role: "tool",
|
|
355
1910
|
content,
|
|
@@ -370,17 +1925,30 @@ var AgentService = class {
|
|
|
370
1925
|
return next;
|
|
371
1926
|
}
|
|
372
1927
|
_session() {
|
|
1928
|
+
if (this.config.session) return this.config.session;
|
|
373
1929
|
const session = this.ctx.session;
|
|
374
1930
|
if (!session) throw new AgentError("session service is required");
|
|
375
1931
|
return session;
|
|
376
1932
|
}
|
|
377
1933
|
_tools() {
|
|
378
|
-
const
|
|
1934
|
+
const scoped = this.ctx.reflect.get("tools", false);
|
|
1935
|
+
const tools = scoped ?? this.ctx.tools;
|
|
379
1936
|
if (!tools) throw new AgentError("tools service is required");
|
|
380
1937
|
return tools;
|
|
381
1938
|
}
|
|
1939
|
+
async _resolveAvailableTools() {
|
|
1940
|
+
const executable = this._tools().list();
|
|
1941
|
+
const promptService = this.ctx.reflect.get("systemPrompt", false);
|
|
1942
|
+
if (!promptService) return executable;
|
|
1943
|
+
const schemas = await promptService.toolSchemas();
|
|
1944
|
+
if (!schemas.length) return executable;
|
|
1945
|
+
const byName = new Map(executable.map((tool) => [tool.schema.name, tool]));
|
|
1946
|
+
return schemas.map((schema) => byName.get(schema.name)).filter((tool) => tool !== void 0);
|
|
1947
|
+
}
|
|
382
1948
|
_llm() {
|
|
383
|
-
return this.config.llm;
|
|
1949
|
+
if (this.config.llm) return this.config.llm;
|
|
1950
|
+
const service = this.ctx.reflect.get("llm", false);
|
|
1951
|
+
return service?.current();
|
|
384
1952
|
}
|
|
385
1953
|
};
|
|
386
1954
|
function defaultContextSummary(messages, usage) {
|
|
@@ -506,6 +2074,15 @@ function defineAgent(definition) {
|
|
|
506
2074
|
agentService = ctx.reflect.get("agent");
|
|
507
2075
|
if (system) agentService.inbox.inject("agentSystem", system);
|
|
508
2076
|
}
|
|
2077
|
+
if (system) {
|
|
2078
|
+
const prompt = ctx.reflect.get("systemPrompt");
|
|
2079
|
+
if (prompt) {
|
|
2080
|
+
ctx.fiber.effect(() => prompt.registerSection({
|
|
2081
|
+
name: `${name2}:persona`,
|
|
2082
|
+
content: system
|
|
2083
|
+
}), `ctx.systemPrompt.section(${JSON.stringify(name2)})`);
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
509
2086
|
for (const tool of definition.tools ?? []) {
|
|
510
2087
|
dynamic.tools.register(tool);
|
|
511
2088
|
}
|
|
@@ -520,12 +2097,2505 @@ function defineAgent(definition) {
|
|
|
520
2097
|
}
|
|
521
2098
|
};
|
|
522
2099
|
}
|
|
2100
|
+
|
|
2101
|
+
// packages/agent/src/live.ts
|
|
2102
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
2103
|
+
|
|
2104
|
+
// packages/core/src/utils.ts
|
|
2105
|
+
var DisposableList = class {
|
|
2106
|
+
sn = 0;
|
|
2107
|
+
map = /* @__PURE__ */ new Map();
|
|
2108
|
+
weak = /* @__PURE__ */ new WeakMap();
|
|
2109
|
+
get length() {
|
|
2110
|
+
return this.map.size;
|
|
2111
|
+
}
|
|
2112
|
+
push(value) {
|
|
2113
|
+
const sn = ++this.sn;
|
|
2114
|
+
this.map.set(sn, value);
|
|
2115
|
+
this.weak.set(value, sn);
|
|
2116
|
+
return () => this.map.delete(sn);
|
|
2117
|
+
}
|
|
2118
|
+
unshift(value) {
|
|
2119
|
+
const sn = ++this.sn;
|
|
2120
|
+
const entries = [...this.map.entries()];
|
|
2121
|
+
this.map.clear();
|
|
2122
|
+
this.map.set(sn, value);
|
|
2123
|
+
this.weak.set(value, sn);
|
|
2124
|
+
for (const [oldSn, oldValue] of entries) {
|
|
2125
|
+
this.map.set(oldSn, oldValue);
|
|
2126
|
+
}
|
|
2127
|
+
return () => this.map.delete(sn);
|
|
2128
|
+
}
|
|
2129
|
+
delete(value) {
|
|
2130
|
+
const sn = this.weak.get(value);
|
|
2131
|
+
if (!sn) return false;
|
|
2132
|
+
return this.map.delete(sn);
|
|
2133
|
+
}
|
|
2134
|
+
clear() {
|
|
2135
|
+
const values = [...this.map.values()];
|
|
2136
|
+
this.map.clear();
|
|
2137
|
+
return values.reverse();
|
|
2138
|
+
}
|
|
2139
|
+
[Symbol.iterator]() {
|
|
2140
|
+
return this.map.values();
|
|
2141
|
+
}
|
|
2142
|
+
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
2143
|
+
return [...this];
|
|
2144
|
+
}
|
|
2145
|
+
};
|
|
2146
|
+
var symbols = {
|
|
2147
|
+
shadow: /* @__PURE__ */ Symbol.for("tnega.shadow"),
|
|
2148
|
+
caller: /* @__PURE__ */ Symbol.for("tnega.caller"),
|
|
2149
|
+
receiver: /* @__PURE__ */ Symbol.for("tnega.receiver"),
|
|
2150
|
+
original: /* @__PURE__ */ Symbol.for("tnega.original"),
|
|
2151
|
+
metadata: /* @__PURE__ */ Symbol.for("tnega.metadata"),
|
|
2152
|
+
initHooks: /* @__PURE__ */ Symbol.for("tnega.initHooks"),
|
|
2153
|
+
checkProto: /* @__PURE__ */ Symbol.for("tnega.checkProto"),
|
|
2154
|
+
effect: /* @__PURE__ */ Symbol.for("tnega.effect"),
|
|
2155
|
+
filter: /* @__PURE__ */ Symbol.for("tnega.filter"),
|
|
2156
|
+
isolate: /* @__PURE__ */ Symbol.for("tnega.isolate"),
|
|
2157
|
+
intercept: /* @__PURE__ */ Symbol.for("tnega.intercept"),
|
|
2158
|
+
init: /* @__PURE__ */ Symbol.for("tnega.init"),
|
|
2159
|
+
check: /* @__PURE__ */ Symbol.for("tnega.check"),
|
|
2160
|
+
config: /* @__PURE__ */ Symbol.for("tnega.config"),
|
|
2161
|
+
invoke: /* @__PURE__ */ Symbol.for("tnega.invoke"),
|
|
2162
|
+
extend: /* @__PURE__ */ Symbol.for("tnega.extend"),
|
|
2163
|
+
tracker: /* @__PURE__ */ Symbol.for("tnega.tracker"),
|
|
2164
|
+
resolveConfig: /* @__PURE__ */ Symbol.for("tnega.resolveConfig")
|
|
2165
|
+
};
|
|
2166
|
+
var GeneratorFunction = function* () {
|
|
2167
|
+
}.constructor;
|
|
2168
|
+
var AsyncGeneratorFunction = async function* () {
|
|
2169
|
+
}.constructor;
|
|
2170
|
+
function isConstructor(func) {
|
|
2171
|
+
if (typeof func !== "function") return false;
|
|
2172
|
+
if (func instanceof GeneratorFunction) return false;
|
|
2173
|
+
if (AsyncGeneratorFunction !== Function && func instanceof AsyncGeneratorFunction) return false;
|
|
2174
|
+
return /^class\s/.test(Function.prototype.toString.call(func));
|
|
2175
|
+
}
|
|
2176
|
+
function isObject(value) {
|
|
2177
|
+
return !!value && (typeof value === "object" || typeof value === "function");
|
|
2178
|
+
}
|
|
2179
|
+
function isNullable(value) {
|
|
2180
|
+
return value == null;
|
|
2181
|
+
}
|
|
2182
|
+
function joinPrototype(proto1, proto2) {
|
|
2183
|
+
if (proto1 === Object.prototype) return proto2;
|
|
2184
|
+
const result = Object.create(joinPrototype(Object.getPrototypeOf(proto1), proto2));
|
|
2185
|
+
for (const key of Reflect.ownKeys(proto1)) {
|
|
2186
|
+
Object.defineProperty(result, key, Reflect.getOwnPropertyDescriptor(proto1, key));
|
|
2187
|
+
}
|
|
2188
|
+
return result;
|
|
2189
|
+
}
|
|
2190
|
+
function getPropertyDescriptor(target, prop) {
|
|
2191
|
+
let proto = target;
|
|
2192
|
+
while (proto) {
|
|
2193
|
+
const desc = Reflect.getOwnPropertyDescriptor(proto, prop);
|
|
2194
|
+
if (desc) return desc;
|
|
2195
|
+
proto = Object.getPrototypeOf(proto);
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
function getTraceable(ctx, value) {
|
|
2199
|
+
if (!isObject(value)) return value;
|
|
2200
|
+
if (Object.hasOwn(value, symbols.shadow)) {
|
|
2201
|
+
return Object.getPrototypeOf(value);
|
|
2202
|
+
}
|
|
2203
|
+
const tracker = value[symbols.tracker];
|
|
2204
|
+
if (!tracker) return value;
|
|
2205
|
+
return createTraceable(ctx, value, tracker);
|
|
2206
|
+
}
|
|
2207
|
+
function withProps(target, props) {
|
|
2208
|
+
if (!props) return target;
|
|
2209
|
+
return new Proxy(target, {
|
|
2210
|
+
get: (target2, prop, receiver) => {
|
|
2211
|
+
if (prop in props && prop !== "constructor") return Reflect.get(props, prop, receiver);
|
|
2212
|
+
return Reflect.get(target2, prop, receiver);
|
|
2213
|
+
},
|
|
2214
|
+
set: (target2, prop, value, receiver) => {
|
|
2215
|
+
if (prop in props && prop !== "constructor") return Reflect.set(props, prop, value, receiver);
|
|
2216
|
+
return Reflect.set(target2, prop, value, receiver);
|
|
2217
|
+
}
|
|
2218
|
+
});
|
|
2219
|
+
}
|
|
2220
|
+
function withProp(target, prop, value) {
|
|
2221
|
+
return withProps(target, Object.defineProperty(/* @__PURE__ */ Object.create(null), prop, {
|
|
2222
|
+
value,
|
|
2223
|
+
writable: false
|
|
2224
|
+
}));
|
|
2225
|
+
}
|
|
2226
|
+
function createShadow(ctx, target, property, receiver) {
|
|
2227
|
+
if (!property) return receiver;
|
|
2228
|
+
const origin = getPropertyDescriptor(target, property)?.value;
|
|
2229
|
+
if (!origin) return receiver;
|
|
2230
|
+
return withProp(receiver, property, ctx.extend({ [symbols.shadow]: origin }));
|
|
2231
|
+
}
|
|
2232
|
+
function createShadowMethod(ctx, value, outer, shadow) {
|
|
2233
|
+
const proxy = new Proxy(value, {
|
|
2234
|
+
apply: (target, thisArg, args) => {
|
|
2235
|
+
if (thisArg === outer) thisArg = shadow;
|
|
2236
|
+
return getTraceable(ctx, Reflect.apply(target, thisArg, args));
|
|
2237
|
+
}
|
|
2238
|
+
});
|
|
2239
|
+
return proxy;
|
|
2240
|
+
}
|
|
2241
|
+
function createTraceable(ctx, value, tracker) {
|
|
2242
|
+
const caller = ctx[symbols.shadow] ?? ctx;
|
|
2243
|
+
if (ctx[symbols.shadow]) {
|
|
2244
|
+
ctx = Object.getPrototypeOf(ctx);
|
|
2245
|
+
}
|
|
2246
|
+
const proxy = new Proxy(value, {
|
|
2247
|
+
get: (target, prop, receiver) => {
|
|
2248
|
+
if (prop === symbols.original) return target;
|
|
2249
|
+
if (prop === symbols.caller) return caller;
|
|
2250
|
+
if (prop === tracker.property) return ctx;
|
|
2251
|
+
if (typeof prop === "symbol") {
|
|
2252
|
+
return Reflect.get(target, prop, receiver);
|
|
2253
|
+
}
|
|
2254
|
+
if (tracker.associate && ctx.reflect.props[`${tracker.associate}.${prop}`]) {
|
|
2255
|
+
return Reflect.get(ctx, `${tracker.associate}.${prop}`, withProp(ctx, symbols.receiver, receiver));
|
|
2256
|
+
}
|
|
2257
|
+
let shadow;
|
|
2258
|
+
let innerValue;
|
|
2259
|
+
const desc = getPropertyDescriptor(target, prop);
|
|
2260
|
+
if (desc?.value !== void 0) {
|
|
2261
|
+
innerValue = desc.value;
|
|
2262
|
+
} else {
|
|
2263
|
+
shadow = createShadow(ctx, target, tracker.property, receiver);
|
|
2264
|
+
innerValue = Reflect.get(target, prop, shadow);
|
|
2265
|
+
}
|
|
2266
|
+
const innerTracker = innerValue?.[symbols.tracker];
|
|
2267
|
+
if (innerTracker) {
|
|
2268
|
+
return createTraceable(ctx, innerValue, innerTracker);
|
|
2269
|
+
} else if (!tracker.noShadow && typeof innerValue === "function") {
|
|
2270
|
+
shadow ??= createShadow(ctx, target, tracker.property, receiver);
|
|
2271
|
+
return createShadowMethod(ctx, innerValue, receiver, shadow);
|
|
2272
|
+
} else {
|
|
2273
|
+
return innerValue;
|
|
2274
|
+
}
|
|
2275
|
+
},
|
|
2276
|
+
set: (target, prop, value2, receiver) => {
|
|
2277
|
+
if (prop === symbols.original || prop === symbols.caller || prop === tracker.property) return false;
|
|
2278
|
+
if (typeof prop === "symbol") {
|
|
2279
|
+
return Reflect.set(target, prop, value2, receiver);
|
|
2280
|
+
}
|
|
2281
|
+
if (tracker.associate && ctx.reflect.props[`${tracker.associate}.${prop}`]) {
|
|
2282
|
+
return Reflect.set(ctx, `${tracker.associate}.${prop}`, value2, withProp(ctx, symbols.receiver, receiver));
|
|
2283
|
+
}
|
|
2284
|
+
const shadow = createShadow(ctx, target, tracker.property, receiver);
|
|
2285
|
+
return Reflect.set(target, prop, value2, shadow);
|
|
2286
|
+
},
|
|
2287
|
+
apply: (target, thisArg, args) => {
|
|
2288
|
+
const receiver = tracker.noShadow ? proxy : createShadow(ctx, target, tracker.property, proxy);
|
|
2289
|
+
return applyTraceable(receiver, target, thisArg, args);
|
|
2290
|
+
}
|
|
2291
|
+
});
|
|
2292
|
+
return proxy;
|
|
2293
|
+
}
|
|
2294
|
+
function applyTraceable(proxy, value, thisArg, args) {
|
|
2295
|
+
if (!value[symbols.invoke]) return Reflect.apply(value, thisArg, args);
|
|
2296
|
+
return value[symbols.invoke].apply(proxy, args);
|
|
2297
|
+
}
|
|
2298
|
+
function createCallable(name2, proto, tracker) {
|
|
2299
|
+
const self = function(...args) {
|
|
2300
|
+
const proxy = createTraceable(self.ctx, self, tracker);
|
|
2301
|
+
return Reflect.apply(proxy, this, args);
|
|
2302
|
+
};
|
|
2303
|
+
Object.defineProperty(self, "name", { value: name2, configurable: true, writable: true });
|
|
2304
|
+
return Object.setPrototypeOf(self, proto);
|
|
2305
|
+
}
|
|
2306
|
+
function handleError(info, reason, getOuterStack) {
|
|
2307
|
+
const innerLines = info.error.stack.split("\n");
|
|
2308
|
+
if (typeof reason?.stack !== "string") {
|
|
2309
|
+
const outerError = new Error(reason);
|
|
2310
|
+
const lines2 = outerError.stack.split("\n");
|
|
2311
|
+
lines2.splice(1, Infinity, ...getOuterStack());
|
|
2312
|
+
outerError.stack = lines2.join("\n");
|
|
2313
|
+
throw outerError;
|
|
2314
|
+
}
|
|
2315
|
+
const lines = reason.stack.split("\n");
|
|
2316
|
+
const index = lines.indexOf(innerLines[2]);
|
|
2317
|
+
if (index === -1) throw reason;
|
|
2318
|
+
lines.splice(index - info.offset, Infinity, ...getOuterStack());
|
|
2319
|
+
reason.stack = lines.join("\n");
|
|
2320
|
+
throw reason;
|
|
2321
|
+
}
|
|
2322
|
+
function composeError(callback, getOuterStack = buildOuterStack()) {
|
|
2323
|
+
const info = { offset: 1, error: new Error() };
|
|
2324
|
+
try {
|
|
2325
|
+
const result = callback(info);
|
|
2326
|
+
if (isObject(result) && "then" in result) {
|
|
2327
|
+
return result.then(void 0, (reason) => handleError(info, reason, getOuterStack));
|
|
2328
|
+
}
|
|
2329
|
+
return result;
|
|
2330
|
+
} catch (reason) {
|
|
2331
|
+
handleError(info, reason, getOuterStack);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
function buildOuterStack(offset = 0) {
|
|
2335
|
+
const outerError = new Error();
|
|
2336
|
+
return () => outerError.stack.split("\n").slice(3 + offset);
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// packages/core/src/events.ts
|
|
2340
|
+
function isBailed(value) {
|
|
2341
|
+
return value !== null && value !== false && value !== void 0;
|
|
2342
|
+
}
|
|
2343
|
+
var EventsService = class {
|
|
2344
|
+
constructor(ctx) {
|
|
2345
|
+
this.ctx = ctx;
|
|
2346
|
+
Object.defineProperty(this, symbols.tracker, {
|
|
2347
|
+
value: { property: "ctx", noShadow: true }
|
|
2348
|
+
});
|
|
2349
|
+
this.on("internal/listener", function(name2, listener, options) {
|
|
2350
|
+
if (name2 === "internal/update" && !options.global) {
|
|
2351
|
+
const hooks = this.fiber._hooks["internal/update"] ??= new DisposableList();
|
|
2352
|
+
const method = options.prepend ? "unshift" : "push";
|
|
2353
|
+
return hooks[method](listener);
|
|
2354
|
+
}
|
|
2355
|
+
});
|
|
2356
|
+
this.on("internal/update", function(config, noSave, next) {
|
|
2357
|
+
const cbs = [...this.fiber._hooks["internal/update"] ?? []];
|
|
2358
|
+
const _next = () => {
|
|
2359
|
+
const cb = cbs.shift() ?? next;
|
|
2360
|
+
return cb.call(this, config, noSave, _next);
|
|
2361
|
+
};
|
|
2362
|
+
return _next();
|
|
2363
|
+
}, { global: true, prepend: true });
|
|
2364
|
+
}
|
|
2365
|
+
ctx;
|
|
2366
|
+
_hooks = /* @__PURE__ */ Object.create(null);
|
|
2367
|
+
_resolve(type, args) {
|
|
2368
|
+
const thisArg = typeof args[0] === "object" || typeof args[0] === "function" ? args.shift() : null;
|
|
2369
|
+
const name2 = args.shift();
|
|
2370
|
+
if (!name2.startsWith("internal/") && this._hooks["internal/dispatch"]?.length) {
|
|
2371
|
+
this.emit("internal/dispatch", type, name2, args, thisArg);
|
|
2372
|
+
}
|
|
2373
|
+
const filter = thisArg?.[Context.filter];
|
|
2374
|
+
return [thisArg, (this._hooks[name2] || []).filter((hook) => hook.global || !filter || filter.call(thisArg, hook.ctx)).map((hook) => hook.callback)];
|
|
2375
|
+
}
|
|
2376
|
+
dispatch(type, args) {
|
|
2377
|
+
const [thisArg, callbacks] = this._resolve(type, args);
|
|
2378
|
+
return callbacks.map((callback) => callback.bind(thisArg));
|
|
2379
|
+
}
|
|
2380
|
+
async parallel(...args) {
|
|
2381
|
+
const [thisArg, callbacks] = this._resolve("parallel", args);
|
|
2382
|
+
const results = await Promise.allSettled(callbacks.map(async (callback) => Reflect.apply(callback, thisArg, args)));
|
|
2383
|
+
const errors = results.filter((result) => result.status === "rejected").map((result) => result.reason);
|
|
2384
|
+
if (errors.length) throw new AggregateError(errors);
|
|
2385
|
+
}
|
|
2386
|
+
emit(...args) {
|
|
2387
|
+
const [thisArg, callbacks] = this._resolve("emit", args);
|
|
2388
|
+
for (const callback of callbacks) Reflect.apply(callback, thisArg, args);
|
|
2389
|
+
}
|
|
2390
|
+
async serial(...args) {
|
|
2391
|
+
const [thisArg, callbacks] = this._resolve("serial", args);
|
|
2392
|
+
for (const callback of callbacks) {
|
|
2393
|
+
const result = await Reflect.apply(callback, thisArg, args);
|
|
2394
|
+
if (isBailed(result)) return result;
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
bail(...args) {
|
|
2398
|
+
const [thisArg, callbacks] = this._resolve("bail", args);
|
|
2399
|
+
for (const callback of callbacks) {
|
|
2400
|
+
const result = Reflect.apply(callback, thisArg, args);
|
|
2401
|
+
if (isBailed(result)) return result;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
waterfall(...args) {
|
|
2405
|
+
const [thisArg, callbacks] = this._resolve("waterfall", args);
|
|
2406
|
+
const inner = args.pop();
|
|
2407
|
+
const next = () => {
|
|
2408
|
+
const callback = callbacks.shift();
|
|
2409
|
+
return callback ? Reflect.apply(callback, thisArg, args) : inner(...args);
|
|
2410
|
+
};
|
|
2411
|
+
args.push(next);
|
|
2412
|
+
return next();
|
|
2413
|
+
}
|
|
2414
|
+
async waterfallAsync(...args) {
|
|
2415
|
+
const [thisArg, callbacks] = this._resolve("waterfallAsync", args);
|
|
2416
|
+
const inner = args.pop();
|
|
2417
|
+
const next = async () => {
|
|
2418
|
+
const callback = callbacks.shift();
|
|
2419
|
+
return callback ? await Reflect.apply(callback, thisArg, args) : inner(...args);
|
|
2420
|
+
};
|
|
2421
|
+
args.push(next);
|
|
2422
|
+
return next();
|
|
2423
|
+
}
|
|
2424
|
+
register(label, hooks, callback, options) {
|
|
2425
|
+
const method = options.prepend ? "unshift" : "push";
|
|
2426
|
+
return this.ctx.fiber.effect(() => {
|
|
2427
|
+
hooks[method]({ ctx: this.ctx, callback, ...options });
|
|
2428
|
+
return () => this.unregister(hooks, callback);
|
|
2429
|
+
}, label);
|
|
2430
|
+
}
|
|
2431
|
+
unregister(hooks, callback) {
|
|
2432
|
+
const index = hooks.findIndex((hook) => hook.callback === callback);
|
|
2433
|
+
if (index >= 0) {
|
|
2434
|
+
hooks.splice(index, 1);
|
|
2435
|
+
return true;
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
on(name2, listener, options = {}) {
|
|
2439
|
+
if (typeof options !== "object") {
|
|
2440
|
+
options = { prepend: options };
|
|
2441
|
+
}
|
|
2442
|
+
this.ctx.fiber.assertActive();
|
|
2443
|
+
listener = this.ctx.reflect.bind(listener);
|
|
2444
|
+
const result = this.bail(this.ctx, "internal/listener", name2, listener, options);
|
|
2445
|
+
if (result) return result;
|
|
2446
|
+
const hooks = this._hooks[name2] ??= [];
|
|
2447
|
+
return this.register(`ctx.on(${JSON.stringify(name2)})`, hooks, listener, options);
|
|
2448
|
+
}
|
|
2449
|
+
once(name2, listener, options = {}) {
|
|
2450
|
+
const dispose = this.on(name2, function(...args) {
|
|
2451
|
+
dispose();
|
|
2452
|
+
return listener.apply(this, args);
|
|
2453
|
+
}, options);
|
|
2454
|
+
return dispose;
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
|
|
2458
|
+
// packages/core/src/fiber.ts
|
|
2459
|
+
var INACTIVE = "__INACTIVE__";
|
|
2460
|
+
var ValidationError = class extends TypeError {
|
|
2461
|
+
name = "ValidationError";
|
|
2462
|
+
constructor(issues) {
|
|
2463
|
+
super("invalid config:\n" + issues.map((issue) => {
|
|
2464
|
+
if (issue.path) {
|
|
2465
|
+
return ` - ${issue.message} (at ${issue.path.join(".")})`;
|
|
2466
|
+
}
|
|
2467
|
+
return ` - ${issue.message}`;
|
|
2468
|
+
}).join("\n"));
|
|
2469
|
+
}
|
|
2470
|
+
};
|
|
2471
|
+
function resolveConfig(runtime, config) {
|
|
2472
|
+
if (!runtime.Config) return config;
|
|
2473
|
+
const schema = runtime.Config;
|
|
2474
|
+
if (typeof schema === "function") return schema(config);
|
|
2475
|
+
const validate = schema?.["~standard"]?.validate;
|
|
2476
|
+
if (typeof validate === "function") {
|
|
2477
|
+
const result = validate(config);
|
|
2478
|
+
if ("then" in result) {
|
|
2479
|
+
throw new TypeError("Async config validation is not supported");
|
|
2480
|
+
}
|
|
2481
|
+
if (result.issues) {
|
|
2482
|
+
throw new ValidationError(result.issues);
|
|
2483
|
+
}
|
|
2484
|
+
return result.value;
|
|
2485
|
+
}
|
|
2486
|
+
return config;
|
|
2487
|
+
}
|
|
2488
|
+
var CordisErrorCode = {
|
|
2489
|
+
INACTIVE_EFFECT: "cannot create effect on inactive context"
|
|
2490
|
+
};
|
|
2491
|
+
var CordisError = class extends Error {
|
|
2492
|
+
constructor(code, message) {
|
|
2493
|
+
super(message ?? CordisErrorCode[code]);
|
|
2494
|
+
this.code = code;
|
|
2495
|
+
}
|
|
2496
|
+
code;
|
|
2497
|
+
};
|
|
2498
|
+
var Fiber = class {
|
|
2499
|
+
constructor(parent, config, inject, runtime, getOuterStack) {
|
|
2500
|
+
this.parent = parent;
|
|
2501
|
+
this.inject = inject;
|
|
2502
|
+
this.runtime = runtime;
|
|
2503
|
+
const collect = (dispose) => {
|
|
2504
|
+
this._disposables.push(dispose);
|
|
2505
|
+
};
|
|
2506
|
+
if (runtime) {
|
|
2507
|
+
this.uid = parent.registry.counter;
|
|
2508
|
+
this.ctx = this.context = parent.extend({ fiber: this });
|
|
2509
|
+
const injectEntries = Object.entries(this.inject);
|
|
2510
|
+
if (injectEntries.length) {
|
|
2511
|
+
this.ctx[symbols.intercept] = Object.create(parent[symbols.intercept]);
|
|
2512
|
+
for (const [name2, config2] of injectEntries) {
|
|
2513
|
+
if (isNullable(config2)) continue;
|
|
2514
|
+
this.ctx[symbols.intercept][name2] = config2;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
this._runner = {
|
|
2518
|
+
epoch: INACTIVE,
|
|
2519
|
+
getOuterStack,
|
|
2520
|
+
execute: () => {
|
|
2521
|
+
if (isConstructor(runtime.callback)) {
|
|
2522
|
+
const instance = new runtime.callback(this.ctx, this.config);
|
|
2523
|
+
for (const hook of instance?.[symbols.initHooks] ?? []) {
|
|
2524
|
+
hook();
|
|
2525
|
+
}
|
|
2526
|
+
return instance?.[symbols.init]?.();
|
|
2527
|
+
} else {
|
|
2528
|
+
return runtime.callback(this.ctx, this.config);
|
|
2529
|
+
}
|
|
2530
|
+
},
|
|
2531
|
+
collect
|
|
2532
|
+
};
|
|
2533
|
+
this.context.emit(this, "internal/plugin");
|
|
2534
|
+
for (const name2 of Object.keys(this.inject)) {
|
|
2535
|
+
this._checkImpl(name2);
|
|
2536
|
+
}
|
|
2537
|
+
this.dispose = parent.fiber.effect(() => {
|
|
2538
|
+
const remove = runtime.fibers.push(this);
|
|
2539
|
+
try {
|
|
2540
|
+
this.config = resolveConfig(runtime, config);
|
|
2541
|
+
this._refresh();
|
|
2542
|
+
} catch (error) {
|
|
2543
|
+
this.ctx.logger.error(error);
|
|
2544
|
+
this._error = error;
|
|
2545
|
+
}
|
|
2546
|
+
return async () => {
|
|
2547
|
+
this.uid = null;
|
|
2548
|
+
this.context.emit(this, "internal/plugin");
|
|
2549
|
+
if (this.ctx.registry.has(runtime.callback)) {
|
|
2550
|
+
remove();
|
|
2551
|
+
if (!runtime.fibers.length) {
|
|
2552
|
+
this.ctx.registry.delete(runtime.callback);
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
this._setEpoch(INACTIVE);
|
|
2556
|
+
while (this.inertia) {
|
|
2557
|
+
await this.inertia;
|
|
2558
|
+
}
|
|
2559
|
+
};
|
|
2560
|
+
}, "ctx.plugin()");
|
|
2561
|
+
} else {
|
|
2562
|
+
this.uid = 0;
|
|
2563
|
+
this.ctx = this.context = parent;
|
|
2564
|
+
this.state = 2 /* ACTIVE */;
|
|
2565
|
+
this.store = /* @__PURE__ */ Object.create(null);
|
|
2566
|
+
this._runner = {
|
|
2567
|
+
epoch: "",
|
|
2568
|
+
getOuterStack,
|
|
2569
|
+
execute: () => {
|
|
2570
|
+
},
|
|
2571
|
+
collect
|
|
2572
|
+
};
|
|
2573
|
+
this.dispose = () => this.restart();
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
parent;
|
|
2577
|
+
inject;
|
|
2578
|
+
runtime;
|
|
2579
|
+
uid;
|
|
2580
|
+
ctx;
|
|
2581
|
+
config;
|
|
2582
|
+
state = 0 /* PENDING */;
|
|
2583
|
+
dispose;
|
|
2584
|
+
store;
|
|
2585
|
+
inertia;
|
|
2586
|
+
_hooks = /* @__PURE__ */ Object.create(null);
|
|
2587
|
+
_disposables = new DisposableList();
|
|
2588
|
+
context;
|
|
2589
|
+
_error = void 0;
|
|
2590
|
+
_runner;
|
|
2591
|
+
_store = /* @__PURE__ */ Object.create(null);
|
|
2592
|
+
_restarting;
|
|
2593
|
+
get name() {
|
|
2594
|
+
if (this.runtime?.name) return this.runtime.name;
|
|
2595
|
+
let fiber = this.parent.fiber;
|
|
2596
|
+
while (fiber !== fiber.parent.fiber) {
|
|
2597
|
+
if (fiber.runtime?.name) return fiber.runtime.name;
|
|
2598
|
+
fiber = fiber.parent.fiber;
|
|
2599
|
+
}
|
|
2600
|
+
return "root";
|
|
2601
|
+
}
|
|
2602
|
+
assertActive() {
|
|
2603
|
+
if (this.uid !== null) return;
|
|
2604
|
+
throw new CordisError("INACTIVE_EFFECT");
|
|
2605
|
+
}
|
|
2606
|
+
_execute(runner) {
|
|
2607
|
+
const oldEpoch = runner.epoch;
|
|
2608
|
+
return composeError((info) => {
|
|
2609
|
+
const safeCollect = (dispose) => {
|
|
2610
|
+
if (typeof dispose === "function") {
|
|
2611
|
+
runner.collect(dispose);
|
|
2612
|
+
} else if (!isNullable(dispose)) {
|
|
2613
|
+
throw new TypeError("Invalid effect");
|
|
2614
|
+
}
|
|
2615
|
+
};
|
|
2616
|
+
const effect = runner.execute.call(this);
|
|
2617
|
+
if (typeof effect === "function") {
|
|
2618
|
+
return runner.collect(effect);
|
|
2619
|
+
} else if (isNullable(effect)) {
|
|
2620
|
+
return;
|
|
2621
|
+
} else if (!isObject(effect)) {
|
|
2622
|
+
throw new TypeError("Invalid effect");
|
|
2623
|
+
} else if ("then" in effect) {
|
|
2624
|
+
return effect.then(safeCollect);
|
|
2625
|
+
} else if (Symbol.iterator in effect) {
|
|
2626
|
+
info.error = new Error();
|
|
2627
|
+
const iter = effect[Symbol.iterator]();
|
|
2628
|
+
while (true) {
|
|
2629
|
+
const result = iter.next();
|
|
2630
|
+
safeCollect(result.value);
|
|
2631
|
+
if (result.done) return;
|
|
2632
|
+
}
|
|
2633
|
+
} else if (Symbol.asyncIterator in effect) {
|
|
2634
|
+
const iter = effect[Symbol.asyncIterator]();
|
|
2635
|
+
return (async () => {
|
|
2636
|
+
await Promise.resolve();
|
|
2637
|
+
info.error = new Error();
|
|
2638
|
+
while (true) {
|
|
2639
|
+
if (runner.epoch !== oldEpoch) return;
|
|
2640
|
+
const result = await iter.next();
|
|
2641
|
+
safeCollect(result.value);
|
|
2642
|
+
if (result.done) return;
|
|
2643
|
+
}
|
|
2644
|
+
})();
|
|
2645
|
+
} else {
|
|
2646
|
+
throw new TypeError("Invalid effect");
|
|
2647
|
+
}
|
|
2648
|
+
}, runner.getOuterStack);
|
|
2649
|
+
}
|
|
2650
|
+
effect(execute, label = "anonymous") {
|
|
2651
|
+
this.assertActive();
|
|
2652
|
+
const disposables = [];
|
|
2653
|
+
const dispose = () => {
|
|
2654
|
+
let task2;
|
|
2655
|
+
for (const dispose2 of disposables.splice(0).reverse()) {
|
|
2656
|
+
if (task2) {
|
|
2657
|
+
task2 = task2.then(dispose2);
|
|
2658
|
+
} else {
|
|
2659
|
+
const result = dispose2();
|
|
2660
|
+
if (isObject(result) && "then" in result) {
|
|
2661
|
+
task2 = result;
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
return task2;
|
|
2666
|
+
};
|
|
2667
|
+
const meta = { label, children: [] };
|
|
2668
|
+
const runner = {
|
|
2669
|
+
execute,
|
|
2670
|
+
epoch: true,
|
|
2671
|
+
collect: (dispose2) => {
|
|
2672
|
+
disposables.push(dispose2);
|
|
2673
|
+
this._disposables.delete(dispose2);
|
|
2674
|
+
if (dispose2[symbols.effect]) {
|
|
2675
|
+
meta.children.push(dispose2[symbols.effect]);
|
|
2676
|
+
}
|
|
2677
|
+
},
|
|
2678
|
+
getOuterStack: buildOuterStack()
|
|
2679
|
+
};
|
|
2680
|
+
let task;
|
|
2681
|
+
try {
|
|
2682
|
+
task = this._execute(runner);
|
|
2683
|
+
} catch (reason) {
|
|
2684
|
+
dispose();
|
|
2685
|
+
throw reason;
|
|
2686
|
+
}
|
|
2687
|
+
task?.catch(dispose).catch((error) => this.ctx.logger.error(error));
|
|
2688
|
+
const wrapper = Object.defineProperty(() => {
|
|
2689
|
+
if (!runner.epoch) return;
|
|
2690
|
+
runner.epoch = false;
|
|
2691
|
+
return task ? task.then(dispose) : dispose();
|
|
2692
|
+
}, symbols.effect, { value: meta });
|
|
2693
|
+
const disposeAsync = () => {
|
|
2694
|
+
if (!runner.epoch) return;
|
|
2695
|
+
runner.epoch = false;
|
|
2696
|
+
return dispose();
|
|
2697
|
+
};
|
|
2698
|
+
wrapper.then = async (onFulfilled, onRejected) => {
|
|
2699
|
+
return Promise.resolve(task).then(() => disposeAsync).then(onFulfilled, onRejected);
|
|
2700
|
+
};
|
|
2701
|
+
disposables.push(this._disposables.push(wrapper));
|
|
2702
|
+
return wrapper;
|
|
2703
|
+
}
|
|
2704
|
+
getEffects() {
|
|
2705
|
+
return [...this._disposables].map((dispose) => dispose[symbols.effect]).filter(Boolean);
|
|
2706
|
+
}
|
|
2707
|
+
_getState() {
|
|
2708
|
+
if (this.uid === null) return 4 /* DISPOSED */;
|
|
2709
|
+
if (this._error) return 3 /* FAILED */;
|
|
2710
|
+
if (this._runner.epoch !== INACTIVE) return 2 /* ACTIVE */;
|
|
2711
|
+
return 0 /* PENDING */;
|
|
2712
|
+
}
|
|
2713
|
+
_updateState(callback) {
|
|
2714
|
+
const oldState = this.state;
|
|
2715
|
+
this.state = callback?.() ?? this._getState();
|
|
2716
|
+
if (oldState === this.state) return;
|
|
2717
|
+
this.context.emit(this, "internal/status", oldState);
|
|
2718
|
+
if (oldState !== 2 /* ACTIVE */ && this.state !== 2 /* ACTIVE */) return;
|
|
2719
|
+
for (const key of Reflect.ownKeys(this.ctx.reflect.store)) {
|
|
2720
|
+
const impl = this.ctx.reflect.store[key];
|
|
2721
|
+
if (!impl) continue;
|
|
2722
|
+
if (impl.fiber !== this) continue;
|
|
2723
|
+
this.ctx.reflect.notify([impl.name]);
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
_checkImpl(name2) {
|
|
2727
|
+
const impl = this.ctx.reflect._getImpl(name2, true);
|
|
2728
|
+
if (!impl) return delete this._store[name2];
|
|
2729
|
+
try {
|
|
2730
|
+
if (impl.check && !impl.check.call(getTraceable(this.ctx, impl.value))) {
|
|
2731
|
+
return delete this._store[name2];
|
|
2732
|
+
}
|
|
2733
|
+
} catch (error) {
|
|
2734
|
+
impl.fiber.ctx.logger.error(error);
|
|
2735
|
+
return delete this._store[name2];
|
|
2736
|
+
}
|
|
2737
|
+
this._store[name2] = impl;
|
|
2738
|
+
}
|
|
2739
|
+
_refresh() {
|
|
2740
|
+
let epoch = "";
|
|
2741
|
+
for (const name2 of Object.keys(this.inject)) {
|
|
2742
|
+
const impl = this._store[name2];
|
|
2743
|
+
if (!impl) {
|
|
2744
|
+
epoch = INACTIVE;
|
|
2745
|
+
break;
|
|
2746
|
+
}
|
|
2747
|
+
epoch += ":" + impl.fiber.uid;
|
|
2748
|
+
}
|
|
2749
|
+
this._setEpoch(epoch);
|
|
2750
|
+
}
|
|
2751
|
+
_setEpoch(epoch) {
|
|
2752
|
+
const oldEpoch = this._runner.epoch;
|
|
2753
|
+
if (epoch === oldEpoch) return;
|
|
2754
|
+
this._runner.epoch = epoch;
|
|
2755
|
+
if (this.inertia) return;
|
|
2756
|
+
this._updateState(() => {
|
|
2757
|
+
if (epoch !== INACTIVE && oldEpoch === INACTIVE) {
|
|
2758
|
+
this.inertia = this._reload();
|
|
2759
|
+
return 1 /* LOADING */;
|
|
2760
|
+
} else {
|
|
2761
|
+
this.inertia = this._unload();
|
|
2762
|
+
return 5 /* UNLOADING */;
|
|
2763
|
+
}
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2766
|
+
async _reload() {
|
|
2767
|
+
this.store = { ...this._store };
|
|
2768
|
+
const oldEpoch = this._runner.epoch;
|
|
2769
|
+
try {
|
|
2770
|
+
await Promise.resolve();
|
|
2771
|
+
await this._execute(this._runner);
|
|
2772
|
+
} catch (reason) {
|
|
2773
|
+
this.ctx.logger.error(reason);
|
|
2774
|
+
this._error = reason;
|
|
2775
|
+
this._runner.epoch = INACTIVE;
|
|
2776
|
+
}
|
|
2777
|
+
this._updateState(() => {
|
|
2778
|
+
if (this._runner.epoch === oldEpoch) {
|
|
2779
|
+
this.inertia = void 0;
|
|
2780
|
+
} else {
|
|
2781
|
+
this.inertia = this._unload();
|
|
2782
|
+
return 5 /* UNLOADING */;
|
|
2783
|
+
}
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
async _unload() {
|
|
2787
|
+
await Promise.all(this._disposables.clear().map(async (dispose) => {
|
|
2788
|
+
try {
|
|
2789
|
+
await composeError(async (info) => {
|
|
2790
|
+
await Promise.resolve();
|
|
2791
|
+
info.error = new Error();
|
|
2792
|
+
await dispose();
|
|
2793
|
+
}, this._runner.getOuterStack);
|
|
2794
|
+
} catch (reason) {
|
|
2795
|
+
this.ctx.logger.error(reason);
|
|
2796
|
+
}
|
|
2797
|
+
}));
|
|
2798
|
+
this.store = void 0;
|
|
2799
|
+
this._updateState(() => {
|
|
2800
|
+
if (this._runner.epoch === INACTIVE) {
|
|
2801
|
+
this.inertia = void 0;
|
|
2802
|
+
} else {
|
|
2803
|
+
this.inertia = this._reload();
|
|
2804
|
+
return 1 /* LOADING */;
|
|
2805
|
+
}
|
|
2806
|
+
});
|
|
2807
|
+
}
|
|
2808
|
+
async await() {
|
|
2809
|
+
while (this.inertia || this._restarting) {
|
|
2810
|
+
await Promise.all([this.inertia, this._restarting].filter((task) => !!task));
|
|
2811
|
+
}
|
|
2812
|
+
if (this._error) throw this._error;
|
|
2813
|
+
return this;
|
|
2814
|
+
}
|
|
2815
|
+
async restart() {
|
|
2816
|
+
const fiber = this.ctx.fiber;
|
|
2817
|
+
fiber.assertActive();
|
|
2818
|
+
if (fiber._restarting) return fiber._restarting.then(() => void 0);
|
|
2819
|
+
let resolve2;
|
|
2820
|
+
const restarting = new Promise((resolvePromise) => {
|
|
2821
|
+
resolve2 = resolvePromise;
|
|
2822
|
+
});
|
|
2823
|
+
fiber._restarting = restarting;
|
|
2824
|
+
try {
|
|
2825
|
+
await this._runRestart(fiber);
|
|
2826
|
+
} finally {
|
|
2827
|
+
fiber._restarting = void 0;
|
|
2828
|
+
resolve2();
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
async _runRestart(fiber) {
|
|
2832
|
+
if (fiber.inertia) {
|
|
2833
|
+
fiber._setEpoch(INACTIVE);
|
|
2834
|
+
while (fiber.inertia) await fiber.inertia;
|
|
2835
|
+
}
|
|
2836
|
+
fiber._setEpoch(INACTIVE);
|
|
2837
|
+
fiber._refresh();
|
|
2838
|
+
while (fiber.inertia) await fiber.inertia;
|
|
2839
|
+
}
|
|
2840
|
+
update(config, noSave = false) {
|
|
2841
|
+
const fiber = this.ctx.fiber;
|
|
2842
|
+
fiber.assertActive();
|
|
2843
|
+
config = resolveConfig(fiber.runtime, config);
|
|
2844
|
+
fiber.context.waterfall(fiber.context, "internal/update", config, noSave, () => {
|
|
2845
|
+
fiber.config = config;
|
|
2846
|
+
fiber._error = void 0;
|
|
2847
|
+
return fiber.restart();
|
|
2848
|
+
});
|
|
2849
|
+
}
|
|
2850
|
+
};
|
|
2851
|
+
|
|
2852
|
+
// packages/core/src/logger.ts
|
|
2853
|
+
var Logger = class {
|
|
2854
|
+
constructor(options, service) {
|
|
2855
|
+
this.service = service;
|
|
2856
|
+
this.name = options.name;
|
|
2857
|
+
this.level = options.level;
|
|
2858
|
+
this.error = this._method("error");
|
|
2859
|
+
this.info = this._method("info");
|
|
2860
|
+
this.warn = this._method("warn");
|
|
2861
|
+
this.debug = this._method("debug");
|
|
2862
|
+
}
|
|
2863
|
+
service;
|
|
2864
|
+
name;
|
|
2865
|
+
level;
|
|
2866
|
+
error;
|
|
2867
|
+
info;
|
|
2868
|
+
warn;
|
|
2869
|
+
debug;
|
|
2870
|
+
_method(type) {
|
|
2871
|
+
return (...args) => this.service._log({ name: this.name, type, args });
|
|
2872
|
+
}
|
|
2873
|
+
};
|
|
2874
|
+
var LoggerService = class {
|
|
2875
|
+
buffer = [];
|
|
2876
|
+
ctx;
|
|
2877
|
+
_snMessage = 0;
|
|
2878
|
+
_snExporter = 0;
|
|
2879
|
+
exporters = /* @__PURE__ */ new Map();
|
|
2880
|
+
constructor(ctx) {
|
|
2881
|
+
const tracker = {
|
|
2882
|
+
property: "ctx",
|
|
2883
|
+
noShadow: true
|
|
2884
|
+
};
|
|
2885
|
+
const self = createCallable("logger", joinPrototype(Object.getPrototypeOf(this), Function.prototype), tracker);
|
|
2886
|
+
Object.assign(self, this);
|
|
2887
|
+
self.ctx = ctx;
|
|
2888
|
+
Object.defineProperty(self, symbols.tracker, { value: tracker });
|
|
2889
|
+
self.exporter({
|
|
2890
|
+
export: (message) => {
|
|
2891
|
+
self.buffer.push(message);
|
|
2892
|
+
}
|
|
2893
|
+
});
|
|
2894
|
+
ctx.reflect.provide("logger", self);
|
|
2895
|
+
return self;
|
|
2896
|
+
}
|
|
2897
|
+
exporter(exporter) {
|
|
2898
|
+
return this.ctx.fiber.effect(() => {
|
|
2899
|
+
const id = ++this._snExporter;
|
|
2900
|
+
this.exporters.set(id, exporter);
|
|
2901
|
+
return () => this.exporters.delete(id);
|
|
2902
|
+
}, "ctx.logger.exporter()");
|
|
2903
|
+
}
|
|
2904
|
+
[symbols.invoke](name2) {
|
|
2905
|
+
const caller = this[symbols.caller];
|
|
2906
|
+
const fiber = (caller ?? this.ctx).fiber;
|
|
2907
|
+
return new Logger({
|
|
2908
|
+
name: name2 ?? fiber.name
|
|
2909
|
+
}, this);
|
|
2910
|
+
}
|
|
2911
|
+
_log(message) {
|
|
2912
|
+
const sn = ++this._snMessage;
|
|
2913
|
+
const ts = Date.now();
|
|
2914
|
+
for (const exporter of this.exporters.values()) {
|
|
2915
|
+
if (exporter.level != null && exporter.level < 0) continue;
|
|
2916
|
+
exporter.export({ sn, ts, ...message });
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
error(...args) {
|
|
2920
|
+
return this().error(...args);
|
|
2921
|
+
}
|
|
2922
|
+
info(...args) {
|
|
2923
|
+
return this().info(...args);
|
|
2924
|
+
}
|
|
2925
|
+
warn(...args) {
|
|
2926
|
+
return this().warn(...args);
|
|
2927
|
+
}
|
|
2928
|
+
debug(...args) {
|
|
2929
|
+
return this().debug(...args);
|
|
2930
|
+
}
|
|
2931
|
+
};
|
|
2932
|
+
|
|
2933
|
+
// packages/core/src/reflect.ts
|
|
2934
|
+
function enhanceError(error) {
|
|
2935
|
+
const lines = error.stack.split("\n");
|
|
2936
|
+
lines.splice(0, 2, `Error: ${error.message}`);
|
|
2937
|
+
error.stack = lines.join("\n");
|
|
2938
|
+
return error;
|
|
2939
|
+
}
|
|
2940
|
+
var RESERVED_WORDS = ["prototype", "then"];
|
|
2941
|
+
function isSpecialProperty(prop) {
|
|
2942
|
+
return typeof prop === "symbol" || RESERVED_WORDS.includes(prop) || parseInt(prop).toString() === prop || prop.startsWith("_");
|
|
2943
|
+
}
|
|
2944
|
+
var ReflectService = class {
|
|
2945
|
+
constructor(ctx) {
|
|
2946
|
+
this.ctx = ctx;
|
|
2947
|
+
Object.defineProperty(this, symbols.tracker, {
|
|
2948
|
+
value: { property: "ctx", noShadow: true }
|
|
2949
|
+
});
|
|
2950
|
+
this.mixin("reflect", ["get", "set", "provide", "accessor", "mixin"]);
|
|
2951
|
+
this.mixin("fiber", ["runtime", "effect"]);
|
|
2952
|
+
this.mixin("registry", ["inject", "plugin"]);
|
|
2953
|
+
this.mixin("events", [
|
|
2954
|
+
"on",
|
|
2955
|
+
"once",
|
|
2956
|
+
"parallel",
|
|
2957
|
+
"emit",
|
|
2958
|
+
"serial",
|
|
2959
|
+
"bail",
|
|
2960
|
+
"waterfall",
|
|
2961
|
+
"waterfallAsync"
|
|
2962
|
+
]);
|
|
2963
|
+
}
|
|
2964
|
+
ctx;
|
|
2965
|
+
static handler = {
|
|
2966
|
+
get: (target, prop, ctx) => {
|
|
2967
|
+
if (isSpecialProperty(prop)) {
|
|
2968
|
+
return Reflect.get(target, prop, ctx);
|
|
2969
|
+
}
|
|
2970
|
+
if (Reflect.has(target, prop)) {
|
|
2971
|
+
return getTraceable(ctx, Reflect.get(target, prop, ctx));
|
|
2972
|
+
}
|
|
2973
|
+
const error = new Error(`cannot get property "${String(prop)}" without inject`);
|
|
2974
|
+
try {
|
|
2975
|
+
const def = target.reflect.props[prop];
|
|
2976
|
+
if (def?.type === "accessor") {
|
|
2977
|
+
return def.get.call(ctx, ctx[symbols.receiver], error);
|
|
2978
|
+
}
|
|
2979
|
+
if (!ctx.fiber.runtime) return ctx.reflect.get(prop, false);
|
|
2980
|
+
return ctx.events.waterfall(ctx, "internal/get", prop, error, () => {
|
|
2981
|
+
const key = target[symbols.isolate][prop];
|
|
2982
|
+
let fiber = (ctx[symbols.shadow] ?? ctx).fiber;
|
|
2983
|
+
while (true) {
|
|
2984
|
+
const impl = fiber.store?.[prop];
|
|
2985
|
+
if (impl) return getTraceable(ctx, impl.value);
|
|
2986
|
+
if (prop in fiber.inject) {
|
|
2987
|
+
error.message = `cannot get required service "${String(prop)}" in inactive context`;
|
|
2988
|
+
throw error;
|
|
2989
|
+
}
|
|
2990
|
+
if (!fiber.runtime) throw error;
|
|
2991
|
+
if (fiber.parent[symbols.isolate][prop] !== key) throw error;
|
|
2992
|
+
fiber = fiber.parent.fiber;
|
|
2993
|
+
}
|
|
2994
|
+
});
|
|
2995
|
+
} catch (e) {
|
|
2996
|
+
throw e === error ? enhanceError(e) : e;
|
|
2997
|
+
}
|
|
2998
|
+
},
|
|
2999
|
+
set: (target, prop, value, ctx) => {
|
|
3000
|
+
if (isSpecialProperty(prop)) {
|
|
3001
|
+
return Reflect.set(target, prop, value, ctx);
|
|
3002
|
+
}
|
|
3003
|
+
const error = new Error(`cannot set property "${String(prop)}" without provide`);
|
|
3004
|
+
const def = target.reflect.props[prop];
|
|
3005
|
+
try {
|
|
3006
|
+
if (def?.type === "accessor") {
|
|
3007
|
+
if (!def.set) return false;
|
|
3008
|
+
return def.set.call(ctx, value, ctx[symbols.receiver], error);
|
|
3009
|
+
}
|
|
3010
|
+
if (!def && !ctx.fiber.runtime) {
|
|
3011
|
+
return Reflect.set(target, prop, value, ctx);
|
|
3012
|
+
}
|
|
3013
|
+
return ctx.events.waterfall(ctx, "internal/set", prop, value, error, () => {
|
|
3014
|
+
if (!def) throw error;
|
|
3015
|
+
return ctx.reflect.set(prop, value);
|
|
3016
|
+
});
|
|
3017
|
+
} catch (e) {
|
|
3018
|
+
throw e === error ? enhanceError(e) : e;
|
|
3019
|
+
}
|
|
3020
|
+
},
|
|
3021
|
+
has: (target, prop) => {
|
|
3022
|
+
if (isSpecialProperty(prop)) {
|
|
3023
|
+
return Reflect.has(target, prop);
|
|
3024
|
+
}
|
|
3025
|
+
if (Reflect.has(target, prop)) return true;
|
|
3026
|
+
return !!target.reflect.props[prop];
|
|
3027
|
+
}
|
|
3028
|
+
};
|
|
3029
|
+
store = /* @__PURE__ */ Object.create(null);
|
|
3030
|
+
props = /* @__PURE__ */ Object.create(null);
|
|
3031
|
+
get(name2, strict = true) {
|
|
3032
|
+
return getTraceable(this.ctx, this._getImpl(name2, strict)?.value);
|
|
3033
|
+
}
|
|
3034
|
+
_getImpl(name2, strict = true) {
|
|
3035
|
+
const key = this.ctx[symbols.isolate][name2];
|
|
3036
|
+
const impl = key && this.store[key];
|
|
3037
|
+
if (!impl) return;
|
|
3038
|
+
if (strict && impl.fiber.state !== 2 /* ACTIVE */) return;
|
|
3039
|
+
return impl;
|
|
3040
|
+
}
|
|
3041
|
+
set(name2, value) {
|
|
3042
|
+
const key = this.ctx[symbols.isolate][name2];
|
|
3043
|
+
const impl = key && this.store[key];
|
|
3044
|
+
if (!impl) {
|
|
3045
|
+
throw new Error(`cannot set property "${name2}" without provide`);
|
|
3046
|
+
}
|
|
3047
|
+
if (impl.fiber !== this.ctx.fiber) {
|
|
3048
|
+
throw new Error(`cannot set property "${name2}" in multiple fibers`);
|
|
3049
|
+
}
|
|
3050
|
+
impl.value = value;
|
|
3051
|
+
return true;
|
|
3052
|
+
}
|
|
3053
|
+
provide(name2, value, check) {
|
|
3054
|
+
return this.ctx.fiber.effect(() => {
|
|
3055
|
+
if (!this.props[name2]) {
|
|
3056
|
+
this.props[name2] = { type: "service" };
|
|
3057
|
+
} else if (this.props[name2].type !== "service") {
|
|
3058
|
+
throw new Error(`property "${name2}" is already declared as ${this.props[name2].type}`);
|
|
3059
|
+
}
|
|
3060
|
+
this.props[name2] = { type: "service" };
|
|
3061
|
+
this.ctx.root[symbols.isolate][name2] ??= Symbol(name2);
|
|
3062
|
+
const key = this.ctx[symbols.isolate][name2];
|
|
3063
|
+
const impl = {
|
|
3064
|
+
name: name2,
|
|
3065
|
+
value,
|
|
3066
|
+
fiber: this.ctx.fiber,
|
|
3067
|
+
...check ? { check } : {}
|
|
3068
|
+
};
|
|
3069
|
+
if (key && this.store[key]) {
|
|
3070
|
+
throw new Error(`service "${name2}" has been registered at <${this.store[key].fiber.name}>`);
|
|
3071
|
+
}
|
|
3072
|
+
this.store[key] = impl;
|
|
3073
|
+
this.ctx.fiber.store[name2] = impl;
|
|
3074
|
+
if (this.ctx.fiber.state === 2 /* ACTIVE */) {
|
|
3075
|
+
this.notify([name2]);
|
|
3076
|
+
}
|
|
3077
|
+
return async () => {
|
|
3078
|
+
delete this.store[key];
|
|
3079
|
+
const fibers = this.notify([name2]);
|
|
3080
|
+
await Promise.allSettled(fibers.map((fiber) => fiber.await()));
|
|
3081
|
+
delete this.ctx.fiber.store[name2];
|
|
3082
|
+
};
|
|
3083
|
+
}, `ctx.provide(${JSON.stringify(name2)})`);
|
|
3084
|
+
}
|
|
3085
|
+
notify(names, filter = (ctx, name2) => ctx[symbols.isolate][name2] === this.ctx[symbols.isolate][name2]) {
|
|
3086
|
+
const fibers = [];
|
|
3087
|
+
for (const runtime of this.ctx.registry.values()) {
|
|
3088
|
+
for (const fiber of runtime.fibers) {
|
|
3089
|
+
let hasUpdate = false;
|
|
3090
|
+
for (const name2 of names) {
|
|
3091
|
+
if (!(name2 in fiber.inject)) continue;
|
|
3092
|
+
if (!filter(fiber.ctx, name2)) continue;
|
|
3093
|
+
hasUpdate = true;
|
|
3094
|
+
fiber._checkImpl(name2);
|
|
3095
|
+
}
|
|
3096
|
+
if (!hasUpdate) continue;
|
|
3097
|
+
fiber._refresh();
|
|
3098
|
+
fibers.push(fiber);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
for (const name2 of names) {
|
|
3102
|
+
const self = Object.create(this.ctx);
|
|
3103
|
+
self[symbols.filter] = (target) => filter(target, name2);
|
|
3104
|
+
this.ctx.events.emit(self, "internal/service", name2, this._getImpl(name2, false)?.value);
|
|
3105
|
+
}
|
|
3106
|
+
return fibers;
|
|
3107
|
+
}
|
|
3108
|
+
accessor(name2, options) {
|
|
3109
|
+
return this.ctx.fiber.effect(() => {
|
|
3110
|
+
if (name2 in this.props) {
|
|
3111
|
+
throw new Error(`property "${name2}" is already declared as ${this.props[name2].type}`);
|
|
3112
|
+
}
|
|
3113
|
+
this.props[name2] = { type: "accessor", ...options };
|
|
3114
|
+
return () => delete this.props[name2];
|
|
3115
|
+
}, `ctx.accessor(${JSON.stringify(name2)})`);
|
|
3116
|
+
}
|
|
3117
|
+
mixin(source, mixins) {
|
|
3118
|
+
return this.ctx.fiber.effect(() => {
|
|
3119
|
+
const entries = Array.isArray(mixins) ? mixins.map((key) => [key, key]) : Object.entries(mixins);
|
|
3120
|
+
const getTarget = (target) => target[source];
|
|
3121
|
+
return entries.map(([key, value]) => {
|
|
3122
|
+
return this.accessor(value, {
|
|
3123
|
+
get(receiver) {
|
|
3124
|
+
const service = getTarget(this);
|
|
3125
|
+
if (service == null) return service;
|
|
3126
|
+
const mixin = receiver ? withProps(receiver, service) : service;
|
|
3127
|
+
const result = Reflect.get(service, key, mixin);
|
|
3128
|
+
if (typeof result !== "function") return result;
|
|
3129
|
+
return result.bind(mixin ?? service);
|
|
3130
|
+
},
|
|
3131
|
+
set(value2, receiver) {
|
|
3132
|
+
const service = getTarget(this);
|
|
3133
|
+
const mixin = receiver ? withProps(receiver, service) : service;
|
|
3134
|
+
return Reflect.set(service, key, value2, mixin);
|
|
3135
|
+
}
|
|
3136
|
+
});
|
|
3137
|
+
});
|
|
3138
|
+
}, `ctx.mixin(${JSON.stringify(source)})`);
|
|
3139
|
+
}
|
|
3140
|
+
trace(value) {
|
|
3141
|
+
return getTraceable(this.ctx, value);
|
|
3142
|
+
}
|
|
3143
|
+
bind(callback) {
|
|
3144
|
+
return new Proxy(callback, {
|
|
3145
|
+
apply: (target, thisArg, args) => {
|
|
3146
|
+
return Reflect.apply(target, this.trace(thisArg), args.map((arg) => this.trace(arg)));
|
|
3147
|
+
},
|
|
3148
|
+
construct: (target, args, newTarget) => {
|
|
3149
|
+
return Reflect.construct(target, args.map((arg) => this.trace(arg)), newTarget);
|
|
3150
|
+
}
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
};
|
|
3154
|
+
|
|
3155
|
+
// packages/core/src/registry.ts
|
|
3156
|
+
function isApplicable(object) {
|
|
3157
|
+
return object && typeof object === "object" && typeof object.apply === "function";
|
|
3158
|
+
}
|
|
3159
|
+
function resolveInject(inject, result = /* @__PURE__ */ Object.create(null)) {
|
|
3160
|
+
if (!inject) return result;
|
|
3161
|
+
if (Array.isArray(inject)) {
|
|
3162
|
+
for (const name2 of inject) {
|
|
3163
|
+
result[name2] = null;
|
|
3164
|
+
}
|
|
3165
|
+
} else if (Reflect.has(inject, symbols.checkProto)) {
|
|
3166
|
+
Object.assign(result, resolveInject(Object.getPrototypeOf(inject)));
|
|
3167
|
+
for (const name2 of Object.keys(inject)) {
|
|
3168
|
+
result[name2] = inject[name2] ?? null;
|
|
3169
|
+
}
|
|
3170
|
+
} else {
|
|
3171
|
+
for (const name2 of Object.keys(inject)) {
|
|
3172
|
+
result[name2] = inject[name2] ?? null;
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
return result;
|
|
3176
|
+
}
|
|
3177
|
+
var RegistryService = class {
|
|
3178
|
+
constructor(ctx) {
|
|
3179
|
+
this.ctx = ctx;
|
|
3180
|
+
Object.defineProperty(this, symbols.tracker, {
|
|
3181
|
+
value: { property: "ctx", noShadow: true }
|
|
3182
|
+
});
|
|
3183
|
+
}
|
|
3184
|
+
ctx;
|
|
3185
|
+
_counter = 0;
|
|
3186
|
+
_internal = /* @__PURE__ */ new Map();
|
|
3187
|
+
get counter() {
|
|
3188
|
+
return ++this._counter;
|
|
3189
|
+
}
|
|
3190
|
+
get size() {
|
|
3191
|
+
return this._internal.size;
|
|
3192
|
+
}
|
|
3193
|
+
resolve(plugin) {
|
|
3194
|
+
try {
|
|
3195
|
+
if (typeof plugin === "function") return plugin;
|
|
3196
|
+
if (isApplicable(plugin)) return plugin.apply;
|
|
3197
|
+
} catch {
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
get(plugin) {
|
|
3201
|
+
const key = this.resolve(plugin);
|
|
3202
|
+
return key && this._internal.get(key);
|
|
3203
|
+
}
|
|
3204
|
+
has(plugin) {
|
|
3205
|
+
const key = this.resolve(plugin);
|
|
3206
|
+
return !!key && this._internal.has(key);
|
|
3207
|
+
}
|
|
3208
|
+
delete(plugin) {
|
|
3209
|
+
const key = this.resolve(plugin);
|
|
3210
|
+
const runtime = key && this._internal.get(key);
|
|
3211
|
+
if (!runtime) return;
|
|
3212
|
+
this._internal.delete(key);
|
|
3213
|
+
for (const fiber of [...runtime.fibers]) {
|
|
3214
|
+
fiber.dispose();
|
|
3215
|
+
}
|
|
3216
|
+
return runtime;
|
|
3217
|
+
}
|
|
3218
|
+
keys() {
|
|
3219
|
+
return this._internal.keys();
|
|
3220
|
+
}
|
|
3221
|
+
values() {
|
|
3222
|
+
return this._internal.values();
|
|
3223
|
+
}
|
|
3224
|
+
entries() {
|
|
3225
|
+
return this._internal.entries();
|
|
3226
|
+
}
|
|
3227
|
+
forEach(callback) {
|
|
3228
|
+
return this._internal.forEach(callback);
|
|
3229
|
+
}
|
|
3230
|
+
inject(inject, callback) {
|
|
3231
|
+
const name2 = callback.name || void 0;
|
|
3232
|
+
return this.plugin(Object.assign(
|
|
3233
|
+
{ inject, apply: callback },
|
|
3234
|
+
name2 ? { name: name2 } : {}
|
|
3235
|
+
));
|
|
3236
|
+
}
|
|
3237
|
+
plugin(plugin, config, getOuterStack = buildOuterStack()) {
|
|
3238
|
+
const callback = this.resolve(plugin);
|
|
3239
|
+
if (!callback) throw new Error('invalid plugin, expect function or object with an "apply" method, received ' + typeof plugin);
|
|
3240
|
+
this.ctx.fiber.assertActive();
|
|
3241
|
+
let runtime = this._internal.get(callback);
|
|
3242
|
+
if (!runtime) {
|
|
3243
|
+
let name2 = plugin.name;
|
|
3244
|
+
if (name2 === "apply") name2 = void 0;
|
|
3245
|
+
runtime = {
|
|
3246
|
+
callback,
|
|
3247
|
+
fibers: new DisposableList(),
|
|
3248
|
+
...name2 ? { name: name2 } : {},
|
|
3249
|
+
...plugin.Config ? { Config: plugin.Config } : {}
|
|
3250
|
+
};
|
|
3251
|
+
this._internal.set(callback, runtime);
|
|
3252
|
+
}
|
|
3253
|
+
const fiber = new Fiber(this.ctx, config, resolveInject(plugin.inject), runtime, getOuterStack);
|
|
3254
|
+
const wrapped = Object.create(fiber);
|
|
3255
|
+
wrapped.then = (onFulfilled, onRejected) => {
|
|
3256
|
+
return fiber.await().then(onFulfilled, onRejected);
|
|
3257
|
+
};
|
|
3258
|
+
return wrapped;
|
|
3259
|
+
}
|
|
3260
|
+
};
|
|
3261
|
+
|
|
3262
|
+
// packages/core/src/context.ts
|
|
3263
|
+
var Context = class _Context {
|
|
3264
|
+
static effect = symbols.effect;
|
|
3265
|
+
static filter = symbols.filter;
|
|
3266
|
+
static isolate = symbols.isolate;
|
|
3267
|
+
static intercept = symbols.intercept;
|
|
3268
|
+
static marker = /* @__PURE__ */ Symbol.for("tnega.is");
|
|
3269
|
+
static is(value) {
|
|
3270
|
+
return !!value?.[_Context.marker];
|
|
3271
|
+
}
|
|
3272
|
+
static {
|
|
3273
|
+
_Context.prototype[_Context.marker] = true;
|
|
3274
|
+
}
|
|
3275
|
+
constructor() {
|
|
3276
|
+
this[symbols.isolate] = /* @__PURE__ */ Object.create(null);
|
|
3277
|
+
this[symbols.intercept] = /* @__PURE__ */ Object.create(null);
|
|
3278
|
+
const self = new Proxy(this, ReflectService.handler);
|
|
3279
|
+
this.root = self;
|
|
3280
|
+
this.baseUrl = void 0;
|
|
3281
|
+
this.fiber = new Fiber(self, {}, /* @__PURE__ */ Object.create(null), null, () => []);
|
|
3282
|
+
this.reflect = new ReflectService(self);
|
|
3283
|
+
this.registry = new RegistryService(self);
|
|
3284
|
+
this.events = new EventsService(self);
|
|
3285
|
+
this.logger = new LoggerService(self);
|
|
3286
|
+
this.fiber._disposables.clear();
|
|
3287
|
+
return self;
|
|
3288
|
+
}
|
|
3289
|
+
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
|
3290
|
+
return `Context <${this.fiber.name}>`;
|
|
3291
|
+
}
|
|
3292
|
+
extend(meta = {}) {
|
|
3293
|
+
const shadow = Reflect.getOwnPropertyDescriptor(this, symbols.shadow)?.value;
|
|
3294
|
+
const self = Object.create(getTraceable(this, this));
|
|
3295
|
+
for (const prop of Reflect.ownKeys(meta)) {
|
|
3296
|
+
Object.defineProperty(self, prop, Reflect.getOwnPropertyDescriptor(meta, prop));
|
|
3297
|
+
}
|
|
3298
|
+
if (!shadow) return self;
|
|
3299
|
+
return Object.assign(Object.create(self), { [symbols.shadow]: shadow });
|
|
3300
|
+
}
|
|
3301
|
+
isolate(name2, label) {
|
|
3302
|
+
const shadow = Object.create(this[symbols.isolate]);
|
|
3303
|
+
shadow[name2] = label ?? Symbol(name2);
|
|
3304
|
+
return this.extend({ [symbols.isolate]: shadow });
|
|
3305
|
+
}
|
|
3306
|
+
intercept(name2, config) {
|
|
3307
|
+
const intercept = Object.create(this[symbols.intercept]);
|
|
3308
|
+
intercept[name2] = config;
|
|
3309
|
+
return this.extend({ [symbols.intercept]: intercept });
|
|
3310
|
+
}
|
|
3311
|
+
};
|
|
3312
|
+
|
|
3313
|
+
// packages/core/src/service.ts
|
|
3314
|
+
var Service = class _Service {
|
|
3315
|
+
constructor(ctx, name2) {
|
|
3316
|
+
this.ctx = ctx;
|
|
3317
|
+
name2 ??= this.constructor.provide;
|
|
3318
|
+
const tracker = {
|
|
3319
|
+
associate: name2,
|
|
3320
|
+
property: "ctx"
|
|
3321
|
+
};
|
|
3322
|
+
const self = this[symbols.invoke] ? createCallable(name2, joinPrototype(Object.getPrototypeOf(this), Function.prototype), tracker) : this;
|
|
3323
|
+
self.ctx = ctx;
|
|
3324
|
+
self.name = name2;
|
|
3325
|
+
Object.defineProperty(self, symbols.tracker, { value: tracker });
|
|
3326
|
+
self.ctx.reflect.provide(name2, self, this[symbols.check]);
|
|
3327
|
+
return self;
|
|
3328
|
+
}
|
|
3329
|
+
ctx;
|
|
3330
|
+
static init = symbols.init;
|
|
3331
|
+
static check = symbols.check;
|
|
3332
|
+
static config = symbols.config;
|
|
3333
|
+
static invoke = symbols.invoke;
|
|
3334
|
+
static extend = symbols.extend;
|
|
3335
|
+
static tracker = symbols.tracker;
|
|
3336
|
+
static resolveConfig = symbols.resolveConfig;
|
|
3337
|
+
name;
|
|
3338
|
+
[symbols.filter](ctx) {
|
|
3339
|
+
return ctx[symbols.isolate][this.name] === this.ctx[symbols.isolate][this.name];
|
|
3340
|
+
}
|
|
3341
|
+
[symbols.extend](props) {
|
|
3342
|
+
let self;
|
|
3343
|
+
if (this[_Service.invoke]) {
|
|
3344
|
+
self = createCallable(this.name, this, this[symbols.tracker]);
|
|
3345
|
+
} else {
|
|
3346
|
+
self = Object.create(this);
|
|
3347
|
+
}
|
|
3348
|
+
return Object.assign(self, props);
|
|
3349
|
+
}
|
|
3350
|
+
[symbols.resolveConfig](base, head) {
|
|
3351
|
+
let intercept = this.ctx[symbols.intercept];
|
|
3352
|
+
const configs = [];
|
|
3353
|
+
while (this.name in intercept) {
|
|
3354
|
+
if (Object.hasOwn(intercept, this.name)) {
|
|
3355
|
+
configs.unshift(intercept[this.name]);
|
|
3356
|
+
}
|
|
3357
|
+
intercept = Object.getPrototypeOf(intercept);
|
|
3358
|
+
}
|
|
3359
|
+
if (base) configs.unshift(base);
|
|
3360
|
+
if (head) configs.push(head);
|
|
3361
|
+
if (this["Config"]?.merge) {
|
|
3362
|
+
return this["Config"].merge(...configs);
|
|
3363
|
+
} else {
|
|
3364
|
+
return Object.assign({}, ...configs);
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
static [Symbol.hasInstance](instance) {
|
|
3368
|
+
if (!instance) return false;
|
|
3369
|
+
let constructor = instance.constructor;
|
|
3370
|
+
while (constructor) {
|
|
3371
|
+
constructor = constructor.prototype?.constructor;
|
|
3372
|
+
if (constructor === this) return true;
|
|
3373
|
+
constructor &&= Object.getPrototypeOf(constructor);
|
|
3374
|
+
}
|
|
3375
|
+
return false;
|
|
3376
|
+
}
|
|
3377
|
+
};
|
|
3378
|
+
|
|
3379
|
+
// packages/agent/src/inbox-durable.ts
|
|
3380
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3381
|
+
function contentOf(input) {
|
|
3382
|
+
return input.text ?? "";
|
|
3383
|
+
}
|
|
3384
|
+
function payloadOf(input) {
|
|
3385
|
+
return input.content !== void 0 ? input.content : void 0;
|
|
3386
|
+
}
|
|
3387
|
+
var DurableInbox = class _DurableInbox {
|
|
3388
|
+
constructor(_session) {
|
|
3389
|
+
this._session = _session;
|
|
3390
|
+
}
|
|
3391
|
+
_session;
|
|
3392
|
+
_nextTurn = [];
|
|
3393
|
+
_nextStep = [];
|
|
3394
|
+
_operationTail = Promise.resolve();
|
|
3395
|
+
get size() {
|
|
3396
|
+
return this._nextTurn.length + this._nextStep.length;
|
|
3397
|
+
}
|
|
3398
|
+
async insert(input, target = "next-turn") {
|
|
3399
|
+
return this._run(async () => {
|
|
3400
|
+
const message = {
|
|
3401
|
+
id: randomUUID3(),
|
|
3402
|
+
...input.text !== void 0 ? { text: input.text } : {},
|
|
3403
|
+
...input.content !== void 0 ? { content: input.content } : {}
|
|
3404
|
+
};
|
|
3405
|
+
const list = target === "next-step" ? this._nextStep : this._nextTurn;
|
|
3406
|
+
const index = list.length;
|
|
3407
|
+
await this._session.append("agent/inbox/spliced", {
|
|
3408
|
+
target,
|
|
3409
|
+
index,
|
|
3410
|
+
inserted: [{
|
|
3411
|
+
id: message.id,
|
|
3412
|
+
content: contentOf(message),
|
|
3413
|
+
...payloadOf(message) !== void 0 ? { payload: payloadOf(message) } : {}
|
|
3414
|
+
}]
|
|
3415
|
+
});
|
|
3416
|
+
list.push(message);
|
|
3417
|
+
return message;
|
|
3418
|
+
});
|
|
3419
|
+
}
|
|
3420
|
+
async steer(input) {
|
|
3421
|
+
return this._run(async () => {
|
|
3422
|
+
const message = {
|
|
3423
|
+
id: randomUUID3(),
|
|
3424
|
+
...input.text !== void 0 ? { text: input.text } : {},
|
|
3425
|
+
...input.content !== void 0 ? { content: input.content } : {}
|
|
3426
|
+
};
|
|
3427
|
+
await this._session.append("agent/inbox/spliced", {
|
|
3428
|
+
target: "next-step",
|
|
3429
|
+
index: this._nextStep.length,
|
|
3430
|
+
inserted: [{
|
|
3431
|
+
id: message.id,
|
|
3432
|
+
content: contentOf(message),
|
|
3433
|
+
...payloadOf(message) !== void 0 ? { payload: payloadOf(message) } : {},
|
|
3434
|
+
mode: "steer"
|
|
3435
|
+
}]
|
|
3436
|
+
});
|
|
3437
|
+
this._nextStep.push(message);
|
|
3438
|
+
return message;
|
|
3439
|
+
});
|
|
3440
|
+
}
|
|
3441
|
+
async claim() {
|
|
3442
|
+
return this._run(async () => {
|
|
3443
|
+
const steered = this._nextStep[0];
|
|
3444
|
+
if (steered) {
|
|
3445
|
+
await this._session.append("agent/inbox/spliced", {
|
|
3446
|
+
target: "next-step",
|
|
3447
|
+
index: 0,
|
|
3448
|
+
deleteCount: 1
|
|
3449
|
+
});
|
|
3450
|
+
this._nextStep.shift();
|
|
3451
|
+
return steered;
|
|
3452
|
+
}
|
|
3453
|
+
const next = this._nextTurn[0];
|
|
3454
|
+
if (next) {
|
|
3455
|
+
await this._session.append("agent/inbox/spliced", {
|
|
3456
|
+
target: "next-turn",
|
|
3457
|
+
index: 0,
|
|
3458
|
+
deleteCount: 1
|
|
3459
|
+
});
|
|
3460
|
+
this._nextTurn.shift();
|
|
3461
|
+
return next;
|
|
3462
|
+
}
|
|
3463
|
+
return void 0;
|
|
3464
|
+
});
|
|
3465
|
+
}
|
|
3466
|
+
/**
|
|
3467
|
+
* Claim the complete proposed step batch: every pending next-step input plus
|
|
3468
|
+
* one next-turn message at a turn boundary. Returns an empty array when
|
|
3469
|
+
* nothing is pending.
|
|
3470
|
+
*/
|
|
3471
|
+
async claimBatch() {
|
|
3472
|
+
return this._run(async () => {
|
|
3473
|
+
const claimed = [...this._nextStep];
|
|
3474
|
+
const nextTurn = this._nextTurn[0];
|
|
3475
|
+
if (nextTurn) claimed.push(nextTurn);
|
|
3476
|
+
if (!claimed.length) return [];
|
|
3477
|
+
const deleteCounts = { nextStep: this._nextStep.length, nextTurn: nextTurn ? 1 : 0 };
|
|
3478
|
+
await this._session.append("agent/inbox/spliced", { target: "all", deleteCounts });
|
|
3479
|
+
this._nextStep.splice(0, deleteCounts.nextStep);
|
|
3480
|
+
this._nextTurn.splice(0, deleteCounts.nextTurn);
|
|
3481
|
+
return claimed;
|
|
3482
|
+
});
|
|
3483
|
+
}
|
|
3484
|
+
/** Claim every input waiting for the next step, without consuming a turn. */
|
|
3485
|
+
async claimNextStep() {
|
|
3486
|
+
return this._run(async () => {
|
|
3487
|
+
if (!this._nextStep.length) return [];
|
|
3488
|
+
const claimed = [...this._nextStep];
|
|
3489
|
+
await this._session.append("agent/inbox/spliced", {
|
|
3490
|
+
target: "next-step",
|
|
3491
|
+
index: 0,
|
|
3492
|
+
deleteCount: claimed.length
|
|
3493
|
+
});
|
|
3494
|
+
this._nextStep.splice(0, claimed.length);
|
|
3495
|
+
return claimed;
|
|
3496
|
+
});
|
|
3497
|
+
}
|
|
3498
|
+
async clear() {
|
|
3499
|
+
return this._run(async () => {
|
|
3500
|
+
if (!this._nextTurn.length && !this._nextStep.length) return;
|
|
3501
|
+
await this._session.append("agent/inbox/spliced", {
|
|
3502
|
+
target: "all"
|
|
3503
|
+
});
|
|
3504
|
+
this._nextTurn = [];
|
|
3505
|
+
this._nextStep = [];
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3508
|
+
/** Insert a message at an explicit target/index boundary. */
|
|
3509
|
+
async insertAt(input, target, index, mode = "followup") {
|
|
3510
|
+
return this._run(async () => {
|
|
3511
|
+
const message = {
|
|
3512
|
+
id: randomUUID3(),
|
|
3513
|
+
...input.text !== void 0 ? { text: input.text } : {},
|
|
3514
|
+
...input.content !== void 0 ? { content: input.content } : {}
|
|
3515
|
+
};
|
|
3516
|
+
const list = target === "next-step" ? this._nextStep : this._nextTurn;
|
|
3517
|
+
const safeIndex = Math.max(0, Math.min(index, list.length));
|
|
3518
|
+
await this._session.append("agent/inbox/spliced", {
|
|
3519
|
+
target,
|
|
3520
|
+
index: safeIndex,
|
|
3521
|
+
inserted: [{
|
|
3522
|
+
id: message.id,
|
|
3523
|
+
content: contentOf(message),
|
|
3524
|
+
...payloadOf(message) !== void 0 ? { payload: payloadOf(message) } : {},
|
|
3525
|
+
mode
|
|
3526
|
+
}]
|
|
3527
|
+
});
|
|
3528
|
+
list.splice(safeIndex, 0, message);
|
|
3529
|
+
return message;
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3532
|
+
/**
|
|
3533
|
+
* Replace one pending message by id. The old message is emitted as
|
|
3534
|
+
* discarded and the new one as inserted, matching the DSH inbox contract.
|
|
3535
|
+
*/
|
|
3536
|
+
async replace(messageId, input) {
|
|
3537
|
+
return this._run(async () => {
|
|
3538
|
+
const entry = this._find(messageId);
|
|
3539
|
+
if (!entry) return void 0;
|
|
3540
|
+
const replacement = {
|
|
3541
|
+
id: randomUUID3(),
|
|
3542
|
+
...input.text !== void 0 ? { text: input.text } : {},
|
|
3543
|
+
...input.content !== void 0 ? { content: input.content } : {}
|
|
3544
|
+
};
|
|
3545
|
+
const target = entry.target;
|
|
3546
|
+
const index = entry.index;
|
|
3547
|
+
await this._session.append("agent/inbox/spliced", {
|
|
3548
|
+
target,
|
|
3549
|
+
index,
|
|
3550
|
+
deleteCount: 1,
|
|
3551
|
+
inserted: [{
|
|
3552
|
+
id: replacement.id,
|
|
3553
|
+
content: contentOf(replacement),
|
|
3554
|
+
...payloadOf(replacement) !== void 0 ? { payload: payloadOf(replacement) } : {}
|
|
3555
|
+
}]
|
|
3556
|
+
});
|
|
3557
|
+
entry.list.splice(index, 1, replacement);
|
|
3558
|
+
return replacement;
|
|
3559
|
+
});
|
|
3560
|
+
}
|
|
3561
|
+
/** Remove one pending message by id. */
|
|
3562
|
+
async remove(messageId) {
|
|
3563
|
+
return this._run(async () => {
|
|
3564
|
+
const entry = this._find(messageId);
|
|
3565
|
+
if (!entry) return void 0;
|
|
3566
|
+
await this._session.append("agent/inbox/spliced", {
|
|
3567
|
+
target: entry.target,
|
|
3568
|
+
index: entry.index,
|
|
3569
|
+
deleteCount: 1
|
|
3570
|
+
});
|
|
3571
|
+
entry.list.splice(entry.index, 1);
|
|
3572
|
+
return entry.message;
|
|
3573
|
+
});
|
|
3574
|
+
}
|
|
3575
|
+
/** Return the first pending message by id across both queues. */
|
|
3576
|
+
get(messageId) {
|
|
3577
|
+
return this._find(messageId)?.message;
|
|
3578
|
+
}
|
|
3579
|
+
snapshot() {
|
|
3580
|
+
return {
|
|
3581
|
+
nextTurn: [...this._nextTurn],
|
|
3582
|
+
nextStep: [...this._nextStep]
|
|
3583
|
+
};
|
|
3584
|
+
}
|
|
3585
|
+
/** Keep the entire read → append → apply transition ordered, including claims. */
|
|
3586
|
+
_run(operation) {
|
|
3587
|
+
const result = this._operationTail.then(operation);
|
|
3588
|
+
this._operationTail = result.then(() => void 0, () => void 0);
|
|
3589
|
+
return result;
|
|
3590
|
+
}
|
|
3591
|
+
_find(messageId) {
|
|
3592
|
+
for (const target of ["next-step", "next-turn"]) {
|
|
3593
|
+
const list = target === "next-step" ? this._nextStep : this._nextTurn;
|
|
3594
|
+
const index = list.findIndex((message) => message.id === messageId);
|
|
3595
|
+
if (index >= 0) return { list, target, index, message: list[index] };
|
|
3596
|
+
}
|
|
3597
|
+
return void 0;
|
|
3598
|
+
}
|
|
3599
|
+
static async restore(session) {
|
|
3600
|
+
const inbox = new _DurableInbox(session);
|
|
3601
|
+
inbox._applyAll(await session.read());
|
|
3602
|
+
return inbox;
|
|
3603
|
+
}
|
|
3604
|
+
_applyAll(events) {
|
|
3605
|
+
for (const event of events) {
|
|
3606
|
+
if (event.type !== "agent/inbox/spliced") continue;
|
|
3607
|
+
const payload = event.payload;
|
|
3608
|
+
if (payload.target === "all") {
|
|
3609
|
+
this._nextTurn.splice(0, payload.deleteCounts?.nextTurn ?? this._nextTurn.length);
|
|
3610
|
+
this._nextStep.splice(0, payload.deleteCounts?.nextStep ?? this._nextStep.length);
|
|
3611
|
+
continue;
|
|
3612
|
+
}
|
|
3613
|
+
const list = payload.target === "next-step" ? this._nextStep : this._nextTurn;
|
|
3614
|
+
const count = payload.deleteCount ?? 0;
|
|
3615
|
+
if (count === Number.POSITIVE_INFINITY) {
|
|
3616
|
+
list.length = 0;
|
|
3617
|
+
} else if (count > 0 && payload.index === 0) {
|
|
3618
|
+
list.splice(0, Math.min(count, list.length));
|
|
3619
|
+
} else if (count > 0 && payload.index !== void 0) {
|
|
3620
|
+
list.splice(payload.index, Math.min(count, list.length - payload.index));
|
|
3621
|
+
}
|
|
3622
|
+
let insertedOffset = 0;
|
|
3623
|
+
for (const item of payload.inserted ?? []) {
|
|
3624
|
+
const message = {
|
|
3625
|
+
id: item.id,
|
|
3626
|
+
...item.content !== void 0 ? { text: item.content } : {},
|
|
3627
|
+
...item.payload !== void 0 ? { content: item.payload } : {}
|
|
3628
|
+
};
|
|
3629
|
+
if (item.mode === "steer") {
|
|
3630
|
+
list.unshift(message);
|
|
3631
|
+
} else {
|
|
3632
|
+
const at = payload.index === void 0 ? list.length : Math.min(list.length, payload.index + insertedOffset);
|
|
3633
|
+
list.splice(at, 0, message);
|
|
3634
|
+
insertedOffset += 1;
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
};
|
|
3640
|
+
|
|
3641
|
+
// packages/agent/src/live.ts
|
|
3642
|
+
function createAgentRuntimeContext(agentCtx) {
|
|
3643
|
+
const runtimeCtx = agentCtx.extend();
|
|
3644
|
+
Object.defineProperty(runtimeCtx, Context.filter, {
|
|
3645
|
+
value: (target) => {
|
|
3646
|
+
const targetScope = target[symbols.isolate].agentScope;
|
|
3647
|
+
let ancestor = agentCtx;
|
|
3648
|
+
while (true) {
|
|
3649
|
+
if (targetScope === ancestor[symbols.isolate].agentScope) return true;
|
|
3650
|
+
if (ancestor.fiber.parent.fiber === ancestor.fiber) return false;
|
|
3651
|
+
ancestor = ancestor.fiber.parent;
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
});
|
|
3655
|
+
const events = agentCtx.events;
|
|
3656
|
+
for (const method of ["emit", "parallel", "serial", "bail", "waterfall", "waterfallAsync"]) {
|
|
3657
|
+
Object.defineProperty(runtimeCtx, method, {
|
|
3658
|
+
value: (...args) => events[method](runtimeCtx, ...args)
|
|
3659
|
+
});
|
|
3660
|
+
}
|
|
3661
|
+
return runtimeCtx;
|
|
3662
|
+
}
|
|
3663
|
+
var LiveAgentImpl = class {
|
|
3664
|
+
constructor(_ctx, _agentCtx, _service, id, inbox, session, durable, meta, manualStreaming = false, wakeReserved = false) {
|
|
3665
|
+
this._ctx = _ctx;
|
|
3666
|
+
this._agentCtx = _agentCtx;
|
|
3667
|
+
this._service = _service;
|
|
3668
|
+
this.id = id;
|
|
3669
|
+
this.inbox = inbox;
|
|
3670
|
+
this.session = session;
|
|
3671
|
+
this.meta = meta;
|
|
3672
|
+
this._durable = durable;
|
|
3673
|
+
this._manualStreaming = manualStreaming;
|
|
3674
|
+
this._wakeReserved = wakeReserved;
|
|
3675
|
+
}
|
|
3676
|
+
_ctx;
|
|
3677
|
+
_agentCtx;
|
|
3678
|
+
_service;
|
|
3679
|
+
id;
|
|
3680
|
+
inbox;
|
|
3681
|
+
session;
|
|
3682
|
+
meta;
|
|
3683
|
+
_status = "idle";
|
|
3684
|
+
_disposed = false;
|
|
3685
|
+
_controller;
|
|
3686
|
+
_drainPromise;
|
|
3687
|
+
_busy = false;
|
|
3688
|
+
_writeTail = Promise.resolve();
|
|
3689
|
+
_pendingWrite;
|
|
3690
|
+
_sessionStarted = false;
|
|
3691
|
+
_maintenance;
|
|
3692
|
+
_maintenanceBusy = false;
|
|
3693
|
+
_setupDisposers = [];
|
|
3694
|
+
_scopeClosed = false;
|
|
3695
|
+
_durable;
|
|
3696
|
+
_manualStreaming = false;
|
|
3697
|
+
_wakeReserved = false;
|
|
3698
|
+
_loop;
|
|
3699
|
+
get agentType() {
|
|
3700
|
+
return this.meta.agentType;
|
|
3701
|
+
}
|
|
3702
|
+
get mode() {
|
|
3703
|
+
return this.meta.mode;
|
|
3704
|
+
}
|
|
3705
|
+
get owner() {
|
|
3706
|
+
return this.meta.owner;
|
|
3707
|
+
}
|
|
3708
|
+
get parentSessionId() {
|
|
3709
|
+
return this.meta.parentSessionId;
|
|
3710
|
+
}
|
|
3711
|
+
get agentCtx() {
|
|
3712
|
+
return this._agentCtx;
|
|
3713
|
+
}
|
|
3714
|
+
trackSetupDispose(dispose) {
|
|
3715
|
+
if (this._scopeClosed) void dispose();
|
|
3716
|
+
else this._setupDisposers.push(dispose);
|
|
3717
|
+
}
|
|
3718
|
+
get status() {
|
|
3719
|
+
return this._status;
|
|
3720
|
+
}
|
|
3721
|
+
get ctx() {
|
|
3722
|
+
return this._ctx;
|
|
3723
|
+
}
|
|
3724
|
+
followup(input) {
|
|
3725
|
+
return this._send(input, "followup");
|
|
3726
|
+
}
|
|
3727
|
+
setLoop(loop) {
|
|
3728
|
+
this._loop = loop;
|
|
3729
|
+
}
|
|
3730
|
+
async _closeCustomTurn(turn, run) {
|
|
3731
|
+
const events = await this.session.read();
|
|
3732
|
+
if (events.some((event) => event.type === "turn/end" && event.payload.turn === turn)) return;
|
|
3733
|
+
const reason = run.finishReason === "length" ? { kind: "max-tokens" } : run.finishReason === "max_steps" ? { kind: "max-steps" } : run.finishReason === "max_turns" ? { kind: "max-turns" } : run.finishReason === "cancelled" ? { kind: "interrupted" } : run.finishReason === "error" ? { kind: "interrupted" } : { kind: "completed" };
|
|
3734
|
+
await this.session.append("turn/end", {
|
|
3735
|
+
turn,
|
|
3736
|
+
finishReason: run.finishReason,
|
|
3737
|
+
reason,
|
|
3738
|
+
...run.output ? { output: run.output } : {},
|
|
3739
|
+
...run.steps.length ? { steps: run.steps.length } : {}
|
|
3740
|
+
});
|
|
3741
|
+
}
|
|
3742
|
+
async _closeFailedCustomTurn(turn, error) {
|
|
3743
|
+
const events = await this.session.read();
|
|
3744
|
+
if (events.some((event) => event.type === "turn/end" && event.payload.turn === turn)) return;
|
|
3745
|
+
const failure = error instanceof Error ? { name: error.name, message: error.message, ...error.stack ? { stack: error.stack } : {} } : { name: "Error", message: String(error) };
|
|
3746
|
+
await this.session.append("turn/end", {
|
|
3747
|
+
turn,
|
|
3748
|
+
finishReason: "error",
|
|
3749
|
+
reason: { kind: "error", error: failure },
|
|
3750
|
+
interrupted: true,
|
|
3751
|
+
error: failure
|
|
3752
|
+
});
|
|
3753
|
+
}
|
|
3754
|
+
/** Replace one pending inbox message by durable message id. */
|
|
3755
|
+
replaceMessage(messageId, input) {
|
|
3756
|
+
return this._mutatePending(async () => {
|
|
3757
|
+
const previous = this._durable.get(messageId);
|
|
3758
|
+
const target = this._findTarget(messageId);
|
|
3759
|
+
if (!previous || !target) return;
|
|
3760
|
+
const replacement = await this._durable.replace(messageId, {
|
|
3761
|
+
text: input.text ?? "",
|
|
3762
|
+
...input.messages !== void 0 || input.context !== void 0 ? { content: input.messages ?? input.context } : {}
|
|
3763
|
+
});
|
|
3764
|
+
if (!replacement) return;
|
|
3765
|
+
this._ctx.emit("agent/inbox/discarded", {
|
|
3766
|
+
id: this.id,
|
|
3767
|
+
agent: this,
|
|
3768
|
+
message: previous
|
|
3769
|
+
});
|
|
3770
|
+
const event = {
|
|
3771
|
+
id: this.id,
|
|
3772
|
+
agent: this,
|
|
3773
|
+
target,
|
|
3774
|
+
input: replacement,
|
|
3775
|
+
inboxSeq: this._durable.size
|
|
3776
|
+
};
|
|
3777
|
+
this._ctx.emit("agent/inbox/inserted", event);
|
|
3778
|
+
});
|
|
3779
|
+
}
|
|
3780
|
+
/** Remove one pending inbox message by durable message id. */
|
|
3781
|
+
removeMessage(messageId) {
|
|
3782
|
+
return this._mutatePending(async () => {
|
|
3783
|
+
const removed = await this._durable.remove(messageId);
|
|
3784
|
+
if (!removed) return;
|
|
3785
|
+
this._ctx.emit("agent/inbox/discarded", {
|
|
3786
|
+
id: this.id,
|
|
3787
|
+
agent: this,
|
|
3788
|
+
message: removed
|
|
3789
|
+
});
|
|
3790
|
+
});
|
|
3791
|
+
}
|
|
3792
|
+
steer(input) {
|
|
3793
|
+
return this._send(input, "steer");
|
|
3794
|
+
}
|
|
3795
|
+
inject(input) {
|
|
3796
|
+
return this._send(input, "inject");
|
|
3797
|
+
}
|
|
3798
|
+
send(input, options = {}) {
|
|
3799
|
+
return this._send(input, options.mode === "steer" ? "steer" : "followup");
|
|
3800
|
+
}
|
|
3801
|
+
_send(input, target) {
|
|
3802
|
+
const text = input.text ?? "";
|
|
3803
|
+
const content = input.messages ?? input.context;
|
|
3804
|
+
const steeredAfterAbort = target === "steer" && this._controller?.signal.aborted === true;
|
|
3805
|
+
return this._mutatePending(async () => {
|
|
3806
|
+
const message = target === "followup" ? await this._durable.insert({ text, ...content !== void 0 ? { content } : {} }) : target === "steer" ? steeredAfterAbort ? await this._durable.insert({ text, ...content !== void 0 ? { content } : {} }) : await this._durable.steer({ text, ...content !== void 0 ? { content } : {} }) : await this._durable.insert({ text, ...content !== void 0 ? { content } : {} }, "next-step");
|
|
3807
|
+
if (target !== "inject") this._wakeReserved = true;
|
|
3808
|
+
const event = {
|
|
3809
|
+
id: this.id,
|
|
3810
|
+
agent: this,
|
|
3811
|
+
target,
|
|
3812
|
+
input: message,
|
|
3813
|
+
inboxSeq: this._durable.size
|
|
3814
|
+
};
|
|
3815
|
+
this._ctx.emit("agent/inbox/inserted", event);
|
|
3816
|
+
}, target !== "inject");
|
|
3817
|
+
}
|
|
3818
|
+
_mutatePending(task, wakes = false, allowDisposed = false) {
|
|
3819
|
+
if (this._disposed && !allowDisposed) {
|
|
3820
|
+
return Promise.reject(new Error(`agent disposed: ${this.id}`));
|
|
3821
|
+
}
|
|
3822
|
+
const result = this._writeTail.then(task);
|
|
3823
|
+
const recovered = result.catch((error) => {
|
|
3824
|
+
try {
|
|
3825
|
+
this._ctx.emit("agent/error", { id: this.id, error });
|
|
3826
|
+
} catch {
|
|
3827
|
+
}
|
|
3828
|
+
});
|
|
3829
|
+
this._writeTail = recovered;
|
|
3830
|
+
this._pendingWrite = recovered;
|
|
3831
|
+
void result.then(
|
|
3832
|
+
() => {
|
|
3833
|
+
if (this._pendingWrite === recovered) this._pendingWrite = void 0;
|
|
3834
|
+
if (wakes) queueMicrotask(() => this._wake());
|
|
3835
|
+
},
|
|
3836
|
+
() => {
|
|
3837
|
+
if (this._pendingWrite === recovered) this._pendingWrite = void 0;
|
|
3838
|
+
}
|
|
3839
|
+
);
|
|
3840
|
+
return result;
|
|
3841
|
+
}
|
|
3842
|
+
_findTarget(messageId) {
|
|
3843
|
+
const snapshot = this._durable.snapshot();
|
|
3844
|
+
if (snapshot.nextStep.some((message) => message.id === messageId)) return "next-step";
|
|
3845
|
+
if (snapshot.nextTurn.some((message) => message.id === messageId)) return "next-turn";
|
|
3846
|
+
return void 0;
|
|
3847
|
+
}
|
|
3848
|
+
cancel(cause, options = {}) {
|
|
3849
|
+
if (!options.keepInbox) {
|
|
3850
|
+
void this._mutatePending(async () => {
|
|
3851
|
+
const before = this._durable.snapshot();
|
|
3852
|
+
await this._durable.clear();
|
|
3853
|
+
for (const message of [...before.nextStep, ...before.nextTurn]) {
|
|
3854
|
+
this._ctx.emit("agent/inbox/discarded", { id: this.id, message });
|
|
3855
|
+
}
|
|
3856
|
+
this._wakeReserved = false;
|
|
3857
|
+
}, false, true);
|
|
3858
|
+
}
|
|
3859
|
+
this._controller?.abort(cause);
|
|
3860
|
+
this._maintenance?.controller.abort(cause);
|
|
3861
|
+
}
|
|
3862
|
+
async whenIdle() {
|
|
3863
|
+
while (true) {
|
|
3864
|
+
while (this._pendingWrite) await this._pendingWrite;
|
|
3865
|
+
const maintenance = this._maintenance;
|
|
3866
|
+
if (maintenance) {
|
|
3867
|
+
await maintenance.promise.catch(() => void 0);
|
|
3868
|
+
continue;
|
|
3869
|
+
}
|
|
3870
|
+
const drain = this._drainPromise;
|
|
3871
|
+
if (drain) {
|
|
3872
|
+
await drain.catch(() => void 0);
|
|
3873
|
+
continue;
|
|
3874
|
+
}
|
|
3875
|
+
if (!this._hasQueuedWork()) break;
|
|
3876
|
+
await new Promise((resolve2) => setTimeout(resolve2, 0));
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
/** Run one non-turn maintenance task from the true idle phase. */
|
|
3880
|
+
runMaintenance(task) {
|
|
3881
|
+
if (this._disposed) throw new Error(`agent disposed: ${this.id}`);
|
|
3882
|
+
if (this._maintenanceBusy || this._drainPromise || this._pendingWrite) {
|
|
3883
|
+
throw new Error("agent is busy; maintenance can only run while idle");
|
|
3884
|
+
}
|
|
3885
|
+
this._maintenanceBusy = true;
|
|
3886
|
+
const controller = new AbortController();
|
|
3887
|
+
const result = Promise.resolve().then(async () => {
|
|
3888
|
+
const value = await task(controller.signal);
|
|
3889
|
+
return value;
|
|
3890
|
+
}).finally(() => {
|
|
3891
|
+
this._maintenanceBusy = false;
|
|
3892
|
+
if (this._maintenance?.controller === controller) {
|
|
3893
|
+
this._maintenance = void 0;
|
|
3894
|
+
}
|
|
3895
|
+
queueMicrotask(() => this._wake());
|
|
3896
|
+
});
|
|
3897
|
+
this._maintenance = { promise: result.then(() => void 0, () => void 0), controller };
|
|
3898
|
+
return result;
|
|
3899
|
+
}
|
|
3900
|
+
async dispose() {
|
|
3901
|
+
if (this._disposed) return;
|
|
3902
|
+
this._disposed = true;
|
|
3903
|
+
this.cancel({ type: "disposed" });
|
|
3904
|
+
await this._writeTail.catch(() => void 0);
|
|
3905
|
+
await this._drainPromise?.catch(() => void 0);
|
|
3906
|
+
if (this._maintenance) this._maintenance.controller.abort({ type: "disposed" });
|
|
3907
|
+
await this._maintenance?.promise.catch(() => void 0);
|
|
3908
|
+
if (!this._scopeClosed) {
|
|
3909
|
+
this._scopeClosed = true;
|
|
3910
|
+
await Promise.all([...this._setupDisposers].reverse().map((dispose) => dispose()));
|
|
3911
|
+
}
|
|
3912
|
+
const writeTail = this._writeTail;
|
|
3913
|
+
if (writeTail !== this._drainPromise && this._pendingWrite) {
|
|
3914
|
+
this._pendingWrite = void 0;
|
|
3915
|
+
await writeTail.catch(() => void 0);
|
|
3916
|
+
}
|
|
3917
|
+
await this.session.close();
|
|
3918
|
+
this._setStatus("idle");
|
|
3919
|
+
}
|
|
3920
|
+
/** Publish the agent lifecycle session-start notification once. */
|
|
3921
|
+
async publishSessionStart(source = "startup") {
|
|
3922
|
+
if (this._sessionStarted) return;
|
|
3923
|
+
this._sessionStarted = true;
|
|
3924
|
+
this._ctx.emit("agent/session-start", {
|
|
3925
|
+
id: this.id,
|
|
3926
|
+
agent: this,
|
|
3927
|
+
source
|
|
3928
|
+
});
|
|
3929
|
+
}
|
|
3930
|
+
/** Current durable turn number for a newly claimed inbox item. */
|
|
3931
|
+
async _nextTurnNumber() {
|
|
3932
|
+
const events = await this.session.read();
|
|
3933
|
+
let max = 0;
|
|
3934
|
+
for (const event of events) {
|
|
3935
|
+
if (event.type === "turn/start") max = Math.max(max, event.payload.turn);
|
|
3936
|
+
}
|
|
3937
|
+
return max + 1;
|
|
3938
|
+
}
|
|
3939
|
+
/** Start draining currently pending durable work without a new input. */
|
|
3940
|
+
wakeNow() {
|
|
3941
|
+
this._wake();
|
|
3942
|
+
}
|
|
3943
|
+
_wake() {
|
|
3944
|
+
if (this._disposed || this._manualStreaming || this._drainPromise || this._busy || this._maintenanceBusy) return;
|
|
3945
|
+
const task = this._drainAll();
|
|
3946
|
+
this._drainPromise = task;
|
|
3947
|
+
void task.then(
|
|
3948
|
+
() => {
|
|
3949
|
+
if (this._drainPromise === task) this._drainPromise = void 0;
|
|
3950
|
+
if (this._hasQueuedWork()) queueMicrotask(() => this._wake());
|
|
3951
|
+
},
|
|
3952
|
+
(error) => {
|
|
3953
|
+
if (this._drainPromise === task) this._drainPromise = void 0;
|
|
3954
|
+
try {
|
|
3955
|
+
this._ctx.emit("agent/error", { id: this.id, agent: this, error });
|
|
3956
|
+
} catch {
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
);
|
|
3960
|
+
}
|
|
3961
|
+
async _drainAll() {
|
|
3962
|
+
await this._writeTail.catch(() => void 0);
|
|
3963
|
+
if (this._hasQueuedWork()) {
|
|
3964
|
+
for await (const _ of this._streamTurns()) void _;
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
/** Public drain over queued durable work (manual-streaming mode). */
|
|
3968
|
+
async *runTurns(signal) {
|
|
3969
|
+
if (this._manualStreaming) await this._writeTail.catch(() => void 0);
|
|
3970
|
+
if (this._busy || this._disposed) return;
|
|
3971
|
+
yield* this._streamTurns(signal);
|
|
3972
|
+
}
|
|
3973
|
+
async *_streamTurns(signal) {
|
|
3974
|
+
if (this._busy || this._disposed) return;
|
|
3975
|
+
this._busy = true;
|
|
3976
|
+
this._setStatus("running");
|
|
3977
|
+
try {
|
|
3978
|
+
while (!this._disposed) {
|
|
3979
|
+
const snapshot = this._durable.snapshot();
|
|
3980
|
+
if (!snapshot.nextTurn.length && !this._wakeReserved) break;
|
|
3981
|
+
const turn = await this._nextTurnNumber();
|
|
3982
|
+
await this.session.append("turn/start", { turn, reason: "user" });
|
|
3983
|
+
const batch = await this._durable.claimBatch();
|
|
3984
|
+
this._wakeReserved = false;
|
|
3985
|
+
const input = batch.length ? await this._inputForBatch(batch, true) : { text: "" };
|
|
3986
|
+
for (const message of batch) {
|
|
3987
|
+
this._ctx.emit("agent/inbox/claimed", {
|
|
3988
|
+
id: this.id,
|
|
3989
|
+
agent: this,
|
|
3990
|
+
message,
|
|
3991
|
+
input: { text: message.text ?? "" },
|
|
3992
|
+
turn
|
|
3993
|
+
});
|
|
3994
|
+
}
|
|
3995
|
+
const controller = new AbortController();
|
|
3996
|
+
this._controller = controller;
|
|
3997
|
+
const forward = () => controller.abort(signal?.reason);
|
|
3998
|
+
if (signal?.aborted) controller.abort(signal.reason);
|
|
3999
|
+
else signal?.addEventListener("abort", forward, { once: true });
|
|
4000
|
+
try {
|
|
4001
|
+
if (this._manualStreaming) {
|
|
4002
|
+
if (this._loop) {
|
|
4003
|
+
const run = await this._loop(input, { signal: controller.signal, turn });
|
|
4004
|
+
await this._closeCustomTurn(turn, run);
|
|
4005
|
+
yield { type: "run/end", run };
|
|
4006
|
+
} else {
|
|
4007
|
+
yield* this._service.runStream(input, { signal: controller.signal, turn });
|
|
4008
|
+
}
|
|
4009
|
+
} else {
|
|
4010
|
+
if (this._loop) {
|
|
4011
|
+
const run = await this._loop(input, { signal: controller.signal, turn });
|
|
4012
|
+
await this._closeCustomTurn(turn, run);
|
|
4013
|
+
} else await this._service.run(input, { signal: controller.signal, turn });
|
|
4014
|
+
}
|
|
4015
|
+
} catch (error) {
|
|
4016
|
+
if (this._loop) await this._closeFailedCustomTurn(turn, error);
|
|
4017
|
+
if (!controller.signal.aborted) {
|
|
4018
|
+
const step = await this._failedStep();
|
|
4019
|
+
this._ctx.emit("agent/error", {
|
|
4020
|
+
id: this.id,
|
|
4021
|
+
agent: this,
|
|
4022
|
+
error,
|
|
4023
|
+
...step !== void 0 ? { turn, step } : {}
|
|
4024
|
+
});
|
|
4025
|
+
if (this._manualStreaming) throw error;
|
|
4026
|
+
}
|
|
4027
|
+
} finally {
|
|
4028
|
+
signal?.removeEventListener("abort", forward);
|
|
4029
|
+
if (this._controller === controller) this._controller = void 0;
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
} finally {
|
|
4033
|
+
this._busy = false;
|
|
4034
|
+
if (!this._durable.size) this._wakeReserved = false;
|
|
4035
|
+
this._setStatus("idle");
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
_hasQueuedWork() {
|
|
4039
|
+
const snapshot = this._durable.snapshot();
|
|
4040
|
+
return snapshot.nextTurn.length > 0 || this._wakeReserved && snapshot.nextStep.length > 0;
|
|
4041
|
+
}
|
|
4042
|
+
async _inputForBatch(batch, includeHistory) {
|
|
4043
|
+
const history = includeHistory ? await this.session.deriveMessages() : [];
|
|
4044
|
+
const messages = batch.flatMap((message) => message.content !== void 0 ? Array.isArray(message.content) ? message.content : [{ role: "user", content: String(message.content ?? "") }] : message.text !== void 0 ? [{ role: "user", content: message.text }] : []);
|
|
4045
|
+
if (history.length || messages.length > 1) return { messages: [...history, ...messages] };
|
|
4046
|
+
return { text: messages[0]?.content ?? "" };
|
|
4047
|
+
}
|
|
4048
|
+
async claimNextStepInputs() {
|
|
4049
|
+
await this._writeTail;
|
|
4050
|
+
const batch = await this._durable.claimNextStep();
|
|
4051
|
+
this._wakeReserved = false;
|
|
4052
|
+
if (batch.length) {
|
|
4053
|
+
const turn = await this._nextTurnNumber() - 1;
|
|
4054
|
+
for (const message of batch) {
|
|
4055
|
+
this._ctx.emit("agent/inbox/claimed", {
|
|
4056
|
+
id: this.id,
|
|
4057
|
+
agent: this,
|
|
4058
|
+
message,
|
|
4059
|
+
input: { text: message.text ?? "" },
|
|
4060
|
+
turn
|
|
4061
|
+
});
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
return batch.length ? [await this._inputForBatch(batch, false)] : [];
|
|
4065
|
+
}
|
|
4066
|
+
_setStatus(status) {
|
|
4067
|
+
const previous = this._status;
|
|
4068
|
+
if (previous === status) return;
|
|
4069
|
+
this._status = status;
|
|
4070
|
+
this._ctx.emit("agent/status", { id: this.id, status, previous });
|
|
4071
|
+
}
|
|
4072
|
+
async _failedStep() {
|
|
4073
|
+
const events = await this.session.read();
|
|
4074
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
4075
|
+
const event = events[index];
|
|
4076
|
+
if (event?.type === "step/end" && (event.payload.finishReason === "error" || event.payload.interrupted === true)) {
|
|
4077
|
+
return event.payload.step;
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
return void 0;
|
|
4081
|
+
}
|
|
4082
|
+
};
|
|
4083
|
+
var AgentRegistry = class {
|
|
4084
|
+
constructor(_ctx) {
|
|
4085
|
+
this._ctx = _ctx;
|
|
4086
|
+
}
|
|
4087
|
+
_ctx;
|
|
4088
|
+
_agents = /* @__PURE__ */ new Map();
|
|
4089
|
+
_owners = /* @__PURE__ */ new Map();
|
|
4090
|
+
_factory;
|
|
4091
|
+
setFactory(factory) {
|
|
4092
|
+
this._factory = factory;
|
|
4093
|
+
return () => {
|
|
4094
|
+
if (this._factory === factory) this._factory = void 0;
|
|
4095
|
+
};
|
|
4096
|
+
}
|
|
4097
|
+
async create(options) {
|
|
4098
|
+
if (!this._factory) throw new Error("no agent factory registered");
|
|
4099
|
+
const id = options.id ?? options.sessionId ?? randomUUID4();
|
|
4100
|
+
if (this._agents.has(id)) throw new Error(`agent already registered: ${id}`);
|
|
4101
|
+
const handle = await this._factory.create({ ...options, id });
|
|
4102
|
+
this._agents.set(id, handle.agent);
|
|
4103
|
+
this._owners.set(id, handle.agent.owner);
|
|
4104
|
+
this._ctx.emit("agent/created", { id, agent: handle.agent });
|
|
4105
|
+
return {
|
|
4106
|
+
agent: handle.agent,
|
|
4107
|
+
dispose: () => this._dispose(id, handle)
|
|
4108
|
+
};
|
|
4109
|
+
}
|
|
4110
|
+
async resume(options) {
|
|
4111
|
+
if (!this._factory) throw new Error("no agent factory registered");
|
|
4112
|
+
if (!options.id && !options.sessionId) {
|
|
4113
|
+
throw new AgentError("agent resume requires a stable session id");
|
|
4114
|
+
}
|
|
4115
|
+
const id = options.id ?? options.sessionId;
|
|
4116
|
+
if (this._agents.has(id)) throw new Error(`agent already registered: ${id}`);
|
|
4117
|
+
const handle = await this._factory.resume({ ...options, id });
|
|
4118
|
+
this._agents.set(id, handle.agent);
|
|
4119
|
+
this._owners.set(id, handle.agent.owner);
|
|
4120
|
+
this._ctx.emit("agent/created", { id, agent: handle.agent });
|
|
4121
|
+
return {
|
|
4122
|
+
agent: handle.agent,
|
|
4123
|
+
dispose: () => this._dispose(id, handle)
|
|
4124
|
+
};
|
|
4125
|
+
}
|
|
4126
|
+
get(id) {
|
|
4127
|
+
return this._agents.get(id);
|
|
4128
|
+
}
|
|
4129
|
+
list() {
|
|
4130
|
+
return [...this._agents.values()];
|
|
4131
|
+
}
|
|
4132
|
+
roots() {
|
|
4133
|
+
return this.list().filter((agent2) => this._owners.get(agent2.id) === void 0);
|
|
4134
|
+
}
|
|
4135
|
+
isOwnedBy(id, owner) {
|
|
4136
|
+
return this._owners.get(id) === owner;
|
|
4137
|
+
}
|
|
4138
|
+
async _dispose(id, handle) {
|
|
4139
|
+
const agent2 = this._agents.get(id);
|
|
4140
|
+
if (!agent2) return;
|
|
4141
|
+
await handle.dispose();
|
|
4142
|
+
this._agents.delete(id);
|
|
4143
|
+
this._owners.delete(id);
|
|
4144
|
+
this._ctx.emit("agent/disposed", { id });
|
|
4145
|
+
}
|
|
4146
|
+
};
|
|
4147
|
+
async function buildHandle(ctx, options, resume = false) {
|
|
4148
|
+
const fileMeta = await readAgentMeta(options.file);
|
|
4149
|
+
if (resume && !fileMeta) {
|
|
4150
|
+
throw new AgentError(`no agent session meta found for resume: ${options.file}`);
|
|
4151
|
+
}
|
|
4152
|
+
const requestedId = options.id ?? options.sessionId ?? randomUUID4();
|
|
4153
|
+
if (fileMeta?.agentId && fileMeta.agentId !== requestedId) {
|
|
4154
|
+
throw new AgentError(
|
|
4155
|
+
`agent id mismatch: session belongs to ${fileMeta.agentId}, requested ${requestedId}`
|
|
4156
|
+
);
|
|
4157
|
+
}
|
|
4158
|
+
const agentId = fileMeta?.agentId ?? requestedId;
|
|
4159
|
+
if (!agentId) throw new AgentError("agent requires a stable identity");
|
|
4160
|
+
let agentCtx;
|
|
4161
|
+
const agentScope = await ctx.inject([], (scopeCtx) => {
|
|
4162
|
+
agentCtx = scopeCtx.isolate("agentScope").isolate("llm").isolate("tools").isolate("systemPrompt");
|
|
4163
|
+
agentCtx.provide("agentScope", agentCtx);
|
|
4164
|
+
});
|
|
4165
|
+
await agentScope;
|
|
4166
|
+
const runtimeCtx = createAgentRuntimeContext(agentCtx);
|
|
4167
|
+
const log = new SessionLog(options.file, void 0, runtimeCtx);
|
|
4168
|
+
try {
|
|
4169
|
+
await log.init();
|
|
4170
|
+
const fileEvents = await log.read();
|
|
4171
|
+
const durableMeta = readDurableAgentMeta(fileEvents);
|
|
4172
|
+
if (durableMeta) {
|
|
4173
|
+
if (durableMeta.agentId !== agentId) {
|
|
4174
|
+
throw new AgentError(
|
|
4175
|
+
`agent id mismatch: session belongs to ${durableMeta.agentId}, requested ${agentId}`
|
|
4176
|
+
);
|
|
4177
|
+
}
|
|
4178
|
+
if (options.agentType && durableMeta.agentType !== options.agentType) {
|
|
4179
|
+
throw new AgentError(
|
|
4180
|
+
`agent type mismatch: session is ${durableMeta.agentType ?? "untyped"}, requested ${options.agentType}`
|
|
4181
|
+
);
|
|
4182
|
+
}
|
|
4183
|
+
if (options.mode && durableMeta.mode !== options.mode) {
|
|
4184
|
+
throw new AgentError(
|
|
4185
|
+
`agent mode mismatch: session is ${durableMeta.mode ?? "unset"}, requested ${options.mode}`
|
|
4186
|
+
);
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
4189
|
+
const boundMeta = {
|
|
4190
|
+
...fileMeta,
|
|
4191
|
+
...durableMeta ?? {},
|
|
4192
|
+
...options.title ? { title: options.title } : {},
|
|
4193
|
+
...options.owner ? { owner: options.owner } : {},
|
|
4194
|
+
...options.parentSessionId ? { parentSessionId: options.parentSessionId } : {},
|
|
4195
|
+
...options.forkedAtMessageId ? { forkedAtMessageId: options.forkedAtMessageId } : {},
|
|
4196
|
+
agentId
|
|
4197
|
+
};
|
|
4198
|
+
const resolvedAgentType = options.agentType ?? durableMeta?.agentType;
|
|
4199
|
+
if (resolvedAgentType) boundMeta.agentType = resolvedAgentType;
|
|
4200
|
+
const resolvedMode = options.mode ?? durableMeta?.mode;
|
|
4201
|
+
if (resolvedMode) boundMeta.mode = resolvedMode;
|
|
4202
|
+
if (!durableMeta) {
|
|
4203
|
+
await log.append("meta", {
|
|
4204
|
+
kind: "agent",
|
|
4205
|
+
agentId,
|
|
4206
|
+
...boundMeta.agentType ? { agentType: boundMeta.agentType } : {},
|
|
4207
|
+
...boundMeta.mode ? { mode: boundMeta.mode } : {},
|
|
4208
|
+
...boundMeta.title ? { title: boundMeta.title } : {},
|
|
4209
|
+
...boundMeta.owner ? { owner: boundMeta.owner } : {},
|
|
4210
|
+
...boundMeta.parentSessionId ? { parentSessionId: boundMeta.parentSessionId } : {},
|
|
4211
|
+
...boundMeta.forkedAtMessageId ? { forkedAtMessageId: boundMeta.forkedAtMessageId } : {},
|
|
4212
|
+
...boundMeta.createdAt !== void 0 ? { createdAt: boundMeta.createdAt } : {}
|
|
4213
|
+
});
|
|
4214
|
+
}
|
|
4215
|
+
const durable = resume ? await DurableInbox.restore(log) : new DurableInbox(log);
|
|
4216
|
+
const restoredWakeReservation = resume && hasRestoredWakeReservation(fileEvents);
|
|
4217
|
+
const injected = new AgentInbox();
|
|
4218
|
+
const inboxView = {
|
|
4219
|
+
get size() {
|
|
4220
|
+
return durable.size;
|
|
4221
|
+
},
|
|
4222
|
+
snapshot: () => durable.snapshot()
|
|
4223
|
+
};
|
|
4224
|
+
let claimNextStep = async () => [];
|
|
4225
|
+
const service = new AgentService(runtimeCtx, {
|
|
4226
|
+
session: log,
|
|
4227
|
+
inbox: injected,
|
|
4228
|
+
...options.llm ? { llm: options.llm } : {},
|
|
4229
|
+
...options.maxTurns !== void 0 ? { maxTurns: options.maxTurns } : {},
|
|
4230
|
+
...options.maxSteps !== void 0 ? { maxSteps: options.maxSteps } : {},
|
|
4231
|
+
...options.contextBudget ? { contextBudget: options.contextBudget } : {},
|
|
4232
|
+
...options.hooks ? { hooks: options.hooks } : {},
|
|
4233
|
+
claimNextStep: () => claimNextStep()
|
|
4234
|
+
});
|
|
4235
|
+
if (options.system) injected.inject("agentSystem", options.system);
|
|
4236
|
+
const agent2 = new LiveAgentImpl(
|
|
4237
|
+
runtimeCtx,
|
|
4238
|
+
agentCtx,
|
|
4239
|
+
service,
|
|
4240
|
+
agentId,
|
|
4241
|
+
inboxView,
|
|
4242
|
+
log,
|
|
4243
|
+
durable,
|
|
4244
|
+
boundMeta,
|
|
4245
|
+
options.manualStreaming === true,
|
|
4246
|
+
restoredWakeReservation
|
|
4247
|
+
);
|
|
4248
|
+
claimNextStep = () => agent2.claimNextStepInputs();
|
|
4249
|
+
agent2.trackSetupDispose(() => agentScope.dispose());
|
|
4250
|
+
let setupResult;
|
|
4251
|
+
try {
|
|
4252
|
+
setupResult = await options.setup?.(agentCtx);
|
|
4253
|
+
for (const name2 of ["llm", "systemPrompt"]) {
|
|
4254
|
+
if (agentCtx.reflect.get(name2, false) !== void 0) continue;
|
|
4255
|
+
const inherited = ctx.reflect.get(name2, false);
|
|
4256
|
+
if (inherited !== void 0) agentCtx.provide(name2, inherited);
|
|
4257
|
+
}
|
|
4258
|
+
const loop = agentCtx.reflect.get("agentLoop", false);
|
|
4259
|
+
agent2.setLoop(loop);
|
|
4260
|
+
setupResult?.commit();
|
|
4261
|
+
} catch (error) {
|
|
4262
|
+
await agent2.dispose().catch(() => void 0);
|
|
4263
|
+
await log.close().catch(() => void 0);
|
|
4264
|
+
throw error;
|
|
4265
|
+
}
|
|
4266
|
+
await agent2.publishSessionStart(resume ? "resume" : "startup");
|
|
4267
|
+
for (const input of options.initial ?? []) {
|
|
4268
|
+
if (!resume) await durable.insert({ text: input.text ?? "" });
|
|
4269
|
+
}
|
|
4270
|
+
if (resume && (durable.snapshot().nextTurn.length || restoredWakeReservation) && options.manualStreaming !== true) {
|
|
4271
|
+
setTimeout(() => agent2.wakeNow(), 0);
|
|
4272
|
+
}
|
|
4273
|
+
return { agent: agent2, dispose: () => agent2.dispose() };
|
|
4274
|
+
} catch (error) {
|
|
4275
|
+
await Promise.resolve().then(() => agentScope.dispose()).catch(() => void 0);
|
|
4276
|
+
throw error;
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
function hasRestoredWakeReservation(events) {
|
|
4280
|
+
const nextTurn = [];
|
|
4281
|
+
const nextStep = [];
|
|
4282
|
+
for (const event of events) {
|
|
4283
|
+
if (event.type !== "agent/inbox/spliced") continue;
|
|
4284
|
+
const payload = event.payload;
|
|
4285
|
+
if (payload.target === "all") {
|
|
4286
|
+
nextTurn.splice(0, payload.deleteCounts?.nextTurn ?? nextTurn.length);
|
|
4287
|
+
nextStep.splice(0, payload.deleteCounts?.nextStep ?? nextStep.length);
|
|
4288
|
+
continue;
|
|
4289
|
+
}
|
|
4290
|
+
const list = payload.target === "next-step" ? nextStep : nextTurn;
|
|
4291
|
+
const count = payload.deleteCount ?? 0;
|
|
4292
|
+
if (count === Number.POSITIVE_INFINITY) {
|
|
4293
|
+
list.length = 0;
|
|
4294
|
+
} else if (count > 0 && payload.index !== void 0) {
|
|
4295
|
+
list.splice(payload.index, count);
|
|
4296
|
+
}
|
|
4297
|
+
let insertedOffset = 0;
|
|
4298
|
+
for (const inserted of payload.inserted ?? []) {
|
|
4299
|
+
const entry = { wakes: payload.target === "next-step" && inserted.mode === "steer" };
|
|
4300
|
+
if (inserted.mode === "steer") {
|
|
4301
|
+
list.unshift(entry);
|
|
4302
|
+
} else {
|
|
4303
|
+
const index = payload.index === void 0 ? list.length : Math.min(list.length, payload.index + insertedOffset);
|
|
4304
|
+
list.splice(index, 0, entry);
|
|
4305
|
+
insertedOffset += 1;
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
return nextStep.some((entry) => entry.wakes);
|
|
4310
|
+
}
|
|
4311
|
+
async function readAgentMeta(file) {
|
|
4312
|
+
const lines = await import("node:fs/promises").then((fs) => fs.readFile(file, "utf8")).catch((error) => {
|
|
4313
|
+
if (error.code === "ENOENT") return void 0;
|
|
4314
|
+
throw error;
|
|
4315
|
+
});
|
|
4316
|
+
if (lines === void 0) return void 0;
|
|
4317
|
+
for (const line of lines.split("\n")) {
|
|
4318
|
+
if (!line.trim()) continue;
|
|
4319
|
+
let record;
|
|
4320
|
+
try {
|
|
4321
|
+
const parsed = JSON.parse(line);
|
|
4322
|
+
record = parsed && typeof parsed === "object" ? parsed : {};
|
|
4323
|
+
} catch {
|
|
4324
|
+
continue;
|
|
4325
|
+
}
|
|
4326
|
+
if (record.type !== "meta") continue;
|
|
4327
|
+
const payload = record.payload;
|
|
4328
|
+
if (!payload || payload.kind !== "agent") continue;
|
|
4329
|
+
return {
|
|
4330
|
+
...typeof payload.agentId === "string" ? { agentId: payload.agentId } : {},
|
|
4331
|
+
...typeof payload.createdAt === "number" ? { createdAt: payload.createdAt } : {}
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
return void 0;
|
|
4335
|
+
}
|
|
4336
|
+
function readDurableAgentMeta(events) {
|
|
4337
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
4338
|
+
const event = events[index];
|
|
4339
|
+
if (event?.type !== "meta") continue;
|
|
4340
|
+
const payload = event.payload;
|
|
4341
|
+
if (payload.kind !== "agent") continue;
|
|
4342
|
+
const meta = {
|
|
4343
|
+
agentId: typeof payload.agentId === "string" ? payload.agentId : ""
|
|
4344
|
+
};
|
|
4345
|
+
if (payload.agentType === "general" || payload.agentType === "coding") {
|
|
4346
|
+
meta.agentType = payload.agentType;
|
|
4347
|
+
}
|
|
4348
|
+
if (payload.mode === "auto" || payload.mode === "plan" || payload.mode === "execute") {
|
|
4349
|
+
meta.mode = payload.mode;
|
|
4350
|
+
}
|
|
4351
|
+
if (typeof payload.title === "string") meta.title = payload.title;
|
|
4352
|
+
if (typeof payload.owner === "string") meta.owner = payload.owner;
|
|
4353
|
+
if (typeof payload.parentSessionId === "string") {
|
|
4354
|
+
meta.parentSessionId = payload.parentSessionId;
|
|
4355
|
+
}
|
|
4356
|
+
if (typeof payload.forkedAtMessageId === "string") {
|
|
4357
|
+
meta.forkedAtMessageId = payload.forkedAtMessageId;
|
|
4358
|
+
}
|
|
4359
|
+
if (typeof payload.createdAt === "number") meta.createdAt = payload.createdAt;
|
|
4360
|
+
return meta;
|
|
4361
|
+
}
|
|
4362
|
+
return void 0;
|
|
4363
|
+
}
|
|
4364
|
+
var agents = {
|
|
4365
|
+
name: "agents",
|
|
4366
|
+
inject: ["tools"],
|
|
4367
|
+
apply: (ctx) => {
|
|
4368
|
+
const registry = new AgentRegistry(ctx);
|
|
4369
|
+
ctx.provide("agents", registry);
|
|
4370
|
+
registry.setFactory({
|
|
4371
|
+
create: (options) => buildHandle(ctx, options),
|
|
4372
|
+
resume: (options) => buildHandle(ctx, options, true)
|
|
4373
|
+
});
|
|
4374
|
+
return () => {
|
|
4375
|
+
for (const agent2 of registry.list()) {
|
|
4376
|
+
void agent2.dispose();
|
|
4377
|
+
}
|
|
4378
|
+
};
|
|
4379
|
+
}
|
|
4380
|
+
};
|
|
4381
|
+
|
|
4382
|
+
// packages/agent/src/prompt.ts
|
|
4383
|
+
var SystemPromptService = class {
|
|
4384
|
+
_sections = /* @__PURE__ */ new Map();
|
|
4385
|
+
_toolProviders = [];
|
|
4386
|
+
_variables = /* @__PURE__ */ new Map();
|
|
4387
|
+
_contexts = [];
|
|
4388
|
+
_ctx;
|
|
4389
|
+
constructor(ctx) {
|
|
4390
|
+
this._ctx = ctx;
|
|
4391
|
+
}
|
|
4392
|
+
registerSection(section) {
|
|
4393
|
+
const name2 = section.name.trim();
|
|
4394
|
+
if (!name2) throw new TypeError("prompt section requires a non-empty name");
|
|
4395
|
+
if (this._sections.has(name2)) {
|
|
4396
|
+
throw new Error(`prompt section already registered: ${name2}`);
|
|
4397
|
+
}
|
|
4398
|
+
this._sections.set(name2, { ...section, name: name2 });
|
|
4399
|
+
this._emitChange();
|
|
4400
|
+
return () => {
|
|
4401
|
+
this._sections.delete(name2);
|
|
4402
|
+
this._emitChange();
|
|
4403
|
+
};
|
|
4404
|
+
}
|
|
4405
|
+
sections() {
|
|
4406
|
+
return [...this._sections.values()].sort((left, right) => (left.order ?? 0) - (right.order ?? 0));
|
|
4407
|
+
}
|
|
4408
|
+
registerTools(provider) {
|
|
4409
|
+
if (typeof provider !== "function") throw new TypeError("tool provider must be a function");
|
|
4410
|
+
this._toolProviders.push(provider);
|
|
4411
|
+
this._emitChange();
|
|
4412
|
+
return () => {
|
|
4413
|
+
const index = this._toolProviders.indexOf(provider);
|
|
4414
|
+
if (index >= 0) this._toolProviders.splice(index, 1);
|
|
4415
|
+
this._emitChange();
|
|
4416
|
+
};
|
|
4417
|
+
}
|
|
4418
|
+
async toolSchemas(options = {}) {
|
|
4419
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4420
|
+
const tools = [];
|
|
4421
|
+
for (const provider of this._toolProviders) {
|
|
4422
|
+
for (const schema of await provider(options)) {
|
|
4423
|
+
if (!seen.has(schema.name)) {
|
|
4424
|
+
seen.add(schema.name);
|
|
4425
|
+
tools.push(schema);
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
return tools;
|
|
4430
|
+
}
|
|
4431
|
+
registerVariable(name2, provider) {
|
|
4432
|
+
const key = name2.trim();
|
|
4433
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(key)) {
|
|
4434
|
+
throw new TypeError(`invalid prompt variable name: ${name2}`);
|
|
4435
|
+
}
|
|
4436
|
+
if (typeof provider !== "function") throw new TypeError("variable provider must be a function");
|
|
4437
|
+
if (this._variables.has(key)) throw new Error(`prompt variable already registered: ${key}`);
|
|
4438
|
+
this._variables.set(key, provider);
|
|
4439
|
+
this._emitChange();
|
|
4440
|
+
return () => {
|
|
4441
|
+
this._variables.delete(key);
|
|
4442
|
+
this._emitChange();
|
|
4443
|
+
};
|
|
4444
|
+
}
|
|
4445
|
+
registerContext(context) {
|
|
4446
|
+
const name2 = context.name.trim();
|
|
4447
|
+
if (!name2) throw new TypeError("prompt context requires a non-empty name");
|
|
4448
|
+
if (this._contexts.some((entry) => entry.name === name2)) {
|
|
4449
|
+
throw new Error(`prompt context already registered: ${name2}`);
|
|
4450
|
+
}
|
|
4451
|
+
this._contexts.push({ ...context, name: name2 });
|
|
4452
|
+
this._emitChange();
|
|
4453
|
+
return () => {
|
|
4454
|
+
this._contexts = this._contexts.filter((entry) => entry.name !== name2);
|
|
4455
|
+
this._emitChange();
|
|
4456
|
+
};
|
|
4457
|
+
}
|
|
4458
|
+
async assemble(options = {}, ctx) {
|
|
4459
|
+
const activeCtx = ctx ?? this._ctx;
|
|
4460
|
+
const sections = this.sections();
|
|
4461
|
+
const variables = await this._resolveVariables(options);
|
|
4462
|
+
const renderedSections = sections.map((section) => ({
|
|
4463
|
+
...section,
|
|
4464
|
+
content: renderTemplate(section.content, variables)
|
|
4465
|
+
}));
|
|
4466
|
+
const dynamicContexts = await this._resolveContexts(options);
|
|
4467
|
+
const contextText = dynamicContexts.map((context) => renderTemplate(context.content, variables)).filter(Boolean);
|
|
4468
|
+
const base = {
|
|
4469
|
+
sections: renderedSections,
|
|
4470
|
+
text: [
|
|
4471
|
+
...renderedSections.map((section) => section.content).filter(Boolean),
|
|
4472
|
+
...contextText
|
|
4473
|
+
].join("\n\n"),
|
|
4474
|
+
...Object.keys(variables).length ? { variables } : {},
|
|
4475
|
+
...Object.keys(options).length ? { context: options } : {}
|
|
4476
|
+
};
|
|
4477
|
+
const tools = await this.toolSchemas(options);
|
|
4478
|
+
if (tools.length) base.tools = tools;
|
|
4479
|
+
if (!activeCtx) return base;
|
|
4480
|
+
const resolved = await activeCtx.waterfallAsync(
|
|
4481
|
+
"system-prompt/assemble",
|
|
4482
|
+
base,
|
|
4483
|
+
async (assembly) => assembly
|
|
4484
|
+
);
|
|
4485
|
+
return resolved ?? base;
|
|
4486
|
+
}
|
|
4487
|
+
async _resolveVariables(options) {
|
|
4488
|
+
const result = {};
|
|
4489
|
+
for (const [name2, provider] of this._variables) {
|
|
4490
|
+
const value = await provider(options);
|
|
4491
|
+
if (value !== void 0) result[name2] = value;
|
|
4492
|
+
}
|
|
4493
|
+
return result;
|
|
4494
|
+
}
|
|
4495
|
+
async _resolveContexts(options) {
|
|
4496
|
+
const contexts = this._contexts.slice().sort((left, right) => (left.order ?? 0) - (right.order ?? 0));
|
|
4497
|
+
const result = [];
|
|
4498
|
+
for (const context of contexts) {
|
|
4499
|
+
const value = await context.content(options);
|
|
4500
|
+
if (value) result.push({ name: context.name, content: value });
|
|
4501
|
+
}
|
|
4502
|
+
return result;
|
|
4503
|
+
}
|
|
4504
|
+
_emitChange() {
|
|
4505
|
+
this._ctx?.emit("system-prompt/change");
|
|
4506
|
+
}
|
|
4507
|
+
};
|
|
4508
|
+
function renderTemplate(text, variables) {
|
|
4509
|
+
return text.replace(/\$\{([a-zA-Z][a-zA-Z0-9_]*)\}/g, (match, name2) => {
|
|
4510
|
+
return variables[name2] ?? match;
|
|
4511
|
+
});
|
|
4512
|
+
}
|
|
4513
|
+
var systemPrompt = {
|
|
4514
|
+
name: "system-prompt",
|
|
4515
|
+
apply: (ctx) => {
|
|
4516
|
+
const service = new SystemPromptService(ctx);
|
|
4517
|
+
ctx.provide("systemPrompt", service);
|
|
4518
|
+
return () => {
|
|
4519
|
+
};
|
|
4520
|
+
}
|
|
4521
|
+
};
|
|
4522
|
+
|
|
4523
|
+
// packages/agent/src/llm-service.ts
|
|
4524
|
+
var LlmService = class {
|
|
4525
|
+
_adapters = /* @__PURE__ */ new Map();
|
|
4526
|
+
_current;
|
|
4527
|
+
_defaultName;
|
|
4528
|
+
register(name2, adapter) {
|
|
4529
|
+
const key = name2.trim();
|
|
4530
|
+
if (!key) throw new TypeError("llm provider requires a non-empty name");
|
|
4531
|
+
if (this._adapters.has(key)) throw new Error(`llm provider already registered: ${key}`);
|
|
4532
|
+
this._adapters.set(key, adapter);
|
|
4533
|
+
if (!this._current) {
|
|
4534
|
+
this._current = adapter;
|
|
4535
|
+
this._defaultName = key;
|
|
4536
|
+
}
|
|
4537
|
+
return () => {
|
|
4538
|
+
this._adapters.delete(key);
|
|
4539
|
+
if (this._defaultName === key) {
|
|
4540
|
+
this._defaultName = void 0;
|
|
4541
|
+
this._current = this._adapters.values().next().value;
|
|
4542
|
+
}
|
|
4543
|
+
};
|
|
4544
|
+
}
|
|
4545
|
+
setCurrent(name2) {
|
|
4546
|
+
const adapter = this._adapters.get(name2.trim());
|
|
4547
|
+
if (!adapter) throw new Error(`llm provider not found: ${name2}`);
|
|
4548
|
+
this._current = adapter;
|
|
4549
|
+
this._defaultName = name2.trim();
|
|
4550
|
+
return adapter;
|
|
4551
|
+
}
|
|
4552
|
+
current() {
|
|
4553
|
+
return this._current;
|
|
4554
|
+
}
|
|
4555
|
+
list() {
|
|
4556
|
+
return [...this._adapters.entries()].map(([name2, adapter]) => ({ name: name2, adapter }));
|
|
4557
|
+
}
|
|
4558
|
+
/**
|
|
4559
|
+
* Report provider/model/contextWindow for the current route. The adapter
|
|
4560
|
+
* may be registered with an optional capacity provider by callers that own
|
|
4561
|
+
* the model catalog (for example the CLI runtime).
|
|
4562
|
+
*/
|
|
4563
|
+
routeCapacity() {
|
|
4564
|
+
if (!this._current || !this._defaultName) return void 0;
|
|
4565
|
+
const provider = this._defaultName;
|
|
4566
|
+
const model = this._current.model;
|
|
4567
|
+
const contextWindow = this._capacityFor?.(model);
|
|
4568
|
+
const result = { provider, model: model ?? "" };
|
|
4569
|
+
if (contextWindow !== void 0) result.contextWindow = contextWindow;
|
|
4570
|
+
return result;
|
|
4571
|
+
}
|
|
4572
|
+
_capacityFor;
|
|
4573
|
+
/** Install a catalog-backed contextWindow resolver (optional). */
|
|
4574
|
+
setCapacityResolver(resolver) {
|
|
4575
|
+
this._capacityFor = resolver;
|
|
4576
|
+
}
|
|
4577
|
+
};
|
|
4578
|
+
var llmService = {
|
|
4579
|
+
name: "llm-service",
|
|
4580
|
+
apply: (ctx) => {
|
|
4581
|
+
ctx.provide("llm", new LlmService());
|
|
4582
|
+
return () => {
|
|
4583
|
+
};
|
|
4584
|
+
}
|
|
4585
|
+
};
|
|
523
4586
|
export {
|
|
524
4587
|
AgentError,
|
|
525
4588
|
AgentInbox,
|
|
4589
|
+
AgentRegistry,
|
|
526
4590
|
AgentService,
|
|
4591
|
+
DurableInbox,
|
|
4592
|
+
LlmService,
|
|
4593
|
+
SystemPromptService,
|
|
527
4594
|
agent,
|
|
4595
|
+
agents,
|
|
528
4596
|
defineAgent,
|
|
529
|
-
|
|
4597
|
+
llmService,
|
|
4598
|
+
name,
|
|
4599
|
+
systemPrompt
|
|
530
4600
|
};
|
|
531
4601
|
//# sourceMappingURL=agent.js.map
|