@amical/aiagent-sdk 0.1.9 → 0.1.11

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.js CHANGED
@@ -18,6 +18,7 @@ var AgentDefaultTool = /* @__PURE__ */ ((AgentDefaultTool2) => {
18
18
  AgentDefaultTool2["FILE_DELETE"] = "agent_delete_file";
19
19
  AgentDefaultTool2["DIR_DELETE"] = "agent_delete_directory";
20
20
  AgentDefaultTool2["SEARCH"] = "agent_search_files";
21
+ AgentDefaultTool2["SEARCH_CONTENT"] = "agent_search_file_content";
21
22
  AgentDefaultTool2["LIST_FILES"] = "agent_list_files";
22
23
  AgentDefaultTool2["LIST_CODE_DEF"] = "agent_list_code_definition_names";
23
24
  AgentDefaultTool2["BROWSER"] = "agent_browser_action";
@@ -191,11 +192,22 @@ return (
191
192
  },
192
193
  {
193
194
  name: "agent_search_files" /* SEARCH */,
194
- description: "Search for files in a directory that match a regex pattern or glob pattern.",
195
+ description: "Search for files or directories by name in a directory tree, matching the name against a regex (and optionally a glob). Directories are traversed level by level: all matches in the current level are returned before descending into deeper levels. Use this tool only to locate files/directories by name.",
195
196
  params: {
196
197
  path: "(required) The absolute or relative directory path to search.",
197
- regex: "(required) The regex pattern to match files against.",
198
- file_pattern: "(optional) glob pattern like *.ts."
198
+ regex: "(required) The regex pattern to match file or directory names against.",
199
+ file_pattern: "(optional) glob pattern like *.ts.",
200
+ include_node_modules: "(optional) set to true to include node_modules in the search. Defaults to false, so node_modules is skipped."
201
+ }
202
+ },
203
+ {
204
+ name: "agent_search_file_content" /* SEARCH_CONTENT */,
205
+ description: "Search the contents of files in a directory tree for a regex pattern. Use this tool when you need to find text or code inside files rather than matching file or directory names.",
206
+ params: {
207
+ path: "(required) The absolute or relative directory path to search.",
208
+ regex: "(required) The regex pattern to match file content against.",
209
+ file_pattern: "(optional) glob pattern like *.ts to limit which files are searched.",
210
+ include_node_modules: "(optional) set to true to include node_modules in the search. Defaults to false, so node_modules is skipped."
199
211
  }
200
212
  },
201
213
  {
@@ -264,6 +276,7 @@ var READ_ONLY_TOOLS = [
264
276
  "agent_list_files" /* LIST_FILES */,
265
277
  "agent_read_file" /* FILE_READ */,
266
278
  "agent_search_files" /* SEARCH */,
279
+ "agent_search_file_content" /* SEARCH_CONTENT */,
267
280
  "agent_list_code_definition_names" /* LIST_CODE_DEF */,
268
281
  "agent_browser_action" /* BROWSER */,
269
282
  "agent_ask_followup_question" /* ASK */,
@@ -272,6 +285,44 @@ var READ_ONLY_TOOLS = [
272
285
  "agent_load_skill" /* LOAD_SKILL */,
273
286
  "agent_use_subagents" /* USE_SUBAGENTS */
274
287
  ];
288
+
289
+ // src/shared/custom-tools.ts
290
+ function isCustomToolGroup(tool) {
291
+ return Array.isArray(tool.children) && tool.children.length > 0;
292
+ }
293
+ function collectCustomToolNames(tools) {
294
+ const names = [];
295
+ const visit = (list) => {
296
+ for (const tool of list) {
297
+ names.push(tool.name);
298
+ if (Array.isArray(tool.children) && tool.children.length > 0) {
299
+ visit(tool.children);
300
+ }
301
+ }
302
+ };
303
+ visit(tools);
304
+ return names;
305
+ }
306
+ function renderCustomToolPrompt(tool, cwd = "") {
307
+ const params = tool.params ? Object.entries(tool.params) : [];
308
+ const replaceCwd = (text) => String(text).replaceAll("${context.cwd}", cwd);
309
+ const paramLines = params.map(([name, description]) => `- ${name}: ${replaceCwd(description)}`).join("\n");
310
+ const usageLines = params.map(([name, description]) => `<${name}>${replaceCwd(description)}</${name}>`).join("\n");
311
+ return `### ${tool.name}
312
+ Description: ${tool.description}
313
+ Parameters:
314
+ ${paramLines}
315
+ - task_progress: (optional) A checklist showing task progress after this tool use is completed. The task_progress parameter must be included as a separate parameter inside of the parent tool call, it must be separate from other parameters such as content, arguments, etc. (See 'UPDATING TASK PROGRESS' section for more details)
316
+ Usage:
317
+ <${tool.name}>
318
+ ${usageLines}
319
+ <task_progress>Checklist here (optional)</task_progress>
320
+ </${tool.name}>
321
+ `;
322
+ }
323
+ function renderCustomToolsPrompt(tools, cwd = "") {
324
+ return tools.map((tool) => renderCustomToolPrompt(tool, cwd)).join("\n");
325
+ }
275
326
  function parseAssistantMessageV2(assistantMessage, toolParamNames2) {
276
327
  const contentBlocks = [];
277
328
  let currentTextContentStart = 0;
@@ -433,6 +484,7 @@ var toolParamNames = [
433
484
  "diff",
434
485
  "regex",
435
486
  "file_pattern",
487
+ "include_node_modules",
436
488
  "recursive",
437
489
  "action",
438
490
  "url",
@@ -471,6 +523,66 @@ var toolParamNames = [
471
523
  "start_line",
472
524
  "end_line"
473
525
  ];
526
+ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
527
+ "txt",
528
+ "md",
529
+ "markdown",
530
+ "json",
531
+ "js",
532
+ "mjs",
533
+ "cjs",
534
+ "ts",
535
+ "tsx",
536
+ "jsx",
537
+ "html",
538
+ "htm",
539
+ "css",
540
+ "scss",
541
+ "sass",
542
+ "less",
543
+ "py",
544
+ "java",
545
+ "c",
546
+ "cpp",
547
+ "cc",
548
+ "cxx",
549
+ "h",
550
+ "hpp",
551
+ "cs",
552
+ "go",
553
+ "rs",
554
+ "rb",
555
+ "php",
556
+ "sh",
557
+ "bash",
558
+ "zsh",
559
+ "bat",
560
+ "cmd",
561
+ "ps1",
562
+ "yaml",
563
+ "yml",
564
+ "toml",
565
+ "ini",
566
+ "cfg",
567
+ "conf",
568
+ "xml",
569
+ "svg",
570
+ "csv",
571
+ "log",
572
+ "sql",
573
+ "vue",
574
+ "svelte",
575
+ "astro",
576
+ "graphql",
577
+ "gql",
578
+ "prisma",
579
+ "env",
580
+ "gitignore",
581
+ "dockerfile",
582
+ "makefile",
583
+ "properties",
584
+ "lock"
585
+ ]);
474
586
  var AgentState = /* @__PURE__ */ ((AgentState2) => {
475
587
  AgentState2["IDLE"] = "idle";
476
588
  AgentState2["THINKING"] = "thinking";
@@ -494,9 +606,19 @@ var ClineAgent = class {
494
606
  skillDir;
495
607
  rulesDir;
496
608
  customTools;
609
+ /** 当前可用(已展开集合)的顶层工具列表,传给 PromptManager 渲染 */
610
+ activeCustomTools;
611
+ /** 所有已知工具名(含未激活 children),便于查找定义 */
612
+ customToolsByName;
497
613
  customToolNames;
498
614
  allowDefaultTools;
499
615
  customInstructions;
616
+ /** 已激活的工具集入口名 */
617
+ activatedGroups = /* @__PURE__ */ new Set();
618
+ /** activeCustomTools 变更版本号,用于 Prompt 缓存失效 */
619
+ activeToolsVersion = 0;
620
+ /** 缓存 sysPrompt 时记录的版本号 */
621
+ promptActiveToolsVersion = -1;
500
622
  sysPrompt;
501
623
  noUseTollSysPrompt;
502
624
  // 简单的事件回调系统,供宿主环境注册以监听 Agent 内部事件
@@ -516,31 +638,38 @@ var ClineAgent = class {
516
638
  this.allowDefaultTools.push("agent_attempt_completion" /* ATTEMPT */);
517
639
  }
518
640
  this.customInstructions = options.customInstructions;
641
+ this.customToolsByName = /* @__PURE__ */ new Map();
642
+ this.activeCustomTools = [...this.customTools];
643
+ this.customToolNames = new Set(collectCustomToolNames(this.customTools));
519
644
  if (this.customTools.length > 0) {
520
- const dynamicToolNames = /* @__PURE__ */ new Set();
521
645
  const customToolParams = /* @__PURE__ */ new Set();
522
- for (const customTool of this.customTools) {
523
- dynamicToolNames.add(customTool.name);
524
- if (customTool.params) {
525
- for (const key in customTool.params) {
526
- if (!Object.hasOwn(customTool.params, key)) continue;
527
- customToolParams.add(key);
646
+ const visit = (tools) => {
647
+ for (const customTool of tools) {
648
+ this.customToolsByName.set(customTool.name, customTool);
649
+ if (customTool.params) {
650
+ for (const key in customTool.params) {
651
+ if (!Object.hasOwn(customTool.params, key)) continue;
652
+ customToolParams.add(key);
653
+ }
654
+ }
655
+ if (Array.isArray(customTool.children) && customTool.children.length > 0) {
656
+ visit(customTool.children);
528
657
  }
529
658
  }
530
- }
659
+ };
660
+ visit(this.customTools);
531
661
  const defaultToolNames = Object.values(AgentDefaultTool);
532
- const overlappingTools = Array.from(dynamicToolNames).filter((name) => defaultToolNames.includes(name));
662
+ const overlappingTools = Array.from(this.customToolNames).filter((name) => defaultToolNames.includes(name));
533
663
  if (overlappingTools.length > 0) {
534
664
  throw new Error(`Custom tools and default tools have overlapping names: ${overlappingTools.join(", ")}`);
535
665
  }
536
- if (dynamicToolNames.size > 0) {
537
- setDynamicToolUseNames("customUser", Array.from(dynamicToolNames));
666
+ if (this.customToolNames.size > 0) {
667
+ setDynamicToolUseNames("customUser", Array.from(this.customToolNames));
538
668
  }
539
669
  if (customToolParams.size > 0) {
540
670
  this.allToolParamNames = Array.from(/* @__PURE__ */ new Set([...this.allToolParamNames, ...Array.from(customToolParams)]));
541
671
  }
542
672
  }
543
- this.customToolNames = new Set(this.customTools.map((t) => t.name));
544
673
  this.skillDir = this.runtime.path.join(this.runtime.env.cwd(), ".agents/skills");
545
674
  this.rulesDir = this.runtime.path.join(this.runtime.env.cwd(), ".agents/rules");
546
675
  }
@@ -564,10 +693,11 @@ var ClineAgent = class {
564
693
  shell: this.runtime.env.shell(),
565
694
  skills: await this.getSkills(),
566
695
  agentRules: await this.getAgentRules(),
567
- customTools: this.customTools,
696
+ customTools: this.activeCustomTools,
568
697
  allowDefaultTools: this.allowDefaultTools,
569
698
  customInstructions: this.customInstructions
570
699
  };
700
+ this.promptActiveToolsVersion = this.activeToolsVersion;
571
701
  this.sysPrompt = await this.promptManager.getSystemPrompt(context);
572
702
  this.noUseTollSysPrompt = this.promptManager.getNoUseToolInstructions(context);
573
703
  return { sysPrompt: this.sysPrompt, noUseTollSysPrompt: this.noUseTollSysPrompt };
@@ -676,9 +806,9 @@ ${content.trim()}\`\`\``);
676
806
  * 核心任务循环
677
807
  */
678
808
  async loop() {
679
- const { sysPrompt, noUseTollSysPrompt } = await this.getPrompt(true);
680
- const { customTools, customToolNames } = this;
809
+ const { customToolsByName, customToolNames } = this;
681
810
  while (this.state === "thinking" /* THINKING */) {
811
+ const { sysPrompt, noUseTollSysPrompt } = await this.getCurrentPrompt();
682
812
  this.loopCount++;
683
813
  if (this.loopCount > this.maxLoopCount) {
684
814
  await this.memory.appendMessage({
@@ -723,7 +853,33 @@ ${content.trim()}\`\`\``);
723
853
  }
724
854
  }
725
855
  if (customToolNames.has(tool.name)) {
726
- const customTool = customTools.find((t) => t.name === tool.name);
856
+ const customTool = customToolsByName.get(tool.name);
857
+ if (!customTool) {
858
+ toolResults.push({ tool, result: `Error: Custom tool "${tool.name}" not found.` });
859
+ continue;
860
+ }
861
+ if (isCustomToolGroup(customTool)) {
862
+ let groupResult;
863
+ if (customTool.onCall) {
864
+ try {
865
+ groupResult = await customTool.onCall(tool.params);
866
+ } catch (error) {
867
+ toolResults.push({ tool, result: `Error: ${error.message}` });
868
+ continue;
869
+ }
870
+ }
871
+ const childrenPrompt = this.activateCustomToolGroup(customTool);
872
+ const finalPrompt = `# New Available Tools
873
+
874
+ The following are instructions for using the new available tools.
875
+
876
+ ${childrenPrompt}`;
877
+ const promptBlock = groupResult !== void 0 ? `${typeof groupResult === "string" ? groupResult : JSON.stringify(groupResult)}
878
+
879
+ ${finalPrompt}` : finalPrompt;
880
+ toolResults.push({ tool, result: promptBlock });
881
+ continue;
882
+ }
727
883
  if (customTool.onCall) {
728
884
  try {
729
885
  const res = await customTool.onCall(tool.params);
@@ -826,15 +982,36 @@ STDERR: ${result.stderr}` });
826
982
  const path = tool.params.path || this.runtime.env.cwd();
827
983
  const regexStr = tool.params.regex;
828
984
  const filePattern = tool.params.file_pattern;
985
+ const includeNodeModules = tool.params.include_node_modules === "true";
829
986
  if (regexStr && path) {
830
987
  try {
831
988
  let result;
832
989
  if (typeof this.runtime.fs.searchFiles === "function") {
833
990
  result = await this.runtime.fs.searchFiles(path, regexStr, filePattern);
834
991
  } else {
835
- result = await this.searchFiles(path, regexStr, filePattern);
992
+ result = await this.searchFiles(path, regexStr, filePattern, includeNodeModules);
836
993
  }
837
- toolResults.push({ tool, result: `${result}` });
994
+ toolResults.push({ tool, result });
995
+ } catch (e) {
996
+ toolResults.push({ tool, result: `Error: ${e.message}` });
997
+ }
998
+ } else {
999
+ toolResults.push({ tool, result: `Error: miss path or regex params` });
1000
+ }
1001
+ } else if (tool.name === "agent_search_file_content" /* SEARCH_CONTENT */) {
1002
+ const path = tool.params.path || this.runtime.env.cwd();
1003
+ const regexStr = tool.params.regex;
1004
+ const filePattern = tool.params.file_pattern;
1005
+ const includeNodeModules = tool.params.include_node_modules === "true";
1006
+ if (regexStr && path) {
1007
+ try {
1008
+ let result;
1009
+ if (typeof this.runtime.fs.searchFileContent === "function") {
1010
+ result = await this.runtime.fs.searchFileContent(path, regexStr, filePattern);
1011
+ } else {
1012
+ result = await this.searchFileContent(path, regexStr, filePattern, includeNodeModules);
1013
+ }
1014
+ toolResults.push({ tool, result });
838
1015
  } catch (e) {
839
1016
  toolResults.push({ tool, result: `Error: ${e.message}` });
840
1017
  }
@@ -963,6 +1140,27 @@ ${this.promptManager.getUsedToolResultInstructions()}`;
963
1140
  }
964
1141
  }
965
1142
  }
1143
+ /**
1144
+ * 激活自定义工具集:将入口的 children 注册到活跃工具列表并返回渲染后的 prompt。
1145
+ */
1146
+ activateCustomToolGroup(group) {
1147
+ if (this.activatedGroups.has(group.name)) {
1148
+ return renderCustomToolsPrompt(group.children || [], this.runtime.env.cwd());
1149
+ }
1150
+ this.activatedGroups.add(group.name);
1151
+ this.activeCustomTools = [...this.activeCustomTools, ...group.children || []];
1152
+ this.activeToolsVersion++;
1153
+ return renderCustomToolsPrompt(group.children || [], this.runtime.env.cwd());
1154
+ }
1155
+ /**
1156
+ * 检查自上次获取 prompt 后是否有工具集被激活,若有则刷新缓存。
1157
+ */
1158
+ async getCurrentPrompt() {
1159
+ if (this.promptActiveToolsVersion !== this.activeToolsVersion) {
1160
+ return this.getPrompt(true);
1161
+ }
1162
+ return this.getPrompt();
1163
+ }
966
1164
  /**
967
1165
  * 简单的 Diff 解析与替换辅助方法
968
1166
  */
@@ -1009,13 +1207,80 @@ ${searchBlock.substring(0, 100)}...`);
1009
1207
  async getHistory() {
1010
1208
  return this.memory.getHistory();
1011
1209
  }
1012
- async searchFiles(dirpath, regex, filePattern) {
1210
+ /**
1211
+ * 判断文件是否为可进行内容搜索的文本类文件
1212
+ */
1213
+ isTextFile(name) {
1214
+ const ext = name.includes(".") ? String(name.split(".").pop()).toLowerCase() : "";
1215
+ if (!ext) {
1216
+ return true;
1217
+ }
1218
+ return TEXT_FILE_EXTENSIONS.has(ext);
1219
+ }
1220
+ /**
1221
+ * 按文件名/目录名搜索,层级优先(BFS):
1222
+ * 每一层先匹配完当前层级的所有条目,再进入更深的子目录。
1223
+ */
1224
+ async searchFiles(dirpath, regex, filePattern, includeNodeModules = false) {
1013
1225
  const matches = [];
1014
- const contentRe = new RegExp(regex);
1015
- const nameRe = filePattern ? new RegExp(filePattern.replace(/\./g, "\\.").replace(/\*/g, ".*")) : null;
1226
+ const MAX_RESULTS = 100;
1227
+ const MAX_DEPTH = 8;
1016
1228
  const SKIP_DIRS = /* @__PURE__ */ new Set([".git", "dist", ".agent", ".turbopack"]);
1229
+ const shouldSkipDir = (name) => SKIP_DIRS.has(name) || name === "node_modules" && !includeNodeModules;
1230
+ const nameRe = new RegExp(regex);
1231
+ const globRe = filePattern ? new RegExp(filePattern.replace(/\./g, "\\.").replace(/\*/g, ".*")) : null;
1232
+ const queue = [{ dir: dirpath, depth: 0 }];
1233
+ while (queue.length > 0 && matches.length < MAX_RESULTS) {
1234
+ const { dir, depth } = queue.shift();
1235
+ if (depth > MAX_DEPTH) {
1236
+ continue;
1237
+ }
1238
+ let entries;
1239
+ try {
1240
+ entries = await this.runtime.fs.readDir(dir, { recursive: false });
1241
+ } catch {
1242
+ continue;
1243
+ }
1244
+ for (const entry of entries) {
1245
+ if (matches.length >= MAX_RESULTS) {
1246
+ break;
1247
+ }
1248
+ const child = dir === "." ? entry.name : `${dir}/${entry.name}`;
1249
+ if (entry.isDirectory()) {
1250
+ if (!shouldSkipDir(entry.name)) {
1251
+ if (nameRe.test(entry.name)) {
1252
+ matches.push(`${child}/`);
1253
+ }
1254
+ queue.push({ dir: child, depth: depth + 1 });
1255
+ }
1256
+ } else if (entry.isFile()) {
1257
+ if (globRe && !globRe.test(entry.name)) {
1258
+ continue;
1259
+ }
1260
+ if (nameRe.test(entry.name)) {
1261
+ matches.push(child);
1262
+ }
1263
+ }
1264
+ }
1265
+ }
1266
+ if (matches.length === 0) {
1267
+ return "No files found.";
1268
+ }
1269
+ return matches.join("\n");
1270
+ }
1271
+ /**
1272
+ * 按文件内容搜索:正则匹配文件内容,返回匹配文件路径。
1273
+ */
1274
+ async searchFileContent(dirpath, regex, filePattern, includeNodeModules = false) {
1275
+ const matches = [];
1276
+ const MAX_RESULTS = 100;
1277
+ const MAX_DEPTH = 8;
1278
+ const SKIP_DIRS = /* @__PURE__ */ new Set([".git", "dist", ".agent", ".turbopack"]);
1279
+ const shouldSkipDir = (name) => SKIP_DIRS.has(name) || name === "node_modules" && !includeNodeModules;
1280
+ const contentRe = new RegExp(regex);
1281
+ const globRe = filePattern ? new RegExp(filePattern.replace(/\./g, "\\.").replace(/\*/g, ".*")) : null;
1017
1282
  const walk = async (dir, depth) => {
1018
- if (depth > 8 || matches.length >= 100) {
1283
+ if (depth > MAX_DEPTH || matches.length >= MAX_RESULTS) {
1019
1284
  return;
1020
1285
  }
1021
1286
  let entries;
@@ -1025,21 +1290,24 @@ ${searchBlock.substring(0, 100)}...`);
1025
1290
  return;
1026
1291
  }
1027
1292
  for (const entry of entries) {
1028
- if (matches.length >= 100) {
1293
+ if (matches.length >= MAX_RESULTS) {
1029
1294
  break;
1030
1295
  }
1031
1296
  const child = dir === "." ? entry.name : `${dir}/${entry.name}`;
1032
1297
  if (entry.isDirectory()) {
1033
- if (!SKIP_DIRS.has(entry.name)) {
1298
+ if (!shouldSkipDir(entry.name)) {
1034
1299
  await walk(child, depth + 1);
1035
1300
  }
1036
1301
  } else if (entry.isFile()) {
1037
- if (nameRe && !nameRe.test(entry.name)) {
1302
+ if (!this.isTextFile(entry.name)) {
1303
+ continue;
1304
+ }
1305
+ if (globRe && !globRe.test(entry.name)) {
1038
1306
  continue;
1039
1307
  }
1040
1308
  try {
1041
- const content = (await this.runtime.fs.readFile(child, "utf8")).toString();
1042
- if (contentRe.test(content)) {
1309
+ const content = await this.runtime.fs.readFile(child);
1310
+ if (typeof content === "string" && contentRe.test(content)) {
1043
1311
  matches.push(child);
1044
1312
  }
1045
1313
  } catch {
@@ -1048,6 +1316,9 @@ ${searchBlock.substring(0, 100)}...`);
1048
1316
  }
1049
1317
  };
1050
1318
  await walk(dirpath, 0);
1319
+ if (matches.length === 0) {
1320
+ return "No files found with matching content.";
1321
+ }
1051
1322
  return matches.join("\n");
1052
1323
  }
1053
1324
  };
@@ -1062,9 +1333,13 @@ exports.ClineAgent = ClineAgent;
1062
1333
  exports.READ_ONLY_TOOLS = READ_ONLY_TOOLS;
1063
1334
  exports.TOOL_RESULT_BLOCK = TOOL_RESULT_BLOCK;
1064
1335
  exports.VERSION = VERSION;
1336
+ exports.collectCustomToolNames = collectCustomToolNames;
1065
1337
  exports.defaultTools = defaultTools;
1066
1338
  exports.getToolUseNames = getToolUseNames;
1339
+ exports.isCustomToolGroup = isCustomToolGroup;
1067
1340
  exports.parseAssistantMessageV2 = parseAssistantMessageV2;
1341
+ exports.renderCustomToolPrompt = renderCustomToolPrompt;
1342
+ exports.renderCustomToolsPrompt = renderCustomToolsPrompt;
1068
1343
  exports.setDynamicToolUseNames = setDynamicToolUseNames;
1069
1344
  exports.toolParamNames = toolParamNames;
1070
1345
  exports.toolUseNames = toolUseNames;