@pentoshi/clai 3.11.9 → 3.11.11

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 (93) hide show
  1. package/dist/agent/compaction-summary.d.ts +2 -2
  2. package/dist/agent/compaction-summary.js +6 -2
  3. package/dist/agent/compaction-summary.js.map +1 -1
  4. package/dist/agent/context-manager.d.ts +3 -4
  5. package/dist/agent/context-manager.js +8 -11
  6. package/dist/agent/context-manager.js.map +1 -1
  7. package/dist/agent/events.d.ts +6 -0
  8. package/dist/agent/finalize-gate.d.ts +1 -0
  9. package/dist/agent/finalize-gate.js +2 -1
  10. package/dist/agent/finalize-gate.js.map +1 -1
  11. package/dist/agent/loop-guard.d.ts +13 -0
  12. package/dist/agent/loop-guard.js +54 -8
  13. package/dist/agent/loop-guard.js.map +1 -1
  14. package/dist/agent/reliability-policy.d.ts +5 -3
  15. package/dist/agent/reliability-policy.js +5 -1
  16. package/dist/agent/reliability-policy.js.map +1 -1
  17. package/dist/agent/request-budget.d.ts +7 -3
  18. package/dist/agent/request-budget.js +24 -7
  19. package/dist/agent/request-budget.js.map +1 -1
  20. package/dist/agent/runner.d.ts +2 -0
  21. package/dist/agent/runner.js +39 -19
  22. package/dist/agent/runner.js.map +1 -1
  23. package/dist/agent/tool-history.js +1 -1
  24. package/dist/agent/tool-history.js.map +1 -1
  25. package/dist/agent/turn-outcome.js +3 -0
  26. package/dist/agent/turn-outcome.js.map +1 -1
  27. package/dist/app/adapters/agent-event-adapter.js +6 -0
  28. package/dist/app/adapters/agent-event-adapter.js.map +1 -1
  29. package/dist/app/adapters/current-agent-adapter.js +1 -0
  30. package/dist/app/adapters/current-agent-adapter.js.map +1 -1
  31. package/dist/app/controllers/session-compact-helper.js +7 -11
  32. package/dist/app/controllers/session-compact-helper.js.map +1 -1
  33. package/dist/app/controllers/session-context-limits.d.ts +14 -0
  34. package/dist/app/controllers/session-context-limits.js +48 -0
  35. package/dist/app/controllers/session-context-limits.js.map +1 -0
  36. package/dist/app/controllers/session-context-usage.d.ts +3 -0
  37. package/dist/app/controllers/session-context-usage.js +27 -9
  38. package/dist/app/controllers/session-context-usage.js.map +1 -1
  39. package/dist/app/controllers/session-controller.d.ts +6 -3
  40. package/dist/app/controllers/session-controller.js +34 -7
  41. package/dist/app/controllers/session-controller.js.map +1 -1
  42. package/dist/app/controllers/session-turn-request.d.ts +1 -0
  43. package/dist/app/controllers/session-turn-request.js +3 -0
  44. package/dist/app/controllers/session-turn-request.js.map +1 -1
  45. package/dist/app/events/app-event.d.ts +4 -0
  46. package/dist/app/events/app-event.js.map +1 -1
  47. package/dist/app/ports/agent-port.d.ts +2 -0
  48. package/dist/llm/adapters/openai-tools.d.ts +1 -0
  49. package/dist/llm/adapters/openai-tools.js +10 -0
  50. package/dist/llm/adapters/openai-tools.js.map +1 -1
  51. package/dist/llm/key-rotation.d.ts +1 -1
  52. package/dist/llm/key-rotation.js +4 -0
  53. package/dist/llm/key-rotation.js.map +1 -1
  54. package/dist/llm/router.d.ts +2 -0
  55. package/dist/llm/router.js +35 -3
  56. package/dist/llm/router.js.map +1 -1
  57. package/dist/llm/token-usage.d.ts +3 -2
  58. package/dist/llm/token-usage.js +6 -3
  59. package/dist/llm/token-usage.js.map +1 -1
  60. package/dist/prompts/index.d.ts +4 -1
  61. package/dist/prompts/index.js +12 -3
  62. package/dist/prompts/index.js.map +1 -1
  63. package/dist/store/config.d.ts +5 -0
  64. package/dist/store/config.js +38 -4
  65. package/dist/store/config.js.map +1 -1
  66. package/dist/store/keys.js +2 -2
  67. package/dist/store/keys.js.map +1 -1
  68. package/dist/tools/file-diff.js +59 -27
  69. package/dist/tools/file-diff.js.map +1 -1
  70. package/dist/tui-v2/bootstrap/composition-root.js +9 -0
  71. package/dist/tui-v2/bootstrap/composition-root.js.map +1 -1
  72. package/dist/tui-v2/components/status/context-limit-chip.d.ts +17 -0
  73. package/dist/tui-v2/components/status/context-limit-chip.js +72 -0
  74. package/dist/tui-v2/components/status/context-limit-chip.js.map +1 -0
  75. package/dist/tui-v2/components/status/status-line.d.ts +3 -4
  76. package/dist/tui-v2/components/status/status-line.js +6 -17
  77. package/dist/tui-v2/components/status/status-line.js.map +1 -1
  78. package/dist/tui-v2/components/transcript/transcript-view.js +8 -7
  79. package/dist/tui-v2/components/transcript/transcript-view.js.map +1 -1
  80. package/dist/tui-v2/components/transcript/use-transcript-selection.js +57 -22
  81. package/dist/tui-v2/components/transcript/use-transcript-selection.js.map +1 -1
  82. package/dist/tui-v2/composer/composer-editor.js +5 -1
  83. package/dist/tui-v2/composer/composer-editor.js.map +1 -1
  84. package/dist/tui-v2/controllers/selection-controller.js +7 -1
  85. package/dist/tui-v2/controllers/selection-controller.js.map +1 -1
  86. package/dist/tui-v2/state/semantic-document.js +13 -1
  87. package/dist/tui-v2/state/semantic-document.js.map +1 -1
  88. package/dist/tui-v2/state/transcript-reducer.js +12 -4
  89. package/dist/tui-v2/state/transcript-reducer.js.map +1 -1
  90. package/dist/version.generated.d.ts +2 -2
  91. package/dist/version.generated.js +2 -2
  92. package/dist/version.generated.js.map +1 -1
  93. package/package.json +1 -1
@@ -4,7 +4,9 @@ import { modelContextWindow } from "../llm/token-usage.js";
4
4
  // request tokens (system blocks, history, tool protocol, schemas and media
5
5
  // together) rather than in transcript size. Behaviour, status line and
6
6
  // reliability policy all read this module so they cannot disagree.
7
- export const DEFAULT_AUTO_COMPACT_REQUEST_TOKENS = 120_000;
7
+ export const DEFAULT_AUTO_COMPACT_REQUEST_TOKENS = 180_000;
8
+ /** A session-specific model window reserves 30% for output and wire overhead. */
9
+ export const CUSTOM_CONTEXT_COMPACTION_RATIO = 0.7;
8
10
  // Never trigger below this: compaction itself needs room to be useful.
9
11
  export const MIN_AUTO_COMPACT_REQUEST_TOKENS = 20_000;
10
12
  // Output allowance reserved inside the model window before the trigger.
@@ -35,25 +37,40 @@ export function modelSafeRequestTokens(provider, model) {
35
37
  return Math.max(MIN_AUTO_COMPACT_REQUEST_TOKENS, window - RESERVED_OUTPUT_TOKENS - SAFETY_MARGIN_TOKENS);
36
38
  }
37
39
  export function resolveRequestBudget(input) {
40
+ const customLimit = input?.contextLimitTokens;
41
+ if (typeof customLimit === "number" &&
42
+ Number.isFinite(customLimit) &&
43
+ customLimit >= MIN_AUTO_COMPACT_REQUEST_TOKENS) {
44
+ const modelSafe = Math.floor(customLimit);
45
+ const effectiveTrigger = Math.floor(modelSafe * CUSTOM_CONTEXT_COMPACTION_RATIO);
46
+ return {
47
+ configured: effectiveTrigger,
48
+ modelSafe,
49
+ effectiveTrigger,
50
+ source: "session",
51
+ clampedByModel: false,
52
+ };
53
+ }
38
54
  const resolved = configuredRequestTokens();
39
55
  const raw = input?.overrideTokens ?? resolved.tokens;
40
56
  const configured = Math.max(MIN_AUTO_COMPACT_REQUEST_TOKENS, raw);
41
57
  const modelSafe = modelSafeRequestTokens(input?.provider, input?.model);
42
- const effectiveTrigger = Math.min(configured, modelSafe);
43
58
  return {
44
59
  configured,
45
60
  modelSafe,
46
- effectiveTrigger,
61
+ effectiveTrigger: configured,
47
62
  source: input?.overrideTokens === undefined ? resolved.source : "explicit",
48
- clampedByModel: modelSafe < configured,
63
+ // The explicit 180k default is provider/model-neutral. A user can opt into
64
+ // a different window through the session control, which always uses 70%.
65
+ clampedByModel: false,
49
66
  };
50
67
  }
51
- // Status/footer denominator: the request budget that actually governs the next
52
- // request, not the raw model window.
53
- export function requestBudgetDenominator(provider, model) {
68
+ // Request budget used by callers that need the active compaction trigger.
69
+ export function requestBudgetDenominator(provider, model, contextLimitTokens) {
54
70
  return resolveRequestBudget({
55
71
  ...(provider ? { provider } : {}),
56
72
  ...(model ? { model } : {}),
73
+ ...(contextLimitTokens ? { contextLimitTokens } : {}),
57
74
  }).effectiveTrigger;
58
75
  }
59
76
  //# sourceMappingURL=request-budget.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"request-budget.js","sourceRoot":"","sources":["../../src/agent/request-budget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D,yEAAyE;AACzE,2EAA2E;AAC3E,uEAAuE;AACvE,mEAAmE;AACnE,MAAM,CAAC,MAAM,mCAAmC,GAAG,OAAO,CAAC;AAE3D,uEAAuE;AACvE,MAAM,CAAC,MAAM,+BAA+B,GAAG,MAAM,CAAC;AAEtD,wEAAwE;AACxE,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE7C,4DAA4D;AAC5D,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAiB1C,0EAA0E;AAC1E,+EAA+E;AAC/E,yEAAyE;AACzE,MAAM,UAAU,uBAAuB;IAIrC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,IACE,oBAAoB,CAAC,0BAA0B,CAAC;QAChD,OAAO,MAAM,CAAC,wBAAwB,KAAK,QAAQ,EACnD,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,wBAAwB,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACzE,CAAC;IACD,IACE,oBAAoB,CAAC,wBAAwB,CAAC;QAC9C,OAAO,MAAM,CAAC,sBAAsB,KAAK,QAAQ,EACjD,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,sBAAsB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACrE,CAAC;IACD,OAAO;QACL,MAAM,EAAE,mCAAmC;QAC3C,MAAM,EAAE,SAAS;KAClB,CAAC;AACJ,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,sBAAsB,CACpC,QAAgC,EAChC,KAAyB;IAEzB,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACnD,OAAO,IAAI,CAAC,GAAG,CACb,+BAA+B,EAC/B,MAAM,GAAG,sBAAsB,GAAG,oBAAoB,CACvD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAIpC;IACC,MAAM,QAAQ,GAAG,uBAAuB,EAAE,CAAC;IAC3C,MAAM,GAAG,GAAG,KAAK,EAAE,cAAc,IAAI,QAAQ,CAAC,MAAM,CAAC;IACrD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC;IAClE,MAAM,SAAS,GAAG,sBAAsB,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACxE,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IACzD,OAAO;QACL,UAAU;QACV,SAAS;QACT,gBAAgB;QAChB,MAAM,EAAE,KAAK,EAAE,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU;QAC1E,cAAc,EAAE,SAAS,GAAG,UAAU;KACvC,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,qCAAqC;AACrC,MAAM,UAAU,wBAAwB,CACtC,QAAgC,EAChC,KAAyB;IAEzB,OAAO,oBAAoB,CAAC;QAC1B,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5B,CAAC,CAAC,gBAAgB,CAAC;AACtB,CAAC"}
1
+ {"version":3,"file":"request-budget.js","sourceRoot":"","sources":["../../src/agent/request-budget.ts"],"names":[],"mappings":"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"}
@@ -56,6 +56,8 @@ export interface AgentRunOptions {
56
56
  displayPrompt?: string | null | undefined;
57
57
  /** How the previous turn ended, so recovery is decided from state. */
58
58
  previousTurn?: PreviousTurnSignal | undefined;
59
+ /** User-declared model window for this provider/model/session. */
60
+ contextLimitTokens?: number | undefined;
59
61
  }
60
62
  export declare function runAgentTurn(prompt: string, options?: AgentRunOptions): Promise<import("./turn-outcome.js").TurnOutcome>;
61
63
  /** Compatibility boundary for callers that consume visible assistant text. */
@@ -60,7 +60,6 @@ import { maybeAppendPlanModeReminder, PLAN_REMINDER_TOAST, } from "./plan-mode-r
60
60
  import { LoopGuard } from "./loop-guard.js";
61
61
  import { appendInterruptedReasoning, interruptedReasoningBrief, isMeaningfulResumptionYield, } from "./interrupted-reasoning.js";
62
62
  import { CompactionAttemptLedger, compactionAttemptKey, } from "./compaction-attempt.js";
63
- import { resolveRequestBudget } from "./request-budget.js";
64
63
  import { loadPlan, mutatePlan, markTask, appendPlanTask, readyPlanTasks, foregroundRemaining, responderOpenTasks, isPlanTerminal, isPlanSuccessful, } from "../store/plan.js";
65
64
  import { stat } from "node:fs/promises";
66
65
  import { isOutsideWorkingDirectory, resolveFsToolPath, } from "../tools/fs.js";
@@ -558,10 +557,11 @@ export async function runAgentTurn(prompt, options = {}) {
558
557
  imageView: visionAvailable,
559
558
  // E6: slim native constitution when API tool schemas are attached.
560
559
  ...(native ? { slimNative: reliability.slimNativePrompt } : {}),
561
- ...(pentestPromptTurn ? { pentest: true } : {}),
562
560
  });
563
561
  };
564
- const systemSections = [renderRequestEnvironmentContext()];
562
+ const systemSections = [
563
+ renderRequestEnvironmentContext({ plan: activePlan }),
564
+ ];
565
565
  if (projectContext) {
566
566
  systemSections.push(`Project context from .clai/context.md:\n${projectContext}`);
567
567
  }
@@ -2829,10 +2829,10 @@ export async function runAgentTurn(prompt, options = {}) {
2829
2829
  { role: "user", content: summaryPrompt },
2830
2830
  ],
2831
2831
  temperature: 0.1,
2832
- maxTokens: 2_048,
2832
+ maxTokens: 8_192,
2833
2833
  signal: options.signal,
2834
2834
  };
2835
- const response = await streamWithProvider(request, (token) => deltaParser?.push(token), { onStatus: () => undefined });
2835
+ const response = await streamWithProvider(request, (token) => deltaParser?.push(token), { onStatus: () => undefined, maxRetries: 1 });
2836
2836
  deltaParser?.finish();
2837
2837
  const parsed = stripThinking(response.text);
2838
2838
  if (parsed.visible.trim() || !parsed.hasThinking)
@@ -2849,7 +2849,7 @@ export async function runAgentTurn(prompt, options = {}) {
2849
2849
  temperature: 0,
2850
2850
  maxTokens: 8_192,
2851
2851
  thinking: { enabled: false, effort: "none" },
2852
- });
2852
+ }, { maxRetries: 1 });
2853
2853
  const retryVisible = stripThinking(retry.text).visible.trim();
2854
2854
  if (retryVisible && compactionId) {
2855
2855
  writeCompactionDelta(compactionId, retryVisible);
@@ -2918,8 +2918,13 @@ export async function runAgentTurn(prompt, options = {}) {
2918
2918
  }
2919
2919
  async function maybeAutoCompact(reason, force = false) {
2920
2920
  const beforeTokens = estimateNextRequestTokens(messages);
2921
- const budget = resolveRequestBudget({ provider, model });
2922
- const compactTrigger = budget.effectiveTrigger;
2921
+ const compactTrigger = autoCompactTriggerTokens(getReliabilityPolicy(), {
2922
+ provider,
2923
+ model,
2924
+ ...(options.contextLimitTokens
2925
+ ? { contextLimitTokens: options.contextLimitTokens }
2926
+ : {}),
2927
+ });
2923
2928
  if (!force && beforeTokens < compactTrigger)
2924
2929
  return;
2925
2930
  // Structural eligibility only: there must be closed history to summarize.
@@ -3003,12 +3008,10 @@ export async function runAgentTurn(prompt, options = {}) {
3003
3008
  : insertedText(insertedSummary, insertedSummary.startsWith(PLAN_IMPLEMENT_MEMORY_PREFIX)
3004
3009
  ? PLAN_IMPLEMENT_MEMORY_PREFIX
3005
3010
  : COMPACTION_MEMORY_PREFIX);
3006
- // Card shows pre/post of the summarization; plan re-injection is noted.
3007
- writeCompactionCompleted(compactionId, summaryText, beforeTokens, compactedTokens);
3008
- const planNote = afterTokens > compactedTokens
3009
- ? ` (compacted to ~${compactedTokens.toLocaleString()}, +plan → ~${afterTokens.toLocaleString()})`
3010
- : "";
3011
- writeNotice("info", `context auto-compacted to fit the window (~${beforeTokens.toLocaleString()} → ~${compactedTokens.toLocaleString()} tokens${planNote})`, chalk.dim(` ℹ context auto-compacted (~${beforeTokens.toLocaleString()} → ~${compactedTokens.toLocaleString()} tokens${planNote})\n`));
3011
+ // Report the final assembled request, including live plan and session
3012
+ // state reinjection, so the card and the next provider request agree.
3013
+ writeCompactionCompleted(compactionId, summaryText, beforeTokens, afterTokens);
3014
+ writeNotice("info", `context auto-compacted to fit the window (~${beforeTokens.toLocaleString()} ~${afterTokens.toLocaleString()} tokens)`, chalk.dim(` ℹ context auto-compacted (~${beforeTokens.toLocaleString()} → ~${afterTokens.toLocaleString()} tokens)\n`));
3012
3015
  }
3013
3016
  catch (error) {
3014
3017
  const message = error instanceof Error ? error.message : String(error);
@@ -3121,7 +3124,7 @@ export async function runAgentTurn(prompt, options = {}) {
3121
3124
  ? `${seconds}s`
3122
3125
  : `${Math.floor(seconds / 60)}m${String(seconds % 60).padStart(2, "0")}s`;
3123
3126
  if (generatedTokens > 0 && !inThinking) {
3124
- return `generating response · ${generatedTokens} tokens · ${elapsed}`;
3127
+ return `generating response · ${elapsed}`;
3125
3128
  }
3126
3129
  if (sawReasoning)
3127
3130
  return `thinking · ${elapsed}`;
@@ -3166,6 +3169,11 @@ export async function runAgentTurn(prompt, options = {}) {
3166
3169
  const turnTools = selectToolDefs(nativeToolsActive, useCompactSystemPrompt);
3167
3170
  toolsAttached = Boolean(turnTools?.length);
3168
3171
  const contextBreakdown = buildContextBreakdown(messages, toolsAttached ? turnTools : undefined);
3172
+ emit({
3173
+ type: "context-estimate",
3174
+ estimatedTokens: contextBreakdown.estimatedTotalTokens,
3175
+ model,
3176
+ });
3169
3177
  // E4: advisory only — never blocks free-tier users.
3170
3178
  if (!freeTierLargeContextWarned) {
3171
3179
  const notices = freeTierGuardNotices({
@@ -3721,7 +3729,10 @@ export async function runAgentTurn(prompt, options = {}) {
3721
3729
  const salvageHistoryCalls = ensureUniqueToolCallIds(nativeToolCalls, toolCallIdsInHistory(messages));
3722
3730
  const salvagedCallIndex = nativeToolCalls.indexOf(writeTc);
3723
3731
  const salvagedCallId = salvageHistoryCalls[salvagedCallIndex]?.id ?? writeTc.id;
3724
- appendAssistantWithTools(messages, assistantText.visible, salvageHistoryCalls, completion.reasoningBlock);
3732
+ appendAssistantWithTools(messages, assistantText.visible, salvageHistoryCalls, completion.reasoningBlock ??
3733
+ (assistantText.hasThinking && assistantText.thinkContent
3734
+ ? { text: assistantText.thinkContent }
3735
+ : undefined));
3725
3736
  for (const tc of salvageHistoryCalls) {
3726
3737
  appendToolResult(messages, tc.id, tc.id === salvagedCallId
3727
3738
  ? `Tool ${tc.name} result (exit=0, ok=true):\nSalvaged partial write: ${lineCount} lines to ${salvaged.path}`
@@ -4071,6 +4082,7 @@ export async function runAgentTurn(prompt, options = {}) {
4071
4082
  sawServerTail,
4072
4083
  sawLocalHttpProbe,
4073
4084
  sawFailedLocalHttpProbe,
4085
+ serverCriterionRequired: outcomeState.outcome.criteria.some((criterion) => criterion.domain === "server" && criterion.required),
4074
4086
  sawActivePentestTest,
4075
4087
  sawSuccessfulMutation,
4076
4088
  featureAppAsk,
@@ -4254,7 +4266,9 @@ export async function runAgentTurn(prompt, options = {}) {
4254
4266
  if (sequenceDecision.suppress) {
4255
4267
  const reason = sequenceDecision.terminal
4256
4268
  ? "The model repeated the same action sequence after it was already suppressed. No commands were run again."
4257
- : "The same action sequence already ran in the previous model round. No commands were run again; reuse the existing results and choose a materially different next action or finish.";
4269
+ : sequenceDecision.oscillation
4270
+ ? "This exact action sequence already completed earlier this turn (the agent is oscillating back to finished work). No commands were run again; every one of these results is already in context — synthesize them and either advance to a genuinely new action or finish."
4271
+ : "The same action sequence already ran in the previous model round. No commands were run again; reuse the existing results and choose a materially different next action or finish.";
4258
4272
  writeNotice("warn", reason, chalk.yellow(` ⚠ ${reason}\n`));
4259
4273
  const suppressedResults = bound.map((b) => {
4260
4274
  const duplicate = runIds.has(b.id);
@@ -4284,7 +4298,10 @@ export async function runAgentTurn(prompt, options = {}) {
4284
4298
  emitToolResult(eventId, result, resultReason);
4285
4299
  }
4286
4300
  if (historyNativeCalls.length) {
4287
- appendAssistantWithTools(messages, beforeTool ?? "", historyNativeCalls, completion.reasoningBlock);
4301
+ appendAssistantWithTools(messages, beforeTool ?? "", historyNativeCalls, completion.reasoningBlock ??
4302
+ (assistantText.hasThinking && assistantText.thinkContent
4303
+ ? { text: assistantText.thinkContent }
4304
+ : undefined));
4288
4305
  for (const { b, resultReason, result } of suppressedResults) {
4289
4306
  appendToolResult(messages, b.id, `Tool ${b.call.name} result (exit=${result.exitCode}, ok=false):\n${resultReason}`, b.call.name, false);
4290
4307
  }
@@ -4325,7 +4342,10 @@ export async function runAgentTurn(prompt, options = {}) {
4325
4342
  }
4326
4343
  }
4327
4344
  if (historyNativeCalls.length) {
4328
- appendAssistantWithTools(messages, beforeTool ?? "", historyNativeCalls, completion.reasoningBlock);
4345
+ appendAssistantWithTools(messages, beforeTool ?? "", historyNativeCalls, completion.reasoningBlock ??
4346
+ (assistantText.hasThinking && assistantText.thinkContent
4347
+ ? { text: assistantText.thinkContent }
4348
+ : undefined));
4329
4349
  }
4330
4350
  else {
4331
4351
  const standardizedContent = (beforeTool ? beforeTool.trim() + "\n\n" : "") +