@bacnh85/pi-sub 0.1.11 → 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
  ------------------------------------------------------------------------
@@ -85,6 +85,10 @@ interface State {
85
85
  inFlight?: Promise<SubscriptionUsageSnapshot>;
86
86
  refreshTimer?: NodeJS.Timeout;
87
87
  debounceTimer?: NodeJS.Timeout;
88
+ responseStartTime?: number;
89
+ lastTokPerSec?: number;
90
+ cumulativeOutput: number;
91
+ cumulativeDurationMs: number;
88
92
  }
89
93
 
90
94
  function isCodexModel(model: ModelLike): boolean {
@@ -531,6 +535,7 @@ function renderSubscriptionLine(ctx: ExtensionContext, state: State): void {
531
535
  const cost = snapshot.cost;
532
536
  const hasWindows = windowParts.length > 0;
533
537
  if (cost !== undefined && cost > 0) segments.push(`$${cost.toFixed(2)}`);
538
+ if (state.lastTokPerSec !== undefined) segments.push(`${state.lastTokPerSec} tok/s`);
534
539
  if (segments.length === 0) {
535
540
  line = `Sub ${state.adapter.displayName}`;
536
541
  } else if (!hasWindows) {
@@ -656,7 +661,13 @@ function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: St
656
661
  });
657
662
 
658
663
  const costLine = snapshot.cost !== undefined ? `\nSession cost: $${snapshot.cost.toFixed(2)}` : "";
659
- 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];
660
671
  if (!hasFiveHour && !hasWeekly) {
661
672
  lines.push("", `${snapshot.providerDisplayName} does not expose usage windows.`);
662
673
  }
@@ -664,7 +675,7 @@ function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: St
664
675
  }
665
676
 
666
677
  export default function (pi: ExtensionAPI) {
667
- const state: State = { lastRefreshAt: 0, refreshGeneration: 0 };
678
+ const state: State = { lastRefreshAt: 0, refreshGeneration: 0, cumulativeOutput: 0, cumulativeDurationMs: 0 };
668
679
 
669
680
  pi.on("session_start", async (_event, ctx) => {
670
681
  updateActiveAdapter(ctx, state, ctx.model);
@@ -676,6 +687,24 @@ export default function (pi: ExtensionAPI) {
676
687
  if (state.adapter) void refreshUsage(ctx, state, true);
677
688
  });
678
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
+
679
708
  pi.on("after_provider_response", async (_event, ctx) => {
680
709
  if (state.adapter) scheduleRefresh(ctx, state);
681
710
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacnh85/pi-sub",
3
- "version": "0.1.11",
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",