@oh-my-pi/pi-coding-agent 3.9.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,17 @@
|
|
|
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
|
+
|
|
14
|
+
## [3.13.1337] - 2026-01-04
|
|
15
|
+
|
|
5
16
|
## [3.9.1337] - 2026-01-04
|
|
6
17
|
|
|
7
18
|
### Changed
|
|
@@ -12,6 +23,7 @@
|
|
|
12
23
|
|
|
13
24
|
### Fixed
|
|
14
25
|
|
|
26
|
+
- Fixed status line not updating token counts and cost after starting a new session
|
|
15
27
|
- Fixed stale diagnostics persisting after file content changes in LSP client
|
|
16
28
|
|
|
17
29
|
## [3.8.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.
|
|
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.
|
|
43
|
-
"@oh-my-pi/pi-ai": "3.
|
|
44
|
-
"@oh-my-pi/pi-tui": "3.
|
|
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",
|
package/src/core/hooks/index.ts
CHANGED
|
@@ -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: "\
|
|
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
|
|
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 (
|
|
380
|
-
const costDisplay = `$${
|
|
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);
|
|
@@ -2408,6 +2408,10 @@ export class InteractiveMode {
|
|
|
2408
2408
|
// New session via session (emits hook and tool session events)
|
|
2409
2409
|
await this.session.newSession();
|
|
2410
2410
|
|
|
2411
|
+
// Update status line (token counts, cost reset)
|
|
2412
|
+
this.statusLine.invalidate();
|
|
2413
|
+
this.updateEditorTopBorder();
|
|
2414
|
+
|
|
2411
2415
|
// Clear UI state
|
|
2412
2416
|
this.chatContainer.clear();
|
|
2413
2417
|
this.pendingMessagesContainer.clear();
|