/** * 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 };