@leo000001/opencode-quota-sidebar 1.12.0 → 1.13.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/README.md +28 -14
- package/dist/events.d.ts +4 -1
- package/dist/events.js +5 -1
- package/dist/format.js +23 -6
- package/dist/index.js +4 -3
- package/dist/providers/core/anthropic.js +107 -38
- package/dist/quota_service.js +25 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Add the package name to `plugin` in your `opencode.json`. OpenCode uses Bun to i
|
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
Note for OpenCode `>=1.2.15`: TUI settings (`theme`/`keybinds`/`tui`) moved to `tui.json`, but plugin loading still stays in `opencode.json` (`plugin: []`).
|
|
21
|
+
This plugin also accepts both `config.providers` and older `provider.list` runtime shapes when discovering provider options.
|
|
21
22
|
|
|
22
23
|
## Development (build from source)
|
|
23
24
|
|
|
@@ -38,12 +39,12 @@ On Windows, use forward slashes: `"file:///D:/Lab/opencode-quota-sidebar/dist/in
|
|
|
38
39
|
|
|
39
40
|
## Supported quota providers
|
|
40
41
|
|
|
41
|
-
| Provider | Endpoint | Auth | Status
|
|
42
|
-
| -------------- | -------------------------------------- | --------------- |
|
|
43
|
-
| OpenAI Codex | `chatgpt.com/backend-api/wham/usage` | OAuth (ChatGPT) | Multi-window (short-term + weekly)
|
|
44
|
-
| GitHub Copilot | `api.github.com/copilot_internal/user` | OAuth | Monthly quota
|
|
45
|
-
| RightCode | `www.right.codes/account/summary` | API key | Subscription or balance (by prefix)
|
|
46
|
-
| Anthropic |
|
|
42
|
+
| Provider | Endpoint | Auth | Status |
|
|
43
|
+
| -------------- | -------------------------------------- | --------------- | --------------------------------------- |
|
|
44
|
+
| OpenAI Codex | `chatgpt.com/backend-api/wham/usage` | OAuth (ChatGPT) | Multi-window (short-term + weekly) |
|
|
45
|
+
| GitHub Copilot | `api.github.com/copilot_internal/user` | OAuth | Monthly quota |
|
|
46
|
+
| RightCode | `www.right.codes/account/summary` | API key | Subscription or balance (by prefix) |
|
|
47
|
+
| Anthropic | `api.anthropic.com/api/oauth/usage` | OAuth | Multi-window (5h + weekly / plan-based) |
|
|
47
48
|
|
|
48
49
|
Want to add support for another provider (Google Antigravity, Zhipu AI, Firmware AI, etc.)? See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
49
50
|
|
|
@@ -55,8 +56,8 @@ Want to add support for another provider (Google Antigravity, Zhipu AI, Firmware
|
|
|
55
56
|
- line 3: Cache Read tokens (only if non-zero)
|
|
56
57
|
- line 4: Cache Write tokens (only if non-zero)
|
|
57
58
|
- line 5: `$X.XX as API cost` (equivalent API billing for subscription-auth providers)
|
|
58
|
-
- quota lines: quota text like `OpenAI 5h 80% Rst 16:20`,
|
|
59
|
-
- RightCode daily quota shows `$remaining/$dailyTotal` + expiry (e.g. `RC Daily $105/$60 Exp 02-27`, without trailing percent) and also shows balance on the next indented line when available
|
|
59
|
+
- quota lines: quota text like `OpenAI 5h 80% Rst 16:20`; short windows (`5h`, `1d`, `Daily`) show `HH:MM` on same-day resets and `MM-DD HH:MM` when crossing days, while longer windows continue to show `MM-DD`
|
|
60
|
+
- RightCode daily quota shows `$remaining/$dailyTotal` + expiry (e.g. `RC Daily $105/$60 Exp 02-27`, without trailing percent) and also shows balance on the next indented line when available; `Exp` remains date-only
|
|
60
61
|
- Session-scoped usage/quota can include descendant subagent sessions (enabled by default via `sidebar.includeChildren=true`). Traversal is bounded by `childrenMaxDepth` (default 6), `childrenMaxSessions` (default 128), and `childrenConcurrency` (default 5); truncation is logged when `OPENCODE_QUOTA_DEBUG=1`. Day/week/month ranges never merge children — only session scope does.
|
|
61
62
|
- Toast message includes three sections: `Token Usage`, `Cost as API` (per provider), and `Quota`
|
|
62
63
|
- Quota snapshots are de-duplicated before rendering to avoid repeated provider lines
|
|
@@ -67,7 +68,7 @@ Want to add support for another provider (Google Antigravity, Zhipu AI, Firmware
|
|
|
67
68
|
- OpenAI Codex OAuth (`/backend-api/wham/usage`)
|
|
68
69
|
- GitHub Copilot OAuth (`/copilot_internal/user`)
|
|
69
70
|
- RightCode API key (`/account/summary`)
|
|
70
|
-
- Anthropic
|
|
71
|
+
- Anthropic Claude OAuth (`/api/oauth/usage`, with beta header)
|
|
71
72
|
- OpenAI OAuth quota checks auto-refresh expired access token (using refresh token)
|
|
72
73
|
- API key providers still show usage aggregation (quota only applies to subscription providers)
|
|
73
74
|
- Incremental usage aggregation — only processes new messages since last cursor
|
|
@@ -145,7 +146,9 @@ Recommended global config:
|
|
|
145
146
|
Optional project overrides:
|
|
146
147
|
|
|
147
148
|
- `<worktree>/quota-sidebar.config.json`
|
|
149
|
+
- `<directory>/quota-sidebar.config.json` (when different from `worktree`)
|
|
148
150
|
- `<worktree>/.opencode/quota-sidebar.config.json`
|
|
151
|
+
- `<directory>/.opencode/quota-sidebar.config.json` (when different from `worktree`)
|
|
149
152
|
|
|
150
153
|
Optional explicit override:
|
|
151
154
|
|
|
@@ -158,9 +161,11 @@ Optional config-home override:
|
|
|
158
161
|
Resolution order (low -> high):
|
|
159
162
|
|
|
160
163
|
1. Global config (`~/.config/opencode/...`)
|
|
161
|
-
2.
|
|
162
|
-
3.
|
|
163
|
-
4. `
|
|
164
|
+
2. `<worktree>/quota-sidebar.config.json`
|
|
165
|
+
3. `<directory>/quota-sidebar.config.json`
|
|
166
|
+
4. `<worktree>/.opencode/quota-sidebar.config.json`
|
|
167
|
+
5. `<directory>/.opencode/quota-sidebar.config.json`
|
|
168
|
+
6. `OPENCODE_QUOTA_CONFIG`
|
|
164
169
|
|
|
165
170
|
Values are layered; later sources override earlier ones.
|
|
166
171
|
|
|
@@ -235,6 +240,7 @@ Example config:
|
|
|
235
240
|
Notes:
|
|
236
241
|
|
|
237
242
|
- `sidebar.showCost` controls API-cost visibility in sidebar title, `quota_summary` markdown report, and toast message.
|
|
243
|
+
- `quota_summary` follows the same reset compaction rules for short windows in its subscription section (`5h` / `1d` / `Daily` show time, long windows show date, RightCode `Exp` stays date-only).
|
|
238
244
|
- `sidebar.width` is measured in terminal cells. CJK/emoji truncation is best-effort to avoid sidebar overflow.
|
|
239
245
|
- `sidebar.multilineTitle` controls multi-line sidebar layout (default: `true`). Set `false` for compact single-line title.
|
|
240
246
|
- `sidebar.wrapQuotaLines` controls quota line wrapping and continuation indentation (default: `true`).
|
|
@@ -273,6 +279,14 @@ OpenAI
|
|
|
273
279
|
Weekly 73% Rst 03-12
|
|
274
280
|
```
|
|
275
281
|
|
|
282
|
+
1 provider, short window crossing into the next day:
|
|
283
|
+
|
|
284
|
+
```text
|
|
285
|
+
Anthropic
|
|
286
|
+
5h 0% Rst 03-10 01:00
|
|
287
|
+
Weekly 46% Rst 03-15
|
|
288
|
+
```
|
|
289
|
+
|
|
276
290
|
2+ providers (even if each provider is single-window):
|
|
277
291
|
|
|
278
292
|
```text
|
|
@@ -306,10 +320,10 @@ RC
|
|
|
306
320
|
Balance $260
|
|
307
321
|
```
|
|
308
322
|
|
|
309
|
-
Provider status (examples):
|
|
323
|
+
Provider status / quota (examples):
|
|
310
324
|
|
|
311
325
|
```text
|
|
312
|
-
Anthropic
|
|
326
|
+
Anthropic 5h 80%+
|
|
313
327
|
Copilot unavailable
|
|
314
328
|
OpenAI Remaining ?
|
|
315
329
|
```
|
package/dist/events.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ export declare function createEventDispatcher(handlers: {
|
|
|
3
3
|
onSessionCreated: (session: Session) => Promise<void>;
|
|
4
4
|
onSessionUpdated: (session: Session) => Promise<void>;
|
|
5
5
|
onSessionDeleted: (session: Session) => Promise<void>;
|
|
6
|
-
onMessageRemoved: (
|
|
6
|
+
onMessageRemoved: (info: {
|
|
7
|
+
sessionID: string;
|
|
8
|
+
messageID?: string;
|
|
9
|
+
}) => Promise<void>;
|
|
7
10
|
onAssistantMessageCompleted: (message: AssistantMessage) => Promise<void>;
|
|
8
11
|
}): (event: Event) => Promise<void>;
|
package/dist/events.js
CHANGED
|
@@ -16,7 +16,11 @@ export function createEventDispatcher(handlers) {
|
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
18
|
if (event.type === 'message.removed') {
|
|
19
|
-
|
|
19
|
+
const props = event.properties;
|
|
20
|
+
await handlers.onMessageRemoved({
|
|
21
|
+
sessionID: props.sessionID,
|
|
22
|
+
messageID: typeof props.messageID === 'string' ? props.messageID : undefined,
|
|
23
|
+
});
|
|
20
24
|
return;
|
|
21
25
|
}
|
|
22
26
|
if (event.type !== 'message.updated')
|
package/dist/format.js
CHANGED
|
@@ -325,7 +325,7 @@ function compactQuotaWide(quota, labelWidth = 0, options) {
|
|
|
325
325
|
? [sanitizeLine(win.label), pct]
|
|
326
326
|
: [sanitizeLine(win.label)]
|
|
327
327
|
: [pct];
|
|
328
|
-
const reset = compactReset(win.resetAt, win.resetLabel);
|
|
328
|
+
const reset = compactReset(win.resetAt, win.resetLabel, win.label);
|
|
329
329
|
if (reset) {
|
|
330
330
|
parts.push(`${sanitizeLine(win.resetLabel || 'Rst')} ${reset}`);
|
|
331
331
|
}
|
|
@@ -360,7 +360,12 @@ function compactQuotaWide(quota, labelWidth = 0, options) {
|
|
|
360
360
|
const fallbackText = `Remaining ${percent}${reset ? ` Rst ${reset}` : ''}`;
|
|
361
361
|
return maybeBreak(fallbackText, [fallbackText]);
|
|
362
362
|
}
|
|
363
|
-
function
|
|
363
|
+
function isShortResetWindow(label) {
|
|
364
|
+
if (typeof label !== 'string')
|
|
365
|
+
return false;
|
|
366
|
+
return /^\s*(?:\d+\s*[hd]|daily)\b/i.test(label);
|
|
367
|
+
}
|
|
368
|
+
function compactReset(iso, resetLabel, windowLabel) {
|
|
364
369
|
if (!iso)
|
|
365
370
|
return undefined;
|
|
366
371
|
const timestamp = Date.parse(iso);
|
|
@@ -378,6 +383,12 @@ function compactReset(iso, resetLabel) {
|
|
|
378
383
|
value.getMonth() === now.getMonth() &&
|
|
379
384
|
value.getDate() === now.getDate();
|
|
380
385
|
const two = (num) => `${num}`.padStart(2, '0');
|
|
386
|
+
if (isShortResetWindow(windowLabel)) {
|
|
387
|
+
const hhmm = `${two(value.getHours())}:${two(value.getMinutes())}`;
|
|
388
|
+
if (sameDay)
|
|
389
|
+
return hhmm;
|
|
390
|
+
return `${two(value.getMonth() + 1)}-${two(value.getDate())} ${hhmm}`;
|
|
391
|
+
}
|
|
381
392
|
if (sameDay) {
|
|
382
393
|
return `${two(value.getHours())}:${two(value.getMinutes())}`;
|
|
383
394
|
}
|
|
@@ -391,6 +402,12 @@ function dateLine(iso) {
|
|
|
391
402
|
return iso;
|
|
392
403
|
return new Date(time).toLocaleString();
|
|
393
404
|
}
|
|
405
|
+
function reportResetLine(iso, resetLabel, windowLabel) {
|
|
406
|
+
const compact = compactReset(iso, resetLabel, windowLabel);
|
|
407
|
+
if (compact)
|
|
408
|
+
return compact;
|
|
409
|
+
return dateLine(iso);
|
|
410
|
+
}
|
|
394
411
|
function periodLabel(period) {
|
|
395
412
|
if (period === 'day')
|
|
396
413
|
return 'Today';
|
|
@@ -462,13 +479,13 @@ export function renderMarkdownReport(period, usage, quotas, options) {
|
|
|
462
479
|
return quota.windows.map((win) => {
|
|
463
480
|
if (win.showPercent === false) {
|
|
464
481
|
const winLabel = win.label ? ` (${win.label})` : '';
|
|
465
|
-
return mdCell(`- ${quota.label}${winLabel}: ${quota.status} | reset ${
|
|
482
|
+
return mdCell(`- ${quota.label}${winLabel}: ${quota.status} | reset ${reportResetLine(win.resetAt, win.resetLabel, win.label)}`);
|
|
466
483
|
}
|
|
467
484
|
const remaining = win.remainingPercent === undefined
|
|
468
485
|
? '-'
|
|
469
486
|
: `${win.remainingPercent.toFixed(1)}%`;
|
|
470
487
|
const winLabel = win.label ? ` (${win.label})` : '';
|
|
471
|
-
return mdCell(`- ${quota.label}${winLabel}: ${quota.status} | remaining ${remaining} | reset ${
|
|
488
|
+
return mdCell(`- ${quota.label}${winLabel}: ${quota.status} | remaining ${remaining} | reset ${reportResetLine(win.resetAt, win.resetLabel, win.label)}`);
|
|
472
489
|
});
|
|
473
490
|
}
|
|
474
491
|
if (quota.status === 'ok' && quota.balance) {
|
|
@@ -480,7 +497,7 @@ export function renderMarkdownReport(period, usage, quotas, options) {
|
|
|
480
497
|
? '-'
|
|
481
498
|
: `${quota.remainingPercent.toFixed(1)}%`;
|
|
482
499
|
return [
|
|
483
|
-
mdCell(`- ${quota.label}: ${quota.status} | remaining ${remaining} | reset ${
|
|
500
|
+
mdCell(`- ${quota.label}: ${quota.status} | remaining ${remaining} | reset ${reportResetLine(quota.resetAt)}${quota.note ? ` | ${quota.note}` : ''}`),
|
|
484
501
|
];
|
|
485
502
|
});
|
|
486
503
|
return [
|
|
@@ -572,7 +589,7 @@ export function renderToastMessage(period, usage, quotas, options) {
|
|
|
572
589
|
const pct = win.remainingPercent === undefined
|
|
573
590
|
? '-'
|
|
574
591
|
: `${win.remainingPercent.toFixed(1)}%`;
|
|
575
|
-
const reset = compactReset(win.resetAt, win.resetLabel);
|
|
592
|
+
const reset = compactReset(win.resetAt, win.resetLabel, win.label);
|
|
576
593
|
const parts = [win.label];
|
|
577
594
|
if (showPercent)
|
|
578
595
|
parts.push(pct);
|
package/dist/index.js
CHANGED
|
@@ -225,9 +225,10 @@ export async function QuotaSidebarPlugin(input) {
|
|
|
225
225
|
titleRefresh.schedule(session.parentID, 0);
|
|
226
226
|
}
|
|
227
227
|
},
|
|
228
|
-
onMessageRemoved: async (
|
|
229
|
-
usageService.markForceRescan(sessionID);
|
|
230
|
-
titleRefresh.schedule(sessionID);
|
|
228
|
+
onMessageRemoved: async (info) => {
|
|
229
|
+
usageService.markForceRescan(info.sessionID);
|
|
230
|
+
titleRefresh.schedule(info.sessionID, 0);
|
|
231
|
+
scheduleParentRefreshIfSafe(info.sessionID, state.sessions[info.sessionID]?.parentID);
|
|
231
232
|
},
|
|
232
233
|
onAssistantMessageCompleted: async (message) => {
|
|
233
234
|
usageService.markSessionDirty(message.sessionID);
|
|
@@ -1,4 +1,109 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { swallow } from '../../helpers.js';
|
|
2
|
+
import { asRecord, configuredProviderEnabled, fetchWithTimeout, normalizePercent, toIso, } from '../common.js';
|
|
3
|
+
const ANTHROPIC_OAUTH_USAGE_BETA = 'oauth-2025-04-20';
|
|
4
|
+
function parseAnthropicWindow(value, label) {
|
|
5
|
+
const win = asRecord(value);
|
|
6
|
+
if (!win)
|
|
7
|
+
return undefined;
|
|
8
|
+
const usedPercent = normalizePercent(win.utilization);
|
|
9
|
+
if (usedPercent === undefined)
|
|
10
|
+
return undefined;
|
|
11
|
+
const parsed = {
|
|
12
|
+
label,
|
|
13
|
+
usedPercent,
|
|
14
|
+
remainingPercent: Math.max(0, 100 - usedPercent),
|
|
15
|
+
resetAt: toIso(win.resets_at),
|
|
16
|
+
};
|
|
17
|
+
return parsed;
|
|
18
|
+
}
|
|
19
|
+
async function fetchAnthropicQuota({ providerID, auth, config, }) {
|
|
20
|
+
const checkedAt = Date.now();
|
|
21
|
+
const base = {
|
|
22
|
+
providerID,
|
|
23
|
+
adapterID: 'anthropic',
|
|
24
|
+
label: 'Anthropic',
|
|
25
|
+
shortLabel: 'Anthropic',
|
|
26
|
+
sortOrder: 30,
|
|
27
|
+
};
|
|
28
|
+
if (!auth) {
|
|
29
|
+
return {
|
|
30
|
+
...base,
|
|
31
|
+
status: 'unavailable',
|
|
32
|
+
checkedAt,
|
|
33
|
+
note: 'auth not found',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (auth.type !== 'oauth') {
|
|
37
|
+
return {
|
|
38
|
+
...base,
|
|
39
|
+
status: 'unsupported',
|
|
40
|
+
checkedAt,
|
|
41
|
+
note: 'api key auth has no quota endpoint',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (typeof auth.access !== 'string' || !auth.access) {
|
|
45
|
+
return {
|
|
46
|
+
...base,
|
|
47
|
+
status: 'unavailable',
|
|
48
|
+
checkedAt,
|
|
49
|
+
note: 'missing oauth access token',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const response = await fetchWithTimeout('https://api.anthropic.com/api/oauth/usage', {
|
|
53
|
+
method: 'GET',
|
|
54
|
+
headers: {
|
|
55
|
+
Accept: 'application/json',
|
|
56
|
+
Authorization: `Bearer ${auth.access}`,
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
'User-Agent': 'opencode-quota-sidebar',
|
|
59
|
+
'anthropic-beta': ANTHROPIC_OAUTH_USAGE_BETA,
|
|
60
|
+
},
|
|
61
|
+
}, config.quota.requestTimeoutMs).catch(swallow('fetchAnthropicQuota:usage'));
|
|
62
|
+
if (!response) {
|
|
63
|
+
return {
|
|
64
|
+
...base,
|
|
65
|
+
status: 'error',
|
|
66
|
+
checkedAt,
|
|
67
|
+
note: 'network request failed',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
return {
|
|
72
|
+
...base,
|
|
73
|
+
status: 'error',
|
|
74
|
+
checkedAt,
|
|
75
|
+
note: `http ${response.status}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const payload = await response
|
|
79
|
+
.json()
|
|
80
|
+
.catch(swallow('fetchAnthropicQuota:json'));
|
|
81
|
+
const usage = asRecord(payload);
|
|
82
|
+
if (!usage) {
|
|
83
|
+
return {
|
|
84
|
+
...base,
|
|
85
|
+
status: 'error',
|
|
86
|
+
checkedAt,
|
|
87
|
+
note: 'invalid response',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const windows = [
|
|
91
|
+
parseAnthropicWindow(usage.five_hour, '5h'),
|
|
92
|
+
parseAnthropicWindow(usage.seven_day, 'Weekly'),
|
|
93
|
+
parseAnthropicWindow(usage.seven_day_sonnet, 'Sonnet 7d'),
|
|
94
|
+
].filter((window) => Boolean(window));
|
|
95
|
+
const primary = windows[0];
|
|
96
|
+
return {
|
|
97
|
+
...base,
|
|
98
|
+
status: primary ? 'ok' : 'error',
|
|
99
|
+
checkedAt,
|
|
100
|
+
usedPercent: primary?.usedPercent,
|
|
101
|
+
remainingPercent: primary?.remainingPercent,
|
|
102
|
+
resetAt: primary?.resetAt,
|
|
103
|
+
note: primary ? undefined : 'missing quota fields',
|
|
104
|
+
windows: windows.length > 0 ? windows : undefined,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
2
107
|
export const anthropicAdapter = {
|
|
3
108
|
id: 'anthropic',
|
|
4
109
|
label: 'Anthropic',
|
|
@@ -6,41 +111,5 @@ export const anthropicAdapter = {
|
|
|
6
111
|
sortOrder: 30,
|
|
7
112
|
matchScore: ({ providerID }) => (providerID === 'anthropic' ? 80 : 0),
|
|
8
113
|
isEnabled: (config) => configuredProviderEnabled(config.quota, 'anthropic', config.quota.includeAnthropic),
|
|
9
|
-
fetch:
|
|
10
|
-
const checkedAt = Date.now();
|
|
11
|
-
if (!auth) {
|
|
12
|
-
return {
|
|
13
|
-
providerID,
|
|
14
|
-
adapterID: 'anthropic',
|
|
15
|
-
label: 'Anthropic',
|
|
16
|
-
shortLabel: 'Anthropic',
|
|
17
|
-
sortOrder: 30,
|
|
18
|
-
status: 'unavailable',
|
|
19
|
-
checkedAt,
|
|
20
|
-
note: 'auth not found',
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
if (auth.type === 'api') {
|
|
24
|
-
return {
|
|
25
|
-
providerID,
|
|
26
|
-
adapterID: 'anthropic',
|
|
27
|
-
label: 'Anthropic',
|
|
28
|
-
shortLabel: 'Anthropic',
|
|
29
|
-
sortOrder: 30,
|
|
30
|
-
status: 'unsupported',
|
|
31
|
-
checkedAt,
|
|
32
|
-
note: 'api key has no public quota endpoint',
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
return {
|
|
36
|
-
providerID,
|
|
37
|
-
adapterID: 'anthropic',
|
|
38
|
-
label: 'Anthropic',
|
|
39
|
-
shortLabel: 'Anthropic',
|
|
40
|
-
sortOrder: 30,
|
|
41
|
-
status: 'unsupported',
|
|
42
|
-
checkedAt,
|
|
43
|
-
note: 'oauth quota endpoint is not publicly documented',
|
|
44
|
-
};
|
|
45
|
-
},
|
|
114
|
+
fetch: fetchAnthropicQuota,
|
|
46
115
|
};
|
package/dist/quota_service.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TtlValueCache } from './cache.js';
|
|
2
|
-
import { swallow } from './helpers.js';
|
|
2
|
+
import { isRecord, swallow } from './helpers.js';
|
|
3
3
|
import { listDefaultQuotaProviderIDs, loadAuthMap, quotaSort } from './quota.js';
|
|
4
4
|
export function createQuotaService(deps) {
|
|
5
5
|
const authCache = new TtlValueCache();
|
|
@@ -16,26 +16,34 @@ export function createQuotaService(deps) {
|
|
|
16
16
|
const cached = providerOptionsCache.get();
|
|
17
17
|
if (cached)
|
|
18
18
|
return cached;
|
|
19
|
-
const
|
|
20
|
-
if (!
|
|
19
|
+
const client = deps.client;
|
|
20
|
+
if (!client.config?.providers && !client.provider?.list) {
|
|
21
21
|
return providerOptionsCache.set({}, 30_000);
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
// Newer runtimes expose config.providers; older clients may only expose
|
|
24
|
+
// provider.list with a slightly different response shape.
|
|
25
|
+
const response = await (client.config?.providers
|
|
26
|
+
? client.config.providers({
|
|
27
|
+
query: { directory: deps.directory },
|
|
28
|
+
throwOnError: true,
|
|
29
|
+
})
|
|
30
|
+
: client.provider.list({
|
|
31
|
+
query: { directory: deps.directory },
|
|
32
|
+
throwOnError: true,
|
|
33
|
+
}))
|
|
28
34
|
.catch(swallow('getProviderOptionsMap'));
|
|
29
|
-
const data = response &&
|
|
30
|
-
|
|
31
|
-
'data' in response &&
|
|
32
|
-
response.data &&
|
|
33
|
-
typeof response.data === 'object' &&
|
|
34
|
-
'providers' in response.data
|
|
35
|
-
? response.data.providers
|
|
35
|
+
const data = isRecord(response) && isRecord(response.data)
|
|
36
|
+
? response.data
|
|
36
37
|
: undefined;
|
|
37
|
-
const
|
|
38
|
-
? data.
|
|
38
|
+
const list = Array.isArray(data?.providers)
|
|
39
|
+
? data.providers
|
|
40
|
+
: Array.isArray(data?.all)
|
|
41
|
+
? data.all
|
|
42
|
+
: Array.isArray(data)
|
|
43
|
+
? data
|
|
44
|
+
: undefined;
|
|
45
|
+
const map = Array.isArray(list)
|
|
46
|
+
? list.reduce((acc, item) => {
|
|
39
47
|
if (!item || typeof item !== 'object')
|
|
40
48
|
return acc;
|
|
41
49
|
const record = item;
|