jinzd-ai-cli 0.1.52 → 0.1.54
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
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
30
30
|
VERSION,
|
|
31
31
|
runTestsTool
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-AF33GPHX.js";
|
|
33
33
|
|
|
34
34
|
// src/index.ts
|
|
35
35
|
import { program } from "commander";
|
|
@@ -632,6 +632,111 @@ var ClaudeProvider = class extends BaseProvider {
|
|
|
632
632
|
throw this.wrapError(err);
|
|
633
633
|
}
|
|
634
634
|
}
|
|
635
|
+
/**
|
|
636
|
+
* 流式工具调用:文本/thinking 实时输出、工具名称/参数逐块发射。
|
|
637
|
+
* 同时收集原始 content blocks 供 buildToolResultMessages 使用。
|
|
638
|
+
*/
|
|
639
|
+
async *chatWithToolsStream(request, tools) {
|
|
640
|
+
const anthropicTools = tools.map((t) => ({
|
|
641
|
+
name: t.name,
|
|
642
|
+
description: t.description,
|
|
643
|
+
input_schema: {
|
|
644
|
+
type: "object",
|
|
645
|
+
properties: Object.fromEntries(
|
|
646
|
+
Object.entries(t.parameters).map(([key, schema]) => [
|
|
647
|
+
key,
|
|
648
|
+
schemaToJsonSchema(schema)
|
|
649
|
+
])
|
|
650
|
+
),
|
|
651
|
+
required: Object.entries(t.parameters).filter(([, s]) => s.required).map(([k]) => k)
|
|
652
|
+
}
|
|
653
|
+
}));
|
|
654
|
+
const baseMessages = request.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: this.contentToClaudeParts(m.content) }));
|
|
655
|
+
const extraMessages = request._extraMessages ?? [];
|
|
656
|
+
const allMessages = [...baseMessages, ...extraMessages];
|
|
657
|
+
const { thinking, temperature } = this.buildThinkingParams(request);
|
|
658
|
+
try {
|
|
659
|
+
const stream = this.client.messages.stream({
|
|
660
|
+
model: request.model,
|
|
661
|
+
messages: allMessages,
|
|
662
|
+
tools: anthropicTools,
|
|
663
|
+
system: request.systemPrompt,
|
|
664
|
+
max_tokens: request.maxTokens ?? 8192,
|
|
665
|
+
temperature,
|
|
666
|
+
thinking
|
|
667
|
+
}, { signal: request.signal });
|
|
668
|
+
let currentBlockType = null;
|
|
669
|
+
let currentToolIndex = 0;
|
|
670
|
+
const rawContentBlocks = [];
|
|
671
|
+
let currentBlockData = {};
|
|
672
|
+
for await (const event of stream) {
|
|
673
|
+
if (event.type === "content_block_start") {
|
|
674
|
+
const block = event.content_block;
|
|
675
|
+
currentBlockType = block.type;
|
|
676
|
+
currentBlockData = { type: block.type };
|
|
677
|
+
if (block.type === "thinking") {
|
|
678
|
+
yield { type: "thinking_start" };
|
|
679
|
+
currentBlockData = { type: "thinking", thinking: "", signature: "" };
|
|
680
|
+
} else if (block.type === "text") {
|
|
681
|
+
currentBlockData = { type: "text", text: "" };
|
|
682
|
+
} else if (block.type === "tool_use") {
|
|
683
|
+
const tuBlock = block;
|
|
684
|
+
yield {
|
|
685
|
+
type: "tool_call_start",
|
|
686
|
+
index: currentToolIndex,
|
|
687
|
+
id: tuBlock.id,
|
|
688
|
+
name: tuBlock.name
|
|
689
|
+
};
|
|
690
|
+
currentBlockData = { type: "tool_use", id: tuBlock.id, name: tuBlock.name, input: {} };
|
|
691
|
+
} else if (block.type === "redacted_thinking") {
|
|
692
|
+
currentBlockData = { type: "redacted_thinking", data: block.data };
|
|
693
|
+
}
|
|
694
|
+
} else if (event.type === "content_block_delta") {
|
|
695
|
+
if (event.delta.type === "text_delta") {
|
|
696
|
+
yield { type: "text_delta", delta: event.delta.text };
|
|
697
|
+
currentBlockData.text += event.delta.text;
|
|
698
|
+
} else if (event.delta.type === "thinking_delta") {
|
|
699
|
+
const thinkingDelta = event.delta.thinking;
|
|
700
|
+
yield { type: "thinking_delta", delta: thinkingDelta };
|
|
701
|
+
currentBlockData.thinking += thinkingDelta;
|
|
702
|
+
} else if (event.delta.type === "input_json_delta") {
|
|
703
|
+
const jsonDelta = event.delta.partial_json;
|
|
704
|
+
yield {
|
|
705
|
+
type: "tool_call_delta",
|
|
706
|
+
index: currentToolIndex,
|
|
707
|
+
argumentsDelta: jsonDelta
|
|
708
|
+
};
|
|
709
|
+
} else if (event.delta.type === "signature_delta") {
|
|
710
|
+
currentBlockData.signature += event.delta.signature ?? "";
|
|
711
|
+
}
|
|
712
|
+
} else if (event.type === "content_block_stop") {
|
|
713
|
+
if (currentBlockType === "thinking") {
|
|
714
|
+
yield { type: "thinking_end" };
|
|
715
|
+
} else if (currentBlockType === "tool_use") {
|
|
716
|
+
yield { type: "tool_call_end", index: currentToolIndex };
|
|
717
|
+
currentToolIndex++;
|
|
718
|
+
}
|
|
719
|
+
rawContentBlocks.push(currentBlockData);
|
|
720
|
+
currentBlockType = null;
|
|
721
|
+
currentBlockData = {};
|
|
722
|
+
} else if (event.type === "message_delta") {
|
|
723
|
+
const usage = event.usage;
|
|
724
|
+
if (usage) {
|
|
725
|
+
yield {
|
|
726
|
+
type: "done",
|
|
727
|
+
usage: {
|
|
728
|
+
inputTokens: usage.input_tokens ?? 0,
|
|
729
|
+
outputTokens: usage.output_tokens ?? 0
|
|
730
|
+
},
|
|
731
|
+
rawContent: rawContentBlocks
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
} catch (err) {
|
|
737
|
+
throw this.wrapError(err);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
635
740
|
buildToolResultMessages(assistantToolCalls, results) {
|
|
636
741
|
const rawContent = assistantToolCalls._rawContent;
|
|
637
742
|
let assistantContent;
|
|
@@ -989,6 +1094,8 @@ var OpenAICompatibleProvider = class extends BaseProvider {
|
|
|
989
1094
|
client;
|
|
990
1095
|
defaultTimeout = 6e4;
|
|
991
1096
|
// ms
|
|
1097
|
+
/** 子类设为 false 可禁用流式工具调用(虚假声明检测需要完整响应) */
|
|
1098
|
+
enableStreamingToolCalls = true;
|
|
992
1099
|
async initialize(apiKey, options) {
|
|
993
1100
|
if (options?.timeout !== void 0) {
|
|
994
1101
|
this.defaultTimeout = options.timeout;
|
|
@@ -1159,6 +1266,122 @@ var OpenAICompatibleProvider = class extends BaseProvider {
|
|
|
1159
1266
|
throw this.wrapError(err);
|
|
1160
1267
|
}
|
|
1161
1268
|
}
|
|
1269
|
+
/**
|
|
1270
|
+
* 流式工具调用:文本内容实时输出、工具名称/参数逐块发射。
|
|
1271
|
+
* 子类(DeepSeek / Kimi)因虚假声明检测需要完整响应,故不继承此方法。
|
|
1272
|
+
*/
|
|
1273
|
+
async *chatWithToolsStream(request, tools) {
|
|
1274
|
+
if (!this.enableStreamingToolCalls) {
|
|
1275
|
+
const result = await this.chatWithTools(request, tools);
|
|
1276
|
+
if ("toolCalls" in result) {
|
|
1277
|
+
for (let i = 0; i < result.toolCalls.length; i++) {
|
|
1278
|
+
const tc = result.toolCalls[i];
|
|
1279
|
+
yield { type: "tool_call_start", index: i, id: tc.id, name: tc.name };
|
|
1280
|
+
yield { type: "tool_call_delta", index: i, argumentsDelta: JSON.stringify(tc.arguments) };
|
|
1281
|
+
yield { type: "tool_call_end", index: i };
|
|
1282
|
+
}
|
|
1283
|
+
} else {
|
|
1284
|
+
yield { type: "text_delta", delta: result.content };
|
|
1285
|
+
}
|
|
1286
|
+
yield { type: "done", usage: result.usage };
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
const openaiTools = tools.map((t) => ({
|
|
1290
|
+
type: "function",
|
|
1291
|
+
function: {
|
|
1292
|
+
name: t.name,
|
|
1293
|
+
description: t.description,
|
|
1294
|
+
parameters: {
|
|
1295
|
+
type: "object",
|
|
1296
|
+
properties: Object.fromEntries(
|
|
1297
|
+
Object.entries(t.parameters).map(([key, schema]) => [
|
|
1298
|
+
key,
|
|
1299
|
+
schemaToJsonSchema(schema)
|
|
1300
|
+
])
|
|
1301
|
+
),
|
|
1302
|
+
required: Object.entries(t.parameters).filter(([, s]) => s.required).map(([k]) => k)
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}));
|
|
1306
|
+
const baseMessages = this.buildMessages(request);
|
|
1307
|
+
const extraMessages = request._extraMessages ?? [];
|
|
1308
|
+
const allMessages = [...baseMessages, ...extraMessages];
|
|
1309
|
+
try {
|
|
1310
|
+
const stream = await this.client.chat.completions.create({
|
|
1311
|
+
model: request.model,
|
|
1312
|
+
messages: allMessages,
|
|
1313
|
+
tools: openaiTools,
|
|
1314
|
+
tool_choice: "auto",
|
|
1315
|
+
temperature: request.temperature,
|
|
1316
|
+
max_tokens: request.maxTokens,
|
|
1317
|
+
stream: true,
|
|
1318
|
+
stream_options: { include_usage: true },
|
|
1319
|
+
...request.thinking ? { thinking: { type: "enabled" } } : {}
|
|
1320
|
+
}, {
|
|
1321
|
+
timeout: request.timeout ?? this.defaultTimeout,
|
|
1322
|
+
signal: request.signal
|
|
1323
|
+
});
|
|
1324
|
+
const toolCallAccumulators = /* @__PURE__ */ new Map();
|
|
1325
|
+
let toolCallsEnded = false;
|
|
1326
|
+
for await (const chunk of stream) {
|
|
1327
|
+
const choice = chunk.choices[0];
|
|
1328
|
+
if (!choice && chunk.usage) {
|
|
1329
|
+
if (!toolCallsEnded && toolCallAccumulators.size > 0) {
|
|
1330
|
+
for (const [idx] of toolCallAccumulators) {
|
|
1331
|
+
yield { type: "tool_call_end", index: idx };
|
|
1332
|
+
}
|
|
1333
|
+
toolCallsEnded = true;
|
|
1334
|
+
}
|
|
1335
|
+
yield {
|
|
1336
|
+
type: "done",
|
|
1337
|
+
usage: {
|
|
1338
|
+
inputTokens: chunk.usage.prompt_tokens,
|
|
1339
|
+
outputTokens: chunk.usage.completion_tokens
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
continue;
|
|
1343
|
+
}
|
|
1344
|
+
if (!choice) continue;
|
|
1345
|
+
const delta = choice.delta;
|
|
1346
|
+
if (delta?.content) {
|
|
1347
|
+
yield { type: "text_delta", delta: delta.content };
|
|
1348
|
+
}
|
|
1349
|
+
if (delta?.tool_calls) {
|
|
1350
|
+
for (const tc of delta.tool_calls) {
|
|
1351
|
+
const idx = tc.index;
|
|
1352
|
+
if (tc.id && tc.function?.name) {
|
|
1353
|
+
toolCallAccumulators.set(idx, {
|
|
1354
|
+
id: tc.id,
|
|
1355
|
+
name: tc.function.name,
|
|
1356
|
+
arguments: tc.function.arguments ?? ""
|
|
1357
|
+
});
|
|
1358
|
+
yield { type: "tool_call_start", index: idx, id: tc.id, name: tc.function.name };
|
|
1359
|
+
} else if (tc.function?.arguments) {
|
|
1360
|
+
const acc = toolCallAccumulators.get(idx);
|
|
1361
|
+
if (acc) {
|
|
1362
|
+
acc.arguments += tc.function.arguments;
|
|
1363
|
+
yield { type: "tool_call_delta", index: idx, argumentsDelta: tc.function.arguments };
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
if (choice.finish_reason && !toolCallsEnded && toolCallAccumulators.size > 0) {
|
|
1369
|
+
for (const [idx] of toolCallAccumulators) {
|
|
1370
|
+
yield { type: "tool_call_end", index: idx };
|
|
1371
|
+
}
|
|
1372
|
+
toolCallsEnded = true;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
if (!toolCallsEnded && toolCallAccumulators.size > 0) {
|
|
1376
|
+
for (const [idx] of toolCallAccumulators) {
|
|
1377
|
+
yield { type: "tool_call_end", index: idx };
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
yield { type: "done" };
|
|
1381
|
+
} catch (err) {
|
|
1382
|
+
throw this.wrapError(err);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1162
1385
|
/**
|
|
1163
1386
|
* 将工具结果作为 tool_call 消息追加,供下一轮使用
|
|
1164
1387
|
*/
|
|
@@ -1206,6 +1429,23 @@ var OpenAICompatibleProvider = class extends BaseProvider {
|
|
|
1206
1429
|
detectsHallucinatedFileOp(content) {
|
|
1207
1430
|
return HALLUCINATION_PATTERNS.some((pattern) => pattern.test(content));
|
|
1208
1431
|
}
|
|
1432
|
+
/**
|
|
1433
|
+
* 检查 _extraMessages 中是否已存在 write_file / edit_file 的成功调用记录。
|
|
1434
|
+
* 若前面的 agentic 轮次已实际调用了写文件工具,则最终文本总结中提到「已保存」
|
|
1435
|
+
* 是合理的事实陈述,不应被判定为虚假声明。
|
|
1436
|
+
* 用于避免误报(false positive)。
|
|
1437
|
+
*/
|
|
1438
|
+
hadPreviousWriteToolCalls(request) {
|
|
1439
|
+
const extraMessages = request._extraMessages ?? [];
|
|
1440
|
+
return extraMessages.some((msg) => {
|
|
1441
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls)) return false;
|
|
1442
|
+
return msg.tool_calls.some((tc) => {
|
|
1443
|
+
const fn = tc.function;
|
|
1444
|
+
const name = fn?.name ?? "";
|
|
1445
|
+
return name === "write_file" || name === "edit_file";
|
|
1446
|
+
});
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1209
1449
|
/**
|
|
1210
1450
|
* 合并两次 API 调用的 token 用量(虚假声明重试时使用)
|
|
1211
1451
|
*/
|
|
@@ -1240,6 +1480,8 @@ var DEEPSEEK_TOOL_CALL_REMINDER = `
|
|
|
1240
1480
|
\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`;
|
|
1241
1481
|
var DeepSeekProvider = class extends OpenAICompatibleProvider {
|
|
1242
1482
|
defaultBaseUrl = "https://api.deepseek.com/v1";
|
|
1483
|
+
// 禁用流式工具调用:DeepSeek 的虚假声明检测(方案 C)需要完整响应
|
|
1484
|
+
enableStreamingToolCalls = false;
|
|
1243
1485
|
info = {
|
|
1244
1486
|
id: "deepseek",
|
|
1245
1487
|
displayName: "DeepSeek",
|
|
@@ -1279,7 +1521,8 @@ var DeepSeekProvider = class extends OpenAICompatibleProvider {
|
|
|
1279
1521
|
const result = await super.chatWithTools(enhancedRequest, tools);
|
|
1280
1522
|
if ("content" in result && result.content) {
|
|
1281
1523
|
const hasWriteTools = tools.some((t) => t.name === "write_file" || t.name === "edit_file");
|
|
1282
|
-
|
|
1524
|
+
const alreadyWrote = this.hadPreviousWriteToolCalls(enhancedRequest);
|
|
1525
|
+
if (hasWriteTools && !alreadyWrote && this.detectsHallucinatedFileOp(result.content)) {
|
|
1283
1526
|
process.stderr.write(
|
|
1284
1527
|
`[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...
|
|
1285
1528
|
`
|
|
@@ -1397,6 +1640,8 @@ var KIMI_TOOL_CALL_REMINDER = `
|
|
|
1397
1640
|
\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`;
|
|
1398
1641
|
var KimiProvider = class extends OpenAICompatibleProvider {
|
|
1399
1642
|
defaultBaseUrl = "https://api.moonshot.ai/v1";
|
|
1643
|
+
// 禁用流式工具调用:Kimi 的 XML 伪调用检测(方案 A)和虚假声明检测(方案 C)需要完整响应
|
|
1644
|
+
enableStreamingToolCalls = false;
|
|
1400
1645
|
info = {
|
|
1401
1646
|
id: "kimi",
|
|
1402
1647
|
displayName: "Kimi (Moonshot AI)",
|
|
@@ -1487,7 +1732,8 @@ var KimiProvider = class extends OpenAICompatibleProvider {
|
|
|
1487
1732
|
return { toolCalls: xmlToolCalls, usage: result.usage };
|
|
1488
1733
|
}
|
|
1489
1734
|
const hasWriteTools = tools.some((t) => t.name === "write_file" || t.name === "edit_file");
|
|
1490
|
-
|
|
1735
|
+
const alreadyWrote = this.hadPreviousWriteToolCalls(enhancedRequest);
|
|
1736
|
+
if (hasWriteTools && !alreadyWrote && this.detectsHallucinatedFileOp(result.content)) {
|
|
1491
1737
|
process.stderr.write(
|
|
1492
1738
|
`[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...
|
|
1493
1739
|
`
|
|
@@ -2383,6 +2629,7 @@ var Renderer = class {
|
|
|
2383
2629
|
console.log(feat("\u5DE5\u5177\u8C03\u7528\u6700\u7EC8\u56DE\u7B54\u6D41\u5F0F\u5316\uFF1A\u6A21\u62DF\u6253\u5B57\u673A\u6548\u679C\u9010\u5757\u8F93\u51FA\uFF0C\u96F6\u989D\u5916 API \u8C03\u7528\uFF0C\u652F\u6301 Escape \u4E2D\u65AD"));
|
|
2384
2630
|
console.log(feat("/diff \u547D\u4EE4\uFF1A\u663E\u793A\u5F53\u524D session \u5185\u6240\u6709\u6587\u4EF6\u4FEE\u6539\u7684\u6C47\u603B diff\uFF08\u5408\u5E76\u540C\u6587\u4EF6\u591A\u6B21\u4FEE\u6539\uFF09"));
|
|
2385
2631
|
console.log(feat("/fork \u5BF9\u8BDD\u5206\u652F\uFF1A\u4ECE\u5F53\u524D\u4F4D\u7F6E\u6216\u6307\u5B9A checkpoint \u5206\u53C9\u4E3A\u65B0 session\uFF0C\u63A2\u7D22\u4E0D\u540C\u65B9\u6848"));
|
|
2632
|
+
console.log(feat("Streaming Tool Use\uFF1Aagentic \u5FAA\u73AF\u4E2D\u6587\u672C\u5B9E\u65F6\u6D41\u51FA + \u5DE5\u5177\u540D\u79F0\u5373\u65F6\u663E\u793A\uFF08OpenAI/Claude\uFF09"));
|
|
2386
2633
|
console.log();
|
|
2387
2634
|
}
|
|
2388
2635
|
printPrompt(provider, _model) {
|
|
@@ -4344,7 +4591,7 @@ ${hint}` : "")
|
|
|
4344
4591
|
description: "Run project tests and show structured report",
|
|
4345
4592
|
usage: "/test [command|filter]",
|
|
4346
4593
|
async execute(args, _ctx) {
|
|
4347
|
-
const { executeTests } = await import("./run-tests-
|
|
4594
|
+
const { executeTests } = await import("./run-tests-BTBQNJ44.js");
|
|
4348
4595
|
const argStr = args.join(" ").trim();
|
|
4349
4596
|
let testArgs = {};
|
|
4350
4597
|
if (argStr) {
|
|
@@ -9971,6 +10218,107 @@ Session '${this.resumeSessionId}' not found.
|
|
|
9971
10218
|
}
|
|
9972
10219
|
await this.checkContextPressure();
|
|
9973
10220
|
}
|
|
10221
|
+
/**
|
|
10222
|
+
* 消费流式工具调用事件生成器,实时渲染文本内容和工具名称,
|
|
10223
|
+
* 累积完整工具调用参数后返回结构化结果。
|
|
10224
|
+
*/
|
|
10225
|
+
async consumeToolStream(stream, spinner) {
|
|
10226
|
+
const textParts = [];
|
|
10227
|
+
const toolCallAccumulators = /* @__PURE__ */ new Map();
|
|
10228
|
+
let usage;
|
|
10229
|
+
let rawContent;
|
|
10230
|
+
let spinnerStopped = false;
|
|
10231
|
+
const stopSpinner = () => {
|
|
10232
|
+
if (!spinnerStopped) {
|
|
10233
|
+
spinner.stop();
|
|
10234
|
+
spinnerStopped = true;
|
|
10235
|
+
}
|
|
10236
|
+
};
|
|
10237
|
+
try {
|
|
10238
|
+
for await (const event of stream) {
|
|
10239
|
+
switch (event.type) {
|
|
10240
|
+
case "text_delta":
|
|
10241
|
+
stopSpinner();
|
|
10242
|
+
process.stdout.write(event.delta);
|
|
10243
|
+
textParts.push(event.delta);
|
|
10244
|
+
break;
|
|
10245
|
+
case "thinking_start":
|
|
10246
|
+
stopSpinner();
|
|
10247
|
+
process.stdout.write(theme.dim("<think>"));
|
|
10248
|
+
break;
|
|
10249
|
+
case "thinking_delta":
|
|
10250
|
+
break;
|
|
10251
|
+
case "thinking_end":
|
|
10252
|
+
process.stdout.write(theme.dim("</think>"));
|
|
10253
|
+
break;
|
|
10254
|
+
case "tool_call_start":
|
|
10255
|
+
stopSpinner();
|
|
10256
|
+
process.stdout.write(
|
|
10257
|
+
theme.dim(`
|
|
10258
|
+
\u2699 Streaming: `) + theme.toolCall(event.name) + theme.dim("...\n")
|
|
10259
|
+
);
|
|
10260
|
+
toolCallAccumulators.set(event.index, {
|
|
10261
|
+
id: event.id,
|
|
10262
|
+
name: event.name,
|
|
10263
|
+
arguments: ""
|
|
10264
|
+
});
|
|
10265
|
+
break;
|
|
10266
|
+
case "tool_call_delta": {
|
|
10267
|
+
const acc = toolCallAccumulators.get(event.index);
|
|
10268
|
+
if (acc) {
|
|
10269
|
+
acc.arguments += event.argumentsDelta;
|
|
10270
|
+
}
|
|
10271
|
+
break;
|
|
10272
|
+
}
|
|
10273
|
+
case "tool_call_end":
|
|
10274
|
+
break;
|
|
10275
|
+
case "done":
|
|
10276
|
+
if (event.usage) usage = event.usage;
|
|
10277
|
+
if (event.rawContent) rawContent = event.rawContent;
|
|
10278
|
+
break;
|
|
10279
|
+
}
|
|
10280
|
+
}
|
|
10281
|
+
} catch (err) {
|
|
10282
|
+
if (err instanceof Error && (err.name === "AbortError" || err.message.includes("aborted"))) {
|
|
10283
|
+
stopSpinner();
|
|
10284
|
+
process.stdout.write(theme.dim("\n[interrupted]\n"));
|
|
10285
|
+
return {
|
|
10286
|
+
textContent: textParts.join(""),
|
|
10287
|
+
toolCalls: [],
|
|
10288
|
+
usage,
|
|
10289
|
+
rawContent
|
|
10290
|
+
};
|
|
10291
|
+
}
|
|
10292
|
+
throw err;
|
|
10293
|
+
}
|
|
10294
|
+
const toolCalls = [];
|
|
10295
|
+
for (const [, acc] of toolCallAccumulators) {
|
|
10296
|
+
let parsedArgs;
|
|
10297
|
+
try {
|
|
10298
|
+
parsedArgs = JSON.parse(acc.arguments || "{}");
|
|
10299
|
+
} catch {
|
|
10300
|
+
const truncated = acc.arguments.trimEnd();
|
|
10301
|
+
const lastComma = truncated.lastIndexOf(",");
|
|
10302
|
+
const fixed = lastComma > 0 ? truncated.slice(0, lastComma) + "}" : truncated.slice(0, truncated.indexOf("{") + 1) + "}";
|
|
10303
|
+
try {
|
|
10304
|
+
parsedArgs = JSON.parse(fixed);
|
|
10305
|
+
} catch {
|
|
10306
|
+
parsedArgs = {};
|
|
10307
|
+
}
|
|
10308
|
+
}
|
|
10309
|
+
toolCalls.push({
|
|
10310
|
+
id: acc.id,
|
|
10311
|
+
name: acc.name,
|
|
10312
|
+
arguments: parsedArgs
|
|
10313
|
+
});
|
|
10314
|
+
}
|
|
10315
|
+
return {
|
|
10316
|
+
textContent: textParts.join(""),
|
|
10317
|
+
toolCalls,
|
|
10318
|
+
usage,
|
|
10319
|
+
rawContent
|
|
10320
|
+
};
|
|
10321
|
+
}
|
|
9974
10322
|
async handleChatWithTools(provider, messages) {
|
|
9975
10323
|
const session = this.sessions.current;
|
|
9976
10324
|
let toolDefs;
|
|
@@ -9997,24 +10345,48 @@ Session '${this.resumeSessionId}' not found.
|
|
|
9997
10345
|
const useStreaming = this.config.get("ui").streaming;
|
|
9998
10346
|
const spinner = this.renderer.showSpinner("Thinking...");
|
|
9999
10347
|
const roundUsage = { inputTokens: 0, outputTokens: 0 };
|
|
10348
|
+
const supportsStreamingTools = useStreaming && typeof provider.chatWithToolsStream === "function";
|
|
10000
10349
|
try {
|
|
10001
10350
|
for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
|
|
10002
10351
|
this.toolExecutor.setRoundInfo(round + 1, MAX_TOOL_ROUNDS);
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
}
|
|
10016
|
-
|
|
10017
|
-
)
|
|
10352
|
+
let result;
|
|
10353
|
+
let alreadyRendered = false;
|
|
10354
|
+
const chatRequest = {
|
|
10355
|
+
messages: apiMessages,
|
|
10356
|
+
model: this.currentModel,
|
|
10357
|
+
systemPrompt,
|
|
10358
|
+
stream: false,
|
|
10359
|
+
temperature: modelParams.temperature,
|
|
10360
|
+
maxTokens: modelParams.maxTokens,
|
|
10361
|
+
timeout: modelParams.timeout,
|
|
10362
|
+
thinking: modelParams.thinking,
|
|
10363
|
+
thinkingBudget: modelParams.thinkingBudget,
|
|
10364
|
+
...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
|
|
10365
|
+
};
|
|
10366
|
+
if (supportsStreamingTools) {
|
|
10367
|
+
const streamAc = this.setupStreamInterrupt();
|
|
10368
|
+
try {
|
|
10369
|
+
const streamGen = provider.chatWithToolsStream(
|
|
10370
|
+
{ ...chatRequest, signal: streamAc.signal },
|
|
10371
|
+
toolDefs
|
|
10372
|
+
);
|
|
10373
|
+
const streamResult = await this.consumeToolStream(streamGen, spinner);
|
|
10374
|
+
if (streamResult.toolCalls.length > 0) {
|
|
10375
|
+
const toolCalls = streamResult.toolCalls;
|
|
10376
|
+
if (streamResult.rawContent) {
|
|
10377
|
+
toolCalls._rawContent = streamResult.rawContent;
|
|
10378
|
+
}
|
|
10379
|
+
result = { toolCalls, usage: streamResult.usage };
|
|
10380
|
+
} else {
|
|
10381
|
+
result = { content: streamResult.textContent, usage: streamResult.usage };
|
|
10382
|
+
alreadyRendered = true;
|
|
10383
|
+
}
|
|
10384
|
+
} finally {
|
|
10385
|
+
this.teardownStreamInterrupt();
|
|
10386
|
+
}
|
|
10387
|
+
} else {
|
|
10388
|
+
result = await provider.chatWithTools(chatRequest, toolDefs);
|
|
10389
|
+
}
|
|
10018
10390
|
if (result.usage) {
|
|
10019
10391
|
roundUsage.inputTokens += result.usage.inputTokens;
|
|
10020
10392
|
roundUsage.outputTokens += result.usage.outputTokens;
|
|
@@ -10022,15 +10394,21 @@ Session '${this.resumeSessionId}' not found.
|
|
|
10022
10394
|
if ("content" in result) {
|
|
10023
10395
|
spinner.stop();
|
|
10024
10396
|
const finalContent = result.content;
|
|
10025
|
-
if (
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10397
|
+
if (!alreadyRendered) {
|
|
10398
|
+
if (useStreaming) {
|
|
10399
|
+
const streamAc = this.setupStreamInterrupt();
|
|
10400
|
+
try {
|
|
10401
|
+
await this.renderer.renderContentAsStream(finalContent, { signal: streamAc.signal });
|
|
10402
|
+
} finally {
|
|
10403
|
+
this.teardownStreamInterrupt();
|
|
10404
|
+
}
|
|
10405
|
+
} else {
|
|
10406
|
+
this.renderer.renderResponse(finalContent);
|
|
10031
10407
|
}
|
|
10032
10408
|
} else {
|
|
10033
|
-
|
|
10409
|
+
if (finalContent.trim()) {
|
|
10410
|
+
process.stdout.write("\n\n");
|
|
10411
|
+
}
|
|
10034
10412
|
}
|
|
10035
10413
|
lastResponseStore.content = finalContent;
|
|
10036
10414
|
session.addMessage({
|