@leo000001/opencode-quota-sidebar 3.0.9 → 3.0.10

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.
@@ -1,5 +1,60 @@
1
1
  import { debug, debugError, isRecord, swallow } from '../../helpers.js';
2
- import { OPENAI_OAUTH_CLIENT_ID, configuredProviderEnabled, fetchWithTimeout, normalizePercent, parseRateLimitWindow, toIso, } from '../common.js';
2
+ import { OPENAI_OAUTH_CLIENT_ID, asNumber, configuredProviderEnabled, fetchWithTimeout, toIso, windowLabel, } from '../common.js';
3
+ function decodeJwtPayload(token) {
4
+ try {
5
+ const parts = token.split('.');
6
+ if (parts.length !== 3)
7
+ return undefined;
8
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
9
+ return isRecord(payload) ? payload : undefined;
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ }
15
+ function extractAccountIdFromJwt(token) {
16
+ const payload = decodeJwtPayload(token);
17
+ if (!payload)
18
+ return undefined;
19
+ const authClaim = payload['https://api.openai.com/auth'];
20
+ if (!isRecord(authClaim))
21
+ return undefined;
22
+ const accountID = authClaim.chatgpt_account_id;
23
+ return typeof accountID === 'string' && accountID ? accountID : undefined;
24
+ }
25
+ function normalizeOpenAIQuotaPercent(value) {
26
+ const numeric = asNumber(value);
27
+ if (numeric === undefined || Number.isNaN(numeric))
28
+ return undefined;
29
+ const expanded = numeric > 0 && numeric < 1 ? numeric * 100 : numeric;
30
+ if (expanded < 0)
31
+ return 0;
32
+ if (expanded > 100)
33
+ return 100;
34
+ return expanded;
35
+ }
36
+ function windowResetAt(win, fallback) {
37
+ const absolute = toIso(win.reset_at ?? fallback?.reset_at);
38
+ if (absolute)
39
+ return absolute;
40
+ const resetAfterSeconds = asNumber(win.reset_after_seconds) ?? asNumber(fallback?.reset_after_seconds);
41
+ if (resetAfterSeconds === undefined || resetAfterSeconds < 0)
42
+ return undefined;
43
+ return new Date(Date.now() + resetAfterSeconds * 1000).toISOString();
44
+ }
45
+ function parseOpenAIWindow(win, fallbackLabel) {
46
+ const usedPercent = normalizeOpenAIQuotaPercent(win.used_percent);
47
+ const remainingPercent = normalizeOpenAIQuotaPercent(win.remaining_percent) ??
48
+ (usedPercent === undefined ? undefined : 100 - usedPercent);
49
+ if (remainingPercent === undefined)
50
+ return undefined;
51
+ return {
52
+ label: windowLabel(win, fallbackLabel),
53
+ remainingPercent,
54
+ usedPercent,
55
+ resetAt: windowResetAt(win),
56
+ };
57
+ }
3
58
  async function fetchOpenAIQuota(ctx) {
4
59
  const checkedAt = Date.now();
5
60
  const base = {
@@ -85,13 +140,15 @@ async function fetchOpenAIQuota(ctx) {
85
140
  }
86
141
  }
87
142
  }
143
+ const accountId = (typeof ctx.auth.accountId === 'string' && ctx.auth.accountId) ||
144
+ extractAccountIdFromJwt(access);
88
145
  const headers = new Headers({
89
146
  Authorization: `Bearer ${access}`,
90
147
  Accept: 'application/json',
91
148
  'User-Agent': 'opencode-quota-sidebar',
92
149
  });
93
- if (typeof ctx.auth.accountId === 'string' && ctx.auth.accountId) {
94
- headers.set('ChatGPT-Account-Id', ctx.auth.accountId);
150
+ if (accountId) {
151
+ headers.set('ChatGPT-Account-Id', accountId);
95
152
  }
96
153
  const response = await fetchWithTimeout('https://chatgpt.com/backend-api/wham/usage', { headers }, ctx.config.quota.requestTimeoutMs).catch(swallow('fetchOpenAIQuota:usage'));
97
154
  if (!response) {
@@ -123,18 +180,18 @@ async function fetchOpenAIQuota(ctx) {
123
180
  const primary = isRecord(rateLimit.primary_window)
124
181
  ? rateLimit.primary_window
125
182
  : {};
126
- const usedPercent = normalizePercent(primary.used_percent);
127
- const remainingPercent = normalizePercent(primary.remaining_percent) ??
183
+ const usedPercent = normalizeOpenAIQuotaPercent(primary.used_percent);
184
+ const remainingPercent = normalizeOpenAIQuotaPercent(primary.remaining_percent) ??
128
185
  (usedPercent === undefined ? undefined : 100 - usedPercent);
129
- const resetAt = toIso(primary.reset_at ?? rateLimit.reset_at);
186
+ const resetAt = windowResetAt(primary, rateLimit);
130
187
  const windows = [];
131
188
  if (remainingPercent !== undefined) {
132
- const primaryWin = parseRateLimitWindow(primary, '');
189
+ const primaryWin = parseOpenAIWindow(primary, '');
133
190
  if (primaryWin)
134
191
  windows.push(primaryWin);
135
192
  }
136
193
  if (isRecord(rateLimit.secondary_window)) {
137
- const secondaryWin = parseRateLimitWindow(rateLimit.secondary_window, 'Weekly');
194
+ const secondaryWin = parseOpenAIWindow(rateLimit.secondary_window, 'Weekly');
138
195
  if (secondaryWin)
139
196
  windows.push(secondaryWin);
140
197
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leo000001/opencode-quota-sidebar",
3
- "version": "3.0.9",
3
+ "version": "3.0.10",
4
4
  "description": "OpenCode plugin that shows quota and token usage in TUI sidebar panels and compact session titles",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",