@openlines/opl-pi-sht 0.2.11 → 0.2.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 +3 -1
- package/extensions/opl-footer/README.md +2 -2
- package/extensions/opl-footer/index.ts +78 -4
- package/images/ss-init.png +0 -0
- package/images/ss-input-footer.png +0 -0
- package/images/ss-input-pet.png +0 -0
- package/images/ss-mode-custom.png +0 -0
- package/images/ss-questionnaire0.png +0 -0
- package/images/ss-questionnaire1.png +0 -0
- package/images/ss-simplebench.png +0 -0
- package/images/ss-todo.png +0 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
A portable collection of various Pi coding agent extensions. Repository directories and config files use `opl-`; established Pi-facing commands and tool names stay compatible.
|
|
16
16
|
|
|
17
17
|
>
|
|
18
|
+
> ### v0.2.12 - refresh OpenRouter/Codex usage during agent runs
|
|
19
|
+
> - `opl-footer` fixed usage refresh from agent_settled to after every turn/toolcall
|
|
18
20
|
> ### v0.2.11 - OpenRouter key-limit usage in opl-footer
|
|
19
21
|
> - `opl-footer` added an OpenRouter API key's used amount, cap, and percentage.
|
|
20
22
|
> ### v0.2.10 - Codex subscription usage in opl-footer
|
|
@@ -141,7 +143,7 @@ Cold prompt-cache write, measured /init session (opl-modes lazy tools + MCP adap
|
|
|
141
143
|
|
|
142
144
|
### See what the agent is doing
|
|
143
145
|
|
|
144
|
-
- **`opl-footer`** surfaces model, cost, token and cache activity, git state, agent status, per-turn timing,
|
|
146
|
+
- **`opl-footer`** surfaces model, cost, token and cache activity, git state, agent status, per-turn timing, optional exact remaining ChatGPT Codex subscription quota and OpenRouter API-key cap usage. Both usage segments refresh during agent runs after assistant responses and tool completions (30-second floor, coalesced trailing refresh). The OpenRouter segment resolves Pi's active key locally and calls only OpenRouter's key endpoint; keys and response bodies are neither persisted nor logged. `/configure-opl` edits and reorders its layout interactively.
|
|
145
147
|
- **`opl-input`** is a configurable editor that reflects the active mode's identity, so you always know which mode you are typing into.
|
|
146
148
|

|
|
147
149
|
|
|
@@ -100,7 +100,7 @@ See the tracked [`configs/opl-footer.json.sample`](../../configs/opl-footer.json
|
|
|
100
100
|
|
|
101
101
|
## Codex Subscription Usage
|
|
102
102
|
|
|
103
|
-
Add `codex_usage` to any row through `/configure-opl` or `opl-footer.json`. While the active model is the OAuth-authenticated `openai-codex` provider, the footer asks Pi's model registry to resolve the same short-lived OAuth access token that Pi uses for the selected model, then sends `GET https://chatgpt.com/backend-api/wham/usage` directly from the local Pi process. The request carries `Authorization: Bearer <Pi OAuth token>`, `Accept: application/json`, a fixed `User-Agent: opl-footer-codex-usage`, and the token's `chatgpt_account_id` JWT claim as `chatgpt-account-id` when present. It
|
|
103
|
+
Add `codex_usage` to any row through `/configure-opl` or `opl-footer.json`. While the active model is the OAuth-authenticated `openai-codex` provider, the footer asks Pi's model registry to resolve the same short-lived OAuth access token that Pi uses for the selected model, then sends `GET https://chatgpt.com/backend-api/wham/usage` directly from the local Pi process. The request carries `Authorization: Bearer <Pi OAuth token>`, `Accept: application/json`, a fixed `User-Agent: opl-footer-codex-usage`, and the token's `chatgpt_account_id` JWT claim as `chatgpt-account-id` when present. It refreshes after session start, model selection, each completed assistant response, each tool completion, and settled agent runs. Refreshes are non-blocking, with a 30-second floor and a 15-second request timeout; events within that floor coalesce into one trailing refresh, including events received during an in-flight request. Pending refreshes are cancelled on session reset, shutdown, or switching away from Codex, and recheck whether the segment is enabled before fetching. It classifies the returned windows by duration rather than response order. This is ChatGPT subscription quota, not OpenAI Platform API-key usage or billing.
|
|
104
104
|
|
|
105
105
|
No OAuth token, account ID, or response body is written to disk, logged, added to the Pi session, or sent anywhere other than `chatgpt.com` for that request. **The footer retains only the parsed 5-hour/weekly percentage and reset time in process memory for the active session.** The endpoint is an internal ChatGPT backend API and can change without notice. A failed refresh keeps the most recent exact snapshot and marks it `(stale)`; if no request has ever succeeded, the segment stays hidden. Disabling the segment prevents future requests and discards any result from a request already in flight.
|
|
106
106
|
|
|
@@ -108,7 +108,7 @@ No OAuth token, account ID, or response body is written to disk, logged, added t
|
|
|
108
108
|
|
|
109
109
|
Add `openrouter_usage` to any row through `/configure-opl` or `opl-footer.json`. While the selected model provider is `openrouter`, the footer resolves the same API key Pi uses for inference and sends `GET https://openrouter.ai/api/v1/key` from the local Pi process. It renders the configured per-key limit as `$<limit - limit_remaining> / $<limit> (<percent>%)`; an absent, malformed, or unlimited key cap leaves the segment hidden. The numerator deliberately uses OpenRouter's authoritative `limit_remaining`, rather than `usage` or `byok_usage`, because those ledgers may not both count against the key limit.
|
|
110
110
|
|
|
111
|
-
It
|
|
111
|
+
It refreshes after session start, model selection, each completed assistant response, each tool completion, and settled agent runs. Refreshes are non-blocking, with a 30-second floor and a 15-second request timeout; events within that floor coalesce into one trailing refresh, including events received during an in-flight request. Pending refreshes are cancelled on session reset, shutdown, or switching away from OpenRouter, and recheck whether the segment is enabled before fetching. No API key or response body is written to disk, logged, added to the Pi session, or sent anywhere other than `openrouter.ai`. A failed refresh keeps the most recent exact snapshot and marks it `(stale)`; if no request has ever succeeded, the segment stays hidden. Disabling the segment prevents future requests and discards any result from a request already in flight.
|
|
112
112
|
|
|
113
113
|
## Context Bar
|
|
114
114
|
|
|
@@ -124,14 +124,31 @@ export default function footer(pi: ExtensionAPI) {
|
|
|
124
124
|
let codexUsageLastAttempt = 0;
|
|
125
125
|
let codexUsageGeneration = 0;
|
|
126
126
|
let codexUsageInFlight: Promise<void> | null = null;
|
|
127
|
+
let codexUsageTimer: ReturnType<typeof setTimeout> | null = null;
|
|
128
|
+
let codexUsagePendingCtx: ExtensionContext | null = null;
|
|
129
|
+
|
|
130
|
+
const cancelCodexUsageRefresh = () => {
|
|
131
|
+
if (codexUsageTimer !== null) clearTimeout(codexUsageTimer);
|
|
132
|
+
codexUsageTimer = null;
|
|
133
|
+
codexUsagePendingCtx = null;
|
|
134
|
+
};
|
|
127
135
|
let openRouterUsage: OpenRouterUsageSnapshot | null = null;
|
|
128
136
|
let openRouterUsageLastAttempt = 0;
|
|
129
137
|
let openRouterUsageGeneration = 0;
|
|
130
138
|
let openRouterUsageInFlight: Promise<void> | null = null;
|
|
139
|
+
let openRouterUsageTimer: ReturnType<typeof setTimeout> | null = null;
|
|
140
|
+
let openRouterUsagePendingCtx: ExtensionContext | null = null;
|
|
141
|
+
|
|
142
|
+
const cancelOpenRouterUsageRefresh = () => {
|
|
143
|
+
if (openRouterUsageTimer !== null) clearTimeout(openRouterUsageTimer);
|
|
144
|
+
openRouterUsageTimer = null;
|
|
145
|
+
openRouterUsagePendingCtx = null;
|
|
146
|
+
};
|
|
131
147
|
|
|
132
148
|
const refreshOpenRouterUsage = (ctx: ExtensionContext, force = false): Promise<void> => {
|
|
133
149
|
const isOpenRouter = ctx.model?.provider === "openrouter";
|
|
134
150
|
if (!isOpenRouter) {
|
|
151
|
+
cancelOpenRouterUsageRefresh();
|
|
135
152
|
openRouterUsageGeneration++;
|
|
136
153
|
openRouterUsageInFlight = null;
|
|
137
154
|
openRouterUsage = null;
|
|
@@ -142,6 +159,7 @@ export default function footer(pi: ExtensionAPI) {
|
|
|
142
159
|
const config = getEffectiveConfig();
|
|
143
160
|
const enabled = LAYOUT_ROWS.some((row) => config[row]?.includes("openrouter_usage"));
|
|
144
161
|
if (!enabled) {
|
|
162
|
+
cancelOpenRouterUsageRefresh();
|
|
145
163
|
openRouterUsageGeneration++;
|
|
146
164
|
openRouterUsageInFlight = null;
|
|
147
165
|
openRouterUsage = null;
|
|
@@ -149,10 +167,23 @@ export default function footer(pi: ExtensionAPI) {
|
|
|
149
167
|
tuiRef?.requestRender();
|
|
150
168
|
return Promise.resolve();
|
|
151
169
|
}
|
|
152
|
-
if (openRouterUsageInFlight)
|
|
170
|
+
if (openRouterUsageInFlight) {
|
|
171
|
+
openRouterUsagePendingCtx = ctx;
|
|
172
|
+
return openRouterUsageInFlight;
|
|
173
|
+
}
|
|
153
174
|
|
|
154
175
|
const now = Date.now();
|
|
155
|
-
|
|
176
|
+
const remaining = CODEX_USAGE_REFRESH_MS - (now - openRouterUsageLastAttempt);
|
|
177
|
+
if (!force && remaining > 0) {
|
|
178
|
+
if (openRouterUsageTimer === null) {
|
|
179
|
+
openRouterUsageTimer = setTimeout(() => {
|
|
180
|
+
openRouterUsageTimer = null;
|
|
181
|
+
void refreshOpenRouterUsage(ctx);
|
|
182
|
+
}, remaining);
|
|
183
|
+
}
|
|
184
|
+
return Promise.resolve();
|
|
185
|
+
}
|
|
186
|
+
cancelOpenRouterUsageRefresh();
|
|
156
187
|
openRouterUsageLastAttempt = now;
|
|
157
188
|
const generation = ++openRouterUsageGeneration;
|
|
158
189
|
|
|
@@ -165,6 +196,10 @@ export default function footer(pi: ExtensionAPI) {
|
|
|
165
196
|
})
|
|
166
197
|
.finally(() => {
|
|
167
198
|
if (openRouterUsageInFlight === request) openRouterUsageInFlight = null;
|
|
199
|
+
if (generation !== openRouterUsageGeneration) return;
|
|
200
|
+
const pendingCtx = openRouterUsagePendingCtx;
|
|
201
|
+
openRouterUsagePendingCtx = null;
|
|
202
|
+
if (pendingCtx) void refreshOpenRouterUsage(pendingCtx);
|
|
168
203
|
});
|
|
169
204
|
openRouterUsageInFlight = request;
|
|
170
205
|
return request;
|
|
@@ -174,6 +209,7 @@ export default function footer(pi: ExtensionAPI) {
|
|
|
174
209
|
const model = ctx.model;
|
|
175
210
|
const isCodexSubscription = model?.provider === "openai-codex" && ctx.modelRegistry.isUsingOAuth(model);
|
|
176
211
|
if (!isCodexSubscription) {
|
|
212
|
+
cancelCodexUsageRefresh();
|
|
177
213
|
codexUsageGeneration++;
|
|
178
214
|
codexUsageInFlight = null;
|
|
179
215
|
codexUsage = null;
|
|
@@ -184,6 +220,7 @@ export default function footer(pi: ExtensionAPI) {
|
|
|
184
220
|
const config = getEffectiveConfig();
|
|
185
221
|
const enabled = LAYOUT_ROWS.some((row) => config[row]?.includes("codex_usage"));
|
|
186
222
|
if (!enabled) {
|
|
223
|
+
cancelCodexUsageRefresh();
|
|
187
224
|
codexUsageGeneration++;
|
|
188
225
|
codexUsageInFlight = null;
|
|
189
226
|
codexUsage = null;
|
|
@@ -191,10 +228,23 @@ export default function footer(pi: ExtensionAPI) {
|
|
|
191
228
|
tuiRef?.requestRender();
|
|
192
229
|
return Promise.resolve();
|
|
193
230
|
}
|
|
194
|
-
if (codexUsageInFlight)
|
|
231
|
+
if (codexUsageInFlight) {
|
|
232
|
+
codexUsagePendingCtx = ctx;
|
|
233
|
+
return codexUsageInFlight;
|
|
234
|
+
}
|
|
195
235
|
|
|
196
236
|
const now = Date.now();
|
|
197
|
-
|
|
237
|
+
const remaining = CODEX_USAGE_REFRESH_MS - (now - codexUsageLastAttempt);
|
|
238
|
+
if (!force && remaining > 0) {
|
|
239
|
+
if (codexUsageTimer === null) {
|
|
240
|
+
codexUsageTimer = setTimeout(() => {
|
|
241
|
+
codexUsageTimer = null;
|
|
242
|
+
void refreshCodexUsage(ctx);
|
|
243
|
+
}, remaining);
|
|
244
|
+
}
|
|
245
|
+
return Promise.resolve();
|
|
246
|
+
}
|
|
247
|
+
cancelCodexUsageRefresh();
|
|
198
248
|
codexUsageLastAttempt = now;
|
|
199
249
|
const generation = ++codexUsageGeneration;
|
|
200
250
|
|
|
@@ -207,6 +257,10 @@ export default function footer(pi: ExtensionAPI) {
|
|
|
207
257
|
})
|
|
208
258
|
.finally(() => {
|
|
209
259
|
if (codexUsageInFlight === request) codexUsageInFlight = null;
|
|
260
|
+
if (generation !== codexUsageGeneration) return;
|
|
261
|
+
const pendingCtx = codexUsagePendingCtx;
|
|
262
|
+
codexUsagePendingCtx = null;
|
|
263
|
+
if (pendingCtx) void refreshCodexUsage(pendingCtx);
|
|
210
264
|
});
|
|
211
265
|
codexUsageInFlight = request;
|
|
212
266
|
return request;
|
|
@@ -241,10 +295,12 @@ export default function footer(pi: ExtensionAPI) {
|
|
|
241
295
|
currentCtx = ctx;
|
|
242
296
|
lastBranchLength = 0;
|
|
243
297
|
cachedUsageStats = null;
|
|
298
|
+
cancelCodexUsageRefresh();
|
|
244
299
|
codexUsage = null;
|
|
245
300
|
codexUsageLastAttempt = 0;
|
|
246
301
|
codexUsageGeneration++;
|
|
247
302
|
codexUsageInFlight = null;
|
|
303
|
+
cancelOpenRouterUsageRefresh();
|
|
248
304
|
openRouterUsage = null;
|
|
249
305
|
openRouterUsageLastAttempt = 0;
|
|
250
306
|
openRouterUsageGeneration++;
|
|
@@ -267,6 +323,22 @@ export default function footer(pi: ExtensionAPI) {
|
|
|
267
323
|
}
|
|
268
324
|
});
|
|
269
325
|
|
|
326
|
+
pi.on("session_shutdown", async () => {
|
|
327
|
+
cancelCodexUsageRefresh();
|
|
328
|
+
codexUsageGeneration++;
|
|
329
|
+
codexUsageInFlight = null;
|
|
330
|
+
cancelOpenRouterUsageRefresh();
|
|
331
|
+
openRouterUsageGeneration++;
|
|
332
|
+
openRouterUsageInFlight = null;
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
pi.on("message_end", async (event, ctx) => {
|
|
336
|
+
if (event.message.role === "assistant") {
|
|
337
|
+
void refreshCodexUsage(ctx);
|
|
338
|
+
void refreshOpenRouterUsage(ctx);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
270
342
|
pi.on("model_select", async (_event: unknown, ctx: ExtensionContext) => {
|
|
271
343
|
void refreshCodexUsage(ctx, true);
|
|
272
344
|
void refreshOpenRouterUsage(ctx, true);
|
|
@@ -320,6 +392,8 @@ export default function footer(pi: ExtensionAPI) {
|
|
|
320
392
|
}
|
|
321
393
|
statusTracker.toolEnded(event.toolCallId);
|
|
322
394
|
tuiRef?.requestRender();
|
|
395
|
+
void refreshCodexUsage(_ctx);
|
|
396
|
+
void refreshOpenRouterUsage(_ctx);
|
|
323
397
|
});
|
|
324
398
|
|
|
325
399
|
pi.on("message_update", async (_event: unknown, _ctx: ExtensionContext) => {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openlines/opl-pi-sht",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"description": "opl-pi-sht (shouldHaveThat) Pi coding-agent extension kit: token-efficient context, tool and session safety, fingerprinted /init, model benchmarking, web access, and browser automation.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"extensions/",
|
|
11
|
-
"configs/"
|
|
11
|
+
"configs/",
|
|
12
|
+
"images/"
|
|
12
13
|
],
|
|
13
14
|
"keywords": [
|
|
14
15
|
"pi",
|