@amical/aiagent-sdk 0.1.11 → 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.
@@ -1,6 +1,6 @@
1
1
  import { IApiHandler, ApiProviderInfo, AgentMessage, ApiStreamChunk, IMemoryManager, IPromptManager, IMcpManager, McpServerInfo, McpTool, McpToolCallResult, IAgentRuntime, ClineAgent } from '../index.js';
2
2
  export { AgentDefaultTool, AgentOptions, AgentState, AssistantMessageContent, CustomToolDefinition, READ_ONLY_TOOLS, ReasoningStreamContent, TOOL_RESULT_BLOCK, TextStreamContent, ToolParamName, ToolUse, VERSION, collectCustomToolNames, defaultTools, getToolUseNames, isCustomToolGroup, parseAssistantMessageV2, renderCustomToolPrompt, renderCustomToolsPrompt, setDynamicToolUseNames, toolParamNames, toolUseNames } from '../index.js';
3
- import { W as WebMemoryManager } from '../WebMemoryManager-DXxLUDBB.js';
3
+ import { W as WebMemoryManager } from '../WebMemoryManager-BZNjinHF.js';
4
4
 
5
5
  interface NodeApiHandlerConfig {
6
6
  providerId?: string;
@@ -20,6 +20,7 @@ declare class NodeApiHandler implements IApiHandler {
20
20
  constructor(config: NodeApiHandlerConfig);
21
21
  getModelInfo(): ApiProviderInfo;
22
22
  createMessage(systemPrompt: string, messages: AgentMessage[]): AsyncGenerator<ApiStreamChunk, void, unknown>;
23
+ clearConversationId(): void;
23
24
  }
24
25
 
25
26
  /**
@@ -29,9 +30,10 @@ declare class NodeApiHandler implements IApiHandler {
29
30
  declare class NodeMemoryManager implements IMemoryManager {
30
31
  private filePath;
31
32
  private maxMessages;
33
+ onCondense?: () => void | Promise<void>;
32
34
  /**
33
35
  * @param dbPath 存储历史记录的本地文件路径(例如:'./.cline_memory.json')
34
- * @param maxMessages 保留的最大消息数量,用于简单的历史截断(默认 150)
36
+ * @param maxMessages 保留的最大消息数量,用于简单的历史截断(默认 200)
35
37
  */
36
38
  constructor(dbPath: string, maxMessages?: number);
37
39
  private ensureFile;
@@ -39,6 +41,13 @@ declare class NodeMemoryManager implements IMemoryManager {
39
41
  private writeMessages;
40
42
  getHistory(): Promise<AgentMessage[]>;
41
43
  appendMessage(message: AgentMessage): Promise<void>;
44
+ /**
45
+ * 压缩历史:
46
+ * - system 消息保持不变
47
+ * - 以 agent_attempt_completion 为边界切分完整对话,仅合并“后面还有其他完整对话”的旧完整对话
48
+ * - 其余需要压缩的场景下,对前半段工具调用结果标记为“结果已过期”
49
+ * - 同时触发 onCondense 回调(宿主可据此清空 dify conversationId 新开窗口)
50
+ */
42
51
  condenseHistory(): Promise<void>;
43
52
  clear(): Promise<void>;
44
53
  }
@@ -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
  {
@@ -612,7 +614,17 @@ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
612
614
  "dockerfile",
613
615
  "makefile",
614
616
  "properties",
615
- "lock"
617
+ "lock",
618
+ "node-version",
619
+ "nvmrc",
620
+ "editorconfig",
621
+ "prettierrc",
622
+ "eslintrc",
623
+ "babelrc",
624
+ "stylelintrc",
625
+ "huskyrc",
626
+ "commitlintrc",
627
+ "lintstagedrc"
616
628
  ]);
617
629
  var AgentState = /* @__PURE__ */ ((AgentState2) => {
618
630
  AgentState2["IDLE"] = "idle";
@@ -703,6 +715,13 @@ var ClineAgent = class {
703
715
  }
704
716
  this.skillDir = this.runtime.path.join(this.runtime.env.cwd(), ".agents/skills");
705
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
+ };
706
725
  }
707
726
  setState(newState) {
708
727
  this.state = newState;
@@ -760,16 +779,6 @@ var ClineAgent = class {
760
779
  async getAgentRules() {
761
780
  const cwd = this.runtime.env.cwd();
762
781
  const rules = [];
763
- const agentsMdPath = this.runtime.path.join(cwd, "Agents.md");
764
- try {
765
- if (await this.runtime.fs.fileExists(agentsMdPath)) {
766
- const content = await this.runtime.fs.readFile(agentsMdPath);
767
- if (content && content.trim()) {
768
- rules.push(content.trim());
769
- }
770
- }
771
- } catch (e) {
772
- }
773
782
  try {
774
783
  if (await this.runtime.fs.fileExists(this.rulesDir)) {
775
784
  const files = await this.runtime.fs.readDir(this.rulesDir);
@@ -795,6 +804,28 @@ ${content.trim()}\`\`\``);
795
804
  }
796
805
  } catch (e) {
797
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
+ }
798
829
  return rules.join("\n---\n");
799
830
  }
800
831
  async getSkills() {
@@ -932,7 +963,8 @@ ${finalPrompt}` : finalPrompt;
932
963
  if (path3) {
933
964
  try {
934
965
  const content = await this.runtime.fs.readFile(path3);
935
- toolResults.push({ tool, result: content });
966
+ const result = this.formatFileContentWithLineNumbers(content, tool.params.start_line, tool.params.end_line);
967
+ toolResults.push({ tool, result });
936
968
  } catch (error) {
937
969
  toolResults.push({ tool, result: `Error: ${error.message}` });
938
970
  }
@@ -945,6 +977,8 @@ ${finalPrompt}` : finalPrompt;
945
977
  const result = await this.runtime.terminal.execCommand(cmd);
946
978
  toolResults.push({ tool, result: `STDOUT: ${result.stdout}
947
979
  STDERR: ${result.stderr}` });
980
+ } else {
981
+ toolResults.push({ tool, result: "Error: No command provided." });
948
982
  }
949
983
  } else if (tool.name === "agent_write_to_file" /* FILE_NEW */) {
950
984
  const path3 = tool.params.path;
@@ -956,12 +990,18 @@ STDERR: ${result.stderr}` });
956
990
  } catch (error) {
957
991
  toolResults.push({ tool, result: `Error: ${error.message}` });
958
992
  }
993
+ } else {
994
+ toolResults.push({ tool, result: "Error: Missing path or content." });
959
995
  }
960
996
  } else if (tool.name === "agent_list_files" /* LIST_FILES */) {
961
997
  const path3 = tool.params.path || this.runtime.env.cwd();
998
+ const recursive = tool.params.recursive === "true";
962
999
  try {
963
- const files = await this.runtime.fs.readDir(path3);
964
- toolResults.push({ tool, result: files.map((f) => f.name + (f.isDirectory() ? "/" : "")).join("\n") });
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
+ });
965
1005
  } catch (e) {
966
1006
  toolResults.push({ tool, result: `Error: ${e.message}` });
967
1007
  }
@@ -1101,10 +1141,15 @@ STDERR: ${result.stderr}` });
1101
1141
  try {
1102
1142
  const res = await fetch(url);
1103
1143
  const text = await res.text();
1104
- toolResults.push({ tool, result: text.substring(0, 3e3) });
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 });
1105
1148
  } catch (e) {
1106
1149
  toolResults.push({ tool, result: `Error: ${e.message}` });
1107
1150
  }
1151
+ } else {
1152
+ toolResults.push({ tool, result: "Error: No url provided." });
1108
1153
  }
1109
1154
  } else if (tool.name === "agent_focus_chain" /* TODO */) {
1110
1155
  toolResults.push({ tool, result: `Focus chain updated successfully.` });
@@ -1192,6 +1237,44 @@ ${this.promptManager.getUsedToolResultInstructions()}`;
1192
1237
  }
1193
1238
  return this.getPrompt();
1194
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
+ }
1195
1278
  /**
1196
1279
  * 简单的 Diff 解析与替换辅助方法
1197
1280
  */
@@ -1441,6 +1524,9 @@ var NodeApiHandler = class {
1441
1524
  };
1442
1525
  }
1443
1526
  }
1527
+ clearConversationId() {
1528
+ this.conversationId = void 0;
1529
+ }
1444
1530
  };
1445
1531
 
1446
1532
  // src/prompt/NodePromptManager.ts
@@ -1745,14 +1831,125 @@ ${this?.getEnvironmentInfoPrompt?.(context)}`;
1745
1831
  4. Ensure the list accurately reflects the current state`;
1746
1832
  }
1747
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
+ }
1748
1944
  var NodeMemoryManager = class {
1749
1945
  filePath;
1750
1946
  maxMessages;
1947
+ onCondense;
1751
1948
  /**
1752
1949
  * @param dbPath 存储历史记录的本地文件路径(例如:'./.cline_memory.json')
1753
- * @param maxMessages 保留的最大消息数量,用于简单的历史截断(默认 150)
1950
+ * @param maxMessages 保留的最大消息数量,用于简单的历史截断(默认 200)
1754
1951
  */
1755
- constructor(dbPath, maxMessages = 150) {
1952
+ constructor(dbPath, maxMessages = 200) {
1756
1953
  this.filePath = path__namespace.resolve(dbPath);
1757
1954
  this.maxMessages = maxMessages;
1758
1955
  }
@@ -1787,19 +1984,22 @@ var NodeMemoryManager = class {
1787
1984
  await this.writeMessages(messages);
1788
1985
  await this.condenseHistory();
1789
1986
  }
1987
+ /**
1988
+ * 压缩历史:
1989
+ * - system 消息保持不变
1990
+ * - 以 agent_attempt_completion 为边界切分完整对话,仅合并“后面还有其他完整对话”的旧完整对话
1991
+ * - 其余需要压缩的场景下,对前半段工具调用结果标记为“结果已过期”
1992
+ * - 同时触发 onCondense 回调(宿主可据此清空 dify conversationId 新开窗口)
1993
+ */
1790
1994
  async condenseHistory() {
1791
1995
  const messages = await this.readMessages();
1792
- if (messages.length > this.maxMessages) {
1793
- const firstMessage = messages[0];
1794
- const isFirstSystem = firstMessage && firstMessage.role === "system";
1795
- const sliceCount = messages.length - this.maxMessages;
1796
- let condensed;
1797
- if (isFirstSystem) {
1798
- condensed = [firstMessage, ...messages.slice(sliceCount + 1)];
1799
- } else {
1800
- condensed = messages.slice(sliceCount);
1801
- }
1802
- 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();
1803
2003
  }
1804
2004
  }
1805
2005
  async clear() {