@eiei114/pi-sub-core 1.5.1
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/CHANGELOG.md +190 -0
- package/README.md +178 -0
- package/index.ts +540 -0
- package/package.json +35 -0
- package/src/cache.ts +546 -0
- package/src/config.ts +35 -0
- package/src/dependencies.ts +37 -0
- package/src/errors.ts +71 -0
- package/src/paths.ts +55 -0
- package/src/provider.ts +66 -0
- package/src/providers/detection.ts +51 -0
- package/src/providers/impl/anthropic.ts +174 -0
- package/src/providers/impl/antigravity.ts +226 -0
- package/src/providers/impl/codex.ts +186 -0
- package/src/providers/impl/copilot.ts +176 -0
- package/src/providers/impl/gemini.ts +130 -0
- package/src/providers/impl/kiro.ts +92 -0
- package/src/providers/impl/zai.ts +120 -0
- package/src/providers/index.ts +5 -0
- package/src/providers/metadata.ts +16 -0
- package/src/providers/registry.ts +54 -0
- package/src/providers/settings.ts +109 -0
- package/src/providers/status.ts +25 -0
- package/src/settings/behavior.ts +58 -0
- package/src/settings/menu.ts +83 -0
- package/src/settings/tools.ts +38 -0
- package/src/settings/ui.ts +450 -0
- package/src/settings-types.ts +95 -0
- package/src/settings-ui.ts +1 -0
- package/src/settings.ts +137 -0
- package/src/status.ts +245 -0
- package/src/storage/lock.ts +150 -0
- package/src/storage.ts +61 -0
- package/src/types.ts +33 -0
- package/src/ui/keybindings.ts +92 -0
- package/src/ui/settings-list.ts +290 -0
- package/src/usage/controller.ts +250 -0
- package/src/usage/fetch.ts +215 -0
- package/src/usage/types.ts +5 -0
- package/src/utils.ts +158 -0
- package/test/all.test.ts +9 -0
- package/test/cache.test.ts +157 -0
- package/test/controller.test.ts +101 -0
- package/test/detection.test.ts +24 -0
- package/test/extension.test.ts +233 -0
- package/test/helpers.ts +48 -0
- package/test/keybindings.test.ts +59 -0
- package/test/lock.test.ts +49 -0
- package/test/prioritize.test.ts +81 -0
- package/test/providers.test.ts +385 -0
- package/test/status.test.ts +70 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { AnthropicProvider } from "../src/providers/impl/anthropic.js";
|
|
4
|
+
import { CopilotProvider } from "../src/providers/impl/copilot.js";
|
|
5
|
+
import { GeminiProvider } from "../src/providers/impl/gemini.js";
|
|
6
|
+
import { AntigravityProvider } from "../src/providers/impl/antigravity.js";
|
|
7
|
+
import { CodexProvider } from "../src/providers/impl/codex.js";
|
|
8
|
+
import { KiroProvider } from "../src/providers/impl/kiro.js";
|
|
9
|
+
import { ZaiProvider } from "../src/providers/impl/zai.js";
|
|
10
|
+
import { createDeps, createJsonResponse, getAuthPath } from "./helpers.js";
|
|
11
|
+
import type { UsageSnapshot } from "../src/types.js";
|
|
12
|
+
|
|
13
|
+
function withAuth(files: Map<string, string>, payload: Record<string, unknown>, home: string): void {
|
|
14
|
+
files.set(getAuthPath(home), JSON.stringify(payload));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function assertWindow(usage: UsageSnapshot, label: string): void {
|
|
18
|
+
const found = usage.windows.find((window) => window.label === label);
|
|
19
|
+
assert.ok(found, `Expected window ${label}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test("anthropic reads token from ANTHROPIC_OAUTH_TOKEN env var", async () => {
|
|
23
|
+
const provider = new AnthropicProvider();
|
|
24
|
+
let authorization: string | undefined;
|
|
25
|
+
|
|
26
|
+
const { deps } = createDeps({
|
|
27
|
+
env: { ANTHROPIC_OAUTH_TOKEN: "env-token" },
|
|
28
|
+
fetch: async (_url, init) => {
|
|
29
|
+
authorization = (init as any)?.headers?.Authorization;
|
|
30
|
+
return createJsonResponse({});
|
|
31
|
+
},
|
|
32
|
+
execFileSync: () => "",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await provider.fetchUsage(deps);
|
|
36
|
+
assert.equal(authorization, "Bearer env-token");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("anthropic env token overrides auth.json", async () => {
|
|
40
|
+
const provider = new AnthropicProvider();
|
|
41
|
+
let authorization: string | undefined;
|
|
42
|
+
|
|
43
|
+
const { deps, files } = createDeps({
|
|
44
|
+
env: { ANTHROPIC_OAUTH_TOKEN: "env-token" },
|
|
45
|
+
fetch: async (_url, init) => {
|
|
46
|
+
authorization = (init as any)?.headers?.Authorization;
|
|
47
|
+
return createJsonResponse({});
|
|
48
|
+
},
|
|
49
|
+
execFileSync: () => "",
|
|
50
|
+
});
|
|
51
|
+
withAuth(files, { anthropic: { access: "file-token" } }, deps.homedir());
|
|
52
|
+
|
|
53
|
+
await provider.fetchUsage(deps);
|
|
54
|
+
assert.equal(authorization, "Bearer env-token");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("anthropic parses windows and extra usage", async () => {
|
|
58
|
+
const provider = new AnthropicProvider();
|
|
59
|
+
const { deps, files } = createDeps({
|
|
60
|
+
fetch: async () => createJsonResponse({
|
|
61
|
+
five_hour: { utilization: 99, resets_at: new Date(Date.now() + 3600_000).toISOString() },
|
|
62
|
+
seven_day: { utilization: 20, resets_at: new Date(Date.now() + 86400_000).toISOString() },
|
|
63
|
+
extra_usage: { is_enabled: true, used_credits: 1234, monthly_limit: 5000, utilization: 40 },
|
|
64
|
+
}),
|
|
65
|
+
execFileSync: () => "",
|
|
66
|
+
});
|
|
67
|
+
withAuth(files, { anthropic: { access: "token" } }, deps.homedir());
|
|
68
|
+
|
|
69
|
+
const usage = await provider.fetchUsage(deps);
|
|
70
|
+
assertWindow(usage, "5h");
|
|
71
|
+
assertWindow(usage, "Week");
|
|
72
|
+
const extra = usage.windows.find((window) => window.label.startsWith("Extra"));
|
|
73
|
+
assert.ok(extra?.label.includes("Extra [active]"));
|
|
74
|
+
assert.equal(usage.extraUsageEnabled, true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("copilot reads token from GITHUB_TOKEN env var", async () => {
|
|
78
|
+
const provider = new CopilotProvider();
|
|
79
|
+
let authorization: string | undefined;
|
|
80
|
+
|
|
81
|
+
const { deps } = createDeps({
|
|
82
|
+
env: { GITHUB_TOKEN: "gh-token" },
|
|
83
|
+
fetch: async (_url, init) => {
|
|
84
|
+
authorization = (init as any)?.headers?.Authorization;
|
|
85
|
+
return createJsonResponse({});
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await provider.fetchUsage(deps);
|
|
90
|
+
assert.equal(authorization, "token gh-token");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("gemini reads token from GOOGLE_GEMINI_CLI_OAUTH_TOKEN env var", async () => {
|
|
94
|
+
const provider = new GeminiProvider();
|
|
95
|
+
let authorization: string | undefined;
|
|
96
|
+
|
|
97
|
+
const { deps } = createDeps({
|
|
98
|
+
env: { GOOGLE_GEMINI_CLI_OAUTH_TOKEN: "g-token" },
|
|
99
|
+
fetch: async (_url, init) => {
|
|
100
|
+
authorization = (init as any)?.headers?.Authorization;
|
|
101
|
+
return createJsonResponse({ buckets: [] });
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await provider.fetchUsage(deps);
|
|
106
|
+
assert.equal(authorization, "Bearer g-token");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("antigravity reads token from GOOGLE_ANTIGRAVITY_OAUTH_TOKEN env var", async () => {
|
|
110
|
+
const provider = new AntigravityProvider();
|
|
111
|
+
let authorization: string | undefined;
|
|
112
|
+
|
|
113
|
+
const { deps } = createDeps({
|
|
114
|
+
env: { GOOGLE_ANTIGRAVITY_OAUTH_TOKEN: "ag-token" },
|
|
115
|
+
fetch: async (_url, init) => {
|
|
116
|
+
authorization = (init as any)?.headers?.Authorization;
|
|
117
|
+
return createJsonResponse({ models: {} });
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await provider.fetchUsage(deps);
|
|
122
|
+
assert.equal(authorization, "Bearer ag-token");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("codex reads token from OPENAI_CODEX_OAUTH_TOKEN env var", async () => {
|
|
126
|
+
const provider = new CodexProvider();
|
|
127
|
+
let authorization: string | undefined;
|
|
128
|
+
let accountIdHeader: string | undefined;
|
|
129
|
+
|
|
130
|
+
const { deps } = createDeps({
|
|
131
|
+
env: { OPENAI_CODEX_OAUTH_TOKEN: "c-token", OPENAI_CODEX_ACCOUNT_ID: "acct_123" },
|
|
132
|
+
fetch: async (_url, init) => {
|
|
133
|
+
authorization = (init as any)?.headers?.Authorization;
|
|
134
|
+
accountIdHeader = (init as any)?.headers?.["ChatGPT-Account-Id"];
|
|
135
|
+
return createJsonResponse({
|
|
136
|
+
rate_limit: {
|
|
137
|
+
primary_window: { reset_at: Math.floor(Date.now() / 1000) + 3600, limit_window_seconds: 10800, used_percent: 12 },
|
|
138
|
+
secondary_window: { reset_at: Math.floor(Date.now() / 1000) + 86400, limit_window_seconds: 86400, used_percent: 34 },
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const usage = await provider.fetchUsage(deps);
|
|
145
|
+
assert.equal(authorization, "Bearer c-token");
|
|
146
|
+
assert.equal(accountIdHeader, "acct_123");
|
|
147
|
+
assertWindow(usage, "3h");
|
|
148
|
+
assertWindow(usage, "Day");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("zai reads token from ZAI_API_KEY env var", async () => {
|
|
152
|
+
const provider = new ZaiProvider();
|
|
153
|
+
let authorization: string | undefined;
|
|
154
|
+
|
|
155
|
+
const { deps } = createDeps({
|
|
156
|
+
env: { ZAI_API_KEY: "z-token" },
|
|
157
|
+
fetch: async (_url, init) => {
|
|
158
|
+
authorization = (init as any)?.headers?.Authorization;
|
|
159
|
+
return createJsonResponse({ success: true, code: 200, data: { limits: [] } });
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
await provider.fetchUsage(deps);
|
|
164
|
+
assert.equal(authorization, "Bearer z-token");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("copilot handles missing quota snapshots", async () => {
|
|
168
|
+
const provider = new CopilotProvider();
|
|
169
|
+
const { deps, files } = createDeps({
|
|
170
|
+
fetch: async () => createJsonResponse({}),
|
|
171
|
+
});
|
|
172
|
+
withAuth(files, { "github-copilot": { refresh: "token" } }, deps.homedir());
|
|
173
|
+
|
|
174
|
+
const usage = await provider.fetchUsage(deps);
|
|
175
|
+
assert.equal(usage.windows.length, 0);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("copilot parses quotas and requests", async () => {
|
|
179
|
+
const provider = new CopilotProvider();
|
|
180
|
+
const { deps, files } = createDeps({
|
|
181
|
+
fetch: async () => createJsonResponse({
|
|
182
|
+
quota_reset_date_utc: "2026-01-01T00:00:00Z",
|
|
183
|
+
quota_snapshots: {
|
|
184
|
+
premium_interactions: {
|
|
185
|
+
percent_remaining: 70,
|
|
186
|
+
remaining: 10,
|
|
187
|
+
entitlement: 50,
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
}),
|
|
191
|
+
});
|
|
192
|
+
withAuth(files, { "github-copilot": { refresh: "token" } }, deps.homedir());
|
|
193
|
+
|
|
194
|
+
const usage = await provider.fetchUsage(deps);
|
|
195
|
+
assertWindow(usage, "Month");
|
|
196
|
+
assert.equal(usage.windows[0]?.usedPercent, 30);
|
|
197
|
+
assert.equal(usage.requestsRemaining, 10);
|
|
198
|
+
assert.equal(usage.requestsEntitlement, 50);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("copilot reports http errors", async () => {
|
|
202
|
+
const provider = new CopilotProvider();
|
|
203
|
+
const { deps, files } = createDeps({
|
|
204
|
+
fetch: async () => createJsonResponse({}, { ok: false, status: 500 }),
|
|
205
|
+
});
|
|
206
|
+
withAuth(files, { "github-copilot": { refresh: "token" } }, deps.homedir());
|
|
207
|
+
|
|
208
|
+
const usage = await provider.fetchUsage(deps);
|
|
209
|
+
assert.equal(usage.error?.code, "HTTP_ERROR");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("gemini handles empty buckets", async () => {
|
|
213
|
+
const provider = new GeminiProvider();
|
|
214
|
+
const { deps, files } = createDeps({
|
|
215
|
+
fetch: async () => createJsonResponse({ buckets: [] }),
|
|
216
|
+
});
|
|
217
|
+
withAuth(files, { "google-gemini-cli": { access: "token" } }, deps.homedir());
|
|
218
|
+
|
|
219
|
+
const usage = await provider.fetchUsage(deps);
|
|
220
|
+
assert.equal(usage.windows.length, 0);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("gemini aggregates pro and flash quotas", async () => {
|
|
224
|
+
const provider = new GeminiProvider();
|
|
225
|
+
const { deps, files } = createDeps({
|
|
226
|
+
fetch: async () => createJsonResponse({
|
|
227
|
+
buckets: [
|
|
228
|
+
{ modelId: "Gemini Pro", remainingFraction: 0.2 },
|
|
229
|
+
{ modelId: "Gemini Flash", remainingFraction: 0.6 },
|
|
230
|
+
],
|
|
231
|
+
}),
|
|
232
|
+
});
|
|
233
|
+
withAuth(files, { "google-gemini-cli": { access: "token" } }, deps.homedir());
|
|
234
|
+
|
|
235
|
+
const usage = await provider.fetchUsage(deps);
|
|
236
|
+
assertWindow(usage, "Pro");
|
|
237
|
+
assertWindow(usage, "Flash");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("antigravity falls back to unknown model labels", async () => {
|
|
241
|
+
const provider = new AntigravityProvider();
|
|
242
|
+
const { deps, files } = createDeps({
|
|
243
|
+
fetch: async () => createJsonResponse({
|
|
244
|
+
models: {
|
|
245
|
+
"1": { displayName: "Unknown A", quotaInfo: { remainingFraction: 0.8 } },
|
|
246
|
+
"2": { displayName: "Unknown B", quotaInfo: { remainingFraction: 0.7 } },
|
|
247
|
+
},
|
|
248
|
+
}),
|
|
249
|
+
});
|
|
250
|
+
withAuth(files, { "google-antigravity": { access: "token" } }, deps.homedir());
|
|
251
|
+
|
|
252
|
+
const usage = await provider.fetchUsage(deps);
|
|
253
|
+
assert.ok(usage.windows.some((window) => window.label === "Unknown A"));
|
|
254
|
+
assert.ok(usage.windows.some((window) => window.label === "Unknown B"));
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("codex formats primary and secondary windows", async () => {
|
|
258
|
+
const provider = new CodexProvider();
|
|
259
|
+
const { deps, files } = createDeps({
|
|
260
|
+
fetch: async () => createJsonResponse({
|
|
261
|
+
rate_limit: {
|
|
262
|
+
primary_window: {
|
|
263
|
+
reset_at: Math.floor(Date.now() / 1000) + 3600,
|
|
264
|
+
limit_window_seconds: 18000,
|
|
265
|
+
used_percent: 12,
|
|
266
|
+
},
|
|
267
|
+
secondary_window: {
|
|
268
|
+
reset_at: Math.floor(Date.now() / 1000) + 86400,
|
|
269
|
+
limit_window_seconds: 86400,
|
|
270
|
+
used_percent: 30,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
}),
|
|
274
|
+
});
|
|
275
|
+
withAuth(files, { "openai-codex": { access: "token", accountId: "acct" } }, deps.homedir());
|
|
276
|
+
|
|
277
|
+
const usage = await provider.fetchUsage(deps);
|
|
278
|
+
assertWindow(usage, "5h");
|
|
279
|
+
assertWindow(usage, "Day");
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("codex includes additional rate limits for model-specific usage", async () => {
|
|
283
|
+
const provider = new CodexProvider();
|
|
284
|
+
const { deps, files } = createDeps({
|
|
285
|
+
fetch: async () => createJsonResponse({
|
|
286
|
+
rate_limit: {
|
|
287
|
+
primary_window: {
|
|
288
|
+
reset_at: Math.floor(Date.now() / 1000) + 3600,
|
|
289
|
+
limit_window_seconds: 3600,
|
|
290
|
+
used_percent: 12,
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
additional_rate_limits: [
|
|
294
|
+
{
|
|
295
|
+
limit_name: "GPT-5.3-Codex-Spark",
|
|
296
|
+
rate_limit: {
|
|
297
|
+
primary_window: {
|
|
298
|
+
reset_at: Math.floor(Date.now() / 1000) + 1800,
|
|
299
|
+
limit_window_seconds: 18000,
|
|
300
|
+
used_percent: 1,
|
|
301
|
+
},
|
|
302
|
+
secondary_window: {
|
|
303
|
+
reset_at: Math.floor(Date.now() / 1000) + 1800 + 604_800,
|
|
304
|
+
limit_window_seconds: 604_800,
|
|
305
|
+
used_percent: 2,
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
}),
|
|
311
|
+
});
|
|
312
|
+
withAuth(files, { "openai-codex": { access: "token", accountId: "acct" } }, deps.homedir());
|
|
313
|
+
|
|
314
|
+
const usage = await provider.fetchUsage(deps);
|
|
315
|
+
assertWindow(usage, "1h");
|
|
316
|
+
assertWindow(usage, "GPT-5.3-Codex-Spark 5h");
|
|
317
|
+
assertWindow(usage, "GPT-5.3-Codex-Spark Week");
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("kiro parses percentage and reset date", async () => {
|
|
321
|
+
const provider = new KiroProvider();
|
|
322
|
+
const output = "██████ 12%\nresets on 01/01";
|
|
323
|
+
const { deps } = createDeps({
|
|
324
|
+
execFileSync: (file: string, args: string[]) => {
|
|
325
|
+
if (file === "which" && args[0] === "kiro-cli") return "/usr/local/bin/kiro-cli";
|
|
326
|
+
if (file === "/usr/local/bin/kiro-cli" && args[0] === "whoami") return "user";
|
|
327
|
+
if (file === "/usr/local/bin/kiro-cli" && args[0] === "chat") return output;
|
|
328
|
+
throw new Error(`Unexpected command ${file} ${args.join(" ")}`);
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const usage = await provider.fetchUsage(deps);
|
|
333
|
+
assertWindow(usage, "Credits");
|
|
334
|
+
assert.equal(usage.windows[0]?.usedPercent, 12);
|
|
335
|
+
assert.ok(usage.windows[0]?.resetAt);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("kiro parses credits when percent is missing", async () => {
|
|
339
|
+
const provider = new KiroProvider();
|
|
340
|
+
const output = "(1.5 of 10 covered in plan) resets on 12/31";
|
|
341
|
+
const { deps } = createDeps({
|
|
342
|
+
execFileSync: (file: string, args: string[]) => {
|
|
343
|
+
if (file === "which" && args[0] === "kiro-cli") return "/usr/local/bin/kiro-cli";
|
|
344
|
+
if (file === "/usr/local/bin/kiro-cli" && args[0] === "whoami") return "user";
|
|
345
|
+
if (file === "/usr/local/bin/kiro-cli" && args[0] === "chat") return output;
|
|
346
|
+
throw new Error(`Unexpected command ${file} ${args.join(" ")}`);
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const usage = await provider.fetchUsage(deps);
|
|
351
|
+
assert.equal(Math.round(usage.windows[0]?.usedPercent ?? 0), 15);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("zai reports api errors and parses limits", async () => {
|
|
355
|
+
const provider = new ZaiProvider();
|
|
356
|
+
const home = "/home/test";
|
|
357
|
+
const authPath = getAuthPath(home);
|
|
358
|
+
|
|
359
|
+
const { deps, files } = createDeps({
|
|
360
|
+
fetch: async () => createJsonResponse({ success: false, code: 500, msg: "Bad" }),
|
|
361
|
+
homedir: home,
|
|
362
|
+
});
|
|
363
|
+
files.set(authPath, JSON.stringify({ "z-ai": { access: "token" } }));
|
|
364
|
+
const errorUsage = await provider.fetchUsage(deps);
|
|
365
|
+
assert.equal(errorUsage.error?.code, "API_ERROR");
|
|
366
|
+
|
|
367
|
+
const { deps: okDeps, files: okFiles } = createDeps({
|
|
368
|
+
fetch: async () => createJsonResponse({
|
|
369
|
+
success: true,
|
|
370
|
+
code: 200,
|
|
371
|
+
data: {
|
|
372
|
+
limits: [
|
|
373
|
+
{ type: "TOKENS_LIMIT", percentage: 12, nextResetTime: "2026-01-01T00:00:00Z" },
|
|
374
|
+
{ type: "TIME_LIMIT", percentage: 34, nextResetTime: "2026-02-01T00:00:00Z" },
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
}),
|
|
378
|
+
homedir: home,
|
|
379
|
+
});
|
|
380
|
+
okFiles.set(authPath, JSON.stringify({ "zai": { access: "token" } }));
|
|
381
|
+
|
|
382
|
+
const usage = await provider.fetchUsage(okDeps);
|
|
383
|
+
assertWindow(usage, "Tokens");
|
|
384
|
+
assertWindow(usage, "Monthly");
|
|
385
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { fetchProviderStatus } from "../src/status.js";
|
|
4
|
+
import { createDeps, createJsonResponse } from "./helpers.js";
|
|
5
|
+
|
|
6
|
+
const STATUS_OPENAI_SUMMARY = "https://status.openai.com/api/v2/summary.json";
|
|
7
|
+
const STATUS_GITHUB = "https://www.githubstatus.com/api/v2/status.json";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
test("fetchProviderStatus uses component-specific status for codex", async () => {
|
|
11
|
+
const urls: string[] = [];
|
|
12
|
+
const { deps } = createDeps({
|
|
13
|
+
fetch: async (url) => {
|
|
14
|
+
urls.push(url as string);
|
|
15
|
+
return createJsonResponse({
|
|
16
|
+
status: {
|
|
17
|
+
indicator: "none",
|
|
18
|
+
description: "All Systems Operational",
|
|
19
|
+
},
|
|
20
|
+
components: [
|
|
21
|
+
{ id: "other", name: "ChatGPT API", status: "operational" },
|
|
22
|
+
{ id: "01JVCV8YSWZFRSM1G5CVP253SK", name: "Codex", status: "partial_outage" },
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const status = await fetchProviderStatus("codex", deps);
|
|
29
|
+
assert.equal(urls[0], STATUS_OPENAI_SUMMARY);
|
|
30
|
+
assert.equal(status.indicator, "major");
|
|
31
|
+
assert.equal(status.description, "Codex: Partial outage");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
test("fetchProviderStatus falls back to summary status when component is missing", async () => {
|
|
36
|
+
const urls: string[] = [];
|
|
37
|
+
const { deps } = createDeps({
|
|
38
|
+
fetch: async (url) => {
|
|
39
|
+
urls.push(url as string);
|
|
40
|
+
return createJsonResponse({
|
|
41
|
+
status: {
|
|
42
|
+
indicator: "minor",
|
|
43
|
+
description: "Minor global issue",
|
|
44
|
+
},
|
|
45
|
+
components: [{ id: "other", name: "ChatGPT API", status: "partial_outage" }],
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const status = await fetchProviderStatus("codex", deps);
|
|
51
|
+
assert.equal(urls[0], STATUS_OPENAI_SUMMARY);
|
|
52
|
+
assert.equal(status.indicator, "minor");
|
|
53
|
+
assert.equal(status.description, "Minor global issue");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
test("fetchProviderStatus uses status endpoint for providers without component filtering", async () => {
|
|
58
|
+
const urls: string[] = [];
|
|
59
|
+
const { deps } = createDeps({
|
|
60
|
+
fetch: async (url) => {
|
|
61
|
+
urls.push(url as string);
|
|
62
|
+
return createJsonResponse({ status: { indicator: "critical", description: "Github outage" } });
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const status = await fetchProviderStatus("copilot", deps);
|
|
67
|
+
assert.equal(urls[0], STATUS_GITHUB);
|
|
68
|
+
assert.equal(status.indicator, "critical");
|
|
69
|
+
assert.equal(status.description, "Github outage");
|
|
70
|
+
});
|