@bacnh85/pi-sub 0.1.10 → 0.1.12

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/README.md CHANGED
@@ -34,25 +34,29 @@ The footer status appears after Pi's built-in status/token usage line and includ
34
34
  Example subscription line:
35
35
 
36
36
  ```text
37
- (user@example.com) R:15%/2H W:20%/3D
37
+ (user@example.com) R:15%/2H W:20%/3D 42 tok/s
38
38
  ```
39
39
 
40
40
  ### OpenCode Go
41
41
 
42
- OpenCode Go does not expose a public usage-window API, so the footer shows the active account/key label and accumulated session cost:
42
+ OpenCode Go does not expose a public usage-window API, so the footer shows the active account/key label, accumulated session cost, and last response speed:
43
43
 
44
44
  ```text
45
- OpenCode Go (OpenCode Go key#1a2b3c4d) $0.23
45
+ OpenCode Go (OpenCode Go key#1a2b3c4d) $0.23 42 tok/s
46
46
  ```
47
47
 
48
48
  ### Z.ai
49
49
 
50
- Z.ai (GLM Coding Plan) shows the active account/key label plus 5-hour rolling and weekly remaining quota with reset countdowns:
50
+ Z.ai (GLM Coding Plan) shows the active account/key label, 5-hour rolling and weekly remaining quota with reset countdowns, and last response speed:
51
51
 
52
52
  ```text
53
- (Z.ai key#1a2b3c4d) R:55%/2H W:80%/3D
53
+ (Z.ai key#1a2b3c4d) R:55%/2H W:80%/3D 42 tok/s
54
54
  ```
55
55
 
56
+ ### Tokens per second
57
+
58
+ `pi-sub` tracks each response's tokens-per-second (tok/s) speed by measuring the time from provider request to message completion against the response's output token count. The last response's speed is shown in the footer next to usage data. The `/sub` detail view shows both the last response speed and the session-wide average.
59
+
56
60
  When the current model provider is not supported, `pi-sub` clears its subscription line and does not refresh subscription data.
57
61
 
58
62
  ## Commands
@@ -63,18 +67,23 @@ When the current model provider is not supported, `pi-sub` clears its subscripti
63
67
  | `/sub status` | Same as `/sub`. |
64
68
  | `/sub refresh` | Force a usage refresh, then show details. |
65
69
 
66
- When Pi OpenAI Codex auth is available, `/sub` shows the active account usage:
70
+ When Pi OpenAI Codex auth is available, `/sub` shows the active account usage and speed:
67
71
 
68
72
  ```text
73
+ Provider: Codex · Model: o4-mini · Fetched: 14:23
74
+ Session cost: $0.12
75
+ Last response: 42 tok/s · Session avg: 39 tok/s
76
+
69
77
  ACCOUNT PLAN ROLLING WEEKLY LAST ACTIVITY
70
78
  * user@example.com Plus 15%/2H 20%/3D Now
71
79
  ```
72
80
 
73
- For OpenCode Go, `/sub` shows the provider/model, active account/key label, and session cost:
81
+ For OpenCode Go, `/sub` shows the provider/model, active account/key label, session cost, and speed:
74
82
 
75
83
  ```text
76
84
  Provider: OpenCode Go · Model: kimi-k2.6 · Fetched: 14:23
77
85
  Session cost: $0.23
86
+ Last response: 42 tok/s · Session avg: 39 tok/s
78
87
 
79
88
  ACCOUNT PLAN LAST ACTIVITY
80
89
  ------------------------------------------------------
@@ -83,10 +92,12 @@ Session cost: $0.23
83
92
  OpenCode Go does not expose usage windows.
84
93
  ```
85
94
 
86
- For Z.ai, `/sub` shows the rolling and weekly quota windows:
95
+ For Z.ai, `/sub` shows the rolling and weekly quota windows and speed:
87
96
 
88
97
  ```text
89
98
  Provider: Z.ai · Model: glm-5.2 · Fetched: 14:23
99
+ Session cost: $0.05
100
+ Last response: 42 tok/s · Session avg: 39 tok/s
90
101
 
91
102
  ACCOUNT PLAN ROLLING WEEKLY LAST ACTIVITY
92
103
  ------------------------------------------------------------------------
@@ -1,5 +1,4 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import { Box, Text } from "@earendil-works/pi-tui";
3
2
  import { createHash } from "node:crypto";
4
3
  import fs from "node:fs/promises";
5
4
  import os from "node:os";
@@ -86,6 +85,10 @@ interface State {
86
85
  inFlight?: Promise<SubscriptionUsageSnapshot>;
87
86
  refreshTimer?: NodeJS.Timeout;
88
87
  debounceTimer?: NodeJS.Timeout;
88
+ responseStartTime?: number;
89
+ lastTokPerSec?: number;
90
+ cumulativeOutput: number;
91
+ cumulativeDurationMs: number;
89
92
  }
90
93
 
91
94
  function isCodexModel(model: ModelLike): boolean {
@@ -532,6 +535,7 @@ function renderSubscriptionLine(ctx: ExtensionContext, state: State): void {
532
535
  const cost = snapshot.cost;
533
536
  const hasWindows = windowParts.length > 0;
534
537
  if (cost !== undefined && cost > 0) segments.push(`$${cost.toFixed(2)}`);
538
+ if (state.lastTokPerSec !== undefined) segments.push(`${state.lastTokPerSec} tok/s`);
535
539
  if (segments.length === 0) {
536
540
  line = `Sub ${state.adapter.displayName}`;
537
541
  } else if (!hasWindows) {
@@ -657,7 +661,13 @@ function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: St
657
661
  });
658
662
 
659
663
  const costLine = snapshot.cost !== undefined ? `\nSession cost: $${snapshot.cost.toFixed(2)}` : "";
660
- const lines = [`Provider: ${snapshot.providerDisplayName} · Model: ${state.model?.id ?? "unknown-model"} · Fetched: ${new Date(snapshot.fetchedAt).toLocaleTimeString()}${costLine}`, "", header, sep, ...body];
664
+ const tokPerSecLine = state.lastTokPerSec !== undefined
665
+ ? `\nLast response: ${state.lastTokPerSec} tok/s` +
666
+ (state.cumulativeDurationMs > 0
667
+ ? ` · Session avg: ${Math.round(state.cumulativeOutput / (state.cumulativeDurationMs / 1000))} tok/s`
668
+ : "")
669
+ : "";
670
+ const lines = [`Provider: ${snapshot.providerDisplayName} · Model: ${state.model?.id ?? "unknown-model"} · Fetched: ${new Date(snapshot.fetchedAt).toLocaleTimeString()}${costLine}${tokPerSecLine}`, "", header, sep, ...body];
661
671
  if (!hasFiveHour && !hasWeekly) {
662
672
  lines.push("", `${snapshot.providerDisplayName} does not expose usage windows.`);
663
673
  }
@@ -665,13 +675,7 @@ function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: St
665
675
  }
666
676
 
667
677
  export default function (pi: ExtensionAPI) {
668
- const state: State = { lastRefreshAt: 0, refreshGeneration: 0 };
669
-
670
- pi.registerMessageRenderer(MESSAGE_TYPE, (message, _options, theme) => {
671
- const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
672
- box.addChild(new Text(String(message.content ?? ""), 0, 0));
673
- return box;
674
- });
678
+ const state: State = { lastRefreshAt: 0, refreshGeneration: 0, cumulativeOutput: 0, cumulativeDurationMs: 0 };
675
679
 
676
680
  pi.on("session_start", async (_event, ctx) => {
677
681
  updateActiveAdapter(ctx, state, ctx.model);
@@ -683,6 +687,24 @@ export default function (pi: ExtensionAPI) {
683
687
  if (state.adapter) void refreshUsage(ctx, state, true);
684
688
  });
685
689
 
690
+ pi.on("before_provider_request", async (_event, _ctx) => {
691
+ state.responseStartTime = Date.now();
692
+ });
693
+
694
+ pi.on("message_end", async (event, ctx) => {
695
+ if (event.message.role === "assistant" && state.responseStartTime) {
696
+ const output = (event.message.usage as any)?.output ?? 0;
697
+ const elapsed = Date.now() - state.responseStartTime;
698
+ state.responseStartTime = undefined;
699
+ if (elapsed > 0 && output > 0) {
700
+ state.lastTokPerSec = Math.round(output / (elapsed / 1000));
701
+ state.cumulativeOutput += output;
702
+ state.cumulativeDurationMs += elapsed;
703
+ if (state.adapter) renderSubscriptionLine(ctx, state);
704
+ }
705
+ }
706
+ });
707
+
686
708
  pi.on("after_provider_response", async (_event, ctx) => {
687
709
  if (state.adapter) scheduleRefresh(ctx, state);
688
710
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacnh85/pi-sub",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Pi extension showing subscription usage for supported model providers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,7 +26,8 @@
26
26
  ],
27
27
  "files": [
28
28
  "README.md",
29
- "extensions/"
29
+ "extensions/index.ts",
30
+ "extensions/package.json"
30
31
  ],
31
32
  "pi": {
32
33
  "extensions": [
@@ -37,7 +38,6 @@
37
38
  "node": ">=20.3.0"
38
39
  },
39
40
  "peerDependencies": {
40
- "@earendil-works/pi-coding-agent": "*",
41
- "@earendil-works/pi-tui": "*"
41
+ "@earendil-works/pi-coding-agent": "*"
42
42
  }
43
43
  }