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.
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-
|
|
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
|
|
1618
|
+
var KIMI_XML_REMINDER = `
|
|
1737
1619
|
|
|
1738
|
-
\u3010\u26A0\uFE0F \u5DE5\u5177\u8C03\u7528\
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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 ?? "") +
|
|
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
|
-
|
|
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-
|
|
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
|
|
6943
|
+
var MAX_TOOL_OUTPUT_CHARS = 12e3;
|
|
7115
6944
|
function truncateOutput(content, toolName) {
|
|
7116
|
-
if (content.length <=
|
|
7117
|
-
const keepHead = Math.floor(
|
|
7118
|
-
const keepTail = Math.floor(
|
|
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) {
|