@averyyy/pi-coding-agent 0.80.10-piclient.2 → 0.82.0-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 (185) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +19 -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/cli/config-selector.d.ts.map +1 -1
  7. package/dist/cli/config-selector.js +1 -1
  8. package/dist/cli/config-selector.js.map +1 -1
  9. package/dist/cli/startup-ui.d.ts.map +1 -1
  10. package/dist/cli/startup-ui.js +1 -1
  11. package/dist/cli/startup-ui.js.map +1 -1
  12. package/dist/core/agent-session-services.d.ts.map +1 -1
  13. package/dist/core/agent-session-services.js +13 -0
  14. package/dist/core/agent-session-services.js.map +1 -1
  15. package/dist/core/agent-session.d.ts +33 -3
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +91 -59
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/compaction/branch-summarization.d.ts +7 -1
  20. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  21. package/dist/core/compaction/branch-summarization.js +6 -2
  22. package/dist/core/compaction/branch-summarization.js.map +1 -1
  23. package/dist/core/compaction/compaction.d.ts +23 -4
  24. package/dist/core/compaction/compaction.d.ts.map +1 -1
  25. package/dist/core/compaction/compaction.js +83 -29
  26. package/dist/core/compaction/compaction.js.map +1 -1
  27. package/dist/core/compaction/utils.d.ts +1 -1
  28. package/dist/core/compaction/utils.d.ts.map +1 -1
  29. package/dist/core/compaction/utils.js +6 -17
  30. package/dist/core/compaction/utils.js.map +1 -1
  31. package/dist/core/extensions/loader.d.ts.map +1 -1
  32. package/dist/core/extensions/loader.js +13 -2
  33. package/dist/core/extensions/loader.js.map +1 -1
  34. package/dist/core/extensions/runner.d.ts +2 -1
  35. package/dist/core/extensions/runner.d.ts.map +1 -1
  36. package/dist/core/extensions/runner.js +35 -0
  37. package/dist/core/extensions/runner.js.map +1 -1
  38. package/dist/core/extensions/types.d.ts +20 -2
  39. package/dist/core/extensions/types.d.ts.map +1 -1
  40. package/dist/core/extensions/types.js.map +1 -1
  41. package/dist/core/model-config.d.ts +30 -0
  42. package/dist/core/model-config.d.ts.map +1 -1
  43. package/dist/core/model-config.js +6 -0
  44. package/dist/core/model-config.js.map +1 -1
  45. package/dist/core/model-registry.d.ts +5 -1
  46. package/dist/core/model-registry.d.ts.map +1 -1
  47. package/dist/core/model-registry.js +19 -4
  48. package/dist/core/model-registry.js.map +1 -1
  49. package/dist/core/model-resolver.d.ts.map +1 -1
  50. package/dist/core/model-resolver.js +9 -0
  51. package/dist/core/model-resolver.js.map +1 -1
  52. package/dist/core/model-runtime.d.ts +7 -3
  53. package/dist/core/model-runtime.d.ts.map +1 -1
  54. package/dist/core/model-runtime.js +42 -21
  55. package/dist/core/model-runtime.js.map +1 -1
  56. package/dist/core/prompt-templates.d.ts +1 -0
  57. package/dist/core/prompt-templates.d.ts.map +1 -1
  58. package/dist/core/prompt-templates.js +4 -4
  59. package/dist/core/prompt-templates.js.map +1 -1
  60. package/dist/core/remote-catalog-provider.d.ts +1 -1
  61. package/dist/core/remote-catalog-provider.d.ts.map +1 -1
  62. package/dist/core/remote-catalog-provider.js +21 -7
  63. package/dist/core/remote-catalog-provider.js.map +1 -1
  64. package/dist/core/resource-loader.d.ts.map +1 -1
  65. package/dist/core/resource-loader.js +1 -0
  66. package/dist/core/resource-loader.js.map +1 -1
  67. package/dist/core/sdk.d.ts.map +1 -1
  68. package/dist/core/sdk.js +6 -2
  69. package/dist/core/sdk.js.map +1 -1
  70. package/dist/core/session-manager.d.ts +9 -4
  71. package/dist/core/session-manager.d.ts.map +1 -1
  72. package/dist/core/session-manager.js +99 -23
  73. package/dist/core/session-manager.js.map +1 -1
  74. package/dist/core/settings-manager.d.ts +1 -1
  75. package/dist/core/settings-manager.d.ts.map +1 -1
  76. package/dist/core/settings-manager.js.map +1 -1
  77. package/dist/core/system-prompt.d.ts.map +1 -1
  78. package/dist/core/system-prompt.js +1 -1
  79. package/dist/core/system-prompt.js.map +1 -1
  80. package/dist/core/tools/bash.d.ts +2 -0
  81. package/dist/core/tools/bash.d.ts.map +1 -1
  82. package/dist/core/tools/bash.js +34 -5
  83. package/dist/core/tools/bash.js.map +1 -1
  84. package/dist/core/tools/read.d.ts.map +1 -1
  85. package/dist/core/tools/read.js +1 -1
  86. package/dist/core/tools/read.js.map +1 -1
  87. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  88. package/dist/core/tools/tool-definition-wrapper.js +3 -1
  89. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  90. package/dist/core/usage-totals.d.ts +19 -0
  91. package/dist/core/usage-totals.d.ts.map +1 -0
  92. package/dist/core/usage-totals.js +52 -0
  93. package/dist/core/usage-totals.js.map +1 -0
  94. package/dist/extensions/index.d.ts +3 -0
  95. package/dist/extensions/index.d.ts.map +1 -0
  96. package/dist/extensions/index.js +3 -0
  97. package/dist/extensions/index.js.map +1 -0
  98. package/dist/extensions/llama/client.d.ts +61 -0
  99. package/dist/extensions/llama/client.d.ts.map +1 -0
  100. package/dist/extensions/llama/client.js +302 -0
  101. package/dist/extensions/llama/client.js.map +1 -0
  102. package/dist/extensions/llama/huggingface.d.ts +23 -0
  103. package/dist/extensions/llama/huggingface.d.ts.map +1 -0
  104. package/dist/extensions/llama/huggingface.js +141 -0
  105. package/dist/extensions/llama/huggingface.js.map +1 -0
  106. package/dist/extensions/llama/index.d.ts +3 -0
  107. package/dist/extensions/llama/index.d.ts.map +1 -0
  108. package/dist/extensions/llama/index.js +208 -0
  109. package/dist/extensions/llama/index.js.map +1 -0
  110. package/dist/extensions/llama/provider.d.ts +10 -0
  111. package/dist/extensions/llama/provider.d.ts.map +1 -0
  112. package/dist/extensions/llama/provider.js +101 -0
  113. package/dist/extensions/llama/provider.js.map +1 -0
  114. package/dist/extensions/llama/ui.d.ts +42 -0
  115. package/dist/extensions/llama/ui.d.ts.map +1 -0
  116. package/dist/extensions/llama/ui.js +416 -0
  117. package/dist/extensions/llama/ui.js.map +1 -0
  118. package/dist/index.d.ts +2 -2
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +1 -1
  121. package/dist/index.js.map +1 -1
  122. package/dist/main.d.ts.map +1 -1
  123. package/dist/main.js +10 -4
  124. package/dist/main.js.map +1 -1
  125. package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
  126. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  127. package/dist/modes/interactive/components/extension-editor.js +16 -46
  128. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  129. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/footer.js +24 -23
  131. package/dist/modes/interactive/components/footer.js.map +1 -1
  132. package/dist/modes/interactive/external-editor.d.ts +12 -0
  133. package/dist/modes/interactive/external-editor.d.ts.map +1 -0
  134. package/dist/modes/interactive/external-editor.js +37 -0
  135. package/dist/modes/interactive/external-editor.js.map +1 -0
  136. package/dist/modes/interactive/interactive-mode.d.ts +2 -1
  137. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  138. package/dist/modes/interactive/interactive-mode.js +72 -68
  139. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  140. package/dist/modes/rpc/rpc-client.d.ts +4 -0
  141. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  142. package/dist/modes/rpc/rpc-client.js +7 -0
  143. package/dist/modes/rpc/rpc-client.js.map +1 -1
  144. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  145. package/dist/modes/rpc/rpc-mode.js +5 -0
  146. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  147. package/dist/modes/rpc/rpc-types.d.ts +11 -0
  148. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  149. package/dist/modes/rpc/rpc-types.js.map +1 -1
  150. package/dist/utils/clipboard.d.ts.map +1 -1
  151. package/dist/utils/clipboard.js +19 -8
  152. package/dist/utils/clipboard.js.map +1 -1
  153. package/dist/utils/version-check.d.ts.map +1 -1
  154. package/dist/utils/version-check.js +3 -1
  155. package/dist/utils/version-check.js.map +1 -1
  156. package/docs/compaction.md +9 -4
  157. package/docs/custom-provider.md +33 -2
  158. package/docs/environment-variables.md +88 -0
  159. package/docs/extensions.md +61 -11
  160. package/docs/index.md +2 -0
  161. package/docs/json.md +5 -1
  162. package/docs/llama-cpp.md +99 -0
  163. package/docs/models.md +6 -2
  164. package/docs/prompt-templates.md +1 -0
  165. package/docs/providers.md +19 -1
  166. package/docs/rpc.md +104 -8
  167. package/docs/sdk.md +3 -0
  168. package/docs/session-format.md +17 -3
  169. package/docs/settings.md +1 -1
  170. package/docs/tui.md +41 -26
  171. package/docs/usage.md +2 -14
  172. package/examples/extensions/README.md +1 -1
  173. package/examples/extensions/custom-compaction.ts +4 -0
  174. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  175. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  176. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  177. package/examples/extensions/gondolin/package-lock.json +2 -2
  178. package/examples/extensions/gondolin/package.json +1 -1
  179. package/examples/extensions/handoff.ts +9 -1
  180. package/examples/extensions/sandbox/package-lock.json +2 -2
  181. package/examples/extensions/sandbox/package.json +1 -1
  182. package/examples/extensions/summarize.ts +3 -0
  183. package/examples/extensions/with-deps/package-lock.json +2 -2
  184. package/examples/extensions/with-deps/package.json +1 -1
  185. package/package.json +5 -4
@@ -25,6 +25,7 @@ import { SessionManager, sessionEntryToContextMessages } from "../../core/sessio
25
25
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
26
26
  import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
27
27
  import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.js";
28
+ import { getUsageCostBreakdown } from "../../core/usage-totals.js";
28
29
  import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelog } from "../../utils/changelog.js";
29
30
  import { copyToClipboard, readClipboardText } from "../../utils/clipboard.js";
30
31
  import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
@@ -64,6 +65,7 @@ import { TreeSelectorComponent } from "./components/tree-selector.js";
64
65
  import { TrustSelectorComponent } from "./components/trust-selector.js";
65
66
  import { UserMessageComponent } from "./components/user-message.js";
66
67
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
68
+ import { editInExternalEditor } from "./external-editor.js";
67
69
  import { getModelSearchText } from "./model-search.js";
68
70
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
69
71
  import { InteractiveThemeController } from "./theme/theme-controller.js";
@@ -272,7 +274,7 @@ export class InteractiveMode {
272
274
  await this.rebindCurrentSession({ renderBeforeBind: true });
273
275
  });
274
276
  this.version = VERSION;
275
- this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
277
+ this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor(), getAgentDir());
276
278
  this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
277
279
  this.headerContainer = new Container();
278
280
  this.loadedResourcesContainer = new Container();
@@ -580,6 +582,12 @@ export class InteractiveMode {
580
582
  */
581
583
  async run() {
582
584
  await this.init();
585
+ if (!process.env.PI_OFFLINE) {
586
+ void this.session.modelRuntime
587
+ .refresh()
588
+ .then(() => this.updateAvailableProviderCount())
589
+ .catch(() => { });
590
+ }
583
591
  // Start version check asynchronously
584
592
  checkForNewPiVersion(this.version).then((newRelease) => {
585
593
  if (newRelease) {
@@ -791,8 +799,15 @@ export class InteractiveMode {
791
799
  * Get a short path relative to the package root for display.
792
800
  */
793
801
  getShortPath(fullPath, sourceInfo) {
802
+ const normalizedFullPath = fullPath.replace(/\\/g, "/");
794
803
  const baseDir = sourceInfo?.baseDir;
795
804
  if (baseDir && this.isPackageSource(sourceInfo)) {
805
+ const normalizedBaseDir = baseDir.replace(/\\/g, "/");
806
+ const npmRootMatch = normalizedBaseDir.match(/^(.*\/node_modules)\/(@?[^/]+(?:\/[^/]+)?)$/);
807
+ // If fullPath is under the same node_modules root as baseDir, preserve that relative topology.
808
+ if (npmRootMatch?.[1] && normalizedFullPath.startsWith(`${npmRootMatch[1]}/`)) {
809
+ return path.posix.relative(normalizedBaseDir, normalizedFullPath);
810
+ }
796
811
  const relativePath = path.relative(path.resolve(baseDir), path.resolve(fullPath));
797
812
  if (relativePath &&
798
813
  relativePath !== "." &&
@@ -803,11 +818,11 @@ export class InteractiveMode {
803
818
  }
804
819
  }
805
820
  const source = sourceInfo?.source ?? "";
806
- const npmMatch = fullPath.match(/node_modules\/(@?[^/]+(?:\/[^/]+)?)\/(.*)/);
821
+ const npmMatch = normalizedFullPath.match(/node_modules\/(@?[^/]+(?:\/[^/]+)?)\/(.*)/);
807
822
  if (npmMatch && source.startsWith("npm:")) {
808
823
  return npmMatch[2];
809
824
  }
810
- const gitMatch = fullPath.match(/git\/[^/]+\/[^/]+\/(.*)/);
825
+ const gitMatch = normalizedFullPath.match(/git\/[^/]+\/[^/]+\/(.*)/);
811
826
  if (gitMatch && source.startsWith("git:")) {
812
827
  return gitMatch[1];
813
828
  }
@@ -1063,7 +1078,10 @@ export class InteractiveMode {
1063
1078
  const promptsResult = this.session.resourceLoader.getPrompts();
1064
1079
  const themesResult = this.session.resourceLoader.getThemes();
1065
1080
  const extensions = options?.extensions ??
1066
- this.session.resourceLoader.getExtensions().extensions.map((extension) => ({
1081
+ this.session.resourceLoader
1082
+ .getExtensions()
1083
+ .extensions.filter((extension) => !extension.hidden)
1084
+ .map((extension) => ({
1067
1085
  path: extension.path,
1068
1086
  sourceInfo: extension.sourceInfo,
1069
1087
  }));
@@ -1344,6 +1362,7 @@ export class InteractiveMode {
1344
1362
  sessionManager: this.sessionManager,
1345
1363
  modelRegistry: extensionRunner.getModelRegistry(),
1346
1364
  model: this.session.model,
1365
+ thinkingLevel: this.session.thinkingLevel,
1347
1366
  isIdle: () => this.session.isIdle,
1348
1367
  isProjectTrusted: () => this.settingsManager.isProjectTrusted(),
1349
1368
  signal: this.session.agent.signal,
@@ -2029,7 +2048,7 @@ export class InteractiveMode {
2029
2048
  this.defaultEditor.onAction("app.model.select", () => this.showModelSelector());
2030
2049
  this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
2031
2050
  this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
2032
- this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
2051
+ this.defaultEditor.onAction("app.editor.external", () => void this.handleOpenExternalEditor());
2033
2052
  this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand());
2034
2053
  this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
2035
2054
  this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
@@ -2397,6 +2416,9 @@ export class InteractiveMode {
2397
2416
  }
2398
2417
  this.ui.requestRender();
2399
2418
  break;
2419
+ case "bash_execution_update":
2420
+ // The bash execution callback handles TUI output rendering.
2421
+ break;
2400
2422
  case "tool_execution_start": {
2401
2423
  let component = this.pendingTools.get(event.toolCallId);
2402
2424
  if (!component) {
@@ -2518,6 +2540,28 @@ export class InteractiveMode {
2518
2540
  this.ui.requestRender();
2519
2541
  break;
2520
2542
  }
2543
+ case "summarization_retry_scheduled": {
2544
+ this.showError(event.errorMessage);
2545
+ this.showStatusIndicator(new RetryStatusIndicator(this.ui, event.attempt, event.maxAttempts, event.delayMs));
2546
+ this.ui.requestRender();
2547
+ break;
2548
+ }
2549
+ case "summarization_retry_attempt_start": {
2550
+ this.clearStatusIndicator("retry");
2551
+ if (event.source === "branchSummary") {
2552
+ this.showStatusIndicator(new BranchSummaryStatusIndicator(this.ui));
2553
+ }
2554
+ else {
2555
+ this.showStatusIndicator(new CompactionStatusIndicator(this.ui, event.reason));
2556
+ }
2557
+ this.ui.requestRender();
2558
+ break;
2559
+ }
2560
+ case "summarization_retry_finished": {
2561
+ this.clearStatusIndicator("retry");
2562
+ this.ui.requestRender();
2563
+ break;
2564
+ }
2521
2565
  }
2522
2566
  }
2523
2567
  /** Extract text content from a user message */
@@ -3097,51 +3141,21 @@ export class InteractiveMode {
3097
3141
  }
3098
3142
  this.showStatus(`Thinking blocks: ${this.hideThinkingBlock ? "hidden" : "visible"}`);
3099
3143
  }
3100
- async openExternalEditor() {
3144
+ async handleOpenExternalEditor() {
3101
3145
  const editorCmd = this.settingsManager.getExternalEditorCommand();
3102
- if (!editorCmd) {
3103
- this.showWarning("No editor configured. Set externalEditor in settings.json or $VISUAL/$EDITOR.");
3104
- return;
3105
- }
3106
- const currentText = this.editor.getExpandedText?.() ?? this.editor.getText();
3107
- const tmpFile = path.join(os.tmpdir(), `pi-editor-${Date.now()}.pi.md`);
3146
+ const content = this.editor.getExpandedText?.() ?? this.editor.getText();
3147
+ this.ui.stop();
3108
3148
  try {
3109
- // Write current content to temp file
3110
- fs.writeFileSync(tmpFile, currentText, "utf-8");
3111
- // Stop TUI to release terminal
3112
- this.ui.stop();
3113
- // Split by space to support editor arguments (e.g., "code --wait")
3114
- const [editor, ...editorArgs] = editorCmd.split(" ");
3115
- process.stdout.write(`Launching external editor: ${editorCmd}\nPi will resume when the editor exits.\n`);
3116
- // Do not use spawnSync here. On Windows, synchronous child_process calls can keep
3117
- // Node/libuv's console input read active after ui.stop() pauses stdin, racing
3118
- // vim/nvim for the console input buffer until Ctrl+C cancels the pending read.
3119
- const status = await new Promise((resolve) => {
3120
- const child = spawn(editor, [...editorArgs, tmpFile], {
3121
- stdio: "inherit",
3122
- shell: process.platform === "win32",
3123
- });
3124
- child.on("error", () => resolve(null));
3125
- child.on("close", (code) => resolve(code));
3149
+ const result = await editInExternalEditor({
3150
+ command: editorCmd,
3151
+ content,
3126
3152
  });
3127
- // On successful exit (status 0), replace editor content
3128
- if (status === 0) {
3129
- const newContent = fs.readFileSync(tmpFile, "utf-8").replace(/\n$/, "");
3130
- this.editor.setText(newContent);
3153
+ if (result.status === "complete") {
3154
+ this.editor.setText(result.content);
3131
3155
  }
3132
- // On non-zero exit, keep original text (no action needed)
3133
3156
  }
3134
3157
  finally {
3135
- // Clean up temp file
3136
- try {
3137
- fs.unlinkSync(tmpFile);
3138
- }
3139
- catch {
3140
- // Ignore cleanup errors
3141
- }
3142
- // Restart TUI
3143
3158
  this.ui.start();
3144
- // Force full re-render since external editor uses alternate screen
3145
3159
  this.ui.requestRender(true);
3146
3160
  }
3147
3161
  }
@@ -3328,8 +3342,10 @@ export class InteractiveMode {
3328
3342
  for (const message of preCommands) {
3329
3343
  await this.session.prompt(message.text);
3330
3344
  }
3331
- // Send first prompt (starts streaming)
3332
- const promptPromise = this.session.prompt(firstPrompt.text).catch((error) => {
3345
+ // Start a prompt when idle, or queue it into a run still finishing compaction.
3346
+ const promptPromise = this.session
3347
+ .prompt(firstPrompt.text, { streamingBehavior: firstPrompt.mode })
3348
+ .catch((error) => {
3333
3349
  restoreQueue(error);
3334
3350
  });
3335
3351
  // Queue remaining messages
@@ -3594,10 +3610,12 @@ export class InteractiveMode {
3594
3610
  return [];
3595
3611
  }
3596
3612
  }
3597
- /** Update the footer's available provider count from current model candidates */
3598
- async updateAvailableProviderCount() {
3599
- const models = await this.getModelCandidates();
3600
- const uniqueProviders = new Set(models.map((m) => m.provider));
3613
+ /** Update the footer's available provider count from the current snapshot without refreshing catalogs. */
3614
+ updateAvailableProviderCount() {
3615
+ const models = this.session.scopedModels.length > 0
3616
+ ? this.session.scopedModels.map((scoped) => scoped.model)
3617
+ : this.session.modelRuntime.getAvailableSnapshot();
3618
+ const uniqueProviders = new Set(models.map((model) => model.provider));
3601
3619
  this.footerDataProvider.setAvailableProviderCount(uniqueProviders.size);
3602
3620
  }
3603
3621
  async maybeWarnAboutAnthropicSubscriptionAuth(model = this.session.model) {
@@ -4704,23 +4722,9 @@ export class InteractiveMode {
4704
4722
  const entries = this.sessionManager.getEntries();
4705
4723
  const cacheWaste = computeCacheWaste(entries, this.session.modelRuntime);
4706
4724
  // Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
4707
- // resolves to a concrete responseModel), sorted by cost descending.
4708
- const perModelMap = new Map();
4709
- for (const entry of entries) {
4710
- if (entry.type !== "message" || entry.message.role !== "assistant")
4711
- continue;
4712
- const message = entry.message;
4713
- const usage = message.usage;
4714
- const key = `${message.provider}/${message.responseModel ?? message.model}`;
4715
- let bucket = perModelMap.get(key);
4716
- if (!bucket) {
4717
- bucket = { key, cost: 0, tokens: 0 };
4718
- perModelMap.set(key, bucket);
4719
- }
4720
- bucket.cost += usage.cost.total;
4721
- bucket.tokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
4722
- }
4723
- const perModel = Array.from(perModelMap.values()).sort((a, b) => b.cost - a.cost);
4725
+ // resolves to a concrete responseModel). Usage without model attribution is
4726
+ // grouped separately so the breakdown reconciles with the session total.
4727
+ const usageBreakdown = getUsageCostBreakdown(entries);
4724
4728
  let info = `${theme.bold("Session Info")}\n\n`;
4725
4729
  if (sessionName) {
4726
4730
  info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
@@ -4751,8 +4755,8 @@ export class InteractiveMode {
4751
4755
  if (stats.cost > 0 || cacheWaste.missedTokens > 0) {
4752
4756
  info += `\n${theme.bold("Cost")}\n`;
4753
4757
  info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(3)}`;
4754
- if (perModel.length > 1) {
4755
- for (const entry of perModel) {
4758
+ if (usageBreakdown.length > 1) {
4759
+ for (const entry of usageBreakdown) {
4756
4760
  info += `\n ${theme.fg("dim", `${entry.key}:`)} $${entry.cost.toFixed(3)} ${theme.fg("dim", `(${formatTokens(entry.tokens)} tokens)`)}`;
4757
4761
  }
4758
4762
  }