@averyyy/pi-coding-agent 0.80.10-piclient.2 → 0.81.1-piclient.1

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 (143) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +6 -3
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +2 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-session-services.d.ts.map +1 -1
  7. package/dist/core/agent-session-services.js +13 -0
  8. package/dist/core/agent-session-services.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +22 -3
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +86 -58
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/compaction/branch-summarization.d.ts +7 -1
  14. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  15. package/dist/core/compaction/branch-summarization.js +6 -2
  16. package/dist/core/compaction/branch-summarization.js.map +1 -1
  17. package/dist/core/compaction/compaction.d.ts +23 -4
  18. package/dist/core/compaction/compaction.d.ts.map +1 -1
  19. package/dist/core/compaction/compaction.js +75 -29
  20. package/dist/core/compaction/compaction.js.map +1 -1
  21. package/dist/core/compaction/utils.d.ts +1 -1
  22. package/dist/core/compaction/utils.d.ts.map +1 -1
  23. package/dist/core/compaction/utils.js +6 -17
  24. package/dist/core/compaction/utils.js.map +1 -1
  25. package/dist/core/extensions/loader.d.ts.map +1 -1
  26. package/dist/core/extensions/loader.js +13 -2
  27. package/dist/core/extensions/loader.js.map +1 -1
  28. package/dist/core/extensions/runner.d.ts +2 -1
  29. package/dist/core/extensions/runner.d.ts.map +1 -1
  30. package/dist/core/extensions/runner.js +31 -0
  31. package/dist/core/extensions/runner.js.map +1 -1
  32. package/dist/core/extensions/types.d.ts +16 -2
  33. package/dist/core/extensions/types.d.ts.map +1 -1
  34. package/dist/core/extensions/types.js.map +1 -1
  35. package/dist/core/model-registry.d.ts +5 -1
  36. package/dist/core/model-registry.d.ts.map +1 -1
  37. package/dist/core/model-registry.js +17 -2
  38. package/dist/core/model-registry.js.map +1 -1
  39. package/dist/core/model-resolver.d.ts.map +1 -1
  40. package/dist/core/model-resolver.js +2 -0
  41. package/dist/core/model-resolver.js.map +1 -1
  42. package/dist/core/model-runtime.d.ts +7 -2
  43. package/dist/core/model-runtime.d.ts.map +1 -1
  44. package/dist/core/model-runtime.js +41 -18
  45. package/dist/core/model-runtime.js.map +1 -1
  46. package/dist/core/prompt-templates.d.ts +1 -0
  47. package/dist/core/prompt-templates.d.ts.map +1 -1
  48. package/dist/core/prompt-templates.js +4 -4
  49. package/dist/core/prompt-templates.js.map +1 -1
  50. package/dist/core/remote-catalog-provider.d.ts +1 -1
  51. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  52. package/dist/core/remote-catalog-provider.js +26 -7
  53. package/dist/core/remote-catalog-provider.js.map +1 -1
  54. package/dist/core/resource-loader.d.ts.map +1 -1
  55. package/dist/core/resource-loader.js +1 -0
  56. package/dist/core/resource-loader.js.map +1 -1
  57. package/dist/core/sdk.d.ts.map +1 -1
  58. package/dist/core/sdk.js +6 -2
  59. package/dist/core/sdk.js.map +1 -1
  60. package/dist/core/session-manager.d.ts +9 -4
  61. package/dist/core/session-manager.d.ts.map +1 -1
  62. package/dist/core/session-manager.js +99 -23
  63. package/dist/core/session-manager.js.map +1 -1
  64. package/dist/core/tools/read.d.ts.map +1 -1
  65. package/dist/core/tools/read.js +1 -1
  66. package/dist/core/tools/read.js.map +1 -1
  67. package/dist/core/usage-totals.d.ts +19 -0
  68. package/dist/core/usage-totals.d.ts.map +1 -0
  69. package/dist/core/usage-totals.js +52 -0
  70. package/dist/core/usage-totals.js.map +1 -0
  71. package/dist/extensions/index.d.ts +3 -0
  72. package/dist/extensions/index.d.ts.map +1 -0
  73. package/dist/extensions/index.js +3 -0
  74. package/dist/extensions/index.js.map +1 -0
  75. package/dist/extensions/llama/client.d.ts +61 -0
  76. package/dist/extensions/llama/client.d.ts.map +1 -0
  77. package/dist/extensions/llama/client.js +302 -0
  78. package/dist/extensions/llama/client.js.map +1 -0
  79. package/dist/extensions/llama/huggingface.d.ts +23 -0
  80. package/dist/extensions/llama/huggingface.d.ts.map +1 -0
  81. package/dist/extensions/llama/huggingface.js +141 -0
  82. package/dist/extensions/llama/huggingface.js.map +1 -0
  83. package/dist/extensions/llama/index.d.ts +3 -0
  84. package/dist/extensions/llama/index.d.ts.map +1 -0
  85. package/dist/extensions/llama/index.js +208 -0
  86. package/dist/extensions/llama/index.js.map +1 -0
  87. package/dist/extensions/llama/provider.d.ts +10 -0
  88. package/dist/extensions/llama/provider.d.ts.map +1 -0
  89. package/dist/extensions/llama/provider.js +102 -0
  90. package/dist/extensions/llama/provider.js.map +1 -0
  91. package/dist/extensions/llama/ui.d.ts +42 -0
  92. package/dist/extensions/llama/ui.d.ts.map +1 -0
  93. package/dist/extensions/llama/ui.js +416 -0
  94. package/dist/extensions/llama/ui.js.map +1 -0
  95. package/dist/index.d.ts +2 -2
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +1 -1
  98. package/dist/index.js.map +1 -1
  99. package/dist/main.d.ts.map +1 -1
  100. package/dist/main.js +10 -4
  101. package/dist/main.js.map +1 -1
  102. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  103. package/dist/modes/interactive/components/footer.js +24 -23
  104. package/dist/modes/interactive/components/footer.js.map +1 -1
  105. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  106. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  107. package/dist/modes/interactive/interactive-mode.js +48 -26
  108. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  109. package/dist/modes/rpc/rpc-client.d.ts +4 -0
  110. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  111. package/dist/modes/rpc/rpc-client.js +7 -0
  112. package/dist/modes/rpc/rpc-client.js.map +1 -1
  113. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  114. package/dist/modes/rpc/rpc-mode.js +4 -0
  115. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  116. package/dist/modes/rpc/rpc-types.d.ts +11 -0
  117. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  118. package/dist/modes/rpc/rpc-types.js.map +1 -1
  119. package/docs/compaction.md +8 -3
  120. package/docs/custom-provider.md +28 -0
  121. package/docs/extensions.md +51 -9
  122. package/docs/index.md +1 -0
  123. package/docs/json.md +5 -1
  124. package/docs/llama-cpp.md +99 -0
  125. package/docs/prompt-templates.md +1 -0
  126. package/docs/providers.md +11 -0
  127. package/docs/rpc.md +81 -2
  128. package/docs/sdk.md +3 -0
  129. package/docs/session-format.md +17 -3
  130. package/docs/tui.md +41 -26
  131. package/docs/usage.md +2 -1
  132. package/examples/extensions/README.md +1 -1
  133. package/examples/extensions/custom-compaction.ts +1 -0
  134. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  135. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  136. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  137. package/examples/extensions/gondolin/package-lock.json +2 -2
  138. package/examples/extensions/gondolin/package.json +1 -1
  139. package/examples/extensions/sandbox/package-lock.json +2 -2
  140. package/examples/extensions/sandbox/package.json +1 -1
  141. package/examples/extensions/with-deps/package-lock.json +2 -2
  142. package/examples/extensions/with-deps/package.json +1 -1
  143. package/package.json +4 -4
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
16
  import { basename, dirname } from "node:path";
17
+ import { contentText } from "@earendil-works/pi-ai";
17
18
  import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, isRetryableAssistantError, modelsAreEqual, resetApiProviders, streamSimple, } from "@earendil-works/pi-ai/compat";
18
19
  import { getThemeByName, theme } from "../modes/interactive/theme/theme.js";
19
20
  import { stripFrontmatter } from "../utils/frontmatter.js";
@@ -36,6 +37,7 @@ import { buildSystemPrompt } from "./system-prompt.js";
36
37
  import { createLocalBashOperations } from "./tools/bash.js";
37
38
  import { createAllToolDefinitions } from "./tools/index.js";
38
39
  import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
40
+ import { addUsageToTotals, createUsageTotals } from "./usage-totals.js";
39
41
  /**
40
42
  * Parse a skill block from message text.
41
43
  * Returns null if the text doesn't contain a skill block.
@@ -286,7 +288,7 @@ export class AgentSession {
286
288
  throw new Error(formatNoApiKeyFoundMessage(model.provider));
287
289
  }
288
290
  async _getSummarizationRequestAuth(model) {
289
- if (this.agent.streamFn === streamSimple) {
291
+ if (this.agent.streamFunction === streamSimple) {
290
292
  return this._getRequiredRequestAuth(model);
291
293
  }
292
294
  try {
@@ -404,6 +406,7 @@ export class AgentSession {
404
406
  content: result.content,
405
407
  details: result.details,
406
408
  isError,
409
+ usage: result.usage,
407
410
  })
408
411
  : undefined;
409
412
  const finalContent = hookResult?.content ?? result.content;
@@ -416,6 +419,7 @@ export class AgentSession {
416
419
  content: hookResult.content,
417
420
  details: hookResult.details,
418
421
  isError: hookResult.isError ?? isError,
422
+ usage: hookResult.usage,
419
423
  };
420
424
  };
421
425
  }
@@ -492,7 +496,7 @@ export class AgentSession {
492
496
  // This ensures the UI sees the updated queue state
493
497
  if (event.type === "message_start" && event.message.role === "user") {
494
498
  this._overflowRecoveryAttempted = false;
495
- const messageText = this._getUserMessageText(event.message);
499
+ const messageText = contentText(event.message.content, "");
496
500
  if (messageText) {
497
501
  // Check steering queue first
498
502
  const steeringIndex = this._steeringMessages.indexOf(messageText);
@@ -568,16 +572,6 @@ export class AgentSession {
568
572
  }
569
573
  return false;
570
574
  }
571
- /** Extract text content from a message */
572
- _getUserMessageText(message) {
573
- if (message.role !== "user")
574
- return "";
575
- const content = message.content;
576
- if (typeof content === "string")
577
- return content;
578
- const textBlocks = content.filter((c) => c.type === "text");
579
- return textBlocks.map((c) => c.text).join("");
580
- }
581
575
  /** Find the last assistant message in agent state (including aborted ones) */
582
576
  _findLastAssistantMessage() {
583
577
  const messages = this.agent.state.messages;
@@ -1042,7 +1036,7 @@ export class AgentSession {
1042
1036
  if (!model || !transcript)
1043
1037
  return;
1044
1038
  try {
1045
- const stream = await this.agent.streamFn(model, {
1039
+ const stream = await this.agent.streamFunction(model, {
1046
1040
  systemPrompt: this.systemPrompt,
1047
1041
  messages: [
1048
1042
  {
@@ -1691,8 +1685,17 @@ export class AgentSession {
1691
1685
  this.sessionManager.replaceTree(result.entries, result.leafId);
1692
1686
  const sessionContext = this.sessionManager.buildSessionContext();
1693
1687
  this.agent.state.messages = sessionContext.messages;
1688
+ const firstKeptEntryId = result.compaction.firstKeptEntryId;
1689
+ if (!firstKeptEntryId) {
1690
+ throw new Error("Pi-server compaction result is missing firstKeptEntryId");
1691
+ }
1692
+ const compaction = result.compaction;
1694
1693
  const compactionResult = {
1695
- ...result.compaction,
1694
+ summary: compaction.summary,
1695
+ firstKeptEntryId,
1696
+ tokensBefore: compaction.tokensBefore,
1697
+ usage: compaction.usage,
1698
+ details: compaction.details,
1696
1699
  estimatedTokensAfter: estimateMessagesTokens(sessionContext.messages),
1697
1700
  };
1698
1701
  const savedCompactionEntry = this.sessionManager.getEntry(result.compactionEntry.id);
@@ -1784,26 +1787,29 @@ export class AgentSession {
1784
1787
  let summary;
1785
1788
  let firstKeptEntryId;
1786
1789
  let tokensBefore;
1790
+ let usage;
1787
1791
  let details;
1788
1792
  if (extensionCompaction) {
1789
1793
  // Extension provided compaction content
1790
1794
  summary = extensionCompaction.summary;
1791
1795
  firstKeptEntryId = extensionCompaction.firstKeptEntryId;
1792
1796
  tokensBefore = extensionCompaction.tokensBefore;
1797
+ usage = extensionCompaction.usage;
1793
1798
  details = extensionCompaction.details;
1794
1799
  }
1795
1800
  else {
1796
1801
  // Generate compaction result
1797
- const result = await compact(preparation, this.model, apiKey, headers, customInstructions, this._compactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, env);
1802
+ const result = await compact(preparation, this.model, apiKey, headers, customInstructions, this._compactionAbortController.signal, this.thinkingLevel, this.agent.streamFunction, env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason: "manual" }));
1798
1803
  summary = result.summary;
1799
1804
  firstKeptEntryId = result.firstKeptEntryId;
1800
1805
  tokensBefore = result.tokensBefore;
1806
+ usage = result.usage;
1801
1807
  details = result.details;
1802
1808
  }
1803
1809
  if (this._compactionAbortController.signal.aborted) {
1804
1810
  throw new Error("Compaction cancelled");
1805
1811
  }
1806
- this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
1812
+ this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
1807
1813
  const newEntries = this.sessionManager.getEntries();
1808
1814
  const sessionContext = this.sessionManager.buildSessionContext();
1809
1815
  this.agent.state.messages = sessionContext.messages;
@@ -1824,6 +1830,7 @@ export class AgentSession {
1824
1830
  firstKeptEntryId,
1825
1831
  tokensBefore,
1826
1832
  estimatedTokensAfter,
1833
+ usage,
1827
1834
  details,
1828
1835
  };
1829
1836
  this._emit({
@@ -1980,7 +1987,7 @@ export class AgentSession {
1980
1987
  let apiKey;
1981
1988
  let headers;
1982
1989
  let env;
1983
- if (this.agent.streamFn === streamSimple) {
1990
+ if (this.agent.streamFunction === streamSimple) {
1984
1991
  const authResult = await this._modelRuntime.getAuth(this.model);
1985
1992
  if (!authResult?.auth.apiKey)
1986
1993
  return false;
@@ -2054,20 +2061,23 @@ export class AgentSession {
2054
2061
  let summary;
2055
2062
  let firstKeptEntryId;
2056
2063
  let tokensBefore;
2064
+ let usage;
2057
2065
  let details;
2058
2066
  if (extensionCompaction) {
2059
2067
  // Extension provided compaction content
2060
2068
  summary = extensionCompaction.summary;
2061
2069
  firstKeptEntryId = extensionCompaction.firstKeptEntryId;
2062
2070
  tokensBefore = extensionCompaction.tokensBefore;
2071
+ usage = extensionCompaction.usage;
2063
2072
  details = extensionCompaction.details;
2064
2073
  }
2065
2074
  else {
2066
2075
  // Generate compaction result
2067
- const compactResult = await compact(preparation, this.model, apiKey, headers, undefined, this._autoCompactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, env);
2076
+ const compactResult = await compact(preparation, this.model, apiKey, headers, undefined, this._autoCompactionAbortController.signal, this.thinkingLevel, this.agent.streamFunction, env, this.settingsManager.getRetrySettings(), this._summarizationRetryCallbacks({ source: "compaction", reason }));
2068
2077
  summary = compactResult.summary;
2069
2078
  firstKeptEntryId = compactResult.firstKeptEntryId;
2070
2079
  tokensBefore = compactResult.tokensBefore;
2080
+ usage = compactResult.usage;
2071
2081
  details = compactResult.details;
2072
2082
  }
2073
2083
  if (this._autoCompactionAbortController.signal.aborted) {
@@ -2080,7 +2090,7 @@ export class AgentSession {
2080
2090
  });
2081
2091
  return false;
2082
2092
  }
2083
- this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
2093
+ this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
2084
2094
  const newEntries = this.sessionManager.getEntries();
2085
2095
  const sessionContext = this.sessionManager.buildSessionContext();
2086
2096
  this.agent.state.messages = sessionContext.messages;
@@ -2101,6 +2111,7 @@ export class AgentSession {
2101
2111
  firstKeptEntryId,
2102
2112
  tokensBefore,
2103
2113
  estimatedTokensAfter,
2114
+ usage,
2104
2115
  details,
2105
2116
  };
2106
2117
  this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
@@ -2388,6 +2399,10 @@ export class AgentSession {
2388
2399
  this._modelRuntime.registerProvider(name, config);
2389
2400
  this._refreshCurrentModelFromRegistry();
2390
2401
  },
2402
+ registerNativeProvider: (provider) => {
2403
+ this._modelRuntime.registerNativeProvider(provider);
2404
+ this._refreshCurrentModelFromRegistry();
2405
+ },
2391
2406
  unregisterProvider: (name) => {
2392
2407
  this._modelRuntime.unregisterProvider(name);
2393
2408
  this._refreshCurrentModelFromRegistry();
@@ -2551,6 +2566,34 @@ export class AgentSession {
2551
2566
  return false;
2552
2567
  return isRetryableAssistantError(message);
2553
2568
  }
2569
+ /**
2570
+ * Retry policy + callbacks shared by compaction and branch-summary summarization calls.
2571
+ * Uses the same `settings.retry` budget/backoff as agent-turn retries so a single transient
2572
+ * stream drop no longer fails the whole operation. `source` carries the context
2573
+ * the TUI needs to render the retry and recreate the underlying indicator.
2574
+ */
2575
+ _summarizationRetryCallbacks(source) {
2576
+ return {
2577
+ onRetryScheduled: (attempt, maxAttempts, delayMs, errorMessage) => {
2578
+ this._emit({
2579
+ type: "summarization_retry_scheduled",
2580
+ attempt,
2581
+ maxAttempts,
2582
+ delayMs,
2583
+ errorMessage,
2584
+ });
2585
+ },
2586
+ onRetryAttemptStart: () => {
2587
+ this._emit({
2588
+ type: "summarization_retry_attempt_start",
2589
+ ...source,
2590
+ });
2591
+ },
2592
+ onRetryFinished: () => {
2593
+ this._emit({ type: "summarization_retry_finished" });
2594
+ },
2595
+ };
2596
+ }
2554
2597
  /**
2555
2598
  * Prepare a retryable error for continuation with exponential backoff.
2556
2599
  * @returns true if the caller should continue the agent, false otherwise
@@ -2813,6 +2856,7 @@ export class AgentSession {
2813
2856
  // Run default summarizer if needed
2814
2857
  let summaryText;
2815
2858
  let summaryDetails;
2859
+ let summaryUsage;
2816
2860
  if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
2817
2861
  const model = this.model;
2818
2862
  const { apiKey, headers, env } = await this._getSummarizationRequestAuth(model);
@@ -2826,7 +2870,9 @@ export class AgentSession {
2826
2870
  customInstructions,
2827
2871
  replaceInstructions,
2828
2872
  reserveTokens: branchSummarySettings.reserveTokens,
2829
- streamFn: this.agent.streamFn,
2873
+ streamFn: this.agent.streamFunction,
2874
+ retry: this.settingsManager.getRetrySettings(),
2875
+ callbacks: this._summarizationRetryCallbacks({ source: "branchSummary" }),
2830
2876
  });
2831
2877
  if (result.aborted) {
2832
2878
  return { cancelled: true, aborted: true };
@@ -2835,6 +2881,7 @@ export class AgentSession {
2835
2881
  throw new Error(result.error);
2836
2882
  }
2837
2883
  summaryText = result.summary;
2884
+ summaryUsage = result.usage;
2838
2885
  summaryDetails = {
2839
2886
  readFiles: result.readFiles || [],
2840
2887
  modifiedFiles: result.modifiedFiles || [],
@@ -2843,6 +2890,7 @@ export class AgentSession {
2843
2890
  else if (extensionSummary) {
2844
2891
  summaryText = extensionSummary.summary;
2845
2892
  summaryDetails = extensionSummary.details;
2893
+ summaryUsage = extensionSummary.usage;
2846
2894
  }
2847
2895
  // Determine the new leaf position based on target type
2848
2896
  let newLeafId;
@@ -2850,18 +2898,12 @@ export class AgentSession {
2850
2898
  if (targetEntry.type === "message" && targetEntry.message.role === "user") {
2851
2899
  // User message: leaf = parent (null if root), text goes to editor
2852
2900
  newLeafId = targetEntry.parentId;
2853
- editorText = this._extractUserMessageText(targetEntry.message.content);
2901
+ editorText = contentText(targetEntry.message.content, "");
2854
2902
  }
2855
2903
  else if (targetEntry.type === "custom_message") {
2856
2904
  // Custom message: leaf = parent (null if root), text goes to editor
2857
2905
  newLeafId = targetEntry.parentId;
2858
- editorText =
2859
- typeof targetEntry.content === "string"
2860
- ? targetEntry.content
2861
- : targetEntry.content
2862
- .filter((c) => c.type === "text")
2863
- .map((c) => c.text)
2864
- .join("");
2906
+ editorText = contentText(targetEntry.content, "");
2865
2907
  }
2866
2908
  else {
2867
2909
  // Non-user message: leaf = selected node
@@ -2872,7 +2914,7 @@ export class AgentSession {
2872
2914
  let summaryEntry;
2873
2915
  if (summaryText) {
2874
2916
  // Create summary at target position (can be null for root)
2875
- const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);
2917
+ const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension, summaryUsage);
2876
2918
  summaryEntry = this.sessionManager.getEntry(summaryId);
2877
2919
  // Attach label to the summary entry
2878
2920
  if (label) {
@@ -2927,24 +2969,13 @@ export class AgentSession {
2927
2969
  continue;
2928
2970
  if (entry.message.role !== "user")
2929
2971
  continue;
2930
- const text = this._extractUserMessageText(entry.message.content);
2972
+ const text = contentText(entry.message.content, "");
2931
2973
  if (text) {
2932
2974
  result.push({ entryId: entry.id, text });
2933
2975
  }
2934
2976
  }
2935
2977
  return result;
2936
2978
  }
2937
- _extractUserMessageText(content) {
2938
- if (typeof content === "string")
2939
- return content;
2940
- if (Array.isArray(content)) {
2941
- return content
2942
- .filter((c) => c.type === "text")
2943
- .map((c) => c.text)
2944
- .join("");
2945
- }
2946
- return "";
2947
- }
2948
2979
  /**
2949
2980
  * Get session statistics. Aggregates over ALL session entries (including
2950
2981
  * history that was compacted away), so token/cost totals reflect what was
@@ -2956,12 +2987,11 @@ export class AgentSession {
2956
2987
  let toolResults = 0;
2957
2988
  let totalMessages = 0;
2958
2989
  let toolCalls = 0;
2959
- let totalInput = 0;
2960
- let totalOutput = 0;
2961
- let totalCacheRead = 0;
2962
- let totalCacheWrite = 0;
2963
- let totalCost = 0;
2990
+ const usageTotals = createUsageTotals();
2964
2991
  for (const entry of this.sessionManager.getEntries()) {
2992
+ if ((entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) {
2993
+ addUsageToTotals(usageTotals, entry.usage);
2994
+ }
2965
2995
  if (entry.type !== "message")
2966
2996
  continue;
2967
2997
  totalMessages++;
@@ -2971,6 +3001,9 @@ export class AgentSession {
2971
3001
  }
2972
3002
  else if (message.role === "toolResult") {
2973
3003
  toolResults++;
3004
+ if (message.usage) {
3005
+ addUsageToTotals(usageTotals, message.usage);
3006
+ }
2974
3007
  }
2975
3008
  else if (message.role === "assistant") {
2976
3009
  assistantMessages++;
@@ -2978,12 +3011,7 @@ export class AgentSession {
2978
3011
  if (Array.isArray(assistantMsg.content)) {
2979
3012
  toolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
2980
3013
  }
2981
- const usage = assistantMsg.usage;
2982
- totalInput += usage.input;
2983
- totalOutput += usage.output;
2984
- totalCacheRead += usage.cacheRead;
2985
- totalCacheWrite += usage.cacheWrite;
2986
- totalCost += usage.cost.total;
3014
+ addUsageToTotals(usageTotals, assistantMsg.usage);
2987
3015
  }
2988
3016
  }
2989
3017
  return {
@@ -2995,13 +3023,13 @@ export class AgentSession {
2995
3023
  toolResults,
2996
3024
  totalMessages,
2997
3025
  tokens: {
2998
- input: totalInput,
2999
- output: totalOutput,
3000
- cacheRead: totalCacheRead,
3001
- cacheWrite: totalCacheWrite,
3002
- total: totalInput + totalOutput + totalCacheRead + totalCacheWrite,
3026
+ input: usageTotals.input,
3027
+ output: usageTotals.output,
3028
+ cacheRead: usageTotals.cacheRead,
3029
+ cacheWrite: usageTotals.cacheWrite,
3030
+ total: usageTotals.input + usageTotals.output + usageTotals.cacheRead + usageTotals.cacheWrite,
3003
3031
  },
3004
- cost: totalCost,
3032
+ cost: usageTotals.cost,
3005
3033
  contextUsage: this.getContextUsage(),
3006
3034
  };
3007
3035
  }