jinzd-ai-cli 0.1.60 → 0.1.62

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.
@@ -8,7 +8,7 @@ import { platform } from "os";
8
8
  import chalk from "chalk";
9
9
 
10
10
  // src/core/constants.ts
11
- var VERSION = "0.1.60";
11
+ var VERSION = "0.1.62";
12
12
  var APP_NAME = "ai-cli";
13
13
  var CONFIG_DIR_NAME = ".aicli";
14
14
  var CONFIG_FILE_NAME = "config.json";
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  SUBAGENT_MAX_ROUNDS_LIMIT,
31
31
  VERSION,
32
32
  runTestsTool
33
- } from "./chunk-S2KBV42Z.js";
33
+ } from "./chunk-LPE66KZ3.js";
34
34
 
35
35
  // src/index.ts
36
36
  import { program } from "commander";
@@ -1100,24 +1100,6 @@ Node.js \u4E0D\u4F1A\u81EA\u52A8\u4F7F\u7528\u7CFB\u7EDF\u4EE3\u7406\u3002\u8BF7
1100
1100
 
1101
1101
  // src/providers/openai-compatible.ts
1102
1102
  import OpenAI from "openai";
1103
- var HALLUCINATION_PATTERNS = [
1104
- /文件路径[::]\s*`?[^\s`]+\.\w{1,5}/,
1105
- // 文件路径: `path/to/file.ext`(要求文件扩展名)
1106
- /已生成[::!!]/,
1107
- // 已生成完成!
1108
- /已保存到?\s*[`'"]/,
1109
- // 已保存到 `path`(要求后跟路径引号)
1110
- /已写入[::!!]/,
1111
- // 已写入!
1112
- /已创建[::!!]/,
1113
- // 已创建!
1114
- /File\s+(?:written|saved|created)\s+(?:to|as|at)/i,
1115
- // File written to / saved as(要求介词)
1116
- /生成完成[!!]/,
1117
- // 生成完成!
1118
- /✅\s*(?:文件|已[生保写创]|第)\S*\.\w{1,5}/
1119
- // ✅ 文件已保存 path.ext(要求文件扩展名)
1120
- ];
1121
1103
  var OpenAICompatibleProvider = class extends BaseProvider {
1122
1104
  client;
1123
1105
  defaultTimeout = 6e4;
@@ -1458,42 +1440,6 @@ var OpenAICompatibleProvider = class extends BaseProvider {
1458
1440
  async listModels() {
1459
1441
  return this.info.models;
1460
1442
  }
1461
- /**
1462
- * 检测 AI 文本响应中是否存在虚假的文件操作完成声明。
1463
- * 当 AI 返回纯文本而非 tool_calls,且文本中包含「已保存」「已写入」等声明时,
1464
- * 说明 AI 跳过了实际工具调用,需要触发重试。
1465
- * 子类(Kimi / DeepSeek)在 chatWithTools 覆写中使用。
1466
- */
1467
- detectsHallucinatedFileOp(content) {
1468
- return HALLUCINATION_PATTERNS.some((pattern) => pattern.test(content));
1469
- }
1470
- /**
1471
- * 检查 _extraMessages 中是否已存在 write_file / edit_file 的成功调用记录。
1472
- * 若前面的 agentic 轮次已实际调用了写文件工具,则最终文本总结中提到「已保存」
1473
- * 是合理的事实陈述,不应被判定为虚假声明。
1474
- * 用于避免误报(false positive)。
1475
- */
1476
- hadPreviousWriteToolCalls(request) {
1477
- const extraMessages = request._extraMessages ?? [];
1478
- return extraMessages.some((msg) => {
1479
- if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls)) return false;
1480
- return msg.tool_calls.some((tc) => {
1481
- const fn = tc.function;
1482
- const name = fn?.name ?? "";
1483
- return name === "write_file" || name === "edit_file";
1484
- });
1485
- });
1486
- }
1487
- /**
1488
- * 合并两次 API 调用的 token 用量(虚假声明重试时使用)
1489
- */
1490
- mergeUsage(a, b) {
1491
- if (!a && !b) return void 0;
1492
- return {
1493
- inputTokens: (a?.inputTokens ?? 0) + (b?.inputTokens ?? 0),
1494
- outputTokens: (a?.outputTokens ?? 0) + (b?.outputTokens ?? 0)
1495
- };
1496
- }
1497
1443
  wrapError(err) {
1498
1444
  if (err instanceof OpenAI.AuthenticationError) {
1499
1445
  return new AuthError(this.info.id);
@@ -1509,17 +1455,8 @@ var OpenAICompatibleProvider = class extends BaseProvider {
1509
1455
  };
1510
1456
 
1511
1457
  // src/providers/deepseek.ts
1512
- var DEEPSEEK_TOOL_CALL_REMINDER = `
1513
-
1514
- \u3010\u26A0\uFE0F \u5DE5\u5177\u8C03\u7528\u5F3A\u5236\u89C4\u8303\u3011
1515
- \u5F53\u9700\u8981\u521B\u5EFA\u3001\u5199\u5165\u6216\u4FEE\u6539\u6587\u4EF6\u65F6\uFF0C\u5FC5\u987B\u4E14\u53EA\u80FD\u901A\u8FC7 API \u7684 function calling \u673A\u5236\u8C03\u7528 write_file \u6216 edit_file \u5DE5\u5177\u3002
1516
- \u7EDD\u5BF9\u4E0D\u5141\u8BB8\u5728\u56DE\u590D\u6587\u672C\u4E2D\u58F0\u79F0"\u6587\u4EF6\u5DF2\u751F\u6210"\u3001"\u6587\u4EF6\u5DF2\u4FDD\u5B58"\u3001"\u5DF2\u5199\u5165"\u7B49\uFF0C\u800C\u4E0D\u5B9E\u9645\u8C03\u7528\u5DE5\u5177\u3002
1517
- \u4EC5\u5728\u6587\u672C\u4E2D\u63CF\u8FF0\u6587\u4EF6\u5185\u5BB9\u800C\u4E0D\u8C03\u7528\u5DE5\u5177 = \u6587\u4EF6\u4E0D\u5B58\u5728 = \u4EFB\u52A1\u5931\u8D25\u3002
1518
- \u5982\u679C\u9700\u8981\u751F\u6210\u591A\u4E2A\u6587\u4EF6\uFF0C\u5FC5\u987B\u5BF9\u6BCF\u4E2A\u6587\u4EF6\u5206\u522B\u8C03\u7528 write_file \u5DE5\u5177\uFF0C\u4E0D\u53EF\u7701\u7565\u4EFB\u4F55\u4E00\u4E2A\u3002`;
1519
1458
  var DeepSeekProvider = class extends OpenAICompatibleProvider {
1520
1459
  defaultBaseUrl = "https://api.deepseek.com/v1";
1521
- // 禁用流式工具调用:DeepSeek 的虚假声明检测(方案 C)需要完整响应
1522
- enableStreamingToolCalls = false;
1523
1460
  info = {
1524
1461
  id: "deepseek",
1525
1462
  displayName: "DeepSeek",
@@ -1542,75 +1479,13 @@ var DeepSeekProvider = class extends OpenAICompatibleProvider {
1542
1479
  }
1543
1480
  ]
1544
1481
  };
1545
- /**
1546
- * 覆写 chatWithTools,叠加两道防线:
1547
- *
1548
- * 方案 B(预防):在 system prompt 末尾追加工具调用规范提示,
1549
- * 指示 DeepSeek 必须使用 API function calling,禁止虚假完成声明。
1550
- *
1551
- * 方案 C(兜底):若 DeepSeek 在文本中声称已完成文件操作但未调用任何工具,
1552
- * 自动检测并注入纠正消息,重新请求一次,强制 DeepSeek 实际调用工具。
1553
- */
1554
- async chatWithTools(request, tools) {
1555
- const enhancedRequest = {
1556
- ...request,
1557
- systemPrompt: (request.systemPrompt ?? "") + DEEPSEEK_TOOL_CALL_REMINDER
1558
- };
1559
- const result = await super.chatWithTools(enhancedRequest, tools);
1560
- if ("content" in result && result.content) {
1561
- const hasWriteTools = tools.some((t) => t.name === "write_file" || t.name === "edit_file");
1562
- const alreadyWrote = this.hadPreviousWriteToolCalls(enhancedRequest);
1563
- if (hasWriteTools && !alreadyWrote && this.detectsHallucinatedFileOp(result.content)) {
1564
- process.stderr.write(
1565
- `[deepseek] \u26A0 \u68C0\u6D4B\u5230\u865A\u5047\u5B8C\u6210\u58F0\u660E\uFF08AI \u58F0\u79F0\u5DF2\u5199\u5165\u6587\u4EF6\u4F46\u672A\u8C03\u7528\u5DE5\u5177\uFF09\uFF0C\u6B63\u5728\u5F3A\u5236\u91CD\u65B0\u8BF7\u6C42...
1566
- `
1567
- );
1568
- const existingExtra = enhancedRequest._extraMessages ?? [];
1569
- const correctionRequest = {
1570
- ...enhancedRequest,
1571
- _extraMessages: [
1572
- ...existingExtra,
1573
- { role: "assistant", content: result.content },
1574
- {
1575
- role: "user",
1576
- content: "\u4F60\u521A\u624D\u6CA1\u6709\u5B9E\u9645\u8C03\u7528 write_file \u5DE5\u5177\uFF0C\u6587\u4EF6\u5E76\u672A\u88AB\u521B\u5EFA\uFF01\u8BF7\u7ACB\u5373\u4F7F\u7528 write_file \u5DE5\u5177\u7684 function calling API \u6267\u884C\u5B9E\u9645\u7684\u6587\u4EF6\u5199\u5165\u64CD\u4F5C\u3002\u4E0D\u8981\u518D\u7528\u6587\u5B57\u63CF\u8FF0\u6587\u4EF6\u5185\u5BB9\uFF0C\u5FC5\u987B\u901A\u8FC7 API \u7684 tool_calls \u673A\u5236\u8C03\u7528 write_file\u3002"
1577
- }
1578
- ]
1579
- };
1580
- const retryResult = await super.chatWithTools(correctionRequest, tools);
1581
- if ("toolCalls" in retryResult) {
1582
- retryResult.usage = this.mergeUsage(result.usage, retryResult.usage);
1583
- } else if ("content" in retryResult) {
1584
- retryResult.usage = this.mergeUsage(result.usage, retryResult.usage);
1585
- if (this.detectsHallucinatedFileOp(retryResult.content)) {
1586
- process.stderr.write(
1587
- `[deepseek] \u26A0 \u91CD\u8BD5\u540E\u4ECD\u68C0\u6D4B\u5230\u865A\u5047\u5B8C\u6210\u58F0\u660E\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5\u6587\u4EF6\u662F\u5426\u5DF2\u521B\u5EFA\u3002
1588
- `
1589
- );
1590
- }
1591
- }
1592
- return retryResult;
1593
- }
1594
- }
1595
- return result;
1596
- }
1597
1482
  };
1598
1483
 
1599
1484
  // src/providers/zhipu.ts
1600
- var ZHIPU_TOOL_CALL_REMINDER = `
1601
-
1602
- \u3010\u26A0\uFE0F \u5DE5\u5177\u8C03\u7528\u5F3A\u5236\u89C4\u8303\u3011
1603
- \u5F53\u9700\u8981\u521B\u5EFA\u3001\u5199\u5165\u6216\u4FEE\u6539\u6587\u4EF6\u65F6\uFF0C\u5FC5\u987B\u4E14\u53EA\u80FD\u901A\u8FC7 API \u7684 function calling \u673A\u5236\u8C03\u7528 write_file \u6216 edit_file \u5DE5\u5177\u3002
1604
- \u7EDD\u5BF9\u4E0D\u5141\u8BB8\u5728\u56DE\u590D\u6587\u672C\u4E2D\u58F0\u79F0"\u6587\u4EF6\u5DF2\u751F\u6210"\u3001"\u6587\u4EF6\u5DF2\u4FDD\u5B58"\u3001"\u5DF2\u5199\u5165"\u3001"\u5DF2\u521B\u5EFA"\u7B49\uFF0C\u800C\u4E0D\u5B9E\u9645\u8C03\u7528\u5DE5\u5177\u3002
1605
- \u4EC5\u5728\u6587\u672C\u4E2D\u63CF\u8FF0\u6587\u4EF6\u5185\u5BB9\u800C\u4E0D\u8C03\u7528\u5DE5\u5177 = \u6587\u4EF6\u4E0D\u5B58\u5728 = \u4EFB\u52A1\u5931\u8D25\u3002
1606
- \u5982\u679C\u9700\u8981\u751F\u6210\u591A\u4E2A\u6587\u4EF6\uFF0C\u5FC5\u987B\u5BF9\u6BCF\u4E2A\u6587\u4EF6\u5206\u522B\u8C03\u7528 write_file \u5DE5\u5177\uFF0C\u4E0D\u53EF\u7701\u7565\u4EFB\u4F55\u4E00\u4E2A\u3002
1607
- \u4E25\u7981\u8F93\u51FA\u865A\u5047\u7684"\u751F\u6210\u62A5\u544A"\u6216"\u5B8C\u6210\u603B\u7ED3"\uFF0C\u9664\u975E\u4F60\u5DF2\u7ECF\u901A\u8FC7 tool_calls \u5B9E\u9645\u5B8C\u6210\u4E86\u6240\u6709\u6587\u4EF6\u5199\u5165\u3002`;
1608
1485
  var ZhipuProvider = class extends OpenAICompatibleProvider {
1609
1486
  defaultBaseUrl = "https://open.bigmodel.cn/api/paas/v4";
1610
1487
  // GLM-5 等深度思考模型生成长内容需要较长时间,默认 5 分钟
1611
1488
  defaultTimeout = 3e5;
1612
- // 禁用流式工具调用:虚假声明检测(方案 C)需要完整响应
1613
- enableStreamingToolCalls = false;
1614
1489
  info = {
1615
1490
  id: "zhipu",
1616
1491
  displayName: "\u667A\u8C31\u6E05\u8A00 (GLM)",
@@ -1678,75 +1553,77 @@ var ZhipuProvider = class extends OpenAICompatibleProvider {
1678
1553
  }
1679
1554
  ]
1680
1555
  };
1681
- /**
1682
- * 覆写 chatWithTools,叠加两道防线:
1683
- *
1684
- * 方案 B(预防):在 system prompt 末尾追加工具调用规范提示,
1685
- * 指示智谱清言必须使用 API function calling,禁止虚假完成声明。
1686
- *
1687
- * 方案 C(兜底):若智谱清言在文本中声称已完成文件操作但未调用任何工具,
1688
- * 自动检测并注入纠正消息,重新请求一次,强制实际调用工具。
1689
- */
1690
- async chatWithTools(request, tools) {
1691
- const enhancedRequest = {
1692
- ...request,
1693
- systemPrompt: (request.systemPrompt ?? "") + ZHIPU_TOOL_CALL_REMINDER
1694
- };
1695
- const result = await super.chatWithTools(enhancedRequest, tools);
1696
- if ("content" in result && result.content) {
1697
- const hasWriteTools = tools.some((t) => t.name === "write_file" || t.name === "edit_file");
1698
- const alreadyWrote = this.hadPreviousWriteToolCalls(enhancedRequest);
1699
- if (hasWriteTools && !alreadyWrote && this.detectsHallucinatedFileOp(result.content)) {
1700
- process.stderr.write(
1701
- `[zhipu] \u26A0 \u68C0\u6D4B\u5230\u865A\u5047\u5B8C\u6210\u58F0\u660E\uFF08AI \u58F0\u79F0\u5DF2\u5199\u5165\u6587\u4EF6\u4F46\u672A\u8C03\u7528\u5DE5\u5177\uFF09\uFF0C\u6B63\u5728\u5F3A\u5236\u91CD\u65B0\u8BF7\u6C42...
1702
- `
1703
- );
1704
- const existingExtra = enhancedRequest._extraMessages ?? [];
1705
- const correctionRequest = {
1706
- ...enhancedRequest,
1707
- _extraMessages: [
1708
- ...existingExtra,
1709
- { role: "assistant", content: result.content },
1710
- {
1711
- role: "user",
1712
- content: "\u4F60\u521A\u624D\u6CA1\u6709\u5B9E\u9645\u8C03\u7528 write_file \u5DE5\u5177\uFF0C\u6587\u4EF6\u5E76\u672A\u88AB\u521B\u5EFA\uFF01\u8BF7\u7ACB\u5373\u4F7F\u7528 write_file \u5DE5\u5177\u7684 function calling API \u6267\u884C\u5B9E\u9645\u7684\u6587\u4EF6\u5199\u5165\u64CD\u4F5C\u3002\u4E0D\u8981\u518D\u7528\u6587\u5B57\u63CF\u8FF0\u6587\u4EF6\u5185\u5BB9\uFF0C\u5FC5\u987B\u901A\u8FC7 API \u7684 tool_calls \u673A\u5236\u8C03\u7528 write_file\u3002"
1713
- }
1714
- ]
1715
- };
1716
- const retryResult = await super.chatWithTools(correctionRequest, tools);
1717
- if ("toolCalls" in retryResult) {
1718
- retryResult.usage = this.mergeUsage(result.usage, retryResult.usage);
1719
- } else if ("content" in retryResult) {
1720
- retryResult.usage = this.mergeUsage(result.usage, retryResult.usage);
1721
- if (this.detectsHallucinatedFileOp(retryResult.content)) {
1722
- process.stderr.write(
1723
- `[zhipu] \u26A0 \u91CD\u8BD5\u540E\u4ECD\u68C0\u6D4B\u5230\u865A\u5047\u5B8C\u6210\u58F0\u660E\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5\u6587\u4EF6\u662F\u5426\u5DF2\u521B\u5EFA\u3002
1724
- `
1725
- );
1726
- }
1727
- }
1728
- return retryResult;
1729
- }
1730
- }
1731
- return result;
1732
- }
1733
1556
  };
1734
1557
 
1558
+ // src/tools/hallucination.ts
1559
+ var HALLUCINATION_PATTERNS = [
1560
+ /文件路径[::]\s*`?[^\s`]+\.\w{1,5}/,
1561
+ // 文件路径: `path/to/file.ext`(要求文件扩展名)
1562
+ /已生成[::!!]/,
1563
+ // 已生成完成!
1564
+ /已保存到?\s*[`'"]/,
1565
+ // 已保存到 `path`(要求后跟路径引号)
1566
+ /已写入[::!!]/,
1567
+ // 已写入!
1568
+ /已创建[::!!]/,
1569
+ // 已创建!
1570
+ /File\s+(?:written|saved|created)\s+(?:to|as|at)/i,
1571
+ // File written to / saved as(要求介词)
1572
+ /生成完成[!!]/,
1573
+ // 生成完成!
1574
+ /✅\s*(?:文件|已[生保写创]|第)\S*\.\w{1,5}/
1575
+ // ✅ 文件已保存 path.ext(要求文件扩展名)
1576
+ ];
1577
+ function detectsHallucinatedFileOp(content) {
1578
+ return HALLUCINATION_PATTERNS.some((pattern) => pattern.test(content));
1579
+ }
1580
+ function hadPreviousWriteToolCalls(extraMessages) {
1581
+ const msgs = extraMessages;
1582
+ return msgs.some((msg) => {
1583
+ if (msg.role === "assistant" && Array.isArray(msg.tool_calls)) {
1584
+ return msg.tool_calls.some((tc) => {
1585
+ const fn = tc.function;
1586
+ const name = fn?.name ?? "";
1587
+ return name === "write_file" || name === "edit_file";
1588
+ });
1589
+ }
1590
+ if (msg.role === "assistant" && Array.isArray(msg.content)) {
1591
+ return msg.content.some((block) => {
1592
+ if (block.type !== "tool_use") return false;
1593
+ const name = block.name ?? "";
1594
+ return name === "write_file" || name === "edit_file";
1595
+ });
1596
+ }
1597
+ if (msg.role === "model" && Array.isArray(msg.parts)) {
1598
+ return msg.parts.some((part) => {
1599
+ const fc = part.functionCall;
1600
+ const name = fc?.name ?? "";
1601
+ return name === "write_file" || name === "edit_file";
1602
+ });
1603
+ }
1604
+ return false;
1605
+ });
1606
+ }
1607
+ var TOOL_CALL_REMINDER = `
1608
+
1609
+ \u3010\u26A0\uFE0F \u5DE5\u5177\u8C03\u7528\u5F3A\u5236\u89C4\u8303\u3011
1610
+ \u5F53\u9700\u8981\u521B\u5EFA\u3001\u5199\u5165\u6216\u4FEE\u6539\u6587\u4EF6\u65F6\uFF0C\u5FC5\u987B\u4E14\u53EA\u80FD\u901A\u8FC7 API \u7684 function calling \u673A\u5236\u8C03\u7528 write_file \u6216 edit_file \u5DE5\u5177\u3002
1611
+ \u7EDD\u5BF9\u4E0D\u5141\u8BB8\u5728\u56DE\u590D\u6587\u672C\u4E2D\u58F0\u79F0"\u6587\u4EF6\u5DF2\u751F\u6210"\u3001"\u6587\u4EF6\u5DF2\u4FDD\u5B58"\u3001"\u5DF2\u5199\u5165"\u3001"\u5DF2\u521B\u5EFA"\u7B49\uFF0C\u800C\u4E0D\u5B9E\u9645\u8C03\u7528\u5DE5\u5177\u3002
1612
+ \u4EC5\u5728\u6587\u672C\u4E2D\u63CF\u8FF0\u6587\u4EF6\u5185\u5BB9\u800C\u4E0D\u8C03\u7528\u5DE5\u5177 = \u6587\u4EF6\u4E0D\u5B58\u5728 = \u4EFB\u52A1\u5931\u8D25\u3002
1613
+ \u5982\u679C\u9700\u8981\u751F\u6210\u591A\u4E2A\u6587\u4EF6\uFF0C\u5FC5\u987B\u5BF9\u6BCF\u4E2A\u6587\u4EF6\u5206\u522B\u8C03\u7528 write_file \u5DE5\u5177\uFF0C\u4E0D\u53EF\u7701\u7565\u4EFB\u4F55\u4E00\u4E2A\u3002
1614
+ \u4E25\u7981\u8F93\u51FA\u865A\u5047\u7684"\u751F\u6210\u62A5\u544A"\u6216"\u5B8C\u6210\u603B\u7ED3"\uFF0C\u9664\u975E\u4F60\u5DF2\u7ECF\u901A\u8FC7 tool_calls \u5B9E\u9645\u5B8C\u6210\u4E86\u6240\u6709\u6587\u4EF6\u5199\u5165\u3002`;
1615
+ var HALLUCINATION_CORRECTION_MESSAGE = "\u4F60\u521A\u624D\u6CA1\u6709\u5B9E\u9645\u8C03\u7528 write_file \u5DE5\u5177\uFF0C\u6587\u4EF6\u5E76\u672A\u88AB\u521B\u5EFA\uFF01\u8BF7\u7ACB\u5373\u4F7F\u7528 write_file \u5DE5\u5177\u7684 function calling API \u6267\u884C\u5B9E\u9645\u7684\u6587\u4EF6\u5199\u5165\u64CD\u4F5C\u3002\u4E0D\u8981\u518D\u7528\u6587\u5B57\u63CF\u8FF0\u6587\u4EF6\u5185\u5BB9\uFF0C\u5FC5\u987B\u901A\u8FC7 API \u7684 tool_calls \u673A\u5236\u8C03\u7528 write_file\u3002";
1616
+
1735
1617
  // src/providers/kimi.ts
1736
- var KIMI_TOOL_CALL_REMINDER = `
1618
+ var KIMI_XML_REMINDER = `
1737
1619
 
1738
- \u3010\u26A0\uFE0F \u5DE5\u5177\u8C03\u7528\u5F3A\u5236\u89C4\u8303 - \u6781\u5176\u91CD\u8981\u3011
1620
+ \u3010\u26A0\uFE0F \u5DE5\u5177\u8C03\u7528\u683C\u5F0F\u89C4\u8303 - Kimi \u4E13\u7528\u3011
1739
1621
  \u8C03\u7528\u4EFB\u4F55\u5DE5\u5177\uFF08write_file\u3001bash\u3001read_file \u7B49\uFF09\u65F6\uFF0C\u5FC5\u987B\u4E14\u53EA\u80FD\u4F7F\u7528 API \u7684 function calling \u7ED3\u6784\u5316\u63A5\u53E3\u3002
1740
1622
  \u4E25\u7981\u5728\u56DE\u590D\u6587\u672C\u4E2D\u8F93\u51FA XML \u683C\u5F0F\u7684\u4F2A\u5DE5\u5177\u8C03\u7528\u6807\u7B7E\uFF08\u5982 <write_file>\u3001<bash>\u3001<read_file> \u7B49\uFF09\u3002
1741
- \u6587\u672C\u4E2D\u7684 XML \u6807\u7B7E\u4E0D\u4F1A\u88AB\u7CFB\u7EDF\u6267\u884C\uFF0C\u4F1A\u5BFC\u81F4\u6587\u4EF6\u672A\u5199\u5165\u3001\u547D\u4EE4\u672A\u8FD0\u884C\uFF0C\u4EFB\u52A1\u5F7B\u5E95\u5931\u8D25\u3002
1742
- \u6BCF\u6B21\u9700\u8981\u5199\u6587\u4EF6\uFF0C\u5FC5\u987B\u8C03\u7528 write_file \u5DE5\u5177 API\uFF0C\u4E0D\u5141\u8BB8\u4EFB\u4F55\u4F8B\u5916\u3002
1743
- \u3010\u26A0\uFE0F \u4E25\u7981\u865A\u5047\u5B8C\u6210\u58F0\u660E\u3011
1744
- \u7EDD\u5BF9\u4E0D\u5141\u8BB8\u5728\u56DE\u590D\u4E2D\u58F0\u79F0"\u6587\u4EF6\u5DF2\u751F\u6210"\u3001"\u6587\u4EF6\u5DF2\u4FDD\u5B58"\u3001"\u5DF2\u5199\u5165"\u7B49\uFF0C\u800C\u4E0D\u5B9E\u9645\u8C03\u7528 write_file \u5DE5\u5177\u3002
1745
- \u5982\u679C\u7528\u6237\u8981\u6C42\u521B\u5EFA/\u5199\u5165\u6587\u4EF6\uFF0C\u4F60\u5FC5\u987B\u901A\u8FC7 function calling \u8C03\u7528 write_file \u5DE5\u5177\uFF0C\u7CFB\u7EDF\u624D\u4F1A\u6267\u884C\u5199\u5165\u3002
1746
- \u4EC5\u5728\u6587\u672C\u4E2D\u63CF\u8FF0\u6587\u4EF6\u5185\u5BB9\u800C\u4E0D\u8C03\u7528\u5DE5\u5177 = \u6587\u4EF6\u4E0D\u5B58\u5728 = \u4EFB\u52A1\u5931\u8D25\u3002`;
1623
+ \u6587\u672C\u4E2D\u7684 XML \u6807\u7B7E\u4E0D\u4F1A\u88AB\u7CFB\u7EDF\u6267\u884C\uFF0C\u4F1A\u5BFC\u81F4\u6587\u4EF6\u672A\u5199\u5165\u3001\u547D\u4EE4\u672A\u8FD0\u884C\uFF0C\u4EFB\u52A1\u5F7B\u5E95\u5931\u8D25\u3002`;
1747
1624
  var KimiProvider = class extends OpenAICompatibleProvider {
1748
1625
  defaultBaseUrl = "https://api.moonshot.ai/v1";
1749
- // 禁用流式工具调用:Kimi 的 XML 伪调用检测(方案 A)和虚假声明检测(方案 C)需要完整响应
1626
+ // 禁用流式工具调用:Kimi 的 XML 伪调用检测(方案 A)需要完整响应
1750
1627
  enableStreamingToolCalls = false;
1751
1628
  info = {
1752
1629
  id: "kimi",
@@ -1810,29 +1687,21 @@ var KimiProvider = class extends OpenAICompatibleProvider {
1810
1687
  ]
1811
1688
  };
1812
1689
  /**
1813
- * 覆写 chatWithTools,叠加三道防线:
1814
- *
1815
- * 方案 B(预防):在 system prompt 末尾追加工具调用规范提示,
1816
- * 指示 Kimi 必须使用 API function calling,禁止输出 XML 伪工具调用,
1817
- * 禁止虚假完成声明。
1690
+ * 覆写 chatWithTools — 仅保留 Kimi 专属的 Plan A(XML 伪调用解析)。
1691
+ * Plan B(system prompt 规范提示)和 Plan C(虚假声明检测 + 重试)
1692
+ * 已由 REPL handleChatWithTools() 统一处理。
1818
1693
  *
1819
- * 方案 A(兜底 1):若 Kimi 在文本中输出了 XML 格式工具调用,
1694
+ * Plan A(Kimi 专属兜底):若 Kimi 在文本中输出了 XML 格式工具调用,
1820
1695
  * 自动检测并解析,转换为真实 ToolCall 对象送入执行器。
1821
- *
1822
- * 方案 C(兜底 2):若 Kimi 在文本中声称已完成文件操作但未调用任何工具,
1823
- * 自动检测并注入纠正消息,重新请求一次,强制 Kimi 实际调用工具。
1824
1696
  */
1825
1697
  async chatWithTools(request, tools) {
1826
1698
  const enhancedRequest = {
1827
1699
  ...request,
1828
- systemPrompt: (request.systemPrompt ?? "") + KIMI_TOOL_CALL_REMINDER
1700
+ systemPrompt: (request.systemPrompt ?? "") + KIMI_XML_REMINDER
1829
1701
  };
1830
1702
  const result = await super.chatWithTools(enhancedRequest, tools);
1831
1703
  if ("content" in result && result.content) {
1832
- const hasWriteTools = tools.some((t) => t.name === "write_file" || t.name === "edit_file");
1833
- const alreadyWrote = this.hadPreviousWriteToolCalls(enhancedRequest);
1834
- const isHallucinated = hasWriteTools && !alreadyWrote && this.detectsHallucinatedFileOp(result.content);
1835
- if (isHallucinated) {
1704
+ if (detectsHallucinatedFileOp(result.content)) {
1836
1705
  const xmlToolCalls = this.parseXmlToolCalls(result.content, tools);
1837
1706
  if (xmlToolCalls.length > 0) {
1838
1707
  process.stderr.write(
@@ -1841,46 +1710,6 @@ var KimiProvider = class extends OpenAICompatibleProvider {
1841
1710
  );
1842
1711
  return { toolCalls: xmlToolCalls, usage: result.usage };
1843
1712
  }
1844
- process.stderr.write(
1845
- `[kimi] \u26A0 \u68C0\u6D4B\u5230\u865A\u5047\u5B8C\u6210\u58F0\u660E\uFF08AI \u58F0\u79F0\u5DF2\u5199\u5165\u6587\u4EF6\u4F46\u672A\u8C03\u7528\u5DE5\u5177\uFF09\uFF0C\u6B63\u5728\u5F3A\u5236\u91CD\u65B0\u8BF7\u6C42...
1846
- `
1847
- );
1848
- const existingExtra = enhancedRequest._extraMessages ?? [];
1849
- const correctionRequest = {
1850
- ...enhancedRequest,
1851
- _extraMessages: [
1852
- ...existingExtra,
1853
- { role: "assistant", content: result.content },
1854
- {
1855
- role: "user",
1856
- content: "\u4F60\u521A\u624D\u6CA1\u6709\u5B9E\u9645\u8C03\u7528 write_file \u5DE5\u5177\uFF0C\u6587\u4EF6\u5E76\u672A\u88AB\u521B\u5EFA\uFF01\u8BF7\u7ACB\u5373\u4F7F\u7528 write_file \u5DE5\u5177\u7684 function calling API \u6267\u884C\u5B9E\u9645\u7684\u6587\u4EF6\u5199\u5165\u64CD\u4F5C\u3002\u4E0D\u8981\u518D\u7528\u6587\u5B57\u63CF\u8FF0\u6587\u4EF6\u5185\u5BB9\uFF0C\u5FC5\u987B\u901A\u8FC7 API \u7684 tool_calls \u673A\u5236\u8C03\u7528 write_file\u3002"
1857
- }
1858
- ]
1859
- };
1860
- const retryResult = await super.chatWithTools(correctionRequest, tools);
1861
- if ("content" in retryResult && retryResult.content) {
1862
- const retryXml = this.parseXmlToolCalls(retryResult.content, tools);
1863
- if (retryXml.length > 0) {
1864
- process.stderr.write(
1865
- `[kimi] \u26A0 \u91CD\u8BD5\u540E\u68C0\u6D4B\u5230 ${retryXml.length} \u4E2A XML \u4F2A\u5DE5\u5177\u8C03\u7528\uFF0C\u5DF2\u8F6C\u6362\u6267\u884C\u3002
1866
- `
1867
- );
1868
- const mergedUsage = this.mergeUsage(result.usage, retryResult.usage);
1869
- return { toolCalls: retryXml, usage: mergedUsage };
1870
- }
1871
- }
1872
- if ("toolCalls" in retryResult) {
1873
- retryResult.usage = this.mergeUsage(result.usage, retryResult.usage);
1874
- } else if ("content" in retryResult) {
1875
- retryResult.usage = this.mergeUsage(result.usage, retryResult.usage);
1876
- if (this.detectsHallucinatedFileOp(retryResult.content)) {
1877
- process.stderr.write(
1878
- `[kimi] \u26A0 \u91CD\u8BD5\u540E\u4ECD\u68C0\u6D4B\u5230\u865A\u5047\u5B8C\u6210\u58F0\u660E\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5\u6587\u4EF6\u662F\u5426\u5DF2\u521B\u5EFA\u3002
1879
- `
1880
- );
1881
- }
1882
- }
1883
- return retryResult;
1884
1713
  }
1885
1714
  }
1886
1715
  return result;
@@ -4768,7 +4597,7 @@ ${hint}` : "")
4768
4597
  description: "Run project tests and show structured report",
4769
4598
  usage: "/test [command|filter]",
4770
4599
  async execute(args, _ctx) {
4771
- const { executeTests } = await import("./run-tests-VBHQ2ECR.js");
4600
+ const { executeTests } = await import("./run-tests-YGTHER7T.js");
4772
4601
  const argStr = args.join(" ").trim();
4773
4602
  let testArgs = {};
4774
4603
  if (argStr) {
@@ -7111,11 +6940,11 @@ function formatResults(query, data, requested) {
7111
6940
  }
7112
6941
 
7113
6942
  // src/tools/truncate.ts
7114
- var MAX_TOOL_OUTPUT_CHARS2 = 12e3;
6943
+ var MAX_TOOL_OUTPUT_CHARS = 12e3;
7115
6944
  function truncateOutput(content, toolName) {
7116
- if (content.length <= MAX_TOOL_OUTPUT_CHARS2) return content;
7117
- const keepHead = Math.floor(MAX_TOOL_OUTPUT_CHARS2 * 0.7);
7118
- const keepTail = Math.floor(MAX_TOOL_OUTPUT_CHARS2 * 0.2);
6945
+ if (content.length <= MAX_TOOL_OUTPUT_CHARS) return content;
6946
+ const keepHead = Math.floor(MAX_TOOL_OUTPUT_CHARS * 0.7);
6947
+ const keepTail = Math.floor(MAX_TOOL_OUTPUT_CHARS * 0.2);
7119
6948
  const omitted = content.length - keepHead - keepTail;
7120
6949
  const lines = content.split("\n").length;
7121
6950
  const head = content.slice(0, keepHead);
@@ -10515,7 +10344,7 @@ Session '${this.resumeSessionId}' not found.
10515
10344
  }
10516
10345
  const apiMessages = [...messages];
10517
10346
  const extraMessages = [];
10518
- const systemPrompt = this.buildCurrentSystemPrompt();
10347
+ const systemPrompt = (this.buildCurrentSystemPrompt() ?? "") + TOOL_CALL_REMINDER;
10519
10348
  const modelParams = this.getModelParams();
10520
10349
  const useStreaming = this.config.get("ui").streaming;
10521
10350
  const spinner = this.renderer.showSpinner("Thinking...");
@@ -10571,6 +10400,24 @@ Session '${this.resumeSessionId}' not found.
10571
10400
  roundUsage.outputTokens += result.usage.outputTokens;
10572
10401
  }
10573
10402
  if ("content" in result) {
10403
+ const hasWriteTools = toolDefs.some((t) => t.name === "write_file" || t.name === "edit_file");
10404
+ const alreadyWrote = hadPreviousWriteToolCalls(extraMessages);
10405
+ if (hasWriteTools && !alreadyWrote && result.content && detectsHallucinatedFileOp(result.content) && round < MAX_TOOL_ROUNDS - 1) {
10406
+ const providerName = this.currentProvider;
10407
+ process.stderr.write(
10408
+ `[${providerName}] \u26A0 \u68C0\u6D4B\u5230\u865A\u5047\u5B8C\u6210\u58F0\u660E\uFF08AI \u58F0\u79F0\u5DF2\u5199\u5165\u6587\u4EF6\u4F46\u672A\u8C03\u7528\u5DE5\u5177\uFF09\uFF0C\u6B63\u5728\u5F3A\u5236\u91CD\u65B0\u8BF7\u6C42...
10409
+ `
10410
+ );
10411
+ if (alreadyRendered) {
10412
+ process.stdout.write("\n");
10413
+ }
10414
+ extraMessages.push(
10415
+ { role: "assistant", content: result.content },
10416
+ { role: "user", content: HALLUCINATION_CORRECTION_MESSAGE }
10417
+ );
10418
+ spinner.start(`Retrying... (round ${round + 2}/${MAX_TOOL_ROUNDS})`);
10419
+ continue;
10420
+ }
10574
10421
  spinner.stop();
10575
10422
  const finalContent = result.content;
10576
10423
  if (!alreadyRendered) {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-S2KBV42Z.js";
5
+ } from "./chunk-LPE66KZ3.js";
6
6
  export {
7
7
  executeTests,
8
8
  runTestsTool
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.1.60",
3
+ "version": "0.1.62",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",