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.
@@ -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.52";
11
+ var VERSION = "0.1.54";
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
@@ -29,7 +29,7 @@ import {
29
29
  SUBAGENT_MAX_ROUNDS_LIMIT,
30
30
  VERSION,
31
31
  runTestsTool
32
- } from "./chunk-KRYABIB4.js";
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
- if (hasWriteTools && this.detectsHallucinatedFileOp(result.content)) {
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
- if (hasWriteTools && this.detectsHallucinatedFileOp(result.content)) {
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-MTYDLLII.js");
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
- const result = await provider.chatWithTools(
10004
- {
10005
- messages: apiMessages,
10006
- model: this.currentModel,
10007
- systemPrompt,
10008
- stream: false,
10009
- temperature: modelParams.temperature,
10010
- maxTokens: modelParams.maxTokens,
10011
- timeout: modelParams.timeout,
10012
- thinking: modelParams.thinking,
10013
- thinkingBudget: modelParams.thinkingBudget,
10014
- ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
10015
- },
10016
- toolDefs
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 (useStreaming) {
10026
- const streamAc = this.setupStreamInterrupt();
10027
- try {
10028
- await this.renderer.renderContentAsStream(finalContent, { signal: streamAc.signal });
10029
- } finally {
10030
- this.teardownStreamInterrupt();
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
- this.renderer.renderResponse(finalContent);
10409
+ if (finalContent.trim()) {
10410
+ process.stdout.write("\n\n");
10411
+ }
10034
10412
  }
10035
10413
  lastResponseStore.content = finalContent;
10036
10414
  session.addMessage({
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-KRYABIB4.js";
5
+ } from "./chunk-AF33GPHX.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.52",
3
+ "version": "0.1.54",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",