@qwen-code/qwen-code 0.16.0 → 0.16.1-nightly.20260524.84f408017

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.
Files changed (30) hide show
  1. package/chunks/{agent-K6OWOMBN.js → agent-QFQGDV7B.js} +2 -2
  2. package/chunks/{chunk-3T4ZT63H.js → chunk-6JQC5PYU.js} +359 -49
  3. package/chunks/{chunk-WCZWAKFG.js → chunk-JUAMIT3V.js} +440 -523
  4. package/chunks/{chunk-NOAHME6A.js → chunk-LKSMYCUK.js} +1 -1
  5. package/chunks/{chunk-VMOAQVBP.js → chunk-M2TQETH7.js} +1 -1
  6. package/chunks/chunk-TEES5V4K.js +956 -0
  7. package/chunks/{chunk-L34E6AGL.js → chunk-UL4NCM6X.js} +5 -5
  8. package/chunks/{contextCommand-7CPNXBLO.js → contextCommand-3FI5VRHY.js} +4 -4
  9. package/chunks/{edit-CBM5NDVK.js → edit-JXJILHZW.js} +2 -2
  10. package/chunks/{enter-worktree-XABKPLO6.js → enter-worktree-HQ3RBRIA.js} +2 -2
  11. package/chunks/{exit-worktree-56MN2PCL.js → exit-worktree-73TYZY5X.js} +2 -2
  12. package/chunks/{exitPlanMode-YDNPCSCJ.js → exitPlanMode-WEETA2GM.js} +2 -2
  13. package/chunks/{geminiContentGenerator-ZGPNBFDS.js → geminiContentGenerator-TVVMIHRE.js} +1 -1
  14. package/chunks/{glob-ZHA35VO5.js → glob-HZYBHMPB.js} +2 -2
  15. package/chunks/{grep-RV6V6T52.js → grep-RO7NJDWP.js} +2 -2
  16. package/chunks/{monitor-5G2OBGE5.js → monitor-IOU376UD.js} +2 -2
  17. package/chunks/{notebook-edit-XUBTCT6L.js → notebook-edit-7NXIUSLW.js} +2 -2
  18. package/chunks/{openaiContentGenerator-POYAZQ6I.js → openaiContentGenerator-BXS7CQJ2.js} +2 -2
  19. package/chunks/{qwenContentGenerator-2E4H56DK.js → qwenContentGenerator-7VOJGPBD.js} +3 -3
  20. package/chunks/{read-file-3JIOOXFT.js → read-file-G4TFJFZ2.js} +1 -1
  21. package/chunks/{ripGrep-LEI3L6PM.js → ripGrep-IMMYV3HQ.js} +2 -2
  22. package/chunks/{serve-CFVRMD4W.js → serve-5EHHVHBV.js} +4440 -360
  23. package/chunks/{shell-3B5DZ437.js → shell-AXZQIDDU.js} +2 -2
  24. package/chunks/{skill-STSZUBXR.js → skill-IMUVSRRZ.js} +1 -1
  25. package/chunks/{src-ROFXAPEP.js → src-VYNNTRVU.js} +4 -2
  26. package/chunks/{tool-search-ARWOD3GD.js → tool-search-BTGLGBDQ.js} +1 -1
  27. package/chunks/{write-file-6MRT7TEW.js → write-file-BNUIFGU4.js} +2 -2
  28. package/cli.js +624 -16436
  29. package/package.json +2 -2
  30. package/chunks/chunk-4J63U5QO.js +0 -1974
@@ -159,7 +159,7 @@ import {
159
159
  truncateSpanError,
160
160
  truncateToolOutput,
161
161
  uiTelemetryService
162
- } from "./chunk-WCZWAKFG.js";
162
+ } from "./chunk-JUAMIT3V.js";
163
163
  import {
164
164
  DEFAULT_QWEN_EMBEDDING_MODEL,
165
165
  DEFAULT_QWEN_MODEL
@@ -35751,6 +35751,128 @@ var InvalidStreamError = class extends Error {
35751
35751
  this.type = type;
35752
35752
  }
35753
35753
  };
35754
+ var ORPHAN_TOOL_USE_REPAIR_REASON = "Tool execution result was not recorded \u2014 likely interrupted by network failure, abort, or process exit. Treat as failure and retry if needed.";
35755
+ function scanModelTurn(history, modelIdx) {
35756
+ const expected = /* @__PURE__ */ new Map();
35757
+ for (const part of history[modelIdx]?.parts ?? []) {
35758
+ const fc = part.functionCall;
35759
+ if (fc?.id) expected.set(fc.id, fc.name ?? "unknown");
35760
+ }
35761
+ const matched = /* @__PURE__ */ new Map();
35762
+ let scanIdx = modelIdx + 1;
35763
+ while (scanIdx < history.length && history[scanIdx]?.role === "user") {
35764
+ const parts2 = history[scanIdx].parts ?? [];
35765
+ for (let pIdx = 0; pIdx < parts2.length; pIdx++) {
35766
+ const part = parts2[pIdx];
35767
+ const id = part.functionResponse?.id;
35768
+ if (id) {
35769
+ const list3 = matched.get(id);
35770
+ if (list3) list3.push({ turnIdx: scanIdx, partIdx: pIdx, part });
35771
+ else matched.set(id, [{ turnIdx: scanIdx, partIdx: pIdx, part }]);
35772
+ }
35773
+ }
35774
+ scanIdx++;
35775
+ }
35776
+ return { modelIdx, expected, matched, scanEnd: scanIdx };
35777
+ }
35778
+ __name(scanModelTurn, "scanModelTurn");
35779
+ function planRepair(scan) {
35780
+ const synthesizeIds = [];
35781
+ const hoistedParts = [];
35782
+ const removalTargets = [];
35783
+ const droppedDuplicates = [];
35784
+ const adjacentIdx = scan.modelIdx + 1;
35785
+ for (const [id, name3] of scan.expected) {
35786
+ const locations = scan.matched.get(id);
35787
+ if (!locations || locations.length === 0) {
35788
+ synthesizeIds.push([id, name3]);
35789
+ continue;
35790
+ }
35791
+ const survivor = locations[0];
35792
+ if (survivor.turnIdx !== adjacentIdx) {
35793
+ hoistedParts.push(survivor.part);
35794
+ removalTargets.push({
35795
+ turnIdx: survivor.turnIdx,
35796
+ partIdx: survivor.partIdx
35797
+ });
35798
+ }
35799
+ for (let k = 1; k < locations.length; k++) {
35800
+ removalTargets.push({
35801
+ turnIdx: locations[k].turnIdx,
35802
+ partIdx: locations[k].partIdx
35803
+ });
35804
+ droppedDuplicates.push({ callId: id, name: name3 });
35805
+ }
35806
+ }
35807
+ return {
35808
+ modelIdx: scan.modelIdx,
35809
+ scanEnd: scan.scanEnd,
35810
+ synthesizeIds,
35811
+ hoistedParts,
35812
+ removalTargets,
35813
+ droppedDuplicates
35814
+ };
35815
+ }
35816
+ __name(planRepair, "planRepair");
35817
+ function applyRepair(history, plan, reason) {
35818
+ if (plan.synthesizeIds.length === 0 && plan.removalTargets.length === 0) {
35819
+ return { insertedBefore: 0 };
35820
+ }
35821
+ const syntheticParts = plan.synthesizeIds.map(([callId, name3]) => ({
35822
+ functionResponse: { id: callId, name: name3, response: { error: reason } }
35823
+ }));
35824
+ const partsToInject = [...syntheticParts, ...plan.hoistedParts];
35825
+ const removals = [...plan.removalTargets].sort((a, b) => {
35826
+ if (a.turnIdx !== b.turnIdx) return b.turnIdx - a.turnIdx;
35827
+ return b.partIdx - a.partIdx;
35828
+ });
35829
+ for (const loc of removals) {
35830
+ const turnParts = history[loc.turnIdx].parts;
35831
+ if (turnParts) turnParts.splice(loc.partIdx, 1);
35832
+ }
35833
+ const adjacentIdx = plan.modelIdx + 1;
35834
+ for (let j = plan.scanEnd - 1; j > adjacentIdx; j--) {
35835
+ if (history[j]?.role === "user" && (history[j].parts?.length ?? 0) === 0) {
35836
+ history.splice(j, 1);
35837
+ }
35838
+ }
35839
+ const next = history[adjacentIdx];
35840
+ if (next?.role === "user") {
35841
+ const existing = next.parts ?? [];
35842
+ const firstNonFr = existing.findIndex((part) => !part.functionResponse);
35843
+ const insertAt = firstNonFr === -1 ? existing.length : firstNonFr;
35844
+ next.parts = [
35845
+ ...existing.slice(0, insertAt),
35846
+ ...partsToInject,
35847
+ ...existing.slice(insertAt)
35848
+ ];
35849
+ return { insertedBefore: 0 };
35850
+ }
35851
+ history.splice(adjacentIdx, 0, { role: "user", parts: partsToInject });
35852
+ return { insertedBefore: 1 };
35853
+ }
35854
+ __name(applyRepair, "applyRepair");
35855
+ function repairOrphanedToolUseTurns(history, reason = ORPHAN_TOOL_USE_REPAIR_REASON) {
35856
+ const injected = [];
35857
+ const droppedDuplicates = [];
35858
+ for (let i2 = 0; i2 < history.length; i2++) {
35859
+ if (history[i2].role !== "model") continue;
35860
+ const scan = scanModelTurn(history, i2);
35861
+ if (scan.expected.size === 0) continue;
35862
+ const plan = planRepair(scan);
35863
+ if (plan.synthesizeIds.length === 0 && plan.removalTargets.length === 0) {
35864
+ continue;
35865
+ }
35866
+ const { insertedBefore } = applyRepair(history, plan, reason);
35867
+ for (const [callId, name3] of plan.synthesizeIds) {
35868
+ injected.push({ callId, name: name3 });
35869
+ }
35870
+ droppedDuplicates.push(...plan.droppedDuplicates);
35871
+ i2 += insertedBefore;
35872
+ }
35873
+ return { injected, droppedDuplicates };
35874
+ }
35875
+ __name(repairOrphanedToolUseTurns, "repairOrphanedToolUseTurns");
35754
35876
  var SESSION_START_CONTEXT_SENTINEL_START = '<qwen:session-start-context hidden="true">';
35755
35877
  var SESSION_START_CONTEXT_SENTINEL_END = "</qwen:session-start-context>";
35756
35878
  var SESSION_START_CONTEXT_HEADER = "SessionStart additional context";
@@ -35826,6 +35948,23 @@ var GeminiChat = class {
35826
35948
  * in a loop. Manual `/compress` still works (it passes `force=true`).
35827
35949
  */
35828
35950
  hasFailedCompressionAttempt = false;
35951
+ /**
35952
+ * Partial-push markers — index of the in-memory `model[partial fc]`
35953
+ * and the matching deferred JSONL record. See the canonical note
35954
+ * above `ORPHAN_TOOL_USE_REPAIR_REASON` for the lifecycle and the
35955
+ * wedge they prevent.
35956
+ */
35957
+ pendingPartialAssistantTurnIndex = null;
35958
+ pendingPartialAssistantRecord = null;
35959
+ /**
35960
+ * Reset both partial-push markers in lockstep. Every history-mutation
35961
+ * site uses this — single-field resets are a bug because the fields
35962
+ * are always paired by lifecycle.
35963
+ */
35964
+ clearPendingPartialState() {
35965
+ this.pendingPartialAssistantTurnIndex = null;
35966
+ this.pendingPartialAssistantRecord = null;
35967
+ }
35829
35968
  /**
35830
35969
  * Most recent prompt-token count reported by the model for *this* chat,
35831
35970
  * mirroring the value in {@link UiTelemetryService} for the main session.
@@ -35951,6 +36090,7 @@ var GeminiChat = class {
35951
36090
  streamDoneResolver = resolve30;
35952
36091
  });
35953
36092
  this.sendPromise = streamDonePromise;
36093
+ this.clearPendingPartialState();
35954
36094
  let compressionInfo;
35955
36095
  let requestContents;
35956
36096
  let userContentAdded = false;
@@ -35964,6 +36104,17 @@ var GeminiChat = class {
35964
36104
  const userContent = createUserContent(params.message);
35965
36105
  this.history.push(userContent);
35966
36106
  userContentAdded = true;
36107
+ const inlineRepair = repairOrphanedToolUseTurns(this.history);
36108
+ if (inlineRepair.injected.length > 0) {
36109
+ debugLogger15.warn(
36110
+ `[REPAIR] sendMessageStream inline pass synthesized ${inlineRepair.injected.length} functionResponse(s): ` + inlineRepair.injected.map((entry) => `${entry.name}(${entry.callId})`).join(", ")
36111
+ );
36112
+ }
36113
+ if (inlineRepair.droppedDuplicates.length > 0) {
36114
+ debugLogger15.warn(
36115
+ `[REPAIR] sendMessageStream inline pass dropped ${inlineRepair.droppedDuplicates.length} duplicate functionResponse(s): ` + inlineRepair.droppedDuplicates.map((entry) => `${entry.name}(${entry.callId})`).join(", ")
36116
+ );
36117
+ }
35967
36118
  requestContents = this.getRequestHistory();
35968
36119
  } catch (error) {
35969
36120
  if (userContentAdded) {
@@ -36015,8 +36166,21 @@ var GeminiChat = class {
36015
36166
  break;
36016
36167
  } catch (error) {
36017
36168
  lastError = error;
36169
+ const popPartialIfPushed = /* @__PURE__ */ __name(() => {
36170
+ const idx = self2.pendingPartialAssistantTurnIndex;
36171
+ if (idx === null) return;
36172
+ if (self2.history.length > idx && self2.history[idx]?.role === "model") {
36173
+ self2.history.splice(idx, 1);
36174
+ } else {
36175
+ debugLogger15.warn(
36176
+ `[PARTIAL_POP] Splice skipped: idx=${idx}, historyLength=${self2.history.length}, roleAtIdx=${self2.history[idx]?.role ?? "undefined"}`
36177
+ );
36178
+ }
36179
+ self2.clearPendingPartialState();
36180
+ }, "popPartialIfPushed");
36018
36181
  const isRateLimit = isRateLimitError(error, extraRetryErrorCodes);
36019
36182
  if (isRateLimit && rateLimitRetryCount < maxRateLimitRetries) {
36183
+ popPartialIfPushed();
36020
36184
  rateLimitRetryCount++;
36021
36185
  const delayMs = getRateLimitRetryDelayMs(rateLimitRetryCount, {
36022
36186
  ...RATE_LIMIT_RETRY_OPTIONS,
@@ -36081,6 +36245,7 @@ var GeminiChat = class {
36081
36245
  }
36082
36246
  );
36083
36247
  if (reactiveInfo.compressionStatus === 1 /* COMPRESSED */) {
36248
+ popPartialIfPushed();
36084
36249
  requestContents = self2.getRequestHistory();
36085
36250
  debugLogger15.info(
36086
36251
  `Reactive compression succeeded: ${reactiveInfo.originalTokenCount} -> ${reactiveInfo.newTokenCount} tokens.`
@@ -36118,6 +36283,7 @@ var GeminiChat = class {
36118
36283
  }
36119
36284
  const isTransientStreamError = error instanceof InvalidStreamError;
36120
36285
  if (isTransientStreamError && invalidStreamRetryCount < INVALID_STREAM_RETRY_CONFIG.maxRetries) {
36286
+ popPartialIfPushed();
36121
36287
  invalidStreamRetryCount++;
36122
36288
  const delayMs = INVALID_STREAM_RETRY_CONFIG.initialDelayMs * invalidStreamRetryCount;
36123
36289
  debugLogger15.warn(
@@ -36143,6 +36309,7 @@ var GeminiChat = class {
36143
36309
  const isContentError = error instanceof InvalidStreamError;
36144
36310
  if (isContentError) {
36145
36311
  if (attempt < INVALID_CONTENT_RETRY_OPTIONS.maxAttempts - 1) {
36312
+ popPartialIfPushed();
36146
36313
  logContentRetry(
36147
36314
  self2.config,
36148
36315
  new ContentRetryEvent(
@@ -36231,6 +36398,17 @@ var GeminiChat = class {
36231
36398
  }
36232
36399
  successfulRecoveries++;
36233
36400
  } catch (recoveryError) {
36401
+ const expectedIdx = self2.pendingPartialAssistantTurnIndex;
36402
+ const lastIdx = self2.history.length - 1;
36403
+ if (expectedIdx !== null && self2.history.length > 0 && self2.history[lastIdx]?.role === "model") {
36404
+ if (expectedIdx !== lastIdx) {
36405
+ debugLogger15.warn(
36406
+ `[RECOVERY_POP] Marker/last-index mismatch: marker=${expectedIdx}, lastIdx=${lastIdx}, historyLength=${self2.history.length}. Popping last entry as best-effort rollback \u2014 investigate any history mutation between processStreamResponse's partial push and this catch.`
36407
+ );
36408
+ }
36409
+ self2.history.pop();
36410
+ self2.clearPendingPartialState();
36411
+ }
36234
36412
  if (self2.history.length > 0 && self2.history[self2.history.length - 1].role === "user") {
36235
36413
  self2.history.pop();
36236
36414
  }
@@ -36271,6 +36449,18 @@ var GeminiChat = class {
36271
36449
  }
36272
36450
  } finally {
36273
36451
  streamDoneResolver();
36452
+ if (self2.pendingPartialAssistantRecord) {
36453
+ try {
36454
+ self2.chatRecordingService?.recordAssistantTurn(
36455
+ self2.pendingPartialAssistantRecord
36456
+ );
36457
+ } catch (recordErr) {
36458
+ debugLogger15.error(
36459
+ "[PARTIAL_FLUSH] Failed to persist deferred JSONL record: " + (recordErr instanceof Error ? recordErr.message : String(recordErr))
36460
+ );
36461
+ }
36462
+ self2.clearPendingPartialState();
36463
+ }
36274
36464
  }
36275
36465
  }();
36276
36466
  }
@@ -36399,26 +36589,53 @@ var GeminiChat = class {
36399
36589
  getHistoryLength() {
36400
36590
  return this.history.length;
36401
36591
  }
36592
+ /**
36593
+ * Set of `functionResponse.id` strings in user turns. Walk-only,
36594
+ * no clone — `useGeminiStream.handleCompletedTools` calls this per
36595
+ * tool-completion batch, so {@link getHistory}'s `structuredClone`
36596
+ * would stall the UI on long sessions.
36597
+ */
36598
+ getHistoryFunctionResponseIds() {
36599
+ const ids = /* @__PURE__ */ new Set();
36600
+ for (const entry of this.history) {
36601
+ if (entry.role !== "user") continue;
36602
+ for (const part of entry.parts ?? []) {
36603
+ const id = part.functionResponse?.id;
36604
+ if (id) ids.add(id);
36605
+ }
36606
+ }
36607
+ return ids;
36608
+ }
36402
36609
  /**
36403
36610
  * Clears the chat history.
36404
36611
  */
36405
36612
  clearHistory() {
36406
36613
  this.history = [];
36614
+ this.clearPendingPartialState();
36407
36615
  }
36408
36616
  /**
36409
36617
  * Adds a new entry to the chat history.
36410
36618
  */
36411
36619
  addHistory(content) {
36412
36620
  this.history.push(content);
36621
+ if (this.pendingPartialAssistantTurnIndex !== null || this.pendingPartialAssistantRecord !== null) {
36622
+ debugLogger15.error(
36623
+ "[INVARIANT_VIOLATION] addHistory called while a partial-push marker is active \u2014 clearing it."
36624
+ );
36625
+ }
36626
+ this.clearPendingPartialState();
36413
36627
  }
36414
36628
  setHistory(history) {
36415
36629
  this.history = history;
36630
+ this.clearPendingPartialState();
36416
36631
  }
36417
36632
  truncateHistory(keepCount) {
36418
36633
  this.history = this.history.slice(0, keepCount);
36634
+ this.clearPendingPartialState();
36419
36635
  }
36420
36636
  stripThoughtsFromHistory() {
36421
36637
  this.history = this.history.map(stripThoughtPartsFromContent).filter((content) => content !== null);
36638
+ this.clearPendingPartialState();
36422
36639
  }
36423
36640
  /**
36424
36641
  * Pop all orphaned trailing user entries from chat history.
@@ -36429,6 +36646,14 @@ var GeminiChat = class {
36429
36646
  while (this.history.length > 0 && this.history[this.history.length - 1].role === "user") {
36430
36647
  this.history.pop();
36431
36648
  }
36649
+ this.clearPendingPartialState();
36650
+ }
36651
+ /**
36652
+ * Instance wrapper around the free-function {@link repairOrphanedToolUseTurns}.
36653
+ * See the canonical note above `ORPHAN_TOOL_USE_REPAIR_REASON`.
36654
+ */
36655
+ repairOrphanedToolUseTurns(reason) {
36656
+ return repairOrphanedToolUseTurns(this.history, reason);
36432
36657
  }
36433
36658
  setTools(tools) {
36434
36659
  this.generationConfig.tools = tools;
@@ -36465,31 +36690,38 @@ This error was probably caused by cyclic schema references in one of the followi
36465
36690
  let usageMetadata;
36466
36691
  let hasToolCall = false;
36467
36692
  let hasFinishReason = false;
36468
- for await (const chunk of streamResponse) {
36469
- hasFinishReason ||= chunk?.candidates?.some((candidate) => candidate.finishReason) ?? false;
36470
- if (isValidResponse(chunk)) {
36471
- const content = chunk.candidates?.[0]?.content;
36472
- if (content?.parts) {
36473
- if (content.parts.some((part) => part.functionCall)) {
36474
- hasToolCall = true;
36475
- }
36476
- allModelParts.push(...content.parts);
36477
- }
36478
- }
36479
- if (chunk.usageMetadata) {
36480
- usageMetadata = chunk.usageMetadata;
36481
- const lastPromptTokenCount = usageMetadata.promptTokenCount || usageMetadata.totalTokenCount;
36482
- if (lastPromptTokenCount) {
36483
- this.lastPromptTokenCount = lastPromptTokenCount;
36484
- this.telemetryService?.setLastPromptTokenCount(lastPromptTokenCount);
36485
- }
36486
- if (usageMetadata.cachedContentTokenCount && this.telemetryService) {
36487
- this.telemetryService.setLastCachedContentTokenCount(
36488
- usageMetadata.cachedContentTokenCount
36489
- );
36693
+ let streamError = null;
36694
+ try {
36695
+ for await (const chunk of streamResponse) {
36696
+ hasFinishReason ||= chunk?.candidates?.some((candidate) => candidate.finishReason) ?? false;
36697
+ if (isValidResponse(chunk)) {
36698
+ const content = chunk.candidates?.[0]?.content;
36699
+ if (content?.parts) {
36700
+ if (content.parts.some((part) => part.functionCall)) {
36701
+ hasToolCall = true;
36702
+ }
36703
+ allModelParts.push(...content.parts);
36704
+ }
36705
+ }
36706
+ if (chunk.usageMetadata) {
36707
+ usageMetadata = chunk.usageMetadata;
36708
+ const lastPromptTokenCount = usageMetadata.promptTokenCount || usageMetadata.totalTokenCount;
36709
+ if (lastPromptTokenCount) {
36710
+ this.lastPromptTokenCount = lastPromptTokenCount;
36711
+ this.telemetryService?.setLastPromptTokenCount(
36712
+ lastPromptTokenCount
36713
+ );
36714
+ }
36715
+ if (usageMetadata.cachedContentTokenCount && this.telemetryService) {
36716
+ this.telemetryService.setLastCachedContentTokenCount(
36717
+ usageMetadata.cachedContentTokenCount
36718
+ );
36719
+ }
36490
36720
  }
36721
+ yield chunk;
36491
36722
  }
36492
- yield chunk;
36723
+ } catch (e) {
36724
+ streamError = e;
36493
36725
  }
36494
36726
  let thoughtContentPart;
36495
36727
  const thoughtText = allModelParts.filter((part) => part.thought).map((part) => part.text).join("").trim();
@@ -36516,9 +36748,10 @@ This error was probably caused by cyclic schema references in one of the followi
36516
36748
  }
36517
36749
  }
36518
36750
  const contentText = consolidatedHistoryParts.filter((part) => part.text).map((part) => part.text).join("").trim();
36519
- if (thoughtContentPart || contentText || hasToolCall || usageMetadata) {
36751
+ const willPersistToHistory = streamError === null || hasToolCall && (thoughtContentPart || consolidatedHistoryParts.length > 0);
36752
+ if (willPersistToHistory && (thoughtContentPart || contentText || hasToolCall || usageMetadata)) {
36520
36753
  const contextWindowSize = this.config.getContentGeneratorConfig()?.contextWindowSize;
36521
- this.chatRecordingService?.recordAssistantTurn({
36754
+ const recordArgs = {
36522
36755
  model,
36523
36756
  message: [
36524
36757
  ...thoughtContentPart ? [thoughtContentPart] : [],
@@ -36529,7 +36762,28 @@ This error was probably caused by cyclic schema references in one of the followi
36529
36762
  ],
36530
36763
  tokens: usageMetadata,
36531
36764
  contextWindowSize
36532
- });
36765
+ };
36766
+ if (streamError !== null) {
36767
+ this.pendingPartialAssistantRecord = recordArgs;
36768
+ } else {
36769
+ this.chatRecordingService?.recordAssistantTurn(recordArgs);
36770
+ }
36771
+ }
36772
+ if (streamError !== null) {
36773
+ if (willPersistToHistory) {
36774
+ this.history.push({
36775
+ role: "model",
36776
+ parts: [
36777
+ ...thoughtContentPart ? [thoughtContentPart] : [],
36778
+ ...consolidatedHistoryParts
36779
+ ]
36780
+ });
36781
+ this.pendingPartialAssistantTurnIndex = this.history.length - 1;
36782
+ debugLogger15.warn(
36783
+ `[PARTIAL_PUSH] Persisting partial assistant turn for mid-stream error recovery (will be rolled back if retry succeeds, kept if break is unretryable). pendingIndex=${this.pendingPartialAssistantTurnIndex} callIds=${consolidatedHistoryParts.map((p) => p.functionCall?.id).filter((id) => Boolean(id)).join(",")} error=${streamError instanceof Error ? streamError.message : String(streamError)}`
36784
+ );
36785
+ }
36786
+ throw streamError;
36533
36787
  }
36534
36788
  const hasAnyContent = contentText || thoughtText;
36535
36789
  if (!hasToolCall && (!hasFinishReason || !hasAnyContent)) {
@@ -67484,6 +67738,20 @@ var GeminiClient = class {
67484
67738
  }
67485
67739
  return void 0;
67486
67740
  }
67741
+ /**
67742
+ * Walk-only accessor for the set of `functionResponse.id` strings in
67743
+ * raw history. Callers that only need the dedup id set (notably
67744
+ * `useGeminiStream.handleCompletedTools`) MUST prefer this over
67745
+ * {@link getHistory}, which deep-clones the entire conversation via
67746
+ * `structuredClone` on every call. On long sessions with sizable
67747
+ * tool outputs the clone is a multi-millisecond hit on the React UI
67748
+ * thread; running it on every tool-completion batch caused visible
67749
+ * frame drops during streaming. See
67750
+ * `GeminiChat.getHistoryFunctionResponseIds` for the implementation.
67751
+ */
67752
+ getHistoryFunctionResponseIds() {
67753
+ return this.getChat().getHistoryFunctionResponseIds();
67754
+ }
67487
67755
  /**
67488
67756
  * Pop orphaned trailing user entries from the in-memory chat history.
67489
67757
  * Used by:
@@ -67511,6 +67779,46 @@ var GeminiClient = class {
67511
67779
  this.config.getFileReadCache().clear();
67512
67780
  this.forceFullIdeContext = true;
67513
67781
  }
67782
+ /**
67783
+ * Synthesize a `functionResponse` for every dangling `model[functionCall]`
67784
+ * in chat history whose corresponding tool_result never landed. Inverse of
67785
+ * {@link stripOrphanedUserEntriesFromHistory}, which only handles trailing
67786
+ * `user` entries.
67787
+ *
67788
+ * This `GeminiClient` method is the resume-path entry point — called once
67789
+ * from {@link startChat} after the transcript loads, covering `--resume`
67790
+ * of a session that crashed between a partial-tool_use push and the
67791
+ * tool's eventual completion.
67792
+ *
67793
+ * The other two coverage points (Retry submit path after
67794
+ * `stripOrphanedUserEntriesFromHistory`, and the defensive pass at the
67795
+ * start of every UserQuery / Cron send) live one layer down inside
67796
+ * `GeminiChat.sendMessageStream` and call the standalone
67797
+ * `repairOrphanedToolUseTurns(history)` function directly — they don't
67798
+ * route through this wrapper. Anyone tracing the repair-pass coupling
67799
+ * between the client and chat layers should follow that path
67800
+ * separately rather than expect everything to funnel through here.
67801
+ *
67802
+ * Synthesizes an `error` `functionResponse`. The React tool scheduler
67803
+ * (`useGeminiStream.handleCompletedTools`) MUST dedupe by `callId` against
67804
+ * the live history before submitting its own `tool_result` — otherwise a
67805
+ * late real result lands as a second `user[tool_result]` block (orphan
67806
+ * because the synthetic already consumed the matching `tool_use`).
67807
+ */
67808
+ repairOrphanedToolUseTurnsInHistory(reason) {
67809
+ const result = this.getChat().repairOrphanedToolUseTurns(reason);
67810
+ if (result.injected.length > 0) {
67811
+ debugLogger43.warn(
67812
+ `[REPAIR] Synthesized ${result.injected.length} functionResponse(s) for dangling tool_use(s): ${result.injected.map((e) => `${e.name}(${e.callId})`).join(", ")}`
67813
+ );
67814
+ }
67815
+ if (result.droppedDuplicates.length > 0) {
67816
+ debugLogger43.warn(
67817
+ `[REPAIR] Dropped ${result.droppedDuplicates.length} duplicate functionResponse(s) for callId(s): ${result.droppedDuplicates.map((e) => `${e.name}(${e.callId})`).join(", ")}`
67818
+ );
67819
+ }
67820
+ return result;
67821
+ }
67514
67822
  setHistory(history) {
67515
67823
  this.getChat().setHistory(history);
67516
67824
  debugLogger43.debug("[FILE_READ_CACHE] clear after setHistory");
@@ -67776,6 +68084,7 @@ var GeminiClient = class {
67776
68084
  this.config.getChatRecordingService(),
67777
68085
  uiTelemetryService
67778
68086
  );
68087
+ this.repairOrphanedToolUseTurnsInHistory();
67779
68088
  const sessionStartAdditionalContext = await this.fireSessionStartHook(sessionStartSource);
67780
68089
  this.lastSessionStartContext = sessionStartAdditionalContext;
67781
68090
  this.lastSessionStartSource = sessionStartAdditionalContext ? sessionStartSource : void 0;
@@ -104180,7 +104489,7 @@ async function findProjectRoot2(startDir) {
104180
104489
  }
104181
104490
  } catch (error) {
104182
104491
  const isENOENT2 = typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
104183
- const isTestEnv = process.env["NODE_ENV"] === "test" || process.env["VITEST"];
104492
+ const isTestEnv = process.env["VITEST"];
104184
104493
  if (!isENOENT2 && !isTestEnv) {
104185
104494
  if (typeof error === "object" && error !== null && "code" in error) {
104186
104495
  const fsError = error;
@@ -104343,7 +104652,7 @@ async function readGeminiMdFiles(filePaths, importFormat = "tree") {
104343
104652
  );
104344
104653
  return { filePath, content: processedResult.content };
104345
104654
  } catch (error) {
104346
- const isTestEnv = process.env["NODE_ENV"] === "test" || process.env["VITEST"];
104655
+ const isTestEnv = process.env["VITEST"];
104347
104656
  if (!isTestEnv) {
104348
104657
  const message = error instanceof Error ? error.message : String(error);
104349
104658
  logger3.warn(
@@ -107055,19 +107364,19 @@ var Config = class {
107055
107364
  }, "registerStructuredOutputIfRequested");
107056
107365
  if (this.getBareMode()) {
107057
107366
  await registerLazy(ToolNames.READ_FILE, async () => {
107058
- const { ReadFileTool } = await import("./read-file-3JIOOXFT.js");
107367
+ const { ReadFileTool } = await import("./read-file-G4TFJFZ2.js");
107059
107368
  return new ReadFileTool(this);
107060
107369
  });
107061
107370
  await registerLazy(ToolNames.EDIT, async () => {
107062
- const { EditTool } = await import("./edit-CBM5NDVK.js");
107371
+ const { EditTool } = await import("./edit-JXJILHZW.js");
107063
107372
  return new EditTool(this);
107064
107373
  });
107065
107374
  await registerLazy(ToolNames.NOTEBOOK_EDIT, async () => {
107066
- const { NotebookEditTool } = await import("./notebook-edit-XUBTCT6L.js");
107375
+ const { NotebookEditTool } = await import("./notebook-edit-7NXIUSLW.js");
107067
107376
  return new NotebookEditTool(this);
107068
107377
  });
107069
107378
  await registerLazy(ToolNames.SHELL, async () => {
107070
- const { ShellTool: ShellTool2 } = await import("./shell-3B5DZ437.js");
107379
+ const { ShellTool: ShellTool2 } = await import("./shell-AXZQIDDU.js");
107071
107380
  return new ShellTool2(this);
107072
107381
  });
107073
107382
  await registerStructuredOutputIfRequested();
@@ -107077,11 +107386,11 @@ var Config = class {
107077
107386
  return registry;
107078
107387
  }
107079
107388
  await registerLazy(ToolNames.TOOL_SEARCH, async () => {
107080
- const { ToolSearchTool } = await import("./tool-search-ARWOD3GD.js");
107389
+ const { ToolSearchTool } = await import("./tool-search-BTGLGBDQ.js");
107081
107390
  return new ToolSearchTool(this);
107082
107391
  });
107083
107392
  await registerLazy(ToolNames.AGENT, async () => {
107084
- const { AgentTool: AgentTool2 } = await import("./agent-K6OWOMBN.js");
107393
+ const { AgentTool: AgentTool2 } = await import("./agent-QFQGDV7B.js");
107085
107394
  return new AgentTool2(this);
107086
107395
  });
107087
107396
  await registerLazy(ToolNames.TASK_STOP, async () => {
@@ -107093,7 +107402,7 @@ var Config = class {
107093
107402
  return new SendMessageTool(this);
107094
107403
  });
107095
107404
  await registerLazy(ToolNames.SKILL, async () => {
107096
- const { SkillTool } = await import("./skill-STSZUBXR.js");
107405
+ const { SkillTool } = await import("./skill-IMUVSRRZ.js");
107097
107406
  return new SkillTool(this);
107098
107407
  });
107099
107408
  await registerLazy(ToolNames.LS, async () => {
@@ -107101,7 +107410,7 @@ var Config = class {
107101
107410
  return new LSTool(this);
107102
107411
  });
107103
107412
  await registerLazy(ToolNames.READ_FILE, async () => {
107104
- const { ReadFileTool } = await import("./read-file-3JIOOXFT.js");
107413
+ const { ReadFileTool } = await import("./read-file-G4TFJFZ2.js");
107105
107414
  return new ReadFileTool(this);
107106
107415
  });
107107
107416
  if (this.getUseRipgrep()) {
@@ -107114,7 +107423,7 @@ var Config = class {
107114
107423
  }
107115
107424
  if (useRipgrep) {
107116
107425
  await registerLazy(ToolNames.GREP, async () => {
107117
- const { RipGrepTool: RipGrepTool2 } = await import("./ripGrep-LEI3L6PM.js");
107426
+ const { RipGrepTool: RipGrepTool2 } = await import("./ripGrep-IMMYV3HQ.js");
107118
107427
  return new RipGrepTool2(this);
107119
107428
  });
107120
107429
  } else {
@@ -107127,34 +107436,34 @@ var Config = class {
107127
107436
  )
107128
107437
  );
107129
107438
  await registerLazy(ToolNames.GREP, async () => {
107130
- const { GrepTool } = await import("./grep-RV6V6T52.js");
107439
+ const { GrepTool } = await import("./grep-RO7NJDWP.js");
107131
107440
  return new GrepTool(this);
107132
107441
  });
107133
107442
  }
107134
107443
  } else {
107135
107444
  await registerLazy(ToolNames.GREP, async () => {
107136
- const { GrepTool } = await import("./grep-RV6V6T52.js");
107445
+ const { GrepTool } = await import("./grep-RO7NJDWP.js");
107137
107446
  return new GrepTool(this);
107138
107447
  });
107139
107448
  }
107140
107449
  await registerLazy(ToolNames.GLOB, async () => {
107141
- const { GlobTool } = await import("./glob-ZHA35VO5.js");
107450
+ const { GlobTool } = await import("./glob-HZYBHMPB.js");
107142
107451
  return new GlobTool(this);
107143
107452
  });
107144
107453
  await registerLazy(ToolNames.EDIT, async () => {
107145
- const { EditTool } = await import("./edit-CBM5NDVK.js");
107454
+ const { EditTool } = await import("./edit-JXJILHZW.js");
107146
107455
  return new EditTool(this);
107147
107456
  });
107148
107457
  await registerLazy(ToolNames.NOTEBOOK_EDIT, async () => {
107149
- const { NotebookEditTool } = await import("./notebook-edit-XUBTCT6L.js");
107458
+ const { NotebookEditTool } = await import("./notebook-edit-7NXIUSLW.js");
107150
107459
  return new NotebookEditTool(this);
107151
107460
  });
107152
107461
  await registerLazy(ToolNames.WRITE_FILE, async () => {
107153
- const { WriteFileTool } = await import("./write-file-6MRT7TEW.js");
107462
+ const { WriteFileTool } = await import("./write-file-BNUIFGU4.js");
107154
107463
  return new WriteFileTool(this);
107155
107464
  });
107156
107465
  await registerLazy(ToolNames.SHELL, async () => {
107157
- const { ShellTool: ShellTool2 } = await import("./shell-3B5DZ437.js");
107466
+ const { ShellTool: ShellTool2 } = await import("./shell-AXZQIDDU.js");
107158
107467
  return new ShellTool2(this);
107159
107468
  });
107160
107469
  await registerLazy(ToolNames.TODO_WRITE, async () => {
@@ -107167,16 +107476,16 @@ var Config = class {
107167
107476
  });
107168
107477
  if (!this.sdkMode) {
107169
107478
  await registerLazy(ToolNames.EXIT_PLAN_MODE, async () => {
107170
- const { ExitPlanModeTool } = await import("./exitPlanMode-YDNPCSCJ.js");
107479
+ const { ExitPlanModeTool } = await import("./exitPlanMode-WEETA2GM.js");
107171
107480
  return new ExitPlanModeTool(this);
107172
107481
  });
107173
107482
  }
107174
107483
  await registerLazy(ToolNames.ENTER_WORKTREE, async () => {
107175
- const { EnterWorktreeTool } = await import("./enter-worktree-XABKPLO6.js");
107484
+ const { EnterWorktreeTool } = await import("./enter-worktree-HQ3RBRIA.js");
107176
107485
  return new EnterWorktreeTool(this);
107177
107486
  });
107178
107487
  await registerLazy(ToolNames.EXIT_WORKTREE, async () => {
107179
- const { ExitWorktreeTool } = await import("./exit-worktree-56MN2PCL.js");
107488
+ const { ExitWorktreeTool } = await import("./exit-worktree-73TYZY5X.js");
107180
107489
  return new ExitWorktreeTool(this);
107181
107490
  });
107182
107491
  await registerLazy(ToolNames.WEB_FETCH, async () => {
@@ -107205,7 +107514,7 @@ var Config = class {
107205
107514
  });
107206
107515
  }
107207
107516
  await registerLazy(ToolNames.MONITOR, async () => {
107208
- const { MonitorTool } = await import("./monitor-5G2OBGE5.js");
107517
+ const { MonitorTool } = await import("./monitor-IOU376UD.js");
107209
107518
  return new MonitorTool(this);
107210
107519
  });
107211
107520
  if (this.pendingMcpBudgetCallback) {
@@ -122690,6 +122999,7 @@ export {
122690
122999
  StreamEventType,
122691
123000
  isValidNonThoughtTextPart,
122692
123001
  InvalidStreamError,
123002
+ repairOrphanedToolUseTurns,
122693
123003
  GeminiChat,
122694
123004
  isSchemaDepthError,
122695
123005
  isInvalidArgumentError,