parser.js
5.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/**
* Parser - 解析 Agent 调用和结果标记
*
* 解析策略(按优先级):
* 1. JSON 优先:从 ```json 代码块中提取结构化调用指令
* 2. 文本标记降级:正则匹配 [CALL:...][/CALL] 和 [RESULT:...][/RESULT]
*
* 串行决策约束:即使解析到多个 CALL,也只返回最后一个。
* 这是刻意的设计决策(见架构文档 §4.4),确保因果链完整性。
*/
class Parser {
/**
* 解析 CALL 指令
* 优先尝试 JSON 解析,失败时降级到正则
*
* @param {string} output - Agent 输出
* @param {Object} [options]
* @param {Object} [options.agents] - Agent 配置(用于 parallelGroup 判断)
* @param {boolean} [options.allowParallel] - 是否允许并行调用所有的 calls
* @returns {Array<{agent: string, task: string}>}
*/
static parseCalls(output, options = {}) {
// 1. 尝试 JSON 优先解析
const jsonCalls = this._parseCallsFromJson(output);
if (jsonCalls.length > 0) {
if (options.allowParallel) return jsonCalls;
return this._applySerialConstraint(jsonCalls, options.agents);
}
// 2. 降级到正则解析
const regexCalls = this._parseCallsFromRegex(output);
if (options.allowParallel) return regexCalls;
return this._applySerialConstraint(regexCalls, options.agents);
}
/**
* 从 JSON 代码块中提取 CALL 指令
* 支持两种 JSON 格式:
* - 单 CALL: { "agent": "coder", "task": "..." }
* - 多 CALL: { "calls": [{ "agent": "coder", "task": "..." }, ...] }
* @private
*/
static _parseCallsFromJson(output) {
const calls = [];
// 匹配 ```json ... ``` 代码块
const jsonBlockRegex = /```json\s*\n?([\s\S]*?)```/g;
let match;
while ((match = jsonBlockRegex.exec(output)) !== null) {
try {
const parsed = JSON.parse(match[1].trim());
if (Array.isArray(parsed?.calls)) {
// 多 CALL 格式: { calls: [{ agent, task }, ...] }
for (const item of parsed.calls) {
if (item.agent && item.task) {
calls.push({ agent: item.agent.trim(), task: item.task.trim() });
}
}
} else if (parsed?.agent && parsed?.task) {
// 单 CALL 格式: { agent, task }
calls.push({ agent: parsed.agent.trim(), task: parsed.task.trim() });
}
} catch {
// JSON 解析失败,忽略这个代码块
}
}
return calls;
}
/**
* 从文本标记中提取 CALL 指令
* 只返回最后一个非占位符的 CALL
* @private
*/
static _parseCallsFromRegex(output) {
const calls = [];
const regex = /\[CALL:\s*([\w-]+)\s*\]([\s\S]*?)\[\/CALL\]/g;
let match;
// 占位符模式(示例中的假 agent 名)
const placeholderPattern = /^(agent[_-]?name|agent|name|xxx|example)$/i;
while ((match = regex.exec(output)) !== null) {
const agent = match[1].trim();
const task = match[2].trim();
// 跳过占位符示例
if (placeholderPattern.test(agent)) {
continue;
}
calls.push({ agent, task });
}
return calls;
}
/**
* 应用串行决策约束:默认只返回最后一个 CALL
* 除非 agents 配置了 parallelGroup 且属于同组
* @private
*/
static _applySerialConstraint(calls, agents) {
if (calls.length <= 1) return calls;
// 如果没有 agents 配置,严格串行
if (!agents) {
return [calls[calls.length - 1]];
}
// 检查是否所有 CALL 都属于同一个 parallelGroup
const groups = new Set();
let hasUngrouped = false;
for (const call of calls) {
const cfg = agents[call.agent];
if (cfg?.parallelGroup) {
groups.add(cfg.parallelGroup);
} else {
hasUngrouped = true;
}
}
// 全部属于同一个 parallelGroup → 允许并行返回
if (!hasUngrouped && groups.size === 1) {
return calls;
}
// 否则严格串行:只返回最后一个
return [calls[calls.length - 1]];
}
/**
* 解析 [RESULT: STATUS]...[/RESULT] 标记
* @param {string} output - Agent 输出
* @returns {{status: string, message: string}}
*/
static parseResult(output) {
// 1. 尝试 JSON 优先
const jsonResult = this._parseResultFromJson(output);
if (jsonResult) return jsonResult;
// 2. 降级到正则
const match = output.match(/\[RESULT:\s*(\w+)\s*\]([\s\S]*?)\[\/RESULT\]/);
if (!match) {
return { status: 'ERROR', message: 'No result marker found' };
}
return {
status: match[1].trim(),
message: match[2].trim()
};
}
/**
* 从 JSON 代码块中提取 RESULT
* 格式: { "status": "DONE", "message": "..." }
* @private
*/
static _parseResultFromJson(output) {
const jsonBlockRegex = /```json\s*\n?([\s\S]*?)```/g;
let match;
while ((match = jsonBlockRegex.exec(output)) !== null) {
try {
const parsed = JSON.parse(match[1].trim());
if (parsed?.status && typeof parsed.status === 'string') {
const validStatuses = ['DONE', 'PASS', 'FAIL', 'ERROR'];
const status = parsed.status.toUpperCase().trim();
if (validStatuses.includes(status)) {
return {
status,
message: (parsed.message || '').trim(),
};
}
}
} catch {
// JSON 解析失败,继续尝试下一个代码块
}
}
return null;
}
}
module.exports = { Parser };