jinzd-ai-cli 0.4.99 → 0.4.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-UWLEBELB.js";
4
+ } from "./chunk-4Y3CQKOA.js";
5
5
  import "./chunk-2ZD3YTVM.js";
6
- import "./chunk-Y7BS6NOY.js";
6
+ import "./chunk-VUNOH5LH.js";
7
7
 
8
8
  // src/cli/batch.ts
9
9
  import Anthropic from "@anthropic-ai/sdk";
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-Y7BS6NOY.js";
11
+ } from "./chunk-VUNOH5LH.js";
12
12
 
13
13
  // src/config/config-manager.ts
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -23,7 +23,7 @@ import {
23
23
  } from "./chunk-6VRJGH25.js";
24
24
  import {
25
25
  runTestsTool
26
- } from "./chunk-P3PTLB4T.js";
26
+ } from "./chunk-NXJF4NTB.js";
27
27
  import {
28
28
  CONFIG_DIR_NAME,
29
29
  DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
@@ -31,7 +31,7 @@ import {
31
31
  SUBAGENT_ALLOWED_TOOLS,
32
32
  SUBAGENT_DEFAULT_MAX_ROUNDS,
33
33
  SUBAGENT_MAX_ROUNDS_LIMIT
34
- } from "./chunk-Y7BS6NOY.js";
34
+ } from "./chunk-VUNOH5LH.js";
35
35
 
36
36
  // src/tools/types.ts
37
37
  function isFileWriteTool(name) {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  schemaToJsonSchema,
4
4
  truncateForPersist
5
- } from "./chunk-PVOAXVZM.js";
5
+ } from "./chunk-7KSCEXT5.js";
6
6
  import {
7
7
  AuthError,
8
8
  ProviderError,
@@ -21,7 +21,7 @@ import {
21
21
  MCP_PROTOCOL_VERSION,
22
22
  MCP_TOOL_PREFIX,
23
23
  VERSION
24
- } from "./chunk-Y7BS6NOY.js";
24
+ } from "./chunk-VUNOH5LH.js";
25
25
 
26
26
  // src/providers/claude.ts
27
27
  import Anthropic from "@anthropic-ai/sdk";
@@ -209,10 +209,7 @@ var ClaudeProvider = class extends BaseProvider {
209
209
  }
210
210
  async chat(request) {
211
211
  try {
212
- const messages = request.messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({
213
- role: m.role,
214
- content: this.contentToClaudeParts(m.content)
215
- }));
212
+ const messages = this.toClaudeMessagesWithToolHistory(request.messages);
216
213
  const { thinking, temperature } = this.buildThinkingParams(request);
217
214
  const response = await this.client.messages.create({
218
215
  model: request.model,
@@ -234,10 +231,7 @@ var ClaudeProvider = class extends BaseProvider {
234
231
  }
235
232
  async *chatStream(request) {
236
233
  try {
237
- const messages = request.messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({
238
- role: m.role,
239
- content: this.contentToClaudeParts(m.content)
240
- }));
234
+ const messages = this.toClaudeMessagesWithToolHistory(request.messages);
241
235
  const { thinking, temperature } = this.buildThinkingParams(request);
242
236
  const stream = this.client.messages.stream({
243
237
  model: request.model,
@@ -291,7 +285,7 @@ var ClaudeProvider = class extends BaseProvider {
291
285
  }
292
286
  }))
293
287
  );
294
- const baseMessages = request.messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({ role: m.role, content: this.contentToClaudeParts(m.content) }));
288
+ const baseMessages = this.toClaudeMessagesWithToolHistory(request.messages);
295
289
  const extraMessages = request._extraMessages ?? [];
296
290
  const allMessages = [...baseMessages, ...extraMessages];
297
291
  const { thinking, temperature } = this.buildThinkingParams(request);
@@ -344,7 +338,7 @@ var ClaudeProvider = class extends BaseProvider {
344
338
  }
345
339
  }))
346
340
  );
347
- const baseMessages = request.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: this.contentToClaudeParts(m.content) }));
341
+ const baseMessages = this.toClaudeMessagesWithToolHistory(request.messages);
348
342
  const extraMessages = request._extraMessages ?? [];
349
343
  const allMessages = [...baseMessages, ...extraMessages];
350
344
  const { thinking, temperature } = this.buildThinkingParams(request);
@@ -456,6 +450,73 @@ var ClaudeProvider = class extends BaseProvider {
456
450
  yield { type: "done", usage: { inputTokens: 0, outputTokens: 0 }, rawContent: rawContentBlocks };
457
451
  }
458
452
  }
453
+ /**
454
+ * 将 Message[] 转换为 Anthropic.MessageParam[],按原始顺序保留历史工具往返。
455
+ *
456
+ * v0.4.100+:之前的实现把 role='tool' 和 assistant.toolCalls 全部剥离后塞进
457
+ * _extraMessages 的尾部,导致跨轮历史的工具调用被插入到当前用户消息之后,
458
+ * 模型会复读上一轮的"完成汇总"。现在按原始顺序内联编码。
459
+ *
460
+ * 注意:历史持久化的 toolCalls 没有原始 thinking blocks(仅当前轮的内存里有),
461
+ * 故只输出 tool_use 块作为 fallback;当前轮通过 buildToolResultMessages() 仍能
462
+ * 携带 thinking 信息。
463
+ */
464
+ toClaudeMessagesWithToolHistory(messages) {
465
+ const out = [];
466
+ let i = 0;
467
+ while (i < messages.length) {
468
+ const m = messages[i];
469
+ if (m.role === "system") {
470
+ i++;
471
+ continue;
472
+ }
473
+ if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
474
+ const toolCalls = m.toolCalls;
475
+ const assistantBlocks = [];
476
+ if (typeof m.content === "string" && m.content.trim()) {
477
+ assistantBlocks.push({ type: "text", text: m.content });
478
+ }
479
+ for (const tc of toolCalls) {
480
+ assistantBlocks.push({
481
+ type: "tool_use",
482
+ id: tc.id,
483
+ name: tc.name,
484
+ input: tc.arguments
485
+ });
486
+ }
487
+ out.push({ role: "assistant", content: assistantBlocks });
488
+ const toolResults = [];
489
+ let j = i + 1;
490
+ while (j < messages.length && messages[j].role === "tool") {
491
+ const tm = messages[j];
492
+ if (tm.toolCallId) {
493
+ toolResults.push({
494
+ type: "tool_result",
495
+ tool_use_id: tm.toolCallId,
496
+ content: typeof tm.content === "string" ? tm.content : "",
497
+ is_error: tm.isError ?? false
498
+ });
499
+ }
500
+ j++;
501
+ }
502
+ if (toolResults.length > 0) {
503
+ out.push({ role: "user", content: toolResults });
504
+ }
505
+ i = j;
506
+ continue;
507
+ }
508
+ if (m.role === "tool") {
509
+ i++;
510
+ continue;
511
+ }
512
+ out.push({
513
+ role: m.role,
514
+ content: this.contentToClaudeParts(m.content)
515
+ });
516
+ i++;
517
+ }
518
+ return out;
519
+ }
459
520
  buildToolResultMessages(assistantToolCalls, results) {
460
521
  const rawContent = assistantToolCalls._rawContent;
461
522
  let assistantContent;
@@ -602,11 +663,62 @@ var GeminiProvider = class extends BaseProvider {
602
663
  }
603
664
  return parts.length > 0 ? parts : [{ text: "" }];
604
665
  }
666
+ /**
667
+ * 将 Message[] 转 Gemini Content[],按原始顺序保留历史工具往返。
668
+ *
669
+ * v0.4.100+:之前过滤掉 role='tool' 和 assistant.toolCalls 后再由 _extraMessages
670
+ * 拼到末尾,导致跨轮历史的工具调用被插到当前用户消息之后,模型会复读上一轮汇总。
671
+ */
605
672
  toGeminiHistory(messages) {
606
- return messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({
607
- role: m.role === "assistant" ? "model" : "user",
608
- parts: this.contentToGeminiParts(m.content)
609
- }));
673
+ const out = [];
674
+ let i = 0;
675
+ while (i < messages.length) {
676
+ const m = messages[i];
677
+ if (m.role === "system") {
678
+ i++;
679
+ continue;
680
+ }
681
+ if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
682
+ const modelParts = [];
683
+ if (typeof m.content === "string" && m.content.trim()) {
684
+ modelParts.push({ text: m.content });
685
+ }
686
+ for (const tc of m.toolCalls) {
687
+ modelParts.push({ functionCall: { name: tc.name, args: tc.arguments } });
688
+ }
689
+ out.push({ role: "model", parts: modelParts });
690
+ const fnParts = [];
691
+ let j = i + 1;
692
+ while (j < messages.length && messages[j].role === "tool") {
693
+ const tm = messages[j];
694
+ fnParts.push({
695
+ functionResponse: {
696
+ name: tm.toolName ?? "unknown",
697
+ response: {
698
+ result: typeof tm.content === "string" ? tm.content : "",
699
+ isError: tm.isError ?? false
700
+ }
701
+ }
702
+ });
703
+ j++;
704
+ }
705
+ if (fnParts.length > 0) {
706
+ out.push({ role: "function", parts: fnParts });
707
+ }
708
+ i = j;
709
+ continue;
710
+ }
711
+ if (m.role === "tool") {
712
+ i++;
713
+ continue;
714
+ }
715
+ out.push({
716
+ role: m.role === "assistant" ? "model" : "user",
717
+ parts: this.contentToGeminiParts(m.content)
718
+ });
719
+ i++;
720
+ }
721
+ return out;
610
722
  }
611
723
  async chat(request) {
612
724
  try {
@@ -830,16 +942,49 @@ var OpenAICompatibleProvider = class extends BaseProvider {
830
942
  }
831
943
  this.client = new OpenAI(clientOptions);
832
944
  }
833
- /** 将 systemPrompt + messages 合并为 OpenAI messages 数组(system 消息放首位)。 */
945
+ /**
946
+ * 将 systemPrompt + messages 合并为 OpenAI messages 数组(system 消息放首位)。
947
+ *
948
+ * v0.4.100+:按原始顺序保留工具消息(assistant.toolCalls 和 role='tool'),
949
+ * 不再剥离到 _extraMessages 末尾——之前的剥离会让历史工具往返被插到当前用户消息之后,
950
+ * 导致模型把"过去的工具调用结果"当作"对当前问题的回应",DeepSeek V4 Flash 上尤其明显
951
+ * (会复读上一轮的"完成汇总")。
952
+ *
953
+ * DeepSeek V4 thinking 模式:所有 assistant 消息(含带 toolCalls 的)必须有
954
+ * reasoning_content 字段,缺失则 API 400。
955
+ */
834
956
  buildMessages(request) {
835
- const filtered = request.messages.filter((m) => m.role !== "tool" && !m.toolCalls);
836
- const msgs = filtered.map((m) => {
957
+ const msgs = [];
958
+ for (const m of request.messages) {
959
+ if (m.role === "tool") {
960
+ if (!m.toolCallId) continue;
961
+ msgs.push({
962
+ role: "tool",
963
+ tool_call_id: m.toolCallId,
964
+ content: typeof m.content === "string" ? m.content : ""
965
+ });
966
+ continue;
967
+ }
968
+ if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
969
+ const assistantMsg = {
970
+ role: "assistant",
971
+ content: typeof m.content === "string" && m.content ? m.content : null,
972
+ tool_calls: m.toolCalls.map((tc) => ({
973
+ id: tc.id,
974
+ type: "function",
975
+ function: { name: tc.name, arguments: JSON.stringify(tc.arguments) }
976
+ })),
977
+ reasoning_content: m.reasoningContent ?? ""
978
+ };
979
+ msgs.push(assistantMsg);
980
+ continue;
981
+ }
837
982
  const base = { role: m.role, content: m.content };
838
983
  if (m.role === "assistant") {
839
984
  base.reasoning_content = m.reasoningContent ?? "";
840
985
  }
841
- return base;
842
- });
986
+ msgs.push(base);
987
+ }
843
988
  const systemContent = [request.systemPrompt, request.systemPromptVolatile].filter(Boolean).join("\n\n---\n\n");
844
989
  if (systemContent) {
845
990
  return [{ role: "system", content: systemContent }, ...msgs];
@@ -3819,67 +3964,6 @@ function persistToolRound(session, toolCalls, toolResults, opts) {
3819
3964
  });
3820
3965
  }
3821
3966
  }
3822
- function isToolMessage(m) {
3823
- return m.role === "tool" || !!(m.toolCalls && m.toolCalls.length > 0);
3824
- }
3825
- function extractToolHistory(messages) {
3826
- const baseMessages = [];
3827
- const toolHistory = [];
3828
- for (const m of messages) {
3829
- if (isToolMessage(m)) {
3830
- toolHistory.push(m);
3831
- } else {
3832
- baseMessages.push(m);
3833
- }
3834
- }
3835
- return { baseMessages, toolHistory };
3836
- }
3837
- function rebuildExtraMessages(provider, toolHistory) {
3838
- if (toolHistory.length === 0) return [];
3839
- const result = [];
3840
- let i = 0;
3841
- while (i < toolHistory.length) {
3842
- const msg = toolHistory[i];
3843
- if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
3844
- const toolCalls = msg.toolCalls;
3845
- const toolResults = [];
3846
- let j = i + 1;
3847
- while (j < toolHistory.length && toolHistory[j].role === "tool") {
3848
- const tm = toolHistory[j];
3849
- if (!tm.toolCallId) {
3850
- console.warn(
3851
- `[rebuildExtraMessages] Skipping malformed tool message (missing toolCallId) at index ${j}; tool name="${toolCalls.find((_tc) => true)?.name ?? "unknown"}"`
3852
- );
3853
- j++;
3854
- continue;
3855
- }
3856
- toolResults.push({
3857
- callId: tm.toolCallId,
3858
- content: typeof tm.content === "string" ? tm.content : getContentText(tm.content),
3859
- isError: tm.isError ?? false
3860
- });
3861
- j++;
3862
- }
3863
- if (toolResults.length > 0) {
3864
- result.push(
3865
- ...provider.buildToolResultMessages(toolCalls, toolResults, msg.reasoningContent)
3866
- );
3867
- } else {
3868
- console.warn(
3869
- `[rebuildExtraMessages] Dropping assistant turn with ${toolCalls.length} toolCalls but no valid tool results`
3870
- );
3871
- }
3872
- i = j;
3873
- } else {
3874
- result.push({
3875
- role: msg.role,
3876
- content: typeof msg.content === "string" ? msg.content : getContentText(msg.content)
3877
- });
3878
- i++;
3879
- }
3880
- }
3881
- return result;
3882
- }
3883
3967
  function trimOldToolOutput(messages, keepRecentRounds = 10) {
3884
3968
  const roundStarts = [];
3885
3969
  for (let i = 0; i < messages.length; i++) {
@@ -4053,8 +4137,6 @@ export {
4053
4137
  formatCost,
4054
4138
  parseSimpleYaml,
4055
4139
  persistToolRound,
4056
- extractToolHistory,
4057
- rebuildExtraMessages,
4058
4140
  autoTrimSessionIfNeeded,
4059
4141
  SNAPSHOT_PROMPT,
4060
4142
  sessionHasMeaningfulContent,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-Y7BS6NOY.js";
4
+ } from "./chunk-VUNOH5LH.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.99";
4
+ var VERSION = "0.4.100";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.99";
9
+ var VERSION = "0.4.100";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -36,7 +36,7 @@ import {
36
36
  VERSION,
37
37
  buildUserIdentityPrompt,
38
38
  runTestsTool
39
- } from "./chunk-DRUEPDBJ.js";
39
+ } from "./chunk-WWODV2IK.js";
40
40
  import {
41
41
  hasSemanticIndex,
42
42
  semanticSearch
@@ -720,10 +720,7 @@ var ClaudeProvider = class extends BaseProvider {
720
720
  }
721
721
  async chat(request) {
722
722
  try {
723
- const messages = request.messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({
724
- role: m.role,
725
- content: this.contentToClaudeParts(m.content)
726
- }));
723
+ const messages = this.toClaudeMessagesWithToolHistory(request.messages);
727
724
  const { thinking, temperature } = this.buildThinkingParams(request);
728
725
  const response = await this.client.messages.create({
729
726
  model: request.model,
@@ -745,10 +742,7 @@ var ClaudeProvider = class extends BaseProvider {
745
742
  }
746
743
  async *chatStream(request) {
747
744
  try {
748
- const messages = request.messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({
749
- role: m.role,
750
- content: this.contentToClaudeParts(m.content)
751
- }));
745
+ const messages = this.toClaudeMessagesWithToolHistory(request.messages);
752
746
  const { thinking, temperature } = this.buildThinkingParams(request);
753
747
  const stream = this.client.messages.stream({
754
748
  model: request.model,
@@ -802,7 +796,7 @@ var ClaudeProvider = class extends BaseProvider {
802
796
  }
803
797
  }))
804
798
  );
805
- const baseMessages = request.messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({ role: m.role, content: this.contentToClaudeParts(m.content) }));
799
+ const baseMessages = this.toClaudeMessagesWithToolHistory(request.messages);
806
800
  const extraMessages = request._extraMessages ?? [];
807
801
  const allMessages = [...baseMessages, ...extraMessages];
808
802
  const { thinking, temperature } = this.buildThinkingParams(request);
@@ -855,7 +849,7 @@ var ClaudeProvider = class extends BaseProvider {
855
849
  }
856
850
  }))
857
851
  );
858
- const baseMessages = request.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: this.contentToClaudeParts(m.content) }));
852
+ const baseMessages = this.toClaudeMessagesWithToolHistory(request.messages);
859
853
  const extraMessages = request._extraMessages ?? [];
860
854
  const allMessages = [...baseMessages, ...extraMessages];
861
855
  const { thinking, temperature } = this.buildThinkingParams(request);
@@ -967,6 +961,73 @@ var ClaudeProvider = class extends BaseProvider {
967
961
  yield { type: "done", usage: { inputTokens: 0, outputTokens: 0 }, rawContent: rawContentBlocks };
968
962
  }
969
963
  }
964
+ /**
965
+ * 将 Message[] 转换为 Anthropic.MessageParam[],按原始顺序保留历史工具往返。
966
+ *
967
+ * v0.4.100+:之前的实现把 role='tool' 和 assistant.toolCalls 全部剥离后塞进
968
+ * _extraMessages 的尾部,导致跨轮历史的工具调用被插入到当前用户消息之后,
969
+ * 模型会复读上一轮的"完成汇总"。现在按原始顺序内联编码。
970
+ *
971
+ * 注意:历史持久化的 toolCalls 没有原始 thinking blocks(仅当前轮的内存里有),
972
+ * 故只输出 tool_use 块作为 fallback;当前轮通过 buildToolResultMessages() 仍能
973
+ * 携带 thinking 信息。
974
+ */
975
+ toClaudeMessagesWithToolHistory(messages) {
976
+ const out = [];
977
+ let i = 0;
978
+ while (i < messages.length) {
979
+ const m = messages[i];
980
+ if (m.role === "system") {
981
+ i++;
982
+ continue;
983
+ }
984
+ if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
985
+ const toolCalls = m.toolCalls;
986
+ const assistantBlocks = [];
987
+ if (typeof m.content === "string" && m.content.trim()) {
988
+ assistantBlocks.push({ type: "text", text: m.content });
989
+ }
990
+ for (const tc of toolCalls) {
991
+ assistantBlocks.push({
992
+ type: "tool_use",
993
+ id: tc.id,
994
+ name: tc.name,
995
+ input: tc.arguments
996
+ });
997
+ }
998
+ out.push({ role: "assistant", content: assistantBlocks });
999
+ const toolResults = [];
1000
+ let j = i + 1;
1001
+ while (j < messages.length && messages[j].role === "tool") {
1002
+ const tm = messages[j];
1003
+ if (tm.toolCallId) {
1004
+ toolResults.push({
1005
+ type: "tool_result",
1006
+ tool_use_id: tm.toolCallId,
1007
+ content: typeof tm.content === "string" ? tm.content : "",
1008
+ is_error: tm.isError ?? false
1009
+ });
1010
+ }
1011
+ j++;
1012
+ }
1013
+ if (toolResults.length > 0) {
1014
+ out.push({ role: "user", content: toolResults });
1015
+ }
1016
+ i = j;
1017
+ continue;
1018
+ }
1019
+ if (m.role === "tool") {
1020
+ i++;
1021
+ continue;
1022
+ }
1023
+ out.push({
1024
+ role: m.role,
1025
+ content: this.contentToClaudeParts(m.content)
1026
+ });
1027
+ i++;
1028
+ }
1029
+ return out;
1030
+ }
970
1031
  buildToolResultMessages(assistantToolCalls, results) {
971
1032
  const rawContent = assistantToolCalls._rawContent;
972
1033
  let assistantContent;
@@ -1113,11 +1174,62 @@ var GeminiProvider = class extends BaseProvider {
1113
1174
  }
1114
1175
  return parts.length > 0 ? parts : [{ text: "" }];
1115
1176
  }
1177
+ /**
1178
+ * 将 Message[] 转 Gemini Content[],按原始顺序保留历史工具往返。
1179
+ *
1180
+ * v0.4.100+:之前过滤掉 role='tool' 和 assistant.toolCalls 后再由 _extraMessages
1181
+ * 拼到末尾,导致跨轮历史的工具调用被插到当前用户消息之后,模型会复读上一轮汇总。
1182
+ */
1116
1183
  toGeminiHistory(messages) {
1117
- return messages.filter((m) => m.role !== "system" && m.role !== "tool" && !m.toolCalls).map((m) => ({
1118
- role: m.role === "assistant" ? "model" : "user",
1119
- parts: this.contentToGeminiParts(m.content)
1120
- }));
1184
+ const out = [];
1185
+ let i = 0;
1186
+ while (i < messages.length) {
1187
+ const m = messages[i];
1188
+ if (m.role === "system") {
1189
+ i++;
1190
+ continue;
1191
+ }
1192
+ if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
1193
+ const modelParts = [];
1194
+ if (typeof m.content === "string" && m.content.trim()) {
1195
+ modelParts.push({ text: m.content });
1196
+ }
1197
+ for (const tc of m.toolCalls) {
1198
+ modelParts.push({ functionCall: { name: tc.name, args: tc.arguments } });
1199
+ }
1200
+ out.push({ role: "model", parts: modelParts });
1201
+ const fnParts = [];
1202
+ let j = i + 1;
1203
+ while (j < messages.length && messages[j].role === "tool") {
1204
+ const tm = messages[j];
1205
+ fnParts.push({
1206
+ functionResponse: {
1207
+ name: tm.toolName ?? "unknown",
1208
+ response: {
1209
+ result: typeof tm.content === "string" ? tm.content : "",
1210
+ isError: tm.isError ?? false
1211
+ }
1212
+ }
1213
+ });
1214
+ j++;
1215
+ }
1216
+ if (fnParts.length > 0) {
1217
+ out.push({ role: "function", parts: fnParts });
1218
+ }
1219
+ i = j;
1220
+ continue;
1221
+ }
1222
+ if (m.role === "tool") {
1223
+ i++;
1224
+ continue;
1225
+ }
1226
+ out.push({
1227
+ role: m.role === "assistant" ? "model" : "user",
1228
+ parts: this.contentToGeminiParts(m.content)
1229
+ });
1230
+ i++;
1231
+ }
1232
+ return out;
1121
1233
  }
1122
1234
  async chat(request) {
1123
1235
  try {
@@ -1341,16 +1453,49 @@ var OpenAICompatibleProvider = class extends BaseProvider {
1341
1453
  }
1342
1454
  this.client = new OpenAI(clientOptions);
1343
1455
  }
1344
- /** 将 systemPrompt + messages 合并为 OpenAI messages 数组(system 消息放首位)。 */
1456
+ /**
1457
+ * 将 systemPrompt + messages 合并为 OpenAI messages 数组(system 消息放首位)。
1458
+ *
1459
+ * v0.4.100+:按原始顺序保留工具消息(assistant.toolCalls 和 role='tool'),
1460
+ * 不再剥离到 _extraMessages 末尾——之前的剥离会让历史工具往返被插到当前用户消息之后,
1461
+ * 导致模型把"过去的工具调用结果"当作"对当前问题的回应",DeepSeek V4 Flash 上尤其明显
1462
+ * (会复读上一轮的"完成汇总")。
1463
+ *
1464
+ * DeepSeek V4 thinking 模式:所有 assistant 消息(含带 toolCalls 的)必须有
1465
+ * reasoning_content 字段,缺失则 API 400。
1466
+ */
1345
1467
  buildMessages(request) {
1346
- const filtered = request.messages.filter((m) => m.role !== "tool" && !m.toolCalls);
1347
- const msgs = filtered.map((m) => {
1468
+ const msgs = [];
1469
+ for (const m of request.messages) {
1470
+ if (m.role === "tool") {
1471
+ if (!m.toolCallId) continue;
1472
+ msgs.push({
1473
+ role: "tool",
1474
+ tool_call_id: m.toolCallId,
1475
+ content: typeof m.content === "string" ? m.content : ""
1476
+ });
1477
+ continue;
1478
+ }
1479
+ if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
1480
+ const assistantMsg = {
1481
+ role: "assistant",
1482
+ content: typeof m.content === "string" && m.content ? m.content : null,
1483
+ tool_calls: m.toolCalls.map((tc) => ({
1484
+ id: tc.id,
1485
+ type: "function",
1486
+ function: { name: tc.name, arguments: JSON.stringify(tc.arguments) }
1487
+ })),
1488
+ reasoning_content: m.reasoningContent ?? ""
1489
+ };
1490
+ msgs.push(assistantMsg);
1491
+ continue;
1492
+ }
1348
1493
  const base = { role: m.role, content: m.content };
1349
1494
  if (m.role === "assistant") {
1350
1495
  base.reasoning_content = m.reasoningContent ?? "";
1351
1496
  }
1352
- return base;
1353
- });
1497
+ msgs.push(base);
1498
+ }
1354
1499
  const systemContent = [request.systemPrompt, request.systemPromptVolatile].filter(Boolean).join("\n\n---\n\n");
1355
1500
  if (systemContent) {
1356
1501
  return [{ role: "system", content: systemContent }, ...msgs];
@@ -9406,67 +9551,6 @@ function persistToolRound(session, toolCalls, toolResults, opts) {
9406
9551
  });
9407
9552
  }
9408
9553
  }
9409
- function isToolMessage(m) {
9410
- return m.role === "tool" || !!(m.toolCalls && m.toolCalls.length > 0);
9411
- }
9412
- function extractToolHistory(messages) {
9413
- const baseMessages = [];
9414
- const toolHistory = [];
9415
- for (const m of messages) {
9416
- if (isToolMessage(m)) {
9417
- toolHistory.push(m);
9418
- } else {
9419
- baseMessages.push(m);
9420
- }
9421
- }
9422
- return { baseMessages, toolHistory };
9423
- }
9424
- function rebuildExtraMessages(provider, toolHistory) {
9425
- if (toolHistory.length === 0) return [];
9426
- const result = [];
9427
- let i = 0;
9428
- while (i < toolHistory.length) {
9429
- const msg = toolHistory[i];
9430
- if (msg.role === "assistant" && msg.toolCalls && msg.toolCalls.length > 0) {
9431
- const toolCalls = msg.toolCalls;
9432
- const toolResults = [];
9433
- let j = i + 1;
9434
- while (j < toolHistory.length && toolHistory[j].role === "tool") {
9435
- const tm = toolHistory[j];
9436
- if (!tm.toolCallId) {
9437
- console.warn(
9438
- `[rebuildExtraMessages] Skipping malformed tool message (missing toolCallId) at index ${j}; tool name="${toolCalls.find((_tc) => true)?.name ?? "unknown"}"`
9439
- );
9440
- j++;
9441
- continue;
9442
- }
9443
- toolResults.push({
9444
- callId: tm.toolCallId,
9445
- content: typeof tm.content === "string" ? tm.content : getContentText(tm.content),
9446
- isError: tm.isError ?? false
9447
- });
9448
- j++;
9449
- }
9450
- if (toolResults.length > 0) {
9451
- result.push(
9452
- ...provider.buildToolResultMessages(toolCalls, toolResults, msg.reasoningContent)
9453
- );
9454
- } else {
9455
- console.warn(
9456
- `[rebuildExtraMessages] Dropping assistant turn with ${toolCalls.length} toolCalls but no valid tool results`
9457
- );
9458
- }
9459
- i = j;
9460
- } else {
9461
- result.push({
9462
- role: msg.role,
9463
- content: typeof msg.content === "string" ? msg.content : getContentText(msg.content)
9464
- });
9465
- i++;
9466
- }
9467
- }
9468
- return result;
9469
- }
9470
9554
  function trimOldToolOutput(messages, keepRecentRounds = 10) {
9471
9555
  const roundStarts = [];
9472
9556
  for (let i = 0; i < messages.length; i++) {
@@ -10004,9 +10088,8 @@ var SessionHandler = class _SessionHandler {
10004
10088
  }
10005
10089
  async handleChatWithTools(provider, messages, toolDefs, mcpBudgetNote) {
10006
10090
  const session = this.sessions.current;
10007
- const { baseMessages: cleanMessages, toolHistory } = extractToolHistory(messages);
10008
- const apiMessages = [...cleanMessages];
10009
- const extraMessages = toolHistory.length > 0 ? rebuildExtraMessages(provider, toolHistory) : [];
10091
+ const apiMessages = [...messages];
10092
+ const extraMessages = [];
10010
10093
  const maxToolRounds = this.config.get("maxToolRounds") ?? DEFAULT_MAX_TOOL_ROUNDS;
10011
10094
  const autoPauseIntervalRaw = this.config.get("autoPauseInterval");
10012
10095
  const autoPauseInterval = typeof autoPauseIntervalRaw === "number" ? autoPauseIntervalRaw : 50;
@@ -11402,7 +11485,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
11402
11485
  case "test": {
11403
11486
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
11404
11487
  try {
11405
- const { executeTests } = await import("./run-tests-LRPE4YY3.js");
11488
+ const { executeTests } = await import("./run-tests-ASV6RF2W.js");
11406
11489
  const argStr = args.join(" ").trim();
11407
11490
  let testArgs = {};
11408
11491
  if (argStr) {
@@ -385,7 +385,7 @@ ${content}`);
385
385
  }
386
386
  }
387
387
  async function runTaskMode(config, providers, configManager, topic) {
388
- const { TaskOrchestrator } = await import("./task-orchestrator-EZK6NLQW.js");
388
+ const { TaskOrchestrator } = await import("./task-orchestrator-VOWDS3AR.js");
389
389
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
390
390
  let interrupted = false;
391
391
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -13,7 +13,6 @@ import {
13
13
  clearDevState,
14
14
  computeCost,
15
15
  detectsHallucinatedFileOp,
16
- extractToolHistory,
17
16
  extractWrittenFilePaths,
18
17
  findPhantomClaims,
19
18
  formatCost,
@@ -26,14 +25,13 @@ import {
26
25
  loadDevState,
27
26
  parseSimpleYaml,
28
27
  persistToolRound,
29
- rebuildExtraMessages,
30
28
  saveDevState,
31
29
  sessionHasMeaningfulContent,
32
30
  setupProxy
33
- } from "./chunk-7NWJTFYS.js";
31
+ } from "./chunk-DUTKX4KD.js";
34
32
  import {
35
33
  ConfigManager
36
- } from "./chunk-UWLEBELB.js";
34
+ } from "./chunk-4Y3CQKOA.js";
37
35
  import {
38
36
  ToolExecutor,
39
37
  ToolRegistry,
@@ -52,7 +50,7 @@ import {
52
50
  spawnAgentContext,
53
51
  theme,
54
52
  undoStack
55
- } from "./chunk-PVOAXVZM.js";
53
+ } from "./chunk-7KSCEXT5.js";
56
54
  import "./chunk-2ZD3YTVM.js";
57
55
  import {
58
56
  fileCheckpoints
@@ -70,7 +68,7 @@ import "./chunk-KJLJPUY2.js";
70
68
  import "./chunk-6VRJGH25.js";
71
69
  import "./chunk-2DXY7UGF.js";
72
70
  import "./chunk-KHYD3WXE.js";
73
- import "./chunk-P3PTLB4T.js";
71
+ import "./chunk-NXJF4NTB.js";
74
72
  import {
75
73
  AGENTIC_BEHAVIOR_GUIDELINE,
76
74
  AUTHOR,
@@ -92,7 +90,7 @@ import {
92
90
  SKILLS_DIR_NAME,
93
91
  VERSION,
94
92
  buildUserIdentityPrompt
95
- } from "./chunk-Y7BS6NOY.js";
93
+ } from "./chunk-VUNOH5LH.js";
96
94
 
97
95
  // src/index.ts
98
96
  import { program } from "commander";
@@ -2612,7 +2610,7 @@ ${hint}` : "")
2612
2610
  usage: "/test [command|filter]",
2613
2611
  async execute(args, ctx) {
2614
2612
  try {
2615
- const { executeTests } = await import("./run-tests-B3XHGUVD.js");
2613
+ const { executeTests } = await import("./run-tests-J4UIFHR3.js");
2616
2614
  const argStr = args.join(" ").trim();
2617
2615
  let testArgs = {};
2618
2616
  if (argStr) {
@@ -5877,9 +5875,8 @@ Session '${this.resumeSessionId}' not found.
5877
5875
  if (this.blockedTools) {
5878
5876
  toolDefs = toolDefs.filter((t) => !this.blockedTools.has(t.name));
5879
5877
  }
5880
- const { baseMessages: cleanMessages, toolHistory } = extractToolHistory(messages);
5881
- const apiMessages = [...cleanMessages];
5882
- const extraMessages = toolHistory.length > 0 ? rebuildExtraMessages(provider, toolHistory) : [];
5878
+ const apiMessages = [...messages];
5879
+ const extraMessages = [];
5883
5880
  const maxToolRounds = this.maxToolRoundsOverride ?? this.config.get("maxToolRounds") ?? DEFAULT_MAX_TOOL_ROUNDS;
5884
5881
  const autoPauseIntervalRaw = this.config.get("autoPauseInterval");
5885
5882
  const autoPauseInterval = typeof autoPauseIntervalRaw === "number" ? autoPauseIntervalRaw : DEFAULT_AUTO_PAUSE_INTERVAL;
@@ -6752,7 +6749,7 @@ program.command("web").description("Start Web UI server with browser-based chat
6752
6749
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
6753
6750
  process.exit(1);
6754
6751
  }
6755
- const { startWebServer } = await import("./server-EGOEPMSQ.js");
6752
+ const { startWebServer } = await import("./server-QHNF2N2Q.js");
6756
6753
  await startWebServer({ port, host: options.host });
6757
6754
  });
6758
6755
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -6875,7 +6872,7 @@ program.command("sessions").description("List recent conversation sessions").act
6875
6872
  });
6876
6873
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
6877
6874
  try {
6878
- const batch = await import("./batch-7DMVTZSV.js");
6875
+ const batch = await import("./batch-6S5U2JDF.js");
6879
6876
  switch (action) {
6880
6877
  case "submit":
6881
6878
  if (!arg) {
@@ -6918,7 +6915,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
6918
6915
  }
6919
6916
  });
6920
6917
  program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
6921
- const { startMcpServer } = await import("./server-WCI7SGTN.js");
6918
+ const { startMcpServer } = await import("./server-EMJPXELL.js");
6922
6919
  await startMcpServer({
6923
6920
  allowDestructive: !!options.allowDestructive,
6924
6921
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7045,7 +7042,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7045
7042
  }),
7046
7043
  config.get("customProviders")
7047
7044
  );
7048
- const { startHub } = await import("./hub-RUAWQFK2.js");
7045
+ const { startHub } = await import("./hub-IV5FAEWL.js");
7049
7046
  await startHub(
7050
7047
  {
7051
7048
  topic: topic ?? "",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-DRUEPDBJ.js";
4
+ } from "./chunk-WWODV2IK.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-P3PTLB4T.js";
6
- import "./chunk-Y7BS6NOY.js";
5
+ } from "./chunk-NXJF4NTB.js";
6
+ import "./chunk-VUNOH5LH.js";
7
7
  export {
8
8
  executeTests,
9
9
  runTestsTool
@@ -3,7 +3,7 @@ import {
3
3
  ToolRegistry,
4
4
  getDangerLevel,
5
5
  schemaToJsonSchema
6
- } from "./chunk-PVOAXVZM.js";
6
+ } from "./chunk-7KSCEXT5.js";
7
7
  import "./chunk-2ZD3YTVM.js";
8
8
  import "./chunk-4BKXL7SM.js";
9
9
  import "./chunk-ANYYM4CF.js";
@@ -12,10 +12,10 @@ import "./chunk-KJLJPUY2.js";
12
12
  import "./chunk-6VRJGH25.js";
13
13
  import "./chunk-2DXY7UGF.js";
14
14
  import "./chunk-KHYD3WXE.js";
15
- import "./chunk-P3PTLB4T.js";
15
+ import "./chunk-NXJF4NTB.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-Y7BS6NOY.js";
18
+ } from "./chunk-VUNOH5LH.js";
19
19
 
20
20
  // src/mcp/server.ts
21
21
  import { createInterface } from "readline";
@@ -12,7 +12,6 @@ import {
12
12
  autoTrimSessionIfNeeded,
13
13
  computeCost,
14
14
  detectsHallucinatedFileOp,
15
- extractToolHistory,
16
15
  formatCost,
17
16
  formatGitContextForPrompt,
18
17
  getContentText,
@@ -21,12 +20,11 @@ import {
21
20
  hadPreviousWriteToolCalls,
22
21
  loadDevState,
23
22
  persistToolRound,
24
- rebuildExtraMessages,
25
23
  setupProxy
26
- } from "./chunk-7NWJTFYS.js";
24
+ } from "./chunk-DUTKX4KD.js";
27
25
  import {
28
26
  ConfigManager
29
- } from "./chunk-UWLEBELB.js";
27
+ } from "./chunk-4Y3CQKOA.js";
30
28
  import {
31
29
  ToolExecutor,
32
30
  ToolRegistry,
@@ -44,7 +42,7 @@ import {
44
42
  spawnAgentContext,
45
43
  truncateOutput,
46
44
  undoStack
47
- } from "./chunk-PVOAXVZM.js";
45
+ } from "./chunk-7KSCEXT5.js";
48
46
  import "./chunk-2ZD3YTVM.js";
49
47
  import "./chunk-4BKXL7SM.js";
50
48
  import "./chunk-ANYYM4CF.js";
@@ -53,7 +51,7 @@ import "./chunk-KJLJPUY2.js";
53
51
  import "./chunk-6VRJGH25.js";
54
52
  import "./chunk-2DXY7UGF.js";
55
53
  import "./chunk-KHYD3WXE.js";
56
- import "./chunk-P3PTLB4T.js";
54
+ import "./chunk-NXJF4NTB.js";
57
55
  import {
58
56
  AGENTIC_BEHAVIOR_GUIDELINE,
59
57
  AUTHOR,
@@ -72,7 +70,7 @@ import {
72
70
  SKILLS_DIR_NAME,
73
71
  VERSION,
74
72
  buildUserIdentityPrompt
75
- } from "./chunk-Y7BS6NOY.js";
73
+ } from "./chunk-VUNOH5LH.js";
76
74
 
77
75
  // src/web/server.ts
78
76
  import express from "express";
@@ -853,9 +851,8 @@ var SessionHandler = class _SessionHandler {
853
851
  }
854
852
  async handleChatWithTools(provider, messages, toolDefs, mcpBudgetNote) {
855
853
  const session = this.sessions.current;
856
- const { baseMessages: cleanMessages, toolHistory } = extractToolHistory(messages);
857
- const apiMessages = [...cleanMessages];
858
- const extraMessages = toolHistory.length > 0 ? rebuildExtraMessages(provider, toolHistory) : [];
854
+ const apiMessages = [...messages];
855
+ const extraMessages = [];
859
856
  const maxToolRounds = this.config.get("maxToolRounds") ?? DEFAULT_MAX_TOOL_ROUNDS;
860
857
  const autoPauseIntervalRaw = this.config.get("autoPauseInterval");
861
858
  const autoPauseInterval = typeof autoPauseIntervalRaw === "number" ? autoPauseIntervalRaw : 50;
@@ -2251,7 +2248,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2251
2248
  case "test": {
2252
2249
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2253
2250
  try {
2254
- const { executeTests } = await import("./run-tests-B3XHGUVD.js");
2251
+ const { executeTests } = await import("./run-tests-J4UIFHR3.js");
2255
2252
  const argStr = args.join(" ").trim();
2256
2253
  let testArgs = {};
2257
2254
  if (argStr) {
@@ -4,7 +4,7 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-PVOAXVZM.js";
7
+ } from "./chunk-7KSCEXT5.js";
8
8
  import "./chunk-2ZD3YTVM.js";
9
9
  import "./chunk-4BKXL7SM.js";
10
10
  import "./chunk-ANYYM4CF.js";
@@ -13,10 +13,10 @@ import "./chunk-KJLJPUY2.js";
13
13
  import "./chunk-6VRJGH25.js";
14
14
  import "./chunk-2DXY7UGF.js";
15
15
  import "./chunk-KHYD3WXE.js";
16
- import "./chunk-P3PTLB4T.js";
16
+ import "./chunk-NXJF4NTB.js";
17
17
  import {
18
18
  SUBAGENT_ALLOWED_TOOLS
19
- } from "./chunk-Y7BS6NOY.js";
19
+ } from "./chunk-VUNOH5LH.js";
20
20
 
21
21
  // src/hub/task-orchestrator.ts
22
22
  import { createInterface } from "readline";
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.99",
3
+ "version": "0.4.100",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",