@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 +19 -8
- package/extensions/index.ts +31 -9
- package/package.json +4 -4
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
|
|
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
|
|
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,
|
|
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
|
------------------------------------------------------------------------
|
package/extensions/index.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
}
|