@oh-my-pi/pi-coding-agent 3.13.1337 → 3.14.0

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [3.14.0] - 2026-01-04
6
+ ### Added
7
+
8
+ - Added `getUsageStatistics()` method to SessionManager for tracking cumulative token usage and costs across session messages
9
+
10
+ ### Changed
11
+
12
+ - Changed status line to display usage statistics more efficiently by using centralized session statistics instead of recalculating from entries
13
+
5
14
  ## [3.13.1337] - 2026-01-04
6
15
 
7
16
  ## [3.9.1337] - 2026-01-04
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-coding-agent",
3
- "version": "3.13.1337",
3
+ "version": "3.14.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "ompConfig": {
@@ -39,9 +39,9 @@
39
39
  "prepublishOnly": "bun run generate-template && bun run clean && bun run build"
40
40
  },
41
41
  "dependencies": {
42
- "@oh-my-pi/pi-agent-core": "3.13.1337",
43
- "@oh-my-pi/pi-ai": "3.13.1337",
44
- "@oh-my-pi/pi-tui": "3.13.1337",
42
+ "@oh-my-pi/pi-agent-core": "3.14.0",
43
+ "@oh-my-pi/pi-ai": "3.14.0",
44
+ "@oh-my-pi/pi-tui": "3.14.0",
45
45
  "@sinclair/typebox": "^0.34.46",
46
46
  "ajv": "^8.17.1",
47
47
  "chalk": "^5.5.0",
@@ -13,4 +13,4 @@ export {
13
13
  export { execCommand, HookRunner, type HookErrorListener } from "./runner";
14
14
  export { wrapToolsWithHooks, wrapToolWithHooks } from "./tool-wrapper";
15
15
  export * from "./types";
16
- export type { ReadonlySessionManager } from "../session-manager";
16
+ export type { UsageStatistics, ReadonlySessionManager } from "../session-manager";
@@ -192,6 +192,7 @@ export type ReadonlySessionManager = Pick<
192
192
  | "getHeader"
193
193
  | "getEntries"
194
194
  | "getTree"
195
+ | "getUsageStatistics"
195
196
  >;
196
197
 
197
198
  /** Generate a unique short ID (8 hex chars, collision-checked) */
@@ -586,6 +587,14 @@ export function getRecentSessions(sessionDir: string, limit = 3): RecentSessionI
586
587
  * Use buildSessionContext() to get the resolved message list for the LLM, which
587
588
  * handles compaction summaries and follows the path from root to current leaf.
588
589
  */
590
+ export interface UsageStatistics {
591
+ input: number;
592
+ output: number;
593
+ cacheRead: number;
594
+ cacheWrite: number;
595
+ cost: number;
596
+ }
597
+
589
598
  export class SessionManager {
590
599
  private sessionId: string = "";
591
600
  private sessionTitle: string | undefined;
@@ -598,6 +607,7 @@ export class SessionManager {
598
607
  private byId: Map<string, SessionEntry> = new Map();
599
608
  private labelsById: Map<string, string> = new Map();
600
609
  private leafId: string | null = null;
610
+ private usageStatistics: UsageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
601
611
 
602
612
  private constructor(cwd: string, sessionDir: string, sessionFile: string | undefined, persist: boolean) {
603
613
  this.cwd = cwd;
@@ -649,6 +659,7 @@ export class SessionManager {
649
659
  this.byId.clear();
650
660
  this.leafId = null;
651
661
  this.flushed = false;
662
+ this.usageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
652
663
 
653
664
  // Only generate filename if persisting and not already set (e.g., via --session flag)
654
665
  if (this.persist && !this.sessionFile) {
@@ -662,6 +673,7 @@ export class SessionManager {
662
673
  this.byId.clear();
663
674
  this.labelsById.clear();
664
675
  this.leafId = null;
676
+ this.usageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
665
677
  for (const entry of this.fileEntries) {
666
678
  if (entry.type === "session") continue;
667
679
  this.byId.set(entry.id, entry);
@@ -673,6 +685,14 @@ export class SessionManager {
673
685
  this.labelsById.delete(entry.targetId);
674
686
  }
675
687
  }
688
+ if (entry.type === "message" && entry.message.role === "assistant") {
689
+ const usage = entry.message.usage;
690
+ this.usageStatistics.input += usage.input;
691
+ this.usageStatistics.output += usage.output;
692
+ this.usageStatistics.cacheRead += usage.cacheRead;
693
+ this.usageStatistics.cacheWrite += usage.cacheWrite;
694
+ this.usageStatistics.cost += usage.cost.total;
695
+ }
676
696
  }
677
697
  }
678
698
 
@@ -690,6 +710,11 @@ export class SessionManager {
690
710
  return this.cwd;
691
711
  }
692
712
 
713
+ /** Get usage statistics across all assistant messages in the session. */
714
+ getUsageStatistics(): UsageStatistics {
715
+ return this.usageStatistics;
716
+ }
717
+
693
718
  getSessionDir(): string {
694
719
  return this.sessionDir;
695
720
  }
@@ -755,6 +780,14 @@ export class SessionManager {
755
780
  this.byId.set(entry.id, entry);
756
781
  this.leafId = entry.id;
757
782
  this._persist(entry);
783
+ if (entry.type === "message" && entry.message.role === "assistant") {
784
+ const usage = entry.message.usage;
785
+ this.usageStatistics.input += usage.input;
786
+ this.usageStatistics.output += usage.output;
787
+ this.usageStatistics.cacheRead += usage.cacheRead;
788
+ this.usageStatistics.cacheWrite += usage.cacheWrite;
789
+ this.usageStatistics.cost += usage.cost.total;
790
+ }
758
791
  }
759
792
 
760
793
  /** Append a message as child of current leaf, then advance leaf. Returns entry id.
@@ -17,13 +17,14 @@ const THINKING_ICONS: Record<string, string> = {
17
17
 
18
18
  // Nerd Font icons
19
19
  const ICONS = {
20
- model: "\uf4bc", // robot/model
21
- folder: "\uf115", // folder
20
+ model: "\uec19", // robot/model
21
+ folder: "\uf115 ", // folder
22
22
  branch: "\ue725", // git branch
23
23
  sep: "\ue0b1", // powerline thin chevron
24
24
  tokens: "\ue26b", // coins
25
25
  context: "\ue70f", // window
26
26
  auto: "\udb80\udc68", // auto
27
+ pi: "\ue22c", // pi
27
28
  } as const;
28
29
 
29
30
  /** Create a colored text segment with background */
@@ -251,23 +252,6 @@ export class StatusLineComponent implements Component {
251
252
  private buildStatusLine(): string {
252
253
  const state = this.session.state;
253
254
 
254
- // Calculate cumulative usage from ALL session entries
255
- let totalInput = 0;
256
- let totalOutput = 0;
257
- let totalCacheRead = 0;
258
- let totalCacheWrite = 0;
259
- let totalCost = 0;
260
-
261
- for (const entry of this.session.sessionManager.getEntries()) {
262
- if (entry.type === "message" && entry.message.role === "assistant") {
263
- totalInput += entry.message.usage.input;
264
- totalOutput += entry.message.usage.output;
265
- totalCacheRead += entry.message.usage.cacheRead;
266
- totalCacheWrite += entry.message.usage.cacheWrite;
267
- totalCost += entry.message.usage.cost.total;
268
- }
269
- }
270
-
271
255
  // Get context percentage from last assistant message
272
256
  const lastAssistantMessage = state.messages
273
257
  .slice()
@@ -370,14 +354,15 @@ export class StatusLineComponent implements Component {
370
354
  // ═══════════════════════════════════════════════════════════════════════
371
355
  const spendParts: string[] = [];
372
356
 
373
- const totalTokens = totalInput + totalOutput + totalCacheRead + totalCacheWrite;
357
+ const { input, output, cacheRead, cacheWrite, cost } = this.session.sessionManager.getUsageStatistics();
358
+ const totalTokens = input + output + cacheRead + cacheWrite;
374
359
  if (totalTokens) {
375
360
  spendParts.push(`${ICONS.tokens} ${formatTokens(totalTokens)}`);
376
361
  }
377
362
 
378
363
  const usingSubscription = state.model ? this.session.modelRegistry.isUsingOAuth(state.model) : false;
379
- if (totalCost || usingSubscription) {
380
- const costDisplay = `$${totalCost.toFixed(2)}${usingSubscription ? " (sub)" : ""}`;
364
+ if (cost || usingSubscription) {
365
+ const costDisplay = `$${cost.toFixed(2)}${usingSubscription ? " (sub)" : ""}`;
381
366
  spendParts.push(costDisplay);
382
367
  }
383
368
 
@@ -391,6 +376,10 @@ export class StatusLineComponent implements Component {
391
376
 
392
377
  let statusLine = "";
393
378
 
379
+ // Pi segment
380
+ statusLine += plSegment(`${ICONS.pi} `, theme.getFgAnsi("statusLineContext"), bgAnsi);
381
+ statusLine += plSep(sepAnsi, bgAnsi);
382
+
394
383
  // Model segment
395
384
  statusLine += plSegment(modelContent, theme.getFgAnsi("statusLineModel"), bgAnsi);
396
385
  statusLine += plSep(sepAnsi, bgAnsi);