@saber2pr/ai-agent 0.0.68 → 0.0.70

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.
@@ -36,6 +36,8 @@ export declare abstract class AgentGraphModel extends BaseChatModel {
36
36
  callApiStream(prompt: string, lastMsg: BaseMessage, onChunk: StreamChunkCallback): Promise<AgentGraphLLMResponse>;
37
37
  _generate(messages: BaseMessage[]): Promise<ChatResult>;
38
38
  private parseToolCalls;
39
+ /** 从 text[start] 开始,按括号配对提取完整的 JSON 对象字符串 */
40
+ private extractBalancedJson;
39
41
  /**
40
42
  * 流式生成:调用 callApiStream 进行流式输出,完成后构建完整的 ChatResult。
41
43
  * @param messages LangChain 消息列表
@@ -96,37 +96,38 @@ class AgentGraphModel extends chat_models_1.BaseChatModel {
96
96
  }
97
97
  parseToolCalls(text) {
98
98
  const actionMatch = text.match(/Action:\s*(\w+)/);
99
- const argsMatch = text.match(/Arguments:\s*({[\s\S]*?})(?=\n|$)/);
100
99
  if (!actionMatch)
101
100
  return [];
102
101
  let args = {};
103
- if (argsMatch) {
104
- try {
105
- let raw = argsMatch[1].trim();
106
- // 关键修复:递归解析,直到它变成真正的对象
107
- // 这能处理 "\"{\\\"path\\\":...}\"" 这种套娃字符串
108
- let safetyDepth = 0;
109
- while (typeof raw === 'string' && safetyDepth < 5) {
110
- try {
111
- const parsed = JSON.parse(raw);
112
- if (typeof parsed === 'object' && parsed !== null) {
102
+ const argsIdx = text.search(/Arguments:\s*\{/);
103
+ if (argsIdx !== -1) {
104
+ const jsonStart = text.indexOf('{', argsIdx);
105
+ const jsonStr = this.extractBalancedJson(text, jsonStart);
106
+ if (jsonStr) {
107
+ try {
108
+ let raw = jsonStr;
109
+ let safetyDepth = 0;
110
+ while (typeof raw === 'string' && safetyDepth < 5) {
111
+ try {
112
+ const parsed = JSON.parse(raw);
113
+ if (typeof parsed === 'object' && parsed !== null) {
114
+ raw = parsed;
115
+ break;
116
+ }
113
117
  raw = parsed;
118
+ }
119
+ catch {
114
120
  break;
115
121
  }
116
- raw = parsed; // 如果解析后还是 string,继续剥
117
- }
118
- catch {
119
- break; // 解析不动了,跳出
122
+ safetyDepth++;
120
123
  }
121
- safetyDepth++;
124
+ args = raw;
125
+ }
126
+ catch {
127
+ args = {};
122
128
  }
123
- args = raw;
124
- }
125
- catch (e) {
126
- args = {};
127
129
  }
128
130
  }
129
- // ✅ 此时返回的 args 必须是 object 类型
130
131
  return [
131
132
  {
132
133
  name: actionMatch[1],
@@ -136,6 +137,39 @@ class AgentGraphModel extends chat_models_1.BaseChatModel {
136
137
  },
137
138
  ];
138
139
  }
140
+ /** 从 text[start] 开始,按括号配对提取完整的 JSON 对象字符串 */
141
+ extractBalancedJson(text, start) {
142
+ if (start < 0 || start >= text.length || text[start] !== '{')
143
+ return null;
144
+ let depth = 0;
145
+ let inString = false;
146
+ let escape = false;
147
+ for (let i = start; i < text.length; i++) {
148
+ const ch = text[i];
149
+ if (escape) {
150
+ escape = false;
151
+ continue;
152
+ }
153
+ if (ch === '\\' && inString) {
154
+ escape = true;
155
+ continue;
156
+ }
157
+ if (ch === '"') {
158
+ inString = !inString;
159
+ continue;
160
+ }
161
+ if (inString)
162
+ continue;
163
+ if (ch === '{')
164
+ depth++;
165
+ else if (ch === '}') {
166
+ depth--;
167
+ if (depth === 0)
168
+ return text.substring(start, i + 1);
169
+ }
170
+ }
171
+ return null;
172
+ }
139
173
  /**
140
174
  * 流式生成:调用 callApiStream 进行流式输出,完成后构建完整的 ChatResult。
141
175
  * @param messages LangChain 消息列表
@@ -211,8 +245,8 @@ ${systemContext}
211
245
  ${format(lastMsg)}
212
246
  # Output Requirement
213
247
  1. Reasoning in <think> tags.
214
- 2. Action: Name
215
- 3. Arguments: {JSON}
248
+ 2. Action: ToolName
249
+ 3. Arguments: {"key": "value"} (MUST be a single-line compact JSON, no newlines inside)
216
250
  `.trim();
217
251
  }
218
252
  _llmType() {
@@ -4,10 +4,44 @@ exports.batchRunTools = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const createTool_1 = require("../../utils/createTool");
6
6
  const kit_1 = require("../../utils/kit");
7
+ const formatOutput = (output) => {
8
+ // 1. 处理空值
9
+ if (output === null || output === undefined)
10
+ return String(output);
11
+ // 2. 处理数组 (MCP 常见返回格式)
12
+ if (Array.isArray(output)) {
13
+ // 尝试提取所有 text 类型的 content 并合并,如果不是 MCP 格式则递归处理
14
+ const isMcpStyle = output.every(item => item && typeof item === 'object' && 'type' in item);
15
+ if (isMcpStyle) {
16
+ return output
17
+ .map(item => {
18
+ if (item.type === 'text')
19
+ return item.text;
20
+ if (item.type === 'resource')
21
+ return `[Resource: ${item.resource?.uri}]`;
22
+ return JSON.stringify(item); // 无法识别的 MCP 类型(如 image)保持 JSON
23
+ })
24
+ .join('\n');
25
+ }
26
+ // 普通数组递归处理
27
+ return output.map(item => formatOutput(item)).join('\n');
28
+ }
29
+ // 3. 处理单个对象
30
+ if (typeof output === 'object') {
31
+ // 处理单个 MCP 文本对象
32
+ if (output.type === 'text' && typeof output.text === 'string') {
33
+ return output.text;
34
+ }
35
+ // 其他对象转为格式化 JSON
36
+ return JSON.stringify(output, null, 2);
37
+ }
38
+ // 4. 基本类型直接转 String
39
+ return String(output);
40
+ };
7
41
  exports.batchRunTools = (0, createTool_1.createTool)({
8
42
  name: 'batch_run_tools',
9
43
  // 使用更具指令性的英文描述
10
- description: `Execute multiple tools in parallel. Use this for independent tasks such as reading multiple files, applying unrelated edits to different files, or fetching diagnostics simultaneously. CRITICAL: DO NOT apply multiple edits to the same file within a single batch to avoid write conflicts.`,
44
+ description: `Execute multiple read-only tools in parallel. Such as reading multiple files or fetching diagnostics simultaneously.`,
11
45
  parameters: zod_1.z.object({
12
46
  actions: zod_1.z.array(zod_1.z.object({
13
47
  // 字段名和描述对齐 AI 习惯
@@ -18,6 +52,11 @@ exports.batchRunTools = (0, createTool_1.createTool)({
18
52
  handler: async ({ actions }, context) => {
19
53
  // 1. 获取所有可用工具的映射
20
54
  const toolMap = (0, kit_1.getArray)(context?.allTools).reduce((acc, tool) => ({ ...acc, [tool.function.name]: tool }), {});
55
+ // 2. 预检:拦截并发写入行为
56
+ const writeTools = actions.filter(action => /edit|write|replace|delete/i.test(action.tool_name));
57
+ if (writeTools.length > 0) {
58
+ return `Error: Parallel write operations detected (${writeTools.map(t => t.tool_name).join(', ')}). Batching is ONLY allowed for READ-ONLY operations.`;
59
+ }
21
60
  if (Object.keys(toolMap).length === 0) {
22
61
  return 'Error: Failed to retrieve tool execution context.';
23
62
  }
@@ -49,8 +88,6 @@ exports.batchRunTools = (0, createTool_1.createTool)({
49
88
  };
50
89
  }
51
90
  }));
52
- // 3. 格式化输出结果
53
- // 虽然参数改成了英文,但返回给 AI 的结果标题可以保留清晰的结构
54
- return results.map(res => `### Tool: ${res.tool_name} (${res.status})\n${res.output}\n---`).join('\n');
91
+ return results.map(res => `### Tool: ${res.tool_name} (${res.status})\n${formatOutput(res.output)}\n---`).join('\n');
55
92
  }
56
93
  });
@@ -15,7 +15,7 @@ Operating System: ${osPlatform}
15
15
  Before providing any output or calling a tool, you **MUST** conduct a deep logical analysis. Wrap your thought process within <think> tags.
16
16
 
17
17
  # Tool Call Specifications
18
- 1. **Pure JSON Arguments**: Arguments must be a valid JSON object. NEVER wrap the entire JSON object in a string or quotes.
18
+ 1. **Pure JSON Arguments**: Arguments MUST be a single-line compact JSON object (no newlines, no pretty-printing). NEVER wrap the entire JSON object in a string or quotes. Example: Arguments: {"path": "/foo", "content": "bar"}
19
19
  2. **No Double Escaping**: Do not double-escape characters within the JSON.
20
20
  3. **No Idle Operations**: If the task is complete or no tool is needed, DO NOT output any "Action" structure. Never use "None", "null", or empty strings as a tool name.
21
21
  4. **Important Note**: For optimal efficiency, when performing multiple operations, use batch_run_tools to invoke all relevant tools in parallel, rather than sequentially. Prioritize parallel tool invocation whenever possible. For example, when reading three files, run three tool invocations in parallel to read all three files into the context simultaneously. When running multiple read-only commands (such as read_file_range, grep_search, or read_text_file), always run all commands in parallel. Use parallel tool invocations whenever possible, rather than running too many tools sequentially.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saber2pr/ai-agent",
3
- "version": "0.0.68",
3
+ "version": "0.0.70",
4
4
  "description": "AI Assistant CLI.",
5
5
  "author": "saber2pr",
6
6
  "license": "ISC",