@amical/aiagent-sdk 0.1.10 → 0.1.12
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/{WebMemoryManager-DXxLUDBB.d.ts → WebMemoryManager-BZNjinHF.d.ts} +10 -3
- package/dist/{WebMemoryManager-Bk19j8i6.d.mts → WebMemoryManager-DpaY-f0l.d.mts} +10 -3
- package/dist/index.d.mts +71 -4
- package/dist/index.d.ts +71 -4
- package/dist/index.js +223 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +220 -34
- package/dist/index.mjs.map +1 -1
- package/dist/runtime/nodeRuntime.d.mts +12 -3
- package/dist/runtime/nodeRuntime.d.ts +12 -3
- package/dist/runtime/nodeRuntime.js +357 -47
- package/dist/runtime/nodeRuntime.js.map +1 -1
- package/dist/runtime/nodeRuntime.mjs +354 -48
- package/dist/runtime/nodeRuntime.mjs.map +1 -1
- package/dist/runtime/webRuntime.d.mts +3 -2
- package/dist/runtime/webRuntime.d.ts +3 -2
- package/dist/runtime/webRuntime.js +357 -47
- package/dist/runtime/webRuntime.js.map +1 -1
- package/dist/runtime/webRuntime.mjs +354 -48
- package/dist/runtime/webRuntime.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -167,9 +167,11 @@ return (
|
|
|
167
167
|
},
|
|
168
168
|
{
|
|
169
169
|
name: "agent_read_file" /* FILE_READ */,
|
|
170
|
-
description: "Read the contents of a specific file.",
|
|
170
|
+
description: "Read the contents of a specific file. The returned content includes line numbers (e.g. '10|const x = 1;') so you can reference exact lines when editing.",
|
|
171
171
|
params: {
|
|
172
|
-
path: "(required) The absolute or relative file path to read."
|
|
172
|
+
path: "(required) The absolute or relative file path to read.",
|
|
173
|
+
start_line: "(optional) The 1-based line number to start reading from (inclusive). Defaults to 1.",
|
|
174
|
+
end_line: "(optional) The 1-based line number to stop reading at (inclusive). Defaults to the last line."
|
|
173
175
|
}
|
|
174
176
|
},
|
|
175
177
|
{
|
|
@@ -516,6 +518,44 @@ var toolParamNames = [
|
|
|
516
518
|
"start_line",
|
|
517
519
|
"end_line"
|
|
518
520
|
];
|
|
521
|
+
|
|
522
|
+
// src/shared/custom-tools.ts
|
|
523
|
+
function isCustomToolGroup(tool) {
|
|
524
|
+
return Array.isArray(tool.children) && tool.children.length > 0;
|
|
525
|
+
}
|
|
526
|
+
function collectCustomToolNames(tools) {
|
|
527
|
+
const names = [];
|
|
528
|
+
const visit = (list) => {
|
|
529
|
+
for (const tool of list) {
|
|
530
|
+
names.push(tool.name);
|
|
531
|
+
if (Array.isArray(tool.children) && tool.children.length > 0) {
|
|
532
|
+
visit(tool.children);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
visit(tools);
|
|
537
|
+
return names;
|
|
538
|
+
}
|
|
539
|
+
function renderCustomToolPrompt(tool, cwd = "") {
|
|
540
|
+
const params = tool.params ? Object.entries(tool.params) : [];
|
|
541
|
+
const replaceCwd = (text) => String(text).replaceAll("${context.cwd}", cwd);
|
|
542
|
+
const paramLines = params.map(([name, description]) => `- ${name}: ${replaceCwd(description)}`).join("\n");
|
|
543
|
+
const usageLines = params.map(([name, description]) => `<${name}>${replaceCwd(description)}</${name}>`).join("\n");
|
|
544
|
+
return `### ${tool.name}
|
|
545
|
+
Description: ${tool.description}
|
|
546
|
+
Parameters:
|
|
547
|
+
${paramLines}
|
|
548
|
+
- 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)
|
|
549
|
+
Usage:
|
|
550
|
+
<${tool.name}>
|
|
551
|
+
${usageLines}
|
|
552
|
+
<task_progress>Checklist here (optional)</task_progress>
|
|
553
|
+
</${tool.name}>
|
|
554
|
+
`;
|
|
555
|
+
}
|
|
556
|
+
function renderCustomToolsPrompt(tools, cwd = "") {
|
|
557
|
+
return tools.map((tool) => renderCustomToolPrompt(tool, cwd)).join("\n");
|
|
558
|
+
}
|
|
519
559
|
var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
520
560
|
"txt",
|
|
521
561
|
"md",
|
|
@@ -574,7 +614,17 @@ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
574
614
|
"dockerfile",
|
|
575
615
|
"makefile",
|
|
576
616
|
"properties",
|
|
577
|
-
"lock"
|
|
617
|
+
"lock",
|
|
618
|
+
"node-version",
|
|
619
|
+
"nvmrc",
|
|
620
|
+
"editorconfig",
|
|
621
|
+
"prettierrc",
|
|
622
|
+
"eslintrc",
|
|
623
|
+
"babelrc",
|
|
624
|
+
"stylelintrc",
|
|
625
|
+
"huskyrc",
|
|
626
|
+
"commitlintrc",
|
|
627
|
+
"lintstagedrc"
|
|
578
628
|
]);
|
|
579
629
|
var AgentState = /* @__PURE__ */ ((AgentState2) => {
|
|
580
630
|
AgentState2["IDLE"] = "idle";
|
|
@@ -599,9 +649,19 @@ var ClineAgent = class {
|
|
|
599
649
|
skillDir;
|
|
600
650
|
rulesDir;
|
|
601
651
|
customTools;
|
|
652
|
+
/** 当前可用(已展开集合)的顶层工具列表,传给 PromptManager 渲染 */
|
|
653
|
+
activeCustomTools;
|
|
654
|
+
/** 所有已知工具名(含未激活 children),便于查找定义 */
|
|
655
|
+
customToolsByName;
|
|
602
656
|
customToolNames;
|
|
603
657
|
allowDefaultTools;
|
|
604
658
|
customInstructions;
|
|
659
|
+
/** 已激活的工具集入口名 */
|
|
660
|
+
activatedGroups = /* @__PURE__ */ new Set();
|
|
661
|
+
/** activeCustomTools 变更版本号,用于 Prompt 缓存失效 */
|
|
662
|
+
activeToolsVersion = 0;
|
|
663
|
+
/** 缓存 sysPrompt 时记录的版本号 */
|
|
664
|
+
promptActiveToolsVersion = -1;
|
|
605
665
|
sysPrompt;
|
|
606
666
|
noUseTollSysPrompt;
|
|
607
667
|
// 简单的事件回调系统,供宿主环境注册以监听 Agent 内部事件
|
|
@@ -621,33 +681,47 @@ var ClineAgent = class {
|
|
|
621
681
|
this.allowDefaultTools.push("agent_attempt_completion" /* ATTEMPT */);
|
|
622
682
|
}
|
|
623
683
|
this.customInstructions = options.customInstructions;
|
|
684
|
+
this.customToolsByName = /* @__PURE__ */ new Map();
|
|
685
|
+
this.activeCustomTools = [...this.customTools];
|
|
686
|
+
this.customToolNames = new Set(collectCustomToolNames(this.customTools));
|
|
624
687
|
if (this.customTools.length > 0) {
|
|
625
|
-
const dynamicToolNames = /* @__PURE__ */ new Set();
|
|
626
688
|
const customToolParams = /* @__PURE__ */ new Set();
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
689
|
+
const visit = (tools) => {
|
|
690
|
+
for (const customTool of tools) {
|
|
691
|
+
this.customToolsByName.set(customTool.name, customTool);
|
|
692
|
+
if (customTool.params) {
|
|
693
|
+
for (const key in customTool.params) {
|
|
694
|
+
if (!Object.hasOwn(customTool.params, key)) continue;
|
|
695
|
+
customToolParams.add(key);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (Array.isArray(customTool.children) && customTool.children.length > 0) {
|
|
699
|
+
visit(customTool.children);
|
|
633
700
|
}
|
|
634
701
|
}
|
|
635
|
-
}
|
|
702
|
+
};
|
|
703
|
+
visit(this.customTools);
|
|
636
704
|
const defaultToolNames = Object.values(AgentDefaultTool);
|
|
637
|
-
const overlappingTools = Array.from(
|
|
705
|
+
const overlappingTools = Array.from(this.customToolNames).filter((name) => defaultToolNames.includes(name));
|
|
638
706
|
if (overlappingTools.length > 0) {
|
|
639
707
|
throw new Error(`Custom tools and default tools have overlapping names: ${overlappingTools.join(", ")}`);
|
|
640
708
|
}
|
|
641
|
-
if (
|
|
642
|
-
setDynamicToolUseNames("customUser", Array.from(
|
|
709
|
+
if (this.customToolNames.size > 0) {
|
|
710
|
+
setDynamicToolUseNames("customUser", Array.from(this.customToolNames));
|
|
643
711
|
}
|
|
644
712
|
if (customToolParams.size > 0) {
|
|
645
713
|
this.allToolParamNames = Array.from(/* @__PURE__ */ new Set([...this.allToolParamNames, ...Array.from(customToolParams)]));
|
|
646
714
|
}
|
|
647
715
|
}
|
|
648
|
-
this.customToolNames = new Set(this.customTools.map((t) => t.name));
|
|
649
716
|
this.skillDir = this.runtime.path.join(this.runtime.env.cwd(), ".agents/skills");
|
|
650
717
|
this.rulesDir = this.runtime.path.join(this.runtime.env.cwd(), ".agents/rules");
|
|
718
|
+
const originalOnCondense = this.memory.onCondense;
|
|
719
|
+
this.memory.onCondense = async () => {
|
|
720
|
+
this.api.clearConversationId();
|
|
721
|
+
if (originalOnCondense) {
|
|
722
|
+
await originalOnCondense();
|
|
723
|
+
}
|
|
724
|
+
};
|
|
651
725
|
}
|
|
652
726
|
setState(newState) {
|
|
653
727
|
this.state = newState;
|
|
@@ -669,10 +743,11 @@ var ClineAgent = class {
|
|
|
669
743
|
shell: this.runtime.env.shell(),
|
|
670
744
|
skills: await this.getSkills(),
|
|
671
745
|
agentRules: await this.getAgentRules(),
|
|
672
|
-
customTools: this.
|
|
746
|
+
customTools: this.activeCustomTools,
|
|
673
747
|
allowDefaultTools: this.allowDefaultTools,
|
|
674
748
|
customInstructions: this.customInstructions
|
|
675
749
|
};
|
|
750
|
+
this.promptActiveToolsVersion = this.activeToolsVersion;
|
|
676
751
|
this.sysPrompt = await this.promptManager.getSystemPrompt(context);
|
|
677
752
|
this.noUseTollSysPrompt = this.promptManager.getNoUseToolInstructions(context);
|
|
678
753
|
return { sysPrompt: this.sysPrompt, noUseTollSysPrompt: this.noUseTollSysPrompt };
|
|
@@ -704,16 +779,6 @@ var ClineAgent = class {
|
|
|
704
779
|
async getAgentRules() {
|
|
705
780
|
const cwd = this.runtime.env.cwd();
|
|
706
781
|
const rules = [];
|
|
707
|
-
const agentsMdPath = this.runtime.path.join(cwd, "Agents.md");
|
|
708
|
-
try {
|
|
709
|
-
if (await this.runtime.fs.fileExists(agentsMdPath)) {
|
|
710
|
-
const content = await this.runtime.fs.readFile(agentsMdPath);
|
|
711
|
-
if (content && content.trim()) {
|
|
712
|
-
rules.push(content.trim());
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
} catch (e) {
|
|
716
|
-
}
|
|
717
782
|
try {
|
|
718
783
|
if (await this.runtime.fs.fileExists(this.rulesDir)) {
|
|
719
784
|
const files = await this.runtime.fs.readDir(this.rulesDir);
|
|
@@ -739,6 +804,28 @@ ${content.trim()}\`\`\``);
|
|
|
739
804
|
}
|
|
740
805
|
} catch (e) {
|
|
741
806
|
}
|
|
807
|
+
try {
|
|
808
|
+
const getAgentsMdPath = async () => {
|
|
809
|
+
const agentsMdPathList = [
|
|
810
|
+
this.runtime.path.join(cwd, "AGENTS.md"),
|
|
811
|
+
this.runtime.path.join(cwd, "Agents.md"),
|
|
812
|
+
this.runtime.path.join(cwd, "agents.md")
|
|
813
|
+
];
|
|
814
|
+
for (const item of agentsMdPathList) {
|
|
815
|
+
if (await this.runtime.fs.fileExists(item)) {
|
|
816
|
+
return item;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
const agentsMdPath = await getAgentsMdPath();
|
|
821
|
+
if (agentsMdPath) {
|
|
822
|
+
const content = await this.runtime.fs.readFile(agentsMdPath);
|
|
823
|
+
if (content && content.trim()) {
|
|
824
|
+
rules.push(content.trim());
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
} catch (e) {
|
|
828
|
+
}
|
|
742
829
|
return rules.join("\n---\n");
|
|
743
830
|
}
|
|
744
831
|
async getSkills() {
|
|
@@ -781,9 +868,9 @@ ${content.trim()}\`\`\``);
|
|
|
781
868
|
* 核心任务循环
|
|
782
869
|
*/
|
|
783
870
|
async loop() {
|
|
784
|
-
const {
|
|
785
|
-
const { customTools, customToolNames } = this;
|
|
871
|
+
const { customToolsByName, customToolNames } = this;
|
|
786
872
|
while (this.state === "thinking" /* THINKING */) {
|
|
873
|
+
const { sysPrompt, noUseTollSysPrompt } = await this.getCurrentPrompt();
|
|
787
874
|
this.loopCount++;
|
|
788
875
|
if (this.loopCount > this.maxLoopCount) {
|
|
789
876
|
await this.memory.appendMessage({
|
|
@@ -828,7 +915,33 @@ ${content.trim()}\`\`\``);
|
|
|
828
915
|
}
|
|
829
916
|
}
|
|
830
917
|
if (customToolNames.has(tool.name)) {
|
|
831
|
-
const customTool =
|
|
918
|
+
const customTool = customToolsByName.get(tool.name);
|
|
919
|
+
if (!customTool) {
|
|
920
|
+
toolResults.push({ tool, result: `Error: Custom tool "${tool.name}" not found.` });
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
if (isCustomToolGroup(customTool)) {
|
|
924
|
+
let groupResult;
|
|
925
|
+
if (customTool.onCall) {
|
|
926
|
+
try {
|
|
927
|
+
groupResult = await customTool.onCall(tool.params);
|
|
928
|
+
} catch (error) {
|
|
929
|
+
toolResults.push({ tool, result: `Error: ${error.message}` });
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
const childrenPrompt = this.activateCustomToolGroup(customTool);
|
|
934
|
+
const finalPrompt = `# New Available Tools
|
|
935
|
+
|
|
936
|
+
The following are instructions for using the new available tools.
|
|
937
|
+
|
|
938
|
+
${childrenPrompt}`;
|
|
939
|
+
const promptBlock = groupResult !== void 0 ? `${typeof groupResult === "string" ? groupResult : JSON.stringify(groupResult)}
|
|
940
|
+
|
|
941
|
+
${finalPrompt}` : finalPrompt;
|
|
942
|
+
toolResults.push({ tool, result: promptBlock });
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
832
945
|
if (customTool.onCall) {
|
|
833
946
|
try {
|
|
834
947
|
const res = await customTool.onCall(tool.params);
|
|
@@ -850,7 +963,8 @@ ${content.trim()}\`\`\``);
|
|
|
850
963
|
if (path3) {
|
|
851
964
|
try {
|
|
852
965
|
const content = await this.runtime.fs.readFile(path3);
|
|
853
|
-
|
|
966
|
+
const result = this.formatFileContentWithLineNumbers(content, tool.params.start_line, tool.params.end_line);
|
|
967
|
+
toolResults.push({ tool, result });
|
|
854
968
|
} catch (error) {
|
|
855
969
|
toolResults.push({ tool, result: `Error: ${error.message}` });
|
|
856
970
|
}
|
|
@@ -863,6 +977,8 @@ ${content.trim()}\`\`\``);
|
|
|
863
977
|
const result = await this.runtime.terminal.execCommand(cmd);
|
|
864
978
|
toolResults.push({ tool, result: `STDOUT: ${result.stdout}
|
|
865
979
|
STDERR: ${result.stderr}` });
|
|
980
|
+
} else {
|
|
981
|
+
toolResults.push({ tool, result: "Error: No command provided." });
|
|
866
982
|
}
|
|
867
983
|
} else if (tool.name === "agent_write_to_file" /* FILE_NEW */) {
|
|
868
984
|
const path3 = tool.params.path;
|
|
@@ -874,12 +990,18 @@ STDERR: ${result.stderr}` });
|
|
|
874
990
|
} catch (error) {
|
|
875
991
|
toolResults.push({ tool, result: `Error: ${error.message}` });
|
|
876
992
|
}
|
|
993
|
+
} else {
|
|
994
|
+
toolResults.push({ tool, result: "Error: Missing path or content." });
|
|
877
995
|
}
|
|
878
996
|
} else if (tool.name === "agent_list_files" /* LIST_FILES */) {
|
|
879
997
|
const path3 = tool.params.path || this.runtime.env.cwd();
|
|
998
|
+
const recursive = tool.params.recursive === "true";
|
|
880
999
|
try {
|
|
881
|
-
const files = await this.runtime.fs.readDir(path3);
|
|
882
|
-
toolResults.push({
|
|
1000
|
+
const files = await this.runtime.fs.readDir(path3, { recursive });
|
|
1001
|
+
toolResults.push({
|
|
1002
|
+
tool,
|
|
1003
|
+
result: files.map((f) => `${f.isDirectory() ? "[DIR] " : " "}${f.name}${f.isDirectory() ? "/" : ""}`).join("\n")
|
|
1004
|
+
});
|
|
883
1005
|
} catch (e) {
|
|
884
1006
|
toolResults.push({ tool, result: `Error: ${e.message}` });
|
|
885
1007
|
}
|
|
@@ -1019,10 +1141,15 @@ STDERR: ${result.stderr}` });
|
|
|
1019
1141
|
try {
|
|
1020
1142
|
const res = await fetch(url);
|
|
1021
1143
|
const text = await res.text();
|
|
1022
|
-
|
|
1144
|
+
const MAX_LEN = 8e3;
|
|
1145
|
+
const truncated = text.length > MAX_LEN ? `${text.substring(0, MAX_LEN)}
|
|
1146
|
+
...[truncated ${text.length - MAX_LEN} chars]` : text;
|
|
1147
|
+
toolResults.push({ tool, result: truncated });
|
|
1023
1148
|
} catch (e) {
|
|
1024
1149
|
toolResults.push({ tool, result: `Error: ${e.message}` });
|
|
1025
1150
|
}
|
|
1151
|
+
} else {
|
|
1152
|
+
toolResults.push({ tool, result: "Error: No url provided." });
|
|
1026
1153
|
}
|
|
1027
1154
|
} else if (tool.name === "agent_focus_chain" /* TODO */) {
|
|
1028
1155
|
toolResults.push({ tool, result: `Focus chain updated successfully.` });
|
|
@@ -1089,6 +1216,65 @@ ${this.promptManager.getUsedToolResultInstructions()}`;
|
|
|
1089
1216
|
}
|
|
1090
1217
|
}
|
|
1091
1218
|
}
|
|
1219
|
+
/**
|
|
1220
|
+
* 激活自定义工具集:将入口的 children 注册到活跃工具列表并返回渲染后的 prompt。
|
|
1221
|
+
*/
|
|
1222
|
+
activateCustomToolGroup(group) {
|
|
1223
|
+
if (this.activatedGroups.has(group.name)) {
|
|
1224
|
+
return renderCustomToolsPrompt(group.children || [], this.runtime.env.cwd());
|
|
1225
|
+
}
|
|
1226
|
+
this.activatedGroups.add(group.name);
|
|
1227
|
+
this.activeCustomTools = [...this.activeCustomTools, ...group.children || []];
|
|
1228
|
+
this.activeToolsVersion++;
|
|
1229
|
+
return renderCustomToolsPrompt(group.children || [], this.runtime.env.cwd());
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* 检查自上次获取 prompt 后是否有工具集被激活,若有则刷新缓存。
|
|
1233
|
+
*/
|
|
1234
|
+
async getCurrentPrompt() {
|
|
1235
|
+
if (this.promptActiveToolsVersion !== this.activeToolsVersion) {
|
|
1236
|
+
return this.getPrompt(true);
|
|
1237
|
+
}
|
|
1238
|
+
return this.getPrompt();
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* 将文件内容格式化为带行号文本(模拟编辑器行号显示),
|
|
1242
|
+
* 支持通过 start_line / end_line 截取读取范围。
|
|
1243
|
+
*/
|
|
1244
|
+
formatFileContentWithLineNumbers(content, startLineParam, endLineParam) {
|
|
1245
|
+
const lines = content.split(/\r?\n/);
|
|
1246
|
+
const normalized = lines.length > 0 && lines[lines.length - 1] === "" ? lines.slice(0, -1) : lines;
|
|
1247
|
+
const total = normalized.length;
|
|
1248
|
+
if (total === 0) {
|
|
1249
|
+
return "1|(empty file)";
|
|
1250
|
+
}
|
|
1251
|
+
const parseLine = (val, fallback) => {
|
|
1252
|
+
if (val === void 0 || val === null || val.trim() === "") {
|
|
1253
|
+
return fallback;
|
|
1254
|
+
}
|
|
1255
|
+
const num = Number.parseInt(val, 10);
|
|
1256
|
+
if (!Number.isFinite(num) || num <= 0) {
|
|
1257
|
+
return fallback;
|
|
1258
|
+
}
|
|
1259
|
+
return num;
|
|
1260
|
+
};
|
|
1261
|
+
const start = parseLine(startLineParam, 1);
|
|
1262
|
+
const end = parseLine(endLineParam, total);
|
|
1263
|
+
if (start > end) {
|
|
1264
|
+
return `Error: start_line (${start}) cannot be greater than end_line (${end}).`;
|
|
1265
|
+
}
|
|
1266
|
+
if (start > total) {
|
|
1267
|
+
return `Error: start_line (${start}) is out of range. The file has ${total} lines.`;
|
|
1268
|
+
}
|
|
1269
|
+
const from = Math.max(1, start);
|
|
1270
|
+
const to = Math.min(total, end);
|
|
1271
|
+
const width = String(total).length;
|
|
1272
|
+
const selected = normalized.slice(from - 1, to);
|
|
1273
|
+
const numbered = selected.map((line, idx) => `${String(from + idx).padStart(width)}|${line}`).join("\n");
|
|
1274
|
+
const header = from === 1 && to === total ? `1-${total}/${total}` : `${from}-${to}/${total}`;
|
|
1275
|
+
return `${header}
|
|
1276
|
+
${numbered}`;
|
|
1277
|
+
}
|
|
1092
1278
|
/**
|
|
1093
1279
|
* 简单的 Diff 解析与替换辅助方法
|
|
1094
1280
|
*/
|
|
@@ -1338,6 +1524,9 @@ var NodeApiHandler = class {
|
|
|
1338
1524
|
};
|
|
1339
1525
|
}
|
|
1340
1526
|
}
|
|
1527
|
+
clearConversationId() {
|
|
1528
|
+
this.conversationId = void 0;
|
|
1529
|
+
}
|
|
1341
1530
|
};
|
|
1342
1531
|
|
|
1343
1532
|
// src/prompt/NodePromptManager.ts
|
|
@@ -1450,10 +1639,11 @@ ${context.customInstructions}
|
|
|
1450
1639
|
if (allowDefaultTools) {
|
|
1451
1640
|
useDefaultTools = defaultTools.filter((tool) => allowDefaultTools.includes(tool.name));
|
|
1452
1641
|
}
|
|
1642
|
+
const renderedCustomTools = renderCustomToolsPrompt(customTools, context.cwd);
|
|
1453
1643
|
return `# Available Tools
|
|
1454
1644
|
You can use the following tools by formatting your output as XML blocks. Only use ONE tool per message, and ALWAYS wait for the user's response with the execution result before proceeding.
|
|
1455
1645
|
|
|
1456
|
-
${[...useDefaultTools
|
|
1646
|
+
${[...useDefaultTools].map((tool) => {
|
|
1457
1647
|
return `### ${tool.name}
|
|
1458
1648
|
Description: ${tool.description}
|
|
1459
1649
|
Parameters:
|
|
@@ -1466,6 +1656,8 @@ ${Object.entries(tool.params).map(([name, description]) => `<${name}>${descripti
|
|
|
1466
1656
|
</${tool.name}>
|
|
1467
1657
|
`;
|
|
1468
1658
|
}).join("\n")}
|
|
1659
|
+
${renderedCustomTools ? `
|
|
1660
|
+
${renderedCustomTools}` : ""}
|
|
1469
1661
|
|
|
1470
1662
|
# Tool Use Examples
|
|
1471
1663
|
${useDefaultTools.filter((item) => Boolean(item.example)).map((item) => item.example).join("\n")}}
|
|
@@ -1639,14 +1831,125 @@ ${this?.getEnvironmentInfoPrompt?.(context)}`;
|
|
|
1639
1831
|
4. Ensure the list accurately reflects the current state`;
|
|
1640
1832
|
}
|
|
1641
1833
|
};
|
|
1834
|
+
|
|
1835
|
+
// src/memory/condense.ts
|
|
1836
|
+
var TOOL_RESULT_CONTENT_REGEX = /<tool_call_result[^>]*>[\s\S]*?<\/tool_call_result>/;
|
|
1837
|
+
var EXPIRED_MARKER_TEXT = "[The result has expired and has been removed from the context]";
|
|
1838
|
+
var ATTEMPT_TOOL_NAME = "agent_attempt_completion" /* ATTEMPT */;
|
|
1839
|
+
function getContentString(content) {
|
|
1840
|
+
return typeof content === "string" ? content : "";
|
|
1841
|
+
}
|
|
1842
|
+
function isToolResultMessage(content) {
|
|
1843
|
+
if (typeof content !== "string") {
|
|
1844
|
+
return false;
|
|
1845
|
+
}
|
|
1846
|
+
return TOOL_RESULT_CONTENT_REGEX.test(content);
|
|
1847
|
+
}
|
|
1848
|
+
function isAttemptCallMessage(message) {
|
|
1849
|
+
if (message.role !== "assistant") {
|
|
1850
|
+
return false;
|
|
1851
|
+
}
|
|
1852
|
+
const content = getContentString(message.content);
|
|
1853
|
+
return new RegExp(`<${ATTEMPT_TOOL_NAME}[\\s>]`).test(content) || new RegExp(`<name>\\s*${ATTEMPT_TOOL_NAME}\\s*</name>`).test(content);
|
|
1854
|
+
}
|
|
1855
|
+
function markExpiredToolResult(content) {
|
|
1856
|
+
if (typeof content !== "string") {
|
|
1857
|
+
return content;
|
|
1858
|
+
}
|
|
1859
|
+
return content.replace(
|
|
1860
|
+
/(<tool_call_result[^>]*tool_name="([^"]*)"[^>]*>)[\s\S]*?(<\/tool_call_result>)/g,
|
|
1861
|
+
(_match, openTag, _toolName, closeTag) => {
|
|
1862
|
+
return `${openTag}
|
|
1863
|
+
${EXPIRED_MARKER_TEXT}
|
|
1864
|
+
${closeTag}`;
|
|
1865
|
+
}
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
function splitConversations(messages) {
|
|
1869
|
+
const conversations = [];
|
|
1870
|
+
let current = [];
|
|
1871
|
+
for (const message of messages) {
|
|
1872
|
+
current.push(message);
|
|
1873
|
+
if (isAttemptCallMessage(message)) {
|
|
1874
|
+
conversations.push(current);
|
|
1875
|
+
current = [];
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
if (current.length > 0) {
|
|
1879
|
+
conversations.push(current);
|
|
1880
|
+
}
|
|
1881
|
+
return conversations;
|
|
1882
|
+
}
|
|
1883
|
+
function isCompleteConversation(conversation) {
|
|
1884
|
+
if (conversation.length === 0) {
|
|
1885
|
+
return false;
|
|
1886
|
+
}
|
|
1887
|
+
return isAttemptCallMessage(conversation[conversation.length - 1]);
|
|
1888
|
+
}
|
|
1889
|
+
function compressCompleteConversation(conversation) {
|
|
1890
|
+
if (conversation.length <= 2) {
|
|
1891
|
+
return conversation;
|
|
1892
|
+
}
|
|
1893
|
+
const head = conversation[0];
|
|
1894
|
+
const tail = conversation[conversation.length - 1];
|
|
1895
|
+
const mergedMarker = {
|
|
1896
|
+
role: "user",
|
|
1897
|
+
content: "[History Condensed] The intermediate tool calls and tool call results of this conversation have been merged. Please refer to the `agent_attempt_completion` summary below for the final conclusion."
|
|
1898
|
+
};
|
|
1899
|
+
return [head, mergedMarker, tail];
|
|
1900
|
+
}
|
|
1901
|
+
function markExpiredFirstHalf(messages) {
|
|
1902
|
+
const half = Math.floor(messages.length / 2);
|
|
1903
|
+
return messages.map((message, index) => {
|
|
1904
|
+
if (index < half && isToolResultMessage(message.content)) {
|
|
1905
|
+
return { ...message, content: markExpiredToolResult(message.content) };
|
|
1906
|
+
}
|
|
1907
|
+
return message;
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
function condenseMessages(messages, maxMessages) {
|
|
1911
|
+
if (messages.length <= maxMessages) {
|
|
1912
|
+
return messages;
|
|
1913
|
+
}
|
|
1914
|
+
const systemMessages = messages.filter((message) => message.role === "system");
|
|
1915
|
+
const restMessages = messages.filter((message) => message.role !== "system");
|
|
1916
|
+
const conversations = splitConversations(restMessages);
|
|
1917
|
+
const completeIndices = [];
|
|
1918
|
+
for (let i = 0; i < conversations.length; i++) {
|
|
1919
|
+
if (isCompleteConversation(conversations[i])) {
|
|
1920
|
+
completeIndices.push(i);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
const currentLength = () => systemMessages.length + conversations.reduce((acc, c) => acc + c.length, 0);
|
|
1924
|
+
if (completeIndices.length >= 2) {
|
|
1925
|
+
const lastCompleteIndex = completeIndices[completeIndices.length - 1];
|
|
1926
|
+
for (const idx of completeIndices) {
|
|
1927
|
+
if (idx === lastCompleteIndex) {
|
|
1928
|
+
break;
|
|
1929
|
+
}
|
|
1930
|
+
conversations[idx] = compressCompleteConversation(conversations[idx]);
|
|
1931
|
+
if (currentLength() <= maxMessages) {
|
|
1932
|
+
break;
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
let condensed = conversations.flat();
|
|
1937
|
+
condensed = markExpiredFirstHalf(condensed);
|
|
1938
|
+
if (systemMessages.length + condensed.length > maxMessages) {
|
|
1939
|
+
const nonSystemBudget = Math.max(0, maxMessages - systemMessages.length);
|
|
1940
|
+
condensed = condensed.slice(-nonSystemBudget);
|
|
1941
|
+
}
|
|
1942
|
+
return [...systemMessages, ...condensed];
|
|
1943
|
+
}
|
|
1642
1944
|
var NodeMemoryManager = class {
|
|
1643
1945
|
filePath;
|
|
1644
1946
|
maxMessages;
|
|
1947
|
+
onCondense;
|
|
1645
1948
|
/**
|
|
1646
1949
|
* @param dbPath 存储历史记录的本地文件路径(例如:'./.cline_memory.json')
|
|
1647
|
-
* @param maxMessages 保留的最大消息数量,用于简单的历史截断(默认
|
|
1950
|
+
* @param maxMessages 保留的最大消息数量,用于简单的历史截断(默认 200)
|
|
1648
1951
|
*/
|
|
1649
|
-
constructor(dbPath, maxMessages =
|
|
1952
|
+
constructor(dbPath, maxMessages = 200) {
|
|
1650
1953
|
this.filePath = path__namespace.resolve(dbPath);
|
|
1651
1954
|
this.maxMessages = maxMessages;
|
|
1652
1955
|
}
|
|
@@ -1681,19 +1984,22 @@ var NodeMemoryManager = class {
|
|
|
1681
1984
|
await this.writeMessages(messages);
|
|
1682
1985
|
await this.condenseHistory();
|
|
1683
1986
|
}
|
|
1987
|
+
/**
|
|
1988
|
+
* 压缩历史:
|
|
1989
|
+
* - system 消息保持不变
|
|
1990
|
+
* - 以 agent_attempt_completion 为边界切分完整对话,仅合并“后面还有其他完整对话”的旧完整对话
|
|
1991
|
+
* - 其余需要压缩的场景下,对前半段工具调用结果标记为“结果已过期”
|
|
1992
|
+
* - 同时触发 onCondense 回调(宿主可据此清空 dify conversationId 新开窗口)
|
|
1993
|
+
*/
|
|
1684
1994
|
async condenseHistory() {
|
|
1685
1995
|
const messages = await this.readMessages();
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
} else {
|
|
1694
|
-
condensed = messages.slice(sliceCount);
|
|
1695
|
-
}
|
|
1696
|
-
await this.writeMessages(condensed);
|
|
1996
|
+
const condensed = condenseMessages(messages, this.maxMessages);
|
|
1997
|
+
if (condensed === messages) {
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
await this.writeMessages(condensed);
|
|
2001
|
+
if (this.onCondense) {
|
|
2002
|
+
await this.onCondense();
|
|
1697
2003
|
}
|
|
1698
2004
|
}
|
|
1699
2005
|
async clear() {
|
|
@@ -1913,10 +2219,14 @@ exports.NodePromptManager = NodePromptManager;
|
|
|
1913
2219
|
exports.READ_ONLY_TOOLS = READ_ONLY_TOOLS;
|
|
1914
2220
|
exports.TOOL_RESULT_BLOCK = TOOL_RESULT_BLOCK;
|
|
1915
2221
|
exports.VERSION = VERSION;
|
|
2222
|
+
exports.collectCustomToolNames = collectCustomToolNames;
|
|
1916
2223
|
exports.createNodeAgent = createNodeAgent;
|
|
1917
2224
|
exports.defaultTools = defaultTools;
|
|
1918
2225
|
exports.getToolUseNames = getToolUseNames;
|
|
2226
|
+
exports.isCustomToolGroup = isCustomToolGroup;
|
|
1919
2227
|
exports.parseAssistantMessageV2 = parseAssistantMessageV2;
|
|
2228
|
+
exports.renderCustomToolPrompt = renderCustomToolPrompt;
|
|
2229
|
+
exports.renderCustomToolsPrompt = renderCustomToolsPrompt;
|
|
1920
2230
|
exports.setDynamicToolUseNames = setDynamicToolUseNames;
|
|
1921
2231
|
exports.toolParamNames = toolParamNames;
|
|
1922
2232
|
exports.toolUseNames = toolUseNames;
|