@narumitw/pi-usage 0.60.7 → 0.60.9
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 +1 -1
- package/dist/index.ts +91 -340
- package/dist/index.ts.map +2 -2
- package/docs/providers.md +2 -1
- package/package.json +73 -74
- package/src/codex-fast-runtime.ts +148 -184
- package/src/codex-fast.ts +73 -83
- package/src/codex-resets.ts +249 -278
- package/src/core.ts +204 -206
- package/src/format.ts +519 -574
- package/src/index.ts +77 -77
- package/src/oauth-credential-source.ts +57 -65
- package/src/providers/baseten.ts +32 -36
- package/src/providers/codex.ts +109 -120
- package/src/providers/deepseek.ts +59 -64
- package/src/providers/fireworks.ts +219 -238
- package/src/providers/github-copilot.ts +89 -95
- package/src/providers/kimi-coding.ts +202 -213
- package/src/providers/minimax.ts +214 -219
- package/src/providers/moonshot.ts +41 -46
- package/src/providers/opencode-zen.ts +56 -59
- package/src/providers/openrouter.ts +50 -53
- package/src/providers/vercel-ai-gateway.ts +33 -33
- package/src/providers/xai.ts +141 -148
- package/src/providers/zai-errors.ts +75 -75
- package/src/providers/zai.ts +173 -189
- package/src/query.ts +887 -967
- package/src/settings.ts +275 -304
- package/src/types.ts +145 -145
- package/src/usage-helpers.ts +18 -18
- package/src/usage-settings-ui.ts +117 -126
- package/src/usage-targets.ts +98 -114
- package/src/usage.ts +1264 -1426
package/docs/providers.md
CHANGED
|
@@ -54,7 +54,8 @@ The extension queries the fixed usage endpoint only when both the selected model
|
|
|
54
54
|
Custom and proxy origins fail before network access, redirects are rejected, and the credential is never sent to an override from Kimi Code's environment-specific development path.
|
|
55
55
|
|
|
56
56
|
Plan buckets remain integer request counts and are rendered with their source-defined windows.
|
|
57
|
-
|
|
57
|
+
Live responses omit both `used` and `remaining` on untouched rows and may report only `remaining` once usage starts, so a row with a positive `limit` and no counters means zero usage, and when only one counter is present the other is derived from `limit`.
|
|
58
|
+
Counters that are present but malformed, unknown units, duplicate windows, invalid timestamps, and malformed rows remain unavailable rather than receiving guessed semantics.
|
|
58
59
|
Booster-wallet `amount` and `amountLeft` values use Kimi's first-party conversion of 1,000,000 fixed-point units per cent, while monthly values already arrive in cents.
|
|
59
60
|
Wallet values retain their currency and stay separate from plan requests and percentages in reports and the statusline.
|
|
60
61
|
Wallet fields remain unavailable unless the response supplies one consistent currency; missing monthly values are omitted, and an enabled zero cap is shown as zero.
|
package/package.json
CHANGED
|
@@ -1,76 +1,75 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
2
|
+
"name": "@narumitw/pi-usage",
|
|
3
|
+
"version": "0.60.9",
|
|
4
|
+
"description": "Pi extension that shows current-account usage and DeepSeek API balance for supported providers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"private": false,
|
|
8
|
+
"keywords": [
|
|
9
|
+
"pi-package",
|
|
10
|
+
"pi-extension",
|
|
11
|
+
"pi",
|
|
12
|
+
"usage",
|
|
13
|
+
"quota",
|
|
14
|
+
"balance",
|
|
15
|
+
"deepseek",
|
|
16
|
+
"fireworks",
|
|
17
|
+
"baseten",
|
|
18
|
+
"vercel-ai-gateway",
|
|
19
|
+
"codex",
|
|
20
|
+
"kimi",
|
|
21
|
+
"kimi-coding",
|
|
22
|
+
"moonshot",
|
|
23
|
+
"moonshotai",
|
|
24
|
+
"minimax",
|
|
25
|
+
"token-plan",
|
|
26
|
+
"copilot",
|
|
27
|
+
"openrouter",
|
|
28
|
+
"opencode",
|
|
29
|
+
"zen",
|
|
30
|
+
"zai",
|
|
31
|
+
"glm",
|
|
32
|
+
"xai",
|
|
33
|
+
"grok"
|
|
34
|
+
],
|
|
35
|
+
"files": [
|
|
36
|
+
"src",
|
|
37
|
+
"dist",
|
|
38
|
+
"docs",
|
|
39
|
+
"README.md",
|
|
40
|
+
"LICENSE"
|
|
41
|
+
],
|
|
42
|
+
"pi": {
|
|
43
|
+
"extensions": [
|
|
44
|
+
"./dist/index.ts"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "node scripts/build-runtime.mjs",
|
|
49
|
+
"check": "npm run build && biome check --vcs-use-ignore-file=false src test scripts package.json tsconfig.json README.md && npm run typecheck",
|
|
50
|
+
"format": "biome check --write --vcs-use-ignore-file=false src test scripts package.json tsconfig.json README.md",
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"prepack": "npm run build"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@earendil-works/pi-ai": "*",
|
|
56
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
57
|
+
"@earendil-works/pi-tui": "*"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@earendil-works/pi-ai": "0.86.0",
|
|
61
|
+
"@earendil-works/pi-coding-agent": "0.86.0",
|
|
62
|
+
"@earendil-works/pi-tui": "0.86.0",
|
|
63
|
+
"@types/node": "26.6.2",
|
|
64
|
+
"esbuild": "0.28.2",
|
|
65
|
+
"typescript": "7.0.2"
|
|
66
|
+
},
|
|
67
|
+
"repository": {
|
|
68
|
+
"type": "git",
|
|
69
|
+
"url": "https://github.com/narumiruna/pi-extensions",
|
|
70
|
+
"directory": "packages/pi-usage"
|
|
71
|
+
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@narumitw/pi-tui-kit": "^0.61.0"
|
|
74
|
+
}
|
|
76
75
|
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExtensionAPI,
|
|
3
|
-
ExtensionCommandContext,
|
|
4
|
-
ExtensionContext,
|
|
5
|
-
} from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
2
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
codexFastAvailability,
|
|
4
|
+
codexFastIsEffective,
|
|
5
|
+
codexFastStatusLabel,
|
|
6
|
+
correctCodexFastMessageCost,
|
|
7
|
+
rewriteCodexFastPayload,
|
|
12
8
|
} from "./codex-fast.js";
|
|
13
9
|
import { errorMessage } from "./core.js";
|
|
14
10
|
import { isStaleExtensionContextError } from "./query.js";
|
|
@@ -21,200 +17,168 @@ type PendingFastRequest = { fastRequested: boolean; model: PiModel };
|
|
|
21
17
|
export const FAST_USAGE_WARNING = "Fast is about 1.5× faster and uses more of your plan allowance.";
|
|
22
18
|
|
|
23
19
|
export function registerCodexFastMode(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
pi: ExtensionAPI,
|
|
21
|
+
settingsRuntime: UsageSettingsRuntime,
|
|
22
|
+
refreshStatus: (ctx: ExtensionContext) => void,
|
|
23
|
+
options: { registerSessionStart?: boolean } = {},
|
|
28
24
|
) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
let sessionController = new AbortController();
|
|
26
|
+
let generation = 0;
|
|
27
|
+
const pendingFastRequests = new Map<string, PendingFastRequest>();
|
|
32
28
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
? `Codex Fast mode enabled. ${FAST_USAGE_WARNING}`
|
|
61
|
-
: "Codex Fast mode disabled; standard routing will be used.",
|
|
62
|
-
"info",
|
|
63
|
-
);
|
|
64
|
-
return true;
|
|
65
|
-
};
|
|
29
|
+
const toggle = async (
|
|
30
|
+
ctx: ExtensionCommandContext,
|
|
31
|
+
enabled: boolean,
|
|
32
|
+
callerSignal?: AbortSignal,
|
|
33
|
+
): Promise<boolean> => {
|
|
34
|
+
const ownerGeneration = generation;
|
|
35
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
36
|
+
const signal = callerSignal ? AbortSignal.any([callerSignal, sessionController.signal]) : sessionController.signal;
|
|
37
|
+
try {
|
|
38
|
+
await settingsRuntime.update({ codexFastMode: enabled }, signal);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (isAbortError(error) || isStaleExtensionContextError(error)) return false;
|
|
41
|
+
ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (signal.aborted || ownerGeneration !== generation || ctx.sessionManager.getSessionId() !== sessionId) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
refreshStatus(ctx);
|
|
48
|
+
ctx.ui.notify(
|
|
49
|
+
enabled
|
|
50
|
+
? `Codex Fast mode enabled. ${FAST_USAGE_WARNING}`
|
|
51
|
+
: "Codex Fast mode disabled; standard routing will be used.",
|
|
52
|
+
"info",
|
|
53
|
+
);
|
|
54
|
+
return true;
|
|
55
|
+
};
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
await toggle(ctx, !availability.enabled);
|
|
96
|
-
},
|
|
97
|
-
});
|
|
57
|
+
pi.registerCommand("fast", {
|
|
58
|
+
description: "Toggle Codex Fast mode",
|
|
59
|
+
handler: async (args, ctx) => {
|
|
60
|
+
if (args.trim()) {
|
|
61
|
+
if (!ctx.hasUI) throw new Error("/fast does not accept arguments.");
|
|
62
|
+
ctx.ui.notify("/fast does not accept arguments.", "warning");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!ctx.hasUI) throw new Error("/fast requires TUI or RPC mode.");
|
|
66
|
+
const availability = codexFastAvailability(ctx.model, settingsRuntime.get().settings.codexFastMode);
|
|
67
|
+
if (availability.kind === "not-codex") {
|
|
68
|
+
ctx.ui.notify("/fast is available only for the active OpenAI Codex model.", "warning");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (availability.kind === "unavailable") {
|
|
72
|
+
ctx.ui.notify(availability.reason, "warning");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (settingsRuntime.get().kind === "invalid") {
|
|
76
|
+
ctx.ui.notify("pi-usage.json is invalid; repair it and run /reload before changing Fast mode.", "error");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
await toggle(ctx, !availability.enabled);
|
|
80
|
+
},
|
|
81
|
+
});
|
|
98
82
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
"warning",
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
refreshStatus(ctx);
|
|
134
|
-
})();
|
|
135
|
-
};
|
|
83
|
+
const prepareSession = (ctx: ExtensionContext): Promise<void> => {
|
|
84
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
85
|
+
generation += 1;
|
|
86
|
+
sessionController.abort();
|
|
87
|
+
pendingFastRequests.clear();
|
|
88
|
+
sessionController = new AbortController();
|
|
89
|
+
const ownerGeneration = generation;
|
|
90
|
+
return (async () => {
|
|
91
|
+
let state: Readonly<UsageSettingsState>;
|
|
92
|
+
try {
|
|
93
|
+
state = await settingsRuntime.reload(sessionController.signal);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (sessionController.signal.aborted || ownerGeneration !== generation) return;
|
|
96
|
+
if (ctx.hasUI) {
|
|
97
|
+
ctx.ui.notify(`Could not load pi-usage.json; using defaults. ${errorMessage(error)}`, "warning");
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (
|
|
102
|
+
sessionController.signal.aborted ||
|
|
103
|
+
ownerGeneration !== generation ||
|
|
104
|
+
ctx.sessionManager.getSessionId() !== sessionId
|
|
105
|
+
) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (ctx.hasUI && state.kind === "invalid") {
|
|
109
|
+
ctx.ui.notify(`Invalid pi-usage.json; using defaults without overwriting it. ${state.issue}`, "warning");
|
|
110
|
+
}
|
|
111
|
+
refreshStatus(ctx);
|
|
112
|
+
})();
|
|
113
|
+
};
|
|
136
114
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
115
|
+
if (options.registerSessionStart !== false) {
|
|
116
|
+
pi.on("session_start", async (_event, ctx) => prepareSession(ctx));
|
|
117
|
+
}
|
|
140
118
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return message ? { message: message as never } : undefined;
|
|
165
|
-
});
|
|
166
|
-
pi.on("session_shutdown", async () => {
|
|
167
|
-
generation += 1;
|
|
168
|
-
sessionController.abort();
|
|
169
|
-
pendingFastRequests.clear();
|
|
170
|
-
await settingsRuntime.flush();
|
|
171
|
-
});
|
|
119
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
120
|
+
const rewritten = rewriteCodexFastPayload(event.payload, ctx.model, settingsRuntime.get().settings.codexFastMode);
|
|
121
|
+
const key = activeRequestKey(ctx);
|
|
122
|
+
if (key && ctx.model) {
|
|
123
|
+
pendingFastRequests.set(key, {
|
|
124
|
+
fastRequested: isRecord(rewritten) && rewritten.service_tier === "priority",
|
|
125
|
+
model: ctx.model,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return rewritten;
|
|
129
|
+
});
|
|
130
|
+
pi.on("message_end", (event, ctx) => {
|
|
131
|
+
const request = consumeFastRequest(ctx, event.message, pendingFastRequests);
|
|
132
|
+
if (request === NO_FAST_REQUEST) return undefined;
|
|
133
|
+
const message = correctCodexFastMessageCost(event.message, request.model, request.fastRequested);
|
|
134
|
+
return message ? { message: message as never } : undefined;
|
|
135
|
+
});
|
|
136
|
+
pi.on("session_shutdown", async () => {
|
|
137
|
+
generation += 1;
|
|
138
|
+
sessionController.abort();
|
|
139
|
+
pendingFastRequests.clear();
|
|
140
|
+
await settingsRuntime.flush();
|
|
141
|
+
});
|
|
172
142
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
},
|
|
184
|
-
toggle,
|
|
185
|
-
};
|
|
143
|
+
return {
|
|
144
|
+
prepareSession,
|
|
145
|
+
availability(model: PiModel | undefined) {
|
|
146
|
+
return codexFastAvailability(model, settingsRuntime.get().settings.codexFastMode);
|
|
147
|
+
},
|
|
148
|
+
decorateStatus(model: PiModel | undefined, status: string) {
|
|
149
|
+
return codexFastStatusLabel(status, codexFastIsEffective(model, settingsRuntime.get().settings.codexFastMode));
|
|
150
|
+
},
|
|
151
|
+
toggle,
|
|
152
|
+
};
|
|
186
153
|
}
|
|
187
154
|
|
|
188
155
|
function activeRequestKey(ctx: ExtensionContext): string | undefined {
|
|
189
|
-
|
|
190
|
-
|
|
156
|
+
const model = ctx.model;
|
|
157
|
+
return model ? `${ctx.sessionManager.getSessionId()}:${model.provider}/${model.id}` : undefined;
|
|
191
158
|
}
|
|
192
159
|
|
|
193
160
|
function consumeFastRequest(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
161
|
+
ctx: ExtensionContext,
|
|
162
|
+
message: unknown,
|
|
163
|
+
pending: Map<string, PendingFastRequest>,
|
|
197
164
|
): PendingFastRequest | typeof NO_FAST_REQUEST {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
165
|
+
if (!isRecord(message) || message.role !== "assistant") return NO_FAST_REQUEST;
|
|
166
|
+
const key = messageRequestKey(ctx, message);
|
|
167
|
+
if (!key) return NO_FAST_REQUEST;
|
|
168
|
+
const request = pending.get(key);
|
|
169
|
+
pending.delete(key);
|
|
170
|
+
return request ?? NO_FAST_REQUEST;
|
|
204
171
|
}
|
|
205
172
|
|
|
206
|
-
function messageRequestKey(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
): string | undefined {
|
|
210
|
-
if (typeof message.provider !== "string" || typeof message.model !== "string") return undefined;
|
|
211
|
-
return `${ctx.sessionManager.getSessionId()}:${message.provider}/${message.model}`;
|
|
173
|
+
function messageRequestKey(ctx: ExtensionContext, message: Record<string, unknown>): string | undefined {
|
|
174
|
+
if (typeof message.provider !== "string" || typeof message.model !== "string") return undefined;
|
|
175
|
+
return `${ctx.sessionManager.getSessionId()}:${message.provider}/${message.model}`;
|
|
212
176
|
}
|
|
213
177
|
|
|
214
178
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
215
|
-
|
|
179
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
216
180
|
}
|
|
217
181
|
|
|
218
182
|
function isAbortError(error: unknown): boolean {
|
|
219
|
-
|
|
183
|
+
return error instanceof Error && error.name === "AbortError";
|
|
220
184
|
}
|