@aipack-ai/multi-agent 1.1.6 → 1.1.8
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 +87 -5
- package/dist/index.js +2091 -1354
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -57,6 +57,10 @@ function createSharedContext(init) {
|
|
|
57
57
|
meta: init?.meta ?? {}
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
+
function storeOriginalInput(ctx, input) {
|
|
61
|
+
const inputText = typeof input === "string" ? input : input.message;
|
|
62
|
+
ctx.blackboard.set("__original_input__", inputText);
|
|
63
|
+
}
|
|
60
64
|
|
|
61
65
|
// ../agent/core/types.ts
|
|
62
66
|
function extractText(content) {
|
|
@@ -74,7 +78,50 @@ function createEmptyUsage() {
|
|
|
74
78
|
return { input: 0, output: 0, total: 0 };
|
|
75
79
|
}
|
|
76
80
|
|
|
81
|
+
// ../agent/core/tokens.ts
|
|
82
|
+
function estimateTextTokens(text) {
|
|
83
|
+
if (!text) return 0;
|
|
84
|
+
return Math.ceil(text.length / 4);
|
|
85
|
+
}
|
|
86
|
+
function estimateMessageTokens(message) {
|
|
87
|
+
const content = message.content;
|
|
88
|
+
if (typeof content === "string") return estimateTextTokens(content);
|
|
89
|
+
try {
|
|
90
|
+
return Math.ceil(JSON.stringify(content ?? []).length / 4);
|
|
91
|
+
} catch {
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ../agent/core/errors.ts
|
|
97
|
+
var AgentErrorCategory = {
|
|
98
|
+
RETRYABLE: "retryable",
|
|
99
|
+
TIMEOUT: "timeout",
|
|
100
|
+
AUTH: "auth",
|
|
101
|
+
CONTEXT_OVERFLOW: "context-overflow",
|
|
102
|
+
RATE_LIMIT: "rate-limit",
|
|
103
|
+
INVALID_REQUEST: "invalid-request",
|
|
104
|
+
UNKNOWN: "unknown"
|
|
105
|
+
};
|
|
106
|
+
|
|
77
107
|
// ../agent/core/tapable.ts
|
|
108
|
+
var tapFailurePolicy = "log";
|
|
109
|
+
var tapErrorHandler;
|
|
110
|
+
function setTapErrorHandler(handler) {
|
|
111
|
+
tapErrorHandler = handler;
|
|
112
|
+
}
|
|
113
|
+
function handleTapError(hook, tap, err) {
|
|
114
|
+
if (tapErrorHandler) {
|
|
115
|
+
try {
|
|
116
|
+
tapErrorHandler({ hook, tap: tap.name, error: err });
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (tapFailurePolicy === "throw") throw err;
|
|
121
|
+
if (tapFailurePolicy === "log") {
|
|
122
|
+
console.warn(`[aipack] extension tap "${tap.name}" on hook "${hook}" \u5931\u8D25:`, err);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
78
125
|
var AsyncSeriesHook = class {
|
|
79
126
|
constructor(name) {
|
|
80
127
|
this.name = name;
|
|
@@ -90,6 +137,7 @@ var AsyncSeriesHook = class {
|
|
|
90
137
|
try {
|
|
91
138
|
await tap.fn(...args);
|
|
92
139
|
} catch (err) {
|
|
140
|
+
handleTapError(this.name, tap, err);
|
|
93
141
|
}
|
|
94
142
|
}
|
|
95
143
|
}
|
|
@@ -116,6 +164,7 @@ var AsyncSeriesWaterfallHook = class {
|
|
|
116
164
|
try {
|
|
117
165
|
current = await tap.fn(current, ...rest);
|
|
118
166
|
} catch (err) {
|
|
167
|
+
handleTapError(this.name, tap, err);
|
|
119
168
|
}
|
|
120
169
|
}
|
|
121
170
|
return current;
|
|
@@ -602,15 +651,6 @@ function resourcesToMessages(resources) {
|
|
|
602
651
|
import { Type } from "@sinclair/typebox";
|
|
603
652
|
|
|
604
653
|
// ../agent/ai/errors.ts
|
|
605
|
-
var AgentErrorCategory = {
|
|
606
|
-
RETRYABLE: "retryable",
|
|
607
|
-
TIMEOUT: "timeout",
|
|
608
|
-
AUTH: "auth",
|
|
609
|
-
CONTEXT_OVERFLOW: "context-overflow",
|
|
610
|
-
RATE_LIMIT: "rate-limit",
|
|
611
|
-
INVALID_REQUEST: "invalid-request",
|
|
612
|
-
UNKNOWN: "unknown"
|
|
613
|
-
};
|
|
614
654
|
var RETRYABLE_CATEGORIES = /* @__PURE__ */ new Set([
|
|
615
655
|
AgentErrorCategory.RETRYABLE,
|
|
616
656
|
AgentErrorCategory.RATE_LIMIT
|
|
@@ -798,62 +838,7 @@ function isContextOverflow(message, contextWindow) {
|
|
|
798
838
|
return false;
|
|
799
839
|
}
|
|
800
840
|
|
|
801
|
-
// ../agent/
|
|
802
|
-
function ensureToolPairing(messages) {
|
|
803
|
-
const toolCallIds = /* @__PURE__ */ new Set();
|
|
804
|
-
const toolResultIds = /* @__PURE__ */ new Set();
|
|
805
|
-
for (const msg of messages) {
|
|
806
|
-
if (msg.role === "assistant") {
|
|
807
|
-
const content = msg.content;
|
|
808
|
-
if (Array.isArray(content)) {
|
|
809
|
-
for (const tc of extractToolCalls(content)) {
|
|
810
|
-
toolCallIds.add(tc.id);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
if (msg.role === "toolResult") {
|
|
815
|
-
toolResultIds.add(msg.toolCallId);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
const orphanedCalls = /* @__PURE__ */ new Set();
|
|
819
|
-
const orphanedResults = /* @__PURE__ */ new Set();
|
|
820
|
-
for (const id of toolCallIds) {
|
|
821
|
-
if (!toolResultIds.has(id)) orphanedCalls.add(id);
|
|
822
|
-
}
|
|
823
|
-
for (const id of toolResultIds) {
|
|
824
|
-
if (!toolCallIds.has(id)) orphanedResults.add(id);
|
|
825
|
-
}
|
|
826
|
-
if (orphanedCalls.size === 0 && orphanedResults.size === 0) {
|
|
827
|
-
return messages;
|
|
828
|
-
}
|
|
829
|
-
const result = [];
|
|
830
|
-
for (const msg of messages) {
|
|
831
|
-
if (msg.role === "assistant") {
|
|
832
|
-
const content = msg.content;
|
|
833
|
-
if (Array.isArray(content)) {
|
|
834
|
-
const filteredContent = content.filter((block) => {
|
|
835
|
-
if (block.type === "toolCall") {
|
|
836
|
-
return !orphanedCalls.has(block.id);
|
|
837
|
-
}
|
|
838
|
-
return true;
|
|
839
|
-
});
|
|
840
|
-
if (filteredContent.length === 0 && content.length > 0) continue;
|
|
841
|
-
result.push({ ...msg, content: filteredContent });
|
|
842
|
-
} else {
|
|
843
|
-
result.push(msg);
|
|
844
|
-
}
|
|
845
|
-
} else if (msg.role === "toolResult") {
|
|
846
|
-
if (!orphanedResults.has(msg.toolCallId)) {
|
|
847
|
-
result.push(msg);
|
|
848
|
-
}
|
|
849
|
-
} else {
|
|
850
|
-
result.push(msg);
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
return result;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
// ../agent/runtime/index.ts
|
|
841
|
+
// ../agent/runtime/shared.ts
|
|
857
842
|
import { randomUUID } from "crypto";
|
|
858
843
|
function newTraceId() {
|
|
859
844
|
return `${Date.now().toString(36)}-${randomUUID()}`;
|
|
@@ -925,19 +910,25 @@ function buildImageContent(media) {
|
|
|
925
910
|
}
|
|
926
911
|
return { type: "image", mimeType: "image/url", data: media };
|
|
927
912
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
913
|
+
function buildUserMessage(request) {
|
|
914
|
+
if (request.media && request.media.length > 0) {
|
|
915
|
+
const blocks = [
|
|
916
|
+
createTextContent(request.message),
|
|
917
|
+
...request.media.filter(Boolean).map(buildImageContent)
|
|
918
|
+
];
|
|
919
|
+
return {
|
|
920
|
+
role: "user",
|
|
921
|
+
content: blocks,
|
|
922
|
+
timestamp: Date.now()
|
|
923
|
+
};
|
|
936
924
|
}
|
|
925
|
+
return {
|
|
926
|
+
role: "user",
|
|
927
|
+
content: request.message,
|
|
928
|
+
timestamp: Date.now()
|
|
929
|
+
};
|
|
937
930
|
}
|
|
938
|
-
|
|
939
|
-
return Math.ceil(text.length / 4);
|
|
940
|
-
}
|
|
931
|
+
var OVERFLOW_RECOVERY_LIMIT = 2;
|
|
941
932
|
var COMPACTION_SUMMARY_BUDGET_RATIO = 0.6;
|
|
942
933
|
var COMPACTION_LINE_CLAMP = 4e3;
|
|
943
934
|
var DEFAULT_COMPACTION_PROMPT = [
|
|
@@ -988,150 +979,146 @@ function messageToSummaryLine(msg) {
|
|
|
988
979
|
}
|
|
989
980
|
}
|
|
990
981
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
_extensionContext;
|
|
1011
|
-
_maxTurns;
|
|
1012
|
-
_toolTimeoutMs;
|
|
1013
|
-
_parallelToolCalls;
|
|
1014
|
-
_contextBudgetRatio;
|
|
1015
|
-
/** 内置摘要压缩配置(未配置 = 保持旧行为,仅硬截断兜底) */
|
|
1016
|
-
_compaction;
|
|
1017
|
-
_telemetry;
|
|
1018
|
-
/** 框架级工具权限策略(未配置 → 放行,向后兼容) */
|
|
1019
|
-
_permissionPolicy;
|
|
1020
|
-
/** 审批管理器(pending 决策挂起等待外部批准;未配置 → pending 视为 deny) */
|
|
1021
|
-
_approvals;
|
|
1022
|
-
/** 审批等待超时(毫秒) */
|
|
1023
|
-
_approvalTimeoutMs;
|
|
1024
|
-
/** traceId 生成器(测试可注入确定性 id) */
|
|
1025
|
-
_traceIdGenerator;
|
|
1026
|
-
constructor(options) {
|
|
1027
|
-
this._config = options.config ?? {};
|
|
1028
|
-
this._extensions = new ExtensionManager();
|
|
1029
|
-
this._hooks = this._extensions.getHooks();
|
|
1030
|
-
this._transformers = [...options.transformers ?? []];
|
|
1031
|
-
this._model = options.model ?? {
|
|
1032
|
-
id: "unknown",
|
|
1033
|
-
name: "unknown",
|
|
1034
|
-
provider: "unknown",
|
|
1035
|
-
contextWindow: 128e3,
|
|
1036
|
-
maxTokens: 8192,
|
|
1037
|
-
reasoning: false
|
|
1038
|
-
};
|
|
1039
|
-
this._streamFn = options.streamFn ?? (async function* () {
|
|
1040
|
-
throw new Error("streamFn \u672A\u8BBE\u7F6E\uFF0C\u8BF7\u901A\u8FC7 setStreamFn() \u6216 RuntimeOptions.streamFn \u63D0\u4F9B");
|
|
1041
|
-
});
|
|
1042
|
-
this._systemPrompt = options.systemPrompt ?? "";
|
|
1043
|
-
this._thinkingLevel = options.thinkingLevel ?? "off";
|
|
1044
|
-
this._sessionStorage = options.sessionStorage;
|
|
1045
|
-
this._maxTurns = options.maxTurns ?? 50;
|
|
1046
|
-
this._toolTimeoutMs = options.toolTimeoutMs ?? 12e4;
|
|
1047
|
-
this._parallelToolCalls = options.parallelToolCalls ?? true;
|
|
1048
|
-
this._contextBudgetRatio = options.contextBudgetRatio ?? 0.8;
|
|
1049
|
-
this._compaction = options.compaction;
|
|
1050
|
-
this._telemetry = options.telemetry;
|
|
1051
|
-
this._permissionPolicy = options.permissionPolicy;
|
|
1052
|
-
this._approvals = options.approvals;
|
|
1053
|
-
this._approvalTimeoutMs = options.approvalTimeoutMs ?? 3e5;
|
|
1054
|
-
this._traceIdGenerator = options.traceIdGenerator;
|
|
1055
|
-
if (options.tools) {
|
|
1056
|
-
for (const tool of options.tools) {
|
|
1057
|
-
this._globalTools.set(tool.name, tool);
|
|
982
|
+
function buildResult(compilation) {
|
|
983
|
+
const messages = compilation.messages;
|
|
984
|
+
let content = "";
|
|
985
|
+
let stopReason = "completed";
|
|
986
|
+
let error;
|
|
987
|
+
const toolsUsed = [];
|
|
988
|
+
const usage = {};
|
|
989
|
+
for (const msg of messages) {
|
|
990
|
+
if (msg.role === "assistant") {
|
|
991
|
+
const assistant = msg;
|
|
992
|
+
content = extractText(assistant.content);
|
|
993
|
+
stopReason = assistant.stopReason ?? "completed";
|
|
994
|
+
error = assistant.errorMessage;
|
|
995
|
+
if (assistant.usage) {
|
|
996
|
+
usage.input = (usage.input ?? 0) + assistant.usage.input;
|
|
997
|
+
usage.output = (usage.output ?? 0) + assistant.usage.output;
|
|
998
|
+
usage.total = (usage.total ?? 0) + assistant.usage.total;
|
|
999
|
+
if (assistant.usage.cacheRead) usage.cacheRead = (usage.cacheRead ?? 0) + assistant.usage.cacheRead;
|
|
1000
|
+
if (assistant.usage.cacheWrite) usage.cacheWrite = (usage.cacheWrite ?? 0) + assistant.usage.cacheWrite;
|
|
1058
1001
|
}
|
|
1059
1002
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
static create(options = {}) {
|
|
1066
|
-
const runtime = new _AgentRuntime(options);
|
|
1067
|
-
if (options.extensions) {
|
|
1068
|
-
runtime._extensions.registerAll(options.extensions);
|
|
1003
|
+
if (msg.role === "toolResult") {
|
|
1004
|
+
const toolMsg = msg;
|
|
1005
|
+
if (!toolsUsed.includes(toolMsg.toolName)) {
|
|
1006
|
+
toolsUsed.push(toolMsg.toolName);
|
|
1007
|
+
}
|
|
1069
1008
|
}
|
|
1070
|
-
const ctx = {
|
|
1071
|
-
config: runtime._config,
|
|
1072
|
-
workspace: options.workspace ?? process.cwd(),
|
|
1073
|
-
sessionKey: runtime._sessionKey,
|
|
1074
|
-
shared: /* @__PURE__ */ new Map(),
|
|
1075
|
-
runtime
|
|
1076
|
-
};
|
|
1077
|
-
runtime._extensionContext = ctx;
|
|
1078
|
-
runtime._extensions.applyAll(ctx);
|
|
1079
|
-
return runtime;
|
|
1080
1009
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1010
|
+
const resources = messagesToResources(messages);
|
|
1011
|
+
const builder = new ResultBuilder().content(content).toolsUsed(toolsUsed).usage(usage).stopReason(stopReason).error(error).resources(resources).metadata("traceId", compilation.traceId);
|
|
1012
|
+
if (compilation.terminateReason) {
|
|
1013
|
+
builder.stopReason("terminated").metadata("terminateReason", compilation.terminateReason);
|
|
1084
1014
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1015
|
+
if (compilation.maxTurnsExhausted) {
|
|
1016
|
+
builder.stopReason("max_turns").metadata("maxTurns", true);
|
|
1087
1017
|
}
|
|
1088
|
-
|
|
1089
|
-
|
|
1018
|
+
return builder.build();
|
|
1019
|
+
}
|
|
1020
|
+
function streamEventToChunk(event) {
|
|
1021
|
+
switch (event.type) {
|
|
1022
|
+
case "text_delta":
|
|
1023
|
+
return { type: "text", content: event.delta };
|
|
1024
|
+
case "thinking_delta":
|
|
1025
|
+
return { type: "thinking", content: event.delta };
|
|
1026
|
+
case "error":
|
|
1027
|
+
return {
|
|
1028
|
+
type: "error",
|
|
1029
|
+
content: event.message.errorMessage || "\u6A21\u578B\u8C03\u7528\u51FA\u9519"
|
|
1030
|
+
};
|
|
1031
|
+
default:
|
|
1032
|
+
return null;
|
|
1090
1033
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// ../agent/runtime/session-store.ts
|
|
1037
|
+
var SessionStore = class {
|
|
1038
|
+
/** 多会话状态表:key = sessionKey。模型/工具/扩展/转换器等资源跨会话共享 */
|
|
1039
|
+
_sessions;
|
|
1040
|
+
/** 内存会话状态表 LRU 上限 */
|
|
1041
|
+
_maxSessions;
|
|
1042
|
+
/** 默认会话 key(常量 'default';请求未指定 sessionKey 时路由到此会话) */
|
|
1043
|
+
defaultKey;
|
|
1044
|
+
constructor(defaultKey, maxSessions = DEFAULT_MAX_SESSIONS) {
|
|
1045
|
+
this.defaultKey = defaultKey;
|
|
1046
|
+
this._maxSessions = maxSessions;
|
|
1047
|
+
this._sessions = /* @__PURE__ */ new Map();
|
|
1048
|
+
this._sessions.set(defaultKey, createSessionState());
|
|
1098
1049
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1050
|
+
/**
|
|
1051
|
+
* 获取(懒创建)会话状态。同一 Runtime 下不同 sessionKey 的消息历史、
|
|
1052
|
+
* 串行队列、abort 控制相互独立;共享模型/工具/扩展/转换器。
|
|
1053
|
+
* 超过 maxSessions 时淘汰最久未用的非活动会话(仅清内存态,不删存储)。
|
|
1054
|
+
*/
|
|
1055
|
+
ensure(key) {
|
|
1056
|
+
let session = this._sessions.get(key);
|
|
1057
|
+
if (session) {
|
|
1058
|
+
this._sessions.delete(key);
|
|
1059
|
+
this._sessions.set(key, session);
|
|
1060
|
+
return session;
|
|
1102
1061
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
return
|
|
1107
|
-
}
|
|
1108
|
-
setModel(model) {
|
|
1109
|
-
this._model = model;
|
|
1110
|
-
return this;
|
|
1062
|
+
session = createSessionState();
|
|
1063
|
+
this._sessions.set(key, session);
|
|
1064
|
+
this.evictIdleSessions();
|
|
1065
|
+
return session;
|
|
1111
1066
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
return this;
|
|
1067
|
+
/** 只读访问(不创建、不刷新 LRU;会话不存在返回 undefined) */
|
|
1068
|
+
peek(key) {
|
|
1069
|
+
return this._sessions.get(key);
|
|
1115
1070
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1071
|
+
/** LRU 淘汰:仅淘汰非活动(未运行、未排队)的最久未用会话 */
|
|
1072
|
+
evictIdleSessions() {
|
|
1073
|
+
if (this._sessions.size <= this._maxSessions) return;
|
|
1074
|
+
for (const [key, session] of this._sessions) {
|
|
1075
|
+
if (this._sessions.size <= this._maxSessions) break;
|
|
1076
|
+
if (session.isStreaming || session.lockHeld) continue;
|
|
1077
|
+
if (session.idleResolvers.length > 0) {
|
|
1078
|
+
const resolvers = session.idleResolvers.splice(0);
|
|
1079
|
+
for (const resolve of resolvers) resolve();
|
|
1080
|
+
}
|
|
1081
|
+
this._sessions.delete(key);
|
|
1082
|
+
}
|
|
1119
1083
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
return this;
|
|
1084
|
+
/** 当前活跃的会话 key 列表(含默认会话) */
|
|
1085
|
+
keys() {
|
|
1086
|
+
return Array.from(this._sessions.keys());
|
|
1123
1087
|
}
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
return this;
|
|
1088
|
+
/** 某会话是否存在(内存中) */
|
|
1089
|
+
has(key) {
|
|
1090
|
+
return this._sessions.has(key);
|
|
1127
1091
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1092
|
+
/**
|
|
1093
|
+
* 获取同一会话的执行锁:返回 release 函数,调用后释放。
|
|
1094
|
+
* 同一 sessionKey 的 run/stream 会串行执行,避免消息数组交错、
|
|
1095
|
+
* abortController 互相覆盖、hydrate 竞态。
|
|
1096
|
+
*/
|
|
1097
|
+
async acquire(session) {
|
|
1098
|
+
let release;
|
|
1099
|
+
const prev = session.queue;
|
|
1100
|
+
session.queue = new Promise((resolve) => {
|
|
1101
|
+
release = () => resolve();
|
|
1102
|
+
});
|
|
1103
|
+
await prev;
|
|
1104
|
+
session.lockHeld = true;
|
|
1105
|
+
return () => {
|
|
1106
|
+
session.lockHeld = false;
|
|
1107
|
+
release();
|
|
1108
|
+
};
|
|
1131
1109
|
}
|
|
1132
|
-
/**
|
|
1133
|
-
|
|
1134
|
-
|
|
1110
|
+
/** 标记会话空闲并唤醒所有 waitForIdle 等待者 */
|
|
1111
|
+
markIdle(session) {
|
|
1112
|
+
session.isStreaming = false;
|
|
1113
|
+
session.abortController = null;
|
|
1114
|
+
if (session.idleResolvers.length > 0) {
|
|
1115
|
+
const resolvers = session.idleResolvers.splice(0);
|
|
1116
|
+
for (const resolve of resolvers) resolve();
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
/** 指定会话的消息列表深拷贝(避免外部直接修改会话内部状态;不存在返回空数组) */
|
|
1120
|
+
getMessagesCopy(key) {
|
|
1121
|
+
const session = this._sessions.get(key);
|
|
1135
1122
|
if (!session) return [];
|
|
1136
1123
|
const messages = session.messages;
|
|
1137
1124
|
try {
|
|
@@ -1140,372 +1127,253 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
1140
1127
|
return JSON.parse(JSON.stringify(messages));
|
|
1141
1128
|
}
|
|
1142
1129
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1130
|
+
/**
|
|
1131
|
+
* 指定会话消息的只读视图(高频轮询用):仅浅拷贝数组(O(n) 引用,
|
|
1132
|
+
* 不做 structuredClone 深拷贝),消息对象与内部共享——调用方不得修改,
|
|
1133
|
+
* 需要可变副本时用 getMessagesCopy。
|
|
1134
|
+
*/
|
|
1135
|
+
peekMessages(key) {
|
|
1136
|
+
return this._sessions.get(key)?.messages.slice() ?? [];
|
|
1137
|
+
}
|
|
1138
|
+
/** 等待指定会话空闲(基于 promise,无轮询;会话不存在或非运行中直接返回) */
|
|
1139
|
+
async waitForIdle(key, timeoutMs) {
|
|
1140
|
+
const session = this._sessions.get(key);
|
|
1141
|
+
if (!session || !session.isStreaming) return;
|
|
1142
|
+
if (timeoutMs === void 0) {
|
|
1143
|
+
await new Promise((resolve) => {
|
|
1144
|
+
session.idleResolvers.push(resolve);
|
|
1145
|
+
});
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
await new Promise((resolve, reject) => {
|
|
1149
|
+
const resolver = () => {
|
|
1150
|
+
clearTimeout(timer);
|
|
1151
|
+
resolve();
|
|
1152
|
+
};
|
|
1153
|
+
const timer = setTimeout(() => {
|
|
1154
|
+
const i = session.idleResolvers.indexOf(resolver);
|
|
1155
|
+
if (i >= 0) session.idleResolvers.splice(i, 1);
|
|
1156
|
+
reject(new Error(
|
|
1157
|
+
`[Runtime] waitForIdle \u8D85\u65F6\uFF08${timeoutMs}ms\uFF09: ${key}`
|
|
1158
|
+
));
|
|
1159
|
+
}, timeoutMs);
|
|
1160
|
+
session.idleResolvers.push(resolver);
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
/** 清除指定会话消息(仅内存;下次 run 会从存储恢复) */
|
|
1164
|
+
clearMessages(key) {
|
|
1165
|
+
const session = this._sessions.get(key);
|
|
1166
|
+
if (!session) return;
|
|
1167
|
+
session.messages = [];
|
|
1168
|
+
session.hydrated = false;
|
|
1147
1169
|
}
|
|
1148
1170
|
/**
|
|
1149
|
-
*
|
|
1150
|
-
* 串行队列、abort 控制相互独立;共享模型/工具/扩展/转换器。
|
|
1151
|
-
* 超过 _maxSessions 时淘汰最久未用的非活动会话(仅清内存态,不删存储)。
|
|
1171
|
+
* 删除指定会话(仅内存态):等待在途任务完成后移除,返回被移除的会话。
|
|
1152
1172
|
*/
|
|
1153
|
-
|
|
1154
|
-
|
|
1173
|
+
async remove(key) {
|
|
1174
|
+
const session = this._sessions.get(key);
|
|
1155
1175
|
if (session) {
|
|
1176
|
+
await session.queue;
|
|
1156
1177
|
this._sessions.delete(key);
|
|
1157
|
-
this._sessions.set(key, session);
|
|
1158
|
-
return session;
|
|
1159
1178
|
}
|
|
1160
|
-
session = createSessionState();
|
|
1161
|
-
this._sessions.set(key, session);
|
|
1162
|
-
this.evictIdleSessions();
|
|
1163
1179
|
return session;
|
|
1164
1180
|
}
|
|
1165
|
-
/**
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
for (const
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
this._sessions.delete(key);
|
|
1181
|
+
/** 关闭用:等待所有会话的在途任务完成后清空状态表 */
|
|
1182
|
+
async drainAndClear() {
|
|
1183
|
+
await Promise.allSettled(Array.from(this._sessions.values(), (s) => s.queue));
|
|
1184
|
+
for (const session of this._sessions.values()) {
|
|
1185
|
+
session.messages = [];
|
|
1186
|
+
session.hydrated = false;
|
|
1172
1187
|
}
|
|
1188
|
+
this._sessions.clear();
|
|
1173
1189
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
// ../agent/runtime/session-persistence.ts
|
|
1193
|
+
var SessionPersistence = class {
|
|
1194
|
+
constructor(_storage, _defaultModel, _store) {
|
|
1195
|
+
this._storage = _storage;
|
|
1196
|
+
this._defaultModel = _defaultModel;
|
|
1197
|
+
this._store = _store;
|
|
1198
|
+
}
|
|
1199
|
+
_storage;
|
|
1200
|
+
_defaultModel;
|
|
1201
|
+
_store;
|
|
1202
|
+
get storage() {
|
|
1203
|
+
return this._storage;
|
|
1177
1204
|
}
|
|
1178
|
-
/**
|
|
1179
|
-
|
|
1180
|
-
|
|
1205
|
+
/** 从存储懒加载会话(串行化后无竞态,每个会话仅恢复一次) */
|
|
1206
|
+
async hydrate(sessionKey, session) {
|
|
1207
|
+
if (!this._storage) return;
|
|
1208
|
+
if (session.hydrated) return;
|
|
1209
|
+
session.hydrated = true;
|
|
1210
|
+
const stored = await this._storage.load(sessionKey);
|
|
1211
|
+
if (!stored) return;
|
|
1212
|
+
session.messages = stored.messages;
|
|
1213
|
+
session.createdAt = stored.createdAt;
|
|
1181
1214
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
if (!
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
const traceId = this.newTraceId();
|
|
1204
|
-
const queuedAt = Date.now();
|
|
1205
|
-
await this.emitTelemetry("onRunStart", {
|
|
1206
|
-
traceId,
|
|
1207
|
-
sessionKey,
|
|
1208
|
-
request: finalRequest,
|
|
1209
|
-
queuedAt
|
|
1210
|
-
});
|
|
1211
|
-
const session = this.getSession(sessionKey);
|
|
1212
|
-
const release = await this.acquire(session);
|
|
1213
|
-
const queuedMs = Date.now() - queuedAt;
|
|
1215
|
+
/** 整体保存指定会话(ephemeral 跳过;失败不影响运行结果) */
|
|
1216
|
+
async persist(sessionKey, session) {
|
|
1217
|
+
if (!this._storage) return;
|
|
1218
|
+
const stored = {
|
|
1219
|
+
key: sessionKey,
|
|
1220
|
+
version: SESSION_VERSION,
|
|
1221
|
+
messages: session.messages,
|
|
1222
|
+
model: this.deriveModel(session.messages),
|
|
1223
|
+
usage: this.sumUsage(session.messages),
|
|
1224
|
+
createdAt: session.createdAt,
|
|
1225
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1226
|
+
};
|
|
1227
|
+
await this._storage.save(sessionKey, stored);
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* 实时持久化指定会话:每轮 assistant 回复/工具结果完成后调用,
|
|
1231
|
+
* 让运行中的会话随时可被持久化数据观测到。ephemeral 跳过;
|
|
1232
|
+
* 存储失败仅告警,不影响对话循环继续。
|
|
1233
|
+
*/
|
|
1234
|
+
async persistSafe(request, sessionKey) {
|
|
1235
|
+
if (request.ephemeral || !this._storage) return;
|
|
1214
1236
|
try {
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
);
|
|
1220
|
-
} finally {
|
|
1221
|
-
release();
|
|
1237
|
+
const session = this._store.peek(sessionKey);
|
|
1238
|
+
if (!session) return;
|
|
1239
|
+
await this.persist(sessionKey, session);
|
|
1240
|
+
} catch (err) {
|
|
1241
|
+
console.warn("[Runtime] \u4F1A\u8BDD\u6301\u4E45\u5316\u5931\u8D25:", err?.message);
|
|
1222
1242
|
}
|
|
1223
1243
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
queuedMs: 0,
|
|
1235
|
-
turnCount: 0,
|
|
1236
|
-
result: new ResultBuilder().error(message).build(),
|
|
1237
|
-
success: false,
|
|
1238
|
-
errorClass: "validation",
|
|
1239
|
-
tokens: { input: 0, output: 0 }
|
|
1240
|
-
});
|
|
1241
|
-
yield { type: "error", content: message };
|
|
1242
|
-
yield { type: "done" };
|
|
1243
|
-
return;
|
|
1244
|
+
/** 从消息中推导最后使用的模型 */
|
|
1245
|
+
deriveModel(messages) {
|
|
1246
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1247
|
+
const msg = messages[i];
|
|
1248
|
+
if (msg.role === "assistant" && msg.model) {
|
|
1249
|
+
return {
|
|
1250
|
+
provider: msg.provider ?? this._defaultModel().provider,
|
|
1251
|
+
modelId: msg.model
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1244
1254
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
() => this._stream(finalRequest, sessionKey, session, traceId, queuedMs)
|
|
1263
|
-
);
|
|
1264
|
-
} finally {
|
|
1265
|
-
release();
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
/** 汇总所有 assistant 消息的 token 用量(含 cache) */
|
|
1258
|
+
sumUsage(messages) {
|
|
1259
|
+
const usage = createEmptyUsage();
|
|
1260
|
+
for (const msg of messages) {
|
|
1261
|
+
if (msg.role === "assistant") {
|
|
1262
|
+
const u = msg.usage;
|
|
1263
|
+
if (u) {
|
|
1264
|
+
usage.input += u.input;
|
|
1265
|
+
usage.output += u.output;
|
|
1266
|
+
usage.total += u.total;
|
|
1267
|
+
usage.cacheRead = (usage.cacheRead ?? 0) + (u.cacheRead ?? 0);
|
|
1268
|
+
usage.cacheWrite = (usage.cacheWrite ?? 0) + (u.cacheWrite ?? 0);
|
|
1269
|
+
usage.reasoning = (usage.reasoning ?? 0) + (u.reasoning ?? 0);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1266
1272
|
}
|
|
1273
|
+
return usage;
|
|
1267
1274
|
}
|
|
1268
1275
|
/**
|
|
1269
1276
|
* 非 ephemeral 请求在"读(load)-改(run)-写(save)"全程持有存储级锁,
|
|
1270
1277
|
* 防止多进程并发写同一会话导致 last-write-wins 丢消息。
|
|
1271
1278
|
* ephemeral / 无锁支持 / 无存储时直接执行。
|
|
1272
1279
|
*/
|
|
1273
|
-
async
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
return storage.withLock(sessionKey, fn);
|
|
1280
|
+
async withStorageLock(request, sessionKey, fn) {
|
|
1281
|
+
if (request.ephemeral || !this._storage?.withLock) return fn();
|
|
1282
|
+
return this._storage.withLock(sessionKey, fn);
|
|
1277
1283
|
}
|
|
1278
1284
|
/** 流式版本:无法用回调包住生成器,改用手动锁(acquire/release) */
|
|
1279
1285
|
async *streamWithStorageLock(request, sessionKey, gen) {
|
|
1280
|
-
|
|
1281
|
-
if (request.ephemeral || !storage?.acquireLock) {
|
|
1286
|
+
if (request.ephemeral || !this._storage?.acquireLock) {
|
|
1282
1287
|
yield* gen();
|
|
1283
1288
|
return;
|
|
1284
1289
|
}
|
|
1285
|
-
const lock = await
|
|
1290
|
+
const lock = await this._storage.acquireLock(sessionKey);
|
|
1286
1291
|
try {
|
|
1287
1292
|
yield* gen();
|
|
1288
1293
|
} finally {
|
|
1289
1294
|
await lock.release();
|
|
1290
1295
|
}
|
|
1291
1296
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1297
|
+
};
|
|
1298
|
+
|
|
1299
|
+
// ../agent/runtime/telemetry.ts
|
|
1300
|
+
var RuntimeTelemetry = class {
|
|
1301
|
+
constructor(_telemetry, _traceIdGenerator) {
|
|
1302
|
+
this._telemetry = _telemetry;
|
|
1303
|
+
this._traceIdGenerator = _traceIdGenerator;
|
|
1304
|
+
}
|
|
1305
|
+
_telemetry;
|
|
1306
|
+
_traceIdGenerator;
|
|
1307
|
+
/** traceId 生成:优先用注入的生成器(测试可确定性) */
|
|
1308
|
+
newTraceId() {
|
|
1309
|
+
return this._traceIdGenerator ? this._traceIdGenerator() : newTraceId();
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* 触发遥测回调。全可选、失败不阻断主流程。
|
|
1313
|
+
*/
|
|
1314
|
+
async emit(event, info) {
|
|
1315
|
+
const fn = this._telemetry?.[event];
|
|
1316
|
+
if (!fn) return;
|
|
1302
1317
|
try {
|
|
1303
|
-
await
|
|
1304
|
-
const result = this.buildResult(compilation);
|
|
1305
|
-
await this._hooks.beforeEmit.promise(result);
|
|
1306
|
-
await this._hooks.afterEmit.promise(result);
|
|
1307
|
-
await this._hooks.done.promise(result, finalRequest);
|
|
1308
|
-
compilation.completed = true;
|
|
1309
|
-
await this.emitRunEnd(finalRequest, sessionKey, compilation, result, queuedMs, activeStartedAt);
|
|
1310
|
-
return result;
|
|
1318
|
+
await Promise.resolve(fn(info));
|
|
1311
1319
|
} catch (err) {
|
|
1312
|
-
|
|
1313
|
-
if (error?.name !== "AbortError") {
|
|
1314
|
-
console.error("[Runtime] \u8FD0\u884C\u5931\u8D25:", error?.stack ?? error);
|
|
1315
|
-
}
|
|
1316
|
-
await this._hooks.failed.promise(error, finalRequest);
|
|
1317
|
-
const result = new ResultBuilder().error(error.message).build();
|
|
1318
|
-
await this.emitRunEnd(finalRequest, sessionKey, compilation, result, queuedMs, activeStartedAt);
|
|
1319
|
-
return result;
|
|
1320
|
-
} finally {
|
|
1321
|
-
await this.persistSessionSafe(finalRequest, sessionKey);
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
async *_stream(request, sessionKey, session, traceId, queuedMs) {
|
|
1325
|
-
const activeStartedAt = Date.now();
|
|
1326
|
-
await this._hooks.beforeInitialize.promise(request);
|
|
1327
|
-
await this._hooks.afterInitialize.promise(request);
|
|
1328
|
-
const finalRequest = await this._hooks.beforeRun.promise(request);
|
|
1329
|
-
if (!finalRequest.ephemeral) {
|
|
1330
|
-
await this.hydrateSession(sessionKey, session);
|
|
1331
|
-
}
|
|
1332
|
-
const compilation = this.createCompilation(finalRequest, sessionKey, session, traceId);
|
|
1333
|
-
compilation.messages.push(this.buildUserMessage(finalRequest));
|
|
1334
|
-
try {
|
|
1335
|
-
for await (const chunk of this.runLoopStream(compilation, finalRequest, session)) {
|
|
1336
|
-
yield chunk;
|
|
1337
|
-
}
|
|
1338
|
-
const result = this.buildResult(compilation);
|
|
1339
|
-
await this._hooks.beforeEmit.promise(result);
|
|
1340
|
-
await this._hooks.afterEmit.promise(result);
|
|
1341
|
-
await this._hooks.done.promise(result, finalRequest);
|
|
1342
|
-
yield { type: "done", result };
|
|
1343
|
-
await this.emitRunEnd(finalRequest, sessionKey, compilation, result, queuedMs, activeStartedAt);
|
|
1344
|
-
} catch (err) {
|
|
1345
|
-
const error = err;
|
|
1346
|
-
if (error?.name !== "AbortError") {
|
|
1347
|
-
console.error("[Runtime] \u6D41\u5F0F\u8FD0\u884C\u5931\u8D25:", error?.stack ?? error);
|
|
1348
|
-
}
|
|
1349
|
-
await this._hooks.failed.promise(error, finalRequest);
|
|
1350
|
-
const result = new ResultBuilder().error(error.message).build();
|
|
1351
|
-
yield { type: "error", content: error.message };
|
|
1352
|
-
yield { type: "done", result };
|
|
1353
|
-
await this.emitRunEnd(finalRequest, sessionKey, compilation, result, queuedMs, activeStartedAt);
|
|
1354
|
-
} finally {
|
|
1355
|
-
await this.persistSessionSafe(finalRequest, sessionKey);
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
createCompilation(request, sessionKey, session, traceId) {
|
|
1359
|
-
return {
|
|
1360
|
-
request,
|
|
1361
|
-
graph: createTaskGraph(),
|
|
1362
|
-
resources: [],
|
|
1363
|
-
messages: (session ?? this.getSession(sessionKey ?? this._sessionKey)).messages,
|
|
1364
|
-
completed: false,
|
|
1365
|
-
traceId: traceId ?? this.newTraceId()
|
|
1366
|
-
};
|
|
1367
|
-
}
|
|
1368
|
-
async close() {
|
|
1369
|
-
await Promise.allSettled(Array.from(this._sessions.values(), (s) => s.queue));
|
|
1370
|
-
for (const session of this._sessions.values()) {
|
|
1371
|
-
session.messages = [];
|
|
1372
|
-
session.hydrated = false;
|
|
1320
|
+
console.warn(`[aipack] telemetry "${String(event)}" \u4E0A\u62A5\u5931\u8D25:`, err);
|
|
1373
1321
|
}
|
|
1374
|
-
this._sessions.clear();
|
|
1375
|
-
this._extensions.clear();
|
|
1376
|
-
this._transformers = [];
|
|
1377
1322
|
}
|
|
1378
|
-
|
|
1379
|
-
async
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
const toolCalls = extractToolCalls(assistantMessage.content);
|
|
1402
|
-
if (toolCalls.length === 0) {
|
|
1403
|
-
break;
|
|
1404
|
-
}
|
|
1405
|
-
const outcome = await this.executeToolCalls(
|
|
1406
|
-
compilation,
|
|
1407
|
-
toolCalls,
|
|
1408
|
-
session.abortController.signal
|
|
1409
|
-
);
|
|
1410
|
-
await this.persistSessionSafe(request, sessionKey);
|
|
1411
|
-
if (outcome.terminate) {
|
|
1412
|
-
compilation.terminateReason = outcome.terminateReason ?? "terminated";
|
|
1413
|
-
break;
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
if (maxTurns < 0) compilation.maxTurnsExhausted = true;
|
|
1417
|
-
compilation.turnCount = turnCount;
|
|
1418
|
-
} finally {
|
|
1419
|
-
this.markIdle(session);
|
|
1420
|
-
}
|
|
1323
|
+
/** 组装并上报 run 级完成事件(run/stream 内部统一调用) */
|
|
1324
|
+
async emitRunEnd(request, sessionKey, compilation, result, queuedMs, activeStartedAt, defaultModelId) {
|
|
1325
|
+
const activeMs = Date.now() - activeStartedAt;
|
|
1326
|
+
await this.emit("onRunEnd", {
|
|
1327
|
+
traceId: compilation.traceId,
|
|
1328
|
+
sessionKey,
|
|
1329
|
+
// 请求未显式指定 model 时补实际模型(模型排行按 run 级 requests 统计,缺省会落入 'unknown')
|
|
1330
|
+
request: request.model ? request : { ...request, model: defaultModelId },
|
|
1331
|
+
durationMs: activeMs + queuedMs,
|
|
1332
|
+
activeMs,
|
|
1333
|
+
queuedMs,
|
|
1334
|
+
turnCount: compilation.turnCount ?? 0,
|
|
1335
|
+
result,
|
|
1336
|
+
success: result.success,
|
|
1337
|
+
errorClass: this.runErrorClass(compilation),
|
|
1338
|
+
tokens: {
|
|
1339
|
+
input: result.usage.input ?? 0,
|
|
1340
|
+
output: result.usage.output ?? 0,
|
|
1341
|
+
cacheRead: result.usage.cacheRead,
|
|
1342
|
+
cacheWrite: result.usage.cacheWrite
|
|
1343
|
+
},
|
|
1344
|
+
ttftMs: compilation.ttftMs
|
|
1345
|
+
});
|
|
1421
1346
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
await this.transformMessages(compilation, sessionKey);
|
|
1433
|
-
await this.maybeCompactByThreshold(
|
|
1434
|
-
compilation,
|
|
1435
|
-
sessionKey,
|
|
1436
|
-
session.abortController.signal
|
|
1437
|
-
);
|
|
1438
|
-
const turn = this.modelTurnWithRecovery(
|
|
1439
|
-
compilation,
|
|
1440
|
-
session.abortController.signal,
|
|
1441
|
-
sessionKey,
|
|
1442
|
-
true
|
|
1443
|
-
// stream 模式:同时统计首 token 延迟
|
|
1444
|
-
);
|
|
1445
|
-
let turnResult = await turn.next();
|
|
1446
|
-
while (!turnResult.done) {
|
|
1447
|
-
yield turnResult.value;
|
|
1448
|
-
turnResult = await turn.next();
|
|
1449
|
-
}
|
|
1450
|
-
const assistantMessage = turnResult.value;
|
|
1451
|
-
if (!assistantMessage) break;
|
|
1452
|
-
compilation.messages.push(assistantMessage);
|
|
1453
|
-
await this.persistSessionSafe(request, sessionKey);
|
|
1454
|
-
const toolCalls = extractToolCalls(assistantMessage.content);
|
|
1455
|
-
if (toolCalls.length === 0) {
|
|
1456
|
-
break;
|
|
1457
|
-
}
|
|
1458
|
-
for (const toolCall of toolCalls) {
|
|
1459
|
-
yield {
|
|
1460
|
-
type: "tool_start",
|
|
1461
|
-
toolName: toolCall.name,
|
|
1462
|
-
toolCallId: toolCall.id
|
|
1463
|
-
};
|
|
1464
|
-
}
|
|
1465
|
-
const outcome = await this.executeToolCalls(
|
|
1466
|
-
compilation,
|
|
1467
|
-
toolCalls,
|
|
1468
|
-
session.abortController.signal
|
|
1469
|
-
);
|
|
1470
|
-
await this.persistSessionSafe(request, sessionKey);
|
|
1471
|
-
for (let i = 0; i < toolCalls.length; i++) {
|
|
1472
|
-
yield {
|
|
1473
|
-
type: "tool_end",
|
|
1474
|
-
toolName: toolCalls[i].name,
|
|
1475
|
-
toolCallId: toolCalls[i].id,
|
|
1476
|
-
isError: this.isErrorResult(outcome.results[i])
|
|
1477
|
-
};
|
|
1478
|
-
}
|
|
1479
|
-
if (outcome.terminate) {
|
|
1480
|
-
compilation.terminateReason = outcome.terminateReason ?? "terminated";
|
|
1481
|
-
break;
|
|
1482
|
-
}
|
|
1347
|
+
/** run 级错误分类:terminate → 'terminated';否则只看最后一条 assistant 消息(与 buildResult 的 result.success 同口径) */
|
|
1348
|
+
runErrorClass(compilation) {
|
|
1349
|
+
if (compilation.terminateReason) return "terminated";
|
|
1350
|
+
const messages = compilation.messages;
|
|
1351
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1352
|
+
const m = messages[i];
|
|
1353
|
+
if (m.role === "assistant") {
|
|
1354
|
+
if (!m.errorMessage) return void 0;
|
|
1355
|
+
const cls = errorClassFromMessage(m.errorMessage);
|
|
1356
|
+
return cls ?? "unknown";
|
|
1483
1357
|
}
|
|
1484
|
-
if (maxTurns < 0) compilation.maxTurnsExhausted = true;
|
|
1485
|
-
compilation.turnCount = turnCount;
|
|
1486
|
-
} finally {
|
|
1487
|
-
this.markIdle(session);
|
|
1488
1358
|
}
|
|
1359
|
+
return void 0;
|
|
1489
1360
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
this.buildToolResultMessage(toolCalls[i], outcome.results[i])
|
|
1497
|
-
);
|
|
1498
|
-
}
|
|
1499
|
-
return outcome;
|
|
1361
|
+
};
|
|
1362
|
+
|
|
1363
|
+
// ../agent/runtime/tool-executor.ts
|
|
1364
|
+
var ToolExecutor = class {
|
|
1365
|
+
constructor(_d) {
|
|
1366
|
+
this._d = _d;
|
|
1500
1367
|
}
|
|
1368
|
+
_d;
|
|
1501
1369
|
/**
|
|
1502
1370
|
* 执行一组工具调用:parallelToolCalls 为 true 时并行,否则串行。
|
|
1503
1371
|
* 返回 ToolExecutionOutcome:任一工具请求 terminate 即终止整个 run;
|
|
1504
1372
|
* 串行模式下 terminate 后剩余工具生成 skipped 结果以保持配对完整。
|
|
1505
1373
|
*/
|
|
1506
|
-
async
|
|
1507
|
-
const execute = (tc) => this.
|
|
1508
|
-
if (this.
|
|
1374
|
+
async run(toolCalls, signal, request, traceId) {
|
|
1375
|
+
const execute = (tc) => this.execute(tc, signal, request, traceId);
|
|
1376
|
+
if (this._d.parallelToolCalls && toolCalls.length > 1) {
|
|
1509
1377
|
const outcomes = await Promise.all(toolCalls.map(execute));
|
|
1510
1378
|
const terminated = outcomes.find((o) => o.terminate);
|
|
1511
1379
|
return {
|
|
@@ -1531,8 +1399,8 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
1531
1399
|
}
|
|
1532
1400
|
return { results, terminate, terminateReason };
|
|
1533
1401
|
}
|
|
1534
|
-
async
|
|
1535
|
-
const tool = this.
|
|
1402
|
+
async execute(toolCall, signal, request, traceId) {
|
|
1403
|
+
const tool = this._d.getTools().get(toolCall.name);
|
|
1536
1404
|
if (!tool) {
|
|
1537
1405
|
return {
|
|
1538
1406
|
result: {
|
|
@@ -1546,27 +1414,43 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
1546
1414
|
if (tool.prepareArguments) {
|
|
1547
1415
|
args = tool.prepareArguments(toolCall.arguments);
|
|
1548
1416
|
}
|
|
1549
|
-
if (this.
|
|
1417
|
+
if (!this._d.permissionPolicy) {
|
|
1418
|
+
if (!this._d.permissionFailOpen) {
|
|
1419
|
+
const reason = `no permissionPolicy configured (fail-closed by default). Configure RuntimeOptions.permissionPolicy (e.g. createAllowAllPolicy()) or explicitly set permissionFailOpen: true to allow unrestricted tool execution`;
|
|
1420
|
+
await this._d.telemetry.emit("onPermissionDenied", {
|
|
1421
|
+
traceId,
|
|
1422
|
+
sessionKey: request.sessionKey ?? this._d.defaultSessionKey,
|
|
1423
|
+
toolName: toolCall.name,
|
|
1424
|
+
permissions: tool.permissions ?? [],
|
|
1425
|
+
args,
|
|
1426
|
+
reason: `permission denied for tool "${toolCall.name}": ${reason}`
|
|
1427
|
+
});
|
|
1428
|
+
return {
|
|
1429
|
+
result: this.makeBlockedResult(`permission denied for tool "${toolCall.name}": ${reason}`),
|
|
1430
|
+
terminate: false
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
} else {
|
|
1550
1434
|
const permissionReq = {
|
|
1551
1435
|
toolName: toolCall.name,
|
|
1552
1436
|
permissions: tool.permissions ?? [],
|
|
1553
1437
|
args,
|
|
1554
|
-
sessionKey: request.sessionKey ?? this.
|
|
1438
|
+
sessionKey: request.sessionKey ?? this._d.defaultSessionKey,
|
|
1555
1439
|
request,
|
|
1556
|
-
shared: this.
|
|
1440
|
+
shared: this._d.getShared() ?? /* @__PURE__ */ new Map()
|
|
1557
1441
|
};
|
|
1558
|
-
const decision = await this.
|
|
1442
|
+
const decision = await this._d.permissionPolicy.check(permissionReq);
|
|
1559
1443
|
let allowed = decision === "allow";
|
|
1560
1444
|
if (decision === "confirm") {
|
|
1561
|
-
allowed = this.
|
|
1445
|
+
allowed = this._d.permissionPolicy.confirm ? await this._d.permissionPolicy.confirm(permissionReq) : false;
|
|
1562
1446
|
}
|
|
1563
1447
|
if (decision === "pending") {
|
|
1564
|
-
if (this.
|
|
1565
|
-
const approval = this.
|
|
1566
|
-
timeoutMs: this.
|
|
1448
|
+
if (this._d.approvals) {
|
|
1449
|
+
const approval = this._d.approvals.create(permissionReq, {
|
|
1450
|
+
timeoutMs: this._d.approvalTimeoutMs,
|
|
1567
1451
|
signal
|
|
1568
1452
|
});
|
|
1569
|
-
await this.
|
|
1453
|
+
await this._d.telemetry.emit("onApprovalPending", {
|
|
1570
1454
|
traceId,
|
|
1571
1455
|
sessionKey: permissionReq.sessionKey,
|
|
1572
1456
|
approvalId: approval.id,
|
|
@@ -1575,9 +1459,9 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
1575
1459
|
args,
|
|
1576
1460
|
expiresAt: approval.expiresAt
|
|
1577
1461
|
});
|
|
1578
|
-
const outcome = await this.
|
|
1462
|
+
const outcome = await this._d.approvals.wait(approval);
|
|
1579
1463
|
allowed = outcome.status === "approved";
|
|
1580
|
-
await this.
|
|
1464
|
+
await this._d.telemetry.emit("onApprovalResolved", {
|
|
1581
1465
|
traceId,
|
|
1582
1466
|
sessionKey: permissionReq.sessionKey,
|
|
1583
1467
|
approvalId: approval.id,
|
|
@@ -1591,7 +1475,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
1591
1475
|
}
|
|
1592
1476
|
if (!allowed) {
|
|
1593
1477
|
const reason = `permission denied by policy for tool "${toolCall.name}"`;
|
|
1594
|
-
await this.
|
|
1478
|
+
await this._d.telemetry.emit("onPermissionDenied", {
|
|
1595
1479
|
traceId,
|
|
1596
1480
|
sessionKey: permissionReq.sessionKey,
|
|
1597
1481
|
toolName: toolCall.name,
|
|
@@ -1605,10 +1489,10 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
1605
1489
|
};
|
|
1606
1490
|
}
|
|
1607
1491
|
}
|
|
1608
|
-
const { signal: timedSignal, clear } = withTimeoutSignal(signal, this.
|
|
1492
|
+
const { signal: timedSignal, clear } = withTimeoutSignal(signal, this._d.toolTimeoutMs);
|
|
1609
1493
|
try {
|
|
1610
1494
|
const beforeCtx = this.buildToolCallContext(toolCall, tool, args, request, timedSignal);
|
|
1611
|
-
const before = await this.
|
|
1495
|
+
const before = await this._d.hooks.beforeToolCall.promise(
|
|
1612
1496
|
{ block: false, terminate: false, args },
|
|
1613
1497
|
beforeCtx
|
|
1614
1498
|
);
|
|
@@ -1640,10 +1524,10 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
1640
1524
|
};
|
|
1641
1525
|
}
|
|
1642
1526
|
const status = toolResultStatus(result);
|
|
1643
|
-
await this.
|
|
1527
|
+
await this._d.telemetry.emit("onToolCall", {
|
|
1644
1528
|
traceId,
|
|
1645
1529
|
spanId: newSpanId(),
|
|
1646
|
-
sessionKey: request.sessionKey ?? this.
|
|
1530
|
+
sessionKey: request.sessionKey ?? this._d.defaultSessionKey,
|
|
1647
1531
|
toolName: toolCall.name,
|
|
1648
1532
|
args,
|
|
1649
1533
|
durationMs: Date.now() - toolStartedAt,
|
|
@@ -1653,7 +1537,7 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
1653
1537
|
errorClass: status === "error" ? errorClassFromMessage(String(result.details?.error ?? "")) ?? "tool_error" : void 0
|
|
1654
1538
|
});
|
|
1655
1539
|
const afterCtx = this.buildToolCallContext(toolCall, tool, args, request, timedSignal);
|
|
1656
|
-
const after = await this.
|
|
1540
|
+
const after = await this._d.hooks.afterToolCall.promise(
|
|
1657
1541
|
{ result, terminate: false },
|
|
1658
1542
|
afterCtx
|
|
1659
1543
|
);
|
|
@@ -1689,13 +1573,14 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
1689
1573
|
toolCall,
|
|
1690
1574
|
tool,
|
|
1691
1575
|
args,
|
|
1692
|
-
sessionKey: request.sessionKey ?? this.
|
|
1576
|
+
sessionKey: request.sessionKey ?? this._d.defaultSessionKey,
|
|
1693
1577
|
request,
|
|
1694
|
-
shared: this.
|
|
1578
|
+
shared: this._d.getShared() ?? /* @__PURE__ */ new Map(),
|
|
1695
1579
|
signal
|
|
1696
1580
|
};
|
|
1697
1581
|
}
|
|
1698
|
-
|
|
1582
|
+
/** 工具结果 → toolResult 消息(写入会话历史) */
|
|
1583
|
+
buildResultMessage(toolCall, result) {
|
|
1699
1584
|
return {
|
|
1700
1585
|
role: "toolResult",
|
|
1701
1586
|
content: result.content,
|
|
@@ -1705,294 +1590,69 @@ var AgentRuntime = class _AgentRuntime {
|
|
|
1705
1590
|
timestamp: Date.now()
|
|
1706
1591
|
};
|
|
1707
1592
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1593
|
+
};
|
|
1594
|
+
|
|
1595
|
+
// ../agent/transformer/index.ts
|
|
1596
|
+
function ensureToolPairing(messages) {
|
|
1597
|
+
const toolCallIds = /* @__PURE__ */ new Set();
|
|
1598
|
+
const toolResultIds = /* @__PURE__ */ new Set();
|
|
1599
|
+
for (const msg of messages) {
|
|
1600
|
+
if (msg.role === "assistant") {
|
|
1601
|
+
const content = msg.content;
|
|
1602
|
+
if (Array.isArray(content)) {
|
|
1603
|
+
for (const tc of extractToolCalls(content)) {
|
|
1604
|
+
toolCallIds.add(tc.id);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1719
1607
|
}
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
content: request.message,
|
|
1723
|
-
timestamp: Date.now()
|
|
1724
|
-
};
|
|
1725
|
-
}
|
|
1726
|
-
// ─── 内部方法 ───────────────────────────────────────────────────
|
|
1727
|
-
/**
|
|
1728
|
-
* 获取同一会话的执行锁:返回 release 函数,调用后释放。
|
|
1729
|
-
* 同一 sessionKey 的 run/stream 会串行执行,避免消息数组交错、
|
|
1730
|
-
* abortController 互相覆盖、hydrate 竞态。
|
|
1731
|
-
*/
|
|
1732
|
-
async acquire(session) {
|
|
1733
|
-
let release;
|
|
1734
|
-
const prev = session.queue;
|
|
1735
|
-
session.queue = new Promise((resolve) => {
|
|
1736
|
-
release = () => resolve();
|
|
1737
|
-
});
|
|
1738
|
-
await prev;
|
|
1739
|
-
session.lockHeld = true;
|
|
1740
|
-
return () => {
|
|
1741
|
-
session.lockHeld = false;
|
|
1742
|
-
release();
|
|
1743
|
-
};
|
|
1744
|
-
}
|
|
1745
|
-
/** 标记会话空闲并唤醒所有 waitForIdle 等待者 */
|
|
1746
|
-
markIdle(session) {
|
|
1747
|
-
session.isStreaming = false;
|
|
1748
|
-
session.abortController = null;
|
|
1749
|
-
if (session.idleResolvers.length > 0) {
|
|
1750
|
-
const resolvers = session.idleResolvers.splice(0);
|
|
1751
|
-
for (const resolve of resolvers) resolve();
|
|
1608
|
+
if (msg.role === "toolResult") {
|
|
1609
|
+
toolResultIds.add(msg.toolCallId);
|
|
1752
1610
|
}
|
|
1753
1611
|
}
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
if (!
|
|
1758
|
-
if (session.hydrated) return;
|
|
1759
|
-
session.hydrated = true;
|
|
1760
|
-
const stored = await this._sessionStorage.load(sessionKey);
|
|
1761
|
-
if (!stored) return;
|
|
1762
|
-
session.messages = stored.messages;
|
|
1763
|
-
session.createdAt = stored.createdAt;
|
|
1612
|
+
const orphanedCalls = /* @__PURE__ */ new Set();
|
|
1613
|
+
const orphanedResults = /* @__PURE__ */ new Set();
|
|
1614
|
+
for (const id of toolCallIds) {
|
|
1615
|
+
if (!toolResultIds.has(id)) orphanedCalls.add(id);
|
|
1764
1616
|
}
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
if (!this._sessionStorage) return;
|
|
1768
|
-
const stored = {
|
|
1769
|
-
key: sessionKey,
|
|
1770
|
-
version: SESSION_VERSION,
|
|
1771
|
-
messages: session.messages,
|
|
1772
|
-
model: this.deriveModel(session.messages),
|
|
1773
|
-
usage: this.sumUsage(session.messages),
|
|
1774
|
-
createdAt: session.createdAt,
|
|
1775
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1776
|
-
};
|
|
1777
|
-
await this._sessionStorage.save(sessionKey, stored);
|
|
1617
|
+
for (const id of toolResultIds) {
|
|
1618
|
+
if (!toolCallIds.has(id)) orphanedResults.add(id);
|
|
1778
1619
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
* 让运行中的会话随时可被持久化数据观测到。ephemeral 跳过;
|
|
1782
|
-
* 存储失败仅告警,不影响对话循环继续。
|
|
1783
|
-
*/
|
|
1784
|
-
async persistSessionSafe(request, sessionKey) {
|
|
1785
|
-
if (request.ephemeral || !this._sessionStorage) return;
|
|
1786
|
-
try {
|
|
1787
|
-
const session = this._sessions.get(sessionKey);
|
|
1788
|
-
if (!session) return;
|
|
1789
|
-
await this.persistSession(sessionKey, session);
|
|
1790
|
-
} catch (err) {
|
|
1791
|
-
console.warn("[Runtime] \u4F1A\u8BDD\u6301\u4E45\u5316\u5931\u8D25:", err?.message);
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
/** 从消息中推导最后使用的模型 */
|
|
1795
|
-
deriveModel(messages) {
|
|
1796
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1797
|
-
const msg = messages[i];
|
|
1798
|
-
if (msg.role === "assistant" && msg.model) {
|
|
1799
|
-
return {
|
|
1800
|
-
provider: msg.provider ?? this._model.provider,
|
|
1801
|
-
modelId: msg.model
|
|
1802
|
-
};
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
return null;
|
|
1806
|
-
}
|
|
1807
|
-
/** 汇总所有 assistant 消息的 token 用量(含 cache) */
|
|
1808
|
-
sumUsage(messages) {
|
|
1809
|
-
const usage = createEmptyUsage();
|
|
1810
|
-
for (const msg of messages) {
|
|
1811
|
-
if (msg.role === "assistant") {
|
|
1812
|
-
const u = msg.usage;
|
|
1813
|
-
if (u) {
|
|
1814
|
-
usage.input += u.input;
|
|
1815
|
-
usage.output += u.output;
|
|
1816
|
-
usage.total += u.total;
|
|
1817
|
-
usage.cacheRead = (usage.cacheRead ?? 0) + (u.cacheRead ?? 0);
|
|
1818
|
-
usage.cacheWrite = (usage.cacheWrite ?? 0) + (u.cacheWrite ?? 0);
|
|
1819
|
-
usage.reasoning = (usage.reasoning ?? 0) + (u.reasoning ?? 0);
|
|
1820
|
-
}
|
|
1821
|
-
}
|
|
1822
|
-
}
|
|
1823
|
-
return usage;
|
|
1824
|
-
}
|
|
1825
|
-
/** 链式执行上下文转换器(按数组顺序,上一个输出作为下一个输入) */
|
|
1826
|
-
async transformMessages(compilation, sessionKey) {
|
|
1827
|
-
if (this._transformers.length === 0) {
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1830
|
-
let resources = messagesToResources(compilation.messages);
|
|
1831
|
-
const context = {
|
|
1832
|
-
graph: compilation.graph,
|
|
1833
|
-
runtime: {
|
|
1834
|
-
sessionKey,
|
|
1835
|
-
turn: compilation.messages.length,
|
|
1836
|
-
contextWindow: this._model.contextWindow,
|
|
1837
|
-
maxTokens: this._model.maxTokens,
|
|
1838
|
-
contextBudgetRatio: this._contextBudgetRatio
|
|
1839
|
-
}
|
|
1840
|
-
};
|
|
1841
|
-
for (const transformer of this._transformers) {
|
|
1842
|
-
try {
|
|
1843
|
-
resources = await transformer.transform(resources, context);
|
|
1844
|
-
} catch (err) {
|
|
1845
|
-
console.warn(
|
|
1846
|
-
`[Runtime] \u8F6C\u6362\u5668 "${transformer.name}" \u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7:`,
|
|
1847
|
-
err?.message ?? err
|
|
1848
|
-
);
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
const messages = resourcesToMessages(resources);
|
|
1852
|
-
compilation.messages.splice(0, compilation.messages.length, ...messages);
|
|
1853
|
-
}
|
|
1854
|
-
buildContext(messages) {
|
|
1855
|
-
const tools = Array.from(this._globalTools.values());
|
|
1856
|
-
return {
|
|
1857
|
-
systemPrompt: this._systemPrompt,
|
|
1858
|
-
// compactionSummary / stateSnapshot 为内部扩展 role,provider 适配层
|
|
1859
|
-
// 仅支持 user/assistant/toolResult,发出前统一转为带标注的 user 消息
|
|
1860
|
-
messages: messages.filter((m) => m.role !== "system").map((m) => {
|
|
1861
|
-
const role = m.role;
|
|
1862
|
-
if (role === "compactionSummary") return this.compactionSummaryToUser(m);
|
|
1863
|
-
if (role === "stateSnapshot") return this.stateSnapshotToUser(m);
|
|
1864
|
-
return m;
|
|
1865
|
-
}),
|
|
1866
|
-
tools: tools.length > 0 ? tools : void 0
|
|
1867
|
-
};
|
|
1868
|
-
}
|
|
1869
|
-
/** compactionSummary 消息 → user 消息(所有 provider 均兼容 user role) */
|
|
1870
|
-
compactionSummaryToUser(msg) {
|
|
1871
|
-
const text = typeof msg.content === "string" ? msg.content : extractText(msg.content);
|
|
1872
|
-
return {
|
|
1873
|
-
role: "user",
|
|
1874
|
-
content: `${COMPACTION_USER_PREFIX}
|
|
1875
|
-
${text}`,
|
|
1876
|
-
timestamp: msg.timestamp
|
|
1877
|
-
};
|
|
1878
|
-
}
|
|
1879
|
-
/** stateSnapshot 消息 → user 消息(所有 provider 均兼容 user role) */
|
|
1880
|
-
stateSnapshotToUser(msg) {
|
|
1881
|
-
const text = typeof msg.content === "string" ? msg.content : extractText(msg.content);
|
|
1882
|
-
return {
|
|
1883
|
-
role: "user",
|
|
1884
|
-
content: `${STATE_SNAPSHOT_USER_PREFIX}
|
|
1885
|
-
${text}`,
|
|
1886
|
-
timestamp: msg.timestamp
|
|
1887
|
-
};
|
|
1888
|
-
}
|
|
1889
|
-
async streamModel(compilation, signal, sessionKey) {
|
|
1890
|
-
const turn = this.modelTurnWithRecovery(compilation, signal, sessionKey, false);
|
|
1891
|
-
let r = await turn.next();
|
|
1892
|
-
while (!r.done) r = await turn.next();
|
|
1893
|
-
return r.value ?? this.emptyAssistantMessage();
|
|
1894
|
-
}
|
|
1895
|
-
/** 空的 assistant 消息(流异常中断无 done/error 事件时的兜底,与旧 streamModel 行为一致) */
|
|
1896
|
-
emptyAssistantMessage() {
|
|
1897
|
-
return {
|
|
1898
|
-
role: "assistant",
|
|
1899
|
-
content: [],
|
|
1900
|
-
stopReason: "stop",
|
|
1901
|
-
usage: createEmptyUsage(),
|
|
1902
|
-
model: this._model.id,
|
|
1903
|
-
provider: this._model.provider,
|
|
1904
|
-
timestamp: Date.now()
|
|
1905
|
-
};
|
|
1620
|
+
if (orphanedCalls.size === 0 && orphanedResults.size === 0) {
|
|
1621
|
+
return messages;
|
|
1906
1622
|
}
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
* - 显式错误 / 零产出截断溢出 / thinking 耗尽溢出 / 输出打满溢出:丢弃失败
|
|
1916
|
-
* 消息后重试;流式路径吞掉可恢复的 error chunk(消费者看不到瞬态错误),
|
|
1917
|
-
* 不可恢复时补发。
|
|
1918
|
-
* - 静默溢出(stop + 有完整产出):保留回复,仅压缩旧上下文供后续轮次。
|
|
1919
|
-
* - 恢复耗尽或单请求超窗(无可丢弃):返回最后一次错误消息,维持旧行为。
|
|
1920
|
-
*
|
|
1921
|
-
* 流式路径 yield 模型事件 chunk;非流式路径由 streamModel 消费(chunk 丢弃)。
|
|
1922
|
-
* 返回最终 assistant 消息;无 done/error 事件时返回 null。
|
|
1923
|
-
*/
|
|
1924
|
-
async *modelTurnWithRecovery(compilation, signal, sessionKey, stream) {
|
|
1925
|
-
const contextWindow = this._model.contextWindow;
|
|
1926
|
-
const maxTokens = this._model.maxTokens;
|
|
1927
|
-
let recoveries = 0;
|
|
1928
|
-
const toProbe = (m) => ({
|
|
1929
|
-
stopReason: m.stopReason,
|
|
1930
|
-
errorMessage: m.errorMessage,
|
|
1931
|
-
usage: m.usage,
|
|
1932
|
-
content: m.content,
|
|
1933
|
-
maxTokens
|
|
1934
|
-
});
|
|
1935
|
-
while (true) {
|
|
1936
|
-
let assistant = null;
|
|
1937
|
-
let suppressed = null;
|
|
1938
|
-
for await (const event of this.streamModelEvents(compilation, signal, stream)) {
|
|
1939
|
-
if (event.type === "error") {
|
|
1940
|
-
const msg = event.message;
|
|
1941
|
-
if (recoveries < OVERFLOW_RECOVERY_LIMIT && isContextOverflow(toProbe(msg), contextWindow)) {
|
|
1942
|
-
suppressed = msg;
|
|
1943
|
-
continue;
|
|
1623
|
+
const result = [];
|
|
1624
|
+
for (const msg of messages) {
|
|
1625
|
+
if (msg.role === "assistant") {
|
|
1626
|
+
const content = msg.content;
|
|
1627
|
+
if (Array.isArray(content)) {
|
|
1628
|
+
const filteredContent = content.filter((block) => {
|
|
1629
|
+
if (block.type === "toolCall") {
|
|
1630
|
+
return !orphanedCalls.has(block.id);
|
|
1944
1631
|
}
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
if (event.type === "done") assistant = event.message;
|
|
1952
|
-
}
|
|
1632
|
+
return true;
|
|
1633
|
+
});
|
|
1634
|
+
if (filteredContent.length === 0 && content.length > 0) continue;
|
|
1635
|
+
result.push({ ...msg, content: filteredContent });
|
|
1636
|
+
} else {
|
|
1637
|
+
result.push(msg);
|
|
1953
1638
|
}
|
|
1954
|
-
|
|
1955
|
-
if (!
|
|
1956
|
-
|
|
1957
|
-
const output = final.usage?.output ?? 0;
|
|
1958
|
-
const blocks = Array.isArray(final.content) ? final.content : null;
|
|
1959
|
-
const hasMeaningfulText = blocks?.some(
|
|
1960
|
-
(b) => b.type === "text" && "text" in b && typeof b.text === "string" && b.text.trim().length > 0
|
|
1961
|
-
);
|
|
1962
|
-
const hasToolCall = blocks?.some((b) => b.type === "toolCall");
|
|
1963
|
-
const thinkingOnly = blocks ? !hasMeaningfulText && !hasToolCall && blocks.some((b) => b.type === "thinking") : false;
|
|
1964
|
-
const outputFull = maxTokens > 0 && output >= Math.floor(maxTokens * 0.95);
|
|
1965
|
-
const failed = final.stopReason === "error" || output === 0 || thinkingOnly || outputFull;
|
|
1966
|
-
if (failed && recoveries < OVERFLOW_RECOVERY_LIMIT) {
|
|
1967
|
-
recoveries += 1;
|
|
1968
|
-
if (await this.recoverFromOverflow(compilation, recoveries, sessionKey, signal)) {
|
|
1969
|
-
const reason = thinkingOnly ? "thinking \u8017\u5C3D" : outputFull ? "output \u6253\u6EE1" : final.stopReason;
|
|
1970
|
-
console.warn(
|
|
1971
|
-
`[Runtime] \u4E0A\u4E0B\u6587\u6EA2\u51FA\uFF08${reason}\uFF09\uFF0C\u5DF2\u538B\u7F29\u5386\u53F2\u5E76\u540C\u56DE\u5408\u91CD\u8BD5\uFF08${recoveries}/${OVERFLOW_RECOVERY_LIMIT}\uFF09`
|
|
1972
|
-
);
|
|
1973
|
-
await this.emitTelemetry("onRetry", {
|
|
1974
|
-
traceId: compilation.traceId,
|
|
1975
|
-
provider: this._model.provider,
|
|
1976
|
-
modelId: this._model.id,
|
|
1977
|
-
attempt: recoveries,
|
|
1978
|
-
errorClass: "context-overflow",
|
|
1979
|
-
delayMs: 0,
|
|
1980
|
-
willRetry: true
|
|
1981
|
-
});
|
|
1982
|
-
continue;
|
|
1983
|
-
}
|
|
1984
|
-
if (suppressed) {
|
|
1985
|
-
yield { type: "error", content: final.errorMessage || "\u6A21\u578B\u8C03\u7528\u51FA\u9519" };
|
|
1986
|
-
}
|
|
1987
|
-
return final;
|
|
1988
|
-
}
|
|
1989
|
-
if (failed) return final;
|
|
1990
|
-
await this.recoverFromOverflow(compilation, 1, sessionKey, signal);
|
|
1991
|
-
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");
|
|
1639
|
+
} else if (msg.role === "toolResult") {
|
|
1640
|
+
if (!orphanedResults.has(msg.toolCallId)) {
|
|
1641
|
+
result.push(msg);
|
|
1992
1642
|
}
|
|
1993
|
-
|
|
1643
|
+
} else {
|
|
1644
|
+
result.push(msg);
|
|
1994
1645
|
}
|
|
1995
1646
|
}
|
|
1647
|
+
return result;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// ../agent/runtime/compactor.ts
|
|
1651
|
+
var ContextCompactor = class {
|
|
1652
|
+
constructor(_d) {
|
|
1653
|
+
this._d = _d;
|
|
1654
|
+
}
|
|
1655
|
+
_d;
|
|
1996
1656
|
/**
|
|
1997
1657
|
* 计算溢出恢复的截断点:按 token 预算从最旧消息开始丢弃。
|
|
1998
1658
|
*
|
|
@@ -2003,10 +1663,10 @@ ${text}`,
|
|
|
2003
1663
|
* 最小集,单条请求即超窗,无法恢复。
|
|
2004
1664
|
*/
|
|
2005
1665
|
computeOverflowSplit(messages, recovery) {
|
|
2006
|
-
const contextWindow = this.
|
|
1666
|
+
const contextWindow = this._d.getModel().contextWindow;
|
|
2007
1667
|
if (!contextWindow || contextWindow <= 0) return 0;
|
|
2008
1668
|
const target = Math.max(
|
|
2009
|
-
Math.floor(contextWindow * this.
|
|
1669
|
+
Math.floor(contextWindow * this._d.contextBudgetRatio * 0.5 ** recovery),
|
|
2010
1670
|
1
|
|
2011
1671
|
);
|
|
2012
1672
|
let total = 0;
|
|
@@ -2034,7 +1694,7 @@ ${text}`,
|
|
|
2034
1694
|
const messages = compilation.messages;
|
|
2035
1695
|
const split = this.computeOverflowSplit(messages, recovery);
|
|
2036
1696
|
if (split <= 0) return false;
|
|
2037
|
-
const useCompaction = !!this.
|
|
1697
|
+
const useCompaction = !!this._d.compaction && this._d.compaction.enabled !== false && this._d.compaction.onOverflow !== false;
|
|
2038
1698
|
if (useCompaction) {
|
|
2039
1699
|
await this.compactOrTruncate(
|
|
2040
1700
|
messages,
|
|
@@ -2057,30 +1717,17 @@ ${text}`,
|
|
|
2057
1717
|
* 其余部分摘要替换;摘要失败降级硬截断。
|
|
2058
1718
|
*/
|
|
2059
1719
|
async maybeCompactByThreshold(compilation, sessionKey, signal) {
|
|
2060
|
-
if (!this.
|
|
2061
|
-
const contextWindow = this.
|
|
1720
|
+
if (!this._d.compaction || this._d.compaction.enabled === false) return;
|
|
1721
|
+
const contextWindow = this._d.getModel().contextWindow;
|
|
2062
1722
|
if (!contextWindow || contextWindow <= 0) return;
|
|
2063
1723
|
const messages = compilation.messages;
|
|
2064
1724
|
const triggerTokens = Math.floor(
|
|
2065
|
-
contextWindow * (this.
|
|
1725
|
+
contextWindow * (this._d.compaction?.triggerRatio ?? this._d.contextBudgetRatio)
|
|
2066
1726
|
);
|
|
2067
1727
|
let total = 0;
|
|
2068
1728
|
for (const m of messages) total += estimateMessageTokens(m);
|
|
2069
1729
|
if (total <= triggerTokens) return;
|
|
2070
|
-
const
|
|
2071
|
-
contextWindow * (this._compaction?.targetRatio ?? 0.5)
|
|
2072
|
-
);
|
|
2073
|
-
const keepTokens = Math.max(Math.floor(targetTokens / 2), 1);
|
|
2074
|
-
let kept = 0;
|
|
2075
|
-
let split = messages.length;
|
|
2076
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2077
|
-
if (kept >= keepTokens) {
|
|
2078
|
-
split = i + 1;
|
|
2079
|
-
break;
|
|
2080
|
-
}
|
|
2081
|
-
kept += estimateMessageTokens(messages[i]);
|
|
2082
|
-
split = i;
|
|
2083
|
-
}
|
|
1730
|
+
const split = this.computeKeepSplit(messages, contextWindow);
|
|
2084
1731
|
if (split <= 0 || split >= messages.length) return;
|
|
2085
1732
|
const mode = await this.compactOrTruncate(
|
|
2086
1733
|
messages,
|
|
@@ -2095,20 +1742,12 @@ ${text}`,
|
|
|
2095
1742
|
);
|
|
2096
1743
|
}
|
|
2097
1744
|
/**
|
|
2098
|
-
*
|
|
2099
|
-
*
|
|
2100
|
-
* 的保留段计算与 compactOrTruncate 执行路径),压缩后持久化。
|
|
1745
|
+
* 保留段计算:从尾部累计保留最新消息,保留量为压缩目标(contextWindow ×
|
|
1746
|
+
* targetRatio)的一半。返回被压缩段的结束下标;messages[0..split) 为被压缩段。
|
|
2101
1747
|
*/
|
|
2102
|
-
|
|
2103
|
-
if (!this._compaction || this._compaction.enabled === false) return null;
|
|
2104
|
-
const key = sessionKey ?? this._sessionKey;
|
|
2105
|
-
const session = this.getSession(key);
|
|
2106
|
-
const messages = session.messages;
|
|
2107
|
-
if (messages.length === 0) return null;
|
|
2108
|
-
const contextWindow = this._model.contextWindow;
|
|
2109
|
-
if (!contextWindow || contextWindow <= 0) return null;
|
|
1748
|
+
computeKeepSplit(messages, contextWindow) {
|
|
2110
1749
|
const targetTokens = Math.floor(
|
|
2111
|
-
contextWindow * (this.
|
|
1750
|
+
contextWindow * (this._d.compaction?.targetRatio ?? 0.5)
|
|
2112
1751
|
);
|
|
2113
1752
|
const keepTokens = Math.max(Math.floor(targetTokens / 2), 1);
|
|
2114
1753
|
let kept = 0;
|
|
@@ -2121,21 +1760,7 @@ ${text}`,
|
|
|
2121
1760
|
kept += estimateMessageTokens(messages[i]);
|
|
2122
1761
|
split = i;
|
|
2123
1762
|
}
|
|
2124
|
-
|
|
2125
|
-
const mode = await this.compactOrTruncate(
|
|
2126
|
-
messages,
|
|
2127
|
-
split,
|
|
2128
|
-
key,
|
|
2129
|
-
`manual-${Date.now().toString(36)}`,
|
|
2130
|
-
new AbortController().signal,
|
|
2131
|
-
"threshold"
|
|
2132
|
-
);
|
|
2133
|
-
try {
|
|
2134
|
-
await this.persistSession(key, session);
|
|
2135
|
-
} catch (err) {
|
|
2136
|
-
console.warn("[Runtime] \u624B\u52A8\u538B\u7F29\u540E\u6301\u4E45\u5316\u5931\u8D25:", err?.message);
|
|
2137
|
-
}
|
|
2138
|
-
return mode;
|
|
1763
|
+
return split;
|
|
2139
1764
|
}
|
|
2140
1765
|
/**
|
|
2141
1766
|
* 执行压缩:messages[0..split) 为被压缩段,原地替换。
|
|
@@ -2150,10 +1775,10 @@ ${text}`,
|
|
|
2150
1775
|
const tokensBefore = messages.reduce((sum, m) => sum + estimateMessageTokens(m), 0);
|
|
2151
1776
|
let mode = "truncate";
|
|
2152
1777
|
let summaryText = "";
|
|
2153
|
-
const summaryEnabled = !!this.
|
|
1778
|
+
const summaryEnabled = !!this._d.compaction && this._d.compaction.enabled !== false;
|
|
2154
1779
|
if (summaryEnabled && compacted.length > 0) {
|
|
2155
1780
|
const summaryBudget = Math.floor(
|
|
2156
|
-
this.
|
|
1781
|
+
this._d.getModel().contextWindow * COMPACTION_SUMMARY_BUDGET_RATIO
|
|
2157
1782
|
);
|
|
2158
1783
|
const inputText = compacted.map((m) => messageToSummaryLine(m)).filter((line) => line.length > 0).join("\n");
|
|
2159
1784
|
if (inputText && estimateTextTokens(inputText) <= summaryBudget) {
|
|
@@ -2180,7 +1805,7 @@ ${text}`,
|
|
|
2180
1805
|
messages.splice(0, messages.length, ...kept);
|
|
2181
1806
|
tokensAfter = kept.reduce((sum, m) => sum + estimateMessageTokens(m), 0);
|
|
2182
1807
|
}
|
|
2183
|
-
await this.
|
|
1808
|
+
await this._d.telemetry.emit("onCompaction", {
|
|
2184
1809
|
traceId,
|
|
2185
1810
|
sessionKey,
|
|
2186
1811
|
mode,
|
|
@@ -2197,7 +1822,7 @@ ${text}`,
|
|
|
2197
1822
|
* 由调用方降级硬截断——摘要失败不影响主流程。
|
|
2198
1823
|
*/
|
|
2199
1824
|
async summarizeMessages(inputText, sessionKey, traceId, signal) {
|
|
2200
|
-
const prompt = this.
|
|
1825
|
+
const prompt = this._d.compaction?.prompt ?? DEFAULT_COMPACTION_PROMPT;
|
|
2201
1826
|
const context = {
|
|
2202
1827
|
systemPrompt: prompt,
|
|
2203
1828
|
messages: [{ role: "user", content: inputText, timestamp: Date.now() }]
|
|
@@ -2207,7 +1832,7 @@ ${text}`,
|
|
|
2207
1832
|
let usage;
|
|
2208
1833
|
let failed = false;
|
|
2209
1834
|
try {
|
|
2210
|
-
for await (const event of this.
|
|
1835
|
+
for await (const event of this._d.getStreamFn()(this._d.getModel(), context, { signal })) {
|
|
2211
1836
|
if (event.type === "text_delta") {
|
|
2212
1837
|
text += event.delta;
|
|
2213
1838
|
} else if (event.type === "error") {
|
|
@@ -2225,11 +1850,11 @@ ${text}`,
|
|
|
2225
1850
|
return null;
|
|
2226
1851
|
}
|
|
2227
1852
|
if (failed || !text.trim()) return null;
|
|
2228
|
-
await this.
|
|
1853
|
+
await this._d.telemetry.emit("onModelCall", {
|
|
2229
1854
|
traceId,
|
|
2230
1855
|
spanId: newSpanId(),
|
|
2231
1856
|
sessionKey,
|
|
2232
|
-
modelId: this.
|
|
1857
|
+
modelId: this._d.getModel().id,
|
|
2233
1858
|
attempts: 1,
|
|
2234
1859
|
inputTokens: usage?.input ?? estimateTextTokens(inputText),
|
|
2235
1860
|
outputTokens: usage?.output ?? estimateTextTokens(text),
|
|
@@ -2246,30 +1871,148 @@ ${text}`,
|
|
|
2246
1871
|
timestamp: Date.now()
|
|
2247
1872
|
};
|
|
2248
1873
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
1874
|
+
};
|
|
1875
|
+
|
|
1876
|
+
// ../agent/runtime/model-turn.ts
|
|
1877
|
+
var ModelTurnRunner = class {
|
|
1878
|
+
constructor(_d) {
|
|
1879
|
+
this._d = _d;
|
|
1880
|
+
}
|
|
1881
|
+
_d;
|
|
1882
|
+
async streamModel(compilation, signal, sessionKey) {
|
|
1883
|
+
const turn = this.modelTurnWithRecovery(compilation, signal, sessionKey, false);
|
|
1884
|
+
let r = await turn.next();
|
|
1885
|
+
while (!r.done) r = await turn.next();
|
|
1886
|
+
return r.value ?? this.emptyAssistantMessage();
|
|
1887
|
+
}
|
|
1888
|
+
/** 空的 assistant 消息(流异常中断无 done/error 事件时的兜底) */
|
|
1889
|
+
emptyAssistantMessage() {
|
|
1890
|
+
const model = this._d.getModel();
|
|
1891
|
+
return {
|
|
1892
|
+
role: "assistant",
|
|
1893
|
+
content: [],
|
|
1894
|
+
stopReason: "stop",
|
|
1895
|
+
usage: createEmptyUsage(),
|
|
1896
|
+
model: model.id,
|
|
1897
|
+
provider: model.provider,
|
|
1898
|
+
timestamp: Date.now()
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
/**
|
|
1902
|
+
* 单回合模型调用 + 上下文溢出自动恢复闭环。
|
|
1903
|
+
*
|
|
1904
|
+
* 检测(isContextOverflow,统一传入 model.contextWindow / content / maxTokens,
|
|
1905
|
+
* 覆盖显式错误 / 静默溢出 / 输入截断溢出 / 输出 thinking 耗尽 / 输出打满 五模式)
|
|
1906
|
+
* → 丢弃失败的 assistant 消息 → 截断会话历史 → 同回合重试(不消耗回合数,
|
|
1907
|
+
* 上限 OVERFLOW_RECOVERY_LIMIT):
|
|
1908
|
+
*
|
|
1909
|
+
* - 显式错误 / 零产出截断溢出 / thinking 耗尽溢出 / 输出打满溢出:丢弃失败
|
|
1910
|
+
* 消息后重试;流式路径吞掉可恢复的 error chunk(消费者看不到瞬态错误),
|
|
1911
|
+
* 不可恢复时补发。
|
|
1912
|
+
* - 静默溢出(stop + 有完整产出):保留回复,仅压缩旧上下文供后续轮次。
|
|
1913
|
+
* - 恢复耗尽或单请求超窗(无可丢弃):返回最后一次错误消息,维持旧行为。
|
|
1914
|
+
*
|
|
1915
|
+
* 流式路径 yield 模型事件 chunk;非流式路径由 streamModel 消费(chunk 丢弃)。
|
|
1916
|
+
* 返回最终 assistant 消息;无 done/error 事件时返回 null。
|
|
1917
|
+
*/
|
|
1918
|
+
async *modelTurnWithRecovery(compilation, signal, sessionKey, stream) {
|
|
1919
|
+
const model = this._d.getModel();
|
|
1920
|
+
const contextWindow = model.contextWindow;
|
|
1921
|
+
const maxTokens = model.maxTokens;
|
|
1922
|
+
let recoveries = 0;
|
|
1923
|
+
const toProbe = (m) => ({
|
|
1924
|
+
stopReason: m.stopReason,
|
|
1925
|
+
errorMessage: m.errorMessage,
|
|
1926
|
+
usage: m.usage,
|
|
1927
|
+
content: m.content,
|
|
1928
|
+
maxTokens
|
|
1929
|
+
});
|
|
1930
|
+
while (true) {
|
|
1931
|
+
let assistant = null;
|
|
1932
|
+
let suppressed = null;
|
|
1933
|
+
for await (const event of this.streamModelEvents(compilation, signal, stream)) {
|
|
1934
|
+
if (event.type === "error") {
|
|
1935
|
+
const msg = event.message;
|
|
1936
|
+
if (recoveries < OVERFLOW_RECOVERY_LIMIT && isContextOverflow(toProbe(msg), contextWindow)) {
|
|
1937
|
+
suppressed = msg;
|
|
1938
|
+
continue;
|
|
1939
|
+
}
|
|
1940
|
+
const chunk = streamEventToChunk(event);
|
|
1941
|
+
if (chunk) yield chunk;
|
|
1942
|
+
assistant = msg;
|
|
1943
|
+
} else {
|
|
1944
|
+
const chunk = streamEventToChunk(event);
|
|
1945
|
+
if (chunk) yield chunk;
|
|
1946
|
+
if (event.type === "done") assistant = event.message;
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
const final = assistant ?? suppressed;
|
|
1950
|
+
if (!final) return null;
|
|
1951
|
+
if (isContextOverflow(toProbe(final), contextWindow)) {
|
|
1952
|
+
const output = final.usage?.output ?? 0;
|
|
1953
|
+
const blocks = Array.isArray(final.content) ? final.content : null;
|
|
1954
|
+
const hasMeaningfulText = blocks?.some(
|
|
1955
|
+
(b) => b.type === "text" && "text" in b && typeof b.text === "string" && b.text.trim().length > 0
|
|
1956
|
+
);
|
|
1957
|
+
const hasToolCall = blocks?.some((b) => b.type === "toolCall");
|
|
1958
|
+
const thinkingOnly = blocks ? !hasMeaningfulText && !hasToolCall && blocks.some((b) => b.type === "thinking") : false;
|
|
1959
|
+
const outputFull = maxTokens > 0 && output >= Math.floor(maxTokens * 0.95);
|
|
1960
|
+
const failed = final.stopReason === "error" || output === 0 || thinkingOnly || outputFull;
|
|
1961
|
+
if (failed && recoveries < OVERFLOW_RECOVERY_LIMIT) {
|
|
1962
|
+
recoveries += 1;
|
|
1963
|
+
if (await this._d.compactor.recoverFromOverflow(compilation, recoveries, sessionKey, signal)) {
|
|
1964
|
+
const reason = thinkingOnly ? "thinking \u8017\u5C3D" : outputFull ? "output \u6253\u6EE1" : final.stopReason;
|
|
1965
|
+
console.warn(
|
|
1966
|
+
`[Runtime] \u4E0A\u4E0B\u6587\u6EA2\u51FA\uFF08${reason}\uFF09\uFF0C\u5DF2\u538B\u7F29\u5386\u53F2\u5E76\u540C\u56DE\u5408\u91CD\u8BD5\uFF08${recoveries}/${OVERFLOW_RECOVERY_LIMIT}\uFF09`
|
|
1967
|
+
);
|
|
1968
|
+
await this._d.telemetry.emit("onRetry", {
|
|
1969
|
+
traceId: compilation.traceId,
|
|
1970
|
+
provider: model.provider,
|
|
1971
|
+
modelId: model.id,
|
|
1972
|
+
attempt: recoveries,
|
|
1973
|
+
errorClass: "context-overflow",
|
|
1974
|
+
delayMs: 0,
|
|
1975
|
+
willRetry: true
|
|
1976
|
+
});
|
|
1977
|
+
continue;
|
|
1978
|
+
}
|
|
1979
|
+
if (suppressed) {
|
|
1980
|
+
yield { type: "error", content: final.errorMessage || "\u6A21\u578B\u8C03\u7528\u51FA\u9519" };
|
|
1981
|
+
}
|
|
1982
|
+
return final;
|
|
1983
|
+
}
|
|
1984
|
+
if (failed) return final;
|
|
1985
|
+
await this._d.compactor.recoverFromOverflow(compilation, 1, sessionKey, signal);
|
|
1986
|
+
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");
|
|
1987
|
+
}
|
|
1988
|
+
return final;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* 统一模型调用埋点生成器:run()(streamModel)与 stream()(runLoopStream)两路径共用。
|
|
1993
|
+
* 职责:模型调用计时、spanId、attempts 累计、onRetry 转发、onModelCall 上报
|
|
1994
|
+
* (含 tokens / cost / errorClass / ttft)。
|
|
1995
|
+
*/
|
|
1996
|
+
async *streamModelEvents(compilation, signal, stream) {
|
|
1997
|
+
const model = this._d.getModel();
|
|
1998
|
+
const sessionKey = this._d.resolveSessionKey(compilation.request);
|
|
1999
|
+
const modelStartedAt = Date.now();
|
|
2257
2000
|
const spanId = newSpanId();
|
|
2258
2001
|
let attempts = 1;
|
|
2259
2002
|
let ttftAt;
|
|
2260
2003
|
let lastAssistant;
|
|
2261
2004
|
const options = { signal };
|
|
2262
|
-
if (this.
|
|
2263
|
-
options.reasoning = this.
|
|
2005
|
+
if (this._d.getThinkingLevel() !== "off" && model.reasoning) {
|
|
2006
|
+
options.reasoning = this._d.getThinkingLevel();
|
|
2264
2007
|
}
|
|
2265
2008
|
options.onRetryAttempt = (info) => {
|
|
2266
2009
|
attempts += 1;
|
|
2267
|
-
void this.
|
|
2010
|
+
void this._d.telemetry.emit("onRetry", {
|
|
2268
2011
|
traceId: compilation.traceId,
|
|
2269
2012
|
spanId,
|
|
2270
2013
|
// P2:重试明细关联到本模型调用 span
|
|
2271
|
-
provider:
|
|
2272
|
-
modelId:
|
|
2014
|
+
provider: model.provider,
|
|
2015
|
+
modelId: model.id,
|
|
2273
2016
|
attempt: info.attempt,
|
|
2274
2017
|
errorClass: classifyError(info.error),
|
|
2275
2018
|
status: statusOfRetryError(info.error),
|
|
@@ -2278,10 +2021,10 @@ ${text}`,
|
|
|
2278
2021
|
});
|
|
2279
2022
|
};
|
|
2280
2023
|
try {
|
|
2281
|
-
const context = await this.
|
|
2024
|
+
const context = await this._d.hooks.beforeModelCall.promise(
|
|
2282
2025
|
this.buildContext(compilation.messages)
|
|
2283
2026
|
);
|
|
2284
|
-
for await (const event of this.
|
|
2027
|
+
for await (const event of this._d.getStreamFn()(model, context, options)) {
|
|
2285
2028
|
if (stream && event.type === "text_delta" && ttftAt === void 0) {
|
|
2286
2029
|
ttftAt = Date.now();
|
|
2287
2030
|
}
|
|
@@ -2293,11 +2036,11 @@ ${text}`,
|
|
|
2293
2036
|
} finally {
|
|
2294
2037
|
const assistant = lastAssistant;
|
|
2295
2038
|
const errorClass = assistant?.errorMessage ? errorClassFromMessage(assistant.errorMessage) ?? "unknown" : void 0;
|
|
2296
|
-
await this.
|
|
2039
|
+
await this._d.telemetry.emit("onModelCall", {
|
|
2297
2040
|
traceId: compilation.traceId,
|
|
2298
2041
|
spanId,
|
|
2299
2042
|
sessionKey,
|
|
2300
|
-
modelId:
|
|
2043
|
+
modelId: model.id,
|
|
2301
2044
|
attempts,
|
|
2302
2045
|
inputTokens: assistant?.usage?.input ?? 0,
|
|
2303
2046
|
outputTokens: assistant?.usage?.output ?? 0,
|
|
@@ -2312,164 +2055,718 @@ ${text}`,
|
|
|
2312
2055
|
}
|
|
2313
2056
|
}
|
|
2314
2057
|
}
|
|
2058
|
+
buildContext(messages) {
|
|
2059
|
+
const tools = Array.from(this._d.getTools().values());
|
|
2060
|
+
return {
|
|
2061
|
+
systemPrompt: this._d.getSystemPrompt(),
|
|
2062
|
+
// compactionSummary / stateSnapshot 为内部扩展 role,provider 适配层
|
|
2063
|
+
// 仅支持 user/assistant/toolResult,发出前统一转为带标注的 user 消息
|
|
2064
|
+
messages: messages.filter((m) => m.role !== "system").map((m) => {
|
|
2065
|
+
const role = m.role;
|
|
2066
|
+
if (role === "compactionSummary") return this.compactionSummaryToUser(m);
|
|
2067
|
+
if (role === "stateSnapshot") return this.stateSnapshotToUser(m);
|
|
2068
|
+
return m;
|
|
2069
|
+
}),
|
|
2070
|
+
tools: tools.length > 0 ? tools : void 0
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
/** compactionSummary 消息 → user 消息(所有 provider 均兼容 user role) */
|
|
2074
|
+
compactionSummaryToUser(msg) {
|
|
2075
|
+
const text = typeof msg.content === "string" ? msg.content : extractText(msg.content);
|
|
2076
|
+
return {
|
|
2077
|
+
role: "user",
|
|
2078
|
+
content: `${COMPACTION_USER_PREFIX}
|
|
2079
|
+
${text}`,
|
|
2080
|
+
timestamp: msg.timestamp
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
/** stateSnapshot 消息 → user 消息(所有 provider 均兼容 user role) */
|
|
2084
|
+
stateSnapshotToUser(msg) {
|
|
2085
|
+
const text = typeof msg.content === "string" ? msg.content : extractText(msg.content);
|
|
2086
|
+
return {
|
|
2087
|
+
role: "user",
|
|
2088
|
+
content: `${STATE_SNAPSHOT_USER_PREFIX}
|
|
2089
|
+
${text}`,
|
|
2090
|
+
timestamp: msg.timestamp
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
};
|
|
2094
|
+
|
|
2095
|
+
// ../agent/runtime/loops.ts
|
|
2096
|
+
var RunLoops = class {
|
|
2097
|
+
constructor(_d) {
|
|
2098
|
+
this._d = _d;
|
|
2099
|
+
}
|
|
2100
|
+
_d;
|
|
2101
|
+
async run(request, sessionKey, session, traceId, queuedMs) {
|
|
2102
|
+
const activeStartedAt = Date.now();
|
|
2103
|
+
this._d.onSessionActivated(sessionKey);
|
|
2104
|
+
await this._d.hooks.beforeInitialize.promise(request);
|
|
2105
|
+
await this._d.hooks.afterInitialize.promise(request);
|
|
2106
|
+
const finalRequest = await this._d.hooks.beforeRun.promise(request);
|
|
2107
|
+
if (!finalRequest.ephemeral) {
|
|
2108
|
+
await this._d.persistence.hydrate(sessionKey, session);
|
|
2109
|
+
}
|
|
2110
|
+
const compilation = this._d.createCompilation(finalRequest, sessionKey, session, traceId);
|
|
2111
|
+
compilation.messages.push(this._d.buildUserMessage(finalRequest));
|
|
2112
|
+
try {
|
|
2113
|
+
await this.runLoop(compilation, finalRequest, session);
|
|
2114
|
+
const result = this._d.buildResult(compilation);
|
|
2115
|
+
await this._d.hooks.beforeEmit.promise(result);
|
|
2116
|
+
await this._d.hooks.afterEmit.promise(result);
|
|
2117
|
+
await this._d.hooks.done.promise(result, finalRequest);
|
|
2118
|
+
compilation.completed = true;
|
|
2119
|
+
await this.emitRunEnd(finalRequest, sessionKey, compilation, result, queuedMs, activeStartedAt);
|
|
2120
|
+
return result;
|
|
2121
|
+
} catch (err) {
|
|
2122
|
+
const error = err;
|
|
2123
|
+
const message = error?.message ?? String(err);
|
|
2124
|
+
if (error?.name !== "AbortError") {
|
|
2125
|
+
console.error("[Runtime] \u8FD0\u884C\u5931\u8D25:", error?.stack ?? message);
|
|
2126
|
+
}
|
|
2127
|
+
await this._d.hooks.failed.promise(error, finalRequest);
|
|
2128
|
+
const result = new ResultBuilder().error(message).build();
|
|
2129
|
+
await this.emitRunEnd(finalRequest, sessionKey, compilation, result, queuedMs, activeStartedAt);
|
|
2130
|
+
return result;
|
|
2131
|
+
} finally {
|
|
2132
|
+
await this._d.persistence.persistSafe(finalRequest, sessionKey);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
async *stream(request, sessionKey, session, traceId, queuedMs) {
|
|
2136
|
+
const activeStartedAt = Date.now();
|
|
2137
|
+
this._d.onSessionActivated(sessionKey);
|
|
2138
|
+
await this._d.hooks.beforeInitialize.promise(request);
|
|
2139
|
+
await this._d.hooks.afterInitialize.promise(request);
|
|
2140
|
+
const finalRequest = await this._d.hooks.beforeRun.promise(request);
|
|
2141
|
+
if (!finalRequest.ephemeral) {
|
|
2142
|
+
await this._d.persistence.hydrate(sessionKey, session);
|
|
2143
|
+
}
|
|
2144
|
+
const compilation = this._d.createCompilation(finalRequest, sessionKey, session, traceId);
|
|
2145
|
+
compilation.messages.push(this._d.buildUserMessage(finalRequest));
|
|
2146
|
+
try {
|
|
2147
|
+
for await (const chunk of this.runLoopStream(compilation, finalRequest, session)) {
|
|
2148
|
+
yield chunk;
|
|
2149
|
+
}
|
|
2150
|
+
const result = this._d.buildResult(compilation);
|
|
2151
|
+
await this._d.hooks.beforeEmit.promise(result);
|
|
2152
|
+
await this._d.hooks.afterEmit.promise(result);
|
|
2153
|
+
await this._d.hooks.done.promise(result, finalRequest);
|
|
2154
|
+
yield { type: "done", result };
|
|
2155
|
+
await this.emitRunEnd(finalRequest, sessionKey, compilation, result, queuedMs, activeStartedAt);
|
|
2156
|
+
} catch (err) {
|
|
2157
|
+
const error = err;
|
|
2158
|
+
const message = error?.message ?? String(err);
|
|
2159
|
+
if (error?.name !== "AbortError") {
|
|
2160
|
+
console.error("[Runtime] \u6D41\u5F0F\u8FD0\u884C\u5931\u8D25:", error?.stack ?? message);
|
|
2161
|
+
}
|
|
2162
|
+
await this._d.hooks.failed.promise(error, finalRequest);
|
|
2163
|
+
const result = new ResultBuilder().error(message).build();
|
|
2164
|
+
yield { type: "error", content: message };
|
|
2165
|
+
yield { type: "done", result };
|
|
2166
|
+
await this.emitRunEnd(finalRequest, sessionKey, compilation, result, queuedMs, activeStartedAt);
|
|
2167
|
+
} finally {
|
|
2168
|
+
await this._d.persistence.persistSafe(finalRequest, sessionKey);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
// ─── 对话循环(同步) ───────────────────────────────────────────
|
|
2172
|
+
async runLoop(compilation, request, session) {
|
|
2173
|
+
session.isStreaming = true;
|
|
2174
|
+
session.abortController = new AbortController();
|
|
2175
|
+
const sessionKey = this._d.resolveSessionKey(request);
|
|
2176
|
+
try {
|
|
2177
|
+
let maxTurns = this._d.getMaxTurns();
|
|
2178
|
+
let turnCount = 0;
|
|
2179
|
+
while (maxTurns-- > 0) {
|
|
2180
|
+
turnCount += 1;
|
|
2181
|
+
await this._d.transformMessages(compilation, sessionKey);
|
|
2182
|
+
await this._d.compactor.maybeCompactByThreshold(
|
|
2183
|
+
compilation,
|
|
2184
|
+
sessionKey,
|
|
2185
|
+
session.abortController.signal
|
|
2186
|
+
);
|
|
2187
|
+
const assistantMessage = await this._d.modelTurn.streamModel(
|
|
2188
|
+
compilation,
|
|
2189
|
+
session.abortController.signal,
|
|
2190
|
+
sessionKey
|
|
2191
|
+
);
|
|
2192
|
+
compilation.messages.push(assistantMessage);
|
|
2193
|
+
await this._d.persistence.persistSafe(request, sessionKey);
|
|
2194
|
+
const toolCalls = extractToolCalls(assistantMessage.content);
|
|
2195
|
+
if (toolCalls.length === 0) {
|
|
2196
|
+
break;
|
|
2197
|
+
}
|
|
2198
|
+
const outcome = await this.executeToolCalls(
|
|
2199
|
+
compilation,
|
|
2200
|
+
toolCalls,
|
|
2201
|
+
session.abortController.signal
|
|
2202
|
+
);
|
|
2203
|
+
await this._d.persistence.persistSafe(request, sessionKey);
|
|
2204
|
+
if (outcome.terminate) {
|
|
2205
|
+
compilation.terminateReason = outcome.terminateReason ?? "terminated";
|
|
2206
|
+
break;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
if (maxTurns < 0) compilation.maxTurnsExhausted = true;
|
|
2210
|
+
compilation.turnCount = turnCount;
|
|
2211
|
+
} finally {
|
|
2212
|
+
this._d.store.markIdle(session);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
// ─── 对话循环(流式) ───────────────────────────────────────────
|
|
2216
|
+
async *runLoopStream(compilation, request, session) {
|
|
2217
|
+
session.isStreaming = true;
|
|
2218
|
+
session.abortController = new AbortController();
|
|
2219
|
+
const sessionKey = this._d.resolveSessionKey(request);
|
|
2220
|
+
try {
|
|
2221
|
+
let maxTurns = this._d.getMaxTurns();
|
|
2222
|
+
let turnCount = 0;
|
|
2223
|
+
while (maxTurns-- > 0) {
|
|
2224
|
+
turnCount += 1;
|
|
2225
|
+
await this._d.transformMessages(compilation, sessionKey);
|
|
2226
|
+
await this._d.compactor.maybeCompactByThreshold(
|
|
2227
|
+
compilation,
|
|
2228
|
+
sessionKey,
|
|
2229
|
+
session.abortController.signal
|
|
2230
|
+
);
|
|
2231
|
+
const turn = this._d.modelTurn.modelTurnWithRecovery(
|
|
2232
|
+
compilation,
|
|
2233
|
+
session.abortController.signal,
|
|
2234
|
+
sessionKey,
|
|
2235
|
+
true
|
|
2236
|
+
// stream 模式:同时统计首 token 延迟
|
|
2237
|
+
);
|
|
2238
|
+
let turnResult = await turn.next();
|
|
2239
|
+
while (!turnResult.done) {
|
|
2240
|
+
yield turnResult.value;
|
|
2241
|
+
turnResult = await turn.next();
|
|
2242
|
+
}
|
|
2243
|
+
const assistantMessage = turnResult.value;
|
|
2244
|
+
if (!assistantMessage) break;
|
|
2245
|
+
compilation.messages.push(assistantMessage);
|
|
2246
|
+
await this._d.persistence.persistSafe(request, sessionKey);
|
|
2247
|
+
const toolCalls = extractToolCalls(assistantMessage.content);
|
|
2248
|
+
if (toolCalls.length === 0) {
|
|
2249
|
+
break;
|
|
2250
|
+
}
|
|
2251
|
+
for (const toolCall of toolCalls) {
|
|
2252
|
+
yield {
|
|
2253
|
+
type: "tool_start",
|
|
2254
|
+
toolName: toolCall.name,
|
|
2255
|
+
toolCallId: toolCall.id
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2258
|
+
const outcome = await this.executeToolCalls(
|
|
2259
|
+
compilation,
|
|
2260
|
+
toolCalls,
|
|
2261
|
+
session.abortController.signal
|
|
2262
|
+
);
|
|
2263
|
+
await this._d.persistence.persistSafe(request, sessionKey);
|
|
2264
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
2265
|
+
yield {
|
|
2266
|
+
type: "tool_end",
|
|
2267
|
+
toolName: toolCalls[i].name,
|
|
2268
|
+
toolCallId: toolCalls[i].id,
|
|
2269
|
+
isError: this._d.tools.isErrorResult(outcome.results[i])
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
if (outcome.terminate) {
|
|
2273
|
+
compilation.terminateReason = outcome.terminateReason ?? "terminated";
|
|
2274
|
+
break;
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
if (maxTurns < 0) compilation.maxTurnsExhausted = true;
|
|
2278
|
+
compilation.turnCount = turnCount;
|
|
2279
|
+
} finally {
|
|
2280
|
+
this._d.store.markIdle(session);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
/** 同步循环:执行工具调用并将结果消息按原顺序追加 */
|
|
2284
|
+
async executeToolCalls(compilation, toolCalls, signal) {
|
|
2285
|
+
const outcome = await this._d.tools.run(
|
|
2286
|
+
toolCalls,
|
|
2287
|
+
signal,
|
|
2288
|
+
compilation.request,
|
|
2289
|
+
compilation.traceId
|
|
2290
|
+
);
|
|
2291
|
+
for (let i = 0; i < toolCalls.length; i++) {
|
|
2292
|
+
compilation.messages.push(
|
|
2293
|
+
this._d.tools.buildResultMessage(toolCalls[i], outcome.results[i])
|
|
2294
|
+
);
|
|
2295
|
+
}
|
|
2296
|
+
return outcome;
|
|
2297
|
+
}
|
|
2298
|
+
/** run 级完成事件上报(补充默认模型 id) */
|
|
2299
|
+
async emitRunEnd(request, sessionKey, compilation, result, queuedMs, activeStartedAt) {
|
|
2300
|
+
await this._d.telemetry.emitRunEnd(
|
|
2301
|
+
request,
|
|
2302
|
+
sessionKey,
|
|
2303
|
+
compilation,
|
|
2304
|
+
result,
|
|
2305
|
+
queuedMs,
|
|
2306
|
+
activeStartedAt,
|
|
2307
|
+
this._d.getModel().id
|
|
2308
|
+
);
|
|
2309
|
+
}
|
|
2310
|
+
};
|
|
2311
|
+
|
|
2312
|
+
// ../agent/runtime/index.ts
|
|
2313
|
+
var AgentRuntime = class _AgentRuntime {
|
|
2314
|
+
_config;
|
|
2315
|
+
_extensions;
|
|
2316
|
+
_hooks;
|
|
2317
|
+
/** 上下文转换器列表,按数组顺序链式执行(上一个输出作为下一个输入) */
|
|
2318
|
+
_transformers;
|
|
2319
|
+
_model;
|
|
2320
|
+
_streamFn;
|
|
2321
|
+
_systemPrompt;
|
|
2322
|
+
_thinkingLevel;
|
|
2323
|
+
_globalTools = /* @__PURE__ */ new Map();
|
|
2324
|
+
/** 默认会话 key(常量 'default';请求未指定 sessionKey 时路由到此会话) */
|
|
2325
|
+
_sessionKey;
|
|
2326
|
+
_sessionStorage;
|
|
2327
|
+
/** Extension 应用时的上下文(shared Map 供 ToolCallContext 引用) */
|
|
2328
|
+
_extensionContext;
|
|
2329
|
+
_maxTurns;
|
|
2330
|
+
_toolTimeoutMs;
|
|
2331
|
+
_parallelToolCalls;
|
|
2332
|
+
_contextBudgetRatio;
|
|
2333
|
+
/** 内置摘要压缩配置(未配置 = 保持旧行为,仅硬截断兜底) */
|
|
2334
|
+
_compaction;
|
|
2335
|
+
_telemetry;
|
|
2336
|
+
/** 框架级工具权限策略(未配置 → 默认 fail-closed,见 permissionFailOpen) */
|
|
2337
|
+
_permissionPolicy;
|
|
2338
|
+
/** 未配置策略时是否显式放行工具执行(默认 false = 拒绝,安全姿态) */
|
|
2339
|
+
_permissionFailOpen;
|
|
2340
|
+
/** 审批管理器(pending 决策挂起等待外部批准;未配置 → pending 视为 deny) */
|
|
2341
|
+
_approvals;
|
|
2342
|
+
/** 审批等待超时(毫秒) */
|
|
2343
|
+
_approvalTimeoutMs;
|
|
2344
|
+
/** traceId 生成器(测试可注入确定性 id) */
|
|
2345
|
+
_traceIdGenerator;
|
|
2346
|
+
// ─── 协作模块(构造时组装,职责见文件头注释) ───────────────────
|
|
2347
|
+
_store;
|
|
2348
|
+
_persistence;
|
|
2349
|
+
_emitter;
|
|
2350
|
+
_tools;
|
|
2351
|
+
_compactor;
|
|
2352
|
+
_modelTurn;
|
|
2353
|
+
_loops;
|
|
2354
|
+
constructor(options) {
|
|
2355
|
+
this._config = options.config ?? {};
|
|
2356
|
+
this._extensions = new ExtensionManager();
|
|
2357
|
+
this._hooks = this._extensions.getHooks();
|
|
2358
|
+
this._transformers = [...options.transformers ?? []];
|
|
2359
|
+
this._model = options.model ?? {
|
|
2360
|
+
id: "unknown",
|
|
2361
|
+
name: "unknown",
|
|
2362
|
+
provider: "unknown",
|
|
2363
|
+
contextWindow: 128e3,
|
|
2364
|
+
maxTokens: 8192,
|
|
2365
|
+
reasoning: false
|
|
2366
|
+
};
|
|
2367
|
+
this._streamFn = options.streamFn ?? (async function* () {
|
|
2368
|
+
throw new Error("streamFn \u672A\u8BBE\u7F6E\uFF0C\u8BF7\u901A\u8FC7 setStreamFn() \u6216 RuntimeOptions.streamFn \u63D0\u4F9B");
|
|
2369
|
+
});
|
|
2370
|
+
this._systemPrompt = options.systemPrompt ?? "";
|
|
2371
|
+
this._thinkingLevel = options.thinkingLevel ?? "off";
|
|
2372
|
+
this._sessionStorage = options.sessionStorage;
|
|
2373
|
+
this._maxTurns = options.maxTurns ?? 50;
|
|
2374
|
+
this._toolTimeoutMs = options.toolTimeoutMs ?? 12e4;
|
|
2375
|
+
this._parallelToolCalls = options.parallelToolCalls ?? true;
|
|
2376
|
+
this._contextBudgetRatio = options.contextBudgetRatio ?? 0.8;
|
|
2377
|
+
this._compaction = options.compaction;
|
|
2378
|
+
this._telemetry = options.telemetry;
|
|
2379
|
+
this._permissionPolicy = options.permissionPolicy;
|
|
2380
|
+
this._permissionFailOpen = options.permissionFailOpen ?? false;
|
|
2381
|
+
this._approvals = options.approvals;
|
|
2382
|
+
this._approvalTimeoutMs = options.approvalTimeoutMs ?? 3e5;
|
|
2383
|
+
this._traceIdGenerator = options.traceIdGenerator;
|
|
2384
|
+
if (options.tools) {
|
|
2385
|
+
for (const tool of options.tools) {
|
|
2386
|
+
this._globalTools.set(tool.name, tool);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
this._sessionKey = "default";
|
|
2390
|
+
this._emitter = new RuntimeTelemetry(options.telemetry, options.traceIdGenerator);
|
|
2391
|
+
setTapErrorHandler((info) => {
|
|
2392
|
+
void this._emitter.emit("onHookError", {
|
|
2393
|
+
sessionKey: this._sessionKey,
|
|
2394
|
+
hook: info.hook,
|
|
2395
|
+
tap: info.tap,
|
|
2396
|
+
error: info.error
|
|
2397
|
+
});
|
|
2398
|
+
});
|
|
2399
|
+
this._store = new SessionStore(this._sessionKey, options.maxSessions);
|
|
2400
|
+
this._persistence = new SessionPersistence(
|
|
2401
|
+
this._sessionStorage,
|
|
2402
|
+
() => this._model,
|
|
2403
|
+
this._store
|
|
2404
|
+
);
|
|
2405
|
+
this._tools = new ToolExecutor({
|
|
2406
|
+
hooks: this._hooks,
|
|
2407
|
+
telemetry: this._emitter,
|
|
2408
|
+
permissionPolicy: this._permissionPolicy,
|
|
2409
|
+
permissionFailOpen: this._permissionFailOpen,
|
|
2410
|
+
approvals: this._approvals,
|
|
2411
|
+
approvalTimeoutMs: this._approvalTimeoutMs,
|
|
2412
|
+
toolTimeoutMs: this._toolTimeoutMs,
|
|
2413
|
+
parallelToolCalls: this._parallelToolCalls,
|
|
2414
|
+
defaultSessionKey: this._sessionKey,
|
|
2415
|
+
getTools: () => this._globalTools,
|
|
2416
|
+
getShared: () => this._extensionContext?.shared
|
|
2417
|
+
});
|
|
2418
|
+
this._compactor = new ContextCompactor({
|
|
2419
|
+
telemetry: this._emitter,
|
|
2420
|
+
getModel: () => this._model,
|
|
2421
|
+
getStreamFn: () => this._streamFn,
|
|
2422
|
+
contextBudgetRatio: this._contextBudgetRatio,
|
|
2423
|
+
compaction: this._compaction
|
|
2424
|
+
});
|
|
2425
|
+
this._modelTurn = new ModelTurnRunner({
|
|
2426
|
+
telemetry: this._emitter,
|
|
2427
|
+
compactor: this._compactor,
|
|
2428
|
+
hooks: this._hooks,
|
|
2429
|
+
getModel: () => this._model,
|
|
2430
|
+
getStreamFn: () => this._streamFn,
|
|
2431
|
+
getSystemPrompt: () => this._systemPrompt,
|
|
2432
|
+
getThinkingLevel: () => this._thinkingLevel,
|
|
2433
|
+
getTools: () => this._globalTools,
|
|
2434
|
+
resolveSessionKey: (request) => this.resolveSessionKey(request)
|
|
2435
|
+
});
|
|
2436
|
+
this._loops = new RunLoops({
|
|
2437
|
+
hooks: this._hooks,
|
|
2438
|
+
telemetry: this._emitter,
|
|
2439
|
+
store: this._store,
|
|
2440
|
+
persistence: this._persistence,
|
|
2441
|
+
tools: this._tools,
|
|
2442
|
+
compactor: this._compactor,
|
|
2443
|
+
modelTurn: this._modelTurn,
|
|
2444
|
+
getModel: () => this._model,
|
|
2445
|
+
getMaxTurns: () => this._maxTurns,
|
|
2446
|
+
resolveSessionKey: (request) => this.resolveSessionKey(request),
|
|
2447
|
+
onSessionActivated: (key) => this.syncExtensionSessionKey(key),
|
|
2448
|
+
createCompilation: (request, sessionKey, session, traceId) => this.createCompilation(request, sessionKey, session, traceId),
|
|
2449
|
+
buildUserMessage: (request) => buildUserMessage(request),
|
|
2450
|
+
buildResult: (compilation) => buildResult(compilation),
|
|
2451
|
+
transformMessages: (compilation, sessionKey) => this.transformMessages(compilation, sessionKey)
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
// ─── 静态工厂 ───────────────────────────────────────────────────
|
|
2455
|
+
static create(options = {}) {
|
|
2456
|
+
const runtime = new _AgentRuntime(options);
|
|
2457
|
+
if (options.extensions) {
|
|
2458
|
+
runtime._extensions.registerAll(options.extensions);
|
|
2459
|
+
}
|
|
2460
|
+
const ctx = {
|
|
2461
|
+
config: runtime._config,
|
|
2462
|
+
workspace: options.workspace ?? process.cwd(),
|
|
2463
|
+
sessionKey: runtime._sessionKey,
|
|
2464
|
+
shared: /* @__PURE__ */ new Map(),
|
|
2465
|
+
runtime
|
|
2466
|
+
};
|
|
2467
|
+
runtime._extensionContext = ctx;
|
|
2468
|
+
runtime._extensions.applyAll(ctx);
|
|
2469
|
+
return runtime;
|
|
2470
|
+
}
|
|
2471
|
+
// ─── Runtime 接口实现 ───────────────────────────────────────────
|
|
2472
|
+
get config() {
|
|
2473
|
+
return this._config;
|
|
2474
|
+
}
|
|
2475
|
+
get extensions() {
|
|
2476
|
+
return this._extensions;
|
|
2477
|
+
}
|
|
2478
|
+
get hooks() {
|
|
2479
|
+
return this._hooks;
|
|
2480
|
+
}
|
|
2481
|
+
// ─── 工具/模型/流管理 ───────────────────────────────────────────
|
|
2482
|
+
registerTool(tool) {
|
|
2483
|
+
if (this._globalTools.has(tool.name)) {
|
|
2484
|
+
console.warn(`[Runtime] \u5DE5\u5177 "${tool.name}" \u5DF2\u5B58\u5728\uFF0C\u5C06\u88AB\u8986\u76D6`);
|
|
2485
|
+
}
|
|
2486
|
+
this._globalTools.set(tool.name, tool);
|
|
2487
|
+
return this;
|
|
2488
|
+
}
|
|
2489
|
+
registerTools(tools) {
|
|
2490
|
+
for (const tool of tools) {
|
|
2491
|
+
this.registerTool(tool);
|
|
2492
|
+
}
|
|
2493
|
+
return this;
|
|
2494
|
+
}
|
|
2495
|
+
unregisterTool(name) {
|
|
2496
|
+
return this._globalTools.delete(name);
|
|
2497
|
+
}
|
|
2498
|
+
setModel(model) {
|
|
2499
|
+
this._model = model;
|
|
2500
|
+
return this;
|
|
2501
|
+
}
|
|
2502
|
+
setSystemPrompt(prompt) {
|
|
2503
|
+
this._systemPrompt = prompt;
|
|
2504
|
+
return this;
|
|
2505
|
+
}
|
|
2506
|
+
setThinkingLevel(level) {
|
|
2507
|
+
this._thinkingLevel = level;
|
|
2508
|
+
return this;
|
|
2509
|
+
}
|
|
2510
|
+
setStreamFn(fn) {
|
|
2511
|
+
this._streamFn = fn;
|
|
2512
|
+
return this;
|
|
2513
|
+
}
|
|
2514
|
+
registerExtension(extension) {
|
|
2515
|
+
this._extensions.register(extension);
|
|
2516
|
+
return this;
|
|
2517
|
+
}
|
|
2518
|
+
useTransformer(transformer) {
|
|
2519
|
+
this._transformers.push(transformer);
|
|
2520
|
+
return this;
|
|
2521
|
+
}
|
|
2522
|
+
/**
|
|
2523
|
+
* 获取指定会话的消息列表(默认会话;会话不存在返回空数组)。
|
|
2524
|
+
* 同步方法:会话被 LRU 淘汰且配置了存储时,后台异步恢复(下次调用可读到),
|
|
2525
|
+
* 需确定性读取(如断言/导出)请用 loadMessages()。
|
|
2526
|
+
*/
|
|
2527
|
+
getMessages(sessionKey) {
|
|
2528
|
+
const key = sessionKey ?? this._sessionKey;
|
|
2529
|
+
if (this._store.has(key)) {
|
|
2530
|
+
return this._store.getMessagesCopy(key);
|
|
2531
|
+
}
|
|
2532
|
+
if (this._sessionStorage) {
|
|
2533
|
+
const session = this._store.ensure(key);
|
|
2534
|
+
void this._persistence.hydrate(key, session).catch(() => {
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2537
|
+
return [];
|
|
2538
|
+
}
|
|
2539
|
+
/** 异步读取指定会话的消息列表:内存未命中(LRU 淘汰)时等待存储恢复后返回 */
|
|
2540
|
+
async loadMessages(sessionKey) {
|
|
2541
|
+
const key = sessionKey ?? this._sessionKey;
|
|
2542
|
+
const existing = this._store.peek(key);
|
|
2543
|
+
if (existing && existing.hydrated) {
|
|
2544
|
+
return this._store.getMessagesCopy(key);
|
|
2545
|
+
}
|
|
2546
|
+
const session = existing ?? this._store.ensure(key);
|
|
2547
|
+
await this._persistence.hydrate(key, session);
|
|
2548
|
+
return this._store.getMessagesCopy(key);
|
|
2549
|
+
}
|
|
2315
2550
|
/**
|
|
2316
|
-
*
|
|
2551
|
+
* 指定会话消息的只读视图(高频轮询用):浅拷贝数组、共享消息对象,
|
|
2552
|
+
* 避免每次 structuredClone 深拷贝整个会话历史;调用方不得修改内容。
|
|
2317
2553
|
*/
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2554
|
+
peekMessages(sessionKey) {
|
|
2555
|
+
return this._store.peekMessages(sessionKey ?? this._sessionKey);
|
|
2556
|
+
}
|
|
2557
|
+
/**
|
|
2558
|
+
* 同步共享扩展上下文的会话键为本次 run/stream 的真实会话。
|
|
2559
|
+
* 注意:ExtensionContext 为跨会话共享单例,多会话并发时该值为最近激活的
|
|
2560
|
+
* 会话;需精确会话请用 ToolCallContext.sessionKey / Request.sessionKey。
|
|
2561
|
+
*/
|
|
2562
|
+
syncExtensionSessionKey(sessionKey) {
|
|
2563
|
+
if (this._extensionContext && this._extensionContext.sessionKey !== sessionKey) {
|
|
2564
|
+
this._extensionContext.sessionKey = sessionKey;
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
// ─── 核心运行逻辑 ───────────────────────────────────────────────
|
|
2568
|
+
/** 解析请求路由的会话 key:request.sessionKey ?? 默认会话 key */
|
|
2569
|
+
resolveSessionKey(request) {
|
|
2570
|
+
return request.sessionKey ?? this._sessionKey;
|
|
2571
|
+
}
|
|
2572
|
+
/** 当前活跃的会话 key 列表(含默认会话) */
|
|
2573
|
+
getSessionKeys() {
|
|
2574
|
+
return this._store.keys();
|
|
2575
|
+
}
|
|
2576
|
+
/** 某会话是否存在(内存中) */
|
|
2577
|
+
hasSession(sessionKey) {
|
|
2578
|
+
return this._store.has(sessionKey);
|
|
2579
|
+
}
|
|
2580
|
+
async run(request) {
|
|
2581
|
+
const validation = validateRequest(request);
|
|
2582
|
+
if (!validation.valid) {
|
|
2583
|
+
const invalidResult = new ResultBuilder().error(`\u8BF7\u6C42\u6821\u9A8C\u5931\u8D25: ${validation.errors.join("; ")}`).build();
|
|
2584
|
+
await this._emitter.emit("onRunEnd", {
|
|
2585
|
+
traceId: this._emitter.newTraceId(),
|
|
2586
|
+
sessionKey: this.resolveSessionKey(request),
|
|
2587
|
+
request,
|
|
2588
|
+
durationMs: 0,
|
|
2589
|
+
activeMs: 0,
|
|
2590
|
+
queuedMs: 0,
|
|
2591
|
+
turnCount: 0,
|
|
2592
|
+
result: invalidResult,
|
|
2593
|
+
success: false,
|
|
2594
|
+
errorClass: "validation",
|
|
2595
|
+
tokens: { input: 0, output: 0 }
|
|
2596
|
+
});
|
|
2597
|
+
return invalidResult;
|
|
2598
|
+
}
|
|
2599
|
+
const finalRequest = normalizeRequest(request);
|
|
2600
|
+
const sessionKey = this.resolveSessionKey(finalRequest);
|
|
2601
|
+
const traceId = this._emitter.newTraceId();
|
|
2602
|
+
const queuedAt = Date.now();
|
|
2603
|
+
await this._emitter.emit("onRunStart", {
|
|
2604
|
+
traceId,
|
|
2605
|
+
sessionKey,
|
|
2606
|
+
request: finalRequest,
|
|
2607
|
+
queuedAt
|
|
2608
|
+
});
|
|
2609
|
+
const session = this._store.ensure(sessionKey);
|
|
2610
|
+
const release = await this._store.acquire(session);
|
|
2611
|
+
const queuedMs = Date.now() - queuedAt;
|
|
2612
|
+
try {
|
|
2613
|
+
return await this._persistence.withStorageLock(
|
|
2614
|
+
finalRequest,
|
|
2615
|
+
sessionKey,
|
|
2616
|
+
() => this._loops.run(finalRequest, sessionKey, session, traceId, queuedMs)
|
|
2617
|
+
);
|
|
2618
|
+
} finally {
|
|
2619
|
+
release();
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
async *stream(request) {
|
|
2623
|
+
const validation = validateRequest(request);
|
|
2624
|
+
if (!validation.valid) {
|
|
2625
|
+
const message = `\u8BF7\u6C42\u6821\u9A8C\u5931\u8D25: ${validation.errors.join("; ")}`;
|
|
2626
|
+
await this._emitter.emit("onRunEnd", {
|
|
2627
|
+
traceId: this._emitter.newTraceId(),
|
|
2628
|
+
sessionKey: this.resolveSessionKey(request),
|
|
2629
|
+
request,
|
|
2630
|
+
durationMs: 0,
|
|
2631
|
+
activeMs: 0,
|
|
2632
|
+
queuedMs: 0,
|
|
2633
|
+
turnCount: 0,
|
|
2634
|
+
result: new ResultBuilder().error(message).build(),
|
|
2635
|
+
success: false,
|
|
2636
|
+
errorClass: "validation",
|
|
2637
|
+
tokens: { input: 0, output: 0 }
|
|
2638
|
+
});
|
|
2639
|
+
yield { type: "error", content: message };
|
|
2640
|
+
yield { type: "done" };
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
const finalRequest = normalizeRequest(request);
|
|
2644
|
+
const sessionKey = this.resolveSessionKey(finalRequest);
|
|
2645
|
+
const traceId = this._emitter.newTraceId();
|
|
2646
|
+
const queuedAt = Date.now();
|
|
2647
|
+
await this._emitter.emit("onRunStart", {
|
|
2648
|
+
traceId,
|
|
2649
|
+
sessionKey,
|
|
2650
|
+
request: finalRequest,
|
|
2651
|
+
queuedAt
|
|
2652
|
+
});
|
|
2653
|
+
const session = this._store.ensure(sessionKey);
|
|
2654
|
+
const release = await this._store.acquire(session);
|
|
2655
|
+
const queuedMs = Date.now() - queuedAt;
|
|
2321
2656
|
try {
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2657
|
+
yield* this._persistence.streamWithStorageLock(
|
|
2658
|
+
finalRequest,
|
|
2659
|
+
sessionKey,
|
|
2660
|
+
() => this._loops.stream(finalRequest, sessionKey, session, traceId, queuedMs)
|
|
2661
|
+
);
|
|
2662
|
+
} finally {
|
|
2663
|
+
release();
|
|
2325
2664
|
}
|
|
2326
2665
|
}
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2666
|
+
createCompilation(request, sessionKey, session, traceId) {
|
|
2667
|
+
return {
|
|
2668
|
+
request,
|
|
2669
|
+
graph: createTaskGraph(),
|
|
2670
|
+
resources: [],
|
|
2671
|
+
messages: (session ?? this._store.ensure(sessionKey ?? this._sessionKey)).messages,
|
|
2672
|
+
completed: false,
|
|
2673
|
+
traceId: traceId ?? this._emitter.newTraceId()
|
|
2674
|
+
};
|
|
2330
2675
|
}
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
traceId: compilation.traceId,
|
|
2336
|
-
sessionKey,
|
|
2337
|
-
// 请求未显式指定 model 时补实际模型(模型排行按 run 级 requests 统计,缺省会落入 'unknown')
|
|
2338
|
-
request: request.model ? request : { ...request, model: this._model.id },
|
|
2339
|
-
durationMs: activeMs + queuedMs,
|
|
2340
|
-
activeMs,
|
|
2341
|
-
queuedMs,
|
|
2342
|
-
turnCount: compilation.turnCount ?? 0,
|
|
2343
|
-
result,
|
|
2344
|
-
success: result.success,
|
|
2345
|
-
errorClass: this.runErrorClass(compilation),
|
|
2346
|
-
tokens: {
|
|
2347
|
-
input: result.usage.input ?? 0,
|
|
2348
|
-
output: result.usage.output ?? 0,
|
|
2349
|
-
cacheRead: result.usage.cacheRead,
|
|
2350
|
-
cacheWrite: result.usage.cacheWrite
|
|
2351
|
-
},
|
|
2352
|
-
ttftMs: compilation.ttftMs
|
|
2353
|
-
});
|
|
2676
|
+
async close() {
|
|
2677
|
+
await this._store.drainAndClear();
|
|
2678
|
+
this._extensions.clear();
|
|
2679
|
+
this._transformers = [];
|
|
2354
2680
|
}
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
const m = messages[i];
|
|
2361
|
-
if (m.role === "assistant") {
|
|
2362
|
-
if (!m.errorMessage) return void 0;
|
|
2363
|
-
const cls = errorClassFromMessage(m.errorMessage);
|
|
2364
|
-
return cls ?? "unknown";
|
|
2365
|
-
}
|
|
2681
|
+
// ─── 上下文转换(链式执行,原地替换保持 session 引用) ──────────
|
|
2682
|
+
/** 链式执行上下文转换器(按数组顺序,上一个输出作为下一个输入) */
|
|
2683
|
+
async transformMessages(compilation, sessionKey) {
|
|
2684
|
+
if (this._transformers.length === 0) {
|
|
2685
|
+
return;
|
|
2366
2686
|
}
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
if (msg.role === "assistant") {
|
|
2378
|
-
const assistant = msg;
|
|
2379
|
-
content = extractText(assistant.content);
|
|
2380
|
-
stopReason = assistant.stopReason ?? "completed";
|
|
2381
|
-
error = assistant.errorMessage;
|
|
2382
|
-
if (assistant.usage) {
|
|
2383
|
-
usage.input = (usage.input ?? 0) + assistant.usage.input;
|
|
2384
|
-
usage.output = (usage.output ?? 0) + assistant.usage.output;
|
|
2385
|
-
usage.total = (usage.total ?? 0) + assistant.usage.total;
|
|
2386
|
-
if (assistant.usage.cacheRead) usage.cacheRead = (usage.cacheRead ?? 0) + assistant.usage.cacheRead;
|
|
2387
|
-
if (assistant.usage.cacheWrite) usage.cacheWrite = (usage.cacheWrite ?? 0) + assistant.usage.cacheWrite;
|
|
2388
|
-
}
|
|
2687
|
+
let resources = messagesToResources(compilation.messages);
|
|
2688
|
+
const originalResources = resources;
|
|
2689
|
+
const context = {
|
|
2690
|
+
graph: compilation.graph,
|
|
2691
|
+
runtime: {
|
|
2692
|
+
sessionKey,
|
|
2693
|
+
turn: compilation.messages.length,
|
|
2694
|
+
contextWindow: this._model.contextWindow,
|
|
2695
|
+
maxTokens: this._model.maxTokens,
|
|
2696
|
+
contextBudgetRatio: this._contextBudgetRatio
|
|
2389
2697
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2698
|
+
};
|
|
2699
|
+
for (const transformer of this._transformers) {
|
|
2700
|
+
try {
|
|
2701
|
+
const next = await transformer.transform(resources, context);
|
|
2702
|
+
if (next !== resources) resources = next;
|
|
2703
|
+
} catch (err) {
|
|
2704
|
+
console.warn(
|
|
2705
|
+
`[Runtime] \u8F6C\u6362\u5668 "${transformer.name}" \u5931\u8D25\uFF0C\u5DF2\u8DF3\u8FC7:`,
|
|
2706
|
+
err?.message ?? err
|
|
2707
|
+
);
|
|
2395
2708
|
}
|
|
2396
2709
|
}
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
if (compilation.terminateReason) {
|
|
2400
|
-
builder.stopReason("terminated").metadata("terminateReason", compilation.terminateReason);
|
|
2401
|
-
}
|
|
2402
|
-
if (compilation.maxTurnsExhausted) {
|
|
2403
|
-
builder.stopReason("max_turns").metadata("maxTurns", true);
|
|
2710
|
+
if (resources === originalResources) {
|
|
2711
|
+
return;
|
|
2404
2712
|
}
|
|
2405
|
-
|
|
2713
|
+
const messages = resourcesToMessages(resources);
|
|
2714
|
+
compilation.messages.splice(0, compilation.messages.length, ...messages);
|
|
2406
2715
|
}
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2716
|
+
// ─── 手动压缩(交互命令 /compact 等) ───────────────────────────
|
|
2717
|
+
/**
|
|
2718
|
+
* 手动压缩指定会话:跳过阈值判断,直接将历史压缩至 targetRatio
|
|
2719
|
+
* 保留量(复用 compactor 的保留段计算与 compactOrTruncate 执行路径),
|
|
2720
|
+
* 压缩后持久化。返回压缩模式;compaction 未启用或无可压缩段时返回 null。
|
|
2721
|
+
*/
|
|
2722
|
+
async compact(sessionKey) {
|
|
2723
|
+
if (!this._compaction || this._compaction.enabled === false) return null;
|
|
2724
|
+
const key = sessionKey ?? this._sessionKey;
|
|
2725
|
+
const session = this._store.ensure(key);
|
|
2726
|
+
const messages = session.messages;
|
|
2727
|
+
if (messages.length === 0) return null;
|
|
2728
|
+
const contextWindow = this._model.contextWindow;
|
|
2729
|
+
if (!contextWindow || contextWindow <= 0) return null;
|
|
2730
|
+
const split = this._compactor.computeKeepSplit(messages, contextWindow);
|
|
2731
|
+
if (split <= 0 || split >= messages.length) return null;
|
|
2732
|
+
const mode = await this._compactor.compactOrTruncate(
|
|
2733
|
+
messages,
|
|
2734
|
+
split,
|
|
2735
|
+
key,
|
|
2736
|
+
`manual-${Date.now().toString(36)}`,
|
|
2737
|
+
new AbortController().signal,
|
|
2738
|
+
"threshold"
|
|
2739
|
+
);
|
|
2740
|
+
try {
|
|
2741
|
+
await this._persistence.persist(key, session);
|
|
2742
|
+
} catch (err) {
|
|
2743
|
+
console.warn("[Runtime] \u624B\u52A8\u538B\u7F29\u540E\u6301\u4E45\u5316\u5931\u8D25:", err?.message);
|
|
2420
2744
|
}
|
|
2745
|
+
return mode;
|
|
2421
2746
|
}
|
|
2422
2747
|
// ─── 便捷方法 ───────────────────────────────────────────────────
|
|
2423
2748
|
/** 终止指定会话的运行(默认会话;会话不存在为 no-op) */
|
|
2424
2749
|
abort(sessionKey) {
|
|
2425
|
-
this.
|
|
2750
|
+
this._store.peek(sessionKey ?? this._sessionKey)?.abortController?.abort();
|
|
2426
2751
|
}
|
|
2427
2752
|
/** 检查指定会话是否正在运行(默认会话;会话不存在返回 false) */
|
|
2428
2753
|
isBusy(sessionKey) {
|
|
2429
|
-
return this.
|
|
2754
|
+
return this._store.peek(sessionKey ?? this._sessionKey)?.isStreaming ?? false;
|
|
2430
2755
|
}
|
|
2431
2756
|
/** 等待指定会话空闲(默认会话;基于 promise,无轮询) */
|
|
2432
2757
|
async waitForIdle(sessionKey, timeoutMs) {
|
|
2433
|
-
|
|
2434
|
-
if (!session || !session.isStreaming) return;
|
|
2435
|
-
if (timeoutMs === void 0) {
|
|
2436
|
-
await new Promise((resolve) => {
|
|
2437
|
-
session.idleResolvers.push(resolve);
|
|
2438
|
-
});
|
|
2439
|
-
return;
|
|
2440
|
-
}
|
|
2441
|
-
await new Promise((resolve, reject) => {
|
|
2442
|
-
const resolver = () => {
|
|
2443
|
-
clearTimeout(timer);
|
|
2444
|
-
resolve();
|
|
2445
|
-
};
|
|
2446
|
-
const timer = setTimeout(() => {
|
|
2447
|
-
const i = session.idleResolvers.indexOf(resolver);
|
|
2448
|
-
if (i >= 0) session.idleResolvers.splice(i, 1);
|
|
2449
|
-
reject(new Error(
|
|
2450
|
-
`[Runtime] waitForIdle \u8D85\u65F6\uFF08${timeoutMs}ms\uFF09: ${sessionKey ?? "default"}`
|
|
2451
|
-
));
|
|
2452
|
-
}, timeoutMs);
|
|
2453
|
-
session.idleResolvers.push(resolver);
|
|
2454
|
-
});
|
|
2758
|
+
await this._store.waitForIdle(sessionKey ?? this._sessionKey, timeoutMs);
|
|
2455
2759
|
}
|
|
2456
2760
|
/** 清除指定会话消息(仅内存;下次 run 会从存储恢复) */
|
|
2457
2761
|
clearSession(sessionKey) {
|
|
2458
|
-
|
|
2459
|
-
if (!session) return;
|
|
2460
|
-
session.messages = [];
|
|
2461
|
-
session.hydrated = false;
|
|
2762
|
+
this._store.clearMessages(sessionKey ?? this._sessionKey);
|
|
2462
2763
|
}
|
|
2463
2764
|
/** 删除指定会话(内存 + 存储),返回是否删除成功 */
|
|
2464
2765
|
async deleteSession(sessionKey) {
|
|
2465
2766
|
const key = sessionKey ?? this._sessionKey;
|
|
2466
|
-
|
|
2467
|
-
if (session) {
|
|
2468
|
-
await session.queue;
|
|
2469
|
-
this._sessions.delete(key);
|
|
2470
|
-
}
|
|
2471
|
-
if (!this._sessionStorage) return true;
|
|
2767
|
+
await this._store.remove(key);
|
|
2472
2768
|
const storage = this._sessionStorage;
|
|
2769
|
+
if (!storage) return true;
|
|
2473
2770
|
if (!storage.withLock) return storage.delete(key);
|
|
2474
2771
|
return storage.withLock(key, () => storage.delete(key));
|
|
2475
2772
|
}
|
|
@@ -2478,7 +2775,55 @@ function createRuntime(options) {
|
|
|
2478
2775
|
return AgentRuntime.create(options);
|
|
2479
2776
|
}
|
|
2480
2777
|
|
|
2778
|
+
// core/event-stream.ts
|
|
2779
|
+
function createEventStream(execute, sink) {
|
|
2780
|
+
const eventQueue = [];
|
|
2781
|
+
let resolveEvent = null;
|
|
2782
|
+
let done = false;
|
|
2783
|
+
const emit = (event) => {
|
|
2784
|
+
eventQueue.push(event);
|
|
2785
|
+
if (sink) {
|
|
2786
|
+
try {
|
|
2787
|
+
sink(event);
|
|
2788
|
+
} catch {
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
resolveEvent?.();
|
|
2792
|
+
};
|
|
2793
|
+
const graphPromise = execute(emit).finally(() => {
|
|
2794
|
+
done = true;
|
|
2795
|
+
resolveEvent?.();
|
|
2796
|
+
});
|
|
2797
|
+
return (async function* () {
|
|
2798
|
+
while (!done || eventQueue.length > 0) {
|
|
2799
|
+
if (eventQueue.length > 0) {
|
|
2800
|
+
yield eventQueue.shift();
|
|
2801
|
+
} else {
|
|
2802
|
+
await new Promise((resolve) => {
|
|
2803
|
+
resolveEvent = resolve;
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
await graphPromise;
|
|
2808
|
+
})();
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2481
2811
|
// core/executor.ts
|
|
2812
|
+
var GraphAbortedError = class extends Error {
|
|
2813
|
+
constructor() {
|
|
2814
|
+
super("AgentGraph: \u6267\u884C\u5DF2\u88AB\u4E2D\u6B62");
|
|
2815
|
+
this.name = "GraphAbortedError";
|
|
2816
|
+
}
|
|
2817
|
+
};
|
|
2818
|
+
function toInputText(input) {
|
|
2819
|
+
return typeof input === "string" ? input : input.message;
|
|
2820
|
+
}
|
|
2821
|
+
var NodeTimeoutError = class extends Error {
|
|
2822
|
+
constructor(nodeId, timeoutMs) {
|
|
2823
|
+
super(`AgentGraph: \u8282\u70B9 "${nodeId}" \u6267\u884C\u8D85\u65F6\uFF08${timeoutMs}ms\uFF09`);
|
|
2824
|
+
this.name = "NodeTimeoutError";
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2482
2827
|
var runtimeCache = /* @__PURE__ */ new WeakMap();
|
|
2483
2828
|
function ensureRuntime(node) {
|
|
2484
2829
|
let rt = runtimeCache.get(node);
|
|
@@ -2491,19 +2836,52 @@ function ensureRuntime(node) {
|
|
|
2491
2836
|
runtimeCache.set(node, rt);
|
|
2492
2837
|
return rt;
|
|
2493
2838
|
}
|
|
2494
|
-
async function
|
|
2839
|
+
async function executeNodeOnce(node, req) {
|
|
2495
2840
|
const runtime = ensureRuntime(node);
|
|
2841
|
+
if (node.timeoutMs != null && node.timeoutMs > 0) {
|
|
2842
|
+
let timer;
|
|
2843
|
+
try {
|
|
2844
|
+
return await Promise.race([
|
|
2845
|
+
runtime.run(req),
|
|
2846
|
+
new Promise((_, reject) => {
|
|
2847
|
+
timer = setTimeout(() => reject(new NodeTimeoutError(node.id, node.timeoutMs)), node.timeoutMs);
|
|
2848
|
+
})
|
|
2849
|
+
]);
|
|
2850
|
+
} finally {
|
|
2851
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
return runtime.run(req);
|
|
2855
|
+
}
|
|
2856
|
+
async function executeNode(node, input, ctx) {
|
|
2496
2857
|
let req;
|
|
2497
2858
|
if (typeof input === "string") {
|
|
2498
2859
|
req = createRequest(input, { sessionKey: `multi-agent:${node.id}` });
|
|
2499
2860
|
} else {
|
|
2500
2861
|
req = { ...input, sessionKey: input.sessionKey ?? `multi-agent:${node.id}` };
|
|
2501
2862
|
}
|
|
2502
|
-
const
|
|
2863
|
+
const maxAttempts = Math.max(1, node.retry?.maxAttempts ?? 1);
|
|
2864
|
+
const backoffMs = node.retry?.backoffMs ?? 0;
|
|
2865
|
+
let lastErr;
|
|
2866
|
+
let succeeded;
|
|
2867
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2868
|
+
if (attempt > 1 && backoffMs > 0) {
|
|
2869
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
2870
|
+
}
|
|
2871
|
+
try {
|
|
2872
|
+
succeeded = await executeNodeOnce(node, req);
|
|
2873
|
+
break;
|
|
2874
|
+
} catch (err) {
|
|
2875
|
+
lastErr = err;
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
if (succeeded === void 0) {
|
|
2879
|
+
throw lastErr;
|
|
2880
|
+
}
|
|
2503
2881
|
if (node.outputMapping) {
|
|
2504
|
-
node.outputMapping(
|
|
2882
|
+
node.outputMapping(succeeded, ctx);
|
|
2505
2883
|
}
|
|
2506
|
-
return
|
|
2884
|
+
return succeeded;
|
|
2507
2885
|
}
|
|
2508
2886
|
function resolveInput(edge, prevResult, ctx, node, originalInput) {
|
|
2509
2887
|
if (edge?.transform) {
|
|
@@ -2530,12 +2908,22 @@ var GraphExecutor = class {
|
|
|
2530
2908
|
entryId;
|
|
2531
2909
|
finishCondition;
|
|
2532
2910
|
abortController = new AbortController();
|
|
2911
|
+
/** 外部事件监听(on() API 的底层接线),事件会同步分发 */
|
|
2912
|
+
eventSink;
|
|
2913
|
+
/** 并行分支最大并发数(默认不限制) */
|
|
2914
|
+
concurrency;
|
|
2915
|
+
/** 单节点最大访问次数(环图防死循环) */
|
|
2916
|
+
maxVisitsPerNode;
|
|
2533
2917
|
state = {
|
|
2534
2918
|
nodeStates: /* @__PURE__ */ new Map(),
|
|
2535
2919
|
nodeResults: /* @__PURE__ */ new Map(),
|
|
2536
2920
|
stepsCompleted: 0,
|
|
2537
2921
|
finished: false
|
|
2538
2922
|
};
|
|
2923
|
+
constructor(opts) {
|
|
2924
|
+
this.concurrency = opts?.concurrency ?? Infinity;
|
|
2925
|
+
this.maxVisitsPerNode = opts?.maxVisitsPerNode ?? 10;
|
|
2926
|
+
}
|
|
2539
2927
|
addNode(node) {
|
|
2540
2928
|
this.nodes.set(node.id, node);
|
|
2541
2929
|
this.state.nodeStates.set(node.id, "pending");
|
|
@@ -2553,12 +2941,42 @@ var GraphExecutor = class {
|
|
|
2553
2941
|
this.finishCondition = condition;
|
|
2554
2942
|
return this;
|
|
2555
2943
|
}
|
|
2944
|
+
/** 注入外部事件监听(由 AgentGraph 实现的 on() 接线) */
|
|
2945
|
+
setEventSink(sink) {
|
|
2946
|
+
this.eventSink = sink;
|
|
2947
|
+
return this;
|
|
2948
|
+
}
|
|
2556
2949
|
getState() {
|
|
2557
|
-
return {
|
|
2950
|
+
return {
|
|
2951
|
+
...this.state,
|
|
2952
|
+
nodeStates: new Map(this.state.nodeStates),
|
|
2953
|
+
nodeResults: new Map(this.state.nodeResults)
|
|
2954
|
+
};
|
|
2558
2955
|
}
|
|
2559
2956
|
abort() {
|
|
2560
2957
|
this.abortController.abort();
|
|
2561
2958
|
}
|
|
2959
|
+
/** 每次 run/stream 前重置中止信号,避免上一次的 abort 永久失效 */
|
|
2960
|
+
resetAbortController() {
|
|
2961
|
+
this.abortController = new AbortController();
|
|
2962
|
+
}
|
|
2963
|
+
/** 每次 run/stream 前重置执行状态,避免残留上一次的 nodeStates/结果 */
|
|
2964
|
+
resetState() {
|
|
2965
|
+
this.state = {
|
|
2966
|
+
nodeStates: /* @__PURE__ */ new Map(),
|
|
2967
|
+
nodeResults: /* @__PURE__ */ new Map(),
|
|
2968
|
+
stepsCompleted: 0,
|
|
2969
|
+
finished: false
|
|
2970
|
+
};
|
|
2971
|
+
for (const id of this.nodes.keys()) {
|
|
2972
|
+
this.state.nodeStates.set(id, "pending");
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
throwIfAborted() {
|
|
2976
|
+
if (this.abortController.signal.aborted) {
|
|
2977
|
+
throw new GraphAbortedError();
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2562
2980
|
/** 同步执行图 */
|
|
2563
2981
|
async run(input) {
|
|
2564
2982
|
if (!this.entryId) {
|
|
@@ -2567,66 +2985,78 @@ var GraphExecutor = class {
|
|
|
2567
2985
|
if (!this.nodes.has(this.entryId)) {
|
|
2568
2986
|
throw new Error(`AgentGraph: \u5165\u53E3\u8282\u70B9 "${this.entryId}" \u4E0D\u5B58\u5728`);
|
|
2569
2987
|
}
|
|
2988
|
+
this.resetAbortController();
|
|
2989
|
+
this.resetState();
|
|
2570
2990
|
const ctx = createSharedContext({
|
|
2571
2991
|
meta: { traceId: `ma-${Date.now()}`, startTime: Date.now() }
|
|
2572
2992
|
});
|
|
2573
|
-
|
|
2993
|
+
storeOriginalInput(ctx, input);
|
|
2994
|
+
const emit = this.eventSink ?? (() => {
|
|
2995
|
+
});
|
|
2574
2996
|
try {
|
|
2575
|
-
const result = await this.executeGraph(input, ctx,
|
|
2997
|
+
const result = await this.executeGraph(input, ctx, emit);
|
|
2998
|
+
emit({ type: "graph_done", result });
|
|
2576
2999
|
return result;
|
|
2577
3000
|
} catch (err) {
|
|
3001
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3002
|
+
this.state.error = errorMsg;
|
|
3003
|
+
if (err instanceof GraphAbortedError) {
|
|
3004
|
+
const lastCompleted = [...this.state.nodeResults.values()].at(-1);
|
|
3005
|
+
const abortedResult = {
|
|
3006
|
+
content: lastCompleted?.content ?? "",
|
|
3007
|
+
lastAgentId: this.state.currentAgentId ?? "",
|
|
3008
|
+
agentResults: new Map(this.state.nodeResults),
|
|
3009
|
+
totalUsage: {},
|
|
3010
|
+
stepsCompleted: this.state.stepsCompleted,
|
|
3011
|
+
stopReason: "aborted",
|
|
3012
|
+
context: ctx,
|
|
3013
|
+
success: false,
|
|
3014
|
+
error: err.message
|
|
3015
|
+
};
|
|
3016
|
+
emit({ type: "graph_error", error: err.message });
|
|
3017
|
+
return abortedResult;
|
|
3018
|
+
}
|
|
3019
|
+
emit({ type: "graph_error", error: errorMsg });
|
|
2578
3020
|
return {
|
|
2579
3021
|
content: "",
|
|
2580
3022
|
lastAgentId: this.state.currentAgentId ?? "",
|
|
2581
|
-
agentResults: this.state.nodeResults,
|
|
3023
|
+
agentResults: new Map(this.state.nodeResults),
|
|
2582
3024
|
totalUsage: {},
|
|
2583
3025
|
stepsCompleted: this.state.stepsCompleted,
|
|
2584
3026
|
stopReason: "error",
|
|
2585
3027
|
context: ctx,
|
|
2586
3028
|
success: false,
|
|
2587
|
-
error:
|
|
3029
|
+
error: errorMsg
|
|
2588
3030
|
};
|
|
2589
3031
|
}
|
|
2590
3032
|
}
|
|
2591
3033
|
/** 流式执行图 */
|
|
2592
|
-
|
|
3034
|
+
stream(input) {
|
|
2593
3035
|
if (!this.entryId) {
|
|
2594
3036
|
throw new Error("AgentGraph: \u5165\u53E3\u8282\u70B9\u672A\u8BBE\u7F6E\uFF0C\u8BF7\u8C03\u7528 setEntry()");
|
|
2595
3037
|
}
|
|
2596
3038
|
if (!this.nodes.has(this.entryId)) {
|
|
2597
3039
|
throw new Error(`AgentGraph: \u5165\u53E3\u8282\u70B9 "${this.entryId}" \u4E0D\u5B58\u5728`);
|
|
2598
3040
|
}
|
|
3041
|
+
this.resetAbortController();
|
|
3042
|
+
this.resetState();
|
|
2599
3043
|
const ctx = createSharedContext({
|
|
2600
3044
|
meta: { traceId: `ma-${Date.now()}`, startTime: Date.now() }
|
|
2601
3045
|
});
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
).finally(() => {
|
|
2617
|
-
done = true;
|
|
2618
|
-
resolveEvent?.();
|
|
2619
|
-
});
|
|
2620
|
-
while (!done || eventQueue.length > 0) {
|
|
2621
|
-
if (eventQueue.length > 0) {
|
|
2622
|
-
yield eventQueue.shift();
|
|
2623
|
-
} else {
|
|
2624
|
-
await new Promise((resolve) => {
|
|
2625
|
-
resolveEvent = resolve;
|
|
2626
|
-
});
|
|
2627
|
-
}
|
|
2628
|
-
}
|
|
2629
|
-
await graphPromise;
|
|
3046
|
+
storeOriginalInput(ctx, input);
|
|
3047
|
+
return createEventStream(
|
|
3048
|
+
(emit) => this.executeGraph(input, ctx, emit).then(
|
|
3049
|
+
(result) => {
|
|
3050
|
+
emit({ type: "graph_done", result });
|
|
3051
|
+
},
|
|
3052
|
+
(err) => {
|
|
3053
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3054
|
+
this.state.error = errorMsg;
|
|
3055
|
+
emit({ type: "graph_error", error: errorMsg });
|
|
3056
|
+
}
|
|
3057
|
+
),
|
|
3058
|
+
this.eventSink
|
|
3059
|
+
);
|
|
2630
3060
|
}
|
|
2631
3061
|
/** 核心执行逻辑 */
|
|
2632
3062
|
async executeGraph(input, ctx, emit) {
|
|
@@ -2639,7 +3069,7 @@ var GraphExecutor = class {
|
|
|
2639
3069
|
const totalUsage = {};
|
|
2640
3070
|
this.state.currentAgentId = currentId;
|
|
2641
3071
|
this.state.nodeStates.set(currentId, "running");
|
|
2642
|
-
emit({ type: "agent_start", agentId: currentId, agentName: entryNode.name });
|
|
3072
|
+
emit({ type: "agent_start", agentId: currentId, agentName: entryNode.name, input: toInputText(currentInput) });
|
|
2643
3073
|
try {
|
|
2644
3074
|
lastResult = await executeNode(entryNode, currentInput, ctx);
|
|
2645
3075
|
this.state.nodeStates.set(currentId, "completed");
|
|
@@ -2658,50 +3088,134 @@ var GraphExecutor = class {
|
|
|
2658
3088
|
if (this.finishCondition?.(ctx)) {
|
|
2659
3089
|
return this.buildResult(lastResult, lastAgentId, ctx, totalUsage, stepsCompleted, "finish_condition");
|
|
2660
3090
|
}
|
|
2661
|
-
|
|
3091
|
+
let frontier = [{ id: currentId, result: lastResult }];
|
|
2662
3092
|
const visitCount = /* @__PURE__ */ new Map();
|
|
2663
|
-
|
|
2664
|
-
while (
|
|
2665
|
-
|
|
2666
|
-
|
|
3093
|
+
let stopReason = "completed";
|
|
3094
|
+
while (frontier.length > 0) {
|
|
3095
|
+
this.throwIfAborted();
|
|
3096
|
+
const candidates = [];
|
|
3097
|
+
const seenTargets = /* @__PURE__ */ new Set();
|
|
3098
|
+
for (const from of frontier) {
|
|
3099
|
+
const nextEdges = findNextEdges(from.id, this.edges, from.result, ctx);
|
|
3100
|
+
for (const edge of nextEdges) {
|
|
3101
|
+
if (seenTargets.has(edge.to)) continue;
|
|
3102
|
+
seenTargets.add(edge.to);
|
|
3103
|
+
const nextNode = this.nodes.get(edge.to);
|
|
3104
|
+
if (!nextNode) {
|
|
3105
|
+
throw new Error(`AgentGraph: \u76EE\u6807\u8282\u70B9 "${edge.to}" \u4E0D\u5B58\u5728`);
|
|
3106
|
+
}
|
|
3107
|
+
candidates.push({ edge, from, node: nextNode });
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
if (candidates.length === 0) {
|
|
2667
3111
|
break;
|
|
2668
3112
|
}
|
|
2669
|
-
const
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
3113
|
+
const allowed = [];
|
|
3114
|
+
let truncated = false;
|
|
3115
|
+
for (const c of candidates) {
|
|
3116
|
+
const count = (visitCount.get(c.edge.to) ?? 0) + 1;
|
|
3117
|
+
if (count > this.maxVisitsPerNode) {
|
|
3118
|
+
truncated = true;
|
|
3119
|
+
continue;
|
|
3120
|
+
}
|
|
3121
|
+
visitCount.set(c.edge.to, count);
|
|
3122
|
+
allowed.push(c);
|
|
2673
3123
|
}
|
|
2674
|
-
|
|
2675
|
-
|
|
3124
|
+
if (allowed.length === 0) {
|
|
3125
|
+
if (truncated) stopReason = "max_visits_exceeded";
|
|
2676
3126
|
break;
|
|
2677
3127
|
}
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
this.state.nodeStates.set(
|
|
2688
|
-
|
|
2689
|
-
|
|
3128
|
+
const tasks = allowed.map((c) => ({
|
|
3129
|
+
node: c.node,
|
|
3130
|
+
input: resolveInput(c.edge, c.from.result, ctx, c.node, input),
|
|
3131
|
+
fromId: c.edge.from
|
|
3132
|
+
}));
|
|
3133
|
+
let waveResults;
|
|
3134
|
+
if (tasks.length === 1) {
|
|
3135
|
+
const t = tasks[0];
|
|
3136
|
+
this.state.currentAgentId = t.node.id;
|
|
3137
|
+
this.state.nodeStates.set(t.node.id, "running");
|
|
3138
|
+
emit({ type: "edge_traversed", from: t.fromId, to: t.node.id });
|
|
3139
|
+
emit({ type: "agent_start", agentId: t.node.id, agentName: t.node.name, input: toInputText(t.input) });
|
|
3140
|
+
let result;
|
|
3141
|
+
try {
|
|
3142
|
+
result = await executeNode(t.node, t.input, ctx);
|
|
3143
|
+
} catch (err) {
|
|
3144
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3145
|
+
this.state.nodeStates.set(t.node.id, "failed");
|
|
3146
|
+
emit({ type: "agent_error", agentId: t.node.id, agentName: t.node.name, error: errorMsg });
|
|
3147
|
+
throw err;
|
|
3148
|
+
}
|
|
3149
|
+
this.state.nodeStates.set(t.node.id, "completed");
|
|
3150
|
+
this.state.nodeResults.set(t.node.id, result);
|
|
3151
|
+
this.mergeUsage(totalUsage, result.usage);
|
|
2690
3152
|
stepsCompleted++;
|
|
2691
3153
|
this.state.stepsCompleted = stepsCompleted;
|
|
2692
|
-
emit({ type: "agent_result", agentId:
|
|
3154
|
+
emit({ type: "agent_result", agentId: t.node.id, agentName: t.node.name, result });
|
|
3155
|
+
waveResults = [{ id: t.node.id, result }];
|
|
3156
|
+
} else {
|
|
3157
|
+
waveResults = await this.executeWave(tasks, ctx, emit, totalUsage);
|
|
3158
|
+
stepsCompleted += waveResults.length;
|
|
3159
|
+
this.state.stepsCompleted = stepsCompleted;
|
|
3160
|
+
}
|
|
3161
|
+
lastResult = waveResults[waveResults.length - 1].result;
|
|
3162
|
+
lastAgentId = waveResults[waveResults.length - 1].id;
|
|
3163
|
+
frontier = waveResults;
|
|
3164
|
+
if (this.finishCondition?.(ctx)) {
|
|
3165
|
+
return this.buildResult(lastResult, lastAgentId, ctx, totalUsage, stepsCompleted, "finish_condition");
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
return this.buildResult(lastResult, lastAgentId, ctx, totalUsage, stepsCompleted, stopReason);
|
|
3169
|
+
}
|
|
3170
|
+
/** 并行执行一个 wave 的节点(fail-fast + concurrency 限流) */
|
|
3171
|
+
async executeWave(tasks, ctx, emit, totalUsage) {
|
|
3172
|
+
const results = [];
|
|
3173
|
+
emit({ type: "parallel_start", agentIds: tasks.map((t) => t.node.id) });
|
|
3174
|
+
const runOne = async (t) => {
|
|
3175
|
+
this.state.currentAgentId = t.node.id;
|
|
3176
|
+
this.state.nodeStates.set(t.node.id, "running");
|
|
3177
|
+
emit({ type: "edge_traversed", from: t.fromId, to: t.node.id });
|
|
3178
|
+
emit({ type: "agent_start", agentId: t.node.id, agentName: t.node.name, input: toInputText(t.input) });
|
|
3179
|
+
try {
|
|
3180
|
+
const result = await executeNode(t.node, t.input, ctx);
|
|
3181
|
+
this.state.nodeStates.set(t.node.id, "completed");
|
|
3182
|
+
this.state.nodeResults.set(t.node.id, result);
|
|
3183
|
+
this.mergeUsage(totalUsage, result.usage);
|
|
3184
|
+
results.push({ id: t.node.id, result });
|
|
3185
|
+
emit({ type: "agent_result", agentId: t.node.id, agentName: t.node.name, result });
|
|
2693
3186
|
} catch (err) {
|
|
2694
3187
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2695
|
-
this.state.nodeStates.set(
|
|
2696
|
-
emit({ type: "agent_error", agentId:
|
|
3188
|
+
this.state.nodeStates.set(t.node.id, "failed");
|
|
3189
|
+
emit({ type: "agent_error", agentId: t.node.id, agentName: t.node.name, error: errorMsg });
|
|
2697
3190
|
throw err;
|
|
2698
3191
|
}
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
3192
|
+
};
|
|
3193
|
+
if (this.concurrency >= tasks.length) {
|
|
3194
|
+
await Promise.all(tasks.map((t) => runOne(t)));
|
|
3195
|
+
} else {
|
|
3196
|
+
let index = 0;
|
|
3197
|
+
const executing = /* @__PURE__ */ new Set();
|
|
3198
|
+
const enqueue = () => {
|
|
3199
|
+
if (index >= tasks.length) return null;
|
|
3200
|
+
const i = index++;
|
|
3201
|
+
const p = runOne(tasks[i]).finally(() => {
|
|
3202
|
+
executing.delete(p);
|
|
3203
|
+
});
|
|
3204
|
+
executing.add(p);
|
|
3205
|
+
p.catch(() => {
|
|
3206
|
+
});
|
|
3207
|
+
return p;
|
|
3208
|
+
};
|
|
3209
|
+
for (let i = 0; i < this.concurrency && index < tasks.length; i++) {
|
|
3210
|
+
enqueue();
|
|
3211
|
+
}
|
|
3212
|
+
while (executing.size > 0) {
|
|
3213
|
+
await Promise.race(executing);
|
|
3214
|
+
enqueue();
|
|
2702
3215
|
}
|
|
2703
3216
|
}
|
|
2704
|
-
|
|
3217
|
+
emit({ type: "parallel_done", results: new Map(results.map((r) => [r.id, r.result])) });
|
|
3218
|
+
return results;
|
|
2705
3219
|
}
|
|
2706
3220
|
buildResult(lastResult, lastAgentId, ctx, totalUsage, stepsCompleted, stopReason) {
|
|
2707
3221
|
this.state.finished = true;
|
|
@@ -2726,8 +3240,12 @@ var GraphExecutor = class {
|
|
|
2726
3240
|
|
|
2727
3241
|
// core/graph.ts
|
|
2728
3242
|
var AgentGraphImpl = class {
|
|
2729
|
-
executor
|
|
2730
|
-
|
|
3243
|
+
executor;
|
|
3244
|
+
/** on() 注册的监听器,按 MultiAgentEvent.type 分发 */
|
|
3245
|
+
bus = new SimpleEventBus();
|
|
3246
|
+
constructor(opts) {
|
|
3247
|
+
this.executor = new GraphExecutor(opts);
|
|
3248
|
+
}
|
|
2731
3249
|
addNode(node) {
|
|
2732
3250
|
this.executor.addNode(node);
|
|
2733
3251
|
return this;
|
|
@@ -2745,9 +3263,11 @@ var AgentGraphImpl = class {
|
|
|
2745
3263
|
return this;
|
|
2746
3264
|
}
|
|
2747
3265
|
async run(input) {
|
|
3266
|
+
this.executor.setEventSink((event) => this.bus.emit(event.type, event));
|
|
2748
3267
|
return this.executor.run(input);
|
|
2749
3268
|
}
|
|
2750
3269
|
async *stream(input) {
|
|
3270
|
+
this.executor.setEventSink((event) => this.bus.emit(event.type, event));
|
|
2751
3271
|
yield* this.executor.stream(input);
|
|
2752
3272
|
}
|
|
2753
3273
|
getState() {
|
|
@@ -2757,17 +3277,12 @@ var AgentGraphImpl = class {
|
|
|
2757
3277
|
this.executor.abort();
|
|
2758
3278
|
}
|
|
2759
3279
|
on(event, listener) {
|
|
2760
|
-
|
|
2761
|
-
if (!set) {
|
|
2762
|
-
set = /* @__PURE__ */ new Set();
|
|
2763
|
-
this.eventListeners.set(event, set);
|
|
2764
|
-
}
|
|
2765
|
-
set.add(listener);
|
|
3280
|
+
this.bus.on(event, listener);
|
|
2766
3281
|
return this;
|
|
2767
3282
|
}
|
|
2768
3283
|
};
|
|
2769
|
-
function createAgentGraph() {
|
|
2770
|
-
return new AgentGraphImpl();
|
|
3284
|
+
function createAgentGraph(opts) {
|
|
3285
|
+
return new AgentGraphImpl(opts);
|
|
2771
3286
|
}
|
|
2772
3287
|
|
|
2773
3288
|
// patterns/pipeline.ts
|
|
@@ -2805,14 +3320,19 @@ function createRouter(router, targets, opts) {
|
|
|
2805
3320
|
}
|
|
2806
3321
|
graph.setEntry(router.id);
|
|
2807
3322
|
const targetIds = new Set(targets.map((t) => t.id));
|
|
3323
|
+
const defaultTarget = opts.defaultTarget && targetIds.has(opts.defaultTarget) ? opts.defaultTarget : void 0;
|
|
3324
|
+
const resolveTarget = (routerResult) => {
|
|
3325
|
+
const resolvedId = opts.resolve(routerResult);
|
|
3326
|
+
if (targetIds.has(resolvedId)) return resolvedId;
|
|
3327
|
+
return defaultTarget ?? resolvedId;
|
|
3328
|
+
};
|
|
2808
3329
|
for (const target of targets) {
|
|
2809
3330
|
graph.addEdge({
|
|
2810
3331
|
from: router.id,
|
|
2811
3332
|
to: target.id,
|
|
2812
|
-
// 条件:从路由器结果中解析目标ID
|
|
3333
|
+
// 条件:从路由器结果中解析目标ID(含 defaultTarget 兜底),匹配当前目标
|
|
2813
3334
|
condition: (routerResult, _ctx) => {
|
|
2814
|
-
|
|
2815
|
-
return resolvedId === target.id;
|
|
3335
|
+
return resolveTarget(routerResult) === target.id;
|
|
2816
3336
|
},
|
|
2817
3337
|
// 转换:决定传给目标Agent的输入
|
|
2818
3338
|
transform: (_routerResult, _ctx) => {
|
|
@@ -2824,8 +3344,6 @@ function createRouter(router, targets, opts) {
|
|
|
2824
3344
|
}
|
|
2825
3345
|
});
|
|
2826
3346
|
}
|
|
2827
|
-
if (opts.defaultTarget && targetIds.has(opts.defaultTarget)) {
|
|
2828
|
-
}
|
|
2829
3347
|
return graph;
|
|
2830
3348
|
}
|
|
2831
3349
|
|
|
@@ -2834,6 +3352,11 @@ var SupervisorExecutor = class {
|
|
|
2834
3352
|
supervisorNode;
|
|
2835
3353
|
workerNodes;
|
|
2836
3354
|
opts;
|
|
3355
|
+
abortController = new AbortController();
|
|
3356
|
+
/** 外部事件监听(on() API 的底层接线) */
|
|
3357
|
+
eventSink;
|
|
3358
|
+
/** 最近一次 run 中失败的 Worker ID(onWorkerError='skip' 时使用) */
|
|
3359
|
+
failedAgents = [];
|
|
2837
3360
|
state = {
|
|
2838
3361
|
nodeStates: /* @__PURE__ */ new Map(),
|
|
2839
3362
|
nodeResults: /* @__PURE__ */ new Map(),
|
|
@@ -2846,7 +3369,8 @@ var SupervisorExecutor = class {
|
|
|
2846
3369
|
this.opts = {
|
|
2847
3370
|
schedule: opts?.schedule ?? "parallel",
|
|
2848
3371
|
concurrency: opts?.concurrency ?? Infinity,
|
|
2849
|
-
passOriginalInput: opts?.passOriginalInput ?? true
|
|
3372
|
+
passOriginalInput: opts?.passOriginalInput ?? true,
|
|
3373
|
+
onWorkerError: opts?.onWorkerError ?? "fail-fast"
|
|
2850
3374
|
};
|
|
2851
3375
|
this.state.nodeStates.set(supervisor.id, "pending");
|
|
2852
3376
|
for (const w of workers) {
|
|
@@ -2854,69 +3378,91 @@ var SupervisorExecutor = class {
|
|
|
2854
3378
|
}
|
|
2855
3379
|
}
|
|
2856
3380
|
getState() {
|
|
2857
|
-
return {
|
|
3381
|
+
return {
|
|
3382
|
+
...this.state,
|
|
3383
|
+
nodeStates: new Map(this.state.nodeStates),
|
|
3384
|
+
nodeResults: new Map(this.state.nodeResults)
|
|
3385
|
+
};
|
|
3386
|
+
}
|
|
3387
|
+
/** 注入外部事件监听(由 AgentGraph 实现的 on() 接线) */
|
|
3388
|
+
setEventSink(sink) {
|
|
3389
|
+
this.eventSink = sink;
|
|
3390
|
+
return this;
|
|
3391
|
+
}
|
|
3392
|
+
abort() {
|
|
3393
|
+
this.abortController.abort();
|
|
3394
|
+
}
|
|
3395
|
+
/** 每次 run/stream 前重置中止信号、失败记录与执行状态 */
|
|
3396
|
+
resetRun() {
|
|
3397
|
+
this.abortController = new AbortController();
|
|
3398
|
+
this.failedAgents = [];
|
|
3399
|
+
this.state = {
|
|
3400
|
+
nodeStates: /* @__PURE__ */ new Map(),
|
|
3401
|
+
nodeResults: /* @__PURE__ */ new Map(),
|
|
3402
|
+
stepsCompleted: 0,
|
|
3403
|
+
finished: false
|
|
3404
|
+
};
|
|
3405
|
+
this.state.nodeStates.set(this.supervisorNode.id, "pending");
|
|
3406
|
+
for (const w of this.workerNodes) {
|
|
3407
|
+
this.state.nodeStates.set(w.id, "pending");
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
throwIfAborted() {
|
|
3411
|
+
if (this.abortController.signal.aborted) {
|
|
3412
|
+
throw new GraphAbortedError();
|
|
3413
|
+
}
|
|
2858
3414
|
}
|
|
2859
3415
|
/** 执行 Supervisor 模式 */
|
|
2860
3416
|
async run(input) {
|
|
3417
|
+
this.resetRun();
|
|
2861
3418
|
const ctx = createSharedContext({
|
|
2862
3419
|
meta: { traceId: `sv-${Date.now()}`, startTime: Date.now() }
|
|
2863
3420
|
});
|
|
2864
3421
|
if (this.opts.passOriginalInput) {
|
|
2865
|
-
|
|
2866
|
-
ctx.blackboard.set("__original_input__", inputText);
|
|
3422
|
+
storeOriginalInput(ctx, input);
|
|
2867
3423
|
}
|
|
3424
|
+
const emit = this.eventSink ?? (() => {
|
|
3425
|
+
});
|
|
2868
3426
|
try {
|
|
2869
|
-
|
|
2870
|
-
});
|
|
3427
|
+
const result = await this.executeSupervisor(input, ctx, emit);
|
|
3428
|
+
emit({ type: "graph_done", result });
|
|
3429
|
+
return result;
|
|
2871
3430
|
} catch (err) {
|
|
2872
|
-
|
|
3431
|
+
const result = this.buildErrorResult(ctx, err);
|
|
3432
|
+
emit({ type: "graph_error", error: result.error ?? "" });
|
|
3433
|
+
return result;
|
|
2873
3434
|
}
|
|
2874
3435
|
}
|
|
2875
3436
|
/** 流式执行 Supervisor 模式 */
|
|
2876
|
-
|
|
3437
|
+
stream(input) {
|
|
3438
|
+
this.resetRun();
|
|
2877
3439
|
const ctx = createSharedContext({
|
|
2878
3440
|
meta: { traceId: `sv-${Date.now()}`, startTime: Date.now() }
|
|
2879
3441
|
});
|
|
2880
3442
|
if (this.opts.passOriginalInput) {
|
|
2881
|
-
|
|
2882
|
-
ctx.blackboard.set("__original_input__", inputText);
|
|
2883
|
-
}
|
|
2884
|
-
const eventQueue = [];
|
|
2885
|
-
let resolveEvent = null;
|
|
2886
|
-
let done = false;
|
|
2887
|
-
const emit = (event) => {
|
|
2888
|
-
eventQueue.push(event);
|
|
2889
|
-
resolveEvent?.();
|
|
2890
|
-
};
|
|
2891
|
-
const graphPromise = this.executeSupervisor(input, ctx, emit).then(
|
|
2892
|
-
(result) => {
|
|
2893
|
-
emit({ type: "graph_done", result });
|
|
2894
|
-
},
|
|
2895
|
-
(err) => {
|
|
2896
|
-
emit({ type: "graph_error", error: err instanceof Error ? err.message : String(err) });
|
|
2897
|
-
}
|
|
2898
|
-
).finally(() => {
|
|
2899
|
-
done = true;
|
|
2900
|
-
resolveEvent?.();
|
|
2901
|
-
});
|
|
2902
|
-
while (!done || eventQueue.length > 0) {
|
|
2903
|
-
if (eventQueue.length > 0) {
|
|
2904
|
-
yield eventQueue.shift();
|
|
2905
|
-
} else {
|
|
2906
|
-
await new Promise((resolve) => {
|
|
2907
|
-
resolveEvent = resolve;
|
|
2908
|
-
});
|
|
2909
|
-
}
|
|
3443
|
+
storeOriginalInput(ctx, input);
|
|
2910
3444
|
}
|
|
2911
|
-
|
|
3445
|
+
return createEventStream(
|
|
3446
|
+
(emit) => this.executeSupervisor(input, ctx, emit).then(
|
|
3447
|
+
(result) => {
|
|
3448
|
+
emit({ type: "graph_done", result });
|
|
3449
|
+
},
|
|
3450
|
+
(err) => {
|
|
3451
|
+
this.state.error = err instanceof Error ? err.message : String(err);
|
|
3452
|
+
emit({ type: "graph_error", error: this.state.error });
|
|
3453
|
+
}
|
|
3454
|
+
),
|
|
3455
|
+
this.eventSink
|
|
3456
|
+
);
|
|
2912
3457
|
}
|
|
2913
3458
|
/** 核心执行逻辑 */
|
|
2914
3459
|
async executeSupervisor(input, ctx, emit) {
|
|
2915
3460
|
let stepsCompleted = 0;
|
|
2916
3461
|
const totalUsage = {};
|
|
3462
|
+
this.throwIfAborted();
|
|
2917
3463
|
this.state.currentAgentId = this.supervisorNode.id;
|
|
2918
3464
|
this.state.nodeStates.set(this.supervisorNode.id, "running");
|
|
2919
|
-
emit({ type: "agent_start", agentId: this.supervisorNode.id, agentName: this.supervisorNode.name });
|
|
3465
|
+
emit({ type: "agent_start", agentId: this.supervisorNode.id, agentName: this.supervisorNode.name, input: toInputText(input) });
|
|
2920
3466
|
let supervisorResult;
|
|
2921
3467
|
try {
|
|
2922
3468
|
supervisorResult = await executeNode(this.supervisorNode, input, ctx);
|
|
@@ -2943,18 +3489,21 @@ var SupervisorExecutor = class {
|
|
|
2943
3489
|
}
|
|
2944
3490
|
stepsCompleted += workerResults.size;
|
|
2945
3491
|
this.state.stepsCompleted = stepsCompleted;
|
|
2946
|
-
const
|
|
2947
|
-
const finalResult =
|
|
3492
|
+
const lastEntry = [...workerResults.entries()].at(-1);
|
|
3493
|
+
const finalResult = lastEntry?.[1] ?? supervisorResult;
|
|
3494
|
+
const finalAgentId = lastEntry?.[0] ?? this.supervisorNode.id;
|
|
3495
|
+
const hasFailures = this.failedAgents.length > 0;
|
|
2948
3496
|
this.state.finished = true;
|
|
2949
3497
|
return {
|
|
2950
3498
|
content: finalResult.content,
|
|
2951
|
-
lastAgentId:
|
|
3499
|
+
lastAgentId: finalAgentId,
|
|
2952
3500
|
agentResults: new Map(this.state.nodeResults),
|
|
2953
3501
|
totalUsage,
|
|
2954
3502
|
stepsCompleted,
|
|
2955
|
-
stopReason: "completed",
|
|
3503
|
+
stopReason: hasFailures ? "partial_failure" : "completed",
|
|
2956
3504
|
context: ctx,
|
|
2957
|
-
success: true
|
|
3505
|
+
success: true,
|
|
3506
|
+
...hasFailures ? { failedAgents: [...this.failedAgents] } : {}
|
|
2958
3507
|
};
|
|
2959
3508
|
}
|
|
2960
3509
|
/** 并行执行所有 Worker */
|
|
@@ -2962,16 +3511,28 @@ var SupervisorExecutor = class {
|
|
|
2962
3511
|
const results = /* @__PURE__ */ new Map();
|
|
2963
3512
|
const workers = this.workerNodes;
|
|
2964
3513
|
const concurrency = this.opts.concurrency;
|
|
3514
|
+
const skip = this.opts.onWorkerError === "skip";
|
|
2965
3515
|
emit({ type: "parallel_start", agentIds: workers.map((w) => w.id) });
|
|
2966
3516
|
if (concurrency >= workers.length) {
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
3517
|
+
if (!skip) {
|
|
3518
|
+
await Promise.all(
|
|
3519
|
+
workers.map(async (worker) => {
|
|
3520
|
+
const r = await this.executeWorker(worker, ctx, emit, totalUsage);
|
|
3521
|
+
results.set(worker.id, r);
|
|
3522
|
+
return r;
|
|
3523
|
+
})
|
|
3524
|
+
);
|
|
3525
|
+
} else {
|
|
3526
|
+
const settled = await Promise.allSettled(
|
|
3527
|
+
workers.map((worker) => this.executeWorker(worker, ctx, emit, totalUsage))
|
|
3528
|
+
);
|
|
3529
|
+
for (let i = 0; i < settled.length; i++) {
|
|
3530
|
+
const s = settled[i];
|
|
3531
|
+
if (s.status === "fulfilled") {
|
|
3532
|
+
results.set(workers[i].id, s.value);
|
|
3533
|
+
} else {
|
|
3534
|
+
this.failedAgents.push(workers[i].id);
|
|
3535
|
+
}
|
|
2975
3536
|
}
|
|
2976
3537
|
}
|
|
2977
3538
|
} else {
|
|
@@ -2980,12 +3541,20 @@ var SupervisorExecutor = class {
|
|
|
2980
3541
|
const enqueue = () => {
|
|
2981
3542
|
if (index >= workers.length) return null;
|
|
2982
3543
|
const worker = workers[index++];
|
|
2983
|
-
const p = this.executeWorker(worker, ctx, emit, totalUsage).then(
|
|
2984
|
-
|
|
3544
|
+
const p = this.executeWorker(worker, ctx, emit, totalUsage).then(
|
|
3545
|
+
(r) => {
|
|
3546
|
+
results.set(worker.id, r);
|
|
3547
|
+
},
|
|
3548
|
+
(err) => {
|
|
3549
|
+
if (!skip) throw err;
|
|
3550
|
+
this.failedAgents.push(worker.id);
|
|
3551
|
+
}
|
|
3552
|
+
).finally(() => {
|
|
2985
3553
|
executing.delete(p);
|
|
2986
|
-
return r;
|
|
2987
3554
|
});
|
|
2988
3555
|
executing.add(p);
|
|
3556
|
+
p.catch(() => {
|
|
3557
|
+
});
|
|
2989
3558
|
return p;
|
|
2990
3559
|
};
|
|
2991
3560
|
for (let i = 0; i < concurrency && index < workers.length; i++) {
|
|
@@ -3002,40 +3571,122 @@ var SupervisorExecutor = class {
|
|
|
3002
3571
|
/** 顺序执行所有 Worker */
|
|
3003
3572
|
async executeWorkersSequential(ctx, emit, totalUsage) {
|
|
3004
3573
|
const results = /* @__PURE__ */ new Map();
|
|
3574
|
+
const skip = this.opts.onWorkerError === "skip";
|
|
3005
3575
|
for (const worker of this.workerNodes) {
|
|
3006
|
-
|
|
3007
|
-
|
|
3576
|
+
try {
|
|
3577
|
+
const result = await this.executeWorker(worker, ctx, emit, totalUsage);
|
|
3578
|
+
results.set(worker.id, result);
|
|
3579
|
+
} catch (err) {
|
|
3580
|
+
if (!skip) throw err;
|
|
3581
|
+
this.failedAgents.push(worker.id);
|
|
3582
|
+
}
|
|
3008
3583
|
}
|
|
3009
3584
|
return results;
|
|
3010
3585
|
}
|
|
3011
|
-
/**
|
|
3586
|
+
/** 自动调度:按依赖分层执行
|
|
3587
|
+
*
|
|
3588
|
+
* 依赖推导规则:
|
|
3589
|
+
* 1. 显式声明 dependsOn 的 worker:依赖指定的 worker(依赖必须都在 workers 列表中,否则报错)
|
|
3590
|
+
* 2. 未声明 dependsOn 但有 inputMapping 的 worker:隐式依赖所有"第 0 层"worker(保持向后兼容)
|
|
3591
|
+
* 3. 其余 worker(无 dependsOn 且无 inputMapping):第 0 层,首批并行
|
|
3592
|
+
*
|
|
3593
|
+
* 按拓扑分层:同层并行执行(受 concurrency 限制),层间按序执行;依赖成环时报错。
|
|
3594
|
+
*/
|
|
3012
3595
|
async executeWorkersAuto(ctx, emit, totalUsage) {
|
|
3013
3596
|
const results = /* @__PURE__ */ new Map();
|
|
3014
|
-
const
|
|
3015
|
-
const
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3597
|
+
const skip = this.opts.onWorkerError === "skip";
|
|
3598
|
+
const workerIds = new Set(this.workerNodes.map((w) => w.id));
|
|
3599
|
+
const layer0 = this.workerNodes.filter((w) => !w.inputMapping && !w.dependsOn);
|
|
3600
|
+
const deps = /* @__PURE__ */ new Map();
|
|
3601
|
+
for (const w of this.workerNodes) {
|
|
3602
|
+
if (w.dependsOn) {
|
|
3603
|
+
for (const dep of w.dependsOn) {
|
|
3604
|
+
if (!workerIds.has(dep)) {
|
|
3605
|
+
throw new Error(`Supervisor: Worker "${w.id}" \u7684 dependsOn \u5F15\u7528\u4E86\u4E0D\u5B58\u5728\u7684 Worker "${dep}"`);
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
deps.set(w.id, new Set(w.dependsOn));
|
|
3609
|
+
} else if (w.inputMapping) {
|
|
3610
|
+
deps.set(w.id, new Set(layer0.map((l) => l.id)));
|
|
3611
|
+
} else {
|
|
3612
|
+
deps.set(w.id, /* @__PURE__ */ new Set());
|
|
3023
3613
|
}
|
|
3024
|
-
emit({ type: "parallel_done", results: new Map(results) });
|
|
3025
3614
|
}
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3615
|
+
const remaining = new Map(deps);
|
|
3616
|
+
const layers = [];
|
|
3617
|
+
const doneIds = /* @__PURE__ */ new Set();
|
|
3618
|
+
while (remaining.size > 0) {
|
|
3619
|
+
const layer = [];
|
|
3620
|
+
for (const [id, depSet] of remaining) {
|
|
3621
|
+
if ([...depSet].every((d) => doneIds.has(d))) {
|
|
3622
|
+
layer.push(this.workerNodes.find((w) => w.id === id));
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
if (layer.length === 0) {
|
|
3626
|
+
throw new Error(`Supervisor: auto \u8C03\u5EA6\u7684 Worker \u4F9D\u8D56\u5B58\u5728\u73AF\uFF08\u6D89\u53CA: ${[...remaining.keys()].join(", ")}\uFF09`);
|
|
3627
|
+
}
|
|
3628
|
+
for (const w of layer) {
|
|
3629
|
+
remaining.delete(w.id);
|
|
3630
|
+
doneIds.add(w.id);
|
|
3631
|
+
}
|
|
3632
|
+
layers.push(layer);
|
|
3633
|
+
}
|
|
3634
|
+
for (const layer of layers) {
|
|
3635
|
+
if (layer.length === 1) {
|
|
3636
|
+
try {
|
|
3637
|
+
const r = await this.executeWorker(layer[0], ctx, emit, totalUsage);
|
|
3638
|
+
results.set(layer[0].id, r);
|
|
3639
|
+
} catch (err) {
|
|
3640
|
+
if (!skip) throw err;
|
|
3641
|
+
this.failedAgents.push(layer[0].id);
|
|
3642
|
+
}
|
|
3643
|
+
continue;
|
|
3644
|
+
}
|
|
3645
|
+
emit({ type: "parallel_start", agentIds: layer.map((w) => w.id) });
|
|
3646
|
+
const runOne = async (worker) => {
|
|
3647
|
+
try {
|
|
3648
|
+
const r = await this.executeWorker(worker, ctx, emit, totalUsage);
|
|
3649
|
+
results.set(worker.id, r);
|
|
3650
|
+
} catch (err) {
|
|
3651
|
+
if (!skip) throw err;
|
|
3652
|
+
this.failedAgents.push(worker.id);
|
|
3653
|
+
}
|
|
3654
|
+
};
|
|
3655
|
+
if (this.opts.concurrency >= layer.length) {
|
|
3656
|
+
await Promise.all(layer.map((w) => runOne(w)));
|
|
3657
|
+
} else {
|
|
3658
|
+
let index = 0;
|
|
3659
|
+
const executing = /* @__PURE__ */ new Set();
|
|
3660
|
+
const enqueue = () => {
|
|
3661
|
+
if (index >= layer.length) return null;
|
|
3662
|
+
const i = index++;
|
|
3663
|
+
const p = runOne(layer[i]).finally(() => {
|
|
3664
|
+
executing.delete(p);
|
|
3665
|
+
});
|
|
3666
|
+
executing.add(p);
|
|
3667
|
+
p.catch(() => {
|
|
3668
|
+
});
|
|
3669
|
+
return p;
|
|
3670
|
+
};
|
|
3671
|
+
for (let i = 0; i < this.opts.concurrency && index < layer.length; i++) {
|
|
3672
|
+
enqueue();
|
|
3673
|
+
}
|
|
3674
|
+
while (executing.size > 0) {
|
|
3675
|
+
await Promise.race(executing);
|
|
3676
|
+
enqueue();
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
emit({ type: "parallel_done", results: new Map(results) });
|
|
3029
3680
|
}
|
|
3030
3681
|
return results;
|
|
3031
3682
|
}
|
|
3032
3683
|
/** 执行单个 Worker */
|
|
3033
3684
|
async executeWorker(worker, ctx, emit, totalUsage) {
|
|
3685
|
+
this.throwIfAborted();
|
|
3034
3686
|
this.state.currentAgentId = worker.id;
|
|
3035
3687
|
this.state.nodeStates.set(worker.id, "running");
|
|
3036
|
-
|
|
3688
|
+
let workerInput;
|
|
3037
3689
|
try {
|
|
3038
|
-
let workerInput;
|
|
3039
3690
|
if (worker.inputMapping) {
|
|
3040
3691
|
workerInput = worker.inputMapping(ctx);
|
|
3041
3692
|
} else {
|
|
@@ -3043,6 +3694,14 @@ var SupervisorExecutor = class {
|
|
|
3043
3694
|
const myTask = tasks?.find((t) => t.assignee === worker.id);
|
|
3044
3695
|
workerInput = myTask ? myTask.task : ctx.blackboard.get("__original_input__") ?? "";
|
|
3045
3696
|
}
|
|
3697
|
+
} catch (err) {
|
|
3698
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3699
|
+
this.state.nodeStates.set(worker.id, "failed");
|
|
3700
|
+
emit({ type: "agent_error", agentId: worker.id, agentName: worker.name, error: errorMsg });
|
|
3701
|
+
throw err;
|
|
3702
|
+
}
|
|
3703
|
+
emit({ type: "agent_start", agentId: worker.id, agentName: worker.name, input: toInputText(workerInput) });
|
|
3704
|
+
try {
|
|
3046
3705
|
const result = await executeNode(worker, workerInput, ctx);
|
|
3047
3706
|
this.state.nodeStates.set(worker.id, "completed");
|
|
3048
3707
|
this.state.nodeResults.set(worker.id, result);
|
|
@@ -3059,16 +3718,21 @@ var SupervisorExecutor = class {
|
|
|
3059
3718
|
}
|
|
3060
3719
|
buildErrorResult(ctx, err) {
|
|
3061
3720
|
this.state.finished = true;
|
|
3721
|
+
const aborted = err instanceof GraphAbortedError;
|
|
3722
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3723
|
+
this.state.error = errorMsg;
|
|
3724
|
+
const lastCompleted = [...this.state.nodeResults.values()].at(-1);
|
|
3062
3725
|
return {
|
|
3063
|
-
content: "",
|
|
3064
|
-
lastAgentId: this.supervisorNode.id,
|
|
3726
|
+
content: aborted ? lastCompleted?.content ?? "" : "",
|
|
3727
|
+
lastAgentId: this.state.currentAgentId ?? this.supervisorNode.id,
|
|
3065
3728
|
agentResults: new Map(this.state.nodeResults),
|
|
3066
3729
|
totalUsage: {},
|
|
3067
3730
|
stepsCompleted: this.state.stepsCompleted,
|
|
3068
|
-
stopReason: "error",
|
|
3731
|
+
stopReason: aborted ? "aborted" : "error",
|
|
3069
3732
|
context: ctx,
|
|
3070
3733
|
success: false,
|
|
3071
|
-
error:
|
|
3734
|
+
error: errorMsg,
|
|
3735
|
+
...this.failedAgents.length > 0 ? { failedAgents: [...this.failedAgents] } : {}
|
|
3072
3736
|
};
|
|
3073
3737
|
}
|
|
3074
3738
|
mergeUsage(total, usage) {
|
|
@@ -3081,7 +3745,8 @@ var SupervisorExecutor = class {
|
|
|
3081
3745
|
// patterns/supervisor.ts
|
|
3082
3746
|
var SupervisorGraphImpl = class {
|
|
3083
3747
|
executor;
|
|
3084
|
-
|
|
3748
|
+
/** on() 注册的监听器,按 MultiAgentEvent.type 分发 */
|
|
3749
|
+
bus = new SimpleEventBus();
|
|
3085
3750
|
constructor(supervisor, workers, opts) {
|
|
3086
3751
|
this.executor = new SupervisorExecutor(supervisor, workers, opts);
|
|
3087
3752
|
}
|
|
@@ -3099,23 +3764,21 @@ var SupervisorGraphImpl = class {
|
|
|
3099
3764
|
throw new Error("Supervisor \u6A21\u5F0F\u4E0D\u652F\u6301 setFinish\uFF0C\u7EC8\u6B62\u7531 Supervisor \u5224\u5B9A");
|
|
3100
3765
|
}
|
|
3101
3766
|
async run(input) {
|
|
3767
|
+
this.executor.setEventSink((event) => this.bus.emit(event.type, event));
|
|
3102
3768
|
return this.executor.run(input);
|
|
3103
3769
|
}
|
|
3104
3770
|
async *stream(input) {
|
|
3771
|
+
this.executor.setEventSink((event) => this.bus.emit(event.type, event));
|
|
3105
3772
|
yield* this.executor.stream(input);
|
|
3106
3773
|
}
|
|
3107
3774
|
getState() {
|
|
3108
3775
|
return this.executor.getState();
|
|
3109
3776
|
}
|
|
3110
3777
|
abort() {
|
|
3778
|
+
this.executor.abort();
|
|
3111
3779
|
}
|
|
3112
3780
|
on(event, listener) {
|
|
3113
|
-
|
|
3114
|
-
if (!set) {
|
|
3115
|
-
set = /* @__PURE__ */ new Set();
|
|
3116
|
-
this.eventListeners.set(event, set);
|
|
3117
|
-
}
|
|
3118
|
-
set.add(listener);
|
|
3781
|
+
this.bus.on(event, listener);
|
|
3119
3782
|
return this;
|
|
3120
3783
|
}
|
|
3121
3784
|
};
|
|
@@ -3133,6 +3796,9 @@ var DebateExecutor = class {
|
|
|
3133
3796
|
maxRounds;
|
|
3134
3797
|
convergeWhen;
|
|
3135
3798
|
feedbackTransform;
|
|
3799
|
+
abortController = new AbortController();
|
|
3800
|
+
/** 外部事件监听(on() API 的底层接线) */
|
|
3801
|
+
eventSink;
|
|
3136
3802
|
state = {
|
|
3137
3803
|
nodeStates: /* @__PURE__ */ new Map(),
|
|
3138
3804
|
nodeResults: /* @__PURE__ */ new Map(),
|
|
@@ -3155,53 +3821,75 @@ ${proposerResult.content}`;
|
|
|
3155
3821
|
this.state.nodeStates.set(reviewer.id, "pending");
|
|
3156
3822
|
}
|
|
3157
3823
|
getState() {
|
|
3158
|
-
return {
|
|
3824
|
+
return {
|
|
3825
|
+
...this.state,
|
|
3826
|
+
nodeStates: new Map(this.state.nodeStates),
|
|
3827
|
+
nodeResults: new Map(this.state.nodeResults)
|
|
3828
|
+
};
|
|
3829
|
+
}
|
|
3830
|
+
/** 注入外部事件监听(由 AgentGraph 实现的 on() 接线) */
|
|
3831
|
+
setEventSink(sink) {
|
|
3832
|
+
this.eventSink = sink;
|
|
3833
|
+
return this;
|
|
3834
|
+
}
|
|
3835
|
+
abort() {
|
|
3836
|
+
this.abortController.abort();
|
|
3837
|
+
}
|
|
3838
|
+
/** 每次 run/stream 前重置中止信号与执行状态 */
|
|
3839
|
+
resetAbortController() {
|
|
3840
|
+
this.abortController = new AbortController();
|
|
3841
|
+
this.state = {
|
|
3842
|
+
nodeStates: /* @__PURE__ */ new Map(),
|
|
3843
|
+
nodeResults: /* @__PURE__ */ new Map(),
|
|
3844
|
+
stepsCompleted: 0,
|
|
3845
|
+
finished: false
|
|
3846
|
+
};
|
|
3847
|
+
this.state.nodeStates.set(this.proposerNode.id, "pending");
|
|
3848
|
+
this.state.nodeStates.set(this.reviewerNode.id, "pending");
|
|
3849
|
+
}
|
|
3850
|
+
throwIfAborted() {
|
|
3851
|
+
if (this.abortController.signal.aborted) {
|
|
3852
|
+
throw new GraphAbortedError();
|
|
3853
|
+
}
|
|
3159
3854
|
}
|
|
3160
3855
|
/** 执行 Debate */
|
|
3161
3856
|
async run(input) {
|
|
3857
|
+
this.resetAbortController();
|
|
3162
3858
|
const ctx = createSharedContext({
|
|
3163
3859
|
meta: { traceId: `debate-${Date.now()}`, startTime: Date.now() }
|
|
3164
3860
|
});
|
|
3861
|
+
storeOriginalInput(ctx, input);
|
|
3862
|
+
const emit = this.eventSink ?? (() => {
|
|
3863
|
+
});
|
|
3165
3864
|
try {
|
|
3166
|
-
|
|
3167
|
-
});
|
|
3865
|
+
const result = await this.executeDebate(input, ctx, emit);
|
|
3866
|
+
emit({ type: "graph_done", result });
|
|
3867
|
+
return result;
|
|
3168
3868
|
} catch (err) {
|
|
3169
|
-
|
|
3869
|
+
const result = this.buildErrorResult(ctx, err);
|
|
3870
|
+
emit({ type: "graph_error", error: result.error ?? "" });
|
|
3871
|
+
return result;
|
|
3170
3872
|
}
|
|
3171
3873
|
}
|
|
3172
3874
|
/** 流式执行 Debate */
|
|
3173
|
-
|
|
3875
|
+
stream(input) {
|
|
3876
|
+
this.resetAbortController();
|
|
3174
3877
|
const ctx = createSharedContext({
|
|
3175
3878
|
meta: { traceId: `debate-${Date.now()}`, startTime: Date.now() }
|
|
3176
3879
|
});
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
}
|
|
3191
|
-
).finally(() => {
|
|
3192
|
-
done = true;
|
|
3193
|
-
resolveEvent?.();
|
|
3194
|
-
});
|
|
3195
|
-
while (!done || eventQueue.length > 0) {
|
|
3196
|
-
if (eventQueue.length > 0) {
|
|
3197
|
-
yield eventQueue.shift();
|
|
3198
|
-
} else {
|
|
3199
|
-
await new Promise((resolve) => {
|
|
3200
|
-
resolveEvent = resolve;
|
|
3201
|
-
});
|
|
3202
|
-
}
|
|
3203
|
-
}
|
|
3204
|
-
await graphPromise;
|
|
3880
|
+
storeOriginalInput(ctx, input);
|
|
3881
|
+
return createEventStream(
|
|
3882
|
+
(emit) => this.executeDebate(input, ctx, emit).then(
|
|
3883
|
+
(result) => {
|
|
3884
|
+
emit({ type: "graph_done", result });
|
|
3885
|
+
},
|
|
3886
|
+
(err) => {
|
|
3887
|
+
this.state.error = err instanceof Error ? err.message : String(err);
|
|
3888
|
+
emit({ type: "graph_error", error: this.state.error });
|
|
3889
|
+
}
|
|
3890
|
+
),
|
|
3891
|
+
this.eventSink
|
|
3892
|
+
);
|
|
3205
3893
|
}
|
|
3206
3894
|
/** 核心执行逻辑 */
|
|
3207
3895
|
async executeDebate(input, ctx, emit) {
|
|
@@ -3213,10 +3901,11 @@ ${proposerResult.content}`;
|
|
|
3213
3901
|
let convergeReason = "";
|
|
3214
3902
|
let currentInput = input;
|
|
3215
3903
|
for (let round = 1; round <= this.maxRounds; round++) {
|
|
3904
|
+
this.throwIfAborted();
|
|
3216
3905
|
emit({ type: "round_start", round });
|
|
3217
3906
|
this.state.currentAgentId = this.proposerNode.id;
|
|
3218
3907
|
this.state.nodeStates.set(this.proposerNode.id, "running");
|
|
3219
|
-
emit({ type: "agent_start", agentId: this.proposerNode.id, agentName: this.proposerNode.name });
|
|
3908
|
+
emit({ type: "agent_start", agentId: this.proposerNode.id, agentName: this.proposerNode.name, input: toInputText(currentInput) });
|
|
3220
3909
|
try {
|
|
3221
3910
|
lastProposerResult = await executeNode(this.proposerNode, currentInput, ctx);
|
|
3222
3911
|
this.state.nodeStates.set(this.proposerNode.id, "completed");
|
|
@@ -3233,7 +3922,7 @@ ${proposerResult.content}`;
|
|
|
3233
3922
|
}
|
|
3234
3923
|
this.state.currentAgentId = this.reviewerNode.id;
|
|
3235
3924
|
this.state.nodeStates.set(this.reviewerNode.id, "running");
|
|
3236
|
-
emit({ type: "agent_start", agentId: this.reviewerNode.id, agentName: this.reviewerNode.name });
|
|
3925
|
+
emit({ type: "agent_start", agentId: this.reviewerNode.id, agentName: this.reviewerNode.name, input: lastProposerResult.content });
|
|
3237
3926
|
try {
|
|
3238
3927
|
lastReviewerResult = await executeNode(this.reviewerNode, lastProposerResult.content, ctx);
|
|
3239
3928
|
this.state.nodeStates.set(this.reviewerNode.id, "completed");
|
|
@@ -3272,16 +3961,20 @@ ${proposerResult.content}`;
|
|
|
3272
3961
|
}
|
|
3273
3962
|
buildErrorResult(ctx, err) {
|
|
3274
3963
|
this.state.finished = true;
|
|
3964
|
+
const aborted = err instanceof GraphAbortedError;
|
|
3965
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3966
|
+
this.state.error = errorMsg;
|
|
3967
|
+
const lastCompleted = [...this.state.nodeResults.values()].at(-1);
|
|
3275
3968
|
return {
|
|
3276
|
-
content: "",
|
|
3969
|
+
content: aborted ? lastCompleted?.content ?? "" : "",
|
|
3277
3970
|
lastAgentId: this.proposerNode.id,
|
|
3278
3971
|
agentResults: new Map(this.state.nodeResults),
|
|
3279
3972
|
totalUsage: {},
|
|
3280
3973
|
stepsCompleted: this.state.stepsCompleted,
|
|
3281
|
-
stopReason: "error",
|
|
3974
|
+
stopReason: aborted ? "aborted" : "error",
|
|
3282
3975
|
context: ctx,
|
|
3283
3976
|
success: false,
|
|
3284
|
-
error:
|
|
3977
|
+
error: errorMsg
|
|
3285
3978
|
};
|
|
3286
3979
|
}
|
|
3287
3980
|
mergeUsage(total, usage) {
|
|
@@ -3294,7 +3987,8 @@ ${proposerResult.content}`;
|
|
|
3294
3987
|
// patterns/debate.ts
|
|
3295
3988
|
var DebateGraphImpl = class {
|
|
3296
3989
|
executor;
|
|
3297
|
-
|
|
3990
|
+
/** on() 注册的监听器,按 MultiAgentEvent.type 分发 */
|
|
3991
|
+
bus = new SimpleEventBus();
|
|
3298
3992
|
constructor(proposer, reviewer, opts) {
|
|
3299
3993
|
this.executor = new DebateExecutor(proposer, reviewer, opts);
|
|
3300
3994
|
}
|
|
@@ -3311,23 +4005,21 @@ var DebateGraphImpl = class {
|
|
|
3311
4005
|
throw new Error("Debate \u6A21\u5F0F\u4E0D\u652F\u6301 setFinish");
|
|
3312
4006
|
}
|
|
3313
4007
|
async run(input) {
|
|
4008
|
+
this.executor.setEventSink((event) => this.bus.emit(event.type, event));
|
|
3314
4009
|
return this.executor.run(input);
|
|
3315
4010
|
}
|
|
3316
4011
|
async *stream(input) {
|
|
4012
|
+
this.executor.setEventSink((event) => this.bus.emit(event.type, event));
|
|
3317
4013
|
yield* this.executor.stream(input);
|
|
3318
4014
|
}
|
|
3319
4015
|
getState() {
|
|
3320
4016
|
return this.executor.getState();
|
|
3321
4017
|
}
|
|
3322
4018
|
abort() {
|
|
4019
|
+
this.executor.abort();
|
|
3323
4020
|
}
|
|
3324
4021
|
on(event, listener) {
|
|
3325
|
-
|
|
3326
|
-
if (!set) {
|
|
3327
|
-
set = /* @__PURE__ */ new Set();
|
|
3328
|
-
this.eventListeners.set(event, set);
|
|
3329
|
-
}
|
|
3330
|
-
set.add(listener);
|
|
4022
|
+
this.bus.on(event, listener);
|
|
3331
4023
|
return this;
|
|
3332
4024
|
}
|
|
3333
4025
|
};
|
|
@@ -3341,7 +4033,11 @@ var MapReduceExecutor = class {
|
|
|
3341
4033
|
reducerNode;
|
|
3342
4034
|
split;
|
|
3343
4035
|
concurrency;
|
|
4036
|
+
onMapperError;
|
|
3344
4037
|
reduceInputFormat;
|
|
4038
|
+
abortController = new AbortController();
|
|
4039
|
+
/** 外部事件监听(on() API 的底层接线) */
|
|
4040
|
+
eventSink;
|
|
3345
4041
|
state = {
|
|
3346
4042
|
nodeStates: /* @__PURE__ */ new Map(),
|
|
3347
4043
|
nodeResults: /* @__PURE__ */ new Map(),
|
|
@@ -3353,6 +4049,7 @@ var MapReduceExecutor = class {
|
|
|
3353
4049
|
this.reducerNode = reducer;
|
|
3354
4050
|
this.split = opts.split;
|
|
3355
4051
|
this.concurrency = opts.concurrency ?? Infinity;
|
|
4052
|
+
this.onMapperError = opts.onMapperError ?? "fail-fast";
|
|
3356
4053
|
this.reduceInputFormat = opts.reduceInputFormat ?? ((mapperResults) => {
|
|
3357
4054
|
const parts = [];
|
|
3358
4055
|
for (const [idx, result] of mapperResults) {
|
|
@@ -3365,132 +4062,98 @@ ${result.content}`);
|
|
|
3365
4062
|
this.state.nodeStates.set(reducer.id, "pending");
|
|
3366
4063
|
}
|
|
3367
4064
|
getState() {
|
|
3368
|
-
return {
|
|
4065
|
+
return {
|
|
4066
|
+
...this.state,
|
|
4067
|
+
nodeStates: new Map(this.state.nodeStates),
|
|
4068
|
+
nodeResults: new Map(this.state.nodeResults)
|
|
4069
|
+
};
|
|
3369
4070
|
}
|
|
3370
|
-
/**
|
|
3371
|
-
|
|
4071
|
+
/** 注入外部事件监听(由 AgentGraph 实现的 on() 接线) */
|
|
4072
|
+
setEventSink(sink) {
|
|
4073
|
+
this.eventSink = sink;
|
|
4074
|
+
return this;
|
|
4075
|
+
}
|
|
4076
|
+
abort() {
|
|
4077
|
+
this.abortController.abort();
|
|
4078
|
+
}
|
|
4079
|
+
/** 每次 run/stream 前重置中止信号与执行状态 */
|
|
4080
|
+
resetAbortController() {
|
|
4081
|
+
this.abortController = new AbortController();
|
|
4082
|
+
this.state = {
|
|
4083
|
+
nodeStates: /* @__PURE__ */ new Map(),
|
|
4084
|
+
nodeResults: /* @__PURE__ */ new Map(),
|
|
4085
|
+
stepsCompleted: 0,
|
|
4086
|
+
finished: false
|
|
4087
|
+
};
|
|
4088
|
+
this.state.nodeStates.set(this.mapperNode.id, "pending");
|
|
4089
|
+
this.state.nodeStates.set(this.reducerNode.id, "pending");
|
|
4090
|
+
}
|
|
4091
|
+
throwIfAborted() {
|
|
4092
|
+
if (this.abortController.signal.aborted) {
|
|
4093
|
+
throw new GraphAbortedError();
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
/** 拆分输入并校验(每次 run/stream 只调用一次 split,避免副作用放大) */
|
|
4097
|
+
splitInput(input) {
|
|
3372
4098
|
const inputText = typeof input === "string" ? input : input.message;
|
|
3373
4099
|
const chunks = this.split(inputText);
|
|
3374
4100
|
if (chunks.length === 0) {
|
|
3375
4101
|
throw new Error("MapReduce: split \u51FD\u6570\u8FD4\u56DE\u4E86\u7A7A\u6570\u7EC4\uFF0C\u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u5B50\u4EFB\u52A1");
|
|
3376
4102
|
}
|
|
4103
|
+
return chunks;
|
|
4104
|
+
}
|
|
4105
|
+
/** 执行 MapReduce */
|
|
4106
|
+
async run(input) {
|
|
4107
|
+
this.resetAbortController();
|
|
4108
|
+
const chunks = this.splitInput(input);
|
|
3377
4109
|
const ctx = createSharedContext({
|
|
3378
4110
|
meta: { traceId: `mr-${Date.now()}`, startTime: Date.now() }
|
|
3379
4111
|
});
|
|
4112
|
+
storeOriginalInput(ctx, input);
|
|
4113
|
+
const emit = this.eventSink ?? (() => {
|
|
4114
|
+
});
|
|
3380
4115
|
try {
|
|
3381
|
-
|
|
3382
|
-
});
|
|
4116
|
+
const result = await this.executeMapReduce(chunks, ctx, emit);
|
|
4117
|
+
emit({ type: "graph_done", result });
|
|
4118
|
+
return result;
|
|
3383
4119
|
} catch (err) {
|
|
3384
|
-
|
|
4120
|
+
const result = this.buildErrorResult(ctx, err);
|
|
4121
|
+
emit({ type: "graph_error", error: result.error ?? "" });
|
|
4122
|
+
return result;
|
|
3385
4123
|
}
|
|
3386
4124
|
}
|
|
3387
4125
|
/** 流式执行 MapReduce */
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
const chunks = this.
|
|
3391
|
-
if (chunks.length === 0) {
|
|
3392
|
-
throw new Error("MapReduce: split \u51FD\u6570\u8FD4\u56DE\u4E86\u7A7A\u6570\u7EC4\uFF0C\u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u5B50\u4EFB\u52A1");
|
|
3393
|
-
}
|
|
4126
|
+
stream(input) {
|
|
4127
|
+
this.resetAbortController();
|
|
4128
|
+
const chunks = this.splitInput(input);
|
|
3394
4129
|
const ctx = createSharedContext({
|
|
3395
4130
|
meta: { traceId: `mr-${Date.now()}`, startTime: Date.now() }
|
|
3396
4131
|
});
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
}
|
|
3411
|
-
).finally(() => {
|
|
3412
|
-
done = true;
|
|
3413
|
-
resolveEvent?.();
|
|
3414
|
-
});
|
|
3415
|
-
while (!done || eventQueue.length > 0) {
|
|
3416
|
-
if (eventQueue.length > 0) {
|
|
3417
|
-
yield eventQueue.shift();
|
|
3418
|
-
} else {
|
|
3419
|
-
await new Promise((resolve) => {
|
|
3420
|
-
resolveEvent = resolve;
|
|
3421
|
-
});
|
|
3422
|
-
}
|
|
3423
|
-
}
|
|
3424
|
-
await graphPromise;
|
|
4132
|
+
storeOriginalInput(ctx, input);
|
|
4133
|
+
return createEventStream(
|
|
4134
|
+
(emit) => this.executeMapReduce(chunks, ctx, emit).then(
|
|
4135
|
+
(result) => {
|
|
4136
|
+
emit({ type: "graph_done", result });
|
|
4137
|
+
},
|
|
4138
|
+
(err) => {
|
|
4139
|
+
this.state.error = err instanceof Error ? err.message : String(err);
|
|
4140
|
+
emit({ type: "graph_error", error: this.state.error });
|
|
4141
|
+
}
|
|
4142
|
+
),
|
|
4143
|
+
this.eventSink
|
|
4144
|
+
);
|
|
3425
4145
|
}
|
|
3426
4146
|
/** 核心执行逻辑 */
|
|
3427
|
-
async executeMapReduce(
|
|
3428
|
-
let stepsCompleted = 0;
|
|
4147
|
+
async executeMapReduce(chunks, ctx, emit) {
|
|
3429
4148
|
const totalUsage = {};
|
|
3430
|
-
|
|
3431
|
-
const chunks = this.split(inputText);
|
|
3432
|
-
const mapperResults = /* @__PURE__ */ new Map();
|
|
4149
|
+
this.throwIfAborted();
|
|
3433
4150
|
this.state.nodeStates.set(this.mapperNode.id, "running");
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
const result = await executeNode(this.mapperNode, chunk, ctx);
|
|
3441
|
-
this.state.nodeResults.set(`${this.mapperNode.id}_${index}`, result);
|
|
3442
|
-
this.mergeUsage(totalUsage, result.usage);
|
|
3443
|
-
stepsCompleted++;
|
|
3444
|
-
this.state.stepsCompleted = stepsCompleted;
|
|
3445
|
-
emit({ type: "agent_result", agentId: `${this.mapperNode.id}_${index}`, agentName: `${this.mapperNode.name}#${index + 1}`, result });
|
|
3446
|
-
return { index, result };
|
|
3447
|
-
} catch (err) {
|
|
3448
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3449
|
-
emit({ type: "agent_error", agentId: `${this.mapperNode.id}_${index}`, agentName: `${this.mapperNode.name}#${index + 1}`, error: errorMsg });
|
|
3450
|
-
throw err;
|
|
3451
|
-
}
|
|
3452
|
-
});
|
|
3453
|
-
const settled = await Promise.allSettled(promises);
|
|
3454
|
-
for (const s of settled) {
|
|
3455
|
-
if (s.status === "fulfilled") {
|
|
3456
|
-
mapperResults.set(s.value.index, s.value.result);
|
|
3457
|
-
}
|
|
3458
|
-
}
|
|
3459
|
-
} else {
|
|
3460
|
-
let index = 0;
|
|
3461
|
-
const executing = /* @__PURE__ */ new Set();
|
|
3462
|
-
const enqueue = () => {
|
|
3463
|
-
if (index >= chunks.length) return null;
|
|
3464
|
-
const currentIndex = index++;
|
|
3465
|
-
const chunk = chunks[currentIndex];
|
|
3466
|
-
const p = (async () => {
|
|
3467
|
-
emit({ type: "agent_start", agentId: `${this.mapperNode.id}_${currentIndex}`, agentName: `${this.mapperNode.name}#${currentIndex + 1}` });
|
|
3468
|
-
try {
|
|
3469
|
-
const result = await executeNode(this.mapperNode, chunk, ctx);
|
|
3470
|
-
mapperResults.set(currentIndex, result);
|
|
3471
|
-
this.state.nodeResults.set(`${this.mapperNode.id}_${currentIndex}`, result);
|
|
3472
|
-
this.mergeUsage(totalUsage, result.usage);
|
|
3473
|
-
stepsCompleted++;
|
|
3474
|
-
this.state.stepsCompleted = stepsCompleted;
|
|
3475
|
-
emit({ type: "agent_result", agentId: `${this.mapperNode.id}_${currentIndex}`, agentName: `${this.mapperNode.name}#${currentIndex + 1}`, result });
|
|
3476
|
-
} catch (err) {
|
|
3477
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3478
|
-
emit({ type: "agent_error", agentId: `${this.mapperNode.id}_${currentIndex}`, agentName: `${this.mapperNode.name}#${currentIndex + 1}`, error: errorMsg });
|
|
3479
|
-
throw err;
|
|
3480
|
-
}
|
|
3481
|
-
})();
|
|
3482
|
-
executing.add(p);
|
|
3483
|
-
p.finally(() => executing.delete(p));
|
|
3484
|
-
return p;
|
|
3485
|
-
};
|
|
3486
|
-
for (let i = 0; i < this.concurrency && index < chunks.length; i++) {
|
|
3487
|
-
enqueue();
|
|
3488
|
-
}
|
|
3489
|
-
while (executing.size > 0) {
|
|
3490
|
-
await Promise.race(executing);
|
|
3491
|
-
enqueue();
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
4151
|
+
chunks.forEach((_, i) => {
|
|
4152
|
+
this.state.nodeStates.set(`${this.mapperNode.id}_${i}`, "pending");
|
|
4153
|
+
});
|
|
4154
|
+
const { results: mapperResults, failed } = await this.runMappers(chunks, ctx, emit, totalUsage);
|
|
4155
|
+
let stepsCompleted = mapperResults.size + failed.length;
|
|
4156
|
+
this.state.stepsCompleted = stepsCompleted;
|
|
3494
4157
|
this.state.nodeStates.set(this.mapperNode.id, "completed");
|
|
3495
4158
|
const mapperResultsForEmit = /* @__PURE__ */ new Map();
|
|
3496
4159
|
for (const [idx, result] of mapperResults) {
|
|
@@ -3498,10 +4161,14 @@ ${result.content}`);
|
|
|
3498
4161
|
}
|
|
3499
4162
|
emit({ type: "parallel_done", results: mapperResultsForEmit });
|
|
3500
4163
|
ctx.blackboard.set("mapper_results", mapperResults);
|
|
4164
|
+
if (failed.length > 0 && mapperResults.size === 0) {
|
|
4165
|
+
throw new Error(`MapReduce: \u6240\u6709 mapper \u5747\u5931\u8D25\uFF08${failed.length}/${chunks.length}\uFF09`);
|
|
4166
|
+
}
|
|
3501
4167
|
const reduceInput = this.reduceInputFormat(mapperResults);
|
|
4168
|
+
this.throwIfAborted();
|
|
3502
4169
|
this.state.currentAgentId = this.reducerNode.id;
|
|
3503
4170
|
this.state.nodeStates.set(this.reducerNode.id, "running");
|
|
3504
|
-
emit({ type: "agent_start", agentId: this.reducerNode.id, agentName: this.reducerNode.name });
|
|
4171
|
+
emit({ type: "agent_start", agentId: this.reducerNode.id, agentName: this.reducerNode.name, input: reduceInput });
|
|
3505
4172
|
let reducerResult;
|
|
3506
4173
|
try {
|
|
3507
4174
|
reducerResult = await executeNode(this.reducerNode, reduceInput, ctx);
|
|
@@ -3518,29 +4185,94 @@ ${result.content}`);
|
|
|
3518
4185
|
throw err;
|
|
3519
4186
|
}
|
|
3520
4187
|
this.state.finished = true;
|
|
4188
|
+
const hasFailures = failed.length > 0;
|
|
3521
4189
|
return {
|
|
3522
4190
|
content: reducerResult.content,
|
|
3523
4191
|
lastAgentId: this.reducerNode.id,
|
|
3524
4192
|
agentResults: new Map(this.state.nodeResults),
|
|
3525
4193
|
totalUsage,
|
|
3526
4194
|
stepsCompleted,
|
|
3527
|
-
stopReason: "completed",
|
|
4195
|
+
stopReason: hasFailures ? "partial_failure" : "completed",
|
|
3528
4196
|
context: ctx,
|
|
3529
|
-
success: true
|
|
4197
|
+
success: true,
|
|
4198
|
+
...hasFailures ? { failedAgents: failed } : {}
|
|
4199
|
+
};
|
|
4200
|
+
}
|
|
4201
|
+
/** 并行执行所有 mapper 子任务(统一处理并发限制与失败策略) */
|
|
4202
|
+
async runMappers(chunks, ctx, emit, totalUsage) {
|
|
4203
|
+
const results = /* @__PURE__ */ new Map();
|
|
4204
|
+
const failed = [];
|
|
4205
|
+
const skip = this.onMapperError === "skip";
|
|
4206
|
+
const mapperId = this.mapperNode.id;
|
|
4207
|
+
const mapperAgentIds = chunks.map((_, i) => `${mapperId}_${i}`);
|
|
4208
|
+
emit({ type: "parallel_start", agentIds: mapperAgentIds });
|
|
4209
|
+
const runOne = async (index) => {
|
|
4210
|
+
const agentId = `${mapperId}_${index}`;
|
|
4211
|
+
const agentName = `${this.mapperNode.name}#${index + 1}`;
|
|
4212
|
+
emit({ type: "agent_start", agentId, agentName, input: chunks[index] });
|
|
4213
|
+
this.state.nodeStates.set(agentId, "running");
|
|
4214
|
+
try {
|
|
4215
|
+
const result = await executeNode(this.mapperNode, chunks[index], ctx);
|
|
4216
|
+
this.state.nodeResults.set(agentId, result);
|
|
4217
|
+
this.state.nodeStates.set(agentId, "completed");
|
|
4218
|
+
this.mergeUsage(totalUsage, result.usage);
|
|
4219
|
+
results.set(index, result);
|
|
4220
|
+
emit({ type: "agent_result", agentId, agentName, result });
|
|
4221
|
+
} catch (err) {
|
|
4222
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
4223
|
+
this.state.nodeStates.set(agentId, "failed");
|
|
4224
|
+
emit({ type: "agent_error", agentId, agentName, error: errorMsg });
|
|
4225
|
+
if (!skip) throw err;
|
|
4226
|
+
failed.push(agentId);
|
|
4227
|
+
}
|
|
3530
4228
|
};
|
|
4229
|
+
if (this.concurrency >= chunks.length) {
|
|
4230
|
+
const promises = chunks.map((_, i) => runOne(i));
|
|
4231
|
+
if (!skip) {
|
|
4232
|
+
await Promise.all(promises);
|
|
4233
|
+
} else {
|
|
4234
|
+
await Promise.allSettled(promises);
|
|
4235
|
+
}
|
|
4236
|
+
} else {
|
|
4237
|
+
let index = 0;
|
|
4238
|
+
const executing = /* @__PURE__ */ new Set();
|
|
4239
|
+
const enqueue = () => {
|
|
4240
|
+
if (index >= chunks.length) return null;
|
|
4241
|
+
const i = index++;
|
|
4242
|
+
const p = runOne(i).finally(() => {
|
|
4243
|
+
executing.delete(p);
|
|
4244
|
+
});
|
|
4245
|
+
executing.add(p);
|
|
4246
|
+
p.catch(() => {
|
|
4247
|
+
});
|
|
4248
|
+
return p;
|
|
4249
|
+
};
|
|
4250
|
+
for (let i = 0; i < this.concurrency && index < chunks.length; i++) {
|
|
4251
|
+
enqueue();
|
|
4252
|
+
}
|
|
4253
|
+
while (executing.size > 0) {
|
|
4254
|
+
await Promise.race(executing);
|
|
4255
|
+
enqueue();
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
return { results, failed };
|
|
3531
4259
|
}
|
|
3532
4260
|
buildErrorResult(ctx, err) {
|
|
3533
4261
|
this.state.finished = true;
|
|
4262
|
+
const aborted = err instanceof GraphAbortedError;
|
|
4263
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
4264
|
+
this.state.error = errorMsg;
|
|
4265
|
+
const lastCompleted = [...this.state.nodeResults.values()].at(-1);
|
|
3534
4266
|
return {
|
|
3535
|
-
content: "",
|
|
4267
|
+
content: aborted ? lastCompleted?.content ?? "" : "",
|
|
3536
4268
|
lastAgentId: this.mapperNode.id,
|
|
3537
4269
|
agentResults: new Map(this.state.nodeResults),
|
|
3538
4270
|
totalUsage: {},
|
|
3539
4271
|
stepsCompleted: this.state.stepsCompleted,
|
|
3540
|
-
stopReason: "error",
|
|
4272
|
+
stopReason: aborted ? "aborted" : "error",
|
|
3541
4273
|
context: ctx,
|
|
3542
4274
|
success: false,
|
|
3543
|
-
error:
|
|
4275
|
+
error: errorMsg
|
|
3544
4276
|
};
|
|
3545
4277
|
}
|
|
3546
4278
|
mergeUsage(total, usage) {
|
|
@@ -3553,7 +4285,8 @@ ${result.content}`);
|
|
|
3553
4285
|
// patterns/map-reduce.ts
|
|
3554
4286
|
var MapReduceGraphImpl = class {
|
|
3555
4287
|
executor;
|
|
3556
|
-
|
|
4288
|
+
/** on() 注册的监听器,按 MultiAgentEvent.type 分发 */
|
|
4289
|
+
bus = new SimpleEventBus();
|
|
3557
4290
|
constructor(mapper, reducer, opts) {
|
|
3558
4291
|
this.executor = new MapReduceExecutor(mapper, reducer, opts);
|
|
3559
4292
|
}
|
|
@@ -3570,23 +4303,21 @@ var MapReduceGraphImpl = class {
|
|
|
3570
4303
|
throw new Error("MapReduce \u6A21\u5F0F\u4E0D\u652F\u6301 setFinish");
|
|
3571
4304
|
}
|
|
3572
4305
|
async run(input) {
|
|
4306
|
+
this.executor.setEventSink((event) => this.bus.emit(event.type, event));
|
|
3573
4307
|
return this.executor.run(input);
|
|
3574
4308
|
}
|
|
3575
4309
|
async *stream(input) {
|
|
4310
|
+
this.executor.setEventSink((event) => this.bus.emit(event.type, event));
|
|
3576
4311
|
yield* this.executor.stream(input);
|
|
3577
4312
|
}
|
|
3578
4313
|
getState() {
|
|
3579
4314
|
return this.executor.getState();
|
|
3580
4315
|
}
|
|
3581
4316
|
abort() {
|
|
4317
|
+
this.executor.abort();
|
|
3582
4318
|
}
|
|
3583
4319
|
on(event, listener) {
|
|
3584
|
-
|
|
3585
|
-
if (!set) {
|
|
3586
|
-
set = /* @__PURE__ */ new Set();
|
|
3587
|
-
this.eventListeners.set(event, set);
|
|
3588
|
-
}
|
|
3589
|
-
set.add(listener);
|
|
4320
|
+
this.bus.on(event, listener);
|
|
3590
4321
|
return this;
|
|
3591
4322
|
}
|
|
3592
4323
|
};
|
|
@@ -4225,6 +4956,7 @@ var GraphDebugger = class {
|
|
|
4225
4956
|
const graphStartTime = Date.now();
|
|
4226
4957
|
const steps = [];
|
|
4227
4958
|
let stepIndex = 0;
|
|
4959
|
+
let finalResult;
|
|
4228
4960
|
for await (const event of this.graph.stream(input)) {
|
|
4229
4961
|
if (event.type === "agent_start") {
|
|
4230
4962
|
stepIndex++;
|
|
@@ -4234,7 +4966,7 @@ var GraphDebugger = class {
|
|
|
4234
4966
|
agentName: event.agentName,
|
|
4235
4967
|
startTime: Date.now(),
|
|
4236
4968
|
duration: 0,
|
|
4237
|
-
input: "",
|
|
4969
|
+
input: event.input ?? "",
|
|
4238
4970
|
output: "",
|
|
4239
4971
|
state: "running"
|
|
4240
4972
|
});
|
|
@@ -4259,6 +4991,8 @@ var GraphDebugger = class {
|
|
|
4259
4991
|
step.error = event.error;
|
|
4260
4992
|
step.state = "failed";
|
|
4261
4993
|
}
|
|
4994
|
+
} else if (event.type === "graph_done") {
|
|
4995
|
+
finalResult = event.result;
|
|
4262
4996
|
}
|
|
4263
4997
|
}
|
|
4264
4998
|
const state = this.graph.getState();
|
|
@@ -4269,10 +5003,10 @@ var GraphDebugger = class {
|
|
|
4269
5003
|
duration: graphDuration,
|
|
4270
5004
|
steps,
|
|
4271
5005
|
result: {
|
|
4272
|
-
success: !state.error,
|
|
4273
|
-
content:
|
|
4274
|
-
stopReason: state.error ? "error" : "completed",
|
|
4275
|
-
stepsCompleted: state.stepsCompleted
|
|
5006
|
+
success: finalResult ? finalResult.success : !state.error,
|
|
5007
|
+
content: finalResult?.content ?? "",
|
|
5008
|
+
stopReason: finalResult?.stopReason ?? (state.error ? "error" : "completed"),
|
|
5009
|
+
stepsCompleted: finalResult?.stepsCompleted ?? state.stepsCompleted
|
|
4276
5010
|
}
|
|
4277
5011
|
};
|
|
4278
5012
|
}
|
|
@@ -4324,13 +5058,16 @@ function createDebugger(graph) {
|
|
|
4324
5058
|
return new GraphDebugger(graph);
|
|
4325
5059
|
}
|
|
4326
5060
|
export {
|
|
5061
|
+
GraphAbortedError,
|
|
4327
5062
|
GraphDebugger,
|
|
4328
5063
|
MCPBridge,
|
|
5064
|
+
NodeTimeoutError,
|
|
4329
5065
|
SimpleEventBus,
|
|
4330
5066
|
SimpleToolRegistry,
|
|
4331
5067
|
createAgentGraph,
|
|
4332
5068
|
createDebate,
|
|
4333
5069
|
createDebugger,
|
|
5070
|
+
createEventStream,
|
|
4334
5071
|
createMCPBridge,
|
|
4335
5072
|
createMapReduce,
|
|
4336
5073
|
createMultiAgentMcpServerHost,
|