@bacnh85/pi-sub 0.1.4 → 0.1.6
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/index.ts +39 -31
- package/package.json +2 -1
package/index.ts
CHANGED
|
@@ -80,8 +80,10 @@ interface SubscriptionProviderAdapter {
|
|
|
80
80
|
interface State {
|
|
81
81
|
model?: ModelLike;
|
|
82
82
|
adapter?: SubscriptionProviderAdapter;
|
|
83
|
+
adapterId?: string;
|
|
83
84
|
snapshot?: SubscriptionUsageSnapshot;
|
|
84
85
|
lastRefreshAt: number;
|
|
86
|
+
refreshGeneration: number;
|
|
85
87
|
inFlight?: Promise<SubscriptionUsageSnapshot>;
|
|
86
88
|
refreshTimer?: NodeJS.Timeout;
|
|
87
89
|
debounceTimer?: NodeJS.Timeout;
|
|
@@ -132,6 +134,14 @@ function accountFromPiAuth(entry: PiAuthEntry): SubscriptionAccountSnapshot {
|
|
|
132
134
|
};
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
function getCodexAccountId(entry: PiAuthEntry | undefined): string | undefined {
|
|
138
|
+
if (!entry) return undefined;
|
|
139
|
+
if (typeof entry.accountId === "string" && entry.accountId.length > 0) return entry.accountId;
|
|
140
|
+
const claims = decodeJwtPayload(entry.access);
|
|
141
|
+
const auth = claims?.["https://api.openai.com/auth"];
|
|
142
|
+
return typeof auth?.chatgpt_account_id === "string" ? auth.chatgpt_account_id : undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
135
145
|
function planLabel(plan: string | undefined): string | undefined {
|
|
136
146
|
if (!plan) return undefined;
|
|
137
147
|
const normalized = plan.toLowerCase().replace(/[_-]+/g, " ");
|
|
@@ -239,11 +249,12 @@ function parseOpcodeUsageResponse(body: unknown): UsageApiSnapshot | undefined {
|
|
|
239
249
|
};
|
|
240
250
|
}
|
|
241
251
|
|
|
242
|
-
async function readPiCodexAuth(): Promise<PiAuthEntry> {
|
|
252
|
+
async function readPiCodexAuth(): Promise<PiAuthEntry & { accountId: string }> {
|
|
243
253
|
const auth = await readJsonFile<PiAuthFile>(piAuthPath());
|
|
244
254
|
const entry = auth[CODEX_PROVIDER];
|
|
245
|
-
|
|
246
|
-
|
|
255
|
+
const accountId = getCodexAccountId(entry!);
|
|
256
|
+
if (!entry?.access || !accountId) throw new Error("Missing openai-codex OAuth entry in Pi auth");
|
|
257
|
+
return { ...entry, accountId };
|
|
247
258
|
}
|
|
248
259
|
|
|
249
260
|
async function readOpenCodeGoAuth(): Promise<{ key: string }> {
|
|
@@ -260,7 +271,7 @@ async function fetchUsageFromPiAuth(entry: PiAuthEntry, signal?: AbortSignal): P
|
|
|
260
271
|
headers: {
|
|
261
272
|
Accept: "application/json",
|
|
262
273
|
Authorization: `Bearer ${entry.access}`,
|
|
263
|
-
"ChatGPT-Account-Id": entry.accountId
|
|
274
|
+
"ChatGPT-Account-Id": getCodexAccountId(entry) ?? entry.accountId,
|
|
264
275
|
"User-Agent": "pi-sub/0.1.0",
|
|
265
276
|
},
|
|
266
277
|
signal: combinedSignal,
|
|
@@ -305,33 +316,13 @@ async function fetchCodexUsage(signal?: AbortSignal): Promise<SubscriptionUsageS
|
|
|
305
316
|
|
|
306
317
|
async function fetchOpenCodeGoUsage(signal?: AbortSignal): Promise<SubscriptionUsageSnapshot> {
|
|
307
318
|
try {
|
|
308
|
-
|
|
309
|
-
const timeoutSignal = AbortSignal.timeout(7_000);
|
|
310
|
-
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
311
|
-
const response = await fetch("https://opencode.ai/zen/go/v1/usage", {
|
|
312
|
-
headers: {
|
|
313
|
-
Accept: "application/json",
|
|
314
|
-
Authorization: `Bearer ${auth.key}`,
|
|
315
|
-
"User-Agent": "pi-sub/0.1.4",
|
|
316
|
-
},
|
|
317
|
-
signal: combinedSignal,
|
|
318
|
-
});
|
|
319
|
-
if (!response.ok) throw new Error(`usage request failed with HTTP ${response.status}`);
|
|
320
|
-
const usage = parseOpcodeUsageResponse(await response.json());
|
|
321
|
-
const account: SubscriptionAccountSnapshot = {
|
|
322
|
-
isActive: true,
|
|
323
|
-
accountLabel: "OpenCode Go",
|
|
324
|
-
plan: planLabel(usage?.plan_type),
|
|
325
|
-
fiveHour: usageWindowFromApi(usage?.primary),
|
|
326
|
-
weekly: usageWindowFromApi(usage?.secondary),
|
|
327
|
-
lastActivity: "Now",
|
|
328
|
-
};
|
|
319
|
+
await readOpenCodeGoAuth();
|
|
329
320
|
return {
|
|
330
321
|
providerId: OPC_PROVIDER,
|
|
331
322
|
providerDisplayName: "OpenCode Go",
|
|
332
|
-
accounts: [
|
|
333
|
-
activeAccount: account,
|
|
323
|
+
accounts: [],
|
|
334
324
|
fetchedAt: Date.now(),
|
|
325
|
+
error: "OpenCode Go usage tracking not yet implemented",
|
|
335
326
|
};
|
|
336
327
|
} catch (error) {
|
|
337
328
|
return {
|
|
@@ -427,9 +418,12 @@ function renderSubscriptionLine(ctx: ExtensionContext, state: State): void {
|
|
|
427
418
|
const account = snapshot.activeAccount;
|
|
428
419
|
const segments = windowSegments(account);
|
|
429
420
|
const cost = snapshot.cost;
|
|
421
|
+
const hasWindows = segments.length > 0;
|
|
430
422
|
if (cost !== undefined && cost > 0) segments.push(`$${cost.toFixed(2)}`);
|
|
431
423
|
if (segments.length === 0) {
|
|
432
424
|
line = `Sub ${state.adapter.displayName}`;
|
|
425
|
+
} else if (!hasWindows) {
|
|
426
|
+
line = `${state.adapter.displayName} ${segments.join(" ")}`;
|
|
433
427
|
} else {
|
|
434
428
|
line = segments.join(" ");
|
|
435
429
|
}
|
|
@@ -454,11 +448,21 @@ function stopTimer(state: State): void {
|
|
|
454
448
|
}
|
|
455
449
|
|
|
456
450
|
function updateActiveAdapter(ctx: ExtensionContext, state: State, model: ModelLike): void {
|
|
451
|
+
const nextAdapter = supportedAdapter(model);
|
|
452
|
+
const adapterChanged = state.adapterId !== nextAdapter?.id;
|
|
453
|
+
|
|
457
454
|
state.model = model;
|
|
458
|
-
state.adapter =
|
|
459
|
-
|
|
455
|
+
state.adapter = nextAdapter;
|
|
456
|
+
state.adapterId = nextAdapter?.id;
|
|
457
|
+
|
|
458
|
+
if (adapterChanged) {
|
|
460
459
|
state.snapshot = undefined;
|
|
461
460
|
state.lastRefreshAt = 0;
|
|
461
|
+
state.inFlight = undefined;
|
|
462
|
+
state.refreshGeneration++;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (!state.adapter) {
|
|
462
466
|
stopTimer(state);
|
|
463
467
|
}
|
|
464
468
|
renderSubscriptionLine(ctx, state);
|
|
@@ -473,15 +477,19 @@ async function refreshUsage(ctx: ExtensionContext, state: State, force: boolean)
|
|
|
473
477
|
}
|
|
474
478
|
if (!force && state.snapshot && Date.now() - state.lastRefreshAt < REFRESH_TTL_MS) return state.snapshot;
|
|
475
479
|
if (state.inFlight) return state.inFlight;
|
|
480
|
+
const generation = state.refreshGeneration;
|
|
476
481
|
renderSubscriptionLine(ctx, state);
|
|
477
482
|
state.inFlight = adapter.fetchUsage(ctx.signal).then((snapshot) => {
|
|
483
|
+
if (state.refreshGeneration !== generation) return snapshot;
|
|
478
484
|
snapshot.cost = aggregateSessionCost(ctx);
|
|
479
485
|
state.snapshot = snapshot;
|
|
480
486
|
state.lastRefreshAt = Date.now();
|
|
481
487
|
renderSubscriptionLine(ctx, state);
|
|
482
488
|
return snapshot;
|
|
483
489
|
}).finally(() => {
|
|
484
|
-
state.
|
|
490
|
+
if (state.refreshGeneration === generation) {
|
|
491
|
+
state.inFlight = undefined;
|
|
492
|
+
}
|
|
485
493
|
});
|
|
486
494
|
return state.inFlight;
|
|
487
495
|
}
|
|
@@ -541,7 +549,7 @@ function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: St
|
|
|
541
549
|
}
|
|
542
550
|
|
|
543
551
|
export default function (pi: ExtensionAPI) {
|
|
544
|
-
const state: State = { lastRefreshAt: 0 };
|
|
552
|
+
const state: State = { lastRefreshAt: 0, refreshGeneration: 0 };
|
|
545
553
|
|
|
546
554
|
pi.registerMessageRenderer(MESSAGE_TYPE, (message, _options, theme) => {
|
|
547
555
|
const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bacnh85/pi-sub",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Pi extension showing subscription usage for supported model providers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"./index.ts"
|
|
34
34
|
]
|
|
35
35
|
},
|
|
36
|
+
"engines": { "node": ">=20.3.0" },
|
|
36
37
|
"peerDependencies": {
|
|
37
38
|
"@earendil-works/pi-coding-agent": "*",
|
|
38
39
|
"@earendil-works/pi-tui": "*"
|