@bacnh85/pi-sub 0.1.12 → 0.1.13
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/extensions/index.ts +31 -35
- package/package.json +1 -1
package/extensions/index.ts
CHANGED
|
@@ -66,7 +66,6 @@ interface SubscriptionUsageSnapshot {
|
|
|
66
66
|
activeAccount?: SubscriptionAccountSnapshot;
|
|
67
67
|
fetchedAt: number;
|
|
68
68
|
error?: string;
|
|
69
|
-
cost?: number;
|
|
70
69
|
}
|
|
71
70
|
|
|
72
71
|
type SubscriptionProviderAdapter = {
|
|
@@ -89,6 +88,7 @@ interface State {
|
|
|
89
88
|
lastTokPerSec?: number;
|
|
90
89
|
cumulativeOutput: number;
|
|
91
90
|
cumulativeDurationMs: number;
|
|
91
|
+
cumulativeCost: number;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
function isCodexModel(model: ModelLike): boolean {
|
|
@@ -213,9 +213,9 @@ function formatRemainingTime(resetAtSec: number | undefined): string | undefined
|
|
|
213
213
|
if (remainingSec <= 0) return "0M";
|
|
214
214
|
const remainingMin = Math.ceil(remainingSec / 60);
|
|
215
215
|
if (remainingMin < 60) return `${remainingMin}M`;
|
|
216
|
-
const remainingH = Math.ceil(
|
|
216
|
+
const remainingH = Math.ceil(remainingSec / 3600);
|
|
217
217
|
if (remainingH < 24) return `${remainingH}H`;
|
|
218
|
-
const remainingD = Math.ceil(
|
|
218
|
+
const remainingD = Math.ceil(remainingSec / 86400);
|
|
219
219
|
return `${remainingD}D`;
|
|
220
220
|
}
|
|
221
221
|
|
|
@@ -276,7 +276,7 @@ function parseUsageResponse(body: unknown): UsageApiSnapshot | undefined {
|
|
|
276
276
|
async function readPiCodexAuth(): Promise<PiAuthEntry & { accountId: string }> {
|
|
277
277
|
const auth = await readJsonFile<PiAuthFile>(piAuthPath());
|
|
278
278
|
const entry = auth[CODEX_PROVIDER];
|
|
279
|
-
const accountId = getCodexAccountId(entry
|
|
279
|
+
const accountId = getCodexAccountId(entry);
|
|
280
280
|
if (!entry?.access || !accountId) throw new Error("Missing openai-codex OAuth entry in Pi auth");
|
|
281
281
|
return { ...entry, accountId };
|
|
282
282
|
}
|
|
@@ -388,7 +388,7 @@ interface ZaiUsageApiResponse {
|
|
|
388
388
|
interface ZaiUsageApiError {
|
|
389
389
|
code: number;
|
|
390
390
|
msg: string;
|
|
391
|
-
success
|
|
391
|
+
success?: boolean;
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
function zaiLimitToUsageWindow(limit: ZaiLimitEntry): UsageWindow | undefined {
|
|
@@ -430,9 +430,11 @@ async function fetchZaiUsage(signal?: AbortSignal): Promise<SubscriptionUsageSna
|
|
|
430
430
|
const body = await response.json();
|
|
431
431
|
|
|
432
432
|
// Z.ai returns HTTP 200 even on auth errors: {"code":401,"msg":"...","success":false}
|
|
433
|
+
// Also handle missing success field, empty msg, or presence of code.
|
|
433
434
|
const apiError = body as ZaiUsageApiError;
|
|
434
|
-
if (typeof apiError.success === "boolean" && !apiError.success && apiError.msg) {
|
|
435
|
-
|
|
435
|
+
if (apiError.code >= 400 || (typeof apiError.success === "boolean" && !apiError.success) || (apiError.msg && apiError.msg.length > 0 && apiError.success === undefined)) {
|
|
436
|
+
const message = apiError.msg || `HTTP status ${apiError.code}`;
|
|
437
|
+
throw new Error(`Z.ai API error: ${message}`);
|
|
436
438
|
}
|
|
437
439
|
|
|
438
440
|
const parsed = body as ZaiUsageApiResponse;
|
|
@@ -481,9 +483,8 @@ function supportedAdapter(model: ModelLike): SubscriptionProviderAdapter | undef
|
|
|
481
483
|
|
|
482
484
|
function formatRemaining(window: UsageWindow | undefined): string {
|
|
483
485
|
if (!window) return "?";
|
|
484
|
-
if (window.
|
|
486
|
+
if (window.remainingLabel) return `${window.remaining}%/${window.remainingLabel}`;
|
|
485
487
|
if (window.remaining !== undefined) return `${window.remaining}%`;
|
|
486
|
-
if (window.percent !== undefined && window.remainingLabel) return `${Math.max(0, 100 - window.percent)}%/${window.remainingLabel}`;
|
|
487
488
|
return "?";
|
|
488
489
|
}
|
|
489
490
|
|
|
@@ -503,16 +504,6 @@ function windowSegments(account: SubscriptionAccountSnapshot | undefined): strin
|
|
|
503
504
|
return segments;
|
|
504
505
|
}
|
|
505
506
|
|
|
506
|
-
function aggregateSessionCost(ctx: ExtensionContext): number {
|
|
507
|
-
let total = 0;
|
|
508
|
-
for (const entry of ctx.sessionManager.getBranch()) {
|
|
509
|
-
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
510
|
-
total += (entry.message.usage as any)?.cost?.total ?? 0;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
return total;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
507
|
function renderSubscriptionLine(ctx: ExtensionContext, state: State): void {
|
|
517
508
|
if (!state.adapter) {
|
|
518
509
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
@@ -532,9 +523,9 @@ function renderSubscriptionLine(ctx: ExtensionContext, state: State): void {
|
|
|
532
523
|
const windowParts = windowSegments(account);
|
|
533
524
|
const accountPart = formatFooterAccount(account);
|
|
534
525
|
const segments = accountPart ? [accountPart, ...windowParts] : [...windowParts];
|
|
535
|
-
const cost =
|
|
526
|
+
const cost = state.cumulativeCost;
|
|
536
527
|
const hasWindows = windowParts.length > 0;
|
|
537
|
-
if (cost
|
|
528
|
+
if (cost > 0) segments.push(`$${cost.toFixed(2)}`);
|
|
538
529
|
if (state.lastTokPerSec !== undefined) segments.push(`${state.lastTokPerSec} tok/s`);
|
|
539
530
|
if (segments.length === 0) {
|
|
540
531
|
line = `Sub ${state.adapter.displayName}`;
|
|
@@ -597,7 +588,6 @@ async function refreshUsage(ctx: ExtensionContext, state: State, force: boolean)
|
|
|
597
588
|
renderSubscriptionLine(ctx, state);
|
|
598
589
|
state.inFlight = adapter.fetchUsage(ctx.signal).then((snapshot) => {
|
|
599
590
|
if (state.refreshGeneration !== generation) return snapshot;
|
|
600
|
-
snapshot.cost = aggregateSessionCost(ctx);
|
|
601
591
|
state.snapshot = snapshot;
|
|
602
592
|
state.lastRefreshAt = Date.now();
|
|
603
593
|
renderSubscriptionLine(ctx, state);
|
|
@@ -628,7 +618,7 @@ function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: St
|
|
|
628
618
|
if (!snapshot) return "Subscription usage has not been loaded yet.";
|
|
629
619
|
if (snapshot.error) return `${snapshot.providerDisplayName}: ${snapshot.error}`;
|
|
630
620
|
if (snapshot.accounts.length === 0) {
|
|
631
|
-
const costLine =
|
|
621
|
+
const costLine = state.cumulativeCost > 0 ? `\nSession cost: $${state.cumulativeCost.toFixed(2)}` : "";
|
|
632
622
|
const modelInfo = state.model?.id ? ` · Model: ${state.model.id}` : "";
|
|
633
623
|
return `Provider: ${snapshot.providerDisplayName}${modelInfo} · Fetched: ${new Date(snapshot.fetchedAt).toLocaleTimeString()}\n${snapshot.providerDisplayName} does not expose usage windows.${costLine}`;
|
|
634
624
|
}
|
|
@@ -660,7 +650,7 @@ function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: St
|
|
|
660
650
|
return `${row.active} ${cols.join(" ")} ${row.snapshot.lastActivity ?? ""}`;
|
|
661
651
|
});
|
|
662
652
|
|
|
663
|
-
const costLine =
|
|
653
|
+
const costLine = state.cumulativeCost > 0 ? `\nSession cost: $${state.cumulativeCost.toFixed(2)}` : "";
|
|
664
654
|
const tokPerSecLine = state.lastTokPerSec !== undefined
|
|
665
655
|
? `\nLast response: ${state.lastTokPerSec} tok/s` +
|
|
666
656
|
(state.cumulativeDurationMs > 0
|
|
@@ -675,7 +665,7 @@ function buildDetails(snapshot: SubscriptionUsageSnapshot | undefined, state: St
|
|
|
675
665
|
}
|
|
676
666
|
|
|
677
667
|
export default function (pi: ExtensionAPI) {
|
|
678
|
-
const state: State = { lastRefreshAt: 0, refreshGeneration: 0, cumulativeOutput: 0, cumulativeDurationMs: 0 };
|
|
668
|
+
const state: State = { lastRefreshAt: 0, refreshGeneration: 0, cumulativeOutput: 0, cumulativeDurationMs: 0, cumulativeCost: 0 };
|
|
679
669
|
|
|
680
670
|
pi.on("session_start", async (_event, ctx) => {
|
|
681
671
|
updateActiveAdapter(ctx, state, ctx.model);
|
|
@@ -692,20 +682,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
692
682
|
});
|
|
693
683
|
|
|
694
684
|
pi.on("message_end", async (event, ctx) => {
|
|
695
|
-
if (event.message.role === "assistant"
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
state.
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
685
|
+
if (event.message.role === "assistant") {
|
|
686
|
+
state.cumulativeCost += (event.message.usage as any)?.cost?.total ?? 0;
|
|
687
|
+
if (state.responseStartTime) {
|
|
688
|
+
const output = (event.message.usage as any)?.output ?? 0;
|
|
689
|
+
const elapsed = Date.now() - state.responseStartTime;
|
|
690
|
+
state.responseStartTime = undefined;
|
|
691
|
+
if (elapsed > 0 && output > 0) {
|
|
692
|
+
state.lastTokPerSec = Math.round(output / (elapsed / 1000));
|
|
693
|
+
state.cumulativeOutput += output;
|
|
694
|
+
state.cumulativeDurationMs += elapsed;
|
|
695
|
+
}
|
|
704
696
|
}
|
|
697
|
+
if (state.adapter) renderSubscriptionLine(ctx, state);
|
|
705
698
|
}
|
|
706
699
|
});
|
|
707
700
|
|
|
708
|
-
pi.on("after_provider_response", async (
|
|
701
|
+
pi.on("after_provider_response", async (event, ctx) => {
|
|
702
|
+
if (event.status >= 400) {
|
|
703
|
+
state.responseStartTime = undefined;
|
|
704
|
+
}
|
|
709
705
|
if (state.adapter) scheduleRefresh(ctx, state);
|
|
710
706
|
});
|
|
711
707
|
|