@earendil-works/pi-coding-agent 0.80.10 → 0.81.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 (144) 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 +75 -56
  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 +8 -11
  16. package/dist/core/compaction/branch-summarization.js.map +1 -1
  17. package/dist/core/compaction/compaction.d.ts +19 -3
  18. package/dist/core/compaction/compaction.d.ts.map +1 -1
  19. package/dist/core/compaction/compaction.js +63 -26
  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/npm-shrinkwrap.json +16 -31
  144. 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";
@@ -35,6 +36,7 @@ import { buildSystemPrompt } from "./system-prompt.js";
35
36
  import { createLocalBashOperations } from "./tools/bash.js";
36
37
  import { createAllToolDefinitions } from "./tools/index.js";
37
38
  import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
39
+ import { addUsageToTotals, createUsageTotals } from "./usage-totals.js";
38
40
  /**
39
41
  * Parse a skill block from message text.
40
42
  * Returns null if the text doesn't contain a skill block.
@@ -188,7 +190,7 @@ export class AgentSession {
188
190
  throw new Error(formatNoApiKeyFoundMessage(model.provider));
189
191
  }
190
192
  async _getSummarizationRequestAuth(model) {
191
- if (this.agent.streamFn === streamSimple) {
193
+ if (this.agent.streamFunction === streamSimple) {
192
194
  return this._getRequiredRequestAuth(model);
193
195
  }
194
196
  try {
@@ -243,6 +245,7 @@ export class AgentSession {
243
245
  content: result.content,
244
246
  details: result.details,
245
247
  isError,
248
+ usage: result.usage,
246
249
  });
247
250
  if (!hookResult) {
248
251
  return undefined;
@@ -251,6 +254,7 @@ export class AgentSession {
251
254
  content: hookResult.content,
252
255
  details: hookResult.details,
253
256
  isError: hookResult.isError ?? isError,
257
+ usage: hookResult.usage,
254
258
  };
255
259
  };
256
260
  }
@@ -325,7 +329,7 @@ export class AgentSession {
325
329
  // This ensures the UI sees the updated queue state
326
330
  if (event.type === "message_start" && event.message.role === "user") {
327
331
  this._overflowRecoveryAttempted = false;
328
- const messageText = this._getUserMessageText(event.message);
332
+ const messageText = contentText(event.message.content, "");
329
333
  if (messageText) {
330
334
  // Check steering queue first
331
335
  const steeringIndex = this._steeringMessages.indexOf(messageText);
@@ -394,16 +398,6 @@ export class AgentSession {
394
398
  }
395
399
  return false;
396
400
  }
397
- /** Extract text content from a message */
398
- _getUserMessageText(message) {
399
- if (message.role !== "user")
400
- return "";
401
- const content = message.content;
402
- if (typeof content === "string")
403
- return content;
404
- const textBlocks = content.filter((c) => c.type === "text");
405
- return textBlocks.map((c) => c.text).join("");
406
- }
407
401
  /** Find the last assistant message in agent state (including aborted ones) */
408
402
  _findLastAssistantMessage() {
409
403
  const messages = this.agent.state.messages;
@@ -1414,26 +1408,29 @@ export class AgentSession {
1414
1408
  let summary;
1415
1409
  let firstKeptEntryId;
1416
1410
  let tokensBefore;
1411
+ let usage;
1417
1412
  let details;
1418
1413
  if (extensionCompaction) {
1419
1414
  // Extension provided compaction content
1420
1415
  summary = extensionCompaction.summary;
1421
1416
  firstKeptEntryId = extensionCompaction.firstKeptEntryId;
1422
1417
  tokensBefore = extensionCompaction.tokensBefore;
1418
+ usage = extensionCompaction.usage;
1423
1419
  details = extensionCompaction.details;
1424
1420
  }
1425
1421
  else {
1426
1422
  // Generate compaction result
1427
- const result = await compact(preparation, this.model, apiKey, headers, customInstructions, this._compactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, env);
1423
+ 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" }));
1428
1424
  summary = result.summary;
1429
1425
  firstKeptEntryId = result.firstKeptEntryId;
1430
1426
  tokensBefore = result.tokensBefore;
1427
+ usage = result.usage;
1431
1428
  details = result.details;
1432
1429
  }
1433
1430
  if (this._compactionAbortController.signal.aborted) {
1434
1431
  throw new Error("Compaction cancelled");
1435
1432
  }
1436
- this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
1433
+ this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
1437
1434
  const newEntries = this.sessionManager.getEntries();
1438
1435
  const sessionContext = this.sessionManager.buildSessionContext();
1439
1436
  this.agent.state.messages = sessionContext.messages;
@@ -1454,6 +1451,7 @@ export class AgentSession {
1454
1451
  firstKeptEntryId,
1455
1452
  tokensBefore,
1456
1453
  estimatedTokensAfter,
1454
+ usage,
1457
1455
  details,
1458
1456
  };
1459
1457
  this._emit({
@@ -1600,7 +1598,7 @@ export class AgentSession {
1600
1598
  let apiKey;
1601
1599
  let headers;
1602
1600
  let env;
1603
- if (this.agent.streamFn === streamSimple) {
1601
+ if (this.agent.streamFunction === streamSimple) {
1604
1602
  const authResult = await this._modelRuntime.getAuth(this.model);
1605
1603
  if (!authResult?.auth.apiKey)
1606
1604
  return false;
@@ -1649,20 +1647,23 @@ export class AgentSession {
1649
1647
  let summary;
1650
1648
  let firstKeptEntryId;
1651
1649
  let tokensBefore;
1650
+ let usage;
1652
1651
  let details;
1653
1652
  if (extensionCompaction) {
1654
1653
  // Extension provided compaction content
1655
1654
  summary = extensionCompaction.summary;
1656
1655
  firstKeptEntryId = extensionCompaction.firstKeptEntryId;
1657
1656
  tokensBefore = extensionCompaction.tokensBefore;
1657
+ usage = extensionCompaction.usage;
1658
1658
  details = extensionCompaction.details;
1659
1659
  }
1660
1660
  else {
1661
1661
  // Generate compaction result
1662
- const compactResult = await compact(preparation, this.model, apiKey, headers, undefined, this._autoCompactionAbortController.signal, this.thinkingLevel, this.agent.streamFn, env);
1662
+ 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 }));
1663
1663
  summary = compactResult.summary;
1664
1664
  firstKeptEntryId = compactResult.firstKeptEntryId;
1665
1665
  tokensBefore = compactResult.tokensBefore;
1666
+ usage = compactResult.usage;
1666
1667
  details = compactResult.details;
1667
1668
  }
1668
1669
  if (this._autoCompactionAbortController.signal.aborted) {
@@ -1675,7 +1676,7 @@ export class AgentSession {
1675
1676
  });
1676
1677
  return false;
1677
1678
  }
1678
- this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
1679
+ this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension, usage);
1679
1680
  const newEntries = this.sessionManager.getEntries();
1680
1681
  const sessionContext = this.sessionManager.buildSessionContext();
1681
1682
  this.agent.state.messages = sessionContext.messages;
@@ -1696,6 +1697,7 @@ export class AgentSession {
1696
1697
  firstKeptEntryId,
1697
1698
  tokensBefore,
1698
1699
  estimatedTokensAfter,
1700
+ usage,
1699
1701
  details,
1700
1702
  };
1701
1703
  this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
@@ -1929,6 +1931,10 @@ export class AgentSession {
1929
1931
  this._modelRuntime.registerProvider(name, config);
1930
1932
  this._refreshCurrentModelFromRegistry();
1931
1933
  },
1934
+ registerNativeProvider: (provider) => {
1935
+ this._modelRuntime.registerNativeProvider(provider);
1936
+ this._refreshCurrentModelFromRegistry();
1937
+ },
1932
1938
  unregisterProvider: (name) => {
1933
1939
  this._modelRuntime.unregisterProvider(name);
1934
1940
  this._refreshCurrentModelFromRegistry();
@@ -2082,6 +2088,34 @@ export class AgentSession {
2082
2088
  return false;
2083
2089
  return isRetryableAssistantError(message);
2084
2090
  }
2091
+ /**
2092
+ * Retry policy + callbacks shared by compaction and branch-summary summarization calls.
2093
+ * Uses the same `settings.retry` budget/backoff as agent-turn retries so a single transient
2094
+ * stream drop no longer fails the whole operation. `source` carries the context
2095
+ * the TUI needs to render the retry and recreate the underlying indicator.
2096
+ */
2097
+ _summarizationRetryCallbacks(source) {
2098
+ return {
2099
+ onRetryScheduled: (attempt, maxAttempts, delayMs, errorMessage) => {
2100
+ this._emit({
2101
+ type: "summarization_retry_scheduled",
2102
+ attempt,
2103
+ maxAttempts,
2104
+ delayMs,
2105
+ errorMessage,
2106
+ });
2107
+ },
2108
+ onRetryAttemptStart: () => {
2109
+ this._emit({
2110
+ type: "summarization_retry_attempt_start",
2111
+ ...source,
2112
+ });
2113
+ },
2114
+ onRetryFinished: () => {
2115
+ this._emit({ type: "summarization_retry_finished" });
2116
+ },
2117
+ };
2118
+ }
2085
2119
  /**
2086
2120
  * Prepare a retryable error for continuation with exponential backoff.
2087
2121
  * @returns true if the caller should continue the agent, false otherwise
@@ -2327,6 +2361,7 @@ export class AgentSession {
2327
2361
  // Run default summarizer if needed
2328
2362
  let summaryText;
2329
2363
  let summaryDetails;
2364
+ let summaryUsage;
2330
2365
  if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
2331
2366
  const model = this.model;
2332
2367
  const { apiKey, headers, env } = await this._getSummarizationRequestAuth(model);
@@ -2340,7 +2375,9 @@ export class AgentSession {
2340
2375
  customInstructions,
2341
2376
  replaceInstructions,
2342
2377
  reserveTokens: branchSummarySettings.reserveTokens,
2343
- streamFn: this.agent.streamFn,
2378
+ streamFn: this.agent.streamFunction,
2379
+ retry: this.settingsManager.getRetrySettings(),
2380
+ callbacks: this._summarizationRetryCallbacks({ source: "branchSummary" }),
2344
2381
  });
2345
2382
  if (result.aborted) {
2346
2383
  return { cancelled: true, aborted: true };
@@ -2349,6 +2386,7 @@ export class AgentSession {
2349
2386
  throw new Error(result.error);
2350
2387
  }
2351
2388
  summaryText = result.summary;
2389
+ summaryUsage = result.usage;
2352
2390
  summaryDetails = {
2353
2391
  readFiles: result.readFiles || [],
2354
2392
  modifiedFiles: result.modifiedFiles || [],
@@ -2357,6 +2395,7 @@ export class AgentSession {
2357
2395
  else if (extensionSummary) {
2358
2396
  summaryText = extensionSummary.summary;
2359
2397
  summaryDetails = extensionSummary.details;
2398
+ summaryUsage = extensionSummary.usage;
2360
2399
  }
2361
2400
  // Determine the new leaf position based on target type
2362
2401
  let newLeafId;
@@ -2364,18 +2403,12 @@ export class AgentSession {
2364
2403
  if (targetEntry.type === "message" && targetEntry.message.role === "user") {
2365
2404
  // User message: leaf = parent (null if root), text goes to editor
2366
2405
  newLeafId = targetEntry.parentId;
2367
- editorText = this._extractUserMessageText(targetEntry.message.content);
2406
+ editorText = contentText(targetEntry.message.content, "");
2368
2407
  }
2369
2408
  else if (targetEntry.type === "custom_message") {
2370
2409
  // Custom message: leaf = parent (null if root), text goes to editor
2371
2410
  newLeafId = targetEntry.parentId;
2372
- editorText =
2373
- typeof targetEntry.content === "string"
2374
- ? targetEntry.content
2375
- : targetEntry.content
2376
- .filter((c) => c.type === "text")
2377
- .map((c) => c.text)
2378
- .join("");
2411
+ editorText = contentText(targetEntry.content, "");
2379
2412
  }
2380
2413
  else {
2381
2414
  // Non-user message: leaf = selected node
@@ -2386,7 +2419,7 @@ export class AgentSession {
2386
2419
  let summaryEntry;
2387
2420
  if (summaryText) {
2388
2421
  // Create summary at target position (can be null for root)
2389
- const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension);
2422
+ const summaryId = this.sessionManager.branchWithSummary(newLeafId, summaryText, summaryDetails, fromExtension, summaryUsage);
2390
2423
  summaryEntry = this.sessionManager.getEntry(summaryId);
2391
2424
  // Attach label to the summary entry
2392
2425
  if (label) {
@@ -2434,24 +2467,13 @@ export class AgentSession {
2434
2467
  continue;
2435
2468
  if (entry.message.role !== "user")
2436
2469
  continue;
2437
- const text = this._extractUserMessageText(entry.message.content);
2470
+ const text = contentText(entry.message.content, "");
2438
2471
  if (text) {
2439
2472
  result.push({ entryId: entry.id, text });
2440
2473
  }
2441
2474
  }
2442
2475
  return result;
2443
2476
  }
2444
- _extractUserMessageText(content) {
2445
- if (typeof content === "string")
2446
- return content;
2447
- if (Array.isArray(content)) {
2448
- return content
2449
- .filter((c) => c.type === "text")
2450
- .map((c) => c.text)
2451
- .join("");
2452
- }
2453
- return "";
2454
- }
2455
2477
  /**
2456
2478
  * Get session statistics. Aggregates over ALL session entries (including
2457
2479
  * history that was compacted away), so token/cost totals reflect what was
@@ -2463,12 +2485,11 @@ export class AgentSession {
2463
2485
  let toolResults = 0;
2464
2486
  let totalMessages = 0;
2465
2487
  let toolCalls = 0;
2466
- let totalInput = 0;
2467
- let totalOutput = 0;
2468
- let totalCacheRead = 0;
2469
- let totalCacheWrite = 0;
2470
- let totalCost = 0;
2488
+ const usageTotals = createUsageTotals();
2471
2489
  for (const entry of this.sessionManager.getEntries()) {
2490
+ if ((entry.type === "branch_summary" || entry.type === "compaction") && entry.usage) {
2491
+ addUsageToTotals(usageTotals, entry.usage);
2492
+ }
2472
2493
  if (entry.type !== "message")
2473
2494
  continue;
2474
2495
  totalMessages++;
@@ -2478,6 +2499,9 @@ export class AgentSession {
2478
2499
  }
2479
2500
  else if (message.role === "toolResult") {
2480
2501
  toolResults++;
2502
+ if (message.usage) {
2503
+ addUsageToTotals(usageTotals, message.usage);
2504
+ }
2481
2505
  }
2482
2506
  else if (message.role === "assistant") {
2483
2507
  assistantMessages++;
@@ -2485,12 +2509,7 @@ export class AgentSession {
2485
2509
  if (Array.isArray(assistantMsg.content)) {
2486
2510
  toolCalls += assistantMsg.content.filter((c) => c.type === "toolCall").length;
2487
2511
  }
2488
- const usage = assistantMsg.usage;
2489
- totalInput += usage.input;
2490
- totalOutput += usage.output;
2491
- totalCacheRead += usage.cacheRead;
2492
- totalCacheWrite += usage.cacheWrite;
2493
- totalCost += usage.cost.total;
2512
+ addUsageToTotals(usageTotals, assistantMsg.usage);
2494
2513
  }
2495
2514
  }
2496
2515
  return {
@@ -2502,13 +2521,13 @@ export class AgentSession {
2502
2521
  toolResults,
2503
2522
  totalMessages,
2504
2523
  tokens: {
2505
- input: totalInput,
2506
- output: totalOutput,
2507
- cacheRead: totalCacheRead,
2508
- cacheWrite: totalCacheWrite,
2509
- total: totalInput + totalOutput + totalCacheRead + totalCacheWrite,
2524
+ input: usageTotals.input,
2525
+ output: usageTotals.output,
2526
+ cacheRead: usageTotals.cacheRead,
2527
+ cacheWrite: usageTotals.cacheWrite,
2528
+ total: usageTotals.input + usageTotals.output + usageTotals.cacheRead + usageTotals.cacheWrite,
2510
2529
  },
2511
- cost: totalCost,
2530
+ cost: usageTotals.cost,
2512
2531
  contextUsage: this.getContextUsage(),
2513
2532
  };
2514
2533
  }