@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, IPromptManager, IMcpManager, McpServerInfo, McpTool, McpToolCallResult, IAgentRuntime, ClineAgent } from '../index.mjs';
2
2
  export { AgentDefaultTool, AgentOptions, AgentState, AssistantMessageContent, CustomToolDefinition, IMemoryManager, READ_ONLY_TOOLS, ReasoningStreamContent, TOOL_RESULT_BLOCK, TextStreamContent, ToolParamName, ToolUse, VERSION, collectCustomToolNames, defaultTools, getToolUseNames, isCustomToolGroup, parseAssistantMessageV2, renderCustomToolPrompt, renderCustomToolsPrompt, setDynamicToolUseNames, toolParamNames, toolUseNames } from '../index.mjs';
3
- import { W as WebMemoryManager } from '../WebMemoryManager-Bk19j8i6.mjs';
3
+ import { W as WebMemoryManager } from '../WebMemoryManager-DpaY-f0l.mjs';
4
4
 
5
5
  interface WebApiHandlerConfig {
6
6
  providerId?: string;
@@ -21,6 +21,7 @@ declare class WebApiHandler implements IApiHandler {
21
21
  constructor(config: WebApiHandlerConfig);
22
22
  getModelInfo(): ApiProviderInfo;
23
23
  createMessage(systemPrompt: string, messages: AgentMessage[]): AsyncGenerator<ApiStreamChunk, void, unknown>;
24
+ clearConversationId(): void;
24
25
  }
25
26
 
26
27
  declare class WebPromptManager implements IPromptManager {
@@ -1,6 +1,6 @@
1
1
  import { IApiHandler, ApiProviderInfo, AgentMessage, ApiStreamChunk, IPromptManager, IMcpManager, McpServerInfo, McpTool, McpToolCallResult, IAgentRuntime, ClineAgent } from '../index.js';
2
2
  export { AgentDefaultTool, AgentOptions, AgentState, AssistantMessageContent, CustomToolDefinition, IMemoryManager, 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 WebApiHandlerConfig {
6
6
  providerId?: string;
@@ -21,6 +21,7 @@ declare class WebApiHandler implements IApiHandler {
21
21
  constructor(config: WebApiHandlerConfig);
22
22
  getModelInfo(): ApiProviderInfo;
23
23
  createMessage(systemPrompt: string, messages: AgentMessage[]): AsyncGenerator<ApiStreamChunk, void, unknown>;
24
+ clearConversationId(): void;
24
25
  }
25
26
 
26
27
  declare class WebPromptManager implements IPromptManager {
@@ -142,9 +142,11 @@ return (
142
142
  },
143
143
  {
144
144
  name: "agent_read_file" /* FILE_READ */,
145
- description: "Read the contents of a specific file.",
145
+ 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.",
146
146
  params: {
147
- path: "(required) The absolute or relative file path to read."
147
+ path: "(required) The absolute or relative file path to read.",
148
+ start_line: "(optional) The 1-based line number to start reading from (inclusive). Defaults to 1.",
149
+ end_line: "(optional) The 1-based line number to stop reading at (inclusive). Defaults to the last line."
148
150
  }
149
151
  },
150
152
  {
@@ -587,7 +589,17 @@ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
587
589
  "dockerfile",
588
590
  "makefile",
589
591
  "properties",
590
- "lock"
592
+ "lock",
593
+ "node-version",
594
+ "nvmrc",
595
+ "editorconfig",
596
+ "prettierrc",
597
+ "eslintrc",
598
+ "babelrc",
599
+ "stylelintrc",
600
+ "huskyrc",
601
+ "commitlintrc",
602
+ "lintstagedrc"
591
603
  ]);
592
604
  var AgentState = /* @__PURE__ */ ((AgentState2) => {
593
605
  AgentState2["IDLE"] = "idle";
@@ -678,6 +690,13 @@ var ClineAgent = class {
678
690
  }
679
691
  this.skillDir = this.runtime.path.join(this.runtime.env.cwd(), ".agents/skills");
680
692
  this.rulesDir = this.runtime.path.join(this.runtime.env.cwd(), ".agents/rules");
693
+ const originalOnCondense = this.memory.onCondense;
694
+ this.memory.onCondense = async () => {
695
+ this.api.clearConversationId();
696
+ if (originalOnCondense) {
697
+ await originalOnCondense();
698
+ }
699
+ };
681
700
  }
682
701
  setState(newState) {
683
702
  this.state = newState;
@@ -735,16 +754,6 @@ var ClineAgent = class {
735
754
  async getAgentRules() {
736
755
  const cwd = this.runtime.env.cwd();
737
756
  const rules = [];
738
- const agentsMdPath = this.runtime.path.join(cwd, "Agents.md");
739
- try {
740
- if (await this.runtime.fs.fileExists(agentsMdPath)) {
741
- const content = await this.runtime.fs.readFile(agentsMdPath);
742
- if (content && content.trim()) {
743
- rules.push(content.trim());
744
- }
745
- }
746
- } catch (e) {
747
- }
748
757
  try {
749
758
  if (await this.runtime.fs.fileExists(this.rulesDir)) {
750
759
  const files = await this.runtime.fs.readDir(this.rulesDir);
@@ -770,6 +779,28 @@ ${content.trim()}\`\`\``);
770
779
  }
771
780
  } catch (e) {
772
781
  }
782
+ try {
783
+ const getAgentsMdPath = async () => {
784
+ const agentsMdPathList = [
785
+ this.runtime.path.join(cwd, "AGENTS.md"),
786
+ this.runtime.path.join(cwd, "Agents.md"),
787
+ this.runtime.path.join(cwd, "agents.md")
788
+ ];
789
+ for (const item of agentsMdPathList) {
790
+ if (await this.runtime.fs.fileExists(item)) {
791
+ return item;
792
+ }
793
+ }
794
+ };
795
+ const agentsMdPath = await getAgentsMdPath();
796
+ if (agentsMdPath) {
797
+ const content = await this.runtime.fs.readFile(agentsMdPath);
798
+ if (content && content.trim()) {
799
+ rules.push(content.trim());
800
+ }
801
+ }
802
+ } catch (e) {
803
+ }
773
804
  return rules.join("\n---\n");
774
805
  }
775
806
  async getSkills() {
@@ -907,7 +938,8 @@ ${finalPrompt}` : finalPrompt;
907
938
  if (path2) {
908
939
  try {
909
940
  const content = await this.runtime.fs.readFile(path2);
910
- toolResults.push({ tool, result: content });
941
+ const result = this.formatFileContentWithLineNumbers(content, tool.params.start_line, tool.params.end_line);
942
+ toolResults.push({ tool, result });
911
943
  } catch (error) {
912
944
  toolResults.push({ tool, result: `Error: ${error.message}` });
913
945
  }
@@ -920,6 +952,8 @@ ${finalPrompt}` : finalPrompt;
920
952
  const result = await this.runtime.terminal.execCommand(cmd);
921
953
  toolResults.push({ tool, result: `STDOUT: ${result.stdout}
922
954
  STDERR: ${result.stderr}` });
955
+ } else {
956
+ toolResults.push({ tool, result: "Error: No command provided." });
923
957
  }
924
958
  } else if (tool.name === "agent_write_to_file" /* FILE_NEW */) {
925
959
  const path2 = tool.params.path;
@@ -931,12 +965,18 @@ STDERR: ${result.stderr}` });
931
965
  } catch (error) {
932
966
  toolResults.push({ tool, result: `Error: ${error.message}` });
933
967
  }
968
+ } else {
969
+ toolResults.push({ tool, result: "Error: Missing path or content." });
934
970
  }
935
971
  } else if (tool.name === "agent_list_files" /* LIST_FILES */) {
936
972
  const path2 = tool.params.path || this.runtime.env.cwd();
973
+ const recursive = tool.params.recursive === "true";
937
974
  try {
938
- const files = await this.runtime.fs.readDir(path2);
939
- toolResults.push({ tool, result: files.map((f) => f.name + (f.isDirectory() ? "/" : "")).join("\n") });
975
+ const files = await this.runtime.fs.readDir(path2, { recursive });
976
+ toolResults.push({
977
+ tool,
978
+ result: files.map((f) => `${f.isDirectory() ? "[DIR] " : " "}${f.name}${f.isDirectory() ? "/" : ""}`).join("\n")
979
+ });
940
980
  } catch (e) {
941
981
  toolResults.push({ tool, result: `Error: ${e.message}` });
942
982
  }
@@ -1076,10 +1116,15 @@ STDERR: ${result.stderr}` });
1076
1116
  try {
1077
1117
  const res = await fetch(url);
1078
1118
  const text = await res.text();
1079
- toolResults.push({ tool, result: text.substring(0, 3e3) });
1119
+ const MAX_LEN = 8e3;
1120
+ const truncated = text.length > MAX_LEN ? `${text.substring(0, MAX_LEN)}
1121
+ ...[truncated ${text.length - MAX_LEN} chars]` : text;
1122
+ toolResults.push({ tool, result: truncated });
1080
1123
  } catch (e) {
1081
1124
  toolResults.push({ tool, result: `Error: ${e.message}` });
1082
1125
  }
1126
+ } else {
1127
+ toolResults.push({ tool, result: "Error: No url provided." });
1083
1128
  }
1084
1129
  } else if (tool.name === "agent_focus_chain" /* TODO */) {
1085
1130
  toolResults.push({ tool, result: `Focus chain updated successfully.` });
@@ -1167,6 +1212,44 @@ ${this.promptManager.getUsedToolResultInstructions()}`;
1167
1212
  }
1168
1213
  return this.getPrompt();
1169
1214
  }
1215
+ /**
1216
+ * 将文件内容格式化为带行号文本(模拟编辑器行号显示),
1217
+ * 支持通过 start_line / end_line 截取读取范围。
1218
+ */
1219
+ formatFileContentWithLineNumbers(content, startLineParam, endLineParam) {
1220
+ const lines = content.split(/\r?\n/);
1221
+ const normalized = lines.length > 0 && lines[lines.length - 1] === "" ? lines.slice(0, -1) : lines;
1222
+ const total = normalized.length;
1223
+ if (total === 0) {
1224
+ return "1|(empty file)";
1225
+ }
1226
+ const parseLine = (val, fallback) => {
1227
+ if (val === void 0 || val === null || val.trim() === "") {
1228
+ return fallback;
1229
+ }
1230
+ const num = Number.parseInt(val, 10);
1231
+ if (!Number.isFinite(num) || num <= 0) {
1232
+ return fallback;
1233
+ }
1234
+ return num;
1235
+ };
1236
+ const start = parseLine(startLineParam, 1);
1237
+ const end = parseLine(endLineParam, total);
1238
+ if (start > end) {
1239
+ return `Error: start_line (${start}) cannot be greater than end_line (${end}).`;
1240
+ }
1241
+ if (start > total) {
1242
+ return `Error: start_line (${start}) is out of range. The file has ${total} lines.`;
1243
+ }
1244
+ const from = Math.max(1, start);
1245
+ const to = Math.min(total, end);
1246
+ const width = String(total).length;
1247
+ const selected = normalized.slice(from - 1, to);
1248
+ const numbered = selected.map((line, idx) => `${String(from + idx).padStart(width)}|${line}`).join("\n");
1249
+ const header = from === 1 && to === total ? `1-${total}/${total}` : `${from}-${to}/${total}`;
1250
+ return `${header}
1251
+ ${numbered}`;
1252
+ }
1170
1253
  /**
1171
1254
  * 简单的 Diff 解析与替换辅助方法
1172
1255
  */
@@ -1416,6 +1499,9 @@ var WebApiHandler = class {
1416
1499
  };
1417
1500
  }
1418
1501
  }
1502
+ clearConversationId() {
1503
+ this.conversationId = void 0;
1504
+ }
1419
1505
  };
1420
1506
 
1421
1507
  // src/prompt/WebPromptManager.ts
@@ -1721,16 +1807,127 @@ ${this?.getEnvironmentInfoPrompt?.(context)}`;
1721
1807
  }
1722
1808
  };
1723
1809
 
1810
+ // src/memory/condense.ts
1811
+ var TOOL_RESULT_CONTENT_REGEX = /<tool_call_result[^>]*>[\s\S]*?<\/tool_call_result>/;
1812
+ var EXPIRED_MARKER_TEXT = "[The result has expired and has been removed from the context]";
1813
+ var ATTEMPT_TOOL_NAME = "agent_attempt_completion" /* ATTEMPT */;
1814
+ function getContentString(content) {
1815
+ return typeof content === "string" ? content : "";
1816
+ }
1817
+ function isToolResultMessage(content) {
1818
+ if (typeof content !== "string") {
1819
+ return false;
1820
+ }
1821
+ return TOOL_RESULT_CONTENT_REGEX.test(content);
1822
+ }
1823
+ function isAttemptCallMessage(message) {
1824
+ if (message.role !== "assistant") {
1825
+ return false;
1826
+ }
1827
+ const content = getContentString(message.content);
1828
+ return new RegExp(`<${ATTEMPT_TOOL_NAME}[\\s>]`).test(content) || new RegExp(`<name>\\s*${ATTEMPT_TOOL_NAME}\\s*</name>`).test(content);
1829
+ }
1830
+ function markExpiredToolResult(content) {
1831
+ if (typeof content !== "string") {
1832
+ return content;
1833
+ }
1834
+ return content.replace(
1835
+ /(<tool_call_result[^>]*tool_name="([^"]*)"[^>]*>)[\s\S]*?(<\/tool_call_result>)/g,
1836
+ (_match, openTag, _toolName, closeTag) => {
1837
+ return `${openTag}
1838
+ ${EXPIRED_MARKER_TEXT}
1839
+ ${closeTag}`;
1840
+ }
1841
+ );
1842
+ }
1843
+ function splitConversations(messages) {
1844
+ const conversations = [];
1845
+ let current = [];
1846
+ for (const message of messages) {
1847
+ current.push(message);
1848
+ if (isAttemptCallMessage(message)) {
1849
+ conversations.push(current);
1850
+ current = [];
1851
+ }
1852
+ }
1853
+ if (current.length > 0) {
1854
+ conversations.push(current);
1855
+ }
1856
+ return conversations;
1857
+ }
1858
+ function isCompleteConversation(conversation) {
1859
+ if (conversation.length === 0) {
1860
+ return false;
1861
+ }
1862
+ return isAttemptCallMessage(conversation[conversation.length - 1]);
1863
+ }
1864
+ function compressCompleteConversation(conversation) {
1865
+ if (conversation.length <= 2) {
1866
+ return conversation;
1867
+ }
1868
+ const head = conversation[0];
1869
+ const tail = conversation[conversation.length - 1];
1870
+ const mergedMarker = {
1871
+ role: "user",
1872
+ 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."
1873
+ };
1874
+ return [head, mergedMarker, tail];
1875
+ }
1876
+ function markExpiredFirstHalf(messages) {
1877
+ const half = Math.floor(messages.length / 2);
1878
+ return messages.map((message, index) => {
1879
+ if (index < half && isToolResultMessage(message.content)) {
1880
+ return { ...message, content: markExpiredToolResult(message.content) };
1881
+ }
1882
+ return message;
1883
+ });
1884
+ }
1885
+ function condenseMessages(messages, maxMessages) {
1886
+ if (messages.length <= maxMessages) {
1887
+ return messages;
1888
+ }
1889
+ const systemMessages = messages.filter((message) => message.role === "system");
1890
+ const restMessages = messages.filter((message) => message.role !== "system");
1891
+ const conversations = splitConversations(restMessages);
1892
+ const completeIndices = [];
1893
+ for (let i = 0; i < conversations.length; i++) {
1894
+ if (isCompleteConversation(conversations[i])) {
1895
+ completeIndices.push(i);
1896
+ }
1897
+ }
1898
+ const currentLength = () => systemMessages.length + conversations.reduce((acc, c) => acc + c.length, 0);
1899
+ if (completeIndices.length >= 2) {
1900
+ const lastCompleteIndex = completeIndices[completeIndices.length - 1];
1901
+ for (const idx of completeIndices) {
1902
+ if (idx === lastCompleteIndex) {
1903
+ break;
1904
+ }
1905
+ conversations[idx] = compressCompleteConversation(conversations[idx]);
1906
+ if (currentLength() <= maxMessages) {
1907
+ break;
1908
+ }
1909
+ }
1910
+ }
1911
+ let condensed = conversations.flat();
1912
+ condensed = markExpiredFirstHalf(condensed);
1913
+ if (systemMessages.length + condensed.length > maxMessages) {
1914
+ const nonSystemBudget = Math.max(0, maxMessages - systemMessages.length);
1915
+ condensed = condensed.slice(-nonSystemBudget);
1916
+ }
1917
+ return [...systemMessages, ...condensed];
1918
+ }
1919
+
1724
1920
  // src/memory/WebMemoryManager.ts
1725
1921
  var WebMemoryManager = class {
1726
1922
  storageKey;
1727
1923
  maxMessages;
1728
1924
  inMemoryFallback = [];
1925
+ onCondense;
1729
1926
  /**
1730
1927
  * @param storageKey SessionStorage 中的键名
1731
- * @param maxMessages 保留的最大消息数量,用于简单的历史截断(默认 150)
1928
+ * @param maxMessages 保留的最大消息数量,用于简单的历史截断(默认 200)
1732
1929
  */
1733
- constructor(storageKey = "cline_agent_memory", maxMessages = 150) {
1930
+ constructor(storageKey = "cline_agent_memory", maxMessages = 200) {
1734
1931
  this.storageKey = storageKey;
1735
1932
  this.maxMessages = maxMessages;
1736
1933
  }
@@ -1771,19 +1968,22 @@ var WebMemoryManager = class {
1771
1968
  this.writeMessages(messages);
1772
1969
  await this.condenseHistory();
1773
1970
  }
1971
+ /**
1972
+ * 压缩历史:
1973
+ * - system 消息保持不变
1974
+ * - 以 agent_attempt_completion 为边界切分完整对话,仅合并“后面还有其他完整对话”的旧完整对话
1975
+ * - 其余需要压缩的场景下,对前半段工具调用结果标记为“结果已过期”
1976
+ * - 同时触发 onCondense 回调(宿主可据此清空 dify conversationId 新开窗口)
1977
+ */
1774
1978
  async condenseHistory() {
1775
1979
  const messages = this.readMessages();
1776
- if (messages.length > this.maxMessages) {
1777
- const firstMessage = messages[0];
1778
- const isFirstSystem = firstMessage && firstMessage.role === "system";
1779
- const sliceCount = messages.length - this.maxMessages;
1780
- let condensed;
1781
- if (isFirstSystem) {
1782
- condensed = [firstMessage, ...messages.slice(sliceCount + 1)];
1783
- } else {
1784
- condensed = messages.slice(sliceCount);
1785
- }
1786
- this.writeMessages(condensed);
1980
+ const condensed = condenseMessages(messages, this.maxMessages);
1981
+ if (condensed === messages) {
1982
+ return Promise.resolve();
1983
+ }
1984
+ this.writeMessages(condensed);
1985
+ if (this.onCondense) {
1986
+ await this.onCondense();
1787
1987
  }
1788
1988
  return Promise.resolve();
1789
1989
  }