@narumitw/pi-usage 0.54.0 → 0.57.0

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@narumitw/pi-usage",
3
- "version": "0.54.0",
4
- "description": "Pi extension that shows current-account usage for Codex, Kimi For Coding, GitHub Copilot, OpenRouter, OpenCode Zen, Z.AI, and xAI OAuth subscriptions.",
3
+ "version": "0.57.0",
4
+ "description": "Pi extension that shows current-account usage and DeepSeek API balance for supported providers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "private": false,
@@ -11,6 +11,8 @@
11
11
  "pi",
12
12
  "usage",
13
13
  "quota",
14
+ "balance",
15
+ "deepseek",
14
16
  "codex",
15
17
  "kimi",
16
18
  "kimi-coding",
@@ -34,9 +36,6 @@
34
36
  "./dist/index.ts"
35
37
  ]
36
38
  },
37
- "piExtension": {
38
- "lifecycle": "stable"
39
- },
40
39
  "scripts": {
41
40
  "build": "node scripts/build-runtime.mjs",
42
41
  "check": "npm run build && biome check --vcs-use-ignore-file=false src test scripts package.json tsconfig.json README.md && npm run typecheck",
@@ -50,11 +49,11 @@
50
49
  "@earendil-works/pi-tui": "*"
51
50
  },
52
51
  "devDependencies": {
53
- "@biomejs/biome": "2.5.10",
54
- "@earendil-works/pi-ai": "0.84.3",
55
- "@earendil-works/pi-coding-agent": "0.84.3",
56
- "@earendil-works/pi-tui": "0.84.3",
57
- "@types/node": "26.2.0",
52
+ "@biomejs/biome": "2.5.11",
53
+ "@earendil-works/pi-ai": "0.84.4",
54
+ "@earendil-works/pi-coding-agent": "0.84.4",
55
+ "@earendil-works/pi-tui": "0.84.4",
56
+ "@types/node": "26.4.0",
58
57
  "esbuild": "0.28.2",
59
58
  "typescript": "7.0.2"
60
59
  },
package/src/format.ts CHANGED
@@ -11,11 +11,14 @@ const VALUE_COLUMN = 29;
11
11
 
12
12
  export function formatUsageReport(report: UsageReport, displayState: UsageDisplayState): string {
13
13
  const stateLabel = displayState === "current" ? "Current" : "Configured";
14
- const lines = [`${report.providerName} Usage · ${stateLabel}`];
14
+ const title =
15
+ report.providerId === "deepseek" ? "DeepSeek API Balance" : `${report.providerName} Usage`;
16
+ const lines = [`${title} · ${stateLabel}`];
15
17
  if (report.accountLabel) lines.push(`Account: ${report.accountLabel}`);
16
18
  lines.push(`Semantics: ${report.semantics.label}`, "");
17
19
 
18
20
  if (report.providerId === "openai-codex") formatCodexReport(lines, report);
21
+ else if (report.providerId === "deepseek") formatDeepSeekReport(lines, report);
19
22
  else if (report.providerId === "github-copilot") formatGitHubCopilotReport(lines, report);
20
23
  else if (report.providerId === "openrouter") formatOpenRouterReport(lines, report);
21
24
  else if (report.providerId === "opencode-go") formatOpenCodeZenReport(lines, report);
@@ -31,8 +34,16 @@ export function formatUsageReport(report: UsageReport, displayState: UsageDispla
31
34
  return lines.join("\n").trimEnd();
32
35
  }
33
36
 
34
- export function formatUsageStatusline(report: UsageReport, model?: UsageModel): string | undefined {
35
- if (report.providerId === "openai-codex") return formatCodexStatusline(report, model);
37
+ export function formatUsageStatusline(
38
+ report: UsageReport,
39
+ model?: UsageModel,
40
+ now = Date.now(),
41
+ showCodexResetCountdown = true,
42
+ ): string | undefined {
43
+ if (report.providerId === "openai-codex") {
44
+ return formatCodexStatusline(report, model, now, showCodexResetCountdown);
45
+ }
46
+ if (report.providerId === "deepseek") return formatDeepSeekStatusline(report);
36
47
  if (report.providerId === "github-copilot") return formatGitHubCopilotStatusline(report);
37
48
  if (report.providerId === "openrouter") {
38
49
  const limit = report.buckets.find((bucket) => bucket.id === "key-limit");
@@ -42,6 +53,9 @@ export function formatUsageStatusline(report: UsageReport, model?: UsageModel):
42
53
  }
43
54
  if (report.providerId === "opencode-go") return formatOpenCodeZenStatusline(report);
44
55
  if (report.providerId === "kimi-coding") return formatKimiCodingStatusline(report);
56
+ if (report.providerId === "zai" || report.providerId === "zai-coding-cn") {
57
+ return formatZaiStatusline(report);
58
+ }
45
59
  return undefined;
46
60
  }
47
61
 
@@ -84,6 +98,33 @@ function formatCodexReport(lines: string[], report: UsageReport): void {
84
98
  }
85
99
  }
86
100
 
101
+ function formatDeepSeekReport(lines: string[], report: UsageReport): void {
102
+ const availability = report.metrics.find((metric) => metric.id === "api-availability");
103
+ lines.push(
104
+ `${"API calls:".padEnd(VALUE_COLUMN)}${availability?.value === "available" ? "Available" : "Unavailable"}`,
105
+ );
106
+ for (const currency of ["CNY", "USD"]) {
107
+ const metrics = report.metrics.filter((metric) => metric.currency === currency);
108
+ if (metrics.length === 0) continue;
109
+ lines.push("", `${currency} balance:`);
110
+ for (const metric of metrics) {
111
+ lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${currency} ${metric.value}`);
112
+ }
113
+ }
114
+ }
115
+
116
+ function formatDeepSeekStatusline(report: UsageReport): string {
117
+ const availability = report.metrics.find((metric) => metric.id === "api-availability");
118
+ if (availability?.value !== "available") return "deepseek API unavailable";
119
+ const totals = ["CNY", "USD"].flatMap((currency) => {
120
+ const metric = report.metrics.find(
121
+ (candidate) => candidate.id === `${currency.toLowerCase()}-total`,
122
+ );
123
+ return metric ? [`${currency} ${metric.value}`] : [];
124
+ });
125
+ return totals.length > 0 ? `deepseek ${totals.join(" · ")}` : "deepseek balance unavailable";
126
+ }
127
+
87
128
  function formatGitHubCopilotReport(lines: string[], report: UsageReport): void {
88
129
  const quota = findGitHubCopilotQuota(report);
89
130
  if (!quota || quota.limit === undefined || quota.remaining === undefined) {
@@ -213,6 +254,22 @@ function formatKimiCodingStatusline(report: UsageReport): string | undefined {
213
254
  return parts.length > 1 ? parts.join(" ") : undefined;
214
255
  }
215
256
 
257
+ function formatZaiStatusline(report: UsageReport): string | undefined {
258
+ const selected = [
259
+ report.buckets.find((bucket) => bucket.id === "five-hour"),
260
+ report.buckets.find((bucket) => bucket.id === "weekly"),
261
+ ];
262
+ const parts = ["zai"];
263
+ for (const bucket of selected) {
264
+ if (!bucket?.limit || bucket.remaining === undefined) continue;
265
+ const fallback = bucket.id === "weekly" ? "weekly" : "5h";
266
+ parts.push(
267
+ `${percentRemaining(bucket)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`,
268
+ );
269
+ }
270
+ return parts.length > 1 ? parts.join(" ") : undefined;
271
+ }
272
+
216
273
  function formatCurrencyMetric(metric: UsageReport["metrics"][number]): string {
217
274
  if (typeof metric.value !== "number") return String(metric.value);
218
275
  if (!metric.currency) return "unavailable";
@@ -289,7 +346,12 @@ function formatGenericReport(lines: string[], report: UsageReport): void {
289
346
  }
290
347
  }
291
348
 
292
- function formatCodexStatusline(report: UsageReport, model?: UsageModel): string | undefined {
349
+ function formatCodexStatusline(
350
+ report: UsageReport,
351
+ model?: UsageModel,
352
+ now = Date.now(),
353
+ showResetCountdown = true,
354
+ ): string | undefined {
293
355
  const group = selectCodexGroup(report, model);
294
356
  if (!group) return formatCodexCreditsStatus(report);
295
357
  const buckets = report.buckets.filter((bucket) => (bucket.groupId ?? bucket.id) === group);
@@ -299,10 +361,15 @@ function formatCodexStatusline(report: UsageReport, model?: UsageModel): string
299
361
  ];
300
362
  for (const bucket of buckets) {
301
363
  if (bucket.remaining === undefined) continue;
364
+ const percent = `${clampPercent(bucket.remaining).toFixed(0)}%`;
302
365
  const fallback = bucket.id.endsWith(":secondary") ? "weekly" : "5h";
303
- parts.push(
304
- `${clampPercent(bucket.remaining).toFixed(0)}% ${formatWindowLabel(bucket.windowMinutes, fallback, true)}`,
305
- );
366
+ const window = formatWindowLabel(bucket.windowMinutes, fallback, true);
367
+ if (!showResetCountdown) {
368
+ parts.push(`${percent} ${window}`);
369
+ continue;
370
+ }
371
+ const reset = formatResetCountdown(bucket.resetsAt, now);
372
+ parts.push(`${percent} ${reset ? `↻ ${reset}` : window}`);
306
373
  }
307
374
  return parts.length > 1 ? parts.join(" ") : formatCodexCreditsStatus(report);
308
375
  }
@@ -405,6 +472,26 @@ function formatWindowLabel(
405
472
  return `${minutes}m`;
406
473
  }
407
474
 
475
+ function formatResetCountdown(resetsAt: number | undefined, now: number): string | undefined {
476
+ if (resetsAt === undefined || !Number.isFinite(resetsAt) || !Number.isFinite(now))
477
+ return undefined;
478
+ const totalMinutes = Math.max(0, Math.ceil((resetsAt * 1_000 - now) / 60_000));
479
+ const days = Math.floor(totalMinutes / 1_440);
480
+ const hours = Math.floor((totalMinutes % 1_440) / 60);
481
+ const minutes = totalMinutes % 60;
482
+ if (days > 0) {
483
+ return [
484
+ `${String(days)}d`,
485
+ hours > 0 ? `${String(hours)}h` : minutes > 0 ? `${String(minutes)}m` : "",
486
+ ]
487
+ .filter(Boolean)
488
+ .join("");
489
+ }
490
+ if (hours > 0)
491
+ return [`${String(hours)}h`, minutes > 0 ? `${String(minutes)}m` : ""].filter(Boolean).join("");
492
+ return `${String(minutes)}m`;
493
+ }
494
+
408
495
  function formatMetricValue(value: number | string, unit: UsageBucket["unit"] | undefined): string {
409
496
  if (unit === "usd" && typeof value === "number") return formatUsd(value);
410
497
  return String(value);
package/src/index.ts CHANGED
@@ -33,6 +33,7 @@ export {
33
33
  } from "./core.js";
34
34
  export { formatProviderStates, formatUsageReport, formatUsageStatusline } from "./format.js";
35
35
  export { normalizeCodexBackendPayload } from "./providers/codex.js";
36
+ export { normalizeDeepSeekBalancePayload } from "./providers/deepseek.js";
36
37
  export { normalizeGitHubCopilotUsagePayload } from "./providers/github-copilot.js";
37
38
  export { normalizeKimiCodingUsagePayload } from "./providers/kimi-coding.js";
38
39
  export { normalizeOpenCodeZenPayload } from "./providers/opencode-zen.js";
@@ -62,6 +63,7 @@ export {
62
63
  usageSettingsPath,
63
64
  } from "./settings.js";
64
65
  export type {
66
+ DeepSeekBalancePayload,
65
67
  KimiCodingUsagePayload,
66
68
  ProviderUsageState,
67
69
  ResolvedUsageAuth,
@@ -0,0 +1,85 @@
1
+ import type { DeepSeekBalancePayload, UsageMetric, UsageReport } from "../types.js";
2
+
3
+ const CURRENCIES = ["CNY", "USD"] as const;
4
+ type DeepSeekCurrency = (typeof CURRENCIES)[number];
5
+
6
+ const BALANCE_FIELDS = [
7
+ ["total", "Total balance", "total_balance"],
8
+ ["granted", "Granted balance", "granted_balance"],
9
+ ["topped-up", "Topped-up balance", "topped_up_balance"],
10
+ ] as const;
11
+
12
+ export function normalizeDeepSeekBalancePayload(
13
+ payload: DeepSeekBalancePayload,
14
+ capturedAt: number,
15
+ ): UsageReport {
16
+ if (typeof payload.is_available !== "boolean") {
17
+ throw new Error("DeepSeek API balance response availability was not a boolean.");
18
+ }
19
+ if (!Array.isArray(payload.balance_infos) || payload.balance_infos.length === 0) {
20
+ throw new Error("DeepSeek API balance response returned no balance information.");
21
+ }
22
+
23
+ const balances = new Map<DeepSeekCurrency, Record<string, unknown>>();
24
+ for (const raw of payload.balance_infos) {
25
+ const balance = asObject(raw);
26
+ if (!balance) throw new Error("DeepSeek API balance row was not an object.");
27
+ const currency = deepSeekCurrency(balance.currency);
28
+ if (!currency) throw new Error("DeepSeek API balance row returned an unsupported currency.");
29
+ if (balances.has(currency)) {
30
+ throw new Error(`DeepSeek API balance response repeated ${currency}.`);
31
+ }
32
+ for (const [, label, field] of BALANCE_FIELDS) {
33
+ if (!decimalAmount(balance[field])) {
34
+ throw new Error(`DeepSeek API balance ${label.toLowerCase()} was not a valid amount.`);
35
+ }
36
+ }
37
+ balances.set(currency, balance);
38
+ }
39
+
40
+ const metrics: UsageMetric[] = [
41
+ {
42
+ id: "api-availability",
43
+ label: "API calls",
44
+ value: payload.is_available ? "available" : "unavailable",
45
+ },
46
+ ];
47
+ for (const currency of CURRENCIES) {
48
+ const balance = balances.get(currency);
49
+ if (!balance) continue;
50
+ for (const [id, label, field] of BALANCE_FIELDS) {
51
+ metrics.push({
52
+ id: `${currency.toLowerCase()}-${id}`,
53
+ label,
54
+ value: balance[field] as string,
55
+ unit: "currency",
56
+ currency,
57
+ });
58
+ }
59
+ }
60
+
61
+ return {
62
+ providerId: "deepseek",
63
+ providerName: "DeepSeek",
64
+ capturedAt,
65
+ source: "deepseek-balance",
66
+ semantics: { kind: "api-key", label: "DeepSeek API balance" },
67
+ buckets: [],
68
+ metrics,
69
+ };
70
+ }
71
+
72
+ function asObject(value: unknown): Record<string, unknown> | undefined {
73
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
74
+ return value as Record<string, unknown>;
75
+ }
76
+
77
+ function deepSeekCurrency(value: unknown): DeepSeekCurrency | undefined {
78
+ return CURRENCIES.find((currency) => currency === value);
79
+ }
80
+
81
+ function decimalAmount(value: unknown): value is string {
82
+ return (
83
+ typeof value === "string" && value.length <= 64 && /^(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(value)
84
+ );
85
+ }
package/src/query.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  type OAuthCredentialCandidateReader,
7
7
  } from "./oauth-credential-source.js";
8
8
  import { normalizeCodexBackendPayload } from "./providers/codex.js";
9
+ import { normalizeDeepSeekBalancePayload } from "./providers/deepseek.js";
9
10
  import { normalizeGitHubCopilotUsagePayload } from "./providers/github-copilot.js";
10
11
  import { normalizeKimiCodingUsagePayload } from "./providers/kimi-coding.js";
11
12
  import { normalizeOpenCodeZenPayload } from "./providers/opencode-zen.js";
@@ -14,6 +15,7 @@ import { normalizeXaiBillingPayload } from "./providers/xai.js";
14
15
  import { normalizeZaiQuotaPayload } from "./providers/zai.js";
15
16
  import type {
16
17
  CodexBackendPayload,
18
+ DeepSeekBalancePayload,
17
19
  GitHubCopilotUsagePayload,
18
20
  KimiCodingUsagePayload,
19
21
  OpenCodeZenPayload,
@@ -28,6 +30,7 @@ import type {
28
30
  } from "./types.js";
29
31
 
30
32
  const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
33
+ const DEEPSEEK_BALANCE_URL = "https://api.deepseek.com/user/balance";
31
34
  const GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
32
35
  const OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
33
36
  const OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
@@ -65,6 +68,27 @@ export const SUPPORTED_ADAPTERS: readonly UsageProviderAdapter[] = [
65
68
  return normalizeCodexBackendPayload(payload as CodexBackendPayload, Date.now());
66
69
  },
67
70
  },
71
+ {
72
+ id: "deepseek",
73
+ displayName: "DeepSeek",
74
+ semantics: { kind: "api-key", label: "DeepSeek API balance" },
75
+ async query(auth, signal, timeoutMs, guard) {
76
+ if (!guard) throw new Error("DeepSeek API balance requires request-boundary revalidation.");
77
+ const startedAt = Date.now();
78
+ await guard();
79
+ const remainingMs = timeoutMs - (Date.now() - startedAt);
80
+ if (remainingMs <= 0) throw new Error("Timed out while revalidating DeepSeek runtime auth.");
81
+ const payload = await fetchProviderJson(
82
+ DEEPSEEK_BALANCE_URL,
83
+ auth,
84
+ signal,
85
+ remainingMs,
86
+ "DeepSeek API balance endpoint",
87
+ { redirect: "error" },
88
+ );
89
+ return normalizeDeepSeekBalancePayload(payload as DeepSeekBalancePayload, Date.now());
90
+ },
91
+ },
68
92
  {
69
93
  id: "github-copilot",
70
94
  displayName: "GitHub Copilot",
@@ -133,7 +157,6 @@ export const SUPPORTED_ADAPTERS: readonly UsageProviderAdapter[] = [
133
157
  id: "zai",
134
158
  displayName: "Z.AI",
135
159
  semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
136
- publishesStatusline: false,
137
160
  async query(auth, signal, timeoutMs) {
138
161
  const payload = await fetchProviderJson(
139
162
  zaiMonitorUrl(auth.model.baseUrl),
@@ -149,7 +172,6 @@ export const SUPPORTED_ADAPTERS: readonly UsageProviderAdapter[] = [
149
172
  id: "zai-coding-cn",
150
173
  displayName: "Z.AI Coding CN",
151
174
  semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
152
- publishesStatusline: false,
153
175
  async query(auth, signal, timeoutMs) {
154
176
  const payload = await fetchProviderJson(
155
177
  zaiMonitorUrl(auth.model.baseUrl),
@@ -223,15 +245,14 @@ export const XAI_ADAPTER: UsageProviderAdapter = {
223
245
  },
224
246
  };
225
247
 
226
- export function usageAdapters(xaiUsage = true): readonly UsageProviderAdapter[] {
227
- return xaiUsage ? [...SUPPORTED_ADAPTERS, XAI_ADAPTER] : SUPPORTED_ADAPTERS;
248
+ export function usageAdapters(): readonly UsageProviderAdapter[] {
249
+ return [...SUPPORTED_ADAPTERS, XAI_ADAPTER];
228
250
  }
229
251
 
230
252
  export function adapterForProvider(
231
253
  providerId: string | undefined,
232
- xaiUsage = true,
233
254
  ): UsageProviderAdapter | undefined {
234
- return usageAdapters(xaiUsage).find((adapter) => adapter.id === providerId);
255
+ return usageAdapters().find((adapter) => adapter.id === providerId);
235
256
  }
236
257
 
237
258
  export function isStaleExtensionContextError(error: unknown): boolean {
@@ -261,11 +282,14 @@ export async function resolveUsageAuth(
261
282
  // SAFETY: Pi exposes the required auth methods at runtime, and checks below narrow them before use.
262
283
  const registry = ctx.modelRegistry as unknown as UsageAuthRegistry;
263
284
  let modelAuth: RequestAuth | undefined;
264
- if (ctx.model?.provider === adapter.id && typeof registry.getApiKeyAndHeaders === "function") {
265
- const result = await registry.getApiKeyAndHeaders(ctx.model);
285
+ const currentModel = ctx.model?.provider === adapter.id ? ctx.model : undefined;
286
+ const resolveCurrentModelAuth = async (): Promise<RequestAuth | undefined> => {
287
+ if (!currentModel || typeof registry.getApiKeyAndHeaders !== "function") return undefined;
288
+ const result = await registry.getApiKeyAndHeaders(currentModel);
266
289
  if (!result.ok) throw new Error(redactUsageError(result.error));
267
- if (authorizationFrom(result)) modelAuth = result;
268
- }
290
+ return authorizationFrom(result) ? result : undefined;
291
+ };
292
+ if (adapter.id !== "deepseek") modelAuth = await resolveCurrentModelAuth();
269
293
  if (typeof registry.getProviderAuth !== "function") {
270
294
  throw new Error("pi-usage requires Pi 0.81.0 or newer to validate resolved provider auth.");
271
295
  }
@@ -278,6 +302,9 @@ export async function resolveUsageAuth(
278
302
  `${adapter.displayName} usage cannot send a proxy-resolved credential to the official usage endpoint.`,
279
303
  );
280
304
  }
305
+ // DeepSeek reads selected-model auth last so a rotation during provider-origin validation
306
+ // cannot leave the earlier credential queued for the balance request.
307
+ if (adapter.id === "deepseek") modelAuth = await resolveCurrentModelAuth();
281
308
  const auth = modelAuth ?? providerResult?.auth;
282
309
  if (!auth) return undefined;
283
310
  if (adapter.id === "github-copilot") {
@@ -302,6 +329,26 @@ export async function resolveUsageAuth(
302
329
  if (!offered.ok) throw new Error("xAI OAuth credential discovery failed closed.");
303
330
  return resolveXaiUsageAuth(auth, model, salt, offered.candidates);
304
331
  }
332
+ if (adapter.id === "deepseek") {
333
+ const resolvedAuthorization = authorizationFrom(auth);
334
+ const access = bearerToken(resolvedAuthorization);
335
+ if (!access) throw new Error("DeepSeek API balance requires Bearer authentication.");
336
+ const authorization = `Bearer ${access}`;
337
+ const headers = { Authorization: authorization };
338
+ return {
339
+ apiKey: access,
340
+ headers,
341
+ fingerprint: fingerprintResolvedAuth({ headers }, salt),
342
+ secrets: [
343
+ access,
344
+ auth.apiKey,
345
+ headerValue(auth.headers, "Authorization"),
346
+ resolvedAuthorization,
347
+ authorization,
348
+ ].filter((value): value is string => Boolean(value)),
349
+ model,
350
+ };
351
+ }
305
352
  const authorization = authorizationFrom(auth);
306
353
  if (!authorization) return undefined;
307
354
  const headers = { Authorization: authorization };
@@ -685,6 +732,7 @@ function hasOfficialUrlOrigin(value: string, providerId: string): boolean {
685
732
  try {
686
733
  const url = new URL(value);
687
734
  if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
735
+ if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
688
736
  if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
689
737
  if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
690
738
  if (providerId === "kimi-coding") return url.origin === "https://api.kimi.com";
package/src/settings.ts CHANGED
@@ -9,12 +9,12 @@ export const MAX_USAGE_SETTINGS_BYTES = 64 * 1024;
9
9
 
10
10
  export interface UsageSettings {
11
11
  codexFastMode: boolean;
12
- xaiUsage: boolean;
12
+ codexStatusResetCountdown: boolean;
13
13
  }
14
14
 
15
15
  export const DEFAULT_USAGE_SETTINGS: Readonly<UsageSettings> = Object.freeze({
16
16
  codexFastMode: false,
17
- xaiUsage: true,
17
+ codexStatusResetCountdown: true,
18
18
  });
19
19
 
20
20
  export interface UsageSettingsState {
@@ -54,7 +54,10 @@ export function normalizeUsageSettings(value: unknown): UsageSettings | undefine
54
54
  if (Object.hasOwn(value, "codexFastMode") && typeof value.codexFastMode !== "boolean") {
55
55
  return undefined;
56
56
  }
57
- if (Object.hasOwn(value, "xaiUsage") && typeof value.xaiUsage !== "boolean") {
57
+ if (
58
+ Object.hasOwn(value, "codexStatusResetCountdown") &&
59
+ typeof value.codexStatusResetCountdown !== "boolean"
60
+ ) {
58
61
  return undefined;
59
62
  }
60
63
  return {
@@ -62,8 +65,10 @@ export function normalizeUsageSettings(value: unknown): UsageSettings | undefine
62
65
  typeof value.codexFastMode === "boolean"
63
66
  ? value.codexFastMode
64
67
  : DEFAULT_USAGE_SETTINGS.codexFastMode,
65
- xaiUsage:
66
- typeof value.xaiUsage === "boolean" ? value.xaiUsage : DEFAULT_USAGE_SETTINGS.xaiUsage,
68
+ codexStatusResetCountdown:
69
+ typeof value.codexStatusResetCountdown === "boolean"
70
+ ? value.codexStatusResetCountdown
71
+ : DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown,
67
72
  };
68
73
  }
69
74
 
package/src/types.ts CHANGED
@@ -84,6 +84,11 @@ export type ProviderUsageState =
84
84
  message: string;
85
85
  };
86
86
 
87
+ export type DeepSeekBalancePayload = {
88
+ is_available?: unknown;
89
+ balance_infos?: unknown;
90
+ };
91
+
87
92
  export type GitHubCopilotUsagePayload = {
88
93
  login?: unknown;
89
94
  copilot_plan?: unknown;
@@ -3,8 +3,8 @@ import { sanitizeDisplayText } from "./core.js";
3
3
  import { providerIsConfigured, usageAdapters } from "./query.js";
4
4
  import type { PiModel, UsageProviderAdapter } from "./types.js";
5
5
 
6
- export function configuredAdapters(ctx: ExtensionContext, xaiUsage = true): UsageProviderAdapter[] {
7
- return usageAdapters(xaiUsage).filter(
6
+ export function configuredAdapters(ctx: ExtensionContext): UsageProviderAdapter[] {
7
+ return usageAdapters().filter(
8
8
  (adapter) => adapter.id === ctx.model?.provider || providerIsConfigured(ctx, adapter.id),
9
9
  );
10
10
  }
@@ -23,7 +23,7 @@ export async function showUsageSettings(
23
23
  settingsRuntime: UsageSettingsRuntime,
24
24
  parentSignal: AbortSignal,
25
25
  isCurrent: () => boolean,
26
- onApplied: (id: UsageSettingId, previous: boolean, next: boolean) => void,
26
+ onApplied: (id: UsageSettingId) => void,
27
27
  ): Promise<boolean> {
28
28
  if (ctx.mode !== "tui") {
29
29
  if (ctx.hasUI) ctx.ui.notify(`Edit settings manually: ${settingsRuntime.get().path}`, "info");
@@ -47,10 +47,10 @@ export async function showUsageSettings(
47
47
  values: [OFF, ON],
48
48
  },
49
49
  {
50
- id: "xaiUsage",
51
- label: "xAI usage",
52
- description: "Report OAuth subscription allowance and credits.",
53
- currentValue: state.kind !== "invalid" && state.settings.xaiUsage ? ON : OFF,
50
+ id: "codexStatusResetCountdown",
51
+ label: "Codex reset countdown",
52
+ description: "Show time remaining until each Codex usage limit resets.",
53
+ currentValue: state.settings.codexStatusResetCountdown ? ON : OFF,
54
54
  values: [OFF, ON],
55
55
  },
56
56
  ];
@@ -75,8 +75,7 @@ export async function showUsageSettings(
75
75
  saveQueue = saveQueue.then(async () => {
76
76
  const previous = settingsRuntime.get().settings[settingId];
77
77
  if (settingsRuntime.get().kind === "invalid") {
78
- const effectivePrevious = settingId === "xaiUsage" ? false : previous;
79
- settingsList.updateValue(id, displayValue(settingId, effectivePrevious));
78
+ settingsList.updateValue(id, displayValue(previous));
80
79
  if (!signal.aborted && isCurrent()) {
81
80
  ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
82
81
  tui.requestRender();
@@ -87,17 +86,17 @@ export async function showUsageSettings(
87
86
  await settingsRuntime.update({ [settingId]: requested }, signal);
88
87
  } catch (error) {
89
88
  if (signal.aborted || !isCurrent()) return;
90
- settingsList.updateValue(id, displayValue(settingId, previous));
89
+ settingsList.updateValue(id, displayValue(previous));
91
90
  ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
92
91
  tui.requestRender();
93
92
  return;
94
93
  }
95
94
  if (previous !== requested) {
96
95
  changed = true;
97
- onApplied(settingId, previous, requested);
96
+ onApplied(settingId);
98
97
  }
99
98
  if (signal.aborted || !isCurrent()) return;
100
- settingsList.updateValue(id, displayValue(settingId, requested));
99
+ settingsList.updateValue(id, displayValue(requested));
101
100
  tui.requestRender();
102
101
  });
103
102
  },
@@ -123,6 +122,6 @@ export async function showUsageSettings(
123
122
  });
124
123
  }
125
124
 
126
- function displayValue(_id: UsageSettingId, enabled: boolean): string {
125
+ function displayValue(enabled: boolean): string {
127
126
  return enabled ? ON : OFF;
128
127
  }