@aipack-ai/multi-agent 0.0.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/dist/index.d.ts +523 -0
- package/dist/index.js +3813 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3813 @@
|
|
|
1
|
+
// core/context.ts
|
|
2
|
+
var SimpleEventBus = class {
|
|
3
|
+
listeners = /* @__PURE__ */ new Map();
|
|
4
|
+
on(event, listener) {
|
|
5
|
+
let set = this.listeners.get(event);
|
|
6
|
+
if (!set) {
|
|
7
|
+
set = /* @__PURE__ */ new Set();
|
|
8
|
+
this.listeners.set(event, set);
|
|
9
|
+
}
|
|
10
|
+
set.add(listener);
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
off(event, listener) {
|
|
14
|
+
this.listeners.get(event)?.delete(listener);
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
emit(event, data) {
|
|
18
|
+
const set = this.listeners.get(event);
|
|
19
|
+
if (set) {
|
|
20
|
+
for (const listener of set) {
|
|
21
|
+
try {
|
|
22
|
+
listener(data);
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return this;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var SimpleToolRegistry = class {
|
|
31
|
+
tools = /* @__PURE__ */ new Map();
|
|
32
|
+
register(tool) {
|
|
33
|
+
this.tools.set(tool.name, tool);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
registerAll(tools) {
|
|
37
|
+
for (const tool of tools) {
|
|
38
|
+
this.tools.set(tool.name, tool);
|
|
39
|
+
}
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
get(name) {
|
|
43
|
+
return this.tools.get(name);
|
|
44
|
+
}
|
|
45
|
+
getAll() {
|
|
46
|
+
return Array.from(this.tools.values());
|
|
47
|
+
}
|
|
48
|
+
has(name) {
|
|
49
|
+
return this.tools.has(name);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
function createSharedContext(init) {
|
|
53
|
+
return {
|
|
54
|
+
blackboard: init?.blackboard ?? /* @__PURE__ */ new Map(),
|
|
55
|
+
bus: init?.bus ?? new SimpleEventBus(),
|
|
56
|
+
toolRegistry: init?.toolRegistry ?? new SimpleToolRegistry(),
|
|
57
|
+
meta: init?.meta ?? {}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ../agent/core/types.ts
|
|
62
|
+
function extractText(content) {
|
|
63
|
+
if (typeof content === "string") return content;
|
|
64
|
+
return content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
65
|
+
}
|
|
66
|
+
function extractToolCalls(content) {
|
|
67
|
+
if (typeof content === "string") return [];
|
|
68
|
+
return content.filter((c) => c.type === "toolCall");
|
|
69
|
+
}
|
|
70
|
+
function createTextContent(text) {
|
|
71
|
+
return { type: "text", text };
|
|
72
|
+
}
|
|
73
|
+
function createEmptyUsage() {
|
|
74
|
+
return { input: 0, output: 0, total: 0 };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ../agent/core/tapable.ts
|
|
78
|
+
var AsyncSeriesHook = class {
|
|
79
|
+
constructor(name) {
|
|
80
|
+
this.name = name;
|
|
81
|
+
}
|
|
82
|
+
name;
|
|
83
|
+
taps = [];
|
|
84
|
+
tapPromise(name, fn, stage) {
|
|
85
|
+
this.taps.push({ name, type: "promise", fn, stage: stage ?? 0 });
|
|
86
|
+
this.taps.sort((a, b) => (a.stage ?? 0) - (b.stage ?? 0));
|
|
87
|
+
}
|
|
88
|
+
async promise(...args) {
|
|
89
|
+
for (const tap of this.taps) {
|
|
90
|
+
try {
|
|
91
|
+
await tap.fn(...args);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
isUsed() {
|
|
97
|
+
return this.taps.length > 0;
|
|
98
|
+
}
|
|
99
|
+
clear() {
|
|
100
|
+
this.taps = [];
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var AsyncSeriesWaterfallHook = class {
|
|
104
|
+
constructor(name) {
|
|
105
|
+
this.name = name;
|
|
106
|
+
}
|
|
107
|
+
name;
|
|
108
|
+
taps = [];
|
|
109
|
+
tapPromise(name, fn, stage) {
|
|
110
|
+
this.taps.push({ name, type: "promise", fn, stage: stage ?? 0 });
|
|
111
|
+
this.taps.sort((a, b) => (a.stage ?? 0) - (b.stage ?? 0));
|
|
112
|
+
}
|
|
113
|
+
async promise(value, ...rest) {
|
|
114
|
+
let current = value;
|
|
115
|
+
for (const tap of this.taps) {
|
|
116
|
+
try {
|
|
117
|
+
current = await tap.fn(current, ...rest);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return current;
|
|
122
|
+
}
|
|
123
|
+
isUsed() {
|
|
124
|
+
return this.taps.length > 0;
|
|
125
|
+
}
|
|
126
|
+
clear() {
|
|
127
|
+
this.taps = [];
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// ../agent/core/request.ts
|
|
132
|
+
function createRequest(message, options) {
|
|
133
|
+
return {
|
|
134
|
+
message,
|
|
135
|
+
type: "message",
|
|
136
|
+
...options
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ../agent/core/context-resource.ts
|
|
141
|
+
var ContextResourceBuilder = class {
|
|
142
|
+
_id;
|
|
143
|
+
_type = "custom";
|
|
144
|
+
_role = "user";
|
|
145
|
+
_content = "";
|
|
146
|
+
_timestamp = Date.now();
|
|
147
|
+
_dependencies = [];
|
|
148
|
+
_meta = {};
|
|
149
|
+
_pinned = false;
|
|
150
|
+
constructor(id) {
|
|
151
|
+
this._id = id || `res_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
152
|
+
}
|
|
153
|
+
id(id) {
|
|
154
|
+
this._id = id;
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
type(t) {
|
|
158
|
+
this._type = t;
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
role(r) {
|
|
162
|
+
this._role = r;
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
content(c) {
|
|
166
|
+
this._content = c;
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
timestamp(ts) {
|
|
170
|
+
this._timestamp = ts;
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
dependsOn(...ids) {
|
|
174
|
+
this._dependencies.push(...ids);
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
meta(key, value) {
|
|
178
|
+
this._meta[key] = value;
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
pinned(p = true) {
|
|
182
|
+
this._pinned = p;
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
build() {
|
|
186
|
+
return {
|
|
187
|
+
id: this._id,
|
|
188
|
+
type: this._type,
|
|
189
|
+
role: this._role,
|
|
190
|
+
content: this._content,
|
|
191
|
+
timestamp: this._timestamp,
|
|
192
|
+
dependencies: this._dependencies,
|
|
193
|
+
meta: this._meta,
|
|
194
|
+
pinned: this._pinned
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
function createToolResultResource(toolCallId, toolName, result, isError, dependsOn, options) {
|
|
199
|
+
return new ContextResourceBuilder().id(`${toolCallId}_result`).type("tool_result").role("toolResult").content(result).dependsOn(dependsOn).meta("toolName", toolName).meta("toolCallId", toolCallId).meta("isError", isError).timestamp(options?.timestamp ?? Date.now()).build();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ../agent/core/task-graph.ts
|
|
203
|
+
var TaskGraphImpl = class {
|
|
204
|
+
nodes = /* @__PURE__ */ new Map();
|
|
205
|
+
_size = 0;
|
|
206
|
+
get size() {
|
|
207
|
+
return this._size;
|
|
208
|
+
}
|
|
209
|
+
add(resource) {
|
|
210
|
+
if (this.nodes.has(resource.id)) {
|
|
211
|
+
this.remove(resource.id);
|
|
212
|
+
}
|
|
213
|
+
const node = {
|
|
214
|
+
resource,
|
|
215
|
+
dependents: /* @__PURE__ */ new Set(),
|
|
216
|
+
dependencies: /* @__PURE__ */ new Set()
|
|
217
|
+
};
|
|
218
|
+
for (const depId of resource.dependencies) {
|
|
219
|
+
node.dependencies.add(depId);
|
|
220
|
+
const depNode = this.nodes.get(depId);
|
|
221
|
+
if (depNode) {
|
|
222
|
+
depNode.dependents.add(resource.id);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
this.nodes.set(resource.id, node);
|
|
226
|
+
this._size++;
|
|
227
|
+
}
|
|
228
|
+
addAll(resources) {
|
|
229
|
+
for (const res of resources) {
|
|
230
|
+
if (!this.nodes.has(res.id)) {
|
|
231
|
+
this.nodes.set(res.id, {
|
|
232
|
+
resource: res,
|
|
233
|
+
dependents: /* @__PURE__ */ new Set(),
|
|
234
|
+
dependencies: /* @__PURE__ */ new Set()
|
|
235
|
+
});
|
|
236
|
+
this._size++;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
for (const res of resources) {
|
|
240
|
+
const node = this.nodes.get(res.id);
|
|
241
|
+
for (const depId of res.dependencies) {
|
|
242
|
+
node.dependencies.add(depId);
|
|
243
|
+
const depNode = this.nodes.get(depId);
|
|
244
|
+
if (depNode) {
|
|
245
|
+
depNode.dependents.add(res.id);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
resolve(id) {
|
|
251
|
+
return this.nodes.get(id)?.resource;
|
|
252
|
+
}
|
|
253
|
+
getNode(id) {
|
|
254
|
+
return this.nodes.get(id);
|
|
255
|
+
}
|
|
256
|
+
remove(id) {
|
|
257
|
+
const node = this.nodes.get(id);
|
|
258
|
+
if (!node) return false;
|
|
259
|
+
for (const depId of node.dependencies) {
|
|
260
|
+
const depNode = this.nodes.get(depId);
|
|
261
|
+
depNode?.dependents.delete(id);
|
|
262
|
+
}
|
|
263
|
+
for (const dependentId of node.dependents) {
|
|
264
|
+
const dependentNode = this.nodes.get(dependentId);
|
|
265
|
+
dependentNode?.dependencies.delete(id);
|
|
266
|
+
}
|
|
267
|
+
this.nodes.delete(id);
|
|
268
|
+
this._size--;
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
getAll() {
|
|
272
|
+
return Array.from(this.nodes.values()).map((n) => n.resource);
|
|
273
|
+
}
|
|
274
|
+
getByType(type) {
|
|
275
|
+
return this.getAll().filter((r) => r.type === type);
|
|
276
|
+
}
|
|
277
|
+
topologicalSort() {
|
|
278
|
+
const result = [];
|
|
279
|
+
const visited = /* @__PURE__ */ new Set();
|
|
280
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
281
|
+
const visit = (id) => {
|
|
282
|
+
if (visited.has(id)) return;
|
|
283
|
+
if (visiting.has(id)) return;
|
|
284
|
+
visiting.add(id);
|
|
285
|
+
const node = this.nodes.get(id);
|
|
286
|
+
if (node) {
|
|
287
|
+
for (const depId of node.dependencies) {
|
|
288
|
+
visit(depId);
|
|
289
|
+
}
|
|
290
|
+
result.push(node.resource);
|
|
291
|
+
}
|
|
292
|
+
visiting.delete(id);
|
|
293
|
+
visited.add(id);
|
|
294
|
+
};
|
|
295
|
+
for (const id of this.nodes.keys()) {
|
|
296
|
+
visit(id);
|
|
297
|
+
}
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
isReachable(from, to) {
|
|
301
|
+
if (from === to) return true;
|
|
302
|
+
const visited = /* @__PURE__ */ new Set();
|
|
303
|
+
const queue = [from];
|
|
304
|
+
while (queue.length > 0) {
|
|
305
|
+
const current = queue.shift();
|
|
306
|
+
if (visited.has(current)) continue;
|
|
307
|
+
visited.add(current);
|
|
308
|
+
const node = this.nodes.get(current);
|
|
309
|
+
if (!node) continue;
|
|
310
|
+
for (const depId of node.dependencies) {
|
|
311
|
+
if (depId === to) return true;
|
|
312
|
+
if (!visited.has(depId)) {
|
|
313
|
+
queue.push(depId);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
getLeaves() {
|
|
320
|
+
return Array.from(this.nodes.values()).filter((n) => n.dependencies.size === 0).map((n) => n.resource);
|
|
321
|
+
}
|
|
322
|
+
getRoots() {
|
|
323
|
+
return Array.from(this.nodes.values()).filter((n) => n.dependents.size === 0).map((n) => n.resource);
|
|
324
|
+
}
|
|
325
|
+
clear() {
|
|
326
|
+
this.nodes.clear();
|
|
327
|
+
this._size = 0;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
function createTaskGraph() {
|
|
331
|
+
return new TaskGraphImpl();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ../agent/core/extension.ts
|
|
335
|
+
var ExtensionManager = class {
|
|
336
|
+
extensions = [];
|
|
337
|
+
hooks;
|
|
338
|
+
constructor() {
|
|
339
|
+
this.hooks = this.createHooks();
|
|
340
|
+
}
|
|
341
|
+
createHooks() {
|
|
342
|
+
return {
|
|
343
|
+
beforeInitialize: new AsyncSeriesHook("beforeInitialize"),
|
|
344
|
+
afterInitialize: new AsyncSeriesHook("afterInitialize"),
|
|
345
|
+
beforeRun: new AsyncSeriesWaterfallHook("beforeRun"),
|
|
346
|
+
beforeTransform: new AsyncSeriesWaterfallHook("beforeTransform"),
|
|
347
|
+
afterTransform: new AsyncSeriesWaterfallHook("afterTransform"),
|
|
348
|
+
beforeEmit: new AsyncSeriesHook("beforeEmit"),
|
|
349
|
+
afterEmit: new AsyncSeriesHook("afterEmit"),
|
|
350
|
+
done: new AsyncSeriesHook("done"),
|
|
351
|
+
failed: new AsyncSeriesHook("failed"),
|
|
352
|
+
beforeToolCall: new AsyncSeriesWaterfallHook("beforeToolCall"),
|
|
353
|
+
afterToolCall: new AsyncSeriesWaterfallHook("afterToolCall")
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/** 注册扩展 */
|
|
357
|
+
register(extension) {
|
|
358
|
+
this.extensions.push(extension);
|
|
359
|
+
return this;
|
|
360
|
+
}
|
|
361
|
+
/** 批量注册扩展 */
|
|
362
|
+
registerAll(extensions) {
|
|
363
|
+
for (const ext of extensions) {
|
|
364
|
+
this.register(ext);
|
|
365
|
+
}
|
|
366
|
+
return this;
|
|
367
|
+
}
|
|
368
|
+
/** 应用所有扩展到钩子 */
|
|
369
|
+
applyAll(context) {
|
|
370
|
+
for (const ext of this.extensions) {
|
|
371
|
+
try {
|
|
372
|
+
ext.apply(this.hooks, context);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
console.warn(`[Extension] "${ext.name}" apply \u5931\u8D25:`, err?.message ?? err);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/** 获取钩子集合 */
|
|
379
|
+
getHooks() {
|
|
380
|
+
return this.hooks;
|
|
381
|
+
}
|
|
382
|
+
/** 移除扩展 */
|
|
383
|
+
unregister(name) {
|
|
384
|
+
const idx = this.extensions.findIndex((e) => e.name === name);
|
|
385
|
+
if (idx === -1) return false;
|
|
386
|
+
this.extensions.splice(idx, 1);
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
/** 获取所有已注册的扩展 */
|
|
390
|
+
getExtensions() {
|
|
391
|
+
return [...this.extensions];
|
|
392
|
+
}
|
|
393
|
+
/** 清空所有扩展 */
|
|
394
|
+
clear() {
|
|
395
|
+
this.extensions = [];
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// ../agent/core/result.ts
|
|
400
|
+
var ResultBuilder = class {
|
|
401
|
+
_content = "";
|
|
402
|
+
_toolsUsed = [];
|
|
403
|
+
_usage = {};
|
|
404
|
+
_stopReason = "completed";
|
|
405
|
+
_metadata = {};
|
|
406
|
+
_error;
|
|
407
|
+
_success = true;
|
|
408
|
+
_resources;
|
|
409
|
+
content(c) {
|
|
410
|
+
this._content = c;
|
|
411
|
+
return this;
|
|
412
|
+
}
|
|
413
|
+
toolsUsed(tools) {
|
|
414
|
+
this._toolsUsed = tools;
|
|
415
|
+
return this;
|
|
416
|
+
}
|
|
417
|
+
addTool(tool) {
|
|
418
|
+
if (!this._toolsUsed.includes(tool)) {
|
|
419
|
+
this._toolsUsed.push(tool);
|
|
420
|
+
}
|
|
421
|
+
return this;
|
|
422
|
+
}
|
|
423
|
+
usage(u) {
|
|
424
|
+
this._usage = u;
|
|
425
|
+
return this;
|
|
426
|
+
}
|
|
427
|
+
stopReason(r) {
|
|
428
|
+
this._stopReason = r;
|
|
429
|
+
return this;
|
|
430
|
+
}
|
|
431
|
+
metadata(keyOrMap, value) {
|
|
432
|
+
if (typeof keyOrMap === "string") {
|
|
433
|
+
this._metadata[keyOrMap] = value;
|
|
434
|
+
} else {
|
|
435
|
+
this._metadata = { ...this._metadata, ...keyOrMap };
|
|
436
|
+
}
|
|
437
|
+
return this;
|
|
438
|
+
}
|
|
439
|
+
error(e) {
|
|
440
|
+
if (e !== void 0) {
|
|
441
|
+
this._error = e;
|
|
442
|
+
this._success = false;
|
|
443
|
+
this._stopReason = "error";
|
|
444
|
+
}
|
|
445
|
+
return this;
|
|
446
|
+
}
|
|
447
|
+
resources(res) {
|
|
448
|
+
this._resources = res;
|
|
449
|
+
return this;
|
|
450
|
+
}
|
|
451
|
+
build() {
|
|
452
|
+
return {
|
|
453
|
+
content: this._content,
|
|
454
|
+
toolsUsed: this._toolsUsed,
|
|
455
|
+
usage: this._usage,
|
|
456
|
+
stopReason: this._stopReason,
|
|
457
|
+
metadata: this._metadata,
|
|
458
|
+
error: this._error,
|
|
459
|
+
success: this._success,
|
|
460
|
+
resources: this._resources
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// ../agent/core/session.ts
|
|
466
|
+
var SESSION_VERSION = 1;
|
|
467
|
+
|
|
468
|
+
// ../agent/request/index.ts
|
|
469
|
+
function validateRequest(request) {
|
|
470
|
+
const errors = [];
|
|
471
|
+
if (!request.message || request.message.trim().length === 0) {
|
|
472
|
+
errors.push("message \u4E0D\u80FD\u4E3A\u7A7A");
|
|
473
|
+
}
|
|
474
|
+
if (request.message.length > 1e5) {
|
|
475
|
+
errors.push("message \u957F\u5EA6\u8D85\u8FC7\u9650\u5236\uFF08100000 \u5B57\u7B26\uFF09");
|
|
476
|
+
}
|
|
477
|
+
return { valid: errors.length === 0, errors };
|
|
478
|
+
}
|
|
479
|
+
function normalizeRequest(request) {
|
|
480
|
+
return createRequest(request.message, {
|
|
481
|
+
type: request.type,
|
|
482
|
+
channel: request.channel || "cli",
|
|
483
|
+
chatId: request.chatId || "direct",
|
|
484
|
+
senderId: request.senderId || "user",
|
|
485
|
+
sessionKey: request.sessionKey,
|
|
486
|
+
media: request.media?.filter(Boolean) || [],
|
|
487
|
+
ephemeral: request.ephemeral ?? false,
|
|
488
|
+
model: request.model || void 0,
|
|
489
|
+
modelPreset: request.modelPreset || void 0,
|
|
490
|
+
metadata: request.metadata || {}
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// ../agent/context-resource/index.ts
|
|
495
|
+
function messageToResource(msg, index) {
|
|
496
|
+
const id = `msg_${index}`;
|
|
497
|
+
const timestamp = msg.timestamp ?? Date.now();
|
|
498
|
+
switch (msg.role) {
|
|
499
|
+
case "user": {
|
|
500
|
+
return new ContextResourceBuilder().id(id).type("user_message").role("user").content(msg.content).timestamp(timestamp).build();
|
|
501
|
+
}
|
|
502
|
+
case "assistant": {
|
|
503
|
+
const assistant = msg;
|
|
504
|
+
const builder = new ContextResourceBuilder().id(id).type("assistant_message").role("assistant").content(assistant.content).timestamp(timestamp).meta("model", assistant.model).meta("provider", assistant.provider).meta("stopReason", assistant.stopReason);
|
|
505
|
+
if (assistant.usage) {
|
|
506
|
+
builder.meta("usage", assistant.usage);
|
|
507
|
+
}
|
|
508
|
+
const toolCalls = extractToolCalls(assistant.content);
|
|
509
|
+
for (const tc of toolCalls) {
|
|
510
|
+
builder.dependsOn(tc.id);
|
|
511
|
+
}
|
|
512
|
+
return builder.build();
|
|
513
|
+
}
|
|
514
|
+
case "toolResult": {
|
|
515
|
+
const toolMsg = msg;
|
|
516
|
+
return createToolResultResource(
|
|
517
|
+
toolMsg.toolCallId,
|
|
518
|
+
toolMsg.toolName,
|
|
519
|
+
toolMsg.content,
|
|
520
|
+
toolMsg.isError,
|
|
521
|
+
toolMsg.toolCallId,
|
|
522
|
+
{ timestamp }
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
case "system": {
|
|
526
|
+
return new ContextResourceBuilder().id(id).type("system_message").role("system").content(msg.content).timestamp(timestamp).pinned().build();
|
|
527
|
+
}
|
|
528
|
+
default: {
|
|
529
|
+
const customMsg = msg;
|
|
530
|
+
const roleStr = customMsg.role;
|
|
531
|
+
const customType = roleStr === "compactionSummary" ? "compaction_summary" : roleStr === "stateSnapshot" ? "state_snapshot" : "custom";
|
|
532
|
+
const builder = new ContextResourceBuilder().id(id).type(customType).role(roleStr).content(customMsg).timestamp(timestamp);
|
|
533
|
+
if (customType === "compaction_summary" || customType === "state_snapshot") {
|
|
534
|
+
builder.pinned();
|
|
535
|
+
}
|
|
536
|
+
return builder.build();
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function messagesToResources(messages) {
|
|
541
|
+
return messages.map((msg, index) => messageToResource(msg, index));
|
|
542
|
+
}
|
|
543
|
+
function resourceToMessage(resource) {
|
|
544
|
+
switch (resource.type) {
|
|
545
|
+
case "user_message":
|
|
546
|
+
return {
|
|
547
|
+
role: "user",
|
|
548
|
+
content: resource.content,
|
|
549
|
+
timestamp: resource.timestamp
|
|
550
|
+
};
|
|
551
|
+
case "assistant_message": {
|
|
552
|
+
const meta = resource.meta;
|
|
553
|
+
const content = resource.content;
|
|
554
|
+
if (Array.isArray(content)) {
|
|
555
|
+
return {
|
|
556
|
+
role: "assistant",
|
|
557
|
+
content,
|
|
558
|
+
stopReason: meta.stopReason,
|
|
559
|
+
usage: meta.usage,
|
|
560
|
+
model: meta.model,
|
|
561
|
+
provider: meta.provider,
|
|
562
|
+
timestamp: resource.timestamp
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
return content;
|
|
566
|
+
}
|
|
567
|
+
case "tool_result": {
|
|
568
|
+
const meta = resource.meta;
|
|
569
|
+
return {
|
|
570
|
+
role: "toolResult",
|
|
571
|
+
toolCallId: meta.toolCallId,
|
|
572
|
+
toolName: meta.toolName,
|
|
573
|
+
content: resource.content,
|
|
574
|
+
isError: meta.isError,
|
|
575
|
+
timestamp: resource.timestamp
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
case "system_message":
|
|
579
|
+
return {
|
|
580
|
+
role: "system",
|
|
581
|
+
content: resource.content,
|
|
582
|
+
timestamp: resource.timestamp
|
|
583
|
+
};
|
|
584
|
+
default:
|
|
585
|
+
return resource.content;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function resourcesToMessages(resources) {
|
|
589
|
+
return resources.map(resourceToMessage);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ../agent/ai/index.ts
|
|
593
|
+
import { Type } from "@sinclair/typebox";
|
|
594
|
+
|
|
595
|
+
// ../agent/ai/errors.ts
|
|
596
|
+
var AgentErrorCategory = {
|
|
597
|
+
RETRYABLE: "retryable",
|
|
598
|
+
TIMEOUT: "timeout",
|
|
599
|
+
AUTH: "auth",
|
|
600
|
+
CONTEXT_OVERFLOW: "context-overflow",
|
|
601
|
+
RATE_LIMIT: "rate-limit",
|
|
602
|
+
INVALID_REQUEST: "invalid-request",
|
|
603
|
+
UNKNOWN: "unknown"
|
|
604
|
+
};
|
|
605
|
+
var RETRYABLE_CATEGORIES = /* @__PURE__ */ new Set([
|
|
606
|
+
AgentErrorCategory.RETRYABLE,
|
|
607
|
+
AgentErrorCategory.RATE_LIMIT
|
|
608
|
+
]);
|
|
609
|
+
function isRetryableCategory(category) {
|
|
610
|
+
return RETRYABLE_CATEGORIES.has(category);
|
|
611
|
+
}
|
|
612
|
+
var AgentError = class extends Error {
|
|
613
|
+
category;
|
|
614
|
+
status;
|
|
615
|
+
retryable;
|
|
616
|
+
/** 原始错误(ES2022 cause,Node 18 起 Error 构造支持 options.cause) */
|
|
617
|
+
cause;
|
|
618
|
+
constructor(message, options = {}) {
|
|
619
|
+
super(message);
|
|
620
|
+
this.name = "AgentError";
|
|
621
|
+
this.category = options.category ?? AgentErrorCategory.UNKNOWN;
|
|
622
|
+
this.status = options.status;
|
|
623
|
+
this.retryable = options.retryable ?? isRetryableCategory(this.category);
|
|
624
|
+
this.cause = options.cause;
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
function isAgentError(error) {
|
|
628
|
+
if (error instanceof AgentError) return true;
|
|
629
|
+
return !!error && typeof error === "object" && error.name === "AgentError" && typeof error.category === "string";
|
|
630
|
+
}
|
|
631
|
+
function classifyHttpStatus(status) {
|
|
632
|
+
if (status === 401 || status === 403) return AgentErrorCategory.AUTH;
|
|
633
|
+
if (status === 429) return AgentErrorCategory.RATE_LIMIT;
|
|
634
|
+
if (status === 413) return AgentErrorCategory.CONTEXT_OVERFLOW;
|
|
635
|
+
if (status >= 500 && status < 600) return AgentErrorCategory.RETRYABLE;
|
|
636
|
+
if (status >= 400 && status < 500) return AgentErrorCategory.INVALID_REQUEST;
|
|
637
|
+
return AgentErrorCategory.UNKNOWN;
|
|
638
|
+
}
|
|
639
|
+
var CONTEXT_OVERFLOW_PATTERNS = [
|
|
640
|
+
"context length",
|
|
641
|
+
"context window",
|
|
642
|
+
"context overflow",
|
|
643
|
+
"context too long",
|
|
644
|
+
"maximum context",
|
|
645
|
+
"too many tokens",
|
|
646
|
+
"token limit",
|
|
647
|
+
"prompt is too long",
|
|
648
|
+
"input is too long",
|
|
649
|
+
"exceeds the model"
|
|
650
|
+
];
|
|
651
|
+
var RATE_LIMIT_PATTERNS = [
|
|
652
|
+
"rate limit",
|
|
653
|
+
"too many requests",
|
|
654
|
+
"ratelimit"
|
|
655
|
+
];
|
|
656
|
+
var AUTH_PATTERNS = [
|
|
657
|
+
"unauthorized",
|
|
658
|
+
"authentication",
|
|
659
|
+
"invalid api key",
|
|
660
|
+
"incorrect api key",
|
|
661
|
+
"api key invalid",
|
|
662
|
+
"permission denied",
|
|
663
|
+
"forbidden",
|
|
664
|
+
"401",
|
|
665
|
+
"403"
|
|
666
|
+
];
|
|
667
|
+
var TIMEOUT_PATTERNS = [
|
|
668
|
+
"timeout",
|
|
669
|
+
"timed out",
|
|
670
|
+
"etimedout"
|
|
671
|
+
];
|
|
672
|
+
function classifyErrorMessage(message) {
|
|
673
|
+
const lower = message.toLowerCase();
|
|
674
|
+
if (CONTEXT_OVERFLOW_PATTERNS.some((p) => lower.includes(p))) {
|
|
675
|
+
return AgentErrorCategory.CONTEXT_OVERFLOW;
|
|
676
|
+
}
|
|
677
|
+
if (RATE_LIMIT_PATTERNS.some((p) => lower.includes(p))) {
|
|
678
|
+
return AgentErrorCategory.RATE_LIMIT;
|
|
679
|
+
}
|
|
680
|
+
if (AUTH_PATTERNS.some((p) => lower.includes(p))) {
|
|
681
|
+
return AgentErrorCategory.AUTH;
|
|
682
|
+
}
|
|
683
|
+
if (TIMEOUT_PATTERNS.some((p) => lower.includes(p))) {
|
|
684
|
+
return AgentErrorCategory.TIMEOUT;
|
|
685
|
+
}
|
|
686
|
+
return AgentErrorCategory.UNKNOWN;
|
|
687
|
+
}
|
|
688
|
+
function classifyError(error) {
|
|
689
|
+
if (isAgentError(error)) return error.category;
|
|
690
|
+
const status = error?.status;
|
|
691
|
+
if (typeof status === "number") return classifyHttpStatus(status);
|
|
692
|
+
const msg = error?.message ?? String(error ?? "");
|
|
693
|
+
return classifyErrorMessage(msg);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// ../agent/ai/overflow.ts
|
|
697
|
+
var OVERFLOW_PATTERNS = [
|
|
698
|
+
// 本框架 formatCategoryError 产出的分类前缀(errors.ts),
|
|
699
|
+
// 覆盖消息体不含已知关键词但已带分类前缀的场景
|
|
700
|
+
/^\[context-overflow\]/i,
|
|
701
|
+
// Anthropic
|
|
702
|
+
/prompt is too long/i,
|
|
703
|
+
/request_too_large/i,
|
|
704
|
+
// Amazon Bedrock
|
|
705
|
+
/input is too long for requested model/i,
|
|
706
|
+
// OpenAI & 兼容
|
|
707
|
+
/exceeds the context window/i,
|
|
708
|
+
/exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\d,]+ tokens?|\s*\([\d,]+\))/i,
|
|
709
|
+
/input token count.*exceeds the maximum/i,
|
|
710
|
+
// xAI Grok
|
|
711
|
+
/maximum prompt length is \d+/i,
|
|
712
|
+
// Groq
|
|
713
|
+
/reduce the length of the messages/i,
|
|
714
|
+
// OpenRouter
|
|
715
|
+
/maximum context length is \d+ tokens/i,
|
|
716
|
+
/exceeds (?:the )?maximum allowed input length of [\d,]+ tokens?/i,
|
|
717
|
+
// Together AI
|
|
718
|
+
/input \(\d+ tokens\) is longer than the model'?s context length \(\d+ tokens\)/i,
|
|
719
|
+
// GitHub Copilot
|
|
720
|
+
/exceeds the limit of \d+/i,
|
|
721
|
+
// llama.cpp
|
|
722
|
+
/exceeds the available context size/i,
|
|
723
|
+
// LM Studio
|
|
724
|
+
/greater than the context length/i,
|
|
725
|
+
// MiniMax
|
|
726
|
+
/context window exceeds limit/i,
|
|
727
|
+
// Kimi
|
|
728
|
+
/exceeded model token limit/i,
|
|
729
|
+
// Mistral
|
|
730
|
+
/too large for model with \d+ maximum context length/i,
|
|
731
|
+
// DS4
|
|
732
|
+
/prompt has [\d,]+ tokens?, but the configured context size is [\d,]+ tokens?/i,
|
|
733
|
+
// z.ai
|
|
734
|
+
/model_context_window_exceeded/i,
|
|
735
|
+
// Ollama
|
|
736
|
+
/prompt too long; exceeded (?:max )?context length/i,
|
|
737
|
+
// DashScope / Qwen
|
|
738
|
+
/range of input length should be/i,
|
|
739
|
+
// 通用回退
|
|
740
|
+
/context[_ ]length[_ ]exceeded/i,
|
|
741
|
+
/too many tokens/i,
|
|
742
|
+
/token limit exceeded/i,
|
|
743
|
+
// Cerebras: 400/413 无 body
|
|
744
|
+
/^4(?:00|13)\s*(?:status code)?\s*\(no body\)/i
|
|
745
|
+
];
|
|
746
|
+
var NON_OVERFLOW_PATTERNS = [
|
|
747
|
+
/^(Throttling error|Service unavailable):/i,
|
|
748
|
+
/rate limit/i,
|
|
749
|
+
/too many requests/i
|
|
750
|
+
];
|
|
751
|
+
function isContextOverflow(message, contextWindow) {
|
|
752
|
+
if (message.stopReason === "error" && message.errorMessage) {
|
|
753
|
+
const errMsg = message.errorMessage;
|
|
754
|
+
const isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(errMsg));
|
|
755
|
+
if (!isNonOverflow && OVERFLOW_PATTERNS.some((p) => p.test(errMsg))) {
|
|
756
|
+
return true;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (contextWindow && message.stopReason === "stop" && message.usage) {
|
|
760
|
+
const inputTokens = message.usage.input + (message.usage.cacheRead ?? 0);
|
|
761
|
+
if (inputTokens > contextWindow) {
|
|
762
|
+
return true;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (contextWindow && message.stopReason === "length" && message.usage && message.usage.output === 0) {
|
|
766
|
+
const inputTokens = message.usage.input + (message.usage.cacheRead ?? 0);
|
|
767
|
+
if (inputTokens >= contextWindow * 0.99) {
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// ../agent/transformer/index.ts
|
|
775
|
+
function ensureToolPairing(messages) {
|
|
776
|
+
const toolCallIds = /* @__PURE__ */ new Set();
|
|
777
|
+
const toolResultIds = /* @__PURE__ */ new Set();
|
|
778
|
+
for (const msg of messages) {
|
|
779
|
+
if (msg.role === "assistant") {
|
|
780
|
+
const content = msg.content;
|
|
781
|
+
if (Array.isArray(content)) {
|
|
782
|
+
for (const tc of extractToolCalls(content)) {
|
|
783
|
+
toolCallIds.add(tc.id);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (msg.role === "toolResult") {
|
|
788
|
+
toolResultIds.add(msg.toolCallId);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const orphanedCalls = /* @__PURE__ */ new Set();
|
|
792
|
+
const orphanedResults = /* @__PURE__ */ new Set();
|
|
793
|
+
for (const id of toolCallIds) {
|
|
794
|
+
if (!toolResultIds.has(id)) orphanedCalls.add(id);
|
|
795
|
+
}
|
|
796
|
+
for (const id of toolResultIds) {
|
|
797
|
+
if (!toolCallIds.has(id)) orphanedResults.add(id);
|
|
798
|
+
}
|
|
799
|
+
if (orphanedCalls.size === 0 && orphanedResults.size === 0) {
|
|
800
|
+
return messages;
|
|
801
|
+
}
|
|
802
|
+
const result = [];
|
|
803
|
+
for (const msg of messages) {
|
|
804
|
+
if (msg.role === "assistant") {
|
|
805
|
+
const content = msg.content;
|
|
806
|
+
if (Array.isArray(content)) {
|
|
807
|
+
const filteredContent = content.filter((block) => {
|
|
808
|
+
if (block.type === "toolCall") {
|
|
809
|
+
return !orphanedCalls.has(block.id);
|
|
810
|
+
}
|
|
811
|
+
return true;
|
|
812
|
+
});
|
|
813
|
+
if (filteredContent.length === 0 && content.length > 0) continue;
|
|
814
|
+
result.push({ ...msg, content: filteredContent });
|
|
815
|
+
} else {
|
|
816
|
+
result.push(msg);
|
|
817
|
+
}
|
|
818
|
+
} else if (msg.role === "toolResult") {
|
|
819
|
+
if (!orphanedResults.has(msg.toolCallId)) {
|
|
820
|
+
result.push(msg);
|
|
821
|
+
}
|
|
822
|
+
} else {
|
|
823
|
+
result.push(msg);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return result;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// ../agent/runtime/index.ts
|
|
830
|
+
import { randomUUID } from "crypto";
|
|
831
|
+
function newTraceId() {
|
|
832
|
+
return `${Date.now().toString(36)}-${randomUUID()}`;
|
|
833
|
+
}
|
|
834
|
+
function newSpanId() {
|
|
835
|
+
return randomUUID();
|
|
836
|
+
}
|
|
837
|
+
function errorClassFromMessage(message) {
|
|
838
|
+
const m = message.match(/^\[([^\]]+)\]/);
|
|
839
|
+
return m ? m[1] : void 0;
|
|
840
|
+
}
|
|
841
|
+
function toolResultStatus(result) {
|
|
842
|
+
if (result.details && typeof result.details === "object" && "error" in result.details) {
|
|
843
|
+
return "error";
|
|
844
|
+
}
|
|
845
|
+
const d = result.details;
|
|
846
|
+
if (d?.blocked) return "blocked";
|
|
847
|
+
if (d?.skipped) return "skipped";
|
|
848
|
+
return "ok";
|
|
849
|
+
}
|
|
850
|
+
function statusOfRetryError(error) {
|
|
851
|
+
if (isAgentError(error)) return error.status;
|
|
852
|
+
const s = error?.status;
|
|
853
|
+
return typeof s === "number" ? s : void 0;
|
|
854
|
+
}
|
|
855
|
+
var DEFAULT_MAX_SESSIONS = 256;
|
|
856
|
+
function createSessionState() {
|
|
857
|
+
return {
|
|
858
|
+
messages: [],
|
|
859
|
+
isStreaming: false,
|
|
860
|
+
abortController: null,
|
|
861
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
862
|
+
hydrated: false,
|
|
863
|
+
queue: Promise.resolve(),
|
|
864
|
+
idleResolvers: [],
|
|
865
|
+
lockHeld: false
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
function withTimeoutSignal(parent, ms) {
|
|
869
|
+
const controller = new AbortController();
|
|
870
|
+
const timer = setTimeout(
|
|
871
|
+
() => controller.abort(new Error(`Tool execution timeout after ${ms}ms`)),
|
|
872
|
+
ms
|
|
873
|
+
);
|
|
874
|
+
if (parent) {
|
|
875
|
+
if (parent.aborted) {
|
|
876
|
+
controller.abort(parent.reason);
|
|
877
|
+
} else {
|
|
878
|
+
parent.addEventListener(
|
|
879
|
+
"abort",
|
|
880
|
+
() => controller.abort(parent.reason),
|
|
881
|
+
{ once: true }
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return { signal: controller.signal, clear: () => clearTimeout(timer) };
|
|
886
|
+
}
|
|
887
|
+
function buildImageContent(media) {
|
|
888
|
+
const dataMatch = media.match(/^data:([^;]+);base64,(.*)$/s);
|
|
889
|
+
if (dataMatch) {
|
|
890
|
+
return { type: "image", mimeType: dataMatch[1], data: dataMatch[2] };
|
|
891
|
+
}
|
|
892
|
+
return { type: "image", mimeType: "image/url", data: media };
|
|
893
|
+
}
|
|
894
|
+
var OVERFLOW_RECOVERY_LIMIT = 2;
|
|
895
|
+
function estimateMessageTokens(message) {
|
|
896
|
+
const content = message.content;
|
|
897
|
+
if (typeof content === "string") return Math.ceil(content.length / 4);
|
|
898
|
+
try {
|
|
899
|
+
return Math.ceil(JSON.stringify(content ?? []).length / 4);
|
|
900
|
+
} catch {
|
|
901
|
+
return 0;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
function estimateTextTokens(text) {
|
|
905
|
+
return Math.ceil(text.length / 4);
|
|
906
|
+
}
|
|
907
|
+
var COMPACTION_SUMMARY_BUDGET_RATIO = 0.6;
|
|
908
|
+
var COMPACTION_LINE_CLAMP = 4e3;
|
|
909
|
+
var DEFAULT_COMPACTION_PROMPT = [
|
|
910
|
+
"\u4F60\u662F\u5BF9\u8BDD\u5386\u53F2\u538B\u7F29\u5668\u3002\u8BF7\u5C06\u4EE5\u4E0B\u5BF9\u8BDD\u5386\u53F2\u538B\u7F29\u4E3A\u4E00\u4EFD\u4FE1\u606F\u5BC6\u5EA6\u9AD8\u7684\u6458\u8981\uFF0C\u4F9B\u540E\u7EED\u5BF9\u8BDD\u4F5C\u4E3A\u4E0A\u4E0B\u6587\u53C2\u8003\u3002",
|
|
911
|
+
"\u8981\u6C42\uFF1A",
|
|
912
|
+
"1. \u4FDD\u7559\u5173\u952E\u4E8B\u5B9E\u3001\u51B3\u7B56\u3001\u7ED3\u8BBA\u4E0E\u672A\u5B8C\u6210\u4E8B\u9879\uFF1B",
|
|
913
|
+
"2. \u4FDD\u7559\u7528\u6237\u660E\u786E\u7684\u504F\u597D\u4E0E\u7EA6\u675F\uFF1B",
|
|
914
|
+
"3. \u4FDD\u7559\u91CD\u8981\u5DE5\u5177\u8C03\u7528\u7684\u76EE\u7684\u4E0E\u7ED3\u679C\u8981\u70B9\uFF08\u7EC6\u8282\u53EF\u7701\u7565\uFF09\uFF1B",
|
|
915
|
+
"4. \u4E22\u5F03\u5BD2\u6684\u3001\u91CD\u590D\u4E0E\u65E0\u5173\u7EC6\u8282\uFF1B",
|
|
916
|
+
"5. \u76F4\u63A5\u8F93\u51FA\u6458\u8981\u6B63\u6587\uFF0C\u4E0D\u8981\u4EFB\u4F55\u524D\u540E\u7F00\u8BF4\u660E\u3002"
|
|
917
|
+
].join("\n");
|
|
918
|
+
var COMPACTION_USER_PREFIX = "[\u4EE5\u4E0B\u4E3A\u6B64\u524D\u5BF9\u8BDD\u5386\u53F2\u7684\u538B\u7F29\u6458\u8981\uFF0C\u4F5C\u4E3A\u4E0A\u4E0B\u6587\u53C2\u8003]";
|
|
919
|
+
function messageToSummaryLine(msg) {
|
|
920
|
+
const clamp = (text) => text.length > COMPACTION_LINE_CLAMP ? `${text.slice(0, COMPACTION_LINE_CLAMP)}\u2026(\u5DF2\u622A\u65AD)` : text;
|
|
921
|
+
switch (msg.role) {
|
|
922
|
+
case "user": {
|
|
923
|
+
const text = typeof msg.content === "string" ? msg.content : extractText(msg.content);
|
|
924
|
+
return `[\u7528\u6237] ${clamp(text)}`;
|
|
925
|
+
}
|
|
926
|
+
case "assistant": {
|
|
927
|
+
const parts = [];
|
|
928
|
+
const content = msg.content;
|
|
929
|
+
if (typeof content === "string") {
|
|
930
|
+
parts.push(content);
|
|
931
|
+
} else {
|
|
932
|
+
for (const block of content) {
|
|
933
|
+
if (block.type === "text") parts.push(block.text);
|
|
934
|
+
else if (block.type === "toolCall") {
|
|
935
|
+
parts.push(`\u8C03\u7528\u5DE5\u5177 ${block.name}(${JSON.stringify(block.arguments)})`);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return `[\u52A9\u624B] ${clamp(parts.join("\uFF1B"))}`;
|
|
940
|
+
}
|
|
941
|
+
case "toolResult": {
|
|
942
|
+
const m = msg;
|
|
943
|
+
const text = typeof m.content === "string" ? m.content : extractText(m.content);
|
|
944
|
+
return `[\u5DE5\u5177\u7ED3\u679C ${m.toolName}] ${clamp(text)}`;
|
|
945
|
+
}
|
|
946
|
+
default: {
|
|
947
|
+
const role = msg.role;
|
|
948
|
+
if (role === "compactionSummary" || role === "stateSnapshot") {
|
|
949
|
+
const text = typeof msg.content === "string" ? msg.content : extractText(msg.content);
|
|
950
|
+
return `[${role === "compactionSummary" ? "\u5386\u53F2\u6458\u8981" : "\u72B6\u6001\u5FEB\u7167"}] ${clamp(text)}`;
|
|
951
|
+
}
|
|
952
|
+
return "";
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
var AgentRuntime = class _AgentRuntime {
|
|
957
|
+
_config;
|
|
958
|
+
_extensions;
|
|
959
|
+
_hooks;
|
|
960
|
+
/** 上下文转换器列表,按数组顺序链式执行(上一个输出作为下一个输入) */
|
|
961
|
+
_transformers;
|
|
962
|
+
_model;
|
|
963
|
+
_streamFn;
|
|
964
|
+
_systemPrompt;
|
|
965
|
+
_thinkingLevel;
|
|
966
|
+
_globalTools = /* @__PURE__ */ new Map();
|
|
967
|
+
/** 多会话状态表:key = sessionKey。模型/工具/扩展/转换器等资源跨会话共享 */
|
|
968
|
+
_sessions;
|
|
969
|
+
/** 默认会话 key(常量 'default';请求未指定 sessionKey 时路由到此会话) */
|
|
970
|
+
_sessionKey;
|
|
971
|
+
/** 内存会话状态表 LRU 上限 */
|
|
972
|
+
_maxSessions;
|
|
973
|
+
_sessionStorage;
|
|
974
|
+
/** Extension 应用时的上下文(shared Map 供 ToolCallContext 引用) */
|
|
975
|
+
_extensionContext;
|
|
976
|
+
_maxTurns;
|
|
977
|
+
_toolTimeoutMs;
|
|
978
|
+
_parallelToolCalls;
|
|
979
|
+
_contextBudgetRatio;
|
|
980
|
+
/** 内置摘要压缩配置(未配置 = 保持旧行为,仅硬截断兜底) */
|
|
981
|
+
_compaction;
|
|
982
|
+
_telemetry;
|
|
983
|
+
/** 框架级工具权限策略(未配置 → 放行,向后兼容) */
|
|
984
|
+
_permissionPolicy;
|
|
985
|
+
/** 审批管理器(pending 决策挂起等待外部批准;未配置 → pending 视为 deny) */
|
|
986
|
+
_approvals;
|
|
987
|
+
/** 审批等待超时(毫秒) */
|
|
988
|
+
_approvalTimeoutMs;
|
|
989
|
+
/** traceId 生成器(测试可注入确定性 id) */
|
|
990
|
+
_traceIdGenerator;
|
|
991
|
+
constructor(options) {
|
|
992
|
+
this._config = options.config ?? {};
|
|
993
|
+
this._extensions = new ExtensionManager();
|
|
994
|
+
this._hooks = this._extensions.getHooks();
|
|
995
|
+
this._transformers = [...options.transformers ?? []];
|
|
996
|
+
this._model = options.model ?? {
|
|
997
|
+
id: "unknown",
|
|
998
|
+
name: "unknown",
|
|
999
|
+
provider: "unknown",
|
|
1000
|
+
contextWindow: 128e3,
|
|
1001
|
+
maxTokens: 8192,
|
|
1002
|
+
reasoning: false
|
|
1003
|
+
};
|
|
1004
|
+
this._streamFn = options.streamFn ?? (async function* () {
|
|
1005
|
+
throw new Error("streamFn \u672A\u8BBE\u7F6E\uFF0C\u8BF7\u901A\u8FC7 setStreamFn() \u6216 RuntimeOptions.streamFn \u63D0\u4F9B");
|
|
1006
|
+
});
|
|
1007
|
+
this._systemPrompt = options.systemPrompt ?? "";
|
|
1008
|
+
this._thinkingLevel = options.thinkingLevel ?? "off";
|
|
1009
|
+
this._sessionStorage = options.sessionStorage;
|
|
1010
|
+
this._maxTurns = options.maxTurns ?? 50;
|
|
1011
|
+
this._toolTimeoutMs = options.toolTimeoutMs ?? 12e4;
|
|
1012
|
+
this._parallelToolCalls = options.parallelToolCalls ?? true;
|
|
1013
|
+
this._contextBudgetRatio = options.contextBudgetRatio ?? 0.8;
|
|
1014
|
+
this._compaction = options.compaction;
|
|
1015
|
+
this._telemetry = options.telemetry;
|
|
1016
|
+
this._permissionPolicy = options.permissionPolicy;
|
|
1017
|
+
this._approvals = options.approvals;
|
|
1018
|
+
this._approvalTimeoutMs = options.approvalTimeoutMs ?? 3e5;
|
|
1019
|
+
this._traceIdGenerator = options.traceIdGenerator;
|
|
1020
|
+
if (options.tools) {
|
|
1021
|
+
for (const tool of options.tools) {
|
|
1022
|
+
this._globalTools.set(tool.name, tool);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
this._sessionKey = "default";
|
|
1026
|
+
this._maxSessions = options.maxSessions ?? DEFAULT_MAX_SESSIONS;
|
|
1027
|
+
this._sessions = /* @__PURE__ */ new Map([[this._sessionKey, createSessionState()]]);
|
|
1028
|
+
}
|
|
1029
|
+
// ─── 静态工厂 ───────────────────────────────────────────────────
|
|
1030
|
+
static create(options = {}) {
|
|
1031
|
+
const runtime = new _AgentRuntime(options);
|
|
1032
|
+
if (options.extensions) {
|
|
1033
|
+
runtime._extensions.registerAll(options.extensions);
|
|
1034
|
+
}
|
|
1035
|
+
const ctx = {
|
|
1036
|
+
config: runtime._config,
|
|
1037
|
+
workspace: options.workspace ?? process.cwd(),
|
|
1038
|
+
sessionKey: runtime._sessionKey,
|
|
1039
|
+
shared: /* @__PURE__ */ new Map()
|
|
1040
|
+
};
|
|
1041
|
+
runtime._extensionContext = ctx;
|
|
1042
|
+
runtime._extensions.applyAll(ctx);
|
|
1043
|
+
return runtime;
|
|
1044
|
+
}
|
|
1045
|
+
// ─── Runtime 接口实现 ───────────────────────────────────────────
|
|
1046
|
+
get config() {
|
|
1047
|
+
return this._config;
|
|
1048
|
+
}
|
|
1049
|
+
get extensions() {
|
|
1050
|
+
return this._extensions;
|
|
1051
|
+
}
|
|
1052
|
+
get hooks() {
|
|
1053
|
+
return this._hooks;
|
|
1054
|
+
}
|
|
1055
|
+
// ─── 工具/模型/流管理 ───────────────────────────────────────────
|
|
1056
|
+
registerTool(tool) {
|
|
1057
|
+
if (this._globalTools.has(tool.name)) {
|
|
1058
|
+
console.warn(`[Runtime] \u5DE5\u5177 "${tool.name}" \u5DF2\u5B58\u5728\uFF0C\u5C06\u88AB\u8986\u76D6`);
|
|
1059
|
+
}
|
|
1060
|
+
this._globalTools.set(tool.name, tool);
|
|
1061
|
+
return this;
|
|
1062
|
+
}
|
|
1063
|
+
registerTools(tools) {
|
|
1064
|
+
for (const tool of tools) {
|
|
1065
|
+
this.registerTool(tool);
|
|
1066
|
+
}
|
|
1067
|
+
return this;
|
|
1068
|
+
}
|
|
1069
|
+
setModel(model) {
|
|
1070
|
+
this._model = model;
|
|
1071
|
+
return this;
|
|
1072
|
+
}
|
|
1073
|
+
setSystemPrompt(prompt) {
|
|
1074
|
+
this._systemPrompt = prompt;
|
|
1075
|
+
return this;
|
|
1076
|
+
}
|
|
1077
|
+
setThinkingLevel(level) {
|
|
1078
|
+
this._thinkingLevel = level;
|
|
1079
|
+
return this;
|
|
1080
|
+
}
|
|
1081
|
+
setStreamFn(fn) {
|
|
1082
|
+
this._streamFn = fn;
|
|
1083
|
+
return this;
|
|
1084
|
+
}
|
|
1085
|
+
registerExtension(extension) {
|
|
1086
|
+
this._extensions.register(extension);
|
|
1087
|
+
return this;
|
|
1088
|
+
}
|
|
1089
|
+
useTransformer(transformer) {
|
|
1090
|
+
this._transformers.push(transformer);
|
|
1091
|
+
return this;
|
|
1092
|
+
}
|
|
1093
|
+
/** 获取指定会话的消息列表(默认会话;会话不存在返回空数组) */
|
|
1094
|
+
getMessages(sessionKey) {
|
|
1095
|
+
const session = this._sessions.get(sessionKey ?? this._sessionKey);
|
|
1096
|
+
if (!session) return [];
|
|
1097
|
+
const messages = session.messages;
|
|
1098
|
+
try {
|
|
1099
|
+
return structuredClone(messages);
|
|
1100
|
+
} catch {
|
|
1101
|
+
return JSON.parse(JSON.stringify(messages));
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
// ─── 核心运行逻辑 ───────────────────────────────────────────────
|
|
1105
|
+
/** 解析请求路由的会话 key:request.sessionKey ?? 默认会话 key */
|
|
1106
|
+
resolveSessionKey(request) {
|
|
1107
|
+
return request.sessionKey ?? this._sessionKey;
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* 获取(懒创建)会话状态。同一 Runtime 下不同 sessionKey 的消息历史、
|
|
1111
|
+
* 串行队列、abort 控制相互独立;共享模型/工具/扩展/转换器。
|
|
1112
|
+
* 超过 _maxSessions 时淘汰最久未用的非活动会话(仅清内存态,不删存储)。
|
|
1113
|
+
*/
|
|
1114
|
+
getSession(key) {
|
|
1115
|
+
let session = this._sessions.get(key);
|
|
1116
|
+
if (session) {
|
|
1117
|
+
this._sessions.delete(key);
|
|
1118
|
+
this._sessions.set(key, session);
|
|
1119
|
+
return session;
|
|
1120
|
+
}
|
|
1121
|
+
session = createSessionState();
|
|
1122
|
+
this._sessions.set(key, session);
|
|
1123
|
+
this.evictIdleSessions();
|
|
1124
|
+
return session;
|
|
1125
|
+
}
|
|
1126
|
+
/** LRU 淘汰:仅淘汰非活动(未运行、未排队)的最久未用会话 */
|
|
1127
|
+
evictIdleSessions() {
|
|
1128
|
+
if (this._sessions.size <= this._maxSessions) return;
|
|
1129
|
+
for (const [key, session] of this._sessions) {
|
|
1130
|
+
if (this._sessions.size <= this._maxSessions) break;
|
|
1131
|
+
if (session.isStreaming || session.lockHeld) continue;
|
|
1132
|
+
this._sessions.delete(key);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
/** 当前活跃的会话 key 列表(含默认会话) */
|
|
1136
|
+
getSessionKeys() {
|
|
1137
|
+
return Array.from(this._sessions.keys());
|
|
1138
|
+
}
|
|
1139
|
+
/** 某会话是否存在(内存中) */
|
|
1140
|
+
hasSession(sessionKey) {
|
|
1141
|
+
return this._sessions.has(sessionKey);
|
|
1142
|
+
}
|
|
1143
|
+
async run(request) {
|
|
1144
|
+
const validation = validateRequest(request);
|
|
1145
|
+
if (!validation.valid) {
|
|
1146
|
+
const invalidResult = new ResultBuilder().error(`\u8BF7\u6C42\u6821\u9A8C\u5931\u8D25: ${validation.errors.join("; ")}`).build();
|
|
1147
|
+
await this.emitTelemetry("onRunEnd", {
|
|
1148
|
+
traceId: this.newTraceId(),
|
|
1149
|
+
sessionKey: this.resolveSessionKey(request),
|
|
1150
|
+
request,
|
|
1151
|
+
durationMs: 0,
|
|
1152
|
+
activeMs: 0,
|
|
1153
|
+
queuedMs: 0,
|
|
1154
|
+
turnCount: 0,
|
|
1155
|
+
result: invalidResult,
|
|
1156
|
+
success: false,
|
|
1157
|
+
errorClass: "validation",
|
|
1158
|
+
tokens: { input: 0, output: 0 }
|
|
1159
|
+
});
|
|
1160
|
+
return invalidResult;
|
|
1161
|
+
}
|
|
1162
|
+
const finalRequest = normalizeRequest(request);
|
|
1163
|
+
const sessionKey = this.resolveSessionKey(finalRequest);
|
|
1164
|
+
const traceId = this.newTraceId();
|
|
1165
|
+
const queuedAt = Date.now();
|
|
1166
|
+
await this.emitTelemetry("onRunStart", {
|
|
1167
|
+
traceId,
|
|
1168
|
+
sessionKey,
|
|
1169
|
+
request: finalRequest,
|
|
1170
|
+
queuedAt
|
|
1171
|
+
});
|
|
1172
|
+
const session = this.getSession(sessionKey);
|
|
1173
|
+
const release = await this.acquire(session);
|
|
1174
|
+
const queuedMs = Date.now() - queuedAt;
|
|
1175
|
+
try {
|
|
1176
|
+
return await this.runWithStorageLock(
|
|
1177
|
+
finalRequest,
|
|
1178
|
+
sessionKey,
|
|
1179
|
+
() => this._run(finalRequest, sessionKey, session, traceId, queuedMs)
|
|
1180
|
+
);
|
|
1181
|
+
} finally {
|
|
1182
|
+
release();
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
async *stream(request) {
|
|
1186
|
+
const validation = validateRequest(request);
|
|
1187
|
+
if (!validation.valid) {
|
|
1188
|
+
const message = `\u8BF7\u6C42\u6821\u9A8C\u5931\u8D25: ${validation.errors.join("; ")}`;
|
|
1189
|
+
await this.emitTelemetry("onRunEnd", {
|
|
1190
|
+
traceId: this.newTraceId(),
|
|
1191
|
+
sessionKey: this.resolveSessionKey(request),
|
|
1192
|
+
request,
|
|
1193
|
+
durationMs: 0,
|
|
1194
|
+
activeMs: 0,
|
|
1195
|
+
queuedMs: 0,
|
|
1196
|
+
turnCount: 0,
|
|
1197
|
+
result: new ResultBuilder().error(message).build(),
|
|
1198
|
+
success: false,
|
|
1199
|
+
errorClass: "validation",
|
|
1200
|
+
tokens: { input: 0, output: 0 }
|
|
1201
|
+
});
|
|
1202
|
+
yield { type: "error", content: message };
|
|
1203
|
+
yield { type: "done" };
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
const finalRequest = normalizeRequest(request);
|
|
1207
|
+
const sessionKey = this.resolveSessionKey(finalRequest);
|
|
1208
|
+
const traceId = this.newTraceId();
|
|
1209
|
+
const queuedAt = Date.now();
|
|
1210
|
+
await this.emitTelemetry("onRunStart", {
|
|
1211
|
+
traceId,
|
|
1212
|
+
sessionKey,
|
|
1213
|
+
request: finalRequest,
|
|
1214
|
+
queuedAt
|
|
1215
|
+
});
|
|
1216
|
+
const session = this.getSession(sessionKey);
|
|
1217
|
+
const release = await this.acquire(session);
|
|
1218
|
+
const queuedMs = Date.now() - queuedAt;
|
|
1219
|
+
try {
|
|
1220
|
+
yield* this.streamWithStorageLock(
|
|
1221
|
+
finalRequest,
|
|
1222
|
+
sessionKey,
|
|
1223
|
+
() => this._stream(finalRequest, sessionKey, session, traceId, queuedMs)
|
|
1224
|
+
);
|
|
1225
|
+
} finally {
|
|
1226
|
+
release();
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* 非 ephemeral 请求在"读(load)-改(run)-写(save)"全程持有存储级锁,
|
|
1231
|
+
* 防止多进程并发写同一会话导致 last-write-wins 丢消息。
|
|
1232
|
+
* ephemeral / 无锁支持 / 无存储时直接执行。
|
|
1233
|
+
*/
|
|
1234
|
+
async runWithStorageLock(request, sessionKey, fn) {
|
|
1235
|
+
const storage = this._sessionStorage;
|
|
1236
|
+
if (request.ephemeral || !storage?.withLock) return fn();
|
|
1237
|
+
return storage.withLock(sessionKey, fn);
|
|
1238
|
+
}
|
|
1239
|
+
/** 流式版本:无法用回调包住生成器,改用手动锁(acquire/release) */
|
|
1240
|
+
async *streamWithStorageLock(request, sessionKey, gen) {
|
|
1241
|
+
const storage = this._sessionStorage;
|
|
1242
|
+
if (request.ephemeral || !storage?.acquireLock) {
|
|
1243
|
+
yield* gen();
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
const lock = await storage.acquireLock(sessionKey);
|
|
1247
|
+
try {
|
|
1248
|
+
yield* gen();
|
|
1249
|
+
} finally {
|
|
1250
|
+
await lock.release();
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
async _run(request, sessionKey, session, traceId, queuedMs) {
|
|
1254
|
+
const activeStartedAt = Date.now();
|
|
1255
|
+
await this._hooks.beforeInitialize.promise(request);
|
|
1256
|
+
await this._hooks.afterInitialize.promise(request);
|
|
1257
|
+
const finalRequest = await this._hooks.beforeRun.promise(request);
|
|
1258
|
+
if (!finalRequest.ephemeral) {
|
|
1259
|
+
await this.hydrateSession(sessionKey, session);
|
|
1260
|
+
}
|
|
1261
|
+
const compilation = this.createCompilation(finalRequest, sessionKey, session, traceId);
|
|
1262
|
+
compilation.messages.push(this.buildUserMessage(finalRequest));
|
|
1263
|
+
try {
|
|
1264
|
+
await this.runLoop(compilation, finalRequest, session);
|
|
1265
|
+
const result = this.buildResult(compilation);
|
|
1266
|
+
await this._hooks.beforeEmit.promise(result);
|
|
1267
|
+
await this._hooks.afterEmit.promise(result);
|
|
1268
|
+
await this._hooks.done.promise(result, finalRequest);
|
|
1269
|
+
compilation.completed = true;
|
|
1270
|
+
await this.emitRunEnd(finalRequest, sessionKey, compilation, result, queuedMs, activeStartedAt);
|
|
1271
|
+
return result;
|
|
1272
|
+
} catch (err) {
|
|
1273
|
+
const error = err;
|
|
1274
|
+
if (error?.name !== "AbortError") {
|
|
1275
|
+
console.error("[Runtime] \u8FD0\u884C\u5931\u8D25:", error?.stack ?? error);
|
|
1276
|
+
}
|
|
1277
|
+
await this._hooks.failed.promise(error, finalRequest);
|
|
1278
|
+
const result = new ResultBuilder().error(error.message).build();
|
|
1279
|
+
await this.emitRunEnd(finalRequest, sessionKey, compilation, result, queuedMs, activeStartedAt);
|
|
1280
|
+
return result;
|
|
1281
|
+
} finally {
|
|
1282
|
+
await this.persistSessionSafe(finalRequest, sessionKey);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
async *_stream(request, sessionKey, session, traceId, queuedMs) {
|
|
1286
|
+
const activeStartedAt = Date.now();
|
|
1287
|
+
await this._hooks.beforeInitialize.promise(request);
|
|
1288
|
+
await this._hooks.afterInitialize.promise(request);
|
|
1289
|
+
const finalRequest = await this._hooks.beforeRun.promise(request);
|
|
1290
|
+
if (!finalRequest.ephemeral) {
|
|
1291
|
+
await this.hydrateSession(sessionKey, session);
|
|
1292
|
+
}
|
|
1293
|
+
const compilation = this.createCompilation(finalRequest, sessionKey, session, traceId);
|
|
1294
|
+
compilation.messages.push(this.buildUserMessage(finalRequest));
|
|
1295
|
+
try {
|
|
1296
|
+
for await (const chunk of this.runLoopStream(compilation, finalRequest, session)) {
|
|
1297
|
+
yield chunk;
|
|
1298
|
+
}
|
|
1299
|
+
const result = this.buildResult(compilation);
|
|
1300
|
+
await this._hooks.beforeEmit.promise(result);
|
|
1301
|
+
await this._hooks.afterEmit.promise(result);
|
|
1302
|
+
await this._hooks.done.promise(result, finalRequest);
|
|
1303
|
+
yield { type: "done", result };
|
|
1304
|
+
await this.emitRunEnd(finalRequest, sessionKey, compilation, result, queuedMs, activeStartedAt);
|
|
1305
|
+
} catch (err) {
|
|
1306
|
+
const error = err;
|
|
1307
|
+
if (error?.name !== "AbortError") {
|
|
1308
|
+
console.error("[Runtime] \u6D41\u5F0F\u8FD0\u884C\u5931\u8D25:", error?.stack ?? error);
|
|
1309
|
+
}
|
|
1310
|
+
await this._hooks.failed.promise(error, finalRequest);
|
|
1311
|
+
const result = new ResultBuilder().error(error.message).build();
|
|
1312
|
+
yield { type: "error", content: error.message };
|
|
1313
|
+
yield { type: "done", result };
|
|
1314
|
+
await this.emitRunEnd(finalRequest, sessionKey, compilation, result, queuedMs, activeStartedAt);
|
|
1315
|
+
} finally {
|
|
1316
|
+
await this.persistSessionSafe(finalRequest, sessionKey);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
createCompilation(request, sessionKey, session, traceId) {
|
|
1320
|
+
return {
|
|
1321
|
+
request,
|
|
1322
|
+
graph: createTaskGraph(),
|
|
1323
|
+
resources: [],
|
|
1324
|
+
messages: (session ?? this.getSession(sessionKey ?? this._sessionKey)).messages,
|
|
1325
|
+
completed: false,
|
|
1326
|
+
traceId: traceId ?? this.newTraceId()
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
async close() {
|
|
1330
|
+
await Promise.allSettled(Array.from(this._sessions.values(), (s) => s.queue));
|
|
1331
|
+
for (const session of this._sessions.values()) {
|
|
1332
|
+
session.messages = [];
|
|
1333
|
+
session.hydrated = false;
|
|
1334
|
+
}
|
|
1335
|
+
this._sessions.clear();
|
|
1336
|
+
this._extensions.clear();
|
|
1337
|
+
this._transformers = [];
|
|
1338
|
+
}
|
|
1339
|
+
// ─── 对话循环(同步) ───────────────────────────────────────────
|
|
1340
|
+
async runLoop(compilation, request, session) {
|
|
1341
|
+
session.isStreaming = true;
|
|
1342
|
+
session.abortController = new AbortController();
|
|
1343
|
+
const sessionKey = this.resolveSessionKey(request);
|
|
1344
|
+
try {
|
|
1345
|
+
let maxTurns = this._maxTurns;
|
|
1346
|
+
let turnCount = 0;
|
|
1347
|
+
while (maxTurns-- > 0) {
|
|
1348
|
+
turnCount += 1;
|
|
1349
|
+
await this.transformMessages(compilation, sessionKey);
|
|
1350
|
+
await this.maybeCompactByThreshold(
|
|
1351
|
+
compilation,
|
|
1352
|
+
sessionKey,
|
|
1353
|
+
session.abortController.signal
|
|
1354
|
+
);
|
|
1355
|
+
const assistantMessage = await this.streamModel(
|
|
1356
|
+
compilation,
|
|
1357
|
+
session.abortController.signal,
|
|
1358
|
+
sessionKey
|
|
1359
|
+
);
|
|
1360
|
+
compilation.messages.push(assistantMessage);
|
|
1361
|
+
await this.persistSessionSafe(request, sessionKey);
|
|
1362
|
+
const toolCalls = extractToolCalls(assistantMessage.content);
|
|
1363
|
+
if (toolCalls.length === 0) {
|
|
1364
|
+
break;
|
|
1365
|
+
}
|
|
1366
|
+
const outcome = await this.executeToolCalls(
|
|
1367
|
+
compilation,
|
|
1368
|
+
toolCalls,
|
|
1369
|
+
session.abortController.signal
|
|
1370
|
+
);
|
|
1371
|
+
await this.persistSessionSafe(request, sessionKey);
|
|
1372
|
+
if (outcome.terminate) {
|
|
1373
|
+
compilation.terminateReason = outcome.terminateReason ?? "terminated";
|
|
1374
|
+
break;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
if (maxTurns < 0) compilation.maxTurnsExhausted = true;
|
|
1378
|
+
compilation.turnCount = turnCount;
|
|
1379
|
+
} finally {
|
|
1380
|
+
this.markIdle(session);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
// ─── 对话循环(流式) ───────────────────────────────────────────
|
|
1384
|
+
async *runLoopStream(compilation, request, session) {
|
|
1385
|
+
session.isStreaming = true;
|
|
1386
|
+
session.abortController = new AbortController();
|
|
1387
|
+
const sessionKey = this.resolveSessionKey(request);
|
|
1388
|
+
try {
|
|
1389
|
+
let maxTurns = this._maxTurns;
|
|
1390
|
+
let turnCount = 0;
|
|
1391
|
+
while (maxTurns-- > 0) {
|
|
1392
|
+
turnCount += 1;
|
|
1393
|
+
await this.transformMessages(compilation, sessionKey);
|
|
1394
|
+
await this.maybeCompactByThreshold(
|
|
1395
|
+
compilation,
|
|
1396
|
+
sessionKey,
|
|
1397
|
+
session.abortController.signal
|
|
1398
|
+
);
|
|
1399
|
+
const turn = this.modelTurnWithRecovery(
|
|
1400
|
+
compilation,
|
|
1401
|
+
session.abortController.signal,
|
|
1402
|
+
sessionKey,
|
|
1403
|
+
true
|
|
1404
|
+
// stream 模式:同时统计首 token 延迟
|
|
1405
|
+
);
|
|
1406
|
+
let turnResult = await turn.next();
|
|
1407
|
+
while (!turnResult.done) {
|
|
1408
|
+
yield turnResult.value;
|
|
1409
|
+
turnResult = await turn.next();
|
|
1410
|
+
}
|
|
1411
|
+
const assistantMessage = turnResult.value;
|
|
1412
|
+
if (!assistantMessage) break;
|
|
1413
|
+
compilation.messages.push(assistantMessage);
|
|
1414
|
+
await this.persistSessionSafe(request, sessionKey);
|
|
1415
|
+
const toolCalls = extractToolCalls(assistantMessage.content);
|
|
1416
|
+
if (toolCalls.length === 0) {
|
|
1417
|
+
break;
|
|
1418
|
+
}
|
|
1419
|
+
for (const toolCall of toolCalls) {
|
|
1420
|
+
yield {
|
|
1421
|
+
type: "tool_start",
|
|
1422
|
+
toolName: toolCall.name,
|
|
1423
|
+
toolCallId: toolCall.id
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
const outcome = await this.executeToolCallsStreaming(
|
|
1427
|
+
compilation,
|
|
1428
|
+
toolCalls,
|
|
1429
|
+
session.abortController.signal
|
|
1430
|
+
);
|
|
1431
|
+
await this.persistSessionSafe(request, sessionKey);
|
|
1432
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
1433
|
+
yield {
|
|
1434
|
+
type: "tool_end",
|
|
1435
|
+
toolName: toolCalls[i].name,
|
|
1436
|
+
toolCallId: toolCalls[i].id,
|
|
1437
|
+
isError: this.isErrorResult(outcome.results[i])
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
if (outcome.terminate) {
|
|
1441
|
+
compilation.terminateReason = outcome.terminateReason ?? "terminated";
|
|
1442
|
+
break;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
if (maxTurns < 0) compilation.maxTurnsExhausted = true;
|
|
1446
|
+
compilation.turnCount = turnCount;
|
|
1447
|
+
} finally {
|
|
1448
|
+
this.markIdle(session);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
// ─── 工具执行 ───────────────────────────────────────────────────
|
|
1452
|
+
/** 同步循环:执行工具调用并将结果消息按原顺序追加 */
|
|
1453
|
+
async executeToolCalls(compilation, toolCalls, signal) {
|
|
1454
|
+
const outcome = await this.runTools(toolCalls, signal, compilation.request, compilation.traceId);
|
|
1455
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
1456
|
+
compilation.messages.push(
|
|
1457
|
+
this.buildToolResultMessage(toolCalls[i], outcome.results[i])
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
return outcome;
|
|
1461
|
+
}
|
|
1462
|
+
/** 流式循环:执行工具调用,返回结果(消息由调用方追加) */
|
|
1463
|
+
async executeToolCallsStreaming(compilation, toolCalls, signal) {
|
|
1464
|
+
const outcome = await this.runTools(toolCalls, signal, compilation.request, compilation.traceId);
|
|
1465
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
1466
|
+
compilation.messages.push(
|
|
1467
|
+
this.buildToolResultMessage(toolCalls[i], outcome.results[i])
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
return outcome;
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* 执行一组工具调用:parallelToolCalls 为 true 时并行,否则串行。
|
|
1474
|
+
* 返回 ToolExecutionOutcome:任一工具请求 terminate 即终止整个 run;
|
|
1475
|
+
* 串行模式下 terminate 后剩余工具生成 skipped 结果以保持配对完整。
|
|
1476
|
+
*/
|
|
1477
|
+
async runTools(toolCalls, signal, request, traceId) {
|
|
1478
|
+
const execute = (tc) => this.executeTool(tc, signal, request, traceId);
|
|
1479
|
+
if (this._parallelToolCalls && toolCalls.length > 1) {
|
|
1480
|
+
const outcomes = await Promise.all(toolCalls.map(execute));
|
|
1481
|
+
const terminated = outcomes.find((o) => o.terminate);
|
|
1482
|
+
return {
|
|
1483
|
+
results: outcomes.map((o) => o.result),
|
|
1484
|
+
terminate: !!terminated,
|
|
1485
|
+
terminateReason: terminated?.terminateReason
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
const results = [];
|
|
1489
|
+
let terminate = false;
|
|
1490
|
+
let terminateReason;
|
|
1491
|
+
for (const tc of toolCalls) {
|
|
1492
|
+
if (terminate) {
|
|
1493
|
+
results.push(this.makeSkippedResult(tc));
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
const outcome = await execute(tc);
|
|
1497
|
+
results.push(outcome.result);
|
|
1498
|
+
if (outcome.terminate) {
|
|
1499
|
+
terminate = true;
|
|
1500
|
+
terminateReason = outcome.terminateReason;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return { results, terminate, terminateReason };
|
|
1504
|
+
}
|
|
1505
|
+
async executeTool(toolCall, signal, request, traceId) {
|
|
1506
|
+
const tool = this._globalTools.get(toolCall.name);
|
|
1507
|
+
if (!tool) {
|
|
1508
|
+
return {
|
|
1509
|
+
result: {
|
|
1510
|
+
content: [createTextContent(`Tool "${toolCall.name}" not found`)],
|
|
1511
|
+
details: { error: `Tool "${toolCall.name}" not found` }
|
|
1512
|
+
},
|
|
1513
|
+
terminate: false
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
let args = toolCall.arguments;
|
|
1517
|
+
if (tool.prepareArguments) {
|
|
1518
|
+
args = tool.prepareArguments(toolCall.arguments);
|
|
1519
|
+
}
|
|
1520
|
+
if (this._permissionPolicy) {
|
|
1521
|
+
const permissionReq = {
|
|
1522
|
+
toolName: toolCall.name,
|
|
1523
|
+
permissions: tool.permissions ?? [],
|
|
1524
|
+
args,
|
|
1525
|
+
sessionKey: request.sessionKey ?? this._sessionKey,
|
|
1526
|
+
request,
|
|
1527
|
+
shared: this._extensionContext?.shared ?? /* @__PURE__ */ new Map()
|
|
1528
|
+
};
|
|
1529
|
+
const decision = await this._permissionPolicy.check(permissionReq);
|
|
1530
|
+
let allowed = decision === "allow";
|
|
1531
|
+
if (decision === "confirm") {
|
|
1532
|
+
allowed = this._permissionPolicy.confirm ? await this._permissionPolicy.confirm(permissionReq) : false;
|
|
1533
|
+
}
|
|
1534
|
+
if (decision === "pending") {
|
|
1535
|
+
if (this._approvals) {
|
|
1536
|
+
const approval = this._approvals.create(permissionReq, {
|
|
1537
|
+
timeoutMs: this._approvalTimeoutMs,
|
|
1538
|
+
signal
|
|
1539
|
+
});
|
|
1540
|
+
await this.emitTelemetry("onApprovalPending", {
|
|
1541
|
+
traceId,
|
|
1542
|
+
sessionKey: permissionReq.sessionKey,
|
|
1543
|
+
approvalId: approval.id,
|
|
1544
|
+
toolName: toolCall.name,
|
|
1545
|
+
permissions: permissionReq.permissions,
|
|
1546
|
+
args,
|
|
1547
|
+
expiresAt: approval.expiresAt
|
|
1548
|
+
});
|
|
1549
|
+
const outcome = await this._approvals.wait(approval);
|
|
1550
|
+
allowed = outcome.status === "approved";
|
|
1551
|
+
await this.emitTelemetry("onApprovalResolved", {
|
|
1552
|
+
traceId,
|
|
1553
|
+
sessionKey: permissionReq.sessionKey,
|
|
1554
|
+
approvalId: approval.id,
|
|
1555
|
+
toolName: toolCall.name,
|
|
1556
|
+
outcome: outcome.status,
|
|
1557
|
+
waitedMs: outcome.waitedMs
|
|
1558
|
+
});
|
|
1559
|
+
} else {
|
|
1560
|
+
allowed = false;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
if (!allowed) {
|
|
1564
|
+
const reason = `permission denied by policy for tool "${toolCall.name}"`;
|
|
1565
|
+
await this.emitTelemetry("onPermissionDenied", {
|
|
1566
|
+
traceId,
|
|
1567
|
+
sessionKey: permissionReq.sessionKey,
|
|
1568
|
+
toolName: toolCall.name,
|
|
1569
|
+
permissions: permissionReq.permissions,
|
|
1570
|
+
args,
|
|
1571
|
+
reason
|
|
1572
|
+
});
|
|
1573
|
+
return {
|
|
1574
|
+
result: this.makeBlockedResult(reason),
|
|
1575
|
+
terminate: false
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
const { signal: timedSignal, clear } = withTimeoutSignal(signal, this._toolTimeoutMs);
|
|
1580
|
+
try {
|
|
1581
|
+
const beforeCtx = this.buildToolCallContext(toolCall, tool, args, request, timedSignal);
|
|
1582
|
+
const before = await this._hooks.beforeToolCall.promise(
|
|
1583
|
+
{ block: false, terminate: false, args },
|
|
1584
|
+
beforeCtx
|
|
1585
|
+
);
|
|
1586
|
+
if (before.terminate) {
|
|
1587
|
+
const reason = before.reason ?? "terminated by beforeToolCall";
|
|
1588
|
+
return {
|
|
1589
|
+
result: this.makeBlockedResult(reason),
|
|
1590
|
+
terminate: true,
|
|
1591
|
+
terminateReason: reason
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
if (before.block) {
|
|
1595
|
+
const reason = before.reason ?? "blocked by beforeToolCall";
|
|
1596
|
+
return {
|
|
1597
|
+
result: this.makeBlockedResult(reason),
|
|
1598
|
+
terminate: false
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
args = before.args;
|
|
1602
|
+
const toolStartedAt = Date.now();
|
|
1603
|
+
let result;
|
|
1604
|
+
try {
|
|
1605
|
+
result = await tool.execute(toolCall.id, args, timedSignal);
|
|
1606
|
+
} catch (err) {
|
|
1607
|
+
const message = err?.message ?? String(err);
|
|
1608
|
+
result = {
|
|
1609
|
+
content: [createTextContent(message)],
|
|
1610
|
+
details: { error: message }
|
|
1611
|
+
};
|
|
1612
|
+
}
|
|
1613
|
+
const status = toolResultStatus(result);
|
|
1614
|
+
await this.emitTelemetry("onToolCall", {
|
|
1615
|
+
traceId,
|
|
1616
|
+
spanId: newSpanId(),
|
|
1617
|
+
sessionKey: request.sessionKey ?? this._sessionKey,
|
|
1618
|
+
toolName: toolCall.name,
|
|
1619
|
+
args,
|
|
1620
|
+
durationMs: Date.now() - toolStartedAt,
|
|
1621
|
+
result,
|
|
1622
|
+
success: status === "ok",
|
|
1623
|
+
status,
|
|
1624
|
+
errorClass: status === "error" ? errorClassFromMessage(String(result.details?.error ?? "")) ?? "tool_error" : void 0
|
|
1625
|
+
});
|
|
1626
|
+
const afterCtx = this.buildToolCallContext(toolCall, tool, args, request, timedSignal);
|
|
1627
|
+
const after = await this._hooks.afterToolCall.promise(
|
|
1628
|
+
{ result, terminate: false },
|
|
1629
|
+
afterCtx
|
|
1630
|
+
);
|
|
1631
|
+
return {
|
|
1632
|
+
result: after.result,
|
|
1633
|
+
terminate: after.terminate,
|
|
1634
|
+
terminateReason: after.terminate ? "terminated by afterToolCall" : void 0
|
|
1635
|
+
};
|
|
1636
|
+
} finally {
|
|
1637
|
+
clear();
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
isErrorResult(result) {
|
|
1641
|
+
return !!(result.details && typeof result.details === "object" && "error" in result.details);
|
|
1642
|
+
}
|
|
1643
|
+
/** beforeToolCall 阻断/终止时生成拒绝结果(非执行错误,isError=false) */
|
|
1644
|
+
makeBlockedResult(reason) {
|
|
1645
|
+
return {
|
|
1646
|
+
content: [createTextContent(`[blocked] ${reason}`)],
|
|
1647
|
+
details: { blocked: true, reason }
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
/** 串行模式下前序工具 terminate 后,剩余工具生成 skipped 结果保持配对 */
|
|
1651
|
+
makeSkippedResult(toolCall) {
|
|
1652
|
+
return {
|
|
1653
|
+
content: [createTextContent(`[skipped] run terminated by prior tool: ${toolCall.name}`)],
|
|
1654
|
+
details: { skipped: true, toolName: toolCall.name }
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
/** 构建 ToolCallContext:beforeToolCall/afterToolCall 的调用上下文 */
|
|
1658
|
+
buildToolCallContext(toolCall, tool, args, request, signal) {
|
|
1659
|
+
return {
|
|
1660
|
+
toolCall,
|
|
1661
|
+
tool,
|
|
1662
|
+
args,
|
|
1663
|
+
sessionKey: request.sessionKey ?? this._sessionKey,
|
|
1664
|
+
request,
|
|
1665
|
+
shared: this._extensionContext?.shared ?? /* @__PURE__ */ new Map(),
|
|
1666
|
+
signal
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
buildToolResultMessage(toolCall, result) {
|
|
1670
|
+
return {
|
|
1671
|
+
role: "toolResult",
|
|
1672
|
+
content: result.content,
|
|
1673
|
+
toolCallId: toolCall.id,
|
|
1674
|
+
toolName: toolCall.name,
|
|
1675
|
+
isError: this.isErrorResult(result),
|
|
1676
|
+
timestamp: Date.now()
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
buildUserMessage(request) {
|
|
1680
|
+
if (request.media && request.media.length > 0) {
|
|
1681
|
+
const blocks = [
|
|
1682
|
+
createTextContent(request.message),
|
|
1683
|
+
...request.media.filter(Boolean).map(buildImageContent)
|
|
1684
|
+
];
|
|
1685
|
+
return {
|
|
1686
|
+
role: "user",
|
|
1687
|
+
content: blocks,
|
|
1688
|
+
timestamp: Date.now()
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
return {
|
|
1692
|
+
role: "user",
|
|
1693
|
+
content: request.message,
|
|
1694
|
+
timestamp: Date.now()
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
// ─── 内部方法 ───────────────────────────────────────────────────
|
|
1698
|
+
/**
|
|
1699
|
+
* 获取同一会话的执行锁:返回 release 函数,调用后释放。
|
|
1700
|
+
* 同一 sessionKey 的 run/stream 会串行执行,避免消息数组交错、
|
|
1701
|
+
* abortController 互相覆盖、hydrate 竞态。
|
|
1702
|
+
*/
|
|
1703
|
+
async acquire(session) {
|
|
1704
|
+
let release;
|
|
1705
|
+
const prev = session.queue;
|
|
1706
|
+
session.queue = new Promise((resolve) => {
|
|
1707
|
+
release = () => resolve();
|
|
1708
|
+
});
|
|
1709
|
+
await prev;
|
|
1710
|
+
session.lockHeld = true;
|
|
1711
|
+
return () => {
|
|
1712
|
+
session.lockHeld = false;
|
|
1713
|
+
release();
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
/** 标记会话空闲并唤醒所有 waitForIdle 等待者 */
|
|
1717
|
+
markIdle(session) {
|
|
1718
|
+
session.isStreaming = false;
|
|
1719
|
+
session.abortController = null;
|
|
1720
|
+
if (session.idleResolvers.length > 0) {
|
|
1721
|
+
const resolvers = session.idleResolvers.splice(0);
|
|
1722
|
+
for (const resolve of resolvers) resolve();
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
// ─── 会话持久化 ─────────────────────────────────────────────────
|
|
1726
|
+
/** 从存储懒加载会话(串行化后无竞态,每个会话仅恢复一次) */
|
|
1727
|
+
async hydrateSession(sessionKey, session) {
|
|
1728
|
+
if (!this._sessionStorage) return;
|
|
1729
|
+
if (session.hydrated) return;
|
|
1730
|
+
session.hydrated = true;
|
|
1731
|
+
const stored = await this._sessionStorage.load(sessionKey);
|
|
1732
|
+
if (!stored) return;
|
|
1733
|
+
session.messages = stored.messages;
|
|
1734
|
+
session.createdAt = stored.createdAt;
|
|
1735
|
+
}
|
|
1736
|
+
/** 整体保存指定会话(ephemeral 跳过;失败不影响运行结果) */
|
|
1737
|
+
async persistSession(sessionKey, session) {
|
|
1738
|
+
if (!this._sessionStorage) return;
|
|
1739
|
+
const stored = {
|
|
1740
|
+
key: sessionKey,
|
|
1741
|
+
version: SESSION_VERSION,
|
|
1742
|
+
messages: session.messages,
|
|
1743
|
+
model: this.deriveModel(session.messages),
|
|
1744
|
+
usage: this.sumUsage(session.messages),
|
|
1745
|
+
createdAt: session.createdAt,
|
|
1746
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1747
|
+
};
|
|
1748
|
+
await this._sessionStorage.save(sessionKey, stored);
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* 实时持久化指定会话:每轮 assistant 回复/工具结果完成后调用,
|
|
1752
|
+
* 让运行中的会话随时可被持久化数据观测到。ephemeral 跳过;
|
|
1753
|
+
* 存储失败仅告警,不影响对话循环继续。
|
|
1754
|
+
*/
|
|
1755
|
+
async persistSessionSafe(request, sessionKey) {
|
|
1756
|
+
if (request.ephemeral || !this._sessionStorage) return;
|
|
1757
|
+
try {
|
|
1758
|
+
const session = this._sessions.get(sessionKey);
|
|
1759
|
+
if (!session) return;
|
|
1760
|
+
await this.persistSession(sessionKey, session);
|
|
1761
|
+
} catch (err) {
|
|
1762
|
+
console.warn("[Runtime] \u4F1A\u8BDD\u6301\u4E45\u5316\u5931\u8D25:", err?.message);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
/** 从消息中推导最后使用的模型 */
|
|
1766
|
+
deriveModel(messages) {
|
|
1767
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1768
|
+
const msg = messages[i];
|
|
1769
|
+
if (msg.role === "assistant" && msg.model) {
|
|
1770
|
+
return {
|
|
1771
|
+
provider: msg.provider ?? this._model.provider,
|
|
1772
|
+
modelId: msg.model
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
return null;
|
|
1777
|
+
}
|
|
1778
|
+
/** 汇总所有 assistant 消息的 token 用量(含 cache) */
|
|
1779
|
+
sumUsage(messages) {
|
|
1780
|
+
const usage = createEmptyUsage();
|
|
1781
|
+
for (const msg of messages) {
|
|
1782
|
+
if (msg.role === "assistant") {
|
|
1783
|
+
const u = msg.usage;
|
|
1784
|
+
if (u) {
|
|
1785
|
+
usage.input += u.input;
|
|
1786
|
+
usage.output += u.output;
|
|
1787
|
+
usage.total += u.total;
|
|
1788
|
+
usage.cacheRead = (usage.cacheRead ?? 0) + (u.cacheRead ?? 0);
|
|
1789
|
+
usage.cacheWrite = (usage.cacheWrite ?? 0) + (u.cacheWrite ?? 0);
|
|
1790
|
+
usage.reasoning = (usage.reasoning ?? 0) + (u.reasoning ?? 0);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
return usage;
|
|
1795
|
+
}
|
|
1796
|
+
/** 链式执行上下文转换器(按数组顺序,上一个输出作为下一个输入) */
|
|
1797
|
+
async transformMessages(compilation, sessionKey) {
|
|
1798
|
+
if (this._transformers.length === 0) {
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
let resources = messagesToResources(compilation.messages);
|
|
1802
|
+
const context = {
|
|
1803
|
+
graph: compilation.graph,
|
|
1804
|
+
runtime: {
|
|
1805
|
+
sessionKey,
|
|
1806
|
+
turn: compilation.messages.length,
|
|
1807
|
+
contextWindow: this._model.contextWindow,
|
|
1808
|
+
maxTokens: this._model.maxTokens,
|
|
1809
|
+
contextBudgetRatio: this._contextBudgetRatio
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
for (const transformer of this._transformers) {
|
|
1813
|
+
try {
|
|
1814
|
+
resources = await transformer.transform(resources, context);
|
|
1815
|
+
} catch (err) {
|
|
1816
|
+
console.warn(
|
|
1817
|
+
`[Runtime] \u8F6C\u6362\u5668 "${transformer.name}" \u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7:`,
|
|
1818
|
+
err?.message ?? err
|
|
1819
|
+
);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
const messages = resourcesToMessages(resources);
|
|
1823
|
+
compilation.messages.splice(0, compilation.messages.length, ...messages);
|
|
1824
|
+
}
|
|
1825
|
+
buildContext(messages) {
|
|
1826
|
+
const tools = Array.from(this._globalTools.values());
|
|
1827
|
+
return {
|
|
1828
|
+
systemPrompt: this._systemPrompt,
|
|
1829
|
+
// compactionSummary 为内部扩展 role,provider 适配层仅支持
|
|
1830
|
+
// user/assistant/toolResult,发出前统一转为带标注的 user 消息
|
|
1831
|
+
messages: messages.filter((m) => m.role !== "system").map((m) => m.role === "compactionSummary" ? this.compactionSummaryToUser(m) : m),
|
|
1832
|
+
tools: tools.length > 0 ? tools : void 0
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
/** compactionSummary 消息 → user 消息(所有 provider 均兼容 user role) */
|
|
1836
|
+
compactionSummaryToUser(msg) {
|
|
1837
|
+
const text = typeof msg.content === "string" ? msg.content : extractText(msg.content);
|
|
1838
|
+
return {
|
|
1839
|
+
role: "user",
|
|
1840
|
+
content: `${COMPACTION_USER_PREFIX}
|
|
1841
|
+
${text}`,
|
|
1842
|
+
timestamp: msg.timestamp
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
async streamModel(compilation, signal, sessionKey) {
|
|
1846
|
+
const turn = this.modelTurnWithRecovery(compilation, signal, sessionKey, false);
|
|
1847
|
+
let r = await turn.next();
|
|
1848
|
+
while (!r.done) r = await turn.next();
|
|
1849
|
+
return r.value ?? this.emptyAssistantMessage();
|
|
1850
|
+
}
|
|
1851
|
+
/** 空的 assistant 消息(流异常中断无 done/error 事件时的兜底,与旧 streamModel 行为一致) */
|
|
1852
|
+
emptyAssistantMessage() {
|
|
1853
|
+
return {
|
|
1854
|
+
role: "assistant",
|
|
1855
|
+
content: [],
|
|
1856
|
+
stopReason: "stop",
|
|
1857
|
+
usage: createEmptyUsage(),
|
|
1858
|
+
model: this._model.id,
|
|
1859
|
+
provider: this._model.provider,
|
|
1860
|
+
timestamp: Date.now()
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
/**
|
|
1864
|
+
* 单回合模型调用 + 上下文溢出自动恢复闭环。
|
|
1865
|
+
*
|
|
1866
|
+
* 检测(isContextOverflow,统一传入 model.contextWindow,覆盖显式错误 /
|
|
1867
|
+
* 静默溢出 / 截断溢出三模式)→ 丢弃失败的 assistant 消息 → 截断会话历史
|
|
1868
|
+
* → 同回合重试(不消耗回合数,上限 OVERFLOW_RECOVERY_LIMIT):
|
|
1869
|
+
*
|
|
1870
|
+
* - 显式错误 / 零产出截断溢出:丢弃错误消息后重试;流式路径吞掉可恢复的
|
|
1871
|
+
* error chunk(消费者看不到瞬态错误),不可恢复时补发。
|
|
1872
|
+
* - 静默溢出(stop + 有完整产出):保留回复,仅压缩旧上下文供后续轮次。
|
|
1873
|
+
* - 恢复耗尽或单请求超窗(无可丢弃):返回最后一次错误消息,维持旧行为。
|
|
1874
|
+
*
|
|
1875
|
+
* 流式路径 yield 模型事件 chunk;非流式路径由 streamModel 消费(chunk 丢弃)。
|
|
1876
|
+
* 返回最终 assistant 消息;无 done/error 事件时返回 null。
|
|
1877
|
+
*/
|
|
1878
|
+
async *modelTurnWithRecovery(compilation, signal, sessionKey, stream) {
|
|
1879
|
+
const contextWindow = this._model.contextWindow;
|
|
1880
|
+
let recoveries = 0;
|
|
1881
|
+
while (true) {
|
|
1882
|
+
let assistant = null;
|
|
1883
|
+
let suppressed = null;
|
|
1884
|
+
for await (const event of this.streamModelEvents(compilation, signal, stream)) {
|
|
1885
|
+
if (event.type === "error") {
|
|
1886
|
+
const msg = event.message;
|
|
1887
|
+
if (recoveries < OVERFLOW_RECOVERY_LIMIT && isContextOverflow(msg, contextWindow)) {
|
|
1888
|
+
suppressed = msg;
|
|
1889
|
+
continue;
|
|
1890
|
+
}
|
|
1891
|
+
const chunk = this.streamEventToChunk(event);
|
|
1892
|
+
if (chunk) yield chunk;
|
|
1893
|
+
assistant = msg;
|
|
1894
|
+
} else {
|
|
1895
|
+
const chunk = this.streamEventToChunk(event);
|
|
1896
|
+
if (chunk) yield chunk;
|
|
1897
|
+
if (event.type === "done") assistant = event.message;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
const final = assistant ?? suppressed;
|
|
1901
|
+
if (!final) return null;
|
|
1902
|
+
if (isContextOverflow(final, contextWindow)) {
|
|
1903
|
+
const failed = final.stopReason === "error" || (final.usage?.output ?? 0) === 0;
|
|
1904
|
+
if (failed && recoveries < OVERFLOW_RECOVERY_LIMIT) {
|
|
1905
|
+
recoveries += 1;
|
|
1906
|
+
if (await this.recoverFromOverflow(compilation, recoveries, sessionKey, signal)) {
|
|
1907
|
+
console.warn(
|
|
1908
|
+
`[Runtime] \u4E0A\u4E0B\u6587\u6EA2\u51FA\uFF0C\u5DF2\u538B\u7F29\u5386\u53F2\uFF08\u6458\u8981\u6216\u622A\u65AD\uFF09\u5E76\u540C\u56DE\u5408\u91CD\u8BD5\uFF08${recoveries}/${OVERFLOW_RECOVERY_LIMIT}\uFF09`
|
|
1909
|
+
);
|
|
1910
|
+
await this.emitTelemetry("onRetry", {
|
|
1911
|
+
traceId: compilation.traceId,
|
|
1912
|
+
provider: this._model.provider,
|
|
1913
|
+
modelId: this._model.id,
|
|
1914
|
+
attempt: recoveries,
|
|
1915
|
+
errorClass: "context-overflow",
|
|
1916
|
+
delayMs: 0,
|
|
1917
|
+
willRetry: true
|
|
1918
|
+
});
|
|
1919
|
+
continue;
|
|
1920
|
+
}
|
|
1921
|
+
if (suppressed) {
|
|
1922
|
+
yield { type: "error", content: final.errorMessage || "\u6A21\u578B\u8C03\u7528\u51FA\u9519" };
|
|
1923
|
+
}
|
|
1924
|
+
return final;
|
|
1925
|
+
}
|
|
1926
|
+
if (failed) return final;
|
|
1927
|
+
await this.recoverFromOverflow(compilation, 1, sessionKey, signal);
|
|
1928
|
+
console.warn("[Runtime] \u68C0\u6D4B\u5230\u9759\u9ED8\u4E0A\u4E0B\u6587\u6EA2\u51FA\uFF08usage \u8D85\u7A97\uFF09\uFF0C\u5DF2\u538B\u7F29\u5386\u53F2\u6D88\u606F");
|
|
1929
|
+
}
|
|
1930
|
+
return final;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* 计算溢出恢复的截断点:按 token 预算从最旧消息开始丢弃。
|
|
1935
|
+
*
|
|
1936
|
+
* 预算随恢复次数指数收紧(contextWindow × ratio × 0.5^recovery),且每次
|
|
1937
|
+
* 至少丢弃可丢弃部分的一半,保证重试规模必然小于上次溢出(token 估算
|
|
1938
|
+
* 偏小时也成立)。最后一条消息(当前请求/最新产出)始终保留。
|
|
1939
|
+
* 返回被压缩段的结束下标(messages[0..split) 为被压缩段);0 = 已到
|
|
1940
|
+
* 最小集,单条请求即超窗,无法恢复。
|
|
1941
|
+
*/
|
|
1942
|
+
computeOverflowSplit(messages, recovery) {
|
|
1943
|
+
const contextWindow = this._model.contextWindow;
|
|
1944
|
+
if (!contextWindow || contextWindow <= 0) return 0;
|
|
1945
|
+
const target = Math.max(
|
|
1946
|
+
Math.floor(contextWindow * this._contextBudgetRatio * 0.5 ** recovery),
|
|
1947
|
+
1
|
|
1948
|
+
);
|
|
1949
|
+
let total = 0;
|
|
1950
|
+
for (const m of messages) total += estimateMessageTokens(m);
|
|
1951
|
+
const droppable = messages.length - 1;
|
|
1952
|
+
if (droppable <= 0) return 0;
|
|
1953
|
+
const mustDrop = Math.max(Math.floor(droppable / 2), 1);
|
|
1954
|
+
let dropUntil = 0;
|
|
1955
|
+
while (dropUntil < droppable && (dropUntil < mustDrop || total > target)) {
|
|
1956
|
+
total -= estimateMessageTokens(messages[dropUntil]);
|
|
1957
|
+
dropUntil += 1;
|
|
1958
|
+
}
|
|
1959
|
+
return dropUntil;
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* 溢出恢复:摘要优先、硬截断兜底(原地修改会话消息)。
|
|
1963
|
+
*
|
|
1964
|
+
* 开启 compaction(默认)时,被压缩段先尝试 LLM 摘要替换(compactionSummary
|
|
1965
|
+
* 消息),摘要失败或序列化超摘要预算(请求本身会超窗)则降级纯丢弃;
|
|
1966
|
+
* 关闭 compaction 时维持旧行为纯截断。截断/摘要后均经 ensureToolPairing
|
|
1967
|
+
* 修复保留段的工具配对。返回是否执行了恢复动作(false = 无可压缩,单条
|
|
1968
|
+
* 请求即超窗)。
|
|
1969
|
+
*/
|
|
1970
|
+
async recoverFromOverflow(compilation, recovery, sessionKey, signal) {
|
|
1971
|
+
const messages = compilation.messages;
|
|
1972
|
+
const split = this.computeOverflowSplit(messages, recovery);
|
|
1973
|
+
if (split <= 0) return false;
|
|
1974
|
+
const useCompaction = !!this._compaction && this._compaction.enabled !== false && this._compaction.onOverflow !== false;
|
|
1975
|
+
if (useCompaction) {
|
|
1976
|
+
await this.compactOrTruncate(
|
|
1977
|
+
messages,
|
|
1978
|
+
split,
|
|
1979
|
+
sessionKey,
|
|
1980
|
+
compilation.traceId,
|
|
1981
|
+
signal,
|
|
1982
|
+
"overflow"
|
|
1983
|
+
);
|
|
1984
|
+
return true;
|
|
1985
|
+
}
|
|
1986
|
+
const kept = ensureToolPairing(messages.slice(split));
|
|
1987
|
+
messages.splice(0, messages.length, ...kept);
|
|
1988
|
+
return true;
|
|
1989
|
+
}
|
|
1990
|
+
/**
|
|
1991
|
+
* 阈值触发内置摘要压缩(runLoop 每轮模型调用前检查,低频):
|
|
1992
|
+
* 估算 token 超过 contextWindow × triggerRatio(默认 contextBudgetRatio)
|
|
1993
|
+
* 时,将历史压缩到 targetRatio(默认 0.5)——最新消息保留目标的一半,
|
|
1994
|
+
* 其余部分摘要替换;摘要失败降级硬截断。
|
|
1995
|
+
*/
|
|
1996
|
+
async maybeCompactByThreshold(compilation, sessionKey, signal) {
|
|
1997
|
+
if (!this._compaction || this._compaction.enabled === false) return;
|
|
1998
|
+
const contextWindow = this._model.contextWindow;
|
|
1999
|
+
if (!contextWindow || contextWindow <= 0) return;
|
|
2000
|
+
const messages = compilation.messages;
|
|
2001
|
+
const triggerTokens = Math.floor(
|
|
2002
|
+
contextWindow * (this._compaction?.triggerRatio ?? this._contextBudgetRatio)
|
|
2003
|
+
);
|
|
2004
|
+
let total = 0;
|
|
2005
|
+
for (const m of messages) total += estimateMessageTokens(m);
|
|
2006
|
+
if (total <= triggerTokens) return;
|
|
2007
|
+
const targetTokens = Math.floor(
|
|
2008
|
+
contextWindow * (this._compaction?.targetRatio ?? 0.5)
|
|
2009
|
+
);
|
|
2010
|
+
const keepTokens = Math.max(Math.floor(targetTokens / 2), 1);
|
|
2011
|
+
let kept = 0;
|
|
2012
|
+
let split = messages.length;
|
|
2013
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2014
|
+
if (kept >= keepTokens) {
|
|
2015
|
+
split = i + 1;
|
|
2016
|
+
break;
|
|
2017
|
+
}
|
|
2018
|
+
kept += estimateMessageTokens(messages[i]);
|
|
2019
|
+
split = i;
|
|
2020
|
+
}
|
|
2021
|
+
if (split <= 0 || split >= messages.length) return;
|
|
2022
|
+
const mode = await this.compactOrTruncate(
|
|
2023
|
+
messages,
|
|
2024
|
+
split,
|
|
2025
|
+
sessionKey,
|
|
2026
|
+
compilation.traceId,
|
|
2027
|
+
signal,
|
|
2028
|
+
"threshold"
|
|
2029
|
+
);
|
|
2030
|
+
console.warn(
|
|
2031
|
+
`[Runtime] \u4E0A\u4E0B\u6587\u8FBE\u9608\u503C\uFF08\u7EA6 ${total} token > ${triggerTokens}\uFF09\uFF0C\u5DF2${mode === "summary" ? "\u6458\u8981\u538B\u7F29" : "\u622A\u65AD"}\u5386\u53F2`
|
|
2032
|
+
);
|
|
2033
|
+
}
|
|
2034
|
+
/**
|
|
2035
|
+
* 手动压缩指定会话(交互命令 /compact 等):跳过阈值判断,
|
|
2036
|
+
* 直接将历史压缩至 targetRatio 保留量(复用 maybeCompactByThreshold
|
|
2037
|
+
* 的保留段计算与 compactOrTruncate 执行路径),压缩后持久化。
|
|
2038
|
+
*/
|
|
2039
|
+
async compact(sessionKey) {
|
|
2040
|
+
if (!this._compaction || this._compaction.enabled === false) return null;
|
|
2041
|
+
const key = sessionKey ?? this._sessionKey;
|
|
2042
|
+
const session = this.getSession(key);
|
|
2043
|
+
const messages = session.messages;
|
|
2044
|
+
if (messages.length === 0) return null;
|
|
2045
|
+
const contextWindow = this._model.contextWindow;
|
|
2046
|
+
if (!contextWindow || contextWindow <= 0) return null;
|
|
2047
|
+
const targetTokens = Math.floor(
|
|
2048
|
+
contextWindow * (this._compaction.targetRatio ?? 0.5)
|
|
2049
|
+
);
|
|
2050
|
+
const keepTokens = Math.max(Math.floor(targetTokens / 2), 1);
|
|
2051
|
+
let kept = 0;
|
|
2052
|
+
let split = messages.length;
|
|
2053
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2054
|
+
if (kept >= keepTokens) {
|
|
2055
|
+
split = i + 1;
|
|
2056
|
+
break;
|
|
2057
|
+
}
|
|
2058
|
+
kept += estimateMessageTokens(messages[i]);
|
|
2059
|
+
split = i;
|
|
2060
|
+
}
|
|
2061
|
+
if (split <= 0 || split >= messages.length) return null;
|
|
2062
|
+
const mode = await this.compactOrTruncate(
|
|
2063
|
+
messages,
|
|
2064
|
+
split,
|
|
2065
|
+
key,
|
|
2066
|
+
`manual-${Date.now().toString(36)}`,
|
|
2067
|
+
new AbortController().signal,
|
|
2068
|
+
"threshold"
|
|
2069
|
+
);
|
|
2070
|
+
try {
|
|
2071
|
+
await this.persistSession(key, session);
|
|
2072
|
+
} catch (err) {
|
|
2073
|
+
console.warn("[Runtime] \u624B\u52A8\u538B\u7F29\u540E\u6301\u4E45\u5316\u5931\u8D25:", err?.message);
|
|
2074
|
+
}
|
|
2075
|
+
return mode;
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* 执行压缩:messages[0..split) 为被压缩段,原地替换。
|
|
2079
|
+
*
|
|
2080
|
+
* 摘要成功 → 被压缩段替换为单条 compactionSummary 消息(资源层 pinned);
|
|
2081
|
+
* 序列化超摘要预算或摘要调用失败 → 降级纯丢弃(旧行为)。两种路径均对
|
|
2082
|
+
* 保留段执行 ensureToolPairing(被压缩段边界可能截断工具配对)。上报
|
|
2083
|
+
* onCompaction 遥测。
|
|
2084
|
+
*/
|
|
2085
|
+
async compactOrTruncate(messages, split, sessionKey, traceId, signal, trigger) {
|
|
2086
|
+
const compacted = messages.slice(0, split);
|
|
2087
|
+
const tokensBefore = messages.reduce((sum, m) => sum + estimateMessageTokens(m), 0);
|
|
2088
|
+
let mode = "truncate";
|
|
2089
|
+
let summaryText = "";
|
|
2090
|
+
const summaryEnabled = !!this._compaction && this._compaction.enabled !== false;
|
|
2091
|
+
if (summaryEnabled && compacted.length > 0) {
|
|
2092
|
+
const summaryBudget = Math.floor(
|
|
2093
|
+
this._model.contextWindow * COMPACTION_SUMMARY_BUDGET_RATIO
|
|
2094
|
+
);
|
|
2095
|
+
const inputText = compacted.map((m) => messageToSummaryLine(m)).filter((line) => line.length > 0).join("\n");
|
|
2096
|
+
if (inputText && estimateTextTokens(inputText) <= summaryBudget) {
|
|
2097
|
+
const summary = await this.summarizeMessages(
|
|
2098
|
+
inputText,
|
|
2099
|
+
sessionKey,
|
|
2100
|
+
traceId,
|
|
2101
|
+
signal
|
|
2102
|
+
);
|
|
2103
|
+
if (summary && summary.trim()) {
|
|
2104
|
+
summaryText = summary.trim();
|
|
2105
|
+
mode = "summary";
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
let tokensAfter;
|
|
2110
|
+
if (mode === "summary") {
|
|
2111
|
+
const summaryMsg = this.createCompactionSummaryMessage(summaryText);
|
|
2112
|
+
const kept = ensureToolPairing(messages.slice(split));
|
|
2113
|
+
messages.splice(0, messages.length, summaryMsg, ...kept);
|
|
2114
|
+
tokensAfter = estimateMessageTokens(summaryMsg) + kept.reduce((sum, m) => sum + estimateMessageTokens(m), 0);
|
|
2115
|
+
} else {
|
|
2116
|
+
const kept = ensureToolPairing(messages.slice(split));
|
|
2117
|
+
messages.splice(0, messages.length, ...kept);
|
|
2118
|
+
tokensAfter = kept.reduce((sum, m) => sum + estimateMessageTokens(m), 0);
|
|
2119
|
+
}
|
|
2120
|
+
await this.emitTelemetry("onCompaction", {
|
|
2121
|
+
traceId,
|
|
2122
|
+
sessionKey,
|
|
2123
|
+
mode,
|
|
2124
|
+
trigger,
|
|
2125
|
+
tokensBefore,
|
|
2126
|
+
tokensAfter,
|
|
2127
|
+
droppedMessages: split,
|
|
2128
|
+
summary: mode === "summary" ? summaryText : void 0
|
|
2129
|
+
});
|
|
2130
|
+
return mode;
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* 调用模型生成摘要文本。失败(模型错误/中止/空产出)返回 null,
|
|
2134
|
+
* 由调用方降级硬截断——摘要失败不影响主流程。
|
|
2135
|
+
*/
|
|
2136
|
+
async summarizeMessages(inputText, sessionKey, traceId, signal) {
|
|
2137
|
+
const prompt = this._compaction?.prompt ?? DEFAULT_COMPACTION_PROMPT;
|
|
2138
|
+
const context = {
|
|
2139
|
+
systemPrompt: prompt,
|
|
2140
|
+
messages: [{ role: "user", content: inputText, timestamp: Date.now() }]
|
|
2141
|
+
};
|
|
2142
|
+
const startedAt = Date.now();
|
|
2143
|
+
let text = "";
|
|
2144
|
+
let usage;
|
|
2145
|
+
let failed = false;
|
|
2146
|
+
try {
|
|
2147
|
+
for await (const event of this._streamFn(this._model, context, { signal })) {
|
|
2148
|
+
if (event.type === "text_delta") {
|
|
2149
|
+
text += event.delta;
|
|
2150
|
+
} else if (event.type === "error") {
|
|
2151
|
+
failed = true;
|
|
2152
|
+
usage = event.message.usage;
|
|
2153
|
+
break;
|
|
2154
|
+
} else if (event.type === "done") {
|
|
2155
|
+
usage = event.message.usage;
|
|
2156
|
+
if (event.message.stopReason === "error") failed = true;
|
|
2157
|
+
if (!text) text = extractText(event.message.content);
|
|
2158
|
+
break;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
} catch {
|
|
2162
|
+
return null;
|
|
2163
|
+
}
|
|
2164
|
+
if (failed || !text.trim()) return null;
|
|
2165
|
+
await this.emitTelemetry("onModelCall", {
|
|
2166
|
+
traceId,
|
|
2167
|
+
spanId: newSpanId(),
|
|
2168
|
+
sessionKey,
|
|
2169
|
+
modelId: this._model.id,
|
|
2170
|
+
attempts: 1,
|
|
2171
|
+
inputTokens: usage?.input ?? estimateTextTokens(inputText),
|
|
2172
|
+
outputTokens: usage?.output ?? estimateTextTokens(text),
|
|
2173
|
+
durationMs: Date.now() - startedAt,
|
|
2174
|
+
stream: false
|
|
2175
|
+
});
|
|
2176
|
+
return text;
|
|
2177
|
+
}
|
|
2178
|
+
/** 构造 compactionSummary 消息(内部扩展 role,发出前经 buildContext 转 user) */
|
|
2179
|
+
createCompactionSummaryMessage(text) {
|
|
2180
|
+
return {
|
|
2181
|
+
role: "compactionSummary",
|
|
2182
|
+
content: text,
|
|
2183
|
+
timestamp: Date.now()
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
/**
|
|
2187
|
+
* 统一模型调用埋点生成器:run()(streamModel)与 stream()(runLoopStream)两路径共用。
|
|
2188
|
+
* 职责:模型调用计时、spanId、attempts 累计、onRetry 转发、onModelCall 上报
|
|
2189
|
+
* (含 tokens / cost / errorClass / ttft)。
|
|
2190
|
+
*/
|
|
2191
|
+
async *streamModelEvents(compilation, signal, stream) {
|
|
2192
|
+
const sessionKey = this.resolveSessionKey(compilation.request);
|
|
2193
|
+
const modelStartedAt = Date.now();
|
|
2194
|
+
const spanId = newSpanId();
|
|
2195
|
+
let attempts = 1;
|
|
2196
|
+
let ttftAt;
|
|
2197
|
+
let lastAssistant;
|
|
2198
|
+
const options = { signal };
|
|
2199
|
+
if (this._thinkingLevel !== "off" && this._model.reasoning) {
|
|
2200
|
+
options.reasoning = this._thinkingLevel;
|
|
2201
|
+
}
|
|
2202
|
+
options.onRetryAttempt = (info) => {
|
|
2203
|
+
attempts += 1;
|
|
2204
|
+
void this.emitTelemetry("onRetry", {
|
|
2205
|
+
traceId: compilation.traceId,
|
|
2206
|
+
spanId,
|
|
2207
|
+
// P2:重试明细关联到本模型调用 span
|
|
2208
|
+
provider: this._model.provider,
|
|
2209
|
+
modelId: this._model.id,
|
|
2210
|
+
attempt: info.attempt,
|
|
2211
|
+
errorClass: classifyError(info.error),
|
|
2212
|
+
status: statusOfRetryError(info.error),
|
|
2213
|
+
delayMs: info.delayMs,
|
|
2214
|
+
willRetry: true
|
|
2215
|
+
});
|
|
2216
|
+
};
|
|
2217
|
+
try {
|
|
2218
|
+
for await (const event of this._streamFn(this._model, this.buildContext(compilation.messages), options)) {
|
|
2219
|
+
if (stream && event.type === "text_delta" && ttftAt === void 0) {
|
|
2220
|
+
ttftAt = Date.now();
|
|
2221
|
+
}
|
|
2222
|
+
if (event.type === "done" || event.type === "error") {
|
|
2223
|
+
lastAssistant = event.message;
|
|
2224
|
+
}
|
|
2225
|
+
yield event;
|
|
2226
|
+
}
|
|
2227
|
+
} finally {
|
|
2228
|
+
const assistant = lastAssistant;
|
|
2229
|
+
const errorClass = assistant?.errorMessage ? errorClassFromMessage(assistant.errorMessage) ?? "unknown" : void 0;
|
|
2230
|
+
await this.emitTelemetry("onModelCall", {
|
|
2231
|
+
traceId: compilation.traceId,
|
|
2232
|
+
spanId,
|
|
2233
|
+
sessionKey,
|
|
2234
|
+
modelId: this._model.id,
|
|
2235
|
+
attempts,
|
|
2236
|
+
inputTokens: assistant?.usage?.input ?? 0,
|
|
2237
|
+
outputTokens: assistant?.usage?.output ?? 0,
|
|
2238
|
+
cacheRead: assistant?.usage?.cacheRead,
|
|
2239
|
+
cacheWrite: assistant?.usage?.cacheWrite,
|
|
2240
|
+
durationMs: Date.now() - modelStartedAt,
|
|
2241
|
+
stream,
|
|
2242
|
+
errorClass
|
|
2243
|
+
});
|
|
2244
|
+
if (stream && ttftAt !== void 0 && compilation.ttftMs === void 0) {
|
|
2245
|
+
compilation.ttftMs = ttftAt - modelStartedAt;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
/**
|
|
2250
|
+
* 触发遥测回调。全可选、失败不阻断主流程。
|
|
2251
|
+
*/
|
|
2252
|
+
async emitTelemetry(event, info) {
|
|
2253
|
+
const fn = this._telemetry?.[event];
|
|
2254
|
+
if (!fn) return;
|
|
2255
|
+
try {
|
|
2256
|
+
await Promise.resolve(fn(info));
|
|
2257
|
+
} catch (err) {
|
|
2258
|
+
console.warn(`[aipack] telemetry "${String(event)}" \u4E0A\u62A5\u5931\u8D25:`, err);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
/** traceId 生成:优先用注入的生成器(测试可确定性) */
|
|
2262
|
+
newTraceId() {
|
|
2263
|
+
return this._traceIdGenerator ? this._traceIdGenerator() : newTraceId();
|
|
2264
|
+
}
|
|
2265
|
+
/** 组装并上报 run 级完成事件(_run/_stream 内部统一调用) */
|
|
2266
|
+
async emitRunEnd(request, sessionKey, compilation, result, queuedMs, activeStartedAt) {
|
|
2267
|
+
const activeMs = Date.now() - activeStartedAt;
|
|
2268
|
+
await this.emitTelemetry("onRunEnd", {
|
|
2269
|
+
traceId: compilation.traceId,
|
|
2270
|
+
sessionKey,
|
|
2271
|
+
// 请求未显式指定 model 时补实际模型(模型排行按 run 级 requests 统计,缺省会落入 'unknown')
|
|
2272
|
+
request: request.model ? request : { ...request, model: this._model.id },
|
|
2273
|
+
durationMs: activeMs + queuedMs,
|
|
2274
|
+
activeMs,
|
|
2275
|
+
queuedMs,
|
|
2276
|
+
turnCount: compilation.turnCount ?? 0,
|
|
2277
|
+
result,
|
|
2278
|
+
success: result.success,
|
|
2279
|
+
errorClass: this.runErrorClass(compilation),
|
|
2280
|
+
tokens: {
|
|
2281
|
+
input: result.usage.input ?? 0,
|
|
2282
|
+
output: result.usage.output ?? 0,
|
|
2283
|
+
cacheRead: result.usage.cacheRead,
|
|
2284
|
+
cacheWrite: result.usage.cacheWrite
|
|
2285
|
+
},
|
|
2286
|
+
ttftMs: compilation.ttftMs
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
/** run 级错误分类:terminate → 'terminated';否则只看最后一条 assistant 消息(与 buildResult 的 result.success 同口径) */
|
|
2290
|
+
runErrorClass(compilation) {
|
|
2291
|
+
if (compilation.terminateReason) return "terminated";
|
|
2292
|
+
const messages = compilation.messages;
|
|
2293
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2294
|
+
const m = messages[i];
|
|
2295
|
+
if (m.role === "assistant") {
|
|
2296
|
+
if (!m.errorMessage) return void 0;
|
|
2297
|
+
const cls = errorClassFromMessage(m.errorMessage);
|
|
2298
|
+
return cls ?? "unknown";
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
return void 0;
|
|
2302
|
+
}
|
|
2303
|
+
buildResult(compilation) {
|
|
2304
|
+
const messages = compilation.messages;
|
|
2305
|
+
let content = "";
|
|
2306
|
+
let stopReason = "completed";
|
|
2307
|
+
let error;
|
|
2308
|
+
const toolsUsed = [];
|
|
2309
|
+
const usage = {};
|
|
2310
|
+
for (const msg of messages) {
|
|
2311
|
+
if (msg.role === "assistant") {
|
|
2312
|
+
const assistant = msg;
|
|
2313
|
+
content = extractText(assistant.content);
|
|
2314
|
+
stopReason = assistant.stopReason ?? "completed";
|
|
2315
|
+
error = assistant.errorMessage;
|
|
2316
|
+
if (assistant.usage) {
|
|
2317
|
+
usage.input = (usage.input ?? 0) + assistant.usage.input;
|
|
2318
|
+
usage.output = (usage.output ?? 0) + assistant.usage.output;
|
|
2319
|
+
usage.total = (usage.total ?? 0) + assistant.usage.total;
|
|
2320
|
+
if (assistant.usage.cacheRead) usage.cacheRead = (usage.cacheRead ?? 0) + assistant.usage.cacheRead;
|
|
2321
|
+
if (assistant.usage.cacheWrite) usage.cacheWrite = (usage.cacheWrite ?? 0) + assistant.usage.cacheWrite;
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
if (msg.role === "toolResult") {
|
|
2325
|
+
const toolMsg = msg;
|
|
2326
|
+
if (!toolsUsed.includes(toolMsg.toolName)) {
|
|
2327
|
+
toolsUsed.push(toolMsg.toolName);
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
const resources = messagesToResources(messages);
|
|
2332
|
+
const builder = new ResultBuilder().content(content).toolsUsed(toolsUsed).usage(usage).stopReason(stopReason).error(error).resources(resources).metadata("traceId", compilation.traceId);
|
|
2333
|
+
if (compilation.terminateReason) {
|
|
2334
|
+
builder.stopReason("terminated").metadata("terminateReason", compilation.terminateReason);
|
|
2335
|
+
}
|
|
2336
|
+
if (compilation.maxTurnsExhausted) {
|
|
2337
|
+
builder.stopReason("max_turns").metadata("maxTurns", true);
|
|
2338
|
+
}
|
|
2339
|
+
return builder.build();
|
|
2340
|
+
}
|
|
2341
|
+
streamEventToChunk(event) {
|
|
2342
|
+
switch (event.type) {
|
|
2343
|
+
case "text_delta":
|
|
2344
|
+
return { type: "text", content: event.delta };
|
|
2345
|
+
case "thinking_delta":
|
|
2346
|
+
return { type: "thinking", content: event.delta };
|
|
2347
|
+
case "error":
|
|
2348
|
+
return {
|
|
2349
|
+
type: "error",
|
|
2350
|
+
content: event.message.errorMessage || "\u6A21\u578B\u8C03\u7528\u51FA\u9519"
|
|
2351
|
+
};
|
|
2352
|
+
default:
|
|
2353
|
+
return null;
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
// ─── 便捷方法 ───────────────────────────────────────────────────
|
|
2357
|
+
/** 终止指定会话的运行(默认会话;会话不存在为 no-op) */
|
|
2358
|
+
abort(sessionKey) {
|
|
2359
|
+
this._sessions.get(sessionKey ?? this._sessionKey)?.abortController?.abort();
|
|
2360
|
+
}
|
|
2361
|
+
/** 检查指定会话是否正在运行(默认会话;会话不存在返回 false) */
|
|
2362
|
+
isBusy(sessionKey) {
|
|
2363
|
+
return this._sessions.get(sessionKey ?? this._sessionKey)?.isStreaming ?? false;
|
|
2364
|
+
}
|
|
2365
|
+
/** 等待指定会话空闲(默认会话;基于 promise,无轮询) */
|
|
2366
|
+
async waitForIdle(sessionKey, timeoutMs) {
|
|
2367
|
+
const session = this._sessions.get(sessionKey ?? this._sessionKey);
|
|
2368
|
+
if (!session || !session.isStreaming) return;
|
|
2369
|
+
if (timeoutMs === void 0) {
|
|
2370
|
+
await new Promise((resolve) => {
|
|
2371
|
+
session.idleResolvers.push(resolve);
|
|
2372
|
+
});
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
await new Promise((resolve, reject) => {
|
|
2376
|
+
const resolver = () => {
|
|
2377
|
+
clearTimeout(timer);
|
|
2378
|
+
resolve();
|
|
2379
|
+
};
|
|
2380
|
+
const timer = setTimeout(() => {
|
|
2381
|
+
const i = session.idleResolvers.indexOf(resolver);
|
|
2382
|
+
if (i >= 0) session.idleResolvers.splice(i, 1);
|
|
2383
|
+
reject(new Error(
|
|
2384
|
+
`[Runtime] waitForIdle \u8D85\u65F6\uFF08${timeoutMs}ms\uFF09: ${sessionKey ?? "default"}`
|
|
2385
|
+
));
|
|
2386
|
+
}, timeoutMs);
|
|
2387
|
+
session.idleResolvers.push(resolver);
|
|
2388
|
+
});
|
|
2389
|
+
}
|
|
2390
|
+
/** 清除指定会话消息(仅内存;下次 run 会从存储恢复) */
|
|
2391
|
+
clearSession(sessionKey) {
|
|
2392
|
+
const session = this._sessions.get(sessionKey ?? this._sessionKey);
|
|
2393
|
+
if (!session) return;
|
|
2394
|
+
session.messages = [];
|
|
2395
|
+
session.hydrated = false;
|
|
2396
|
+
}
|
|
2397
|
+
/** 删除指定会话(内存 + 存储),返回是否删除成功 */
|
|
2398
|
+
async deleteSession(sessionKey) {
|
|
2399
|
+
const key = sessionKey ?? this._sessionKey;
|
|
2400
|
+
const session = this._sessions.get(key);
|
|
2401
|
+
if (session) {
|
|
2402
|
+
await session.queue;
|
|
2403
|
+
this._sessions.delete(key);
|
|
2404
|
+
}
|
|
2405
|
+
if (!this._sessionStorage) return true;
|
|
2406
|
+
const storage = this._sessionStorage;
|
|
2407
|
+
if (!storage.withLock) return storage.delete(key);
|
|
2408
|
+
return storage.withLock(key, () => storage.delete(key));
|
|
2409
|
+
}
|
|
2410
|
+
};
|
|
2411
|
+
function createRuntime(options) {
|
|
2412
|
+
return AgentRuntime.create(options);
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
// core/executor.ts
|
|
2416
|
+
var runtimeCache = /* @__PURE__ */ new WeakMap();
|
|
2417
|
+
function ensureRuntime(node) {
|
|
2418
|
+
let rt = runtimeCache.get(node);
|
|
2419
|
+
if (rt) return rt;
|
|
2420
|
+
if (typeof node.runtime.run === "function") {
|
|
2421
|
+
rt = node.runtime;
|
|
2422
|
+
} else {
|
|
2423
|
+
rt = createRuntime(node.runtime);
|
|
2424
|
+
}
|
|
2425
|
+
runtimeCache.set(node, rt);
|
|
2426
|
+
return rt;
|
|
2427
|
+
}
|
|
2428
|
+
async function executeNode(node, input, ctx) {
|
|
2429
|
+
const runtime = ensureRuntime(node);
|
|
2430
|
+
let req;
|
|
2431
|
+
if (typeof input === "string") {
|
|
2432
|
+
req = createRequest(input, { sessionKey: `multi-agent:${node.id}` });
|
|
2433
|
+
} else {
|
|
2434
|
+
req = { ...input, sessionKey: input.sessionKey ?? `multi-agent:${node.id}` };
|
|
2435
|
+
}
|
|
2436
|
+
const result = await runtime.run(req);
|
|
2437
|
+
if (node.outputMapping) {
|
|
2438
|
+
node.outputMapping(result, ctx);
|
|
2439
|
+
}
|
|
2440
|
+
return result;
|
|
2441
|
+
}
|
|
2442
|
+
function resolveInput(edge, prevResult, ctx, node, originalInput) {
|
|
2443
|
+
if (edge?.transform) {
|
|
2444
|
+
return edge.transform(prevResult, ctx);
|
|
2445
|
+
}
|
|
2446
|
+
if (node.inputMapping) {
|
|
2447
|
+
return node.inputMapping(ctx);
|
|
2448
|
+
}
|
|
2449
|
+
return prevResult.content;
|
|
2450
|
+
}
|
|
2451
|
+
function findNextEdges(fromId, edges, result, ctx) {
|
|
2452
|
+
const outEdges = edges.filter((e) => e.from === fromId);
|
|
2453
|
+
const matched = [];
|
|
2454
|
+
for (const edge of outEdges) {
|
|
2455
|
+
if (!edge.condition || edge.condition(result, ctx)) {
|
|
2456
|
+
matched.push(edge);
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
return matched;
|
|
2460
|
+
}
|
|
2461
|
+
var GraphExecutor = class {
|
|
2462
|
+
nodes = /* @__PURE__ */ new Map();
|
|
2463
|
+
edges = [];
|
|
2464
|
+
entryId;
|
|
2465
|
+
finishCondition;
|
|
2466
|
+
abortController = new AbortController();
|
|
2467
|
+
state = {
|
|
2468
|
+
nodeStates: /* @__PURE__ */ new Map(),
|
|
2469
|
+
nodeResults: /* @__PURE__ */ new Map(),
|
|
2470
|
+
stepsCompleted: 0,
|
|
2471
|
+
finished: false
|
|
2472
|
+
};
|
|
2473
|
+
addNode(node) {
|
|
2474
|
+
this.nodes.set(node.id, node);
|
|
2475
|
+
this.state.nodeStates.set(node.id, "pending");
|
|
2476
|
+
return this;
|
|
2477
|
+
}
|
|
2478
|
+
addEdge(edge) {
|
|
2479
|
+
this.edges.push(edge);
|
|
2480
|
+
return this;
|
|
2481
|
+
}
|
|
2482
|
+
setEntry(agentId) {
|
|
2483
|
+
this.entryId = agentId;
|
|
2484
|
+
return this;
|
|
2485
|
+
}
|
|
2486
|
+
setFinish(condition) {
|
|
2487
|
+
this.finishCondition = condition;
|
|
2488
|
+
return this;
|
|
2489
|
+
}
|
|
2490
|
+
getState() {
|
|
2491
|
+
return { ...this.state };
|
|
2492
|
+
}
|
|
2493
|
+
abort() {
|
|
2494
|
+
this.abortController.abort();
|
|
2495
|
+
}
|
|
2496
|
+
/** 同步执行图 */
|
|
2497
|
+
async run(input) {
|
|
2498
|
+
if (!this.entryId) {
|
|
2499
|
+
throw new Error("AgentGraph: \u5165\u53E3\u8282\u70B9\u672A\u8BBE\u7F6E\uFF0C\u8BF7\u8C03\u7528 setEntry()");
|
|
2500
|
+
}
|
|
2501
|
+
if (!this.nodes.has(this.entryId)) {
|
|
2502
|
+
throw new Error(`AgentGraph: \u5165\u53E3\u8282\u70B9 "${this.entryId}" \u4E0D\u5B58\u5728`);
|
|
2503
|
+
}
|
|
2504
|
+
const ctx = createSharedContext({
|
|
2505
|
+
meta: { traceId: `ma-${Date.now()}`, startTime: Date.now() }
|
|
2506
|
+
});
|
|
2507
|
+
const events = [];
|
|
2508
|
+
try {
|
|
2509
|
+
const result = await this.executeGraph(input, ctx, (event) => events.push(event));
|
|
2510
|
+
return result;
|
|
2511
|
+
} catch (err) {
|
|
2512
|
+
return {
|
|
2513
|
+
content: "",
|
|
2514
|
+
lastAgentId: this.state.currentAgentId ?? "",
|
|
2515
|
+
agentResults: this.state.nodeResults,
|
|
2516
|
+
totalUsage: {},
|
|
2517
|
+
stepsCompleted: this.state.stepsCompleted,
|
|
2518
|
+
stopReason: "error",
|
|
2519
|
+
context: ctx,
|
|
2520
|
+
success: false,
|
|
2521
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
/** 流式执行图 */
|
|
2526
|
+
async *stream(input) {
|
|
2527
|
+
if (!this.entryId) {
|
|
2528
|
+
throw new Error("AgentGraph: \u5165\u53E3\u8282\u70B9\u672A\u8BBE\u7F6E\uFF0C\u8BF7\u8C03\u7528 setEntry()");
|
|
2529
|
+
}
|
|
2530
|
+
if (!this.nodes.has(this.entryId)) {
|
|
2531
|
+
throw new Error(`AgentGraph: \u5165\u53E3\u8282\u70B9 "${this.entryId}" \u4E0D\u5B58\u5728`);
|
|
2532
|
+
}
|
|
2533
|
+
const ctx = createSharedContext({
|
|
2534
|
+
meta: { traceId: `ma-${Date.now()}`, startTime: Date.now() }
|
|
2535
|
+
});
|
|
2536
|
+
const eventQueue = [];
|
|
2537
|
+
let resolveEvent = null;
|
|
2538
|
+
let done = false;
|
|
2539
|
+
const emit = (event) => {
|
|
2540
|
+
eventQueue.push(event);
|
|
2541
|
+
resolveEvent?.();
|
|
2542
|
+
};
|
|
2543
|
+
const graphPromise = this.executeGraph(input, ctx, emit).then(
|
|
2544
|
+
(result) => {
|
|
2545
|
+
emit({ type: "graph_done", result });
|
|
2546
|
+
},
|
|
2547
|
+
(err) => {
|
|
2548
|
+
emit({ type: "graph_error", error: err instanceof Error ? err.message : String(err) });
|
|
2549
|
+
}
|
|
2550
|
+
).finally(() => {
|
|
2551
|
+
done = true;
|
|
2552
|
+
resolveEvent?.();
|
|
2553
|
+
});
|
|
2554
|
+
while (!done || eventQueue.length > 0) {
|
|
2555
|
+
if (eventQueue.length > 0) {
|
|
2556
|
+
yield eventQueue.shift();
|
|
2557
|
+
} else {
|
|
2558
|
+
await new Promise((resolve) => {
|
|
2559
|
+
resolveEvent = resolve;
|
|
2560
|
+
});
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
await graphPromise;
|
|
2564
|
+
}
|
|
2565
|
+
/** 核心执行逻辑 */
|
|
2566
|
+
async executeGraph(input, ctx, emit) {
|
|
2567
|
+
const entryNode = this.nodes.get(this.entryId);
|
|
2568
|
+
let currentId = this.entryId;
|
|
2569
|
+
let currentInput = input;
|
|
2570
|
+
let lastResult;
|
|
2571
|
+
let lastAgentId = currentId;
|
|
2572
|
+
let stepsCompleted = 0;
|
|
2573
|
+
const totalUsage = {};
|
|
2574
|
+
this.state.currentAgentId = currentId;
|
|
2575
|
+
this.state.nodeStates.set(currentId, "running");
|
|
2576
|
+
emit({ type: "agent_start", agentId: currentId, agentName: entryNode.name });
|
|
2577
|
+
try {
|
|
2578
|
+
lastResult = await executeNode(entryNode, currentInput, ctx);
|
|
2579
|
+
this.state.nodeStates.set(currentId, "completed");
|
|
2580
|
+
this.state.nodeResults.set(currentId, lastResult);
|
|
2581
|
+
this.mergeUsage(totalUsage, lastResult.usage);
|
|
2582
|
+
stepsCompleted++;
|
|
2583
|
+
this.state.stepsCompleted = stepsCompleted;
|
|
2584
|
+
emit({ type: "agent_result", agentId: currentId, agentName: entryNode.name, result: lastResult });
|
|
2585
|
+
} catch (err) {
|
|
2586
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2587
|
+
this.state.nodeStates.set(currentId, "failed");
|
|
2588
|
+
emit({ type: "agent_error", agentId: currentId, agentName: entryNode.name, error: errorMsg });
|
|
2589
|
+
throw err;
|
|
2590
|
+
}
|
|
2591
|
+
lastAgentId = currentId;
|
|
2592
|
+
if (this.finishCondition?.(ctx)) {
|
|
2593
|
+
return this.buildResult(lastResult, lastAgentId, ctx, totalUsage, stepsCompleted, "finish_condition");
|
|
2594
|
+
}
|
|
2595
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2596
|
+
const visitCount = /* @__PURE__ */ new Map();
|
|
2597
|
+
const MAX_VISITS_PER_NODE = 10;
|
|
2598
|
+
while (lastResult) {
|
|
2599
|
+
const nextEdges = findNextEdges(currentId, this.edges, lastResult, ctx);
|
|
2600
|
+
if (nextEdges.length === 0) {
|
|
2601
|
+
break;
|
|
2602
|
+
}
|
|
2603
|
+
const edge = nextEdges[0];
|
|
2604
|
+
const nextNode = this.nodes.get(edge.to);
|
|
2605
|
+
if (!nextNode) {
|
|
2606
|
+
throw new Error(`AgentGraph: \u76EE\u6807\u8282\u70B9 "${edge.to}" \u4E0D\u5B58\u5728`);
|
|
2607
|
+
}
|
|
2608
|
+
const count = (visitCount.get(edge.to) ?? 0) + 1;
|
|
2609
|
+
if (count > MAX_VISITS_PER_NODE) {
|
|
2610
|
+
break;
|
|
2611
|
+
}
|
|
2612
|
+
visitCount.set(edge.to, count);
|
|
2613
|
+
currentInput = resolveInput(edge, lastResult, ctx, nextNode, input);
|
|
2614
|
+
currentId = edge.to;
|
|
2615
|
+
this.state.currentAgentId = currentId;
|
|
2616
|
+
this.state.nodeStates.set(currentId, "running");
|
|
2617
|
+
emit({ type: "edge_traversed", from: edge.from, to: edge.to });
|
|
2618
|
+
emit({ type: "agent_start", agentId: currentId, agentName: nextNode.name });
|
|
2619
|
+
try {
|
|
2620
|
+
lastResult = await executeNode(nextNode, currentInput, ctx);
|
|
2621
|
+
this.state.nodeStates.set(currentId, "completed");
|
|
2622
|
+
this.state.nodeResults.set(currentId, lastResult);
|
|
2623
|
+
this.mergeUsage(totalUsage, lastResult.usage);
|
|
2624
|
+
stepsCompleted++;
|
|
2625
|
+
this.state.stepsCompleted = stepsCompleted;
|
|
2626
|
+
emit({ type: "agent_result", agentId: currentId, agentName: nextNode.name, result: lastResult });
|
|
2627
|
+
} catch (err) {
|
|
2628
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2629
|
+
this.state.nodeStates.set(currentId, "failed");
|
|
2630
|
+
emit({ type: "agent_error", agentId: currentId, agentName: nextNode.name, error: errorMsg });
|
|
2631
|
+
throw err;
|
|
2632
|
+
}
|
|
2633
|
+
lastAgentId = currentId;
|
|
2634
|
+
if (this.finishCondition?.(ctx)) {
|
|
2635
|
+
return this.buildResult(lastResult, lastAgentId, ctx, totalUsage, stepsCompleted, "finish_condition");
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
return this.buildResult(lastResult, lastAgentId, ctx, totalUsage, stepsCompleted, "completed");
|
|
2639
|
+
}
|
|
2640
|
+
buildResult(lastResult, lastAgentId, ctx, totalUsage, stepsCompleted, stopReason) {
|
|
2641
|
+
this.state.finished = true;
|
|
2642
|
+
return {
|
|
2643
|
+
content: lastResult.content,
|
|
2644
|
+
lastAgentId,
|
|
2645
|
+
agentResults: new Map(this.state.nodeResults),
|
|
2646
|
+
totalUsage,
|
|
2647
|
+
stepsCompleted,
|
|
2648
|
+
stopReason,
|
|
2649
|
+
context: ctx,
|
|
2650
|
+
success: lastResult.success,
|
|
2651
|
+
error: lastResult.error
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
mergeUsage(total, usage) {
|
|
2655
|
+
for (const [key, value] of Object.entries(usage)) {
|
|
2656
|
+
total[key] = (total[key] ?? 0) + value;
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
};
|
|
2660
|
+
|
|
2661
|
+
// core/graph.ts
|
|
2662
|
+
var AgentGraphImpl = class {
|
|
2663
|
+
executor = new GraphExecutor();
|
|
2664
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
2665
|
+
addNode(node) {
|
|
2666
|
+
this.executor.addNode(node);
|
|
2667
|
+
return this;
|
|
2668
|
+
}
|
|
2669
|
+
addEdge(edge) {
|
|
2670
|
+
this.executor.addEdge(edge);
|
|
2671
|
+
return this;
|
|
2672
|
+
}
|
|
2673
|
+
setEntry(agentId) {
|
|
2674
|
+
this.executor.setEntry(agentId);
|
|
2675
|
+
return this;
|
|
2676
|
+
}
|
|
2677
|
+
setFinish(condition) {
|
|
2678
|
+
this.executor.setFinish(condition);
|
|
2679
|
+
return this;
|
|
2680
|
+
}
|
|
2681
|
+
async run(input) {
|
|
2682
|
+
return this.executor.run(input);
|
|
2683
|
+
}
|
|
2684
|
+
async *stream(input) {
|
|
2685
|
+
yield* this.executor.stream(input);
|
|
2686
|
+
}
|
|
2687
|
+
getState() {
|
|
2688
|
+
return this.executor.getState();
|
|
2689
|
+
}
|
|
2690
|
+
abort() {
|
|
2691
|
+
this.executor.abort();
|
|
2692
|
+
}
|
|
2693
|
+
on(event, listener) {
|
|
2694
|
+
let set = this.eventListeners.get(event);
|
|
2695
|
+
if (!set) {
|
|
2696
|
+
set = /* @__PURE__ */ new Set();
|
|
2697
|
+
this.eventListeners.set(event, set);
|
|
2698
|
+
}
|
|
2699
|
+
set.add(listener);
|
|
2700
|
+
return this;
|
|
2701
|
+
}
|
|
2702
|
+
};
|
|
2703
|
+
function createAgentGraph() {
|
|
2704
|
+
return new AgentGraphImpl();
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
// patterns/pipeline.ts
|
|
2708
|
+
function createPipeline(agents, opts) {
|
|
2709
|
+
if (agents.length === 0) {
|
|
2710
|
+
throw new Error("Pipeline: \u81F3\u5C11\u9700\u8981\u4E00\u4E2A Agent \u8282\u70B9");
|
|
2711
|
+
}
|
|
2712
|
+
const graph = createAgentGraph();
|
|
2713
|
+
for (const agent of agents) {
|
|
2714
|
+
graph.addNode(agent);
|
|
2715
|
+
}
|
|
2716
|
+
graph.setEntry(agents[0].id);
|
|
2717
|
+
for (let i = 0; i < agents.length - 1; i++) {
|
|
2718
|
+
const from = agents[i];
|
|
2719
|
+
const to = agents[i + 1];
|
|
2720
|
+
graph.addEdge({
|
|
2721
|
+
from: from.id,
|
|
2722
|
+
to: to.id,
|
|
2723
|
+
// 边上的转换:将前一个Agent的输出传递给下一个Agent
|
|
2724
|
+
transform: opts?.outputTransform ? (result, ctx) => opts.outputTransform(result, ctx) : void 0
|
|
2725
|
+
});
|
|
2726
|
+
}
|
|
2727
|
+
return graph;
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
// patterns/router.ts
|
|
2731
|
+
function createRouter(router, targets, opts) {
|
|
2732
|
+
if (targets.length === 0) {
|
|
2733
|
+
throw new Error("Router: \u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u76EE\u6807 Agent");
|
|
2734
|
+
}
|
|
2735
|
+
const graph = createAgentGraph();
|
|
2736
|
+
graph.addNode(router);
|
|
2737
|
+
for (const target of targets) {
|
|
2738
|
+
graph.addNode(target);
|
|
2739
|
+
}
|
|
2740
|
+
graph.setEntry(router.id);
|
|
2741
|
+
const targetIds = new Set(targets.map((t) => t.id));
|
|
2742
|
+
for (const target of targets) {
|
|
2743
|
+
graph.addEdge({
|
|
2744
|
+
from: router.id,
|
|
2745
|
+
to: target.id,
|
|
2746
|
+
// 条件:从路由器结果中解析目标ID,匹配当前目标
|
|
2747
|
+
condition: (routerResult, _ctx) => {
|
|
2748
|
+
const resolvedId = opts.resolve(routerResult);
|
|
2749
|
+
return resolvedId === target.id;
|
|
2750
|
+
},
|
|
2751
|
+
// 转换:决定传给目标Agent的输入
|
|
2752
|
+
transform: (_routerResult, _ctx) => {
|
|
2753
|
+
const originalInput = _ctx.blackboard.get("__original_input__");
|
|
2754
|
+
if (opts.passOriginalInput === false) {
|
|
2755
|
+
return _routerResult.content;
|
|
2756
|
+
}
|
|
2757
|
+
return originalInput ?? _routerResult.content;
|
|
2758
|
+
}
|
|
2759
|
+
});
|
|
2760
|
+
}
|
|
2761
|
+
if (opts.defaultTarget && targetIds.has(opts.defaultTarget)) {
|
|
2762
|
+
}
|
|
2763
|
+
return graph;
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
// core/supervisor-executor.ts
|
|
2767
|
+
var SupervisorExecutor = class {
|
|
2768
|
+
supervisorNode;
|
|
2769
|
+
workerNodes;
|
|
2770
|
+
opts;
|
|
2771
|
+
state = {
|
|
2772
|
+
nodeStates: /* @__PURE__ */ new Map(),
|
|
2773
|
+
nodeResults: /* @__PURE__ */ new Map(),
|
|
2774
|
+
stepsCompleted: 0,
|
|
2775
|
+
finished: false
|
|
2776
|
+
};
|
|
2777
|
+
constructor(supervisor, workers, opts) {
|
|
2778
|
+
this.supervisorNode = supervisor;
|
|
2779
|
+
this.workerNodes = workers;
|
|
2780
|
+
this.opts = {
|
|
2781
|
+
schedule: opts?.schedule ?? "parallel",
|
|
2782
|
+
concurrency: opts?.concurrency ?? Infinity,
|
|
2783
|
+
passOriginalInput: opts?.passOriginalInput ?? true
|
|
2784
|
+
};
|
|
2785
|
+
this.state.nodeStates.set(supervisor.id, "pending");
|
|
2786
|
+
for (const w of workers) {
|
|
2787
|
+
this.state.nodeStates.set(w.id, "pending");
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
getState() {
|
|
2791
|
+
return { ...this.state };
|
|
2792
|
+
}
|
|
2793
|
+
/** 执行 Supervisor 模式 */
|
|
2794
|
+
async run(input) {
|
|
2795
|
+
const ctx = createSharedContext({
|
|
2796
|
+
meta: { traceId: `sv-${Date.now()}`, startTime: Date.now() }
|
|
2797
|
+
});
|
|
2798
|
+
if (this.opts.passOriginalInput) {
|
|
2799
|
+
const inputText = typeof input === "string" ? input : input.message;
|
|
2800
|
+
ctx.blackboard.set("__original_input__", inputText);
|
|
2801
|
+
}
|
|
2802
|
+
try {
|
|
2803
|
+
return await this.executeSupervisor(input, ctx, () => {
|
|
2804
|
+
});
|
|
2805
|
+
} catch (err) {
|
|
2806
|
+
return this.buildErrorResult(ctx, err);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
/** 流式执行 Supervisor 模式 */
|
|
2810
|
+
async *stream(input) {
|
|
2811
|
+
const ctx = createSharedContext({
|
|
2812
|
+
meta: { traceId: `sv-${Date.now()}`, startTime: Date.now() }
|
|
2813
|
+
});
|
|
2814
|
+
if (this.opts.passOriginalInput) {
|
|
2815
|
+
const inputText = typeof input === "string" ? input : input.message;
|
|
2816
|
+
ctx.blackboard.set("__original_input__", inputText);
|
|
2817
|
+
}
|
|
2818
|
+
const eventQueue = [];
|
|
2819
|
+
let resolveEvent = null;
|
|
2820
|
+
let done = false;
|
|
2821
|
+
const emit = (event) => {
|
|
2822
|
+
eventQueue.push(event);
|
|
2823
|
+
resolveEvent?.();
|
|
2824
|
+
};
|
|
2825
|
+
const graphPromise = this.executeSupervisor(input, ctx, emit).then(
|
|
2826
|
+
(result) => {
|
|
2827
|
+
emit({ type: "graph_done", result });
|
|
2828
|
+
},
|
|
2829
|
+
(err) => {
|
|
2830
|
+
emit({ type: "graph_error", error: err instanceof Error ? err.message : String(err) });
|
|
2831
|
+
}
|
|
2832
|
+
).finally(() => {
|
|
2833
|
+
done = true;
|
|
2834
|
+
resolveEvent?.();
|
|
2835
|
+
});
|
|
2836
|
+
while (!done || eventQueue.length > 0) {
|
|
2837
|
+
if (eventQueue.length > 0) {
|
|
2838
|
+
yield eventQueue.shift();
|
|
2839
|
+
} else {
|
|
2840
|
+
await new Promise((resolve) => {
|
|
2841
|
+
resolveEvent = resolve;
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
await graphPromise;
|
|
2846
|
+
}
|
|
2847
|
+
/** 核心执行逻辑 */
|
|
2848
|
+
async executeSupervisor(input, ctx, emit) {
|
|
2849
|
+
let stepsCompleted = 0;
|
|
2850
|
+
const totalUsage = {};
|
|
2851
|
+
this.state.currentAgentId = this.supervisorNode.id;
|
|
2852
|
+
this.state.nodeStates.set(this.supervisorNode.id, "running");
|
|
2853
|
+
emit({ type: "agent_start", agentId: this.supervisorNode.id, agentName: this.supervisorNode.name });
|
|
2854
|
+
let supervisorResult;
|
|
2855
|
+
try {
|
|
2856
|
+
supervisorResult = await executeNode(this.supervisorNode, input, ctx);
|
|
2857
|
+
this.state.nodeStates.set(this.supervisorNode.id, "completed");
|
|
2858
|
+
this.state.nodeResults.set(this.supervisorNode.id, supervisorResult);
|
|
2859
|
+
this.mergeUsage(totalUsage, supervisorResult.usage);
|
|
2860
|
+
stepsCompleted++;
|
|
2861
|
+
this.state.stepsCompleted = stepsCompleted;
|
|
2862
|
+
emit({ type: "agent_result", agentId: this.supervisorNode.id, agentName: this.supervisorNode.name, result: supervisorResult });
|
|
2863
|
+
} catch (err) {
|
|
2864
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2865
|
+
this.state.nodeStates.set(this.supervisorNode.id, "failed");
|
|
2866
|
+
emit({ type: "agent_error", agentId: this.supervisorNode.id, agentName: this.supervisorNode.name, error: errorMsg });
|
|
2867
|
+
throw err;
|
|
2868
|
+
}
|
|
2869
|
+
const schedule = this.opts.schedule;
|
|
2870
|
+
let workerResults;
|
|
2871
|
+
if (schedule === "parallel") {
|
|
2872
|
+
workerResults = await this.executeWorkersParallel(ctx, emit, totalUsage);
|
|
2873
|
+
} else if (schedule === "sequential") {
|
|
2874
|
+
workerResults = await this.executeWorkersSequential(ctx, emit, totalUsage);
|
|
2875
|
+
} else {
|
|
2876
|
+
workerResults = await this.executeWorkersAuto(ctx, emit, totalUsage);
|
|
2877
|
+
}
|
|
2878
|
+
stepsCompleted += workerResults.size;
|
|
2879
|
+
this.state.stepsCompleted = stepsCompleted;
|
|
2880
|
+
const lastWorkerId = this.workerNodes[this.workerNodes.length - 1].id;
|
|
2881
|
+
const finalResult = workerResults.get(lastWorkerId) ?? supervisorResult;
|
|
2882
|
+
this.state.finished = true;
|
|
2883
|
+
return {
|
|
2884
|
+
content: finalResult.content,
|
|
2885
|
+
lastAgentId: lastWorkerId,
|
|
2886
|
+
agentResults: new Map(this.state.nodeResults),
|
|
2887
|
+
totalUsage,
|
|
2888
|
+
stepsCompleted,
|
|
2889
|
+
stopReason: "completed",
|
|
2890
|
+
context: ctx,
|
|
2891
|
+
success: true
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
/** 并行执行所有 Worker */
|
|
2895
|
+
async executeWorkersParallel(ctx, emit, totalUsage) {
|
|
2896
|
+
const results = /* @__PURE__ */ new Map();
|
|
2897
|
+
const workers = this.workerNodes;
|
|
2898
|
+
const concurrency = this.opts.concurrency;
|
|
2899
|
+
emit({ type: "parallel_start", agentIds: workers.map((w) => w.id) });
|
|
2900
|
+
if (concurrency >= workers.length) {
|
|
2901
|
+
const promises = workers.map(async (worker) => {
|
|
2902
|
+
return this.executeWorker(worker, ctx, emit, totalUsage);
|
|
2903
|
+
});
|
|
2904
|
+
const settled = await Promise.allSettled(promises);
|
|
2905
|
+
for (let i = 0; i < settled.length; i++) {
|
|
2906
|
+
const s = settled[i];
|
|
2907
|
+
if (s.status === "fulfilled") {
|
|
2908
|
+
results.set(workers[i].id, s.value);
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
} else {
|
|
2912
|
+
let index = 0;
|
|
2913
|
+
const executing = /* @__PURE__ */ new Set();
|
|
2914
|
+
const enqueue = () => {
|
|
2915
|
+
if (index >= workers.length) return null;
|
|
2916
|
+
const worker = workers[index++];
|
|
2917
|
+
const p = this.executeWorker(worker, ctx, emit, totalUsage).then((r) => {
|
|
2918
|
+
results.set(worker.id, r);
|
|
2919
|
+
executing.delete(p);
|
|
2920
|
+
return r;
|
|
2921
|
+
});
|
|
2922
|
+
executing.add(p);
|
|
2923
|
+
return p;
|
|
2924
|
+
};
|
|
2925
|
+
for (let i = 0; i < concurrency && index < workers.length; i++) {
|
|
2926
|
+
enqueue();
|
|
2927
|
+
}
|
|
2928
|
+
while (executing.size > 0) {
|
|
2929
|
+
await Promise.race(executing);
|
|
2930
|
+
enqueue();
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
emit({ type: "parallel_done", results });
|
|
2934
|
+
return results;
|
|
2935
|
+
}
|
|
2936
|
+
/** 顺序执行所有 Worker */
|
|
2937
|
+
async executeWorkersSequential(ctx, emit, totalUsage) {
|
|
2938
|
+
const results = /* @__PURE__ */ new Map();
|
|
2939
|
+
for (const worker of this.workerNodes) {
|
|
2940
|
+
const result = await this.executeWorker(worker, ctx, emit, totalUsage);
|
|
2941
|
+
results.set(worker.id, result);
|
|
2942
|
+
}
|
|
2943
|
+
return results;
|
|
2944
|
+
}
|
|
2945
|
+
/** 自动调度:根据 inputMapping 依赖分层执行 */
|
|
2946
|
+
async executeWorkersAuto(ctx, emit, totalUsage) {
|
|
2947
|
+
const results = /* @__PURE__ */ new Map();
|
|
2948
|
+
const noInputMapping = this.workerNodes.filter((w) => !w.inputMapping);
|
|
2949
|
+
const withInputMapping = this.workerNodes.filter((w) => !!w.inputMapping);
|
|
2950
|
+
if (noInputMapping.length > 0) {
|
|
2951
|
+
emit({ type: "parallel_start", agentIds: noInputMapping.map((w) => w.id) });
|
|
2952
|
+
const batchResults = await Promise.all(
|
|
2953
|
+
noInputMapping.map((w) => this.executeWorker(w, ctx, emit, totalUsage))
|
|
2954
|
+
);
|
|
2955
|
+
for (let i = 0; i < noInputMapping.length; i++) {
|
|
2956
|
+
results.set(noInputMapping[i].id, batchResults[i]);
|
|
2957
|
+
}
|
|
2958
|
+
emit({ type: "parallel_done", results: new Map(results) });
|
|
2959
|
+
}
|
|
2960
|
+
for (const worker of withInputMapping) {
|
|
2961
|
+
const result = await this.executeWorker(worker, ctx, emit, totalUsage);
|
|
2962
|
+
results.set(worker.id, result);
|
|
2963
|
+
}
|
|
2964
|
+
return results;
|
|
2965
|
+
}
|
|
2966
|
+
/** 执行单个 Worker */
|
|
2967
|
+
async executeWorker(worker, ctx, emit, totalUsage) {
|
|
2968
|
+
this.state.currentAgentId = worker.id;
|
|
2969
|
+
this.state.nodeStates.set(worker.id, "running");
|
|
2970
|
+
emit({ type: "agent_start", agentId: worker.id, agentName: worker.name });
|
|
2971
|
+
try {
|
|
2972
|
+
let workerInput;
|
|
2973
|
+
if (worker.inputMapping) {
|
|
2974
|
+
workerInput = worker.inputMapping(ctx);
|
|
2975
|
+
} else {
|
|
2976
|
+
const tasks = ctx.blackboard.get("tasks");
|
|
2977
|
+
const myTask = tasks?.find((t) => t.assignee === worker.id);
|
|
2978
|
+
workerInput = myTask ? myTask.task : ctx.blackboard.get("__original_input__") ?? "";
|
|
2979
|
+
}
|
|
2980
|
+
const result = await executeNode(worker, workerInput, ctx);
|
|
2981
|
+
this.state.nodeStates.set(worker.id, "completed");
|
|
2982
|
+
this.state.nodeResults.set(worker.id, result);
|
|
2983
|
+
this.mergeUsage(totalUsage, result.usage);
|
|
2984
|
+
ctx.blackboard.set(`${worker.id}_result`, result.content);
|
|
2985
|
+
emit({ type: "agent_result", agentId: worker.id, agentName: worker.name, result });
|
|
2986
|
+
return result;
|
|
2987
|
+
} catch (err) {
|
|
2988
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2989
|
+
this.state.nodeStates.set(worker.id, "failed");
|
|
2990
|
+
emit({ type: "agent_error", agentId: worker.id, agentName: worker.name, error: errorMsg });
|
|
2991
|
+
throw err;
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
buildErrorResult(ctx, err) {
|
|
2995
|
+
this.state.finished = true;
|
|
2996
|
+
return {
|
|
2997
|
+
content: "",
|
|
2998
|
+
lastAgentId: this.supervisorNode.id,
|
|
2999
|
+
agentResults: new Map(this.state.nodeResults),
|
|
3000
|
+
totalUsage: {},
|
|
3001
|
+
stepsCompleted: this.state.stepsCompleted,
|
|
3002
|
+
stopReason: "error",
|
|
3003
|
+
context: ctx,
|
|
3004
|
+
success: false,
|
|
3005
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3006
|
+
};
|
|
3007
|
+
}
|
|
3008
|
+
mergeUsage(total, usage) {
|
|
3009
|
+
for (const [key, value] of Object.entries(usage)) {
|
|
3010
|
+
total[key] = (total[key] ?? 0) + value;
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
};
|
|
3014
|
+
|
|
3015
|
+
// patterns/supervisor.ts
|
|
3016
|
+
var SupervisorGraphImpl = class {
|
|
3017
|
+
executor;
|
|
3018
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
3019
|
+
constructor(supervisor, workers, opts) {
|
|
3020
|
+
this.executor = new SupervisorExecutor(supervisor, workers, opts);
|
|
3021
|
+
}
|
|
3022
|
+
// AgentGraph 接口方法(Supervisor 不使用 addNode/addEdge/setEntry/setFinish)
|
|
3023
|
+
addNode() {
|
|
3024
|
+
throw new Error("Supervisor \u6A21\u5F0F\u4E0D\u652F\u6301\u624B\u52A8 addNode\uFF0C\u8BF7\u5728 createSupervisor() \u4E2D\u914D\u7F6E");
|
|
3025
|
+
}
|
|
3026
|
+
addEdge() {
|
|
3027
|
+
throw new Error("Supervisor \u6A21\u5F0F\u4E0D\u652F\u6301\u624B\u52A8 addEdge\uFF0C\u8C03\u5EA6\u7531 Supervisor \u81EA\u52A8\u7BA1\u7406");
|
|
3028
|
+
}
|
|
3029
|
+
setEntry() {
|
|
3030
|
+
throw new Error("Supervisor \u6A21\u5F0F\u4E0D\u652F\u6301 setEntry\uFF0C\u5165\u53E3\u56FA\u5B9A\u4E3A Supervisor");
|
|
3031
|
+
}
|
|
3032
|
+
setFinish() {
|
|
3033
|
+
throw new Error("Supervisor \u6A21\u5F0F\u4E0D\u652F\u6301 setFinish\uFF0C\u7EC8\u6B62\u7531 Supervisor \u5224\u5B9A");
|
|
3034
|
+
}
|
|
3035
|
+
async run(input) {
|
|
3036
|
+
return this.executor.run(input);
|
|
3037
|
+
}
|
|
3038
|
+
async *stream(input) {
|
|
3039
|
+
yield* this.executor.stream(input);
|
|
3040
|
+
}
|
|
3041
|
+
getState() {
|
|
3042
|
+
return this.executor.getState();
|
|
3043
|
+
}
|
|
3044
|
+
abort() {
|
|
3045
|
+
}
|
|
3046
|
+
on(event, listener) {
|
|
3047
|
+
let set = this.eventListeners.get(event);
|
|
3048
|
+
if (!set) {
|
|
3049
|
+
set = /* @__PURE__ */ new Set();
|
|
3050
|
+
this.eventListeners.set(event, set);
|
|
3051
|
+
}
|
|
3052
|
+
set.add(listener);
|
|
3053
|
+
return this;
|
|
3054
|
+
}
|
|
3055
|
+
};
|
|
3056
|
+
function createSupervisor(supervisor, workers, opts) {
|
|
3057
|
+
if (workers.length === 0) {
|
|
3058
|
+
throw new Error("Supervisor: \u81F3\u5C11\u9700\u8981\u4E00\u4E2A Worker Agent");
|
|
3059
|
+
}
|
|
3060
|
+
return new SupervisorGraphImpl(supervisor, workers, opts);
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
// core/debate-executor.ts
|
|
3064
|
+
var DebateExecutor = class {
|
|
3065
|
+
proposerNode;
|
|
3066
|
+
reviewerNode;
|
|
3067
|
+
maxRounds;
|
|
3068
|
+
convergeWhen;
|
|
3069
|
+
feedbackTransform;
|
|
3070
|
+
state = {
|
|
3071
|
+
nodeStates: /* @__PURE__ */ new Map(),
|
|
3072
|
+
nodeResults: /* @__PURE__ */ new Map(),
|
|
3073
|
+
stepsCompleted: 0,
|
|
3074
|
+
finished: false
|
|
3075
|
+
};
|
|
3076
|
+
constructor(proposer, reviewer, opts) {
|
|
3077
|
+
this.proposerNode = proposer;
|
|
3078
|
+
this.reviewerNode = reviewer;
|
|
3079
|
+
this.maxRounds = opts.maxRounds ?? 3;
|
|
3080
|
+
this.convergeWhen = opts.convergeWhen;
|
|
3081
|
+
this.feedbackTransform = opts.feedbackTransform ?? ((reviewerResult, proposerResult) => {
|
|
3082
|
+
return `\u4EE5\u4E0B\u662F\u5BA1\u67E5\u610F\u89C1:
|
|
3083
|
+
${reviewerResult.content}
|
|
3084
|
+
|
|
3085
|
+
\u8BF7\u4FEE\u590D\u4EE5\u4E0A\u95EE\u9898\u5E76\u91CD\u65B0\u63D0\u4EA4\u3002\u539F\u59CB\u8F93\u51FA:
|
|
3086
|
+
${proposerResult.content}`;
|
|
3087
|
+
});
|
|
3088
|
+
this.state.nodeStates.set(proposer.id, "pending");
|
|
3089
|
+
this.state.nodeStates.set(reviewer.id, "pending");
|
|
3090
|
+
}
|
|
3091
|
+
getState() {
|
|
3092
|
+
return { ...this.state };
|
|
3093
|
+
}
|
|
3094
|
+
/** 执行 Debate */
|
|
3095
|
+
async run(input) {
|
|
3096
|
+
const ctx = createSharedContext({
|
|
3097
|
+
meta: { traceId: `debate-${Date.now()}`, startTime: Date.now() }
|
|
3098
|
+
});
|
|
3099
|
+
try {
|
|
3100
|
+
return await this.executeDebate(input, ctx, () => {
|
|
3101
|
+
});
|
|
3102
|
+
} catch (err) {
|
|
3103
|
+
return this.buildErrorResult(ctx, err);
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
/** 流式执行 Debate */
|
|
3107
|
+
async *stream(input) {
|
|
3108
|
+
const ctx = createSharedContext({
|
|
3109
|
+
meta: { traceId: `debate-${Date.now()}`, startTime: Date.now() }
|
|
3110
|
+
});
|
|
3111
|
+
const eventQueue = [];
|
|
3112
|
+
let resolveEvent = null;
|
|
3113
|
+
let done = false;
|
|
3114
|
+
const emit = (event) => {
|
|
3115
|
+
eventQueue.push(event);
|
|
3116
|
+
resolveEvent?.();
|
|
3117
|
+
};
|
|
3118
|
+
const graphPromise = this.executeDebate(input, ctx, emit).then(
|
|
3119
|
+
(result) => {
|
|
3120
|
+
emit({ type: "graph_done", result });
|
|
3121
|
+
},
|
|
3122
|
+
(err) => {
|
|
3123
|
+
emit({ type: "graph_error", error: err instanceof Error ? err.message : String(err) });
|
|
3124
|
+
}
|
|
3125
|
+
).finally(() => {
|
|
3126
|
+
done = true;
|
|
3127
|
+
resolveEvent?.();
|
|
3128
|
+
});
|
|
3129
|
+
while (!done || eventQueue.length > 0) {
|
|
3130
|
+
if (eventQueue.length > 0) {
|
|
3131
|
+
yield eventQueue.shift();
|
|
3132
|
+
} else {
|
|
3133
|
+
await new Promise((resolve) => {
|
|
3134
|
+
resolveEvent = resolve;
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
await graphPromise;
|
|
3139
|
+
}
|
|
3140
|
+
/** 核心执行逻辑 */
|
|
3141
|
+
async executeDebate(input, ctx, emit) {
|
|
3142
|
+
let stepsCompleted = 0;
|
|
3143
|
+
const totalUsage = {};
|
|
3144
|
+
let lastProposerResult;
|
|
3145
|
+
let lastReviewerResult;
|
|
3146
|
+
let convergedRound = 0;
|
|
3147
|
+
let convergeReason = "";
|
|
3148
|
+
let currentInput = input;
|
|
3149
|
+
for (let round = 1; round <= this.maxRounds; round++) {
|
|
3150
|
+
emit({ type: "round_start", round });
|
|
3151
|
+
this.state.currentAgentId = this.proposerNode.id;
|
|
3152
|
+
this.state.nodeStates.set(this.proposerNode.id, "running");
|
|
3153
|
+
emit({ type: "agent_start", agentId: this.proposerNode.id, agentName: this.proposerNode.name });
|
|
3154
|
+
try {
|
|
3155
|
+
lastProposerResult = await executeNode(this.proposerNode, currentInput, ctx);
|
|
3156
|
+
this.state.nodeStates.set(this.proposerNode.id, "completed");
|
|
3157
|
+
this.state.nodeResults.set(`${this.proposerNode.id}_r${round}`, lastProposerResult);
|
|
3158
|
+
this.mergeUsage(totalUsage, lastProposerResult.usage);
|
|
3159
|
+
stepsCompleted++;
|
|
3160
|
+
this.state.stepsCompleted = stepsCompleted;
|
|
3161
|
+
emit({ type: "agent_result", agentId: this.proposerNode.id, agentName: this.proposerNode.name, result: lastProposerResult });
|
|
3162
|
+
} catch (err) {
|
|
3163
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3164
|
+
this.state.nodeStates.set(this.proposerNode.id, "failed");
|
|
3165
|
+
emit({ type: "agent_error", agentId: this.proposerNode.id, agentName: this.proposerNode.name, error: errorMsg });
|
|
3166
|
+
throw err;
|
|
3167
|
+
}
|
|
3168
|
+
this.state.currentAgentId = this.reviewerNode.id;
|
|
3169
|
+
this.state.nodeStates.set(this.reviewerNode.id, "running");
|
|
3170
|
+
emit({ type: "agent_start", agentId: this.reviewerNode.id, agentName: this.reviewerNode.name });
|
|
3171
|
+
try {
|
|
3172
|
+
lastReviewerResult = await executeNode(this.reviewerNode, lastProposerResult.content, ctx);
|
|
3173
|
+
this.state.nodeStates.set(this.reviewerNode.id, "completed");
|
|
3174
|
+
this.state.nodeResults.set(`${this.reviewerNode.id}_r${round}`, lastReviewerResult);
|
|
3175
|
+
this.mergeUsage(totalUsage, lastReviewerResult.usage);
|
|
3176
|
+
stepsCompleted++;
|
|
3177
|
+
this.state.stepsCompleted = stepsCompleted;
|
|
3178
|
+
emit({ type: "agent_result", agentId: this.reviewerNode.id, agentName: this.reviewerNode.name, result: lastReviewerResult });
|
|
3179
|
+
} catch (err) {
|
|
3180
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3181
|
+
this.state.nodeStates.set(this.reviewerNode.id, "failed");
|
|
3182
|
+
emit({ type: "agent_error", agentId: this.reviewerNode.id, agentName: this.reviewerNode.name, error: errorMsg });
|
|
3183
|
+
throw err;
|
|
3184
|
+
}
|
|
3185
|
+
if (this.convergeWhen(lastReviewerResult)) {
|
|
3186
|
+
convergedRound = round;
|
|
3187
|
+
convergeReason = `\u6536\u655B\u4E8E\u7B2C ${round} \u8F6E\uFF1Areviewer \u8F93\u51FA\u6EE1\u8DB3\u6536\u655B\u6761\u4EF6`;
|
|
3188
|
+
emit({ type: "converged", round, reason: convergeReason });
|
|
3189
|
+
break;
|
|
3190
|
+
}
|
|
3191
|
+
currentInput = this.feedbackTransform(lastReviewerResult, lastProposerResult);
|
|
3192
|
+
}
|
|
3193
|
+
const finalResult = lastProposerResult;
|
|
3194
|
+
const stopReason = convergedRound > 0 ? `converged_at_round_${convergedRound}` : `max_rounds_reached`;
|
|
3195
|
+
this.state.finished = true;
|
|
3196
|
+
return {
|
|
3197
|
+
content: finalResult.content,
|
|
3198
|
+
lastAgentId: this.reviewerNode.id,
|
|
3199
|
+
agentResults: new Map(this.state.nodeResults),
|
|
3200
|
+
totalUsage,
|
|
3201
|
+
stepsCompleted,
|
|
3202
|
+
stopReason,
|
|
3203
|
+
context: ctx,
|
|
3204
|
+
success: true
|
|
3205
|
+
};
|
|
3206
|
+
}
|
|
3207
|
+
buildErrorResult(ctx, err) {
|
|
3208
|
+
this.state.finished = true;
|
|
3209
|
+
return {
|
|
3210
|
+
content: "",
|
|
3211
|
+
lastAgentId: this.proposerNode.id,
|
|
3212
|
+
agentResults: new Map(this.state.nodeResults),
|
|
3213
|
+
totalUsage: {},
|
|
3214
|
+
stepsCompleted: this.state.stepsCompleted,
|
|
3215
|
+
stopReason: "error",
|
|
3216
|
+
context: ctx,
|
|
3217
|
+
success: false,
|
|
3218
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3219
|
+
};
|
|
3220
|
+
}
|
|
3221
|
+
mergeUsage(total, usage) {
|
|
3222
|
+
for (const [key, value] of Object.entries(usage)) {
|
|
3223
|
+
total[key] = (total[key] ?? 0) + value;
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
};
|
|
3227
|
+
|
|
3228
|
+
// patterns/debate.ts
|
|
3229
|
+
var DebateGraphImpl = class {
|
|
3230
|
+
executor;
|
|
3231
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
3232
|
+
constructor(proposer, reviewer, opts) {
|
|
3233
|
+
this.executor = new DebateExecutor(proposer, reviewer, opts);
|
|
3234
|
+
}
|
|
3235
|
+
addNode() {
|
|
3236
|
+
throw new Error("Debate \u6A21\u5F0F\u4E0D\u652F\u6301\u624B\u52A8 addNode");
|
|
3237
|
+
}
|
|
3238
|
+
addEdge() {
|
|
3239
|
+
throw new Error("Debate \u6A21\u5F0F\u4E0D\u652F\u6301\u624B\u52A8 addEdge");
|
|
3240
|
+
}
|
|
3241
|
+
setEntry() {
|
|
3242
|
+
throw new Error("Debate \u6A21\u5F0F\u4E0D\u652F\u6301 setEntry");
|
|
3243
|
+
}
|
|
3244
|
+
setFinish() {
|
|
3245
|
+
throw new Error("Debate \u6A21\u5F0F\u4E0D\u652F\u6301 setFinish");
|
|
3246
|
+
}
|
|
3247
|
+
async run(input) {
|
|
3248
|
+
return this.executor.run(input);
|
|
3249
|
+
}
|
|
3250
|
+
async *stream(input) {
|
|
3251
|
+
yield* this.executor.stream(input);
|
|
3252
|
+
}
|
|
3253
|
+
getState() {
|
|
3254
|
+
return this.executor.getState();
|
|
3255
|
+
}
|
|
3256
|
+
abort() {
|
|
3257
|
+
}
|
|
3258
|
+
on(event, listener) {
|
|
3259
|
+
let set = this.eventListeners.get(event);
|
|
3260
|
+
if (!set) {
|
|
3261
|
+
set = /* @__PURE__ */ new Set();
|
|
3262
|
+
this.eventListeners.set(event, set);
|
|
3263
|
+
}
|
|
3264
|
+
set.add(listener);
|
|
3265
|
+
return this;
|
|
3266
|
+
}
|
|
3267
|
+
};
|
|
3268
|
+
function createDebate(proposer, reviewer, opts) {
|
|
3269
|
+
return new DebateGraphImpl(proposer, reviewer, opts);
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
// core/map-reduce-executor.ts
|
|
3273
|
+
var MapReduceExecutor = class {
|
|
3274
|
+
mapperNode;
|
|
3275
|
+
reducerNode;
|
|
3276
|
+
split;
|
|
3277
|
+
concurrency;
|
|
3278
|
+
reduceInputFormat;
|
|
3279
|
+
state = {
|
|
3280
|
+
nodeStates: /* @__PURE__ */ new Map(),
|
|
3281
|
+
nodeResults: /* @__PURE__ */ new Map(),
|
|
3282
|
+
stepsCompleted: 0,
|
|
3283
|
+
finished: false
|
|
3284
|
+
};
|
|
3285
|
+
constructor(mapper, reducer, opts) {
|
|
3286
|
+
this.mapperNode = mapper;
|
|
3287
|
+
this.reducerNode = reducer;
|
|
3288
|
+
this.split = opts.split;
|
|
3289
|
+
this.concurrency = opts.concurrency ?? Infinity;
|
|
3290
|
+
this.reduceInputFormat = opts.reduceInputFormat ?? ((mapperResults) => {
|
|
3291
|
+
const parts = [];
|
|
3292
|
+
for (const [idx, result] of mapperResults) {
|
|
3293
|
+
parts.push(`--- \u5B50\u4EFB\u52A1 ${idx + 1} ---
|
|
3294
|
+
${result.content}`);
|
|
3295
|
+
}
|
|
3296
|
+
return parts.join("\n\n");
|
|
3297
|
+
});
|
|
3298
|
+
this.state.nodeStates.set(mapper.id, "pending");
|
|
3299
|
+
this.state.nodeStates.set(reducer.id, "pending");
|
|
3300
|
+
}
|
|
3301
|
+
getState() {
|
|
3302
|
+
return { ...this.state };
|
|
3303
|
+
}
|
|
3304
|
+
/** 执行 MapReduce */
|
|
3305
|
+
async run(input) {
|
|
3306
|
+
const inputText = typeof input === "string" ? input : input.message;
|
|
3307
|
+
const chunks = this.split(inputText);
|
|
3308
|
+
if (chunks.length === 0) {
|
|
3309
|
+
throw new Error("MapReduce: split \u51FD\u6570\u8FD4\u56DE\u4E86\u7A7A\u6570\u7EC4\uFF0C\u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u5B50\u4EFB\u52A1");
|
|
3310
|
+
}
|
|
3311
|
+
const ctx = createSharedContext({
|
|
3312
|
+
meta: { traceId: `mr-${Date.now()}`, startTime: Date.now() }
|
|
3313
|
+
});
|
|
3314
|
+
try {
|
|
3315
|
+
return await this.executeMapReduce(input, ctx, () => {
|
|
3316
|
+
});
|
|
3317
|
+
} catch (err) {
|
|
3318
|
+
return this.buildErrorResult(ctx, err);
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
/** 流式执行 MapReduce */
|
|
3322
|
+
async *stream(input) {
|
|
3323
|
+
const inputText = typeof input === "string" ? input : input.message;
|
|
3324
|
+
const chunks = this.split(inputText);
|
|
3325
|
+
if (chunks.length === 0) {
|
|
3326
|
+
throw new Error("MapReduce: split \u51FD\u6570\u8FD4\u56DE\u4E86\u7A7A\u6570\u7EC4\uFF0C\u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u5B50\u4EFB\u52A1");
|
|
3327
|
+
}
|
|
3328
|
+
const ctx = createSharedContext({
|
|
3329
|
+
meta: { traceId: `mr-${Date.now()}`, startTime: Date.now() }
|
|
3330
|
+
});
|
|
3331
|
+
const eventQueue = [];
|
|
3332
|
+
let resolveEvent = null;
|
|
3333
|
+
let done = false;
|
|
3334
|
+
const emit = (event) => {
|
|
3335
|
+
eventQueue.push(event);
|
|
3336
|
+
resolveEvent?.();
|
|
3337
|
+
};
|
|
3338
|
+
const graphPromise = this.executeMapReduce(input, ctx, emit).then(
|
|
3339
|
+
(result) => {
|
|
3340
|
+
emit({ type: "graph_done", result });
|
|
3341
|
+
},
|
|
3342
|
+
(err) => {
|
|
3343
|
+
emit({ type: "graph_error", error: err instanceof Error ? err.message : String(err) });
|
|
3344
|
+
}
|
|
3345
|
+
).finally(() => {
|
|
3346
|
+
done = true;
|
|
3347
|
+
resolveEvent?.();
|
|
3348
|
+
});
|
|
3349
|
+
while (!done || eventQueue.length > 0) {
|
|
3350
|
+
if (eventQueue.length > 0) {
|
|
3351
|
+
yield eventQueue.shift();
|
|
3352
|
+
} else {
|
|
3353
|
+
await new Promise((resolve) => {
|
|
3354
|
+
resolveEvent = resolve;
|
|
3355
|
+
});
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
await graphPromise;
|
|
3359
|
+
}
|
|
3360
|
+
/** 核心执行逻辑 */
|
|
3361
|
+
async executeMapReduce(input, ctx, emit) {
|
|
3362
|
+
let stepsCompleted = 0;
|
|
3363
|
+
const totalUsage = {};
|
|
3364
|
+
const inputText = typeof input === "string" ? input : input.message;
|
|
3365
|
+
const chunks = this.split(inputText);
|
|
3366
|
+
const mapperResults = /* @__PURE__ */ new Map();
|
|
3367
|
+
this.state.nodeStates.set(this.mapperNode.id, "running");
|
|
3368
|
+
const mapperAgentIds = chunks.map((_, i) => `${this.mapperNode.id}_${i}`);
|
|
3369
|
+
emit({ type: "parallel_start", agentIds: mapperAgentIds });
|
|
3370
|
+
if (this.concurrency >= chunks.length) {
|
|
3371
|
+
const promises = chunks.map(async (chunk, index) => {
|
|
3372
|
+
emit({ type: "agent_start", agentId: `${this.mapperNode.id}_${index}`, agentName: `${this.mapperNode.name}#${index + 1}` });
|
|
3373
|
+
try {
|
|
3374
|
+
const result = await executeNode(this.mapperNode, chunk, ctx);
|
|
3375
|
+
this.state.nodeResults.set(`${this.mapperNode.id}_${index}`, result);
|
|
3376
|
+
this.mergeUsage(totalUsage, result.usage);
|
|
3377
|
+
stepsCompleted++;
|
|
3378
|
+
this.state.stepsCompleted = stepsCompleted;
|
|
3379
|
+
emit({ type: "agent_result", agentId: `${this.mapperNode.id}_${index}`, agentName: `${this.mapperNode.name}#${index + 1}`, result });
|
|
3380
|
+
return { index, result };
|
|
3381
|
+
} catch (err) {
|
|
3382
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3383
|
+
emit({ type: "agent_error", agentId: `${this.mapperNode.id}_${index}`, agentName: `${this.mapperNode.name}#${index + 1}`, error: errorMsg });
|
|
3384
|
+
throw err;
|
|
3385
|
+
}
|
|
3386
|
+
});
|
|
3387
|
+
const settled = await Promise.allSettled(promises);
|
|
3388
|
+
for (const s of settled) {
|
|
3389
|
+
if (s.status === "fulfilled") {
|
|
3390
|
+
mapperResults.set(s.value.index, s.value.result);
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
} else {
|
|
3394
|
+
let index = 0;
|
|
3395
|
+
const executing = /* @__PURE__ */ new Set();
|
|
3396
|
+
const enqueue = () => {
|
|
3397
|
+
if (index >= chunks.length) return null;
|
|
3398
|
+
const currentIndex = index++;
|
|
3399
|
+
const chunk = chunks[currentIndex];
|
|
3400
|
+
const p = (async () => {
|
|
3401
|
+
emit({ type: "agent_start", agentId: `${this.mapperNode.id}_${currentIndex}`, agentName: `${this.mapperNode.name}#${currentIndex + 1}` });
|
|
3402
|
+
try {
|
|
3403
|
+
const result = await executeNode(this.mapperNode, chunk, ctx);
|
|
3404
|
+
mapperResults.set(currentIndex, result);
|
|
3405
|
+
this.state.nodeResults.set(`${this.mapperNode.id}_${currentIndex}`, result);
|
|
3406
|
+
this.mergeUsage(totalUsage, result.usage);
|
|
3407
|
+
stepsCompleted++;
|
|
3408
|
+
this.state.stepsCompleted = stepsCompleted;
|
|
3409
|
+
emit({ type: "agent_result", agentId: `${this.mapperNode.id}_${currentIndex}`, agentName: `${this.mapperNode.name}#${currentIndex + 1}`, result });
|
|
3410
|
+
} catch (err) {
|
|
3411
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3412
|
+
emit({ type: "agent_error", agentId: `${this.mapperNode.id}_${currentIndex}`, agentName: `${this.mapperNode.name}#${currentIndex + 1}`, error: errorMsg });
|
|
3413
|
+
throw err;
|
|
3414
|
+
}
|
|
3415
|
+
})();
|
|
3416
|
+
executing.add(p);
|
|
3417
|
+
p.finally(() => executing.delete(p));
|
|
3418
|
+
return p;
|
|
3419
|
+
};
|
|
3420
|
+
for (let i = 0; i < this.concurrency && index < chunks.length; i++) {
|
|
3421
|
+
enqueue();
|
|
3422
|
+
}
|
|
3423
|
+
while (executing.size > 0) {
|
|
3424
|
+
await Promise.race(executing);
|
|
3425
|
+
enqueue();
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
this.state.nodeStates.set(this.mapperNode.id, "completed");
|
|
3429
|
+
const mapperResultsForEmit = /* @__PURE__ */ new Map();
|
|
3430
|
+
for (const [idx, result] of mapperResults) {
|
|
3431
|
+
mapperResultsForEmit.set(`${this.mapperNode.id}_${idx}`, result);
|
|
3432
|
+
}
|
|
3433
|
+
emit({ type: "parallel_done", results: mapperResultsForEmit });
|
|
3434
|
+
ctx.blackboard.set("mapper_results", mapperResults);
|
|
3435
|
+
const reduceInput = this.reduceInputFormat(mapperResults);
|
|
3436
|
+
this.state.currentAgentId = this.reducerNode.id;
|
|
3437
|
+
this.state.nodeStates.set(this.reducerNode.id, "running");
|
|
3438
|
+
emit({ type: "agent_start", agentId: this.reducerNode.id, agentName: this.reducerNode.name });
|
|
3439
|
+
let reducerResult;
|
|
3440
|
+
try {
|
|
3441
|
+
reducerResult = await executeNode(this.reducerNode, reduceInput, ctx);
|
|
3442
|
+
this.state.nodeStates.set(this.reducerNode.id, "completed");
|
|
3443
|
+
this.state.nodeResults.set(this.reducerNode.id, reducerResult);
|
|
3444
|
+
this.mergeUsage(totalUsage, reducerResult.usage);
|
|
3445
|
+
stepsCompleted++;
|
|
3446
|
+
this.state.stepsCompleted = stepsCompleted;
|
|
3447
|
+
emit({ type: "agent_result", agentId: this.reducerNode.id, agentName: this.reducerNode.name, result: reducerResult });
|
|
3448
|
+
} catch (err) {
|
|
3449
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3450
|
+
this.state.nodeStates.set(this.reducerNode.id, "failed");
|
|
3451
|
+
emit({ type: "agent_error", agentId: this.reducerNode.id, agentName: this.reducerNode.name, error: errorMsg });
|
|
3452
|
+
throw err;
|
|
3453
|
+
}
|
|
3454
|
+
this.state.finished = true;
|
|
3455
|
+
return {
|
|
3456
|
+
content: reducerResult.content,
|
|
3457
|
+
lastAgentId: this.reducerNode.id,
|
|
3458
|
+
agentResults: new Map(this.state.nodeResults),
|
|
3459
|
+
totalUsage,
|
|
3460
|
+
stepsCompleted,
|
|
3461
|
+
stopReason: "completed",
|
|
3462
|
+
context: ctx,
|
|
3463
|
+
success: true
|
|
3464
|
+
};
|
|
3465
|
+
}
|
|
3466
|
+
buildErrorResult(ctx, err) {
|
|
3467
|
+
this.state.finished = true;
|
|
3468
|
+
return {
|
|
3469
|
+
content: "",
|
|
3470
|
+
lastAgentId: this.mapperNode.id,
|
|
3471
|
+
agentResults: new Map(this.state.nodeResults),
|
|
3472
|
+
totalUsage: {},
|
|
3473
|
+
stepsCompleted: this.state.stepsCompleted,
|
|
3474
|
+
stopReason: "error",
|
|
3475
|
+
context: ctx,
|
|
3476
|
+
success: false,
|
|
3477
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
mergeUsage(total, usage) {
|
|
3481
|
+
for (const [key, value] of Object.entries(usage)) {
|
|
3482
|
+
total[key] = (total[key] ?? 0) + value;
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
};
|
|
3486
|
+
|
|
3487
|
+
// patterns/map-reduce.ts
|
|
3488
|
+
var MapReduceGraphImpl = class {
|
|
3489
|
+
executor;
|
|
3490
|
+
eventListeners = /* @__PURE__ */ new Map();
|
|
3491
|
+
constructor(mapper, reducer, opts) {
|
|
3492
|
+
this.executor = new MapReduceExecutor(mapper, reducer, opts);
|
|
3493
|
+
}
|
|
3494
|
+
addNode() {
|
|
3495
|
+
throw new Error("MapReduce \u6A21\u5F0F\u4E0D\u652F\u6301\u624B\u52A8 addNode");
|
|
3496
|
+
}
|
|
3497
|
+
addEdge() {
|
|
3498
|
+
throw new Error("MapReduce \u6A21\u5F0F\u4E0D\u652F\u6301\u624B\u52A8 addEdge");
|
|
3499
|
+
}
|
|
3500
|
+
setEntry() {
|
|
3501
|
+
throw new Error("MapReduce \u6A21\u5F0F\u4E0D\u652F\u6301 setEntry");
|
|
3502
|
+
}
|
|
3503
|
+
setFinish() {
|
|
3504
|
+
throw new Error("MapReduce \u6A21\u5F0F\u4E0D\u652F\u6301 setFinish");
|
|
3505
|
+
}
|
|
3506
|
+
async run(input) {
|
|
3507
|
+
return this.executor.run(input);
|
|
3508
|
+
}
|
|
3509
|
+
async *stream(input) {
|
|
3510
|
+
yield* this.executor.stream(input);
|
|
3511
|
+
}
|
|
3512
|
+
getState() {
|
|
3513
|
+
return this.executor.getState();
|
|
3514
|
+
}
|
|
3515
|
+
abort() {
|
|
3516
|
+
}
|
|
3517
|
+
on(event, listener) {
|
|
3518
|
+
let set = this.eventListeners.get(event);
|
|
3519
|
+
if (!set) {
|
|
3520
|
+
set = /* @__PURE__ */ new Set();
|
|
3521
|
+
this.eventListeners.set(event, set);
|
|
3522
|
+
}
|
|
3523
|
+
set.add(listener);
|
|
3524
|
+
return this;
|
|
3525
|
+
}
|
|
3526
|
+
};
|
|
3527
|
+
function createMapReduce(mapper, reducer, opts) {
|
|
3528
|
+
return new MapReduceGraphImpl(mapper, reducer, opts);
|
|
3529
|
+
}
|
|
3530
|
+
|
|
3531
|
+
// extensions/mcp-bridge.ts
|
|
3532
|
+
var MCPBridge = class {
|
|
3533
|
+
graph;
|
|
3534
|
+
opts;
|
|
3535
|
+
constructor(graph, opts) {
|
|
3536
|
+
this.graph = graph;
|
|
3537
|
+
this.opts = {
|
|
3538
|
+
serverName: opts?.serverName ?? "aipack-multi-agent",
|
|
3539
|
+
serverVersion: opts?.serverVersion ?? "1.0.0",
|
|
3540
|
+
toolPrefix: opts?.toolPrefix ?? ""
|
|
3541
|
+
};
|
|
3542
|
+
}
|
|
3543
|
+
/** 获取 MCP Server 信息 */
|
|
3544
|
+
getServerInfo() {
|
|
3545
|
+
return {
|
|
3546
|
+
name: this.opts.serverName,
|
|
3547
|
+
version: this.opts.serverVersion
|
|
3548
|
+
};
|
|
3549
|
+
}
|
|
3550
|
+
/** 列出所有可用 MCP 工具 */
|
|
3551
|
+
listTools() {
|
|
3552
|
+
const prefix = this.opts.toolPrefix;
|
|
3553
|
+
return [
|
|
3554
|
+
{
|
|
3555
|
+
name: `${prefix}run`,
|
|
3556
|
+
description: "\u8FD0\u884C\u591AAgent\u7F16\u6392\u56FE\uFF0C\u4F20\u5165\u7528\u6237\u8F93\u5165\uFF0C\u8FD4\u56DE\u6267\u884C\u7ED3\u679C",
|
|
3557
|
+
parameters: [
|
|
3558
|
+
{
|
|
3559
|
+
name: "input",
|
|
3560
|
+
description: "\u7528\u6237\u8F93\u5165\u6587\u672C",
|
|
3561
|
+
type: "string",
|
|
3562
|
+
required: true
|
|
3563
|
+
}
|
|
3564
|
+
]
|
|
3565
|
+
},
|
|
3566
|
+
{
|
|
3567
|
+
name: `${prefix}status`,
|
|
3568
|
+
description: "\u83B7\u53D6\u5F53\u524D\u56FE\u6267\u884C\u72B6\u6001",
|
|
3569
|
+
parameters: []
|
|
3570
|
+
}
|
|
3571
|
+
];
|
|
3572
|
+
}
|
|
3573
|
+
/** 处理 MCP 工具调用 */
|
|
3574
|
+
async handleCall(request) {
|
|
3575
|
+
const prefix = this.opts.toolPrefix;
|
|
3576
|
+
const runTool = `${prefix}run`;
|
|
3577
|
+
const statusTool = `${prefix}status`;
|
|
3578
|
+
try {
|
|
3579
|
+
if (request.name === runTool) {
|
|
3580
|
+
const input = request.arguments.input;
|
|
3581
|
+
if (!input) {
|
|
3582
|
+
return {
|
|
3583
|
+
isError: true,
|
|
3584
|
+
content: [{ type: "text", text: "\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: input" }]
|
|
3585
|
+
};
|
|
3586
|
+
}
|
|
3587
|
+
const result = await this.graph.run(input);
|
|
3588
|
+
return {
|
|
3589
|
+
content: [
|
|
3590
|
+
{
|
|
3591
|
+
type: "text",
|
|
3592
|
+
text: JSON.stringify({
|
|
3593
|
+
success: result.success,
|
|
3594
|
+
content: result.content,
|
|
3595
|
+
lastAgentId: result.lastAgentId,
|
|
3596
|
+
stepsCompleted: result.stepsCompleted,
|
|
3597
|
+
stopReason: result.stopReason,
|
|
3598
|
+
totalUsage: result.totalUsage
|
|
3599
|
+
}, null, 2)
|
|
3600
|
+
}
|
|
3601
|
+
]
|
|
3602
|
+
};
|
|
3603
|
+
}
|
|
3604
|
+
if (request.name === statusTool) {
|
|
3605
|
+
const state = this.graph.getState();
|
|
3606
|
+
return {
|
|
3607
|
+
content: [
|
|
3608
|
+
{
|
|
3609
|
+
type: "text",
|
|
3610
|
+
text: JSON.stringify({
|
|
3611
|
+
currentAgentId: state.currentAgentId,
|
|
3612
|
+
stepsCompleted: state.stepsCompleted,
|
|
3613
|
+
finished: state.finished,
|
|
3614
|
+
error: state.error,
|
|
3615
|
+
nodeStates: Object.fromEntries(state.nodeStates)
|
|
3616
|
+
}, null, 2)
|
|
3617
|
+
}
|
|
3618
|
+
]
|
|
3619
|
+
};
|
|
3620
|
+
}
|
|
3621
|
+
return {
|
|
3622
|
+
isError: true,
|
|
3623
|
+
content: [{ type: "text", text: `\u672A\u77E5\u5DE5\u5177: ${request.name}` }]
|
|
3624
|
+
};
|
|
3625
|
+
} catch (err) {
|
|
3626
|
+
return {
|
|
3627
|
+
isError: true,
|
|
3628
|
+
content: [
|
|
3629
|
+
{
|
|
3630
|
+
type: "text",
|
|
3631
|
+
text: `\u6267\u884C\u9519\u8BEF: ${err instanceof Error ? err.message : String(err)}`
|
|
3632
|
+
}
|
|
3633
|
+
]
|
|
3634
|
+
};
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
};
|
|
3638
|
+
function createMCPBridge(graph, opts) {
|
|
3639
|
+
return new MCPBridge(graph, opts);
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3642
|
+
// extensions/debug.ts
|
|
3643
|
+
var GraphDebugger = class {
|
|
3644
|
+
graph;
|
|
3645
|
+
nodes = /* @__PURE__ */ new Map();
|
|
3646
|
+
edges = [];
|
|
3647
|
+
entryId;
|
|
3648
|
+
constructor(graph, nodes, edges, entryId) {
|
|
3649
|
+
this.graph = graph;
|
|
3650
|
+
if (nodes) {
|
|
3651
|
+
for (const node of nodes) {
|
|
3652
|
+
this.nodes.set(node.id, node);
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
if (edges) {
|
|
3656
|
+
this.edges = edges;
|
|
3657
|
+
}
|
|
3658
|
+
this.entryId = entryId;
|
|
3659
|
+
}
|
|
3660
|
+
/** 设置图元数据(供无法直接访问内部结构的场景使用) */
|
|
3661
|
+
setGraphMeta(nodes, edges, entryId) {
|
|
3662
|
+
this.nodes.clear();
|
|
3663
|
+
for (const node of nodes) {
|
|
3664
|
+
this.nodes.set(node.id, node);
|
|
3665
|
+
}
|
|
3666
|
+
this.edges = edges;
|
|
3667
|
+
this.entryId = entryId;
|
|
3668
|
+
}
|
|
3669
|
+
/** 导出为 DOT 格式 */
|
|
3670
|
+
toDOT() {
|
|
3671
|
+
const lines = [];
|
|
3672
|
+
lines.push("digraph AgentGraph {");
|
|
3673
|
+
lines.push(" rankdir=LR;");
|
|
3674
|
+
lines.push(' node [shape=box, style=filled, fillcolor="#e8f4fd", fontname="Arial"];');
|
|
3675
|
+
lines.push(' edge [fontname="Arial", fontsize=10];');
|
|
3676
|
+
lines.push("");
|
|
3677
|
+
for (const [id, node] of this.nodes) {
|
|
3678
|
+
const isEntry = id === this.entryId;
|
|
3679
|
+
const fillColor = isEntry ? "#4CAF50" : "#e8f4fd";
|
|
3680
|
+
const fontColor = isEntry ? "white" : "black";
|
|
3681
|
+
const label = `${node.name}\\n(${id})`;
|
|
3682
|
+
lines.push(` "${id}" [label="${label}", fillcolor="${fillColor}", fontcolor="${fontColor}"];`);
|
|
3683
|
+
}
|
|
3684
|
+
lines.push("");
|
|
3685
|
+
for (const edge of this.edges) {
|
|
3686
|
+
const hasCondition = !!edge.condition;
|
|
3687
|
+
const style = hasCondition ? "dashed" : "solid";
|
|
3688
|
+
const label = hasCondition ? "\u6761\u4EF6" : "";
|
|
3689
|
+
lines.push(` "${edge.from}" -> "${edge.to}" [style=${style}, label="${label}"];`);
|
|
3690
|
+
}
|
|
3691
|
+
lines.push("}");
|
|
3692
|
+
return lines.join("\n");
|
|
3693
|
+
}
|
|
3694
|
+
/** 执行图并记录 Trace */
|
|
3695
|
+
async trace(input) {
|
|
3696
|
+
const traceId = `trace-${Date.now()}`;
|
|
3697
|
+
const graphStartTime = Date.now();
|
|
3698
|
+
const steps = [];
|
|
3699
|
+
let stepIndex = 0;
|
|
3700
|
+
for await (const event of this.graph.stream(input)) {
|
|
3701
|
+
if (event.type === "agent_start") {
|
|
3702
|
+
stepIndex++;
|
|
3703
|
+
steps.push({
|
|
3704
|
+
step: stepIndex,
|
|
3705
|
+
agentId: event.agentId,
|
|
3706
|
+
agentName: event.agentName,
|
|
3707
|
+
startTime: Date.now(),
|
|
3708
|
+
duration: 0,
|
|
3709
|
+
input: "",
|
|
3710
|
+
output: "",
|
|
3711
|
+
state: "running"
|
|
3712
|
+
});
|
|
3713
|
+
} else if (event.type === "agent_result") {
|
|
3714
|
+
const step = this.findLastRunning(steps, event.agentId);
|
|
3715
|
+
if (step) {
|
|
3716
|
+
step.duration = Date.now() - step.startTime;
|
|
3717
|
+
step.output = this.truncate(event.result.content, 200);
|
|
3718
|
+
step.state = "completed";
|
|
3719
|
+
} else {
|
|
3720
|
+
const fallback = this.findLastById(steps, event.agentId);
|
|
3721
|
+
if (fallback) {
|
|
3722
|
+
fallback.duration = Date.now() - fallback.startTime;
|
|
3723
|
+
fallback.output = this.truncate(event.result.content, 200);
|
|
3724
|
+
fallback.state = "completed";
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
} else if (event.type === "agent_error") {
|
|
3728
|
+
const step = this.findLastRunning(steps, event.agentId);
|
|
3729
|
+
if (step) {
|
|
3730
|
+
step.duration = Date.now() - step.startTime;
|
|
3731
|
+
step.error = event.error;
|
|
3732
|
+
step.state = "failed";
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
const state = this.graph.getState();
|
|
3737
|
+
const graphDuration = Date.now() - graphStartTime;
|
|
3738
|
+
return {
|
|
3739
|
+
traceId,
|
|
3740
|
+
startTime: graphStartTime,
|
|
3741
|
+
duration: graphDuration,
|
|
3742
|
+
steps,
|
|
3743
|
+
result: {
|
|
3744
|
+
success: !state.error,
|
|
3745
|
+
content: state.error ?? "",
|
|
3746
|
+
stopReason: state.error ? "error" : "completed",
|
|
3747
|
+
stepsCompleted: state.stepsCompleted
|
|
3748
|
+
}
|
|
3749
|
+
};
|
|
3750
|
+
}
|
|
3751
|
+
/** 将 Trace 转为 JSON 字符串 */
|
|
3752
|
+
traceToJSON(trace) {
|
|
3753
|
+
return JSON.stringify(trace, null, 2);
|
|
3754
|
+
}
|
|
3755
|
+
/** 将 Trace 转为可读的执行日志 */
|
|
3756
|
+
traceToLog(trace) {
|
|
3757
|
+
const lines = [];
|
|
3758
|
+
lines.push(`=== Graph Trace: ${trace.traceId} ===`);
|
|
3759
|
+
lines.push(`Duration: ${trace.duration}ms`);
|
|
3760
|
+
lines.push("");
|
|
3761
|
+
for (const step of trace.steps) {
|
|
3762
|
+
const status = step.state === "completed" ? "OK" : step.state === "failed" ? "FAIL" : step.state;
|
|
3763
|
+
lines.push(`[${step.step}] ${step.agentName} (${step.agentId}) - ${status} (${step.duration}ms)`);
|
|
3764
|
+
if (step.input) lines.push(` Input: ${this.truncate(step.input, 100)}`);
|
|
3765
|
+
if (step.output) lines.push(` Output: ${this.truncate(step.output, 100)}`);
|
|
3766
|
+
if (step.error) lines.push(` Error: ${step.error}`);
|
|
3767
|
+
lines.push("");
|
|
3768
|
+
}
|
|
3769
|
+
lines.push(`Result: ${trace.result.success ? "SUCCESS" : "FAILED"} (${trace.result.stopReason})`);
|
|
3770
|
+
return lines.join("\n");
|
|
3771
|
+
}
|
|
3772
|
+
truncate(text, maxLength) {
|
|
3773
|
+
if (text.length <= maxLength) return text;
|
|
3774
|
+
return text.slice(0, maxLength) + "...";
|
|
3775
|
+
}
|
|
3776
|
+
/** 找最后一个匹配 agentId 且状态为 running 的 step */
|
|
3777
|
+
findLastRunning(steps, agentId) {
|
|
3778
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
3779
|
+
if (steps[i].agentId === agentId && steps[i].state === "running") {
|
|
3780
|
+
return steps[i];
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
return void 0;
|
|
3784
|
+
}
|
|
3785
|
+
/** 找最后一个匹配 agentId 的 step(不管状态) */
|
|
3786
|
+
findLastById(steps, agentId) {
|
|
3787
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
3788
|
+
if (steps[i].agentId === agentId) {
|
|
3789
|
+
return steps[i];
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
return void 0;
|
|
3793
|
+
}
|
|
3794
|
+
};
|
|
3795
|
+
function createDebugger(graph) {
|
|
3796
|
+
return new GraphDebugger(graph);
|
|
3797
|
+
}
|
|
3798
|
+
export {
|
|
3799
|
+
GraphDebugger,
|
|
3800
|
+
MCPBridge,
|
|
3801
|
+
SimpleEventBus,
|
|
3802
|
+
SimpleToolRegistry,
|
|
3803
|
+
createAgentGraph,
|
|
3804
|
+
createDebate,
|
|
3805
|
+
createDebugger,
|
|
3806
|
+
createMCPBridge,
|
|
3807
|
+
createMapReduce,
|
|
3808
|
+
createPipeline,
|
|
3809
|
+
createRouter,
|
|
3810
|
+
createSharedContext,
|
|
3811
|
+
createSupervisor
|
|
3812
|
+
};
|
|
3813
|
+
//# sourceMappingURL=index.js.map
|