@narumitw/pi-usage 0.61.2 → 0.62.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 +38 -6
- package/dist/chunks/chunk-WEXCFYZX.ts +3005 -0
- package/dist/chunks/chunk-WEXCFYZX.ts.map +7 -0
- package/dist/chunks/openai-companion-auth-Q456G46T.ts +85 -0
- package/dist/chunks/openai-companion-auth-Q456G46T.ts.map +7 -0
- package/dist/chunks/openai-companion-usage-Y3WTMUB2.ts +120 -0
- package/dist/chunks/openai-companion-usage-Y3WTMUB2.ts.map +7 -0
- package/dist/index.ts +544 -3309
- package/dist/index.ts.map +4 -4
- package/docs/providers.md +29 -7
- package/package.json +4 -4
- package/src/format.ts +29 -1
- package/src/openai-companion-auth.ts +103 -0
- package/src/providers/openai-chatgpt.ts +24 -9
- package/src/providers/openai-companion-usage.ts +146 -0
- package/src/query.ts +5 -1
- package/src/settings.ts +5 -0
- package/src/types.ts +4 -0
- package/src/usage-settings-ui.ts +51 -9
- package/src/usage.ts +86 -4
package/dist/index.ts
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
2
|
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
|
+
import {
|
|
4
|
+
AUTH_FINGERPRINT_SALT,
|
|
5
|
+
SUPPORTED_ADAPTERS,
|
|
6
|
+
UnsupportedOpenAIUsageAuthError,
|
|
7
|
+
UsageCache,
|
|
8
|
+
XAI_ADAPTER,
|
|
9
|
+
abortError,
|
|
10
|
+
adapterForProvider,
|
|
11
|
+
awaitWithDeadline,
|
|
12
|
+
createFireworksAdapter,
|
|
13
|
+
createOAuthCredentialCandidateReader,
|
|
14
|
+
createUsageTargetSelectOptions,
|
|
15
|
+
errorMessage,
|
|
16
|
+
fallbackOAuthCredentialCandidates,
|
|
17
|
+
fetchProviderJson,
|
|
18
|
+
fingerprintResolvedAuth,
|
|
19
|
+
isBoundedTargetId,
|
|
20
|
+
isFireworksAccountId,
|
|
21
|
+
isStaleExtensionContextError,
|
|
22
|
+
listUsageTargets,
|
|
23
|
+
miniMaxUsageKind,
|
|
24
|
+
normalizeBasetenBillingUsagePayload,
|
|
25
|
+
normalizeCodexBackendPayload,
|
|
26
|
+
normalizeDeepSeekBalancePayload,
|
|
27
|
+
normalizeFireworksAccountsPayload,
|
|
28
|
+
normalizeFireworksBillingSummaryPayload,
|
|
29
|
+
normalizeGitHubCopilotUsagePayload,
|
|
30
|
+
normalizeKimiCodingUsagePayload,
|
|
31
|
+
normalizeMiniMaxUsagePayload,
|
|
32
|
+
normalizeMoonshotBalancePayload,
|
|
33
|
+
normalizeOpenCodeZenPayload,
|
|
34
|
+
normalizeOpenRouterKeyPayload,
|
|
35
|
+
normalizeUsageTargets,
|
|
36
|
+
normalizeVercelAIGatewayCreditsPayload,
|
|
37
|
+
normalizeXaiBillingPayload,
|
|
38
|
+
normalizeZaiQuotaPayload,
|
|
39
|
+
normalizeZaiSubscriptionPayload,
|
|
40
|
+
providerIsConfigured,
|
|
41
|
+
queryProviderUsage,
|
|
42
|
+
redactUsageError,
|
|
43
|
+
resolveUsageAuth,
|
|
44
|
+
resolveUsageTarget,
|
|
45
|
+
runWithConcurrency,
|
|
46
|
+
sanitizeDisplayText,
|
|
47
|
+
usageAdapters
|
|
48
|
+
} from "./chunks/chunk-WEXCFYZX.ts";
|
|
3
49
|
|
|
4
50
|
// src/codex-fast.ts
|
|
5
51
|
import { calculateCost, hasApi } from "@earendil-works/pi-ai";
|
|
@@ -17,3025 +63,84 @@ function codexFastAvailability(model, enabled) {
|
|
|
17
63
|
if (!isOfficialCodexModel(model)) {
|
|
18
64
|
return {
|
|
19
65
|
kind: "unavailable",
|
|
20
|
-
reason: "Fast mode requires the official OpenAI Codex Responses endpoint."
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
if (!CODEX_FAST_MODEL_IDS.has(model.id)) {
|
|
24
|
-
return {
|
|
25
|
-
kind: "unavailable",
|
|
26
|
-
reason: `${model.id} does not advertise Codex Fast support.`
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
return { kind: "available", enabled };
|
|
30
|
-
}
|
|
31
|
-
function codexFastIsEffective(model, enabled) {
|
|
32
|
-
return codexFastAvailability(model, enabled).kind === "available" && enabled;
|
|
33
|
-
}
|
|
34
|
-
function codexFastRequestTier(model, enabled) {
|
|
35
|
-
if (!isOfficialCodexModel(model)) return void 0;
|
|
36
|
-
return enabled && CODEX_FAST_MODEL_IDS.has(model.id) ? CODEX_FAST_SERVICE_TIER : CODEX_STANDARD_SERVICE_TIER;
|
|
37
|
-
}
|
|
38
|
-
function rewriteCodexFastPayload(payload, model, enabled) {
|
|
39
|
-
const serviceTier = codexFastRequestTier(model, enabled);
|
|
40
|
-
if (!serviceTier || !isRecord(payload)) return void 0;
|
|
41
|
-
return { ...payload, service_tier: serviceTier };
|
|
42
|
-
}
|
|
43
|
-
function correctCodexFastMessageCost(message, model, fastRequested) {
|
|
44
|
-
if (!codexFastIsEffective(model, fastRequested) || !isRecord(message) || message.role !== "assistant" || message.provider !== model?.provider || message.model !== model?.id) {
|
|
45
|
-
return void 0;
|
|
46
|
-
}
|
|
47
|
-
const usage = isRecord(message.usage) ? message.usage : void 0;
|
|
48
|
-
const cost = usage && isRecord(usage.cost) ? usage.cost : void 0;
|
|
49
|
-
if (!usage || !cost || !hasCompleteUsage(usage) || !isOfficialCodexModel(model)) return void 0;
|
|
50
|
-
const correctedUsage = structuredClone(usage);
|
|
51
|
-
calculateCost(model, correctedUsage);
|
|
52
|
-
const multiplier = model.id === "gpt-5.5" ? 2.5 : 2;
|
|
53
|
-
const correctedCost = correctedUsage.cost;
|
|
54
|
-
for (const key of ["input", "output", "cacheRead", "cacheWrite", "total"]) {
|
|
55
|
-
correctedCost[key] *= multiplier;
|
|
56
|
-
}
|
|
57
|
-
if (costsEqual(cost, correctedCost)) return void 0;
|
|
58
|
-
return { ...message, usage: correctedUsage };
|
|
59
|
-
}
|
|
60
|
-
function codexFastStatusLabel(status, enabled) {
|
|
61
|
-
if (!enabled || !/^codex(?:\s|$)/u.test(status)) return status;
|
|
62
|
-
return status === "codex" ? "codex fast" : `codex fast${status.slice("codex".length)}`;
|
|
63
|
-
}
|
|
64
|
-
function isOfficialCodexModel(model) {
|
|
65
|
-
if (model?.provider !== "openai-codex" || !hasApi(model, "openai-codex-responses")) {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
try {
|
|
69
|
-
return new URL(model.baseUrl).origin === "https://chatgpt.com";
|
|
70
|
-
} catch {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function isRecord(value) {
|
|
75
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
76
|
-
}
|
|
77
|
-
function hasCompleteUsage(value) {
|
|
78
|
-
return ["input", "output", "cacheRead", "cacheWrite"].every(
|
|
79
|
-
(key) => typeof value[key] === "number" && Number.isFinite(value[key])
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
function costsEqual(left, right) {
|
|
83
|
-
return ["input", "output", "cacheRead", "cacheWrite", "total"].every((key) => left[key] === right[key]);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// src/codex-resets.ts
|
|
87
|
-
import { readStoredCredential as readStoredCredential3 } from "@earendil-works/pi-coding-agent";
|
|
88
|
-
|
|
89
|
-
// src/core.ts
|
|
90
|
-
import { createHmac } from "node:crypto";
|
|
91
|
-
var UsageCache = class {
|
|
92
|
-
entries = /* @__PURE__ */ new Map();
|
|
93
|
-
ttlMs;
|
|
94
|
-
maxEntries;
|
|
95
|
-
constructor(ttlMs, maxEntries = 32) {
|
|
96
|
-
if (!Number.isFinite(ttlMs) || ttlMs <= 0) throw new Error("Cache TTL must be positive.");
|
|
97
|
-
if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
|
|
98
|
-
throw new Error("Cache entry limit must be a positive integer.");
|
|
99
|
-
}
|
|
100
|
-
this.ttlMs = ttlMs;
|
|
101
|
-
this.maxEntries = maxEntries;
|
|
102
|
-
}
|
|
103
|
-
get size() {
|
|
104
|
-
return this.entries.size;
|
|
105
|
-
}
|
|
106
|
-
get(providerId, fingerprint, now = Date.now()) {
|
|
107
|
-
this.sweepExpired(now);
|
|
108
|
-
return this.entries.get(cacheKey(providerId, fingerprint))?.report;
|
|
109
|
-
}
|
|
110
|
-
set(providerId, fingerprint, report2, now = Date.now()) {
|
|
111
|
-
this.sweepExpired(now);
|
|
112
|
-
const key = cacheKey(providerId, fingerprint);
|
|
113
|
-
this.entries.delete(key);
|
|
114
|
-
while (this.entries.size >= this.maxEntries) {
|
|
115
|
-
const oldest = this.entries.keys().next().value;
|
|
116
|
-
if (oldest === void 0) break;
|
|
117
|
-
this.entries.delete(oldest);
|
|
118
|
-
}
|
|
119
|
-
this.entries.set(key, { createdAt: now, report: report2 });
|
|
120
|
-
}
|
|
121
|
-
delete(providerId, fingerprint) {
|
|
122
|
-
this.entries.delete(cacheKey(providerId, fingerprint));
|
|
123
|
-
}
|
|
124
|
-
clearProvider(providerId) {
|
|
125
|
-
for (const key of this.entries.keys()) {
|
|
126
|
-
if (key.startsWith(`${providerId}:`)) this.entries.delete(key);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
clear() {
|
|
130
|
-
this.entries.clear();
|
|
131
|
-
}
|
|
132
|
-
sweepExpired(now) {
|
|
133
|
-
for (const [key, entry] of this.entries) {
|
|
134
|
-
if (now - entry.createdAt >= this.ttlMs) this.entries.delete(key);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
function fingerprintResolvedAuth(auth, salt) {
|
|
139
|
-
const headers = Object.entries(auth.headers ?? {}).map(([name, value]) => [name.toLowerCase(), value]).sort(([left], [right]) => left.localeCompare(right));
|
|
140
|
-
const env = Object.entries(auth.env ?? {}).sort(([left], [right]) => left.localeCompare(right));
|
|
141
|
-
const providerHeaders = Object.entries(auth.providerAuth?.headers ?? {}).map(([name, value]) => [name.toLowerCase(), value]).sort(([left], [right]) => left.localeCompare(right));
|
|
142
|
-
const canonical = JSON.stringify({
|
|
143
|
-
apiKey: auth.apiKey ?? "",
|
|
144
|
-
headers,
|
|
145
|
-
baseUrl: auth.baseUrl ?? "",
|
|
146
|
-
env,
|
|
147
|
-
source: auth.source ?? "",
|
|
148
|
-
providerAuth: {
|
|
149
|
-
apiKey: auth.providerAuth?.apiKey ?? "",
|
|
150
|
-
headers: providerHeaders,
|
|
151
|
-
baseUrl: auth.providerAuth?.baseUrl ?? ""
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
return createHmac("sha256", salt).update(canonical).digest("hex");
|
|
155
|
-
}
|
|
156
|
-
async function runWithConcurrency(items, limit, worker, signal) {
|
|
157
|
-
if (signal.aborted) throw abortError();
|
|
158
|
-
if (!Number.isSafeInteger(limit) || limit < 1) throw new Error("Concurrency limit must be positive.");
|
|
159
|
-
const results = new Array(items.length);
|
|
160
|
-
let nextIndex = 0;
|
|
161
|
-
const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
162
|
-
while (nextIndex < items.length) {
|
|
163
|
-
if (signal.aborted) throw abortError();
|
|
164
|
-
const index = nextIndex;
|
|
165
|
-
nextIndex += 1;
|
|
166
|
-
try {
|
|
167
|
-
results[index] = {
|
|
168
|
-
status: "fulfilled",
|
|
169
|
-
value: await worker(items[index], index, signal)
|
|
170
|
-
};
|
|
171
|
-
} catch (reason) {
|
|
172
|
-
results[index] = { status: "rejected", reason };
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
await Promise.all(runners);
|
|
177
|
-
if (signal.aborted) throw abortError();
|
|
178
|
-
return results;
|
|
179
|
-
}
|
|
180
|
-
async function awaitWithDeadline(operation, signal, timeoutMs, description) {
|
|
181
|
-
if (signal.aborted) throw abortError();
|
|
182
|
-
const controller = new AbortController();
|
|
183
|
-
const abortFromCaller = () => controller.abort();
|
|
184
|
-
signal.addEventListener("abort", abortFromCaller, { once: true });
|
|
185
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
186
|
-
try {
|
|
187
|
-
return await Promise.race([
|
|
188
|
-
operation,
|
|
189
|
-
new Promise((_resolve, reject) => {
|
|
190
|
-
controller.signal.addEventListener(
|
|
191
|
-
"abort",
|
|
192
|
-
() => {
|
|
193
|
-
reject(
|
|
194
|
-
signal.aborted ? abortError() : Object.assign(new Error(`Timed out after ${Math.round(timeoutMs / 1e3)}s ${description}.`), {
|
|
195
|
-
name: "TimeoutError"
|
|
196
|
-
})
|
|
197
|
-
);
|
|
198
|
-
},
|
|
199
|
-
{ once: true }
|
|
200
|
-
);
|
|
201
|
-
})
|
|
202
|
-
]);
|
|
203
|
-
} finally {
|
|
204
|
-
clearTimeout(timeout);
|
|
205
|
-
signal.removeEventListener("abort", abortFromCaller);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
function sanitizeDisplayText(value, maxChars = 160) {
|
|
209
|
-
let result = "";
|
|
210
|
-
for (let index = 0; index < value.length; ) {
|
|
211
|
-
const codePoint = value.codePointAt(index) ?? 0;
|
|
212
|
-
const character = String.fromCodePoint(codePoint);
|
|
213
|
-
if (codePoint === 27 || codePoint === 155 || codePoint === 157) {
|
|
214
|
-
index = skipTerminalEscape(value, index, codePoint);
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
if (codePoint <= 31 || codePoint >= 127 && codePoint <= 159) {
|
|
218
|
-
if (codePoint === 9 || codePoint === 10 || codePoint === 13) result += " ";
|
|
219
|
-
index += character.length;
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
result += character;
|
|
223
|
-
index += character.length;
|
|
224
|
-
}
|
|
225
|
-
return truncate(result.replace(/\s+/gu, " ").trim(), maxChars);
|
|
226
|
-
}
|
|
227
|
-
function redactUsageError(value, secrets = []) {
|
|
228
|
-
let redacted = value;
|
|
229
|
-
for (const secret of [...new Set(secrets)].filter(Boolean).sort((a, b) => b.length - a.length)) {
|
|
230
|
-
redacted = redacted.replace(new RegExp(escapeRegExp(secret), "g"), "<redacted>");
|
|
231
|
-
}
|
|
232
|
-
redacted = redacted.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer <redacted>").replace(/"(?:access_token|refresh_token|api_key)"\s*:\s*"[^"]+"/gi, (match) => {
|
|
233
|
-
const separator = match.indexOf(":");
|
|
234
|
-
return `${match.slice(0, separator + 1)}"<redacted>"`;
|
|
235
|
-
});
|
|
236
|
-
return sanitizeDisplayText(redacted, 600);
|
|
237
|
-
}
|
|
238
|
-
function errorMessage(error) {
|
|
239
|
-
return sanitizeDisplayText(error instanceof Error ? error.message : String(error), 600);
|
|
240
|
-
}
|
|
241
|
-
function abortError() {
|
|
242
|
-
return Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
243
|
-
}
|
|
244
|
-
function skipTerminalEscape(value, start, codePoint) {
|
|
245
|
-
let index = start + 1;
|
|
246
|
-
const next = value.charCodeAt(index);
|
|
247
|
-
const isOsc = codePoint === 157 || codePoint === 27 && next === 93;
|
|
248
|
-
if (isOsc) {
|
|
249
|
-
if (codePoint === 27) index += 1;
|
|
250
|
-
while (index < value.length) {
|
|
251
|
-
const current = value.charCodeAt(index);
|
|
252
|
-
if (current === 7) return index + 1;
|
|
253
|
-
if (current === 27 && value.charCodeAt(index + 1) === 92) return index + 2;
|
|
254
|
-
index += 1;
|
|
255
|
-
}
|
|
256
|
-
return index;
|
|
257
|
-
}
|
|
258
|
-
const isCsi = codePoint === 155 || codePoint === 27 && next === 91;
|
|
259
|
-
if (isCsi) {
|
|
260
|
-
if (codePoint === 27) index += 1;
|
|
261
|
-
while (index < value.length) {
|
|
262
|
-
const current = value.charCodeAt(index);
|
|
263
|
-
index += 1;
|
|
264
|
-
if (current >= 64 && current <= 126) break;
|
|
265
|
-
}
|
|
266
|
-
return index;
|
|
267
|
-
}
|
|
268
|
-
return Math.min(value.length, start + (codePoint === 27 ? 2 : 1));
|
|
269
|
-
}
|
|
270
|
-
function cacheKey(providerId, fingerprint) {
|
|
271
|
-
return `${providerId}:${fingerprint}`;
|
|
272
|
-
}
|
|
273
|
-
function escapeRegExp(value) {
|
|
274
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
275
|
-
}
|
|
276
|
-
function truncate(value, maxChars) {
|
|
277
|
-
if (value.length <= maxChars) return value;
|
|
278
|
-
return `${value.slice(0, maxChars - 1)}\u2026`;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// src/oauth-credential-source.ts
|
|
282
|
-
import { readStoredCredential } from "@earendil-works/pi-coding-agent";
|
|
283
|
-
var OAUTH_CREDENTIAL_SOURCE_CHANNEL = "oauth:credential-source:v1";
|
|
284
|
-
var OAUTH_CREDENTIAL_READINESS_CHANNEL = "oauth:credential-readiness:v1";
|
|
285
|
-
function createOAuthCredentialCandidateReader(pi, credentialReader = readStoredCredential) {
|
|
286
|
-
const reader = (ctx, providerId) => collectOAuthCredentialCandidates(pi, ctx, providerId, credentialReader);
|
|
287
|
-
reader.waitUntilReady = (ctx, providerId) => waitForOAuthCredentialSources(pi, ctx, providerId);
|
|
288
|
-
return reader;
|
|
289
|
-
}
|
|
290
|
-
function collectOAuthCredentialCandidates(pi, ctx, providerId, credentialReader = readStoredCredential) {
|
|
291
|
-
const candidates = [];
|
|
292
|
-
let collecting = true;
|
|
293
|
-
const request = Object.freeze({
|
|
294
|
-
session: ctx.sessionManager,
|
|
295
|
-
provider: providerId,
|
|
296
|
-
offer(candidate) {
|
|
297
|
-
if (!collecting) return;
|
|
298
|
-
const clone = cloneOAuthCredential(candidate);
|
|
299
|
-
if (clone) candidates.push(clone);
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
try {
|
|
303
|
-
pi.events.emit(OAUTH_CREDENTIAL_SOURCE_CHANNEL, request);
|
|
304
|
-
} catch {
|
|
305
|
-
return { ok: false };
|
|
306
|
-
} finally {
|
|
307
|
-
collecting = false;
|
|
308
|
-
}
|
|
309
|
-
const offeredCount = candidates.length;
|
|
310
|
-
try {
|
|
311
|
-
const fallback = cloneOAuthCredential(credentialReader(providerId));
|
|
312
|
-
if (fallback) candidates.push(fallback);
|
|
313
|
-
} catch {
|
|
314
|
-
}
|
|
315
|
-
return { ok: true, candidates, offeredCount };
|
|
316
|
-
}
|
|
317
|
-
async function waitForOAuthCredentialSources(pi, ctx, providerId) {
|
|
318
|
-
const pending = [];
|
|
319
|
-
let collecting = true;
|
|
320
|
-
const request = Object.freeze({
|
|
321
|
-
session: ctx.sessionManager,
|
|
322
|
-
provider: providerId,
|
|
323
|
-
waitUntil(value) {
|
|
324
|
-
if (!collecting || !(value instanceof Promise)) return;
|
|
325
|
-
pending.push(value);
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
try {
|
|
329
|
-
pi.events.emit(OAUTH_CREDENTIAL_READINESS_CHANNEL, request);
|
|
330
|
-
} catch {
|
|
331
|
-
return false;
|
|
332
|
-
} finally {
|
|
333
|
-
collecting = false;
|
|
334
|
-
}
|
|
335
|
-
try {
|
|
336
|
-
await Promise.all(pending);
|
|
337
|
-
return true;
|
|
338
|
-
} catch {
|
|
339
|
-
return false;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
function fallbackOAuthCredentialCandidates(providerId, credentialReader) {
|
|
343
|
-
try {
|
|
344
|
-
const credential = cloneOAuthCredential(credentialReader(providerId));
|
|
345
|
-
return { ok: true, candidates: credential ? [credential] : [], offeredCount: 0 };
|
|
346
|
-
} catch {
|
|
347
|
-
return { ok: true, candidates: [], offeredCount: 0 };
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
function cloneOAuthCredential(value) {
|
|
351
|
-
try {
|
|
352
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
353
|
-
const clone = structuredClone(value);
|
|
354
|
-
if (!clone || typeof clone !== "object" || Array.isArray(clone)) return void 0;
|
|
355
|
-
if (clone.type !== "oauth") return void 0;
|
|
356
|
-
if (typeof clone.access !== "string" || !clone.access) return void 0;
|
|
357
|
-
if (typeof clone.refresh !== "string" || !clone.refresh) return void 0;
|
|
358
|
-
if (typeof clone.expires !== "number" || !Number.isFinite(clone.expires)) return void 0;
|
|
359
|
-
return clone;
|
|
360
|
-
} catch {
|
|
361
|
-
return void 0;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// src/query.ts
|
|
366
|
-
import { randomBytes } from "node:crypto";
|
|
367
|
-
import { readStoredCredential as readStoredCredential2 } from "@earendil-works/pi-coding-agent";
|
|
368
|
-
|
|
369
|
-
// src/providers/baseten.ts
|
|
370
|
-
var DECIMAL_AMOUNT = /^(?:0|[1-9]\d*)(?:\.\d+)?$/u;
|
|
371
|
-
function normalizeBasetenBillingUsagePayload(payload, capturedAt) {
|
|
372
|
-
if (payload.model_apis_usage === void 0 || payload.model_apis_usage === null) {
|
|
373
|
-
return report(capturedAt, [], ["Baseten returned no Model APIs usage for the last 30 days."]);
|
|
374
|
-
}
|
|
375
|
-
const usage = asObject(payload.model_apis_usage);
|
|
376
|
-
if (!usage) throw new Error("Baseten Model APIs usage was not an object.");
|
|
377
|
-
const metrics = [
|
|
378
|
-
metric("gross-usage", "Gross usage", usage.total),
|
|
379
|
-
metric("credits-used", "Credits used", usage.credits_used),
|
|
380
|
-
metric("net-subtotal", "Net subtotal", usage.subtotal)
|
|
381
|
-
];
|
|
382
|
-
return report(capturedAt, metrics);
|
|
383
|
-
}
|
|
384
|
-
function report(capturedAt, metrics, notes) {
|
|
385
|
-
return {
|
|
386
|
-
providerId: "baseten",
|
|
387
|
-
providerName: "Baseten",
|
|
388
|
-
capturedAt,
|
|
389
|
-
source: "baseten-billing-usage-summary",
|
|
390
|
-
semantics: { kind: "api-key", label: "Organization Model APIs spend" },
|
|
391
|
-
buckets: [],
|
|
392
|
-
metrics,
|
|
393
|
-
...notes ? { notes } : {}
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
function metric(id, label, value) {
|
|
397
|
-
const amount2 = decimalAmount(value, label);
|
|
398
|
-
return { id, label, value: amount2, unit: "currency", currency: "USD" };
|
|
399
|
-
}
|
|
400
|
-
function decimalAmount(value, label) {
|
|
401
|
-
const normalized = typeof value === "number" && Number.isFinite(value) ? String(value) : value;
|
|
402
|
-
if (typeof normalized !== "string" || normalized.length > 64 || !DECIMAL_AMOUNT.test(normalized)) {
|
|
403
|
-
throw new Error(`Baseten ${label.toLowerCase()} was not a valid nonnegative amount.`);
|
|
404
|
-
}
|
|
405
|
-
return normalized;
|
|
406
|
-
}
|
|
407
|
-
function asObject(value) {
|
|
408
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
409
|
-
return value;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// src/providers/codex.ts
|
|
413
|
-
function normalizeCodexBackendPayload(payload, capturedAt) {
|
|
414
|
-
const buckets = [];
|
|
415
|
-
normalizeRateLimitGroup(buckets, "codex", "Codex", payload.rate_limit, false);
|
|
416
|
-
const additional = Array.isArray(payload.additional_rate_limits) ? payload.additional_rate_limits : [];
|
|
417
|
-
for (const item of additional) {
|
|
418
|
-
const value = asObject2(item);
|
|
419
|
-
const id = asString(value?.metered_feature) ?? asString(value?.limit_name);
|
|
420
|
-
if (!value || !id) continue;
|
|
421
|
-
try {
|
|
422
|
-
normalizeRateLimitGroup(buckets, id, asString(value.limit_name) ?? id, value.rate_limit, true);
|
|
423
|
-
} catch {
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
const metrics = [];
|
|
427
|
-
const credits = asObject2(payload.credits);
|
|
428
|
-
if (credits?.has_credits === true) {
|
|
429
|
-
if (credits.unlimited === true) {
|
|
430
|
-
metrics.push({ id: "credits", label: "Credits", value: "unlimited" });
|
|
431
|
-
} else {
|
|
432
|
-
const balance = asNumber(credits.balance);
|
|
433
|
-
if (balance !== void 0) {
|
|
434
|
-
metrics.push({ id: "credits", label: "Credits", value: balance, unit: "count" });
|
|
435
|
-
} else {
|
|
436
|
-
metrics.push({ id: "credits", label: "Credits", value: "available" });
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
} else if (credits?.has_credits === false) {
|
|
440
|
-
metrics.push({ id: "credits", label: "Credits", value: "none" });
|
|
441
|
-
}
|
|
442
|
-
const resetCredits = asObject2(payload.rate_limit_reset_credits);
|
|
443
|
-
const resetCount = asNonnegativeInteger(resetCredits?.available_count);
|
|
444
|
-
if (resetCount !== void 0) {
|
|
445
|
-
metrics.push({
|
|
446
|
-
id: "reset-credits",
|
|
447
|
-
label: "Usage limit resets",
|
|
448
|
-
value: resetCount,
|
|
449
|
-
unit: "count"
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
if (buckets.length === 0 && metrics.length === 0) {
|
|
453
|
-
throw new Error("Codex usage endpoint returned no displayable usage data.");
|
|
454
|
-
}
|
|
455
|
-
const planType = asString(payload.plan_type);
|
|
456
|
-
return {
|
|
457
|
-
providerId: "openai-codex",
|
|
458
|
-
providerName: "OpenAI Codex",
|
|
459
|
-
capturedAt,
|
|
460
|
-
source: "codex-pi-auth",
|
|
461
|
-
semantics: {
|
|
462
|
-
kind: "consumer-subscription",
|
|
463
|
-
label: "ChatGPT subscription limits"
|
|
464
|
-
},
|
|
465
|
-
buckets,
|
|
466
|
-
metrics,
|
|
467
|
-
...planType ? { notes: [`Plan: ${planType}`] } : {}
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
function normalizeRateLimitGroup(buckets, groupId, groupLabel, raw, optional) {
|
|
471
|
-
if (raw === void 0 || raw === null) return;
|
|
472
|
-
const details = asObject2(raw);
|
|
473
|
-
if (!details) {
|
|
474
|
-
if (optional) return;
|
|
475
|
-
throw new Error("Codex rate limit was not an object.");
|
|
476
|
-
}
|
|
477
|
-
addWindow(buckets, groupId, groupLabel, "primary", details.primary_window);
|
|
478
|
-
addWindow(buckets, groupId, groupLabel, "secondary", details.secondary_window);
|
|
479
|
-
}
|
|
480
|
-
function addWindow(buckets, groupId, groupLabel, position, raw) {
|
|
481
|
-
if (raw === void 0 || raw === null) return;
|
|
482
|
-
const value = asObject2(raw);
|
|
483
|
-
if (!value) throw new Error("Codex rate-limit window was not an object.");
|
|
484
|
-
const used = asNumber(value.used_percent);
|
|
485
|
-
if (used === void 0) return;
|
|
486
|
-
const seconds = asNumber(value.limit_window_seconds);
|
|
487
|
-
const resetsAt = asNumber(value.reset_at);
|
|
488
|
-
buckets.push({
|
|
489
|
-
id: `${groupId}:${position}`,
|
|
490
|
-
label: position === "primary" ? "Primary limit" : "Secondary limit",
|
|
491
|
-
groupId,
|
|
492
|
-
groupLabel,
|
|
493
|
-
modelKeys: [groupId, groupLabel],
|
|
494
|
-
used,
|
|
495
|
-
remaining: 100 - clampPercent(used),
|
|
496
|
-
limit: 100,
|
|
497
|
-
unit: "percent",
|
|
498
|
-
...seconds !== void 0 && seconds > 0 ? { windowMinutes: Math.ceil(seconds / 60) } : {},
|
|
499
|
-
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
function asObject2(value) {
|
|
503
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
504
|
-
return value;
|
|
505
|
-
}
|
|
506
|
-
function asString(value) {
|
|
507
|
-
if (typeof value !== "string") return void 0;
|
|
508
|
-
return sanitizeDisplayText(value, 160) || void 0;
|
|
509
|
-
}
|
|
510
|
-
function asNumber(value) {
|
|
511
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
512
|
-
if (typeof value === "string" && value.trim()) {
|
|
513
|
-
const parsed = Number(value);
|
|
514
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
515
|
-
}
|
|
516
|
-
return void 0;
|
|
517
|
-
}
|
|
518
|
-
function asNonnegativeInteger(value) {
|
|
519
|
-
const parsed = asNumber(value);
|
|
520
|
-
if (parsed === void 0 || !Number.isSafeInteger(parsed)) return void 0;
|
|
521
|
-
return Math.max(0, parsed);
|
|
522
|
-
}
|
|
523
|
-
function clampPercent(value) {
|
|
524
|
-
return Math.min(100, Math.max(0, value));
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
// src/providers/deepseek.ts
|
|
528
|
-
var CURRENCIES = ["CNY", "USD"];
|
|
529
|
-
var BALANCE_FIELDS = [
|
|
530
|
-
["total", "Total balance", "total_balance"],
|
|
531
|
-
["granted", "Granted balance", "granted_balance"],
|
|
532
|
-
["topped-up", "Topped-up balance", "topped_up_balance"]
|
|
533
|
-
];
|
|
534
|
-
function normalizeDeepSeekBalancePayload(payload, capturedAt) {
|
|
535
|
-
if (typeof payload.is_available !== "boolean") {
|
|
536
|
-
throw new Error("DeepSeek API balance response availability was not a boolean.");
|
|
537
|
-
}
|
|
538
|
-
if (!Array.isArray(payload.balance_infos) || payload.balance_infos.length === 0) {
|
|
539
|
-
throw new Error("DeepSeek API balance response returned no balance information.");
|
|
540
|
-
}
|
|
541
|
-
const balances = /* @__PURE__ */ new Map();
|
|
542
|
-
for (const raw of payload.balance_infos) {
|
|
543
|
-
const balance = asObject3(raw);
|
|
544
|
-
if (!balance) throw new Error("DeepSeek API balance row was not an object.");
|
|
545
|
-
const currency = deepSeekCurrency(balance.currency);
|
|
546
|
-
if (!currency) throw new Error("DeepSeek API balance row returned an unsupported currency.");
|
|
547
|
-
if (balances.has(currency)) {
|
|
548
|
-
throw new Error(`DeepSeek API balance response repeated ${currency}.`);
|
|
549
|
-
}
|
|
550
|
-
for (const [, label, field] of BALANCE_FIELDS) {
|
|
551
|
-
if (!decimalAmount2(balance[field])) {
|
|
552
|
-
throw new Error(`DeepSeek API balance ${label.toLowerCase()} was not a valid amount.`);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
balances.set(currency, balance);
|
|
556
|
-
}
|
|
557
|
-
const metrics = [
|
|
558
|
-
{
|
|
559
|
-
id: "api-availability",
|
|
560
|
-
label: "API calls",
|
|
561
|
-
value: payload.is_available ? "available" : "unavailable"
|
|
562
|
-
}
|
|
563
|
-
];
|
|
564
|
-
for (const currency of CURRENCIES) {
|
|
565
|
-
const balance = balances.get(currency);
|
|
566
|
-
if (!balance) continue;
|
|
567
|
-
for (const [id, label, field] of BALANCE_FIELDS) {
|
|
568
|
-
metrics.push({
|
|
569
|
-
id: `${currency.toLowerCase()}-${id}`,
|
|
570
|
-
label,
|
|
571
|
-
value: balance[field],
|
|
572
|
-
unit: "currency",
|
|
573
|
-
currency
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
return {
|
|
578
|
-
providerId: "deepseek",
|
|
579
|
-
providerName: "DeepSeek",
|
|
580
|
-
capturedAt,
|
|
581
|
-
source: "deepseek-balance",
|
|
582
|
-
semantics: { kind: "api-key", label: "DeepSeek API balance" },
|
|
583
|
-
buckets: [],
|
|
584
|
-
metrics
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
function asObject3(value) {
|
|
588
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
589
|
-
return value;
|
|
590
|
-
}
|
|
591
|
-
function deepSeekCurrency(value) {
|
|
592
|
-
return CURRENCIES.find((currency) => currency === value);
|
|
593
|
-
}
|
|
594
|
-
function decimalAmount2(value) {
|
|
595
|
-
return typeof value === "string" && value.length <= 64 && /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(value);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// src/providers/fireworks.ts
|
|
599
|
-
var NANOS_PER_UNIT = 1000000000n;
|
|
600
|
-
var CURRENCY_PATTERN = /^[A-Z]{3}$/u;
|
|
601
|
-
var ACCOUNT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$/u;
|
|
602
|
-
var INTEGER_PATTERN = /^-?\d+$/u;
|
|
603
|
-
var INT64_MIN = -(2n ** 63n);
|
|
604
|
-
var INT64_MAX = 2n ** 63n - 1n;
|
|
605
|
-
var MAX_UNITS_CHARS = 20;
|
|
606
|
-
var MAX_NANOS_CHARS = 11;
|
|
607
|
-
var FIREWORKS_BILLING_SUMMARY_ORIGIN = "https://api.fireworks.ai";
|
|
608
|
-
var FIREWORKS_SPEND_WINDOW_DAYS = 30;
|
|
609
|
-
var FIREWORKS_MAX_ACCOUNT_PAGES = 5;
|
|
610
|
-
var SERIES_KEYS = ["serverless", "dedicated", "training", "other"];
|
|
611
|
-
var SERIES_LABELS = {
|
|
612
|
-
serverless: "Serverless",
|
|
613
|
-
dedicated: "Dedicated deployments",
|
|
614
|
-
training: "Training",
|
|
615
|
-
other: "Other"
|
|
616
|
-
};
|
|
617
|
-
function isFireworksAccountId(value) {
|
|
618
|
-
return typeof value === "string" && ACCOUNT_ID_PATTERN.test(value);
|
|
619
|
-
}
|
|
620
|
-
function normalizeFireworksAccountsPayload(payload) {
|
|
621
|
-
if (!Array.isArray(payload.accounts)) {
|
|
622
|
-
throw new Error("Fireworks accounts response did not contain an accounts array.");
|
|
623
|
-
}
|
|
624
|
-
const accounts = [];
|
|
625
|
-
for (const raw of payload.accounts) {
|
|
626
|
-
const account = asObject4(raw);
|
|
627
|
-
if (!account) throw new Error("Fireworks accounts response row was not an object.");
|
|
628
|
-
if (typeof account.name !== "string") {
|
|
629
|
-
throw new Error("Fireworks accounts response omitted the account resource name.");
|
|
630
|
-
}
|
|
631
|
-
const match = /^accounts\/([^/]+)$/u.exec(account.name);
|
|
632
|
-
if (!match || !isFireworksAccountId(match[1])) {
|
|
633
|
-
throw new Error("Fireworks accounts response returned an unsafe account resource name.");
|
|
634
|
-
}
|
|
635
|
-
const accountId = match[1];
|
|
636
|
-
if (accounts.includes(accountId)) {
|
|
637
|
-
throw new Error(`Fireworks accounts response repeated ${accountId}.`);
|
|
638
|
-
}
|
|
639
|
-
accounts.push(accountId);
|
|
640
|
-
}
|
|
641
|
-
return accounts;
|
|
642
|
-
}
|
|
643
|
-
function createFireworksAdapter(fetchProviderJson2) {
|
|
644
|
-
return {
|
|
645
|
-
id: "fireworks",
|
|
646
|
-
displayName: "Fireworks",
|
|
647
|
-
semantics: { kind: "api-key", label: "Fireworks API spend" },
|
|
648
|
-
targets: {
|
|
649
|
-
singularLabel: "account",
|
|
650
|
-
pluralLabel: "accounts",
|
|
651
|
-
async list(auth, signal, timeoutMs, guard) {
|
|
652
|
-
const startedAt = Date.now();
|
|
653
|
-
const accounts = [];
|
|
654
|
-
let pageToken;
|
|
655
|
-
for (let page = 0; page < FIREWORKS_MAX_ACCOUNT_PAGES; page += 1) {
|
|
656
|
-
await guard();
|
|
657
|
-
const payload = await fetchProviderJson2(
|
|
658
|
-
fireworksAccountsUrl(pageToken),
|
|
659
|
-
auth,
|
|
660
|
-
signal,
|
|
661
|
-
remainingTimeout(timeoutMs, startedAt, "fetching Fireworks accounts"),
|
|
662
|
-
"Fireworks accounts endpoint",
|
|
663
|
-
{ redirect: "error" }
|
|
664
|
-
);
|
|
665
|
-
await guard();
|
|
666
|
-
for (const accountId of normalizeFireworksAccountsPayload(payload)) {
|
|
667
|
-
if (accounts.includes(accountId)) {
|
|
668
|
-
throw new Error(`Fireworks accounts listing repeated ${accountId}.`);
|
|
669
|
-
}
|
|
670
|
-
accounts.push(accountId);
|
|
671
|
-
}
|
|
672
|
-
pageToken = fireworksNextPageToken(payload.nextPageToken);
|
|
673
|
-
if (!pageToken) break;
|
|
674
|
-
}
|
|
675
|
-
if (pageToken) {
|
|
676
|
-
throw new Error(`Fireworks account listing exceeded ${FIREWORKS_MAX_ACCOUNT_PAGES} pages.`);
|
|
677
|
-
}
|
|
678
|
-
return accounts.map((id) => ({ id, label: id }));
|
|
679
|
-
}
|
|
680
|
-
},
|
|
681
|
-
async query(auth, signal, timeoutMs, guard, targetId) {
|
|
682
|
-
if (!guard) throw new Error("Fireworks API spend requires request-boundary revalidation.");
|
|
683
|
-
if (!isFireworksAccountId(targetId)) {
|
|
684
|
-
throw new Error("Fireworks billing requires a safe selected account slug.");
|
|
685
|
-
}
|
|
686
|
-
const startedAt = Date.now();
|
|
687
|
-
await guard();
|
|
688
|
-
const billingWindowAt = Date.now();
|
|
689
|
-
const payload = await fetchProviderJson2(
|
|
690
|
-
fireworksBillingSummaryUrl(targetId, billingWindowAt),
|
|
691
|
-
auth,
|
|
692
|
-
signal,
|
|
693
|
-
remainingTimeout(timeoutMs, startedAt, "fetching Fireworks rated spend"),
|
|
694
|
-
"Fireworks billing summary endpoint",
|
|
695
|
-
{ redirect: "error" }
|
|
696
|
-
);
|
|
697
|
-
await guard();
|
|
698
|
-
return normalizeFireworksBillingSummaryPayload(payload, targetId, Date.now());
|
|
699
|
-
}
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
function normalizeFireworksBillingSummaryPayload(payload, accountId, capturedAt) {
|
|
703
|
-
if (!isFireworksAccountId(accountId)) {
|
|
704
|
-
throw new Error("Fireworks billing summary received an unsafe account identifier.");
|
|
705
|
-
}
|
|
706
|
-
if (payload.lineItems !== void 0 && !Array.isArray(payload.lineItems)) {
|
|
707
|
-
throw new Error("Fireworks billing summary lineItems was not an array.");
|
|
708
|
-
}
|
|
709
|
-
const totals = /* @__PURE__ */ new Map();
|
|
710
|
-
for (const raw of payload.lineItems ?? []) {
|
|
711
|
-
const lineItem = asObject4(raw);
|
|
712
|
-
if (!lineItem) throw new Error("Fireworks billing line item was not an object.");
|
|
713
|
-
const cost = moneyAmount(lineItem.totalCost, "line item total cost");
|
|
714
|
-
const series = seriesKey(lineItem.series);
|
|
715
|
-
let amounts = totals.get(cost.currency);
|
|
716
|
-
if (!amounts) {
|
|
717
|
-
amounts = /* @__PURE__ */ new Map();
|
|
718
|
-
totals.set(cost.currency, amounts);
|
|
719
|
-
}
|
|
720
|
-
amounts.set(series, (amounts.get(series) ?? 0n) + cost.amount);
|
|
721
|
-
}
|
|
722
|
-
const metrics = [];
|
|
723
|
-
for (const [currency, amounts] of totals) {
|
|
724
|
-
metrics.push({
|
|
725
|
-
id: `${currency.toLowerCase()}-total`,
|
|
726
|
-
label: "Total spend",
|
|
727
|
-
value: formatMoneyAmount(sumSeries(amounts)),
|
|
728
|
-
unit: "currency",
|
|
729
|
-
currency
|
|
730
|
-
});
|
|
731
|
-
for (const series of SERIES_KEYS) {
|
|
732
|
-
const amount2 = amounts.get(series);
|
|
733
|
-
if (amount2 === void 0) continue;
|
|
734
|
-
metrics.push({
|
|
735
|
-
id: `${currency.toLowerCase()}-${series}`,
|
|
736
|
-
label: SERIES_LABELS[series],
|
|
737
|
-
value: formatMoneyAmount(amount2),
|
|
738
|
-
unit: "currency",
|
|
739
|
-
currency
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
const notes = ["Rated line items may differ from the final invoice once credits or adjustments are applied."];
|
|
744
|
-
if (metrics.length === 0) {
|
|
745
|
-
notes.push("Fireworks returned no rated line items for the last 30 days.");
|
|
746
|
-
}
|
|
747
|
-
return {
|
|
748
|
-
providerId: "fireworks",
|
|
749
|
-
providerName: "Fireworks",
|
|
750
|
-
capturedAt,
|
|
751
|
-
source: "fireworks-billing-summary",
|
|
752
|
-
semantics: { kind: "api-key", label: "Fireworks API spend" },
|
|
753
|
-
accountLabel: sanitizeDisplayText(accountId, 80),
|
|
754
|
-
buckets: [],
|
|
755
|
-
metrics,
|
|
756
|
-
notes
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
function moneyAmount(value, description) {
|
|
760
|
-
const money = asObject4(value);
|
|
761
|
-
if (!money) throw new Error(`Fireworks billing ${description} was not a money object.`);
|
|
762
|
-
const currency = typeof money.currencyCode === "string" ? money.currencyCode : void 0;
|
|
763
|
-
if (!currency || !CURRENCY_PATTERN.test(currency)) {
|
|
764
|
-
throw new Error(`Fireworks billing ${description} currency was not an ISO 4217 code.`);
|
|
765
|
-
}
|
|
766
|
-
const units = money.units === void 0 ? 0n : integerComponent(money.units, description, "whole units", MAX_UNITS_CHARS);
|
|
767
|
-
if (units < INT64_MIN || units > INT64_MAX) {
|
|
768
|
-
throw new Error(`Fireworks billing ${description} whole units exceeded the int64 range.`);
|
|
769
|
-
}
|
|
770
|
-
const nanos = money.nanos === void 0 ? 0n : integerComponent(money.nanos, description, "nano units", MAX_NANOS_CHARS);
|
|
771
|
-
if (nanos <= -NANOS_PER_UNIT || nanos >= NANOS_PER_UNIT) {
|
|
772
|
-
throw new Error(`Fireworks billing ${description} nano units exceeded the Money range.`);
|
|
773
|
-
}
|
|
774
|
-
if (units > 0n && nanos < 0n || units < 0n && nanos > 0n) {
|
|
775
|
-
throw new Error(`Fireworks billing ${description} mixed unit and nano signs.`);
|
|
776
|
-
}
|
|
777
|
-
return { currency, amount: units * NANOS_PER_UNIT + nanos };
|
|
778
|
-
}
|
|
779
|
-
function integerComponent(value, description, component, maxChars) {
|
|
780
|
-
const text = typeof value === "number" && Number.isSafeInteger(value) ? String(value) : typeof value === "string" && INTEGER_PATTERN.test(value) ? value : void 0;
|
|
781
|
-
if (text === void 0 || text.length > maxChars) {
|
|
782
|
-
throw new Error(`Fireworks billing ${description} ${component} was not a bounded integer.`);
|
|
783
|
-
}
|
|
784
|
-
return BigInt(text);
|
|
785
|
-
}
|
|
786
|
-
function seriesKey(value) {
|
|
787
|
-
if (value === void 0 || value === null) return "other";
|
|
788
|
-
if (typeof value !== "string") throw new Error("Fireworks billing line item series was invalid.");
|
|
789
|
-
if (value === "SERVERLESS") return "serverless";
|
|
790
|
-
if (value === "DEDICATED_DEPLOYMENT") return "dedicated";
|
|
791
|
-
if (value === "TRAINING") return "training";
|
|
792
|
-
return "other";
|
|
793
|
-
}
|
|
794
|
-
function sumSeries(amounts) {
|
|
795
|
-
let total = 0n;
|
|
796
|
-
for (const amount2 of amounts.values()) total += amount2;
|
|
797
|
-
return total;
|
|
798
|
-
}
|
|
799
|
-
function formatMoneyAmount(amount2) {
|
|
800
|
-
const negative = amount2 < 0n;
|
|
801
|
-
const magnitude = negative ? -amount2 : amount2;
|
|
802
|
-
const units = magnitude / NANOS_PER_UNIT;
|
|
803
|
-
const nanos = (magnitude % NANOS_PER_UNIT).toString().padStart(9, "0").replace(/0+$/u, "");
|
|
804
|
-
return `${negative ? "-" : ""}${units.toString()}${nanos ? `.${nanos}` : ""}`;
|
|
805
|
-
}
|
|
806
|
-
function fireworksAccountsUrl(pageToken) {
|
|
807
|
-
const url = new URL("/v1/accounts", FIREWORKS_BILLING_SUMMARY_ORIGIN);
|
|
808
|
-
url.searchParams.set("pageSize", "200");
|
|
809
|
-
if (pageToken !== void 0) url.searchParams.set("pageToken", pageToken);
|
|
810
|
-
return url.toString();
|
|
811
|
-
}
|
|
812
|
-
function fireworksNextPageToken(value) {
|
|
813
|
-
if (value === void 0 || value === null) return void 0;
|
|
814
|
-
if (typeof value !== "string" || !value || value.length > 512) {
|
|
815
|
-
throw new Error("Fireworks accounts listing returned an invalid page token.");
|
|
816
|
-
}
|
|
817
|
-
return value;
|
|
818
|
-
}
|
|
819
|
-
function fireworksBillingSummaryUrl(accountId, startedAt) {
|
|
820
|
-
const dayMs = 24 * 60 * 60 * 1e3;
|
|
821
|
-
const dayFloor = (time) => `${new Date(time).toISOString().slice(0, 10)}T00:00:00Z`;
|
|
822
|
-
const url = new URL(`/v1/accounts/${accountId}/billing/summary`, FIREWORKS_BILLING_SUMMARY_ORIGIN);
|
|
823
|
-
url.searchParams.set("startTime", dayFloor(startedAt - (FIREWORKS_SPEND_WINDOW_DAYS - 1) * dayMs));
|
|
824
|
-
url.searchParams.set("endTime", dayFloor(startedAt + dayMs));
|
|
825
|
-
return url.toString();
|
|
826
|
-
}
|
|
827
|
-
function remainingTimeout(timeoutMs, startedAt, description) {
|
|
828
|
-
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
829
|
-
if (remaining <= 0) throw new Error(`Timed out while ${description}.`);
|
|
830
|
-
return remaining;
|
|
831
|
-
}
|
|
832
|
-
function asObject4(value) {
|
|
833
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
834
|
-
return value;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
// src/providers/github-copilot.ts
|
|
838
|
-
function normalizeGitHubCopilotUsagePayload(payload, capturedAt) {
|
|
839
|
-
const snapshots = asObject5(payload.quota_snapshots);
|
|
840
|
-
const premium = asObject5(snapshots?.premium_interactions);
|
|
841
|
-
const metrics = [];
|
|
842
|
-
let semanticsLabel;
|
|
843
|
-
let bucket;
|
|
844
|
-
if (premium) {
|
|
845
|
-
const tokenBasedBilling = premium.token_based_billing === true;
|
|
846
|
-
const id = tokenBasedBilling ? "ai-credits" : "premium-requests";
|
|
847
|
-
const label = tokenBasedBilling ? "AI credits" : "Premium requests";
|
|
848
|
-
semanticsLabel = tokenBasedBilling ? "GitHub Copilot AI Credits allowance" : "GitHub Copilot premium request quota";
|
|
849
|
-
if (premium.unlimited === true) {
|
|
850
|
-
bucket = { id, label, unit: "count" };
|
|
851
|
-
} else {
|
|
852
|
-
const entitlement = asNonnegativeNumber(premium.entitlement);
|
|
853
|
-
const rawRemaining = asFiniteNumber(premium.remaining) ?? asFiniteNumber(premium.quota_remaining);
|
|
854
|
-
if (entitlement === void 0 || rawRemaining === void 0) {
|
|
855
|
-
throw new Error(`GitHub Copilot ${label.toLowerCase()} quota was incomplete.`);
|
|
856
|
-
}
|
|
857
|
-
const overageUsed = Math.max(asNonnegativeNumber(premium.overage_count) ?? 0, Math.max(0, -rawRemaining));
|
|
858
|
-
if (overageUsed > 0) {
|
|
859
|
-
metrics.push({
|
|
860
|
-
id: "overage-used",
|
|
861
|
-
label: "Additional usage",
|
|
862
|
-
value: overageUsed,
|
|
863
|
-
unit: "count"
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
bucket = {
|
|
867
|
-
id,
|
|
868
|
-
label,
|
|
869
|
-
used: asNonnegativeNumber(premium.credits_used) ?? Math.max(0, entitlement - rawRemaining),
|
|
870
|
-
remaining: Math.max(0, rawRemaining),
|
|
871
|
-
limit: entitlement,
|
|
872
|
-
unit: "count",
|
|
873
|
-
period: "monthly",
|
|
874
|
-
...resetTimestamp(payload)
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
} else {
|
|
878
|
-
const limited = asObject5(payload.limited_user_quotas);
|
|
879
|
-
const monthly = asObject5(payload.monthly_quotas);
|
|
880
|
-
const remaining = asNonnegativeNumber(limited?.chat);
|
|
881
|
-
const entitlement = asNonnegativeNumber(monthly?.chat);
|
|
882
|
-
if (remaining === void 0 || entitlement === void 0) {
|
|
883
|
-
throw new Error("GitHub Copilot usage response contained no supported quota.");
|
|
884
|
-
}
|
|
885
|
-
semanticsLabel = "GitHub Copilot Free chat quota";
|
|
886
|
-
bucket = {
|
|
887
|
-
id: "chat-requests",
|
|
888
|
-
label: "Chat requests",
|
|
889
|
-
used: Math.max(0, entitlement - remaining),
|
|
890
|
-
remaining,
|
|
891
|
-
limit: entitlement,
|
|
892
|
-
unit: "count",
|
|
893
|
-
period: "monthly",
|
|
894
|
-
...resetTimestamp(payload)
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
const notes = [];
|
|
898
|
-
const plan = asString2(payload.copilot_plan) ?? asString2(payload.access_type_sku);
|
|
899
|
-
if (plan) notes.push(`Plan: ${plan}`);
|
|
900
|
-
return {
|
|
901
|
-
providerId: "github-copilot",
|
|
902
|
-
providerName: "GitHub Copilot",
|
|
903
|
-
capturedAt,
|
|
904
|
-
source: "github-copilot-user",
|
|
905
|
-
semantics: { kind: "consumer-subscription", label: semanticsLabel },
|
|
906
|
-
accountLabel: asString2(payload.login),
|
|
907
|
-
buckets: [bucket],
|
|
908
|
-
metrics,
|
|
909
|
-
...notes.length > 0 ? { notes } : {}
|
|
910
|
-
};
|
|
911
|
-
}
|
|
912
|
-
function resetTimestamp(payload) {
|
|
913
|
-
const raw = asString2(payload.quota_reset_date_utc) ?? asString2(payload.quota_reset_date) ?? asString2(payload.limited_user_reset_date);
|
|
914
|
-
if (!raw) return {};
|
|
915
|
-
const milliseconds = Date.parse(raw);
|
|
916
|
-
return Number.isNaN(milliseconds) ? {} : { resetsAt: Math.floor(milliseconds / 1e3) };
|
|
917
|
-
}
|
|
918
|
-
function asObject5(value) {
|
|
919
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
920
|
-
return value;
|
|
921
|
-
}
|
|
922
|
-
function asString2(value) {
|
|
923
|
-
if (typeof value !== "string") return void 0;
|
|
924
|
-
return sanitizeDisplayText(value, 80) || void 0;
|
|
925
|
-
}
|
|
926
|
-
function asFiniteNumber(value) {
|
|
927
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
|
|
928
|
-
return value;
|
|
929
|
-
}
|
|
930
|
-
function asNonnegativeNumber(value) {
|
|
931
|
-
const number = asFiniteNumber(value);
|
|
932
|
-
return number === void 0 || number < 0 ? void 0 : number;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// src/providers/kimi-coding.ts
|
|
936
|
-
var FIVE_HOUR_WINDOW_MINUTES = 300;
|
|
937
|
-
var DAILY_WINDOW_MINUTES = 1440;
|
|
938
|
-
var WEEKLY_WINDOW_MINUTES = 10080;
|
|
939
|
-
var FIXED_POINT_UNITS_PER_CENT = 1e6;
|
|
940
|
-
var RATIO_WINDOWS = [
|
|
941
|
-
{ key: "limit_5h", id: "five-hour", label: "Five-hour window", windowMinutes: FIVE_HOUR_WINDOW_MINUTES },
|
|
942
|
-
{ key: "limit_week", id: "weekly", label: "Weekly window", windowMinutes: WEEKLY_WINDOW_MINUTES },
|
|
943
|
-
{ key: "limit_month_total", id: "monthly", label: "Monthly window" }
|
|
944
|
-
];
|
|
945
|
-
function normalizeKimiCodingUsagePayload(payload, capturedAt) {
|
|
946
|
-
const root = asObject6(payload);
|
|
947
|
-
if (!root) throw new Error("Kimi Coding usage response was not an object.");
|
|
948
|
-
const candidates = [];
|
|
949
|
-
const explicitWindows = /* @__PURE__ */ new Set();
|
|
950
|
-
let omittedWindow = false;
|
|
951
|
-
if (root.usage !== void 0) explicitWindows.add(WEEKLY_WINDOW_MINUTES);
|
|
952
|
-
const summary = parseUsageRow(root.usage, WEEKLY_WINDOW_MINUTES, "Weekly window");
|
|
953
|
-
if (summary) candidates.push(summary);
|
|
954
|
-
else if (root.usage !== void 0) omittedWindow = true;
|
|
955
|
-
if (Array.isArray(root.limits)) {
|
|
956
|
-
for (const raw of root.limits) {
|
|
957
|
-
const item = asObject6(raw);
|
|
958
|
-
const windowMinutes = parseWindowMinutes(item?.window);
|
|
959
|
-
if (windowMinutes !== void 0) explicitWindows.add(windowMinutes);
|
|
960
|
-
const label = sanitizedLabel(item?.name);
|
|
961
|
-
const bucket = windowMinutes === void 0 ? void 0 : parseUsageRow(item?.detail, windowMinutes, label ?? defaultWindowLabel(windowMinutes));
|
|
962
|
-
if (bucket) candidates.push(bucket);
|
|
963
|
-
else omittedWindow = true;
|
|
964
|
-
}
|
|
965
|
-
} else if (root.limits !== void 0) {
|
|
966
|
-
omittedWindow = true;
|
|
967
|
-
}
|
|
968
|
-
const buckets = [];
|
|
969
|
-
const byWindow = /* @__PURE__ */ new Map();
|
|
970
|
-
for (const bucket of candidates) {
|
|
971
|
-
const windowMinutes = bucket.windowMinutes;
|
|
972
|
-
byWindow.set(windowMinutes, [...byWindow.get(windowMinutes) ?? [], bucket]);
|
|
973
|
-
}
|
|
974
|
-
for (const rows of byWindow.values()) {
|
|
975
|
-
if (rows.length === 1) buckets.push(rows[0]);
|
|
976
|
-
else omittedWindow = true;
|
|
977
|
-
}
|
|
978
|
-
const usages = asObject6(root.usages);
|
|
979
|
-
if (root.usages !== void 0 && !usages) omittedWindow = true;
|
|
980
|
-
if (usages) {
|
|
981
|
-
for (const window of RATIO_WINDOWS) {
|
|
982
|
-
const raw = usages[window.key];
|
|
983
|
-
if (raw === void 0 || "windowMinutes" in window && explicitWindows.has(window.windowMinutes)) continue;
|
|
984
|
-
const bucket = parseRatioWindow(raw, window.id, window.label);
|
|
985
|
-
if (bucket) {
|
|
986
|
-
buckets.push({
|
|
987
|
-
...bucket,
|
|
988
|
-
..."windowMinutes" in window ? { windowMinutes: window.windowMinutes } : {}
|
|
989
|
-
});
|
|
990
|
-
} else omittedWindow = true;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
buckets.sort((left, right) => (left.windowMinutes ?? Infinity) - (right.windowMinutes ?? Infinity));
|
|
994
|
-
const metrics = parseBoosterWallet(root.boosterWallet === void 0 ? root.booster_wallet : root.boosterWallet);
|
|
995
|
-
if (buckets.length === 0 && metrics.length === 0) {
|
|
996
|
-
throw new Error("Kimi Coding usage endpoint returned no displayable usage data.");
|
|
997
|
-
}
|
|
998
|
-
return {
|
|
999
|
-
providerId: "kimi-coding",
|
|
1000
|
-
providerName: "Kimi For Coding",
|
|
1001
|
-
capturedAt,
|
|
1002
|
-
source: "kimi-managed-usage",
|
|
1003
|
-
semantics: { kind: "consumer-subscription", label: "Kimi Coding Plan usage" },
|
|
1004
|
-
buckets,
|
|
1005
|
-
metrics,
|
|
1006
|
-
...omittedWindow ? { notes: ["Unsupported, malformed, or duplicate plan windows were unavailable."] } : {}
|
|
1007
|
-
};
|
|
1008
|
-
}
|
|
1009
|
-
function parseUsageRow(value, windowMinutes, label) {
|
|
1010
|
-
const row = asObject6(value);
|
|
1011
|
-
if (!row) return void 0;
|
|
1012
|
-
const limit = asNonnegativeInteger2(row.limit);
|
|
1013
|
-
if (limit === void 0 || limit === 0) return void 0;
|
|
1014
|
-
const usedRaw = asNonnegativeInteger2(row.used);
|
|
1015
|
-
const remainingRaw = asNonnegativeInteger2(row.remaining);
|
|
1016
|
-
if (row.used !== void 0 && usedRaw === void 0 || row.remaining !== void 0 && remainingRaw === void 0) {
|
|
1017
|
-
return void 0;
|
|
1018
|
-
}
|
|
1019
|
-
const used = usedRaw ?? (remainingRaw === void 0 ? 0 : Math.max(0, limit - remainingRaw));
|
|
1020
|
-
const remaining = remainingRaw ?? Math.max(0, limit - used);
|
|
1021
|
-
const resetsAt = asIsoEpochSeconds(row.resetTime);
|
|
1022
|
-
return {
|
|
1023
|
-
id: windowId(windowMinutes),
|
|
1024
|
-
label,
|
|
1025
|
-
used,
|
|
1026
|
-
remaining,
|
|
1027
|
-
limit,
|
|
1028
|
-
unit: "count",
|
|
1029
|
-
windowMinutes,
|
|
1030
|
-
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
1031
|
-
};
|
|
1032
|
-
}
|
|
1033
|
-
function parseRatioWindow(value, id, label) {
|
|
1034
|
-
const row = asObject6(value);
|
|
1035
|
-
const ratio = row?.used_ratio;
|
|
1036
|
-
if (typeof ratio !== "number" || !Number.isFinite(ratio) || ratio < 0 || ratio > 1) return void 0;
|
|
1037
|
-
const resetsAt = asIsoEpochSeconds(row?.reset_time);
|
|
1038
|
-
const used = ratio * 100;
|
|
1039
|
-
return {
|
|
1040
|
-
id,
|
|
1041
|
-
label,
|
|
1042
|
-
used,
|
|
1043
|
-
remaining: 100 - used,
|
|
1044
|
-
unit: "percent",
|
|
1045
|
-
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
function parseWindowMinutes(value) {
|
|
1049
|
-
const window = asObject6(value);
|
|
1050
|
-
if (!window) return void 0;
|
|
1051
|
-
const duration = asPositiveInteger(window.duration);
|
|
1052
|
-
if (duration === void 0) return void 0;
|
|
1053
|
-
const multiplier = window.timeUnit === "TIME_UNIT_MINUTE" ? 1 : window.timeUnit === "TIME_UNIT_HOUR" ? 60 : window.timeUnit === "TIME_UNIT_DAY" ? 1440 : window.timeUnit === "TIME_UNIT_WEEK" ? 10080 : void 0;
|
|
1054
|
-
if (multiplier === void 0) return void 0;
|
|
1055
|
-
const minutes = duration * multiplier;
|
|
1056
|
-
return Number.isSafeInteger(minutes) ? minutes : void 0;
|
|
1057
|
-
}
|
|
1058
|
-
function parseBoosterWallet(value) {
|
|
1059
|
-
const wallet = asObject6(value);
|
|
1060
|
-
const balance = asObject6(wallet?.balance);
|
|
1061
|
-
if (!wallet || !balance || balance.type !== "BOOSTER") return [];
|
|
1062
|
-
const totalRaw = asPositiveInteger(balance.amount);
|
|
1063
|
-
if (totalRaw === void 0) return [];
|
|
1064
|
-
const leftRaw = asNonnegativeInteger2(balance.amountLeft) ?? 0;
|
|
1065
|
-
const monthlyLimit = parseMoney(wallet.monthlyChargeLimit);
|
|
1066
|
-
const monthlyUsed = parseMoney(wallet.monthlyUsed);
|
|
1067
|
-
const currencies = new Set(
|
|
1068
|
-
[monthlyLimit?.currency, monthlyUsed?.currency].filter((currency2) => currency2 !== void 0)
|
|
1069
|
-
);
|
|
1070
|
-
if (currencies.size !== 1) return [];
|
|
1071
|
-
const currency = currencies.values().next().value;
|
|
1072
|
-
if (!currency) return [];
|
|
1073
|
-
const total = fixedPointToMajor(totalRaw);
|
|
1074
|
-
const left = fixedPointToMajor(leftRaw);
|
|
1075
|
-
if (total === void 0 || left === void 0) return [];
|
|
1076
|
-
const metrics = [
|
|
1077
|
-
{ id: "booster-balance", label: "Balance", value: left, unit: "currency", currency },
|
|
1078
|
-
{ id: "booster-total", label: "Total balance", value: total, unit: "currency", currency }
|
|
1079
|
-
];
|
|
1080
|
-
if (monthlyUsed) {
|
|
1081
|
-
metrics.push({
|
|
1082
|
-
id: "booster-monthly-used",
|
|
1083
|
-
label: "Used this month",
|
|
1084
|
-
value: monthlyUsed.cents / 100,
|
|
1085
|
-
unit: "currency",
|
|
1086
|
-
currency
|
|
1087
|
-
});
|
|
1088
|
-
}
|
|
1089
|
-
if (wallet.monthlyChargeLimitEnabled === false) {
|
|
1090
|
-
metrics.push({
|
|
1091
|
-
id: "booster-monthly-limit",
|
|
1092
|
-
label: "Monthly limit",
|
|
1093
|
-
value: "unlimited",
|
|
1094
|
-
unit: "currency",
|
|
1095
|
-
currency
|
|
1096
|
-
});
|
|
1097
|
-
} else if (wallet.monthlyChargeLimitEnabled === true && monthlyLimit) {
|
|
1098
|
-
metrics.push({
|
|
1099
|
-
id: "booster-monthly-limit",
|
|
1100
|
-
label: "Monthly limit",
|
|
1101
|
-
value: monthlyLimit.cents / 100,
|
|
1102
|
-
unit: "currency",
|
|
1103
|
-
currency
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
return metrics;
|
|
1107
|
-
}
|
|
1108
|
-
function parseMoney(value) {
|
|
1109
|
-
const money = asObject6(value);
|
|
1110
|
-
if (!money) return void 0;
|
|
1111
|
-
const cents = asNonnegativeInteger2(money.priceInCents);
|
|
1112
|
-
if (cents === void 0) return void 0;
|
|
1113
|
-
const currency = asCurrency(money.currency);
|
|
1114
|
-
if (!currency) return void 0;
|
|
1115
|
-
return { cents, currency };
|
|
1116
|
-
}
|
|
1117
|
-
function fixedPointToMajor(value) {
|
|
1118
|
-
const cents = value / FIXED_POINT_UNITS_PER_CENT;
|
|
1119
|
-
const roundedCents = cents > 0 && cents < 1 ? 1 : Math.round(cents);
|
|
1120
|
-
const major = roundedCents / 100;
|
|
1121
|
-
return Number.isSafeInteger(roundedCents) && Number.isFinite(major) ? major : void 0;
|
|
1122
|
-
}
|
|
1123
|
-
function asObject6(value) {
|
|
1124
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1125
|
-
return value;
|
|
1126
|
-
}
|
|
1127
|
-
function asNonnegativeInteger2(value) {
|
|
1128
|
-
if (typeof value === "string" && !/^\d+$/u.test(value)) return void 0;
|
|
1129
|
-
const number = typeof value === "string" ? Number(value) : value;
|
|
1130
|
-
if (typeof number !== "number" || !Number.isSafeInteger(number) || number < 0) return void 0;
|
|
1131
|
-
return number;
|
|
1132
|
-
}
|
|
1133
|
-
function asPositiveInteger(value) {
|
|
1134
|
-
const number = asNonnegativeInteger2(value);
|
|
1135
|
-
return number !== void 0 && number > 0 ? number : void 0;
|
|
1136
|
-
}
|
|
1137
|
-
function asIsoEpochSeconds(value) {
|
|
1138
|
-
if (typeof value !== "string") return void 0;
|
|
1139
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|([+-])(\d{2}):(\d{2}))$/u.exec(value);
|
|
1140
|
-
if (!match) return void 0;
|
|
1141
|
-
const [, yearText, monthText, dayText, hourText, minuteText, secondText, , offsetHour, offsetMinute] = match;
|
|
1142
|
-
const year = Number(yearText);
|
|
1143
|
-
const month = Number(monthText);
|
|
1144
|
-
const day = Number(dayText);
|
|
1145
|
-
const hour = Number(hourText);
|
|
1146
|
-
const minute = Number(minuteText);
|
|
1147
|
-
const second = Number(secondText);
|
|
1148
|
-
if (month < 1 || month > 12 || day < 1 || day > new Date(Date.UTC(year, month, 0)).getUTCDate() || hour > 23 || minute > 59 || second > 59 || offsetHour !== void 0 && Number(offsetHour) > 23 || offsetMinute !== void 0 && Number(offsetMinute) > 59) {
|
|
1149
|
-
return void 0;
|
|
1150
|
-
}
|
|
1151
|
-
const millis = Date.parse(value);
|
|
1152
|
-
return Number.isFinite(millis) && millis >= 0 ? Math.floor(millis / 1e3) : void 0;
|
|
1153
|
-
}
|
|
1154
|
-
function sanitizedLabel(value) {
|
|
1155
|
-
if (typeof value !== "string") return void 0;
|
|
1156
|
-
return sanitizeDisplayText(value, 80) || void 0;
|
|
1157
|
-
}
|
|
1158
|
-
function asCurrency(value) {
|
|
1159
|
-
if (typeof value !== "string") return void 0;
|
|
1160
|
-
const currency = sanitizeDisplayText(value, 3).toUpperCase();
|
|
1161
|
-
return /^[A-Z]{3}$/u.test(currency) ? currency : void 0;
|
|
1162
|
-
}
|
|
1163
|
-
function windowId(minutes) {
|
|
1164
|
-
if (minutes === FIVE_HOUR_WINDOW_MINUTES) return "five-hour";
|
|
1165
|
-
if (minutes === DAILY_WINDOW_MINUTES) return "daily";
|
|
1166
|
-
if (minutes === WEEKLY_WINDOW_MINUTES) return "weekly";
|
|
1167
|
-
return `window-${minutes}-minutes`;
|
|
1168
|
-
}
|
|
1169
|
-
function defaultWindowLabel(minutes) {
|
|
1170
|
-
if (minutes === WEEKLY_WINDOW_MINUTES) return "Weekly window";
|
|
1171
|
-
if (minutes % 10080 === 0) return `${minutes / 10080}w window`;
|
|
1172
|
-
if (minutes % 1440 === 0) return `${minutes / 1440}d window`;
|
|
1173
|
-
if (minutes % 60 === 0) return `${minutes / 60}h window`;
|
|
1174
|
-
return `${minutes}m window`;
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
// src/providers/minimax.ts
|
|
1178
|
-
var PROVIDERS = {
|
|
1179
|
-
minimax: { name: "MiniMax", currency: "USD" },
|
|
1180
|
-
"minimax-cn": { name: "MiniMax CN", currency: "CNY" }
|
|
1181
|
-
};
|
|
1182
|
-
var DECIMAL_AMOUNT2 = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/u;
|
|
1183
|
-
var PERCENT_TOLERANCE = 1;
|
|
1184
|
-
function miniMaxUsageKind(apiKey) {
|
|
1185
|
-
return apiKey.startsWith("sk-api-") ? "account-balance" : "token-plan";
|
|
1186
|
-
}
|
|
1187
|
-
function normalizeMiniMaxUsagePayload(providerId, kind, payload, capturedAt) {
|
|
1188
|
-
return kind === "account-balance" ? normalizeBalance(providerId, payload, capturedAt) : normalizeTokenPlan(providerId, payload, capturedAt);
|
|
1189
|
-
}
|
|
1190
|
-
function normalizeBalance(providerId, payload, capturedAt) {
|
|
1191
|
-
assertSuccess(payload);
|
|
1192
|
-
const provider = PROVIDERS[providerId];
|
|
1193
|
-
const metrics = [
|
|
1194
|
-
balanceMetric("available-balance", "Available balance", payload.available_amount, provider.currency),
|
|
1195
|
-
balanceMetric("cash-balance", "Cash balance", payload.cash_balance, provider.currency, true),
|
|
1196
|
-
balanceMetric("voucher-balance", "Voucher balance", payload.voucher_balance, provider.currency),
|
|
1197
|
-
balanceMetric("credit-balance", "Credit balance", payload.credit_balance, provider.currency),
|
|
1198
|
-
balanceMetric("owed-amount", "Owed amount", payload.owed_amount, provider.currency)
|
|
1199
|
-
];
|
|
1200
|
-
return {
|
|
1201
|
-
providerId,
|
|
1202
|
-
providerName: provider.name,
|
|
1203
|
-
capturedAt,
|
|
1204
|
-
source: "minimax-account-balance",
|
|
1205
|
-
semantics: { kind: "api-key", label: "MiniMax pay-as-you-go account balance" },
|
|
1206
|
-
buckets: [],
|
|
1207
|
-
metrics
|
|
1208
|
-
};
|
|
1209
|
-
}
|
|
1210
|
-
function normalizeTokenPlan(providerId, payload, capturedAt) {
|
|
1211
|
-
assertSuccess(payload);
|
|
1212
|
-
if (!Array.isArray(payload.model_remains) || payload.model_remains.length === 0) {
|
|
1213
|
-
throw new Error("MiniMax Token Plan returned no quota rows.");
|
|
1214
|
-
}
|
|
1215
|
-
const provider = PROVIDERS[providerId];
|
|
1216
|
-
const buckets = [];
|
|
1217
|
-
const groups = /* @__PURE__ */ new Set();
|
|
1218
|
-
for (const [index, raw] of payload.model_remains.entries()) {
|
|
1219
|
-
const row = asObject7(raw);
|
|
1220
|
-
if (!row) throw new Error("MiniMax Token Plan quota row was not an object.");
|
|
1221
|
-
const groupLabel = safeLabel(row.model_name, `Quota ${index + 1}`);
|
|
1222
|
-
const groupId = uniqueGroupId(groupLabel, index, groups);
|
|
1223
|
-
buckets.push(
|
|
1224
|
-
normalizeWindow(row, {
|
|
1225
|
-
id: `${groupId}:interval`,
|
|
1226
|
-
label: "Rolling window",
|
|
1227
|
-
groupId,
|
|
1228
|
-
groupLabel,
|
|
1229
|
-
countField: "current_interval_usage_count",
|
|
1230
|
-
totalField: "current_interval_total_count",
|
|
1231
|
-
percentField: "current_interval_remaining_percent",
|
|
1232
|
-
statusField: "current_interval_status",
|
|
1233
|
-
startField: "start_time",
|
|
1234
|
-
endField: "end_time"
|
|
1235
|
-
}),
|
|
1236
|
-
normalizeWindow(row, {
|
|
1237
|
-
id: `${groupId}:weekly`,
|
|
1238
|
-
label: "Weekly window",
|
|
1239
|
-
groupId,
|
|
1240
|
-
groupLabel,
|
|
1241
|
-
countField: "current_weekly_usage_count",
|
|
1242
|
-
totalField: "current_weekly_total_count",
|
|
1243
|
-
percentField: "current_weekly_remaining_percent",
|
|
1244
|
-
statusField: "current_weekly_status",
|
|
1245
|
-
startField: "weekly_start_time",
|
|
1246
|
-
endField: "weekly_end_time",
|
|
1247
|
-
boostPermille: row.weekly_boost_permille
|
|
1248
|
-
})
|
|
1249
|
-
);
|
|
1250
|
-
}
|
|
1251
|
-
return {
|
|
1252
|
-
providerId,
|
|
1253
|
-
providerName: provider.name,
|
|
1254
|
-
capturedAt,
|
|
1255
|
-
source: "minimax-token-plan",
|
|
1256
|
-
semantics: { kind: "consumer-subscription", label: "MiniMax Token Plan quota" },
|
|
1257
|
-
buckets,
|
|
1258
|
-
metrics: []
|
|
1259
|
-
};
|
|
1260
|
-
}
|
|
1261
|
-
function normalizeWindow(row, fields) {
|
|
1262
|
-
const status = optionalInteger(row[fields.statusField], fields.statusField);
|
|
1263
|
-
if (status !== void 0 && ![1, 2, 3].includes(status)) {
|
|
1264
|
-
throw new Error(`MiniMax Token Plan ${fields.label} status was unsupported.`);
|
|
1265
|
-
}
|
|
1266
|
-
const percent = optionalPercent(row[fields.percentField], fields.percentField);
|
|
1267
|
-
validateBoost(fields.boostPermille);
|
|
1268
|
-
const start = timestamp(row[fields.startField], fields.startField);
|
|
1269
|
-
const end = timestamp(row[fields.endField], fields.endField);
|
|
1270
|
-
if (end < start) throw new Error(`MiniMax Token Plan ${fields.label} timestamps were reversed.`);
|
|
1271
|
-
const resetsAt = Math.floor(end / 1e3);
|
|
1272
|
-
const windowMinutes = Math.max(1, Math.round((end - start) / 6e4));
|
|
1273
|
-
if (status === 3) {
|
|
1274
|
-
return {
|
|
1275
|
-
id: fields.id,
|
|
1276
|
-
label: fields.label,
|
|
1277
|
-
groupId: fields.groupId,
|
|
1278
|
-
groupLabel: fields.groupLabel,
|
|
1279
|
-
remaining: 100,
|
|
1280
|
-
unit: "percent",
|
|
1281
|
-
period: "unlimited",
|
|
1282
|
-
windowMinutes
|
|
1283
|
-
};
|
|
1284
|
-
}
|
|
1285
|
-
const total = nonnegativeInteger(row[fields.totalField], fields.totalField);
|
|
1286
|
-
const count = nonnegativeInteger(row[fields.countField], fields.countField);
|
|
1287
|
-
if (total === 0) {
|
|
1288
|
-
if (count !== 0) {
|
|
1289
|
-
throw new Error(`MiniMax Token Plan ${fields.label} counts were inconsistent.`);
|
|
1290
|
-
}
|
|
1291
|
-
if (percent === void 0) {
|
|
1292
|
-
throw new Error(`MiniMax Token Plan ${fields.label} returned no quota and no percent.`);
|
|
1293
|
-
}
|
|
1294
|
-
return {
|
|
1295
|
-
id: fields.id,
|
|
1296
|
-
label: fields.label,
|
|
1297
|
-
groupId: fields.groupId,
|
|
1298
|
-
groupLabel: fields.groupLabel,
|
|
1299
|
-
remaining: percent,
|
|
1300
|
-
used: 100 - percent,
|
|
1301
|
-
limit: 0,
|
|
1302
|
-
unit: "percent",
|
|
1303
|
-
windowMinutes,
|
|
1304
|
-
resetsAt
|
|
1305
|
-
};
|
|
1306
|
-
}
|
|
1307
|
-
const resolved = resolveQuotaCounts(count, total, percent);
|
|
1308
|
-
if (!resolved) throw new Error(`MiniMax Token Plan ${fields.label} counts were inconsistent.`);
|
|
1309
|
-
return {
|
|
1310
|
-
id: fields.id,
|
|
1311
|
-
label: fields.label,
|
|
1312
|
-
groupId: fields.groupId,
|
|
1313
|
-
groupLabel: fields.groupLabel,
|
|
1314
|
-
...resolved,
|
|
1315
|
-
unit: "count",
|
|
1316
|
-
windowMinutes,
|
|
1317
|
-
resetsAt
|
|
1318
|
-
};
|
|
1319
|
-
}
|
|
1320
|
-
function resolveQuotaCounts(reportedCount, total, remainingPercent) {
|
|
1321
|
-
if (total <= 0 || reportedCount > total) return void 0;
|
|
1322
|
-
let remaining = reportedCount;
|
|
1323
|
-
if (remainingPercent !== void 0) {
|
|
1324
|
-
const asRemaining = reportedCount / total * 100;
|
|
1325
|
-
const asUsed = (total - reportedCount) / total * 100;
|
|
1326
|
-
const remainingDistance = Math.abs(asRemaining - remainingPercent);
|
|
1327
|
-
const usedDistance = Math.abs(asUsed - remainingPercent);
|
|
1328
|
-
if (Math.min(remainingDistance, usedDistance) > PERCENT_TOLERANCE) return void 0;
|
|
1329
|
-
if (usedDistance < remainingDistance) remaining = total - reportedCount;
|
|
1330
|
-
}
|
|
1331
|
-
return { used: total - remaining, remaining, limit: total };
|
|
1332
|
-
}
|
|
1333
|
-
function assertSuccess(payload) {
|
|
1334
|
-
const base = asObject7(payload.base_resp);
|
|
1335
|
-
if (base?.status_code !== 0) {
|
|
1336
|
-
throw new Error("MiniMax usage response did not report success.");
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
function balanceMetric(id, label, value, currency, allowNegative = false) {
|
|
1340
|
-
if (typeof value !== "string" || value.length > 64 || !DECIMAL_AMOUNT2.test(value) || !allowNegative && value.startsWith("-")) {
|
|
1341
|
-
throw new Error(`MiniMax ${label.toLowerCase()} was not a valid amount.`);
|
|
1342
|
-
}
|
|
1343
|
-
return { id, label, value, unit: "currency", currency };
|
|
1344
|
-
}
|
|
1345
|
-
function asObject7(value) {
|
|
1346
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1347
|
-
return value;
|
|
1348
|
-
}
|
|
1349
|
-
function safeLabel(value, fallback) {
|
|
1350
|
-
if (typeof value !== "string") throw new Error("MiniMax Token Plan model name was not a string.");
|
|
1351
|
-
return sanitizeDisplayText(value, 80) || fallback;
|
|
1352
|
-
}
|
|
1353
|
-
function uniqueGroupId(label, index, groups) {
|
|
1354
|
-
const base = label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-|-$/gu, "") || "quota";
|
|
1355
|
-
const id = groups.has(base) ? `${base}-${index + 1}` : base;
|
|
1356
|
-
groups.add(id);
|
|
1357
|
-
return id;
|
|
1358
|
-
}
|
|
1359
|
-
function nonnegativeInteger(value, field) {
|
|
1360
|
-
if (!Number.isSafeInteger(value) || value < 0) {
|
|
1361
|
-
throw new Error(`MiniMax Token Plan ${field} was not a nonnegative safe integer.`);
|
|
1362
|
-
}
|
|
1363
|
-
return value;
|
|
1364
|
-
}
|
|
1365
|
-
function optionalInteger(value, field) {
|
|
1366
|
-
if (value === void 0 || value === null) return void 0;
|
|
1367
|
-
return nonnegativeInteger(value, field);
|
|
1368
|
-
}
|
|
1369
|
-
function optionalPercent(value, field) {
|
|
1370
|
-
if (value === void 0 || value === null) return void 0;
|
|
1371
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 100) {
|
|
1372
|
-
throw new Error(`MiniMax Token Plan ${field} was not a percentage.`);
|
|
1373
|
-
}
|
|
1374
|
-
return value;
|
|
1375
|
-
}
|
|
1376
|
-
function validateBoost(boost) {
|
|
1377
|
-
if (boost === void 0 || boost === null) return;
|
|
1378
|
-
const permille = nonnegativeInteger(boost, "weekly_boost_permille");
|
|
1379
|
-
if (permille > 1e4) throw new Error("MiniMax Token Plan weekly boost was unreasonable.");
|
|
1380
|
-
}
|
|
1381
|
-
function timestamp(value, field) {
|
|
1382
|
-
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
1383
|
-
throw new Error(`MiniMax Token Plan ${field} was not a valid timestamp.`);
|
|
1384
|
-
}
|
|
1385
|
-
return value;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
// src/providers/moonshot.ts
|
|
1389
|
-
var PROVIDERS2 = {
|
|
1390
|
-
moonshotai: { name: "Moonshot AI", currency: "USD" },
|
|
1391
|
-
"moonshotai-cn": { name: "Moonshot AI CN", currency: "CNY" }
|
|
1392
|
-
};
|
|
1393
|
-
function normalizeMoonshotBalancePayload(providerId, payload, capturedAt) {
|
|
1394
|
-
if (payload.code !== 0 || payload.status !== true) {
|
|
1395
|
-
throw new Error("Moonshot AI balance response did not report success.");
|
|
1396
|
-
}
|
|
1397
|
-
const data = asObject8(payload.data);
|
|
1398
|
-
if (!data) throw new Error("Moonshot AI balance response data was not an object.");
|
|
1399
|
-
const provider = PROVIDERS2[providerId];
|
|
1400
|
-
const available = amount(data.available_balance, "available balance", false);
|
|
1401
|
-
const voucher = amount(data.voucher_balance, "voucher balance", false);
|
|
1402
|
-
const cash = amount(data.cash_balance, "cash balance", true);
|
|
1403
|
-
const metrics = [
|
|
1404
|
-
currencyMetric("available-balance", "Available balance", available, provider.currency),
|
|
1405
|
-
currencyMetric("voucher-balance", "Voucher balance", voucher, provider.currency),
|
|
1406
|
-
currencyMetric("cash-balance", "Cash balance", cash, provider.currency)
|
|
1407
|
-
];
|
|
1408
|
-
return {
|
|
1409
|
-
providerId,
|
|
1410
|
-
providerName: provider.name,
|
|
1411
|
-
capturedAt,
|
|
1412
|
-
source: "moonshot-balance",
|
|
1413
|
-
semantics: { kind: "api-key", label: "Moonshot API account balance" },
|
|
1414
|
-
buckets: [],
|
|
1415
|
-
metrics
|
|
1416
|
-
};
|
|
1417
|
-
}
|
|
1418
|
-
function currencyMetric(id, label, value, currency) {
|
|
1419
|
-
return { id, label, value, unit: "currency", currency };
|
|
1420
|
-
}
|
|
1421
|
-
function asObject8(value) {
|
|
1422
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1423
|
-
return value;
|
|
1424
|
-
}
|
|
1425
|
-
function amount(value, label, allowNegative) {
|
|
1426
|
-
if (typeof value !== "number" || !Number.isFinite(value) || !allowNegative && value < 0 || Math.abs(value) > Number.MAX_SAFE_INTEGER) {
|
|
1427
|
-
throw new Error(`Moonshot AI ${label} was not a valid amount.`);
|
|
1428
|
-
}
|
|
1429
|
-
return String(value);
|
|
1430
|
-
}
|
|
1431
|
-
|
|
1432
|
-
// src/providers/openai-chatgpt.ts
|
|
1433
|
-
var CHATGPT_USAGE_SETTINGS_URL = "https://chatgpt.com/settings/usage";
|
|
1434
|
-
var DIRECT_TOKEN_SCOPE = "chatgpt.tokens.use.direct";
|
|
1435
|
-
var UnsupportedOpenAIUsageAuthError = class extends Error {
|
|
1436
|
-
name = "UnsupportedOpenAIUsageAuthError";
|
|
1437
|
-
constructor() {
|
|
1438
|
-
super("OpenAI API-key usage reporting is not supported; ChatGPT plan status requires native /login openai OAuth.");
|
|
1439
|
-
}
|
|
1440
|
-
};
|
|
1441
|
-
var OPENAI_CHATGPT_ADAPTER = {
|
|
1442
|
-
id: "openai",
|
|
1443
|
-
displayName: "OpenAI",
|
|
1444
|
-
semantics: { kind: "consumer-subscription", label: "ChatGPT plan authentication status" },
|
|
1445
|
-
async query(_auth, signal, _timeoutMs, guard) {
|
|
1446
|
-
if (!guard) throw new Error("ChatGPT plan status requires runtime-auth revalidation.");
|
|
1447
|
-
signal.throwIfAborted();
|
|
1448
|
-
await guard();
|
|
1449
|
-
signal.throwIfAborted();
|
|
1450
|
-
return {
|
|
1451
|
-
providerId: "openai",
|
|
1452
|
-
providerName: "OpenAI",
|
|
1453
|
-
capturedAt: Date.now(),
|
|
1454
|
-
source: "openai-chatgpt-auth",
|
|
1455
|
-
semantics: { kind: "consumer-subscription", label: "ChatGPT plan authentication status" },
|
|
1456
|
-
buckets: [],
|
|
1457
|
-
metrics: [{ id: "plan-auth", label: "ChatGPT plan authentication", value: "Connected (native OAuth)" }],
|
|
1458
|
-
notes: [
|
|
1459
|
-
"Numerical usage and reset times are unavailable in pi-usage for native OpenAI OAuth.",
|
|
1460
|
-
`Manage usage: ${CHATGPT_USAGE_SETTINGS_URL}`,
|
|
1461
|
-
"Fast mode and earned reset redemption remain legacy openai-codex features."
|
|
1462
|
-
]
|
|
1463
|
-
};
|
|
1464
|
-
}
|
|
1465
|
-
};
|
|
1466
|
-
function resolveOpenAIChatGPTAuth(auth, providerAuth, source, model, candidates) {
|
|
1467
|
-
if (source !== "OAuth") throw new UnsupportedOpenAIUsageAuthError();
|
|
1468
|
-
const access = accessToken(auth);
|
|
1469
|
-
if (!access || access !== accessToken(providerAuth)) {
|
|
1470
|
-
throw new Error("The active OpenAI model authorization does not match its resolved OAuth credential.");
|
|
1471
|
-
}
|
|
1472
|
-
const matches = /* @__PURE__ */ new Map();
|
|
1473
|
-
for (const candidate of candidates) {
|
|
1474
|
-
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue;
|
|
1475
|
-
const credential = candidate;
|
|
1476
|
-
if (credential.type !== "oauth" || credential.access !== access) continue;
|
|
1477
|
-
if (typeof credential.refresh !== "string" || !credential.refresh || typeof credential.expires !== "number" || !Number.isFinite(credential.expires) || typeof credential.clientId !== "string" || !credential.clientId.trim() || !Array.isArray(credential.scopes) || !credential.scopes.every((scope) => typeof scope === "string") || !credential.scopes.includes(DIRECT_TOKEN_SCOPE)) {
|
|
1478
|
-
throw new Error(
|
|
1479
|
-
"The matching OpenAI OAuth credential is not a complete native ChatGPT grant; reconnect with /login openai."
|
|
1480
|
-
);
|
|
1481
|
-
}
|
|
1482
|
-
matches.set(JSON.stringify([credential.clientId, credential.refresh]), credential.refresh);
|
|
1483
|
-
}
|
|
1484
|
-
if (matches.size > 1)
|
|
1485
|
-
throw new Error("Conflicting native ChatGPT credentials match the active OpenAI runtime account.");
|
|
1486
|
-
const refresh = matches.values().next().value;
|
|
1487
|
-
if (!refresh)
|
|
1488
|
-
throw new Error("The active OpenAI runtime account does not match an available native ChatGPT OAuth credential.");
|
|
1489
|
-
const authorization = `Bearer ${access}`;
|
|
1490
|
-
return {
|
|
1491
|
-
apiKey: access,
|
|
1492
|
-
headers: { Authorization: authorization },
|
|
1493
|
-
fingerprint: "",
|
|
1494
|
-
secrets: [access, refresh, authorization],
|
|
1495
|
-
model
|
|
1496
|
-
};
|
|
1497
|
-
}
|
|
1498
|
-
function accessToken(auth) {
|
|
1499
|
-
if (!auth) return void 0;
|
|
1500
|
-
const authorization = Object.entries(auth.headers ?? {}).filter(([name]) => name.toLowerCase() === "authorization");
|
|
1501
|
-
if (authorization.length > 1) throw new Error("OpenAI runtime authorization headers are ambiguous.");
|
|
1502
|
-
if (authorization.length === 1) return /^Bearer\s+(.+)$/iu.exec(authorization[0]?.[1] ?? "")?.[1];
|
|
1503
|
-
return auth.apiKey;
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
// src/providers/opencode-zen.ts
|
|
1507
|
-
var ZEN_WINDOWS = [
|
|
1508
|
-
{ key: "rolling", label: "Rolling" },
|
|
1509
|
-
{ key: "weekly", label: "Weekly" },
|
|
1510
|
-
{ key: "monthly", label: "Monthly" }
|
|
1511
|
-
];
|
|
1512
|
-
function normalizeOpenCodeZenPayload(payload, capturedAt) {
|
|
1513
|
-
const usage = asObject9(payload.usage);
|
|
1514
|
-
if (!usage) throw new Error("OpenCode Zen usage response was not an object.");
|
|
1515
|
-
const buckets = [];
|
|
1516
|
-
const notes = [];
|
|
1517
|
-
for (const window of ZEN_WINDOWS) {
|
|
1518
|
-
const raw = asObject9(usage[window.key]);
|
|
1519
|
-
if (!raw) continue;
|
|
1520
|
-
const status = asString3(raw.status);
|
|
1521
|
-
if (status !== "ok" && status !== "rate-limited") {
|
|
1522
|
-
notes.push(`${window.label} window unavailable (${status ?? "unknown status"}).`);
|
|
1523
|
-
continue;
|
|
1524
|
-
}
|
|
1525
|
-
const used = asNonnegativeNumber2(raw.percent);
|
|
1526
|
-
if (used === void 0) continue;
|
|
1527
|
-
const resetsAt = asEpochSeconds(raw.resetsAt);
|
|
1528
|
-
buckets.push({
|
|
1529
|
-
id: window.key,
|
|
1530
|
-
label: `${window.label} window`,
|
|
1531
|
-
used,
|
|
1532
|
-
remaining: 100 - clampPercent2(used),
|
|
1533
|
-
limit: 100,
|
|
1534
|
-
unit: "percent",
|
|
1535
|
-
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
1536
|
-
});
|
|
1537
|
-
}
|
|
1538
|
-
if (buckets.length === 0) {
|
|
1539
|
-
throw new Error("OpenCode Zen usage endpoint returned no displayable usage data.");
|
|
1540
|
-
}
|
|
1541
|
-
return {
|
|
1542
|
-
providerId: "opencode-go",
|
|
1543
|
-
providerName: "OpenCode Go",
|
|
1544
|
-
capturedAt,
|
|
1545
|
-
source: "opencode-zen-usage",
|
|
1546
|
-
semantics: {
|
|
1547
|
-
kind: "consumer-subscription",
|
|
1548
|
-
label: "OpenCode Zen plan usage"
|
|
1549
|
-
},
|
|
1550
|
-
buckets,
|
|
1551
|
-
metrics: [],
|
|
1552
|
-
...notes.length > 0 ? { notes } : {}
|
|
1553
|
-
};
|
|
1554
|
-
}
|
|
1555
|
-
function asObject9(value) {
|
|
1556
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1557
|
-
return value;
|
|
1558
|
-
}
|
|
1559
|
-
function asString3(value) {
|
|
1560
|
-
if (typeof value !== "string") return void 0;
|
|
1561
|
-
return sanitizeDisplayText(value, 80) || void 0;
|
|
1562
|
-
}
|
|
1563
|
-
function asNonnegativeNumber2(value) {
|
|
1564
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
1565
|
-
return value;
|
|
1566
|
-
}
|
|
1567
|
-
function asEpochSeconds(value) {
|
|
1568
|
-
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
1569
|
-
const parsed = Date.parse(value);
|
|
1570
|
-
if (Number.isNaN(parsed)) return void 0;
|
|
1571
|
-
return Math.floor(parsed / 1e3);
|
|
1572
|
-
}
|
|
1573
|
-
function clampPercent2(value) {
|
|
1574
|
-
return Math.min(100, Math.max(0, value));
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
// src/providers/openrouter.ts
|
|
1578
|
-
function normalizeOpenRouterKeyPayload(payload, capturedAt) {
|
|
1579
|
-
const data = asObject10(payload.data);
|
|
1580
|
-
if (!data) throw new Error("OpenRouter key response data was not an object.");
|
|
1581
|
-
const limit = asNonnegativeNumber3(data.limit);
|
|
1582
|
-
const remaining = asNonnegativeNumber3(data.limit_remaining);
|
|
1583
|
-
const period = asString4(data.limit_reset);
|
|
1584
|
-
const totalUsage = asNonnegativeNumber3(data.usage);
|
|
1585
|
-
const buckets = [];
|
|
1586
|
-
if (limit !== void 0) {
|
|
1587
|
-
buckets.push({
|
|
1588
|
-
id: "key-limit",
|
|
1589
|
-
label: "Key limit",
|
|
1590
|
-
...remaining !== void 0 ? { used: Math.max(0, limit - remaining), remaining } : {},
|
|
1591
|
-
limit,
|
|
1592
|
-
unit: "usd",
|
|
1593
|
-
...period ? { period } : {}
|
|
1594
|
-
});
|
|
1595
|
-
}
|
|
1596
|
-
const metrics = [];
|
|
1597
|
-
addUsageMetric(metrics, "usage-daily", "Usage today", data.usage_daily);
|
|
1598
|
-
addUsageMetric(metrics, "usage-weekly", "Usage this week", data.usage_weekly);
|
|
1599
|
-
addUsageMetric(metrics, "usage-monthly", "Usage this month", data.usage_monthly);
|
|
1600
|
-
addUsageMetric(metrics, "usage-total", "All-time usage", totalUsage);
|
|
1601
|
-
if (buckets.length === 0 && metrics.length === 0) {
|
|
1602
|
-
throw new Error("OpenRouter key response returned no displayable usage data.");
|
|
1603
|
-
}
|
|
1604
|
-
const notes = [];
|
|
1605
|
-
if (data.limit === null) notes.push("No per-key spend cap");
|
|
1606
|
-
else if (limit === void 0) notes.push("Per-key spend cap unavailable");
|
|
1607
|
-
if (data.is_free_tier === true) notes.push("Free-tier API key");
|
|
1608
|
-
return {
|
|
1609
|
-
providerId: "openrouter",
|
|
1610
|
-
providerName: "OpenRouter",
|
|
1611
|
-
capturedAt,
|
|
1612
|
-
source: "openrouter-key",
|
|
1613
|
-
semantics: { kind: "api-key", label: "API-key spend limits" },
|
|
1614
|
-
accountLabel: asString4(data.label),
|
|
1615
|
-
buckets,
|
|
1616
|
-
metrics,
|
|
1617
|
-
...notes.length > 0 ? { notes } : {}
|
|
1618
|
-
};
|
|
1619
|
-
}
|
|
1620
|
-
function addUsageMetric(metrics, id, label, value) {
|
|
1621
|
-
const amount2 = typeof value === "number" ? asNonnegativeNumber3(value) : void 0;
|
|
1622
|
-
if (amount2 === void 0) return;
|
|
1623
|
-
metrics.push({ id, label, value: amount2, unit: "usd" });
|
|
1624
|
-
}
|
|
1625
|
-
function asObject10(value) {
|
|
1626
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1627
|
-
return value;
|
|
1628
|
-
}
|
|
1629
|
-
function asString4(value) {
|
|
1630
|
-
if (typeof value !== "string") return void 0;
|
|
1631
|
-
return sanitizeDisplayText(value, 80) || void 0;
|
|
1632
|
-
}
|
|
1633
|
-
function asNonnegativeNumber3(value) {
|
|
1634
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
1635
|
-
return value;
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
// src/providers/vercel-ai-gateway.ts
|
|
1639
|
-
var DECIMAL_AMOUNT3 = /^(?:0|[1-9]\d*)(?:\.\d+)?$/u;
|
|
1640
|
-
function normalizeVercelAIGatewayCreditsPayload(payload, capturedAt) {
|
|
1641
|
-
const balance = decimalAmount3(payload.balance, "balance");
|
|
1642
|
-
const totalUsed = decimalAmount3(payload.total_used, "total used");
|
|
1643
|
-
const metrics = [
|
|
1644
|
-
{
|
|
1645
|
-
id: "credit-balance",
|
|
1646
|
-
label: "Credit balance",
|
|
1647
|
-
value: balance,
|
|
1648
|
-
unit: "currency",
|
|
1649
|
-
currency: "USD"
|
|
1650
|
-
},
|
|
1651
|
-
{
|
|
1652
|
-
id: "lifetime-spend",
|
|
1653
|
-
label: "Lifetime spend",
|
|
1654
|
-
value: totalUsed,
|
|
1655
|
-
unit: "currency",
|
|
1656
|
-
currency: "USD"
|
|
1657
|
-
}
|
|
1658
|
-
];
|
|
1659
|
-
return {
|
|
1660
|
-
providerId: "vercel-ai-gateway",
|
|
1661
|
-
providerName: "Vercel AI Gateway",
|
|
1662
|
-
capturedAt,
|
|
1663
|
-
source: "vercel-ai-gateway-credits",
|
|
1664
|
-
semantics: { kind: "api-key", label: "AI Gateway credits and lifetime spend" },
|
|
1665
|
-
buckets: [],
|
|
1666
|
-
metrics
|
|
1667
|
-
};
|
|
1668
|
-
}
|
|
1669
|
-
function decimalAmount3(value, label) {
|
|
1670
|
-
if (typeof value !== "string" || value.length > 64 || !DECIMAL_AMOUNT3.test(value)) {
|
|
1671
|
-
throw new Error(`Vercel AI Gateway ${label} was not a valid nonnegative amount.`);
|
|
1672
|
-
}
|
|
1673
|
-
return value;
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
// src/providers/xai.ts
|
|
1677
|
-
var MAX_SAFE_CENTS = Number.MAX_SAFE_INTEGER;
|
|
1678
|
-
function normalizeXaiBillingPayload(payload, subscriptionTier, capturedAt) {
|
|
1679
|
-
const configValue = payload.config;
|
|
1680
|
-
if (configValue !== null && configValue !== void 0 && !isRecord2(configValue)) {
|
|
1681
|
-
throw new Error("xAI billing response config was not an object or null.");
|
|
1682
|
-
}
|
|
1683
|
-
const config = isRecord2(configValue) ? configValue : void 0;
|
|
1684
|
-
const buckets = [];
|
|
1685
|
-
const metrics = [];
|
|
1686
|
-
const notes = [];
|
|
1687
|
-
if (config) {
|
|
1688
|
-
const period = normalizePeriod(config.currentPeriod, config.billingPeriodStart, config.billingPeriodEnd);
|
|
1689
|
-
const preferredPercent = optionalPercent2(config.creditUsagePercent, "creditUsagePercent");
|
|
1690
|
-
if (preferredPercent !== void 0) {
|
|
1691
|
-
buckets.push({
|
|
1692
|
-
id: "included-allowance",
|
|
1693
|
-
label: "Included allowance",
|
|
1694
|
-
used: preferredPercent,
|
|
1695
|
-
remaining: 100 - preferredPercent,
|
|
1696
|
-
unit: "percent",
|
|
1697
|
-
...period
|
|
1698
|
-
});
|
|
1699
|
-
} else {
|
|
1700
|
-
const limit = optionalUsd(config.monthlyLimit, "monthlyLimit");
|
|
1701
|
-
const used = optionalUsd(config.used, "used");
|
|
1702
|
-
if (limit !== void 0 || used !== void 0) {
|
|
1703
|
-
buckets.push({
|
|
1704
|
-
id: "included-allowance",
|
|
1705
|
-
label: "Included allowance",
|
|
1706
|
-
...limit !== void 0 ? { limit } : {},
|
|
1707
|
-
...used !== void 0 ? { used } : {},
|
|
1708
|
-
...limit !== void 0 && used !== void 0 ? { remaining: limit - used } : {},
|
|
1709
|
-
unit: "usd",
|
|
1710
|
-
...period
|
|
1711
|
-
});
|
|
1712
|
-
} else if (period.period || period.resetsAt !== void 0) {
|
|
1713
|
-
buckets.push({
|
|
1714
|
-
id: "included-allowance",
|
|
1715
|
-
label: "Included allowance",
|
|
1716
|
-
unit: "percent",
|
|
1717
|
-
...period
|
|
1718
|
-
});
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
const onDemandCap = optionalUsd(config.onDemandCap, "onDemandCap");
|
|
1722
|
-
const onDemandUsed = optionalUsd(config.onDemandUsed, "onDemandUsed");
|
|
1723
|
-
if (onDemandCap !== void 0 || onDemandUsed !== void 0) {
|
|
1724
|
-
buckets.push({
|
|
1725
|
-
id: "on-demand",
|
|
1726
|
-
label: "On-demand usage",
|
|
1727
|
-
...onDemandCap !== void 0 ? { limit: onDemandCap } : {},
|
|
1728
|
-
...onDemandUsed !== void 0 ? { used: onDemandUsed } : {},
|
|
1729
|
-
...onDemandCap !== void 0 && onDemandUsed !== void 0 ? { remaining: onDemandCap - onDemandUsed } : {},
|
|
1730
|
-
unit: "usd"
|
|
1731
|
-
});
|
|
1732
|
-
}
|
|
1733
|
-
const prepaidBalance = optionalUsd(config.prepaidBalance, "prepaidBalance");
|
|
1734
|
-
if (prepaidBalance !== void 0) {
|
|
1735
|
-
metrics.push({
|
|
1736
|
-
id: "prepaid-balance",
|
|
1737
|
-
label: "Prepaid balance",
|
|
1738
|
-
value: prepaidBalance,
|
|
1739
|
-
unit: "usd"
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
|
-
}
|
|
1743
|
-
const tier = optionalTier(subscriptionTier);
|
|
1744
|
-
if (tier) metrics.push({ id: "subscription-tier", label: "Plan tier", value: tier });
|
|
1745
|
-
if (!config) notes.push("No xAI consumer billing configuration is available for this account.");
|
|
1746
|
-
else if (buckets.length === 0 && metrics.length === 0) {
|
|
1747
|
-
notes.push("The xAI consumer billing response contained no displayable usage fields.");
|
|
1748
|
-
}
|
|
1749
|
-
return {
|
|
1750
|
-
providerId: "xai",
|
|
1751
|
-
providerName: "xAI",
|
|
1752
|
-
capturedAt,
|
|
1753
|
-
source: "cli-chat-proxy.grok.com consumer billing",
|
|
1754
|
-
semantics: {
|
|
1755
|
-
kind: "consumer-subscription",
|
|
1756
|
-
label: "xAI consumer subscription usage"
|
|
1757
|
-
},
|
|
1758
|
-
buckets,
|
|
1759
|
-
metrics,
|
|
1760
|
-
...notes.length > 0 ? { notes } : {}
|
|
1761
|
-
};
|
|
1762
|
-
}
|
|
1763
|
-
function normalizePeriod(currentPeriod, legacyStart, legacyEnd) {
|
|
1764
|
-
if (currentPeriod !== void 0 && currentPeriod !== null && !isRecord2(currentPeriod)) {
|
|
1765
|
-
throw new Error("xAI billing currentPeriod was not an object or null.");
|
|
1766
|
-
}
|
|
1767
|
-
if (isRecord2(currentPeriod)) {
|
|
1768
|
-
const type = optionalString(currentPeriod.type, "currentPeriod.type");
|
|
1769
|
-
const start2 = optionalTimestamp(currentPeriod.start, "currentPeriod.start");
|
|
1770
|
-
const end2 = optionalTimestamp(currentPeriod.end, "currentPeriod.end");
|
|
1771
|
-
return {
|
|
1772
|
-
...periodLabel(type, start2) ? { period: periodLabel(type, start2) } : {},
|
|
1773
|
-
...end2 !== void 0 ? { resetsAt: end2 } : {}
|
|
1774
|
-
};
|
|
1775
|
-
}
|
|
1776
|
-
const start = optionalTimestamp(legacyStart, "billingPeriodStart");
|
|
1777
|
-
const end = optionalTimestamp(legacyEnd, "billingPeriodEnd");
|
|
1778
|
-
return {
|
|
1779
|
-
...start !== void 0 ? { period: "Monthly" } : {},
|
|
1780
|
-
...end !== void 0 ? { resetsAt: end } : {}
|
|
1781
|
-
};
|
|
1782
|
-
}
|
|
1783
|
-
function periodLabel(type, start) {
|
|
1784
|
-
if (type === "USAGE_PERIOD_TYPE_WEEKLY") return "Weekly";
|
|
1785
|
-
if (type === "USAGE_PERIOD_TYPE_MONTHLY") return "Monthly";
|
|
1786
|
-
if (type) return sanitizeDisplayText(type.replace(/^USAGE_PERIOD_TYPE_/u, "").replaceAll("_", " "), 40);
|
|
1787
|
-
return start === void 0 ? void 0 : "Current period";
|
|
1788
|
-
}
|
|
1789
|
-
function optionalUsd(value, field) {
|
|
1790
|
-
if (value === void 0 || value === null) return void 0;
|
|
1791
|
-
if (!isRecord2(value)) throw new Error(`xAI billing ${field} was not a cent wrapper.`);
|
|
1792
|
-
const cents = value.val === void 0 ? 0 : value.val;
|
|
1793
|
-
if (!Number.isSafeInteger(cents) || Math.abs(cents) > MAX_SAFE_CENTS) {
|
|
1794
|
-
throw new Error(`xAI billing ${field}.val was not a safe signed integer.`);
|
|
1795
|
-
}
|
|
1796
|
-
return cents / 100;
|
|
1797
|
-
}
|
|
1798
|
-
function optionalPercent2(value, field) {
|
|
1799
|
-
if (value === void 0 || value === null) return void 0;
|
|
1800
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 100) {
|
|
1801
|
-
throw new Error(`xAI billing ${field} was outside 0\u2013100.`);
|
|
1802
|
-
}
|
|
1803
|
-
return value;
|
|
1804
|
-
}
|
|
1805
|
-
function optionalTimestamp(value, field) {
|
|
1806
|
-
if (value === void 0 || value === null) return void 0;
|
|
1807
|
-
if (typeof value !== "string" || value.length > 80) {
|
|
1808
|
-
throw new Error(`xAI billing ${field} was not a bounded timestamp.`);
|
|
1809
|
-
}
|
|
1810
|
-
const milliseconds = Date.parse(value);
|
|
1811
|
-
if (!Number.isFinite(milliseconds)) throw new Error(`xAI billing ${field} was invalid.`);
|
|
1812
|
-
return Math.floor(milliseconds / 1e3);
|
|
1813
|
-
}
|
|
1814
|
-
function optionalString(value, field) {
|
|
1815
|
-
if (value === void 0 || value === null) return void 0;
|
|
1816
|
-
if (typeof value !== "string" || value.length > 80) {
|
|
1817
|
-
throw new Error(`xAI billing ${field} was not a bounded string.`);
|
|
1818
|
-
}
|
|
1819
|
-
return value;
|
|
1820
|
-
}
|
|
1821
|
-
function optionalTier(value) {
|
|
1822
|
-
if (value === void 0 || value === null) return void 0;
|
|
1823
|
-
if (typeof value !== "string" || value.length > 160) {
|
|
1824
|
-
throw new Error("xAI subscription tier was not a bounded string or null.");
|
|
1825
|
-
}
|
|
1826
|
-
return sanitizeDisplayText(value, 80) || void 0;
|
|
1827
|
-
}
|
|
1828
|
-
function isRecord2(value) {
|
|
1829
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
// src/providers/zai-errors.ts
|
|
1833
|
-
var ERROR_MESSAGES = {
|
|
1834
|
-
"1000": "Authentication failed. Check your API key.",
|
|
1835
|
-
"1001": "Authentication header missing. Check your API key.",
|
|
1836
|
-
"1003": "Authentication token expired. Obtain a new token.",
|
|
1837
|
-
"1005": "Two-factor authentication required.",
|
|
1838
|
-
"1113": "Insufficient balance or no resource package. Recharge your account.",
|
|
1839
|
-
"1200": "API call error. Try again later.",
|
|
1840
|
-
"1210": "Invalid API parameter. Check the API documentation.",
|
|
1841
|
-
"1211": "Unknown model. Check the model ID.",
|
|
1842
|
-
"1212": "This model does not support the requested method.",
|
|
1843
|
-
"1213": "A required parameter is missing.",
|
|
1844
|
-
"1214": "Invalid parameter. Check the API documentation.",
|
|
1845
|
-
"1215": "Conflicting parameters. Check the API documentation.",
|
|
1846
|
-
"1220": "Access denied. Check your permissions.",
|
|
1847
|
-
"1221": "This API has been taken offline.",
|
|
1848
|
-
"1222": "This API does not exist.",
|
|
1849
|
-
"1230": "API processing error. Try again later.",
|
|
1850
|
-
"1234": "Network error. Try again later.",
|
|
1851
|
-
"1261": "Prompt too long. Reduce the input length.",
|
|
1852
|
-
"1301": "Content rejected by the safety policy.",
|
|
1853
|
-
"1302": "Request rate limit reached. Try again later.",
|
|
1854
|
-
"1305": "Service overloaded. Try again later.",
|
|
1855
|
-
"1308": "Usage limit reached. Wait for the quota reset.",
|
|
1856
|
-
"1309": "GLM Coding Plan expired. Renew your subscription.",
|
|
1857
|
-
"1310": "Weekly or monthly limit exhausted. Wait for the quota reset.",
|
|
1858
|
-
"1311": "Your subscription does not include this model.",
|
|
1859
|
-
"1313": "Request frequency restricted by the Fair Usage Policy. Contact support.",
|
|
1860
|
-
"1314": "Enterprise package expired. Contact your administrator.",
|
|
1861
|
-
"1315": "This API key requires an enterprise coding package scenario. Replace the key.",
|
|
1862
|
-
"1316": "5-hour limit reached; insufficient balance for extra usage. Wait for reset.",
|
|
1863
|
-
"1317": "7-day limit reached; insufficient balance for extra usage. Wait for reset.",
|
|
1864
|
-
"1318": "5-hour limit reached; extra usage blocked by monthly spend limit.",
|
|
1865
|
-
"1319": "7-day limit reached; extra usage blocked by monthly spend limit.",
|
|
1866
|
-
"1320": "5-hour limit reached; extra usage blocked by monthly spend limit.",
|
|
1867
|
-
"1321": "7-day limit reached; extra usage blocked by monthly spend limit."
|
|
1868
|
-
};
|
|
1869
|
-
var HTTP_MESSAGES = {
|
|
1870
|
-
400: "Invalid request. Check the API documentation.",
|
|
1871
|
-
401: "Authentication failed. Check your API key.",
|
|
1872
|
-
403: "Access denied. Check your permissions.",
|
|
1873
|
-
429: "Request or usage limit reached. Try again later.",
|
|
1874
|
-
500: "Internal error. Try again later."
|
|
1875
|
-
};
|
|
1876
|
-
function zaiPayloadError(payload) {
|
|
1877
|
-
const object = asObject11(payload);
|
|
1878
|
-
if (!object) return void 0;
|
|
1879
|
-
const nested = asObject11(object.error);
|
|
1880
|
-
const rawCode = nested?.code === void 0 ? object.code : nested.code;
|
|
1881
|
-
const code = errorCode(rawCode);
|
|
1882
|
-
if (object.error === void 0 && object.success !== false && (rawCode === void 0 || code === "0" || code === "200")) {
|
|
1883
|
-
return void 0;
|
|
1884
|
-
}
|
|
1885
|
-
return code && code !== "0" && code !== "200" ? `Z.AI ${code}: ${ERROR_MESSAGES[code] ?? "API request failed."}` : "Z.AI: API request failed.";
|
|
1886
|
-
}
|
|
1887
|
-
function zaiResponseError(status, text) {
|
|
1888
|
-
let payload;
|
|
1889
|
-
try {
|
|
1890
|
-
payload = JSON.parse(text);
|
|
1891
|
-
} catch {
|
|
1892
|
-
if (status >= 200 && status < 300) return "Z.AI: Invalid JSON response.";
|
|
1893
|
-
}
|
|
1894
|
-
const error = zaiPayloadError(payload);
|
|
1895
|
-
if (error) return error;
|
|
1896
|
-
if (status < 200 || status >= 300) {
|
|
1897
|
-
return `Z.AI HTTP ${status}: ${HTTP_MESSAGES[status] ?? "API request failed."}`;
|
|
1898
|
-
}
|
|
1899
|
-
return void 0;
|
|
1900
|
-
}
|
|
1901
|
-
function errorCode(value) {
|
|
1902
|
-
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 9999) {
|
|
1903
|
-
return String(value);
|
|
1904
|
-
}
|
|
1905
|
-
return typeof value === "string" && /^(?:0|[1-9]\d{0,3})$/u.test(value) ? value : void 0;
|
|
1906
|
-
}
|
|
1907
|
-
function asObject11(value) {
|
|
1908
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
// src/providers/zai.ts
|
|
1912
|
-
var FIVE_HOUR_WINDOW_MINUTES2 = 300;
|
|
1913
|
-
var WEEKLY_WINDOW_MINUTES2 = 10080;
|
|
1914
|
-
function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt, plan) {
|
|
1915
|
-
const error = zaiPayloadError(payload);
|
|
1916
|
-
if (error) throw new Error(error);
|
|
1917
|
-
const data = asObject12(payload.data);
|
|
1918
|
-
if (!data) throw new Error("Z.AI quota response data was not an object.");
|
|
1919
|
-
const limits = Array.isArray(data.limits) ? data.limits : [];
|
|
1920
|
-
const buckets = [];
|
|
1921
|
-
const metrics = [];
|
|
1922
|
-
for (const raw of limits) {
|
|
1923
|
-
const limit = asObject12(raw);
|
|
1924
|
-
if (!limit) continue;
|
|
1925
|
-
const type = asString5(limit.type);
|
|
1926
|
-
const unit = asNonnegativeNumber4(limit.unit);
|
|
1927
|
-
const isPlanUsage = type === "TOKENS_LIMIT" || type === "CREDIT_LIMIT";
|
|
1928
|
-
if (type === "TIME_LIMIT") {
|
|
1929
|
-
addCountBucket(buckets, limit, "mcp-monthly", "MCP monthly allowance");
|
|
1930
|
-
addUsageDetailMetrics(metrics, limit.usageDetails);
|
|
1931
|
-
} else if (isPlanUsage && unit === 3) {
|
|
1932
|
-
addPercentBucket(buckets, limit, "five-hour", sessionWindowLabel(limit), sessionWindowMinutes(limit));
|
|
1933
|
-
} else if (isPlanUsage && unit === 6) {
|
|
1934
|
-
const used = asNonnegativeNumber4(limit.currentValue);
|
|
1935
|
-
const quota = asNonnegativeNumber4(limit.usage);
|
|
1936
|
-
if (used !== void 0 && quota !== void 0) {
|
|
1937
|
-
addCountBucket(buckets, limit, "weekly", "Weekly window", weeklyWindowMinutes(limit));
|
|
1938
|
-
} else {
|
|
1939
|
-
addPercentBucket(buckets, limit, "weekly", "Weekly window", weeklyWindowMinutes(limit));
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
if (buckets.length === 0) {
|
|
1944
|
-
throw new Error("Z.AI quota endpoint returned no displayable usage data.");
|
|
1945
|
-
}
|
|
1946
|
-
const notes = [];
|
|
1947
|
-
const level = asString5(data.level);
|
|
1948
|
-
const planLabel = plan?.name ?? level;
|
|
1949
|
-
if (planLabel) {
|
|
1950
|
-
notes.push(plan?.renewsAt ? `Plan: ${planLabel} \xB7 renews ${plan.renewsAt}` : `Plan: ${planLabel}`);
|
|
1951
|
-
}
|
|
1952
|
-
return {
|
|
1953
|
-
providerId,
|
|
1954
|
-
providerName,
|
|
1955
|
-
capturedAt,
|
|
1956
|
-
source: "zai-quota",
|
|
1957
|
-
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
1958
|
-
buckets,
|
|
1959
|
-
metrics,
|
|
1960
|
-
...notes.length > 0 ? { notes } : {}
|
|
1961
|
-
};
|
|
1962
|
-
}
|
|
1963
|
-
function normalizeZaiSubscriptionPayload(payload) {
|
|
1964
|
-
if (zaiPayloadError(payload)) return void 0;
|
|
1965
|
-
if (!Array.isArray(payload.data)) return void 0;
|
|
1966
|
-
const candidates = [];
|
|
1967
|
-
for (const raw of payload.data) {
|
|
1968
|
-
const entry = asObject12(raw);
|
|
1969
|
-
if (!entry) continue;
|
|
1970
|
-
const name = asString5(entry.productName);
|
|
1971
|
-
if (!name) continue;
|
|
1972
|
-
const renewsAt = planRenewalDate(entry.nextRenewTime);
|
|
1973
|
-
const status = asString5(entry.status)?.toUpperCase();
|
|
1974
|
-
const inCurrentPeriod = asBoolean(entry.inCurrentPeriod);
|
|
1975
|
-
candidates.push({
|
|
1976
|
-
plan: { name, ...renewsAt !== void 0 ? { renewsAt } : {} },
|
|
1977
|
-
...status !== void 0 ? { status } : {},
|
|
1978
|
-
...inCurrentPeriod !== void 0 ? { inCurrentPeriod } : {}
|
|
1979
|
-
});
|
|
1980
|
-
}
|
|
1981
|
-
const hasStateMetadata = candidates.some(
|
|
1982
|
-
(candidate) => candidate.status !== void 0 || candidate.inCurrentPeriod !== void 0
|
|
1983
|
-
);
|
|
1984
|
-
if (!hasStateMetadata) return candidates[0]?.plan;
|
|
1985
|
-
return candidates.find((candidate) => candidate.inCurrentPeriod === true && candidate.status === "VALID")?.plan ?? candidates.find((candidate) => candidate.inCurrentPeriod === true && candidate.status === void 0)?.plan ?? candidates.find((candidate) => candidate.status === "VALID" && candidate.inCurrentPeriod === void 0)?.plan;
|
|
1986
|
-
}
|
|
1987
|
-
function planRenewalDate(value) {
|
|
1988
|
-
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}/u.test(value)) return value.slice(0, 10);
|
|
1989
|
-
const millis = asNonnegativeNumber4(value);
|
|
1990
|
-
if (millis === void 0 || millis === 0) return void 0;
|
|
1991
|
-
return new Date(millis).toISOString().slice(0, 10);
|
|
1992
|
-
}
|
|
1993
|
-
function sessionWindowMinutes(limit) {
|
|
1994
|
-
const hours = asPositiveNumber(limit.number);
|
|
1995
|
-
return hours === void 0 ? FIVE_HOUR_WINDOW_MINUTES2 : Math.round(hours * 60);
|
|
1996
|
-
}
|
|
1997
|
-
function sessionWindowLabel(limit) {
|
|
1998
|
-
const minutes = sessionWindowMinutes(limit);
|
|
1999
|
-
return minutes === FIVE_HOUR_WINDOW_MINUTES2 ? "5h window" : `${Math.round(minutes / 60)}h window`;
|
|
2000
|
-
}
|
|
2001
|
-
function weeklyWindowMinutes(limit) {
|
|
2002
|
-
const weeks = asPositiveNumber(limit.number);
|
|
2003
|
-
return weeks === void 0 ? WEEKLY_WINDOW_MINUTES2 : Math.round(weeks * WEEKLY_WINDOW_MINUTES2);
|
|
2004
|
-
}
|
|
2005
|
-
function addPercentBucket(buckets, limit, id, label, windowMinutes) {
|
|
2006
|
-
const used = asNonnegativeNumber4(limit.percentage);
|
|
2007
|
-
if (used === void 0) return;
|
|
2008
|
-
const percent = clampPercent3(used);
|
|
2009
|
-
const resetsAt = asEpochSeconds2(limit.nextResetTime);
|
|
2010
|
-
buckets.push({
|
|
2011
|
-
id,
|
|
2012
|
-
label,
|
|
2013
|
-
used: percent,
|
|
2014
|
-
remaining: 100 - percent,
|
|
2015
|
-
limit: 100,
|
|
2016
|
-
unit: "percent",
|
|
2017
|
-
windowMinutes,
|
|
2018
|
-
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
2019
|
-
});
|
|
2020
|
-
}
|
|
2021
|
-
function addCountBucket(buckets, limit, id, label, windowMinutes) {
|
|
2022
|
-
const used = asNonnegativeNumber4(limit.currentValue);
|
|
2023
|
-
const quota = asNonnegativeNumber4(limit.usage);
|
|
2024
|
-
if (used === void 0 || quota === void 0) return;
|
|
2025
|
-
const resetsAt = asEpochSeconds2(limit.nextResetTime);
|
|
2026
|
-
buckets.push({
|
|
2027
|
-
id,
|
|
2028
|
-
label,
|
|
2029
|
-
used,
|
|
2030
|
-
remaining: Math.max(0, quota - used),
|
|
2031
|
-
limit: quota,
|
|
2032
|
-
unit: "count",
|
|
2033
|
-
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
2034
|
-
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
2035
|
-
});
|
|
2036
|
-
}
|
|
2037
|
-
function addUsageDetailMetrics(metrics, value) {
|
|
2038
|
-
if (!Array.isArray(value)) return;
|
|
2039
|
-
for (const raw of value) {
|
|
2040
|
-
const detail = asObject12(raw);
|
|
2041
|
-
if (!detail) continue;
|
|
2042
|
-
const label = asString5(detail.modelCode);
|
|
2043
|
-
const usage = asNonnegativeNumber4(detail.usage);
|
|
2044
|
-
if (!label || usage === void 0) continue;
|
|
2045
|
-
metrics.push({ id: `mcp-${kebabCase(label)}`, label, value: usage, unit: "count" });
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
function asObject12(value) {
|
|
2049
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2050
|
-
return value;
|
|
2051
|
-
}
|
|
2052
|
-
function asString5(value) {
|
|
2053
|
-
if (typeof value !== "string") return void 0;
|
|
2054
|
-
return sanitizeDisplayText(value, 80) || void 0;
|
|
2055
|
-
}
|
|
2056
|
-
function asNonnegativeNumber4(value) {
|
|
2057
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
2058
|
-
return value;
|
|
2059
|
-
}
|
|
2060
|
-
function asPositiveNumber(value) {
|
|
2061
|
-
const number = asNonnegativeNumber4(value);
|
|
2062
|
-
return number !== void 0 && number > 0 ? number : void 0;
|
|
2063
|
-
}
|
|
2064
|
-
function asBoolean(value) {
|
|
2065
|
-
if (typeof value === "boolean") return value;
|
|
2066
|
-
if (value === 1) return true;
|
|
2067
|
-
if (value === 0) return false;
|
|
2068
|
-
return void 0;
|
|
2069
|
-
}
|
|
2070
|
-
function asEpochSeconds2(value) {
|
|
2071
|
-
const millis = asNonnegativeNumber4(value);
|
|
2072
|
-
if (millis === void 0) return void 0;
|
|
2073
|
-
return Math.floor(millis / 1e3);
|
|
2074
|
-
}
|
|
2075
|
-
function kebabCase(label) {
|
|
2076
|
-
return label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || "tool";
|
|
2077
|
-
}
|
|
2078
|
-
function clampPercent3(value) {
|
|
2079
|
-
return Math.min(100, Math.max(0, value));
|
|
2080
|
-
}
|
|
2081
|
-
|
|
2082
|
-
// src/usage-targets.ts
|
|
2083
|
-
var MAX_TARGETS = 1e3;
|
|
2084
|
-
var MAX_TARGET_ID_CHARS = 256;
|
|
2085
|
-
var MAX_TARGET_LABEL_CHARS = 120;
|
|
2086
|
-
var MAX_TARGET_DESCRIPTION_CHARS = 180;
|
|
2087
|
-
async function resolveUsageTarget(adapter, auth, rememberedTargetId, signal, timeoutMs, guard) {
|
|
2088
|
-
if (!adapter.targets) return { kind: "selected" };
|
|
2089
|
-
if (rememberedTargetId !== void 0 && !isBoundedTargetId(rememberedTargetId)) {
|
|
2090
|
-
throw new Error(`The remembered ${adapter.targets.singularLabel} identifier was invalid.`);
|
|
2091
|
-
}
|
|
2092
|
-
const choices = await listUsageTargets(adapter, auth, signal, timeoutMs, guard);
|
|
2093
|
-
if (rememberedTargetId) {
|
|
2094
|
-
return choices.some((choice) => choice.id === rememberedTargetId) ? { kind: "selected", targetId: rememberedTargetId } : { kind: "selection-required", choices };
|
|
2095
|
-
}
|
|
2096
|
-
if (choices.length === 1) return { kind: "selected", targetId: choices[0]?.id };
|
|
2097
|
-
return { kind: "selection-required", choices };
|
|
2098
|
-
}
|
|
2099
|
-
async function listUsageTargets(adapter, auth, signal, timeoutMs, guard) {
|
|
2100
|
-
if (!adapter.targets) return [];
|
|
2101
|
-
const startedAt = Date.now();
|
|
2102
|
-
await guard();
|
|
2103
|
-
const listed = await adapter.targets.list(auth, signal, remainingTargetTimeout(timeoutMs, startedAt), guard);
|
|
2104
|
-
await guard();
|
|
2105
|
-
remainingTargetTimeout(timeoutMs, startedAt);
|
|
2106
|
-
const choices = normalizeUsageTargets(listed);
|
|
2107
|
-
if (choices.length === 0) {
|
|
2108
|
-
throw new Error(`${adapter.targets.pluralLabel} discovery returned no choices.`);
|
|
2109
|
-
}
|
|
2110
|
-
return choices;
|
|
2111
|
-
}
|
|
2112
|
-
function normalizeUsageTargets(targets) {
|
|
2113
|
-
if (!Array.isArray(targets)) throw new Error("Target discovery did not return a choices array.");
|
|
2114
|
-
if (targets.length > MAX_TARGETS) {
|
|
2115
|
-
throw new Error(`Target discovery exceeded ${MAX_TARGETS} choices.`);
|
|
2116
|
-
}
|
|
2117
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2118
|
-
return targets.map((target) => {
|
|
2119
|
-
if (!target || typeof target !== "object" || Array.isArray(target)) {
|
|
2120
|
-
throw new Error("Target discovery returned an invalid choice.");
|
|
2121
|
-
}
|
|
2122
|
-
const { id, label, description } = target;
|
|
2123
|
-
if (!isBoundedTargetId(id)) throw new Error("Target discovery returned an invalid ID.");
|
|
2124
|
-
if (seen.has(id)) throw new Error(`Target discovery repeated ${id}.`);
|
|
2125
|
-
seen.add(id);
|
|
2126
|
-
if (typeof label !== "string") {
|
|
2127
|
-
throw new Error("Target discovery returned an invalid display label.");
|
|
2128
|
-
}
|
|
2129
|
-
if (description !== void 0 && typeof description !== "string") {
|
|
2130
|
-
throw new Error("Target discovery returned an invalid description.");
|
|
2131
|
-
}
|
|
2132
|
-
const safeLabel2 = sanitizeDisplayText(label, MAX_TARGET_LABEL_CHARS);
|
|
2133
|
-
if (!safeLabel2) throw new Error("Target discovery returned an empty display label.");
|
|
2134
|
-
const safeDescription = description ? sanitizeDisplayText(description, MAX_TARGET_DESCRIPTION_CHARS) : void 0;
|
|
2135
|
-
return { id, label: safeLabel2, ...safeDescription ? { description: safeDescription } : {} };
|
|
2136
|
-
});
|
|
2137
|
-
}
|
|
2138
|
-
function createUsageTargetSelectOptions(targets) {
|
|
2139
|
-
const normalized = normalizeUsageTargets(targets);
|
|
2140
|
-
const ids = /* @__PURE__ */ new Map();
|
|
2141
|
-
const options = normalized.map((target) => {
|
|
2142
|
-
const base = target.description ? `${target.label} \u2014 ${target.description}` : target.label;
|
|
2143
|
-
let option = base;
|
|
2144
|
-
if (ids.has(option)) {
|
|
2145
|
-
const safeId = sanitizeDisplayText(target.id, 80) || "target";
|
|
2146
|
-
option = `${base} \xB7 ${safeId}`;
|
|
2147
|
-
let duplicate = 2;
|
|
2148
|
-
while (ids.has(option)) {
|
|
2149
|
-
option = `${base} \xB7 ${safeId} (${duplicate})`;
|
|
2150
|
-
duplicate += 1;
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
ids.set(option, target.id);
|
|
2154
|
-
return option;
|
|
2155
|
-
});
|
|
2156
|
-
return { options, targetIdFor: (option) => ids.get(option) };
|
|
2157
|
-
}
|
|
2158
|
-
function remainingTargetTimeout(timeoutMs, startedAt) {
|
|
2159
|
-
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
2160
|
-
if (remaining <= 0) throw new Error("Timed out while discovering provider targets.");
|
|
2161
|
-
return remaining;
|
|
2162
|
-
}
|
|
2163
|
-
function isBoundedTargetId(value) {
|
|
2164
|
-
return typeof value === "string" && value.length > 0 && value.length <= MAX_TARGET_ID_CHARS && ![...value].some((character) => {
|
|
2165
|
-
const codePoint = character.codePointAt(0) ?? 0;
|
|
2166
|
-
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
|
|
2167
|
-
});
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
// src/query.ts
|
|
2171
|
-
var BASETEN_BILLING_USAGE_URL = "https://api.baseten.co/v1/billing/usage_summary";
|
|
2172
|
-
var BASETEN_USAGE_WINDOW_DAYS = 30;
|
|
2173
|
-
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
2174
|
-
var DEEPSEEK_BALANCE_URL = "https://api.deepseek.com/user/balance";
|
|
2175
|
-
var GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
|
|
2176
|
-
var OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
|
|
2177
|
-
var VERCEL_AI_GATEWAY_CREDITS_URL = "https://ai-gateway.vercel.sh/v1/credits";
|
|
2178
|
-
var OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
|
|
2179
|
-
var KIMI_CODING_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
|
|
2180
|
-
var MINIMAX_API_ROOTS = Object.freeze({
|
|
2181
|
-
minimax: "https://api.minimax.io",
|
|
2182
|
-
"minimax-cn": "https://api.minimaxi.com"
|
|
2183
|
-
});
|
|
2184
|
-
var MOONSHOT_BALANCE_URLS = Object.freeze({
|
|
2185
|
-
moonshotai: "https://api.moonshot.ai/v1/users/me/balance",
|
|
2186
|
-
"moonshotai-cn": "https://api.moonshot.cn/v1/users/me/balance"
|
|
2187
|
-
});
|
|
2188
|
-
var SHARED_MOONSHOT_ENV_VAR = "MOONSHOT_API_KEY";
|
|
2189
|
-
var XAI_USER_URL = "https://cli-chat-proxy.grok.com/v1/user?include=subscription";
|
|
2190
|
-
var XAI_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
|
|
2191
|
-
var XAI_CLIENT_HEADERS = Object.freeze({
|
|
2192
|
-
"X-XAI-Token-Auth": "xai-grok-cli",
|
|
2193
|
-
"x-grok-client-version": "1.0.10",
|
|
2194
|
-
"x-grok-client-mode": "interactive"
|
|
2195
|
-
});
|
|
2196
|
-
var MAX_SUCCESS_BODY_BYTES = 64 * 1024;
|
|
2197
|
-
var MAX_ERROR_BODY_BYTES = 4 * 1024;
|
|
2198
|
-
var AUTH_FINGERPRINT_SALT = randomBytes(32);
|
|
2199
|
-
var SUPPORTED_ADAPTERS = [
|
|
2200
|
-
{
|
|
2201
|
-
id: "baseten",
|
|
2202
|
-
displayName: "Baseten",
|
|
2203
|
-
semantics: { kind: "api-key", label: "Organization Model APIs spend" },
|
|
2204
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2205
|
-
if (!guard) throw new Error("Baseten billing usage requires request-boundary revalidation.");
|
|
2206
|
-
const startedAt = Date.now();
|
|
2207
|
-
await guard();
|
|
2208
|
-
const windowAt = Date.now();
|
|
2209
|
-
const payload = await fetchProviderJson(
|
|
2210
|
-
basetenBillingUsageUrl(windowAt),
|
|
2211
|
-
auth,
|
|
2212
|
-
signal,
|
|
2213
|
-
remainingTimeout2(timeoutMs, startedAt, "fetching Baseten billing usage"),
|
|
2214
|
-
"Baseten billing usage endpoint",
|
|
2215
|
-
{ redirect: "error" }
|
|
2216
|
-
);
|
|
2217
|
-
await guard();
|
|
2218
|
-
return normalizeBasetenBillingUsagePayload(payload, Date.now());
|
|
2219
|
-
}
|
|
2220
|
-
},
|
|
2221
|
-
OPENAI_CHATGPT_ADAPTER,
|
|
2222
|
-
{
|
|
2223
|
-
id: "openai-codex",
|
|
2224
|
-
displayName: "OpenAI Codex",
|
|
2225
|
-
semantics: {
|
|
2226
|
-
kind: "consumer-subscription",
|
|
2227
|
-
label: "ChatGPT subscription limits"
|
|
2228
|
-
},
|
|
2229
|
-
async query(auth, signal, timeoutMs) {
|
|
2230
|
-
const payload = await fetchProviderJson(CODEX_USAGE_URL, auth, signal, timeoutMs, "Codex usage endpoint");
|
|
2231
|
-
return normalizeCodexBackendPayload(payload, Date.now());
|
|
2232
|
-
}
|
|
2233
|
-
},
|
|
2234
|
-
{
|
|
2235
|
-
id: "deepseek",
|
|
2236
|
-
displayName: "DeepSeek",
|
|
2237
|
-
semantics: { kind: "api-key", label: "DeepSeek API balance" },
|
|
2238
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2239
|
-
if (!guard) throw new Error("DeepSeek API balance requires request-boundary revalidation.");
|
|
2240
|
-
const startedAt = Date.now();
|
|
2241
|
-
await guard();
|
|
2242
|
-
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
2243
|
-
if (remainingMs <= 0) throw new Error("Timed out while revalidating DeepSeek runtime auth.");
|
|
2244
|
-
const payload = await fetchProviderJson(
|
|
2245
|
-
DEEPSEEK_BALANCE_URL,
|
|
2246
|
-
auth,
|
|
2247
|
-
signal,
|
|
2248
|
-
remainingMs,
|
|
2249
|
-
"DeepSeek API balance endpoint",
|
|
2250
|
-
{ redirect: "error" }
|
|
2251
|
-
);
|
|
2252
|
-
return normalizeDeepSeekBalancePayload(payload, Date.now());
|
|
2253
|
-
}
|
|
2254
|
-
},
|
|
2255
|
-
{
|
|
2256
|
-
id: "github-copilot",
|
|
2257
|
-
displayName: "GitHub Copilot",
|
|
2258
|
-
semantics: {
|
|
2259
|
-
kind: "consumer-subscription",
|
|
2260
|
-
label: "GitHub Copilot account allowance"
|
|
2261
|
-
},
|
|
2262
|
-
async query(auth, signal, timeoutMs) {
|
|
2263
|
-
const payload = await fetchProviderJson(
|
|
2264
|
-
GITHUB_COPILOT_USAGE_URL,
|
|
2265
|
-
auth,
|
|
2266
|
-
signal,
|
|
2267
|
-
timeoutMs,
|
|
2268
|
-
"GitHub Copilot usage endpoint"
|
|
2269
|
-
);
|
|
2270
|
-
return normalizeGitHubCopilotUsagePayload(payload, Date.now());
|
|
2271
|
-
}
|
|
2272
|
-
},
|
|
2273
|
-
{
|
|
2274
|
-
id: "openrouter",
|
|
2275
|
-
displayName: "OpenRouter",
|
|
2276
|
-
semantics: { kind: "api-key", label: "API-key spend limits" },
|
|
2277
|
-
async query(auth, signal, timeoutMs) {
|
|
2278
|
-
const payload = await fetchProviderJson(OPENROUTER_KEY_URL, auth, signal, timeoutMs, "OpenRouter key endpoint");
|
|
2279
|
-
return normalizeOpenRouterKeyPayload(payload, Date.now());
|
|
2280
|
-
}
|
|
2281
|
-
},
|
|
2282
|
-
{
|
|
2283
|
-
id: "vercel-ai-gateway",
|
|
2284
|
-
displayName: "Vercel AI Gateway",
|
|
2285
|
-
semantics: { kind: "api-key", label: "AI Gateway credits and lifetime spend" },
|
|
2286
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2287
|
-
if (!guard) throw new Error("Vercel AI Gateway usage requires request-boundary revalidation.");
|
|
2288
|
-
const startedAt = Date.now();
|
|
2289
|
-
await guard();
|
|
2290
|
-
const payload = await fetchProviderJson(
|
|
2291
|
-
VERCEL_AI_GATEWAY_CREDITS_URL,
|
|
2292
|
-
auth,
|
|
2293
|
-
signal,
|
|
2294
|
-
remainingTimeout2(timeoutMs, startedAt, "fetching Vercel AI Gateway credits"),
|
|
2295
|
-
"Vercel AI Gateway credits endpoint",
|
|
2296
|
-
{ redirect: "error" }
|
|
2297
|
-
);
|
|
2298
|
-
await guard();
|
|
2299
|
-
return normalizeVercelAIGatewayCreditsPayload(payload, Date.now());
|
|
2300
|
-
}
|
|
2301
|
-
},
|
|
2302
|
-
createFireworksAdapter(fetchProviderJson),
|
|
2303
|
-
{
|
|
2304
|
-
id: "opencode-go",
|
|
2305
|
-
displayName: "OpenCode Go",
|
|
2306
|
-
semantics: { kind: "consumer-subscription", label: "OpenCode Zen plan usage" },
|
|
2307
|
-
async query(auth, signal, timeoutMs) {
|
|
2308
|
-
const payload = await fetchProviderJson(
|
|
2309
|
-
OPENCODE_GO_USAGE_URL,
|
|
2310
|
-
auth,
|
|
2311
|
-
signal,
|
|
2312
|
-
timeoutMs,
|
|
2313
|
-
"OpenCode Zen usage endpoint"
|
|
2314
|
-
);
|
|
2315
|
-
return normalizeOpenCodeZenPayload(payload, Date.now());
|
|
2316
|
-
}
|
|
2317
|
-
},
|
|
2318
|
-
{
|
|
2319
|
-
id: "kimi-coding",
|
|
2320
|
-
displayName: "Kimi For Coding",
|
|
2321
|
-
semantics: { kind: "consumer-subscription", label: "Kimi Coding Plan usage" },
|
|
2322
|
-
async query(auth, signal, timeoutMs) {
|
|
2323
|
-
const payload = await fetchProviderJson(
|
|
2324
|
-
KIMI_CODING_USAGE_URL,
|
|
2325
|
-
auth,
|
|
2326
|
-
signal,
|
|
2327
|
-
timeoutMs,
|
|
2328
|
-
"Kimi Coding usage endpoint",
|
|
2329
|
-
{ redirect: "error" }
|
|
2330
|
-
);
|
|
2331
|
-
return normalizeKimiCodingUsagePayload(payload, Date.now());
|
|
2332
|
-
}
|
|
2333
|
-
},
|
|
2334
|
-
{
|
|
2335
|
-
id: "minimax",
|
|
2336
|
-
displayName: "MiniMax",
|
|
2337
|
-
semantics: { kind: "consumer-subscription", label: "MiniMax usage" },
|
|
2338
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2339
|
-
return queryMiniMaxUsage("minimax", auth, signal, timeoutMs, guard);
|
|
2340
|
-
}
|
|
2341
|
-
},
|
|
2342
|
-
{
|
|
2343
|
-
id: "minimax-cn",
|
|
2344
|
-
displayName: "MiniMax CN",
|
|
2345
|
-
semantics: { kind: "consumer-subscription", label: "MiniMax usage" },
|
|
2346
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2347
|
-
return queryMiniMaxUsage("minimax-cn", auth, signal, timeoutMs, guard);
|
|
2348
|
-
}
|
|
2349
|
-
},
|
|
2350
|
-
{
|
|
2351
|
-
id: "moonshotai",
|
|
2352
|
-
displayName: "Moonshot AI",
|
|
2353
|
-
semantics: { kind: "api-key", label: "Moonshot API account balance" },
|
|
2354
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2355
|
-
return queryMoonshotBalance("moonshotai", auth, signal, timeoutMs, guard);
|
|
2356
|
-
}
|
|
2357
|
-
},
|
|
2358
|
-
{
|
|
2359
|
-
id: "moonshotai-cn",
|
|
2360
|
-
displayName: "Moonshot AI CN",
|
|
2361
|
-
semantics: { kind: "api-key", label: "Moonshot API account balance" },
|
|
2362
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2363
|
-
return queryMoonshotBalance("moonshotai-cn", auth, signal, timeoutMs, guard);
|
|
2364
|
-
}
|
|
2365
|
-
},
|
|
2366
|
-
{
|
|
2367
|
-
id: "zai",
|
|
2368
|
-
displayName: "Z.AI",
|
|
2369
|
-
invalidateCacheOnFailure: true,
|
|
2370
|
-
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
2371
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2372
|
-
return queryZaiUsage("zai", "Z.AI", auth, signal, timeoutMs, guard);
|
|
2373
|
-
}
|
|
2374
|
-
},
|
|
2375
|
-
{
|
|
2376
|
-
id: "zai-coding-cn",
|
|
2377
|
-
displayName: "Z.AI Coding CN",
|
|
2378
|
-
invalidateCacheOnFailure: true,
|
|
2379
|
-
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
2380
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2381
|
-
return queryZaiUsage("zai-coding-cn", "Z.AI Coding CN", auth, signal, timeoutMs, guard);
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2384
|
-
];
|
|
2385
|
-
var XAI_ADAPTER = {
|
|
2386
|
-
id: "xai",
|
|
2387
|
-
displayName: "xAI",
|
|
2388
|
-
semantics: {
|
|
2389
|
-
kind: "consumer-subscription",
|
|
2390
|
-
label: "xAI consumer subscription usage"
|
|
2391
|
-
},
|
|
2392
|
-
publishesStatusline: false,
|
|
2393
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2394
|
-
if (!guard) throw new Error("xAI usage requires request-boundary revalidation.");
|
|
2395
|
-
const startedAt = Date.now();
|
|
2396
|
-
const clientAuth = {
|
|
2397
|
-
...auth,
|
|
2398
|
-
headers: { ...auth.headers, ...XAI_CLIENT_HEADERS }
|
|
2399
|
-
};
|
|
2400
|
-
await guard();
|
|
2401
|
-
const userPayload = await fetchProviderJson(
|
|
2402
|
-
XAI_USER_URL,
|
|
2403
|
-
clientAuth,
|
|
2404
|
-
signal,
|
|
2405
|
-
remainingTimeout2(timeoutMs, startedAt),
|
|
2406
|
-
"xAI consumer identity endpoint",
|
|
2407
|
-
{ redirect: "error", userAgent: false }
|
|
2408
|
-
);
|
|
2409
|
-
await guard();
|
|
2410
|
-
const userId = validatedXaiUserId(userPayload.userId);
|
|
2411
|
-
const billingAuth = {
|
|
2412
|
-
...clientAuth,
|
|
2413
|
-
headers: { ...clientAuth.headers, "x-userid": userId },
|
|
2414
|
-
secrets: [...clientAuth.secrets, userId]
|
|
2415
|
-
};
|
|
2416
|
-
await guard();
|
|
2417
|
-
const billingPayload = await fetchProviderJson(
|
|
2418
|
-
XAI_BILLING_URL,
|
|
2419
|
-
billingAuth,
|
|
2420
|
-
signal,
|
|
2421
|
-
remainingTimeout2(timeoutMs, startedAt),
|
|
2422
|
-
"xAI consumer billing endpoint",
|
|
2423
|
-
{ redirect: "error", userAgent: false }
|
|
2424
|
-
);
|
|
2425
|
-
await guard();
|
|
2426
|
-
return normalizeXaiBillingPayload(billingPayload, userPayload.subscriptionTier, Date.now());
|
|
2427
|
-
}
|
|
2428
|
-
};
|
|
2429
|
-
function usageAdapters() {
|
|
2430
|
-
return [...SUPPORTED_ADAPTERS, XAI_ADAPTER];
|
|
2431
|
-
}
|
|
2432
|
-
function adapterForProvider(providerId) {
|
|
2433
|
-
return usageAdapters().find((adapter) => adapter.id === providerId);
|
|
2434
|
-
}
|
|
2435
|
-
function isStaleExtensionContextError(error) {
|
|
2436
|
-
return error instanceof Error && error.message.includes("This extension ctx is stale after session replacement or reload");
|
|
2437
|
-
}
|
|
2438
|
-
async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, credentialReader = readStoredCredential2, candidateReader) {
|
|
2439
|
-
if (ctx.model?.provider === adapter.id && !hasOfficialOrigin(ctx.model, adapter.id)) {
|
|
2440
|
-
throw new Error(
|
|
2441
|
-
`${adapter.displayName} usage cannot send a custom provider base URL credential to the official usage endpoint.`
|
|
2442
|
-
);
|
|
2443
|
-
}
|
|
2444
|
-
const model = candidateModels(ctx, adapter.id).find((candidate) => hasOfficialOrigin(candidate, adapter.id));
|
|
2445
|
-
if (!model) return void 0;
|
|
2446
|
-
if (["github-copilot", "openai", "xai"].includes(adapter.id) && candidateReader?.waitUntilReady && !await candidateReader.waitUntilReady(ctx, adapter.id)) {
|
|
2447
|
-
throw new Error(`${adapter.displayName} OAuth credential readiness failed closed.`);
|
|
2448
|
-
}
|
|
2449
|
-
const registry = ctx.modelRegistry;
|
|
2450
|
-
const provider = registry.getProvider?.(adapter.id);
|
|
2451
|
-
if (provider?.baseUrl && !hasOfficialUrlOrigin(provider.baseUrl, adapter.id)) {
|
|
2452
|
-
throw new Error(
|
|
2453
|
-
`${adapter.displayName} usage cannot send an overridden provider credential to the official usage endpoint.`
|
|
2454
|
-
);
|
|
2455
|
-
}
|
|
2456
|
-
let modelAuth;
|
|
2457
|
-
const currentModel = ctx.model?.provider === adapter.id ? ctx.model : void 0;
|
|
2458
|
-
const resolveCurrentModelAuth = async () => {
|
|
2459
|
-
if (!currentModel || typeof registry.getApiKeyAndHeaders !== "function") return void 0;
|
|
2460
|
-
const result = await registry.getApiKeyAndHeaders(currentModel);
|
|
2461
|
-
if (!result.ok) throw new Error(redactUsageError(result.error));
|
|
2462
|
-
return adapter.id === "openai" || authorizationFrom(result) ? result : void 0;
|
|
2463
|
-
};
|
|
2464
|
-
const resolveSelectedAuthLast = ["deepseek", "minimax", "minimax-cn", "openai"].includes(adapter.id);
|
|
2465
|
-
if (!resolveSelectedAuthLast) modelAuth = await resolveCurrentModelAuth();
|
|
2466
|
-
if (typeof registry.getProviderAuth !== "function") {
|
|
2467
|
-
throw new Error("pi-usage requires Pi 0.81.0 or newer to validate resolved provider auth.");
|
|
2468
|
-
}
|
|
2469
|
-
if (!moonshotProviderAuthIsAllowed(ctx, adapter.id)) return void 0;
|
|
2470
|
-
const providerResult = await registry.getProviderAuth(adapter.id);
|
|
2471
|
-
if (!moonshotProviderAuthIsAllowed(ctx, adapter.id)) return void 0;
|
|
2472
|
-
if (providerResult?.auth.baseUrl && !hasOfficialUrlOrigin(providerResult.auth.baseUrl, adapter.id)) {
|
|
2473
|
-
throw new Error(
|
|
2474
|
-
`${adapter.displayName} usage cannot send a proxy-resolved credential to the official usage endpoint.`
|
|
2475
|
-
);
|
|
2476
|
-
}
|
|
2477
|
-
if (resolveSelectedAuthLast) modelAuth = await resolveCurrentModelAuth();
|
|
2478
|
-
if (modelAuth?.baseUrl && !hasOfficialUrlOrigin(modelAuth.baseUrl, adapter.id)) {
|
|
2479
|
-
throw new Error(
|
|
2480
|
-
`${adapter.displayName} usage cannot send model-resolved proxy credentials to the official usage endpoint.`
|
|
2481
|
-
);
|
|
2482
|
-
}
|
|
2483
|
-
const auth = modelAuth ?? providerResult?.auth;
|
|
2484
|
-
if (!auth) return void 0;
|
|
2485
|
-
const finalize = (resolved) => {
|
|
2486
|
-
const preservedAuth = { ...providerResult?.auth ?? auth };
|
|
2487
|
-
const env = providerResult?.env ?? modelAuth?.env;
|
|
2488
|
-
const source = providerResult?.source;
|
|
2489
|
-
const effectiveBaseUrl = modelAuth?.baseUrl ?? providerResult?.auth.baseUrl ?? provider?.baseUrl ?? model.baseUrl;
|
|
2490
|
-
const redactionInputs = [
|
|
2491
|
-
preservedAuth.apiKey,
|
|
2492
|
-
...Object.values(preservedAuth.headers ?? {}),
|
|
2493
|
-
...Object.values(env ?? {}),
|
|
2494
|
-
modelAuth?.apiKey,
|
|
2495
|
-
...Object.values(modelAuth?.headers ?? {})
|
|
2496
|
-
].filter((value) => typeof value === "string" && value.length > 0);
|
|
2497
|
-
return {
|
|
2498
|
-
...resolved,
|
|
2499
|
-
auth: preservedAuth,
|
|
2500
|
-
...env ? { env: { ...env } } : {},
|
|
2501
|
-
...source ? { source } : {},
|
|
2502
|
-
effectiveBaseUrl,
|
|
2503
|
-
secrets: [.../* @__PURE__ */ new Set([...resolved.secrets, ...redactionInputs])],
|
|
2504
|
-
fingerprint: fingerprintResolvedAuth(
|
|
2505
|
-
{
|
|
2506
|
-
apiKey: resolved.apiKey,
|
|
2507
|
-
headers: resolved.headers,
|
|
2508
|
-
baseUrl: effectiveBaseUrl,
|
|
2509
|
-
env,
|
|
2510
|
-
source,
|
|
2511
|
-
providerAuth: preservedAuth
|
|
2512
|
-
},
|
|
2513
|
-
salt
|
|
2514
|
-
)
|
|
2515
|
-
};
|
|
2516
|
-
};
|
|
2517
|
-
if (adapter.id === "openai") {
|
|
2518
|
-
if (!providerResult) return void 0;
|
|
2519
|
-
if (providerResult.source !== "OAuth") throw new UnsupportedOpenAIUsageAuthError();
|
|
2520
|
-
const offered = candidateReader ? await candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
|
|
2521
|
-
if (!offered.ok) throw new Error("OpenAI OAuth credential discovery failed closed.");
|
|
2522
|
-
return finalize(
|
|
2523
|
-
resolveOpenAIChatGPTAuth(auth, providerResult.auth, providerResult.source, model, offered.candidates)
|
|
2524
|
-
);
|
|
2525
|
-
}
|
|
2526
|
-
if (adapter.id === "github-copilot") {
|
|
2527
|
-
const offered = candidateReader ? await candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
|
|
2528
|
-
if (!offered.ok) {
|
|
2529
|
-
throw new Error("GitHub Copilot OAuth credential discovery failed closed.");
|
|
2530
|
-
}
|
|
2531
|
-
return finalize(resolveGitHubCopilotUsageAuth(auth, model, salt, offered.candidates, offered.offeredCount === 0));
|
|
2532
|
-
}
|
|
2533
|
-
if (adapter.id === "xai") {
|
|
2534
|
-
const offered = candidateReader ? await candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
|
|
2535
|
-
if (!offered.ok) throw new Error("xAI OAuth credential discovery failed closed.");
|
|
2536
|
-
return finalize(resolveXaiUsageAuth(auth, model, salt, offered.candidates));
|
|
2537
|
-
}
|
|
2538
|
-
if (adapter.id === "deepseek") {
|
|
2539
|
-
const resolvedAuthorization = authorizationFrom(auth);
|
|
2540
|
-
const access = bearerToken(resolvedAuthorization);
|
|
2541
|
-
if (!access) throw new Error("DeepSeek API balance requires Bearer authentication.");
|
|
2542
|
-
const authorization2 = `Bearer ${access}`;
|
|
2543
|
-
const headers2 = { Authorization: authorization2 };
|
|
2544
|
-
return finalize({
|
|
2545
|
-
apiKey: access,
|
|
2546
|
-
headers: headers2,
|
|
2547
|
-
fingerprint: "",
|
|
2548
|
-
secrets: [
|
|
2549
|
-
access,
|
|
2550
|
-
auth.apiKey,
|
|
2551
|
-
headerValue(auth.headers, "Authorization"),
|
|
2552
|
-
resolvedAuthorization,
|
|
2553
|
-
authorization2
|
|
2554
|
-
].filter((value) => Boolean(value)),
|
|
2555
|
-
model
|
|
2556
|
-
});
|
|
2557
|
-
}
|
|
2558
|
-
const authorization = authorizationFrom(auth);
|
|
2559
|
-
if (!authorization) return void 0;
|
|
2560
|
-
const headers = { Authorization: authorization };
|
|
2561
|
-
const secrets = [auth.apiKey, headerValue(auth.headers, "Authorization"), authorization].filter(
|
|
2562
|
-
(value) => Boolean(value)
|
|
2563
|
-
);
|
|
2564
|
-
return finalize({
|
|
2565
|
-
apiKey: auth.apiKey,
|
|
2566
|
-
headers,
|
|
2567
|
-
fingerprint: "",
|
|
2568
|
-
secrets,
|
|
2569
|
-
model
|
|
2570
|
-
});
|
|
2571
|
-
}
|
|
2572
|
-
async function queryProviderUsage(adapter, auth, signal, timeoutMs, guard, targetOrSettings) {
|
|
2573
|
-
const startedAt = Date.now();
|
|
2574
|
-
let targetId = typeof targetOrSettings === "string" ? targetOrSettings : adapter.id === "fireworks" ? targetOrSettings?.fireworksAccountId : void 0;
|
|
2575
|
-
let resolvedLegacyFireworksTarget = false;
|
|
2576
|
-
try {
|
|
2577
|
-
if (adapter.id === "fireworks" && typeof targetOrSettings !== "string" && adapter.targets && guard) {
|
|
2578
|
-
const target = await resolveUsageTarget(
|
|
2579
|
-
adapter,
|
|
2580
|
-
auth,
|
|
2581
|
-
targetId,
|
|
2582
|
-
signal,
|
|
2583
|
-
remainingTimeout2(timeoutMs, startedAt, "resolving the Fireworks account"),
|
|
2584
|
-
guard
|
|
2585
|
-
);
|
|
2586
|
-
if (target.kind === "selection-required") {
|
|
2587
|
-
throw new Error("Fireworks account selection is required.");
|
|
2588
|
-
}
|
|
2589
|
-
targetId = target.targetId;
|
|
2590
|
-
resolvedLegacyFireworksTarget = true;
|
|
2591
|
-
}
|
|
2592
|
-
return await adapter.query(
|
|
2593
|
-
auth,
|
|
2594
|
-
signal,
|
|
2595
|
-
resolvedLegacyFireworksTarget ? remainingTimeout2(timeoutMs, startedAt, `querying ${adapter.displayName} usage`) : timeoutMs,
|
|
2596
|
-
guard,
|
|
2597
|
-
targetId
|
|
2598
|
-
);
|
|
2599
|
-
} catch (error) {
|
|
2600
|
-
if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
|
|
2601
|
-
throw new Error(redactUsageError(errorMessage(error), auth.secrets));
|
|
2602
|
-
}
|
|
2603
|
-
}
|
|
2604
|
-
function providerIsConfigured(ctx, providerId) {
|
|
2605
|
-
try {
|
|
2606
|
-
const status = ctx.modelRegistry.getProviderAuthStatus(providerId);
|
|
2607
|
-
return status.configured && moonshotProviderAuthSourceIsAllowed(ctx, providerId, status.source, status.label);
|
|
2608
|
-
} catch {
|
|
2609
|
-
return !isMoonshotSiblingProvider(ctx, providerId) && candidateModels(ctx, providerId).length > 0;
|
|
66
|
+
reason: "Fast mode requires the official OpenAI Codex Responses endpoint."
|
|
67
|
+
};
|
|
2610
68
|
}
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
return moonshotProviderAuthSourceIsAllowed(ctx, providerId, status.source, status.label);
|
|
2617
|
-
} catch {
|
|
2618
|
-
return false;
|
|
69
|
+
if (!CODEX_FAST_MODEL_IDS.has(model.id)) {
|
|
70
|
+
return {
|
|
71
|
+
kind: "unavailable",
|
|
72
|
+
reason: `${model.id} does not advertise Codex Fast support.`
|
|
73
|
+
};
|
|
2619
74
|
}
|
|
75
|
+
return { kind: "available", enabled };
|
|
2620
76
|
}
|
|
2621
|
-
function
|
|
2622
|
-
|
|
2623
|
-
if (source === void 0) return false;
|
|
2624
|
-
if (source !== "environment") return true;
|
|
2625
|
-
return label !== void 0 && !label.split(",").map((name) => name.trim()).includes(SHARED_MOONSHOT_ENV_VAR);
|
|
2626
|
-
}
|
|
2627
|
-
function isMoonshotSiblingProvider(ctx, providerId) {
|
|
2628
|
-
return (providerId === "moonshotai" || providerId === "moonshotai-cn") && ctx.model?.provider !== providerId;
|
|
2629
|
-
}
|
|
2630
|
-
function candidateModels(ctx, providerId) {
|
|
2631
|
-
const candidates = [];
|
|
2632
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2633
|
-
const add = (model) => {
|
|
2634
|
-
if (!model || model.provider !== providerId) return;
|
|
2635
|
-
const key = `${model.provider}/${model.id}`;
|
|
2636
|
-
if (seen.has(key)) return;
|
|
2637
|
-
seen.add(key);
|
|
2638
|
-
candidates.push(model);
|
|
2639
|
-
};
|
|
2640
|
-
add(ctx.model);
|
|
2641
|
-
for (const model of ctx.modelRegistry.getAvailable()) add(model);
|
|
2642
|
-
for (const model of ctx.modelRegistry.getAll()) add(model);
|
|
2643
|
-
return candidates;
|
|
2644
|
-
}
|
|
2645
|
-
async function fetchProviderJson(url, auth, signal, timeoutMs, description, request = {}) {
|
|
2646
|
-
const controller = new AbortController();
|
|
2647
|
-
let timedOut = false;
|
|
2648
|
-
const abortFromCaller = () => controller.abort();
|
|
2649
|
-
if (signal.aborted) controller.abort();
|
|
2650
|
-
else signal.addEventListener("abort", abortFromCaller, { once: true });
|
|
2651
|
-
const timeout = setTimeout(() => {
|
|
2652
|
-
timedOut = true;
|
|
2653
|
-
controller.abort();
|
|
2654
|
-
}, timeoutMs);
|
|
2655
|
-
try {
|
|
2656
|
-
const headers = { ...auth.headers };
|
|
2657
|
-
if (request.userAgent !== false && !hasHeader(headers, "User-Agent")) {
|
|
2658
|
-
headers["User-Agent"] = "pi-usage";
|
|
2659
|
-
}
|
|
2660
|
-
if (request.body && !hasHeader(headers, "Content-Type")) {
|
|
2661
|
-
headers["Content-Type"] = "application/json";
|
|
2662
|
-
}
|
|
2663
|
-
const response = await fetch(url, {
|
|
2664
|
-
method: request.method ?? "GET",
|
|
2665
|
-
headers,
|
|
2666
|
-
...request.body ? { body: JSON.stringify(request.body) } : {},
|
|
2667
|
-
...request.redirect ? { redirect: request.redirect } : {},
|
|
2668
|
-
signal: controller.signal
|
|
2669
|
-
});
|
|
2670
|
-
if (response.redirected) throw new Error(`${description} refused a redirected response.`);
|
|
2671
|
-
if (controller.signal.aborted) throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
2672
|
-
const text = await readBoundedResponse(
|
|
2673
|
-
response,
|
|
2674
|
-
response.ok ? MAX_SUCCESS_BODY_BYTES : MAX_ERROR_BODY_BYTES,
|
|
2675
|
-
!response.ok,
|
|
2676
|
-
description,
|
|
2677
|
-
controller.signal
|
|
2678
|
-
);
|
|
2679
|
-
if (controller.signal.aborted) throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
2680
|
-
const responseError = request.responseError?.(response.status, text);
|
|
2681
|
-
if (responseError) throw new Error(responseError);
|
|
2682
|
-
if (!response.ok) {
|
|
2683
|
-
throw new Error(
|
|
2684
|
-
`${description} returned ${response.status} ${response.statusText}: ${redactUsageError(text, auth.secrets)}`
|
|
2685
|
-
);
|
|
2686
|
-
}
|
|
2687
|
-
let parsed;
|
|
2688
|
-
try {
|
|
2689
|
-
parsed = JSON.parse(text);
|
|
2690
|
-
} catch (error) {
|
|
2691
|
-
throw new Error(`${description} returned invalid JSON: ${errorMessage(error)}`);
|
|
2692
|
-
}
|
|
2693
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2694
|
-
throw new Error(`${description} response was not an object.`);
|
|
2695
|
-
}
|
|
2696
|
-
return parsed;
|
|
2697
|
-
} catch (error) {
|
|
2698
|
-
if (timedOut) {
|
|
2699
|
-
throw new Error(`Timed out after ${Math.round(timeoutMs / 1e3)}s while fetching usage.`);
|
|
2700
|
-
}
|
|
2701
|
-
if (signal.aborted) throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
2702
|
-
throw error;
|
|
2703
|
-
} finally {
|
|
2704
|
-
clearTimeout(timeout);
|
|
2705
|
-
signal.removeEventListener("abort", abortFromCaller);
|
|
2706
|
-
}
|
|
77
|
+
function codexFastIsEffective(model, enabled) {
|
|
78
|
+
return codexFastAvailability(model, enabled).kind === "available" && enabled;
|
|
2707
79
|
}
|
|
2708
|
-
|
|
2709
|
-
if (!
|
|
2710
|
-
|
|
2711
|
-
const chunks = [];
|
|
2712
|
-
let total = 0;
|
|
2713
|
-
let truncated = false;
|
|
2714
|
-
const abort = () => void reader.cancel().catch(() => void 0);
|
|
2715
|
-
if (signal.aborted) abort();
|
|
2716
|
-
else signal.addEventListener("abort", abort, { once: true });
|
|
2717
|
-
try {
|
|
2718
|
-
while (true) {
|
|
2719
|
-
const { done, value } = await reader.read();
|
|
2720
|
-
if (done) break;
|
|
2721
|
-
const remaining = maxBytes - total;
|
|
2722
|
-
if (value.byteLength > remaining) {
|
|
2723
|
-
if (remaining > 0) chunks.push(value.subarray(0, remaining));
|
|
2724
|
-
total = maxBytes;
|
|
2725
|
-
truncated = true;
|
|
2726
|
-
await reader.cancel();
|
|
2727
|
-
break;
|
|
2728
|
-
}
|
|
2729
|
-
chunks.push(value);
|
|
2730
|
-
total += value.byteLength;
|
|
2731
|
-
}
|
|
2732
|
-
} finally {
|
|
2733
|
-
signal.removeEventListener("abort", abort);
|
|
2734
|
-
reader.releaseLock();
|
|
2735
|
-
}
|
|
2736
|
-
if (truncated && !truncateOverflow) {
|
|
2737
|
-
throw new Error(`${description} response exceeded ${maxBytes} bytes.`);
|
|
2738
|
-
}
|
|
2739
|
-
const body = new Uint8Array(total);
|
|
2740
|
-
let offset = 0;
|
|
2741
|
-
for (const chunk of chunks) {
|
|
2742
|
-
body.set(chunk, offset);
|
|
2743
|
-
offset += chunk.byteLength;
|
|
2744
|
-
}
|
|
2745
|
-
const text = new TextDecoder().decode(body);
|
|
2746
|
-
return truncated ? `${text}\u2026` : text;
|
|
80
|
+
function codexFastRequestTier(model, enabled) {
|
|
81
|
+
if (!isOfficialCodexModel(model)) return void 0;
|
|
82
|
+
return enabled && CODEX_FAST_MODEL_IDS.has(model.id) ? CODEX_FAST_SERVICE_TIER : CODEX_STANDARD_SERVICE_TIER;
|
|
2747
83
|
}
|
|
2748
|
-
function
|
|
2749
|
-
const
|
|
2750
|
-
if (!
|
|
2751
|
-
|
|
2752
|
-
let sawMatchingAccess = false;
|
|
2753
|
-
let sawIncompleteMatch = false;
|
|
2754
|
-
const matches = [];
|
|
2755
|
-
for (const candidate of candidates) {
|
|
2756
|
-
try {
|
|
2757
|
-
const credential = asObject13(candidate);
|
|
2758
|
-
if (credential?.type !== "oauth") continue;
|
|
2759
|
-
sawOAuth = true;
|
|
2760
|
-
if (credential.access !== resolvedAccess) continue;
|
|
2761
|
-
sawMatchingAccess = true;
|
|
2762
|
-
if (typeof credential.access !== "string" || !credential.access || typeof credential.refresh !== "string" || !credential.refresh || typeof credential.expires !== "number" || !Number.isFinite(credential.expires)) {
|
|
2763
|
-
sawIncompleteMatch = true;
|
|
2764
|
-
continue;
|
|
2765
|
-
}
|
|
2766
|
-
matches.push({ access: credential.access, refresh: credential.refresh });
|
|
2767
|
-
} catch {
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
if (sawIncompleteMatch) throw new Error("The matching xAI OAuth credential was incomplete.");
|
|
2771
|
-
if (matches.length > 1) {
|
|
2772
|
-
throw new Error("Multiple OAuth credentials match the active xAI runtime account.");
|
|
2773
|
-
}
|
|
2774
|
-
const match = matches[0];
|
|
2775
|
-
if (!match) {
|
|
2776
|
-
if (!sawOAuth) {
|
|
2777
|
-
throw new Error(
|
|
2778
|
-
"xAI consumer usage requires the OAuth subscription account configured through Pi /login; XAI_API_KEY users can review API spend at console.x.ai."
|
|
2779
|
-
);
|
|
2780
|
-
}
|
|
2781
|
-
if (sawMatchingAccess) throw new Error("The matching xAI OAuth credential was incomplete.");
|
|
2782
|
-
throw new Error("The active xAI runtime account does not match Pi's stored OAuth account.");
|
|
2783
|
-
}
|
|
2784
|
-
const authorization = `Bearer ${match.access}`;
|
|
2785
|
-
const headers = { Authorization: authorization };
|
|
2786
|
-
return {
|
|
2787
|
-
apiKey: match.access,
|
|
2788
|
-
headers,
|
|
2789
|
-
fingerprint: fingerprintResolvedAuth({ headers }, salt),
|
|
2790
|
-
secrets: [
|
|
2791
|
-
match.access,
|
|
2792
|
-
match.refresh,
|
|
2793
|
-
resolvedAccess,
|
|
2794
|
-
auth.apiKey,
|
|
2795
|
-
headerValue(auth.headers, "Authorization"),
|
|
2796
|
-
authorization
|
|
2797
|
-
].filter((value) => Boolean(value)),
|
|
2798
|
-
model
|
|
2799
|
-
};
|
|
84
|
+
function rewriteCodexFastPayload(payload, model, enabled) {
|
|
85
|
+
const serviceTier = codexFastRequestTier(model, enabled);
|
|
86
|
+
if (!serviceTier || !isRecord(payload)) return void 0;
|
|
87
|
+
return { ...payload, service_tier: serviceTier };
|
|
2800
88
|
}
|
|
2801
|
-
function
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
let sawOAuth = false;
|
|
2805
|
-
let sawMatchingAccess = false;
|
|
2806
|
-
let sawIncompleteMatch = false;
|
|
2807
|
-
let sawEnterpriseMatch = false;
|
|
2808
|
-
const matches = /* @__PURE__ */ new Map();
|
|
2809
|
-
for (const candidate of candidates) {
|
|
2810
|
-
try {
|
|
2811
|
-
const credential = asObject13(candidate);
|
|
2812
|
-
if (credential?.type !== "oauth") continue;
|
|
2813
|
-
sawOAuth = true;
|
|
2814
|
-
const storedAccess2 = typeof credential.access === "string" && credential.access ? credential.access : void 0;
|
|
2815
|
-
if (storedAccess2 !== resolvedAccess) continue;
|
|
2816
|
-
sawMatchingAccess = true;
|
|
2817
|
-
const enterpriseUrl = credential.enterpriseUrl;
|
|
2818
|
-
if (typeof enterpriseUrl === "string" && enterpriseUrl && !isPublicGitHubDomain(enterpriseUrl)) {
|
|
2819
|
-
sawEnterpriseMatch = true;
|
|
2820
|
-
continue;
|
|
2821
|
-
}
|
|
2822
|
-
const refresh2 = typeof credential.refresh === "string" && credential.refresh ? credential.refresh : void 0;
|
|
2823
|
-
if (!refresh2) {
|
|
2824
|
-
sawIncompleteMatch = true;
|
|
2825
|
-
continue;
|
|
2826
|
-
}
|
|
2827
|
-
matches.set(`${storedAccess2.length}:${storedAccess2}${refresh2}`, { refresh: refresh2, storedAccess: storedAccess2 });
|
|
2828
|
-
} catch {
|
|
2829
|
-
}
|
|
2830
|
-
}
|
|
2831
|
-
if (sawEnterpriseMatch) {
|
|
2832
|
-
throw new Error("GitHub Copilot usage does not yet support GitHub Enterprise accounts.");
|
|
2833
|
-
}
|
|
2834
|
-
if (sawIncompleteMatch) throw new Error("GitHub Copilot OAuth credentials were incomplete.");
|
|
2835
|
-
if (matches.size > 1) {
|
|
2836
|
-
throw new Error("Conflicting OAuth credentials match the active GitHub Copilot runtime account.");
|
|
89
|
+
function correctCodexFastMessageCost(message, model, fastRequested) {
|
|
90
|
+
if (!codexFastIsEffective(model, fastRequested) || !isRecord(message) || message.role !== "assistant" || message.provider !== model?.provider || message.model !== model?.id) {
|
|
91
|
+
return void 0;
|
|
2837
92
|
}
|
|
2838
|
-
const
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
standaloneFallback ? "The active GitHub Copilot runtime account does not match Pi's stored OAuth account." : "The active GitHub Copilot runtime account does not match any available OAuth account."
|
|
2848
|
-
);
|
|
93
|
+
const usage = isRecord(message.usage) ? message.usage : void 0;
|
|
94
|
+
const cost = usage && isRecord(usage.cost) ? usage.cost : void 0;
|
|
95
|
+
if (!usage || !cost || !hasCompleteUsage(usage) || !isOfficialCodexModel(model)) return void 0;
|
|
96
|
+
const correctedUsage = structuredClone(usage);
|
|
97
|
+
calculateCost(model, correctedUsage);
|
|
98
|
+
const multiplier = model.id === "gpt-5.5" ? 2.5 : 2;
|
|
99
|
+
const correctedCost = correctedUsage.cost;
|
|
100
|
+
for (const key of ["input", "output", "cacheRead", "cacheWrite", "total"]) {
|
|
101
|
+
correctedCost[key] *= multiplier;
|
|
2849
102
|
}
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
const headers = {
|
|
2853
|
-
Authorization: authorization,
|
|
2854
|
-
"X-GitHub-Api-Version": "2025-05-01"
|
|
2855
|
-
};
|
|
2856
|
-
return {
|
|
2857
|
-
apiKey: refresh,
|
|
2858
|
-
headers,
|
|
2859
|
-
fingerprint: fingerprintResolvedAuth({ headers }, salt),
|
|
2860
|
-
secrets: [refresh, storedAccess, resolvedAccess, authorization],
|
|
2861
|
-
model
|
|
2862
|
-
};
|
|
2863
|
-
}
|
|
2864
|
-
function authorizationFrom(auth) {
|
|
2865
|
-
return headerValue(auth.headers, "Authorization") ?? (auth.apiKey ? `Bearer ${auth.apiKey}` : void 0);
|
|
2866
|
-
}
|
|
2867
|
-
function bearerToken(authorization) {
|
|
2868
|
-
const match = /^Bearer\s+(.+)$/iu.exec(authorization ?? "");
|
|
2869
|
-
return match?.[1];
|
|
103
|
+
if (costsEqual(cost, correctedCost)) return void 0;
|
|
104
|
+
return { ...message, usage: correctedUsage };
|
|
2870
105
|
}
|
|
2871
|
-
function
|
|
2872
|
-
if (!
|
|
2873
|
-
return
|
|
106
|
+
function codexFastStatusLabel(status, enabled) {
|
|
107
|
+
if (!enabled || !/^codex(?:\s|$)/u.test(status)) return status;
|
|
108
|
+
return status === "codex" ? "codex fast" : `codex fast${status.slice("codex".length)}`;
|
|
2874
109
|
}
|
|
2875
|
-
function
|
|
2876
|
-
|
|
2877
|
-
const url = new URL(value.includes("://") ? value : `https://${value}`);
|
|
2878
|
-
return url.hostname.toLowerCase() === "github.com";
|
|
2879
|
-
} catch {
|
|
110
|
+
function isOfficialCodexModel(model) {
|
|
111
|
+
if (model?.provider !== "openai-codex" || !hasApi(model, "openai-codex-responses")) {
|
|
2880
112
|
return false;
|
|
2881
113
|
}
|
|
2882
|
-
}
|
|
2883
|
-
function hasOfficialOrigin(model, providerId) {
|
|
2884
|
-
return hasOfficialUrlOrigin(model.baseUrl, providerId);
|
|
2885
|
-
}
|
|
2886
|
-
function hasOfficialUrlOrigin(value, providerId) {
|
|
2887
114
|
try {
|
|
2888
|
-
|
|
2889
|
-
if (providerId === "baseten") {
|
|
2890
|
-
return ["https://inference.baseten.co", "https://api.baseten.co"].includes(url.origin);
|
|
2891
|
-
}
|
|
2892
|
-
if (providerId === "openai") return url.origin === "https://api.openai.com";
|
|
2893
|
-
if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
|
|
2894
|
-
if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
|
|
2895
|
-
if (providerId === "fireworks") return url.origin === "https://api.fireworks.ai";
|
|
2896
|
-
if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
|
|
2897
|
-
if (providerId === "vercel-ai-gateway") return url.origin === "https://ai-gateway.vercel.sh";
|
|
2898
|
-
if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
|
|
2899
|
-
if (providerId === "kimi-coding") return url.origin === "https://api.kimi.com";
|
|
2900
|
-
if (providerId === "minimax") return url.origin === "https://api.minimax.io";
|
|
2901
|
-
if (providerId === "minimax-cn") return url.origin === "https://api.minimaxi.com";
|
|
2902
|
-
if (providerId === "moonshotai") return url.origin === "https://api.moonshot.ai";
|
|
2903
|
-
if (providerId === "moonshotai-cn") return url.origin === "https://api.moonshot.cn";
|
|
2904
|
-
if (providerId === "xai") return url.origin === "https://api.x.ai";
|
|
2905
|
-
if (providerId === "zai") return url.origin === "https://api.z.ai";
|
|
2906
|
-
if (providerId === "zai-coding-cn") return url.origin === "https://open.bigmodel.cn";
|
|
2907
|
-
if (providerId === "github-copilot") {
|
|
2908
|
-
return url.protocol === "https:" && /^api\.[a-z0-9-]+\.githubcopilot\.com$/u.test(url.hostname);
|
|
2909
|
-
}
|
|
2910
|
-
return false;
|
|
115
|
+
return new URL(model.baseUrl).origin === "https://chatgpt.com";
|
|
2911
116
|
} catch {
|
|
2912
117
|
return false;
|
|
2913
118
|
}
|
|
2914
119
|
}
|
|
2915
|
-
function
|
|
2916
|
-
|
|
2917
|
-
return entry?.[1] ?? void 0;
|
|
2918
|
-
}
|
|
2919
|
-
function hasHeader(headers, name) {
|
|
2920
|
-
return Object.keys(headers).some((key) => key.toLowerCase() === name.toLowerCase());
|
|
2921
|
-
}
|
|
2922
|
-
function validatedXaiUserId(value) {
|
|
2923
|
-
if (typeof value !== "string" || !/^[A-Za-z0-9._~-]{1,128}$/u.test(value)) {
|
|
2924
|
-
throw new Error("xAI consumer identity returned an unsafe canonical user ID.");
|
|
2925
|
-
}
|
|
2926
|
-
return value;
|
|
2927
|
-
}
|
|
2928
|
-
function basetenBillingUsageUrl(windowAt) {
|
|
2929
|
-
const url = new URL(BASETEN_BILLING_USAGE_URL);
|
|
2930
|
-
url.searchParams.set(
|
|
2931
|
-
"start_date",
|
|
2932
|
-
new Date(windowAt - BASETEN_USAGE_WINDOW_DAYS * 24 * 60 * 60 * 1e3).toISOString()
|
|
2933
|
-
);
|
|
2934
|
-
url.searchParams.set("end_date", new Date(windowAt).toISOString());
|
|
2935
|
-
return url.toString();
|
|
2936
|
-
}
|
|
2937
|
-
async function queryMiniMaxUsage(providerId, auth, signal, timeoutMs, guard) {
|
|
2938
|
-
if (!guard) throw new Error("MiniMax usage requires request-boundary revalidation.");
|
|
2939
|
-
const apiKey = bearerToken(headerValue(auth.headers, "Authorization")) ?? auth.apiKey;
|
|
2940
|
-
if (!apiKey) throw new Error("MiniMax runtime API key was unavailable.");
|
|
2941
|
-
const kind = miniMaxUsageKind(apiKey);
|
|
2942
|
-
const path = kind === "account-balance" ? "/account/query_balance" : "/v1/token_plan/remains";
|
|
2943
|
-
const startedAt = Date.now();
|
|
2944
|
-
await guard();
|
|
2945
|
-
const payload = await fetchProviderJson(
|
|
2946
|
-
`${MINIMAX_API_ROOTS[providerId]}${path}`,
|
|
2947
|
-
auth,
|
|
2948
|
-
signal,
|
|
2949
|
-
remainingTimeout2(timeoutMs, startedAt, "fetching MiniMax usage"),
|
|
2950
|
-
"MiniMax usage endpoint",
|
|
2951
|
-
{ redirect: "error" }
|
|
2952
|
-
);
|
|
2953
|
-
await guard();
|
|
2954
|
-
return normalizeMiniMaxUsagePayload(providerId, kind, payload, Date.now());
|
|
2955
|
-
}
|
|
2956
|
-
async function queryMoonshotBalance(providerId, auth, signal, timeoutMs, guard) {
|
|
2957
|
-
if (!guard) throw new Error("Moonshot AI balance requires request-boundary revalidation.");
|
|
2958
|
-
const startedAt = Date.now();
|
|
2959
|
-
await guard();
|
|
2960
|
-
const payload = await fetchProviderJson(
|
|
2961
|
-
MOONSHOT_BALANCE_URLS[providerId],
|
|
2962
|
-
auth,
|
|
2963
|
-
signal,
|
|
2964
|
-
remainingTimeout2(timeoutMs, startedAt, "fetching Moonshot AI balance"),
|
|
2965
|
-
"Moonshot AI balance endpoint",
|
|
2966
|
-
{ redirect: "error" }
|
|
2967
|
-
);
|
|
2968
|
-
await guard();
|
|
2969
|
-
return normalizeMoonshotBalancePayload(providerId, payload, Date.now());
|
|
2970
|
-
}
|
|
2971
|
-
function remainingTimeout2(timeoutMs, startedAt, description = "fetching xAI consumer usage") {
|
|
2972
|
-
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
2973
|
-
if (remaining <= 0) throw new Error(`Timed out while ${description}.`);
|
|
2974
|
-
return remaining;
|
|
2975
|
-
}
|
|
2976
|
-
function zaiOrigin(baseUrl) {
|
|
2977
|
-
const base = baseUrl?.trim();
|
|
2978
|
-
if (!base) throw new Error("Z.AI model base URL is unavailable.");
|
|
2979
|
-
return new URL(base).origin;
|
|
2980
|
-
}
|
|
2981
|
-
function zaiMonitorUrl(baseUrl) {
|
|
2982
|
-
return `${zaiOrigin(baseUrl)}/api/monitor/usage/quota/limit`;
|
|
2983
|
-
}
|
|
2984
|
-
function zaiMonitorAuth(auth) {
|
|
2985
|
-
const authorization = headerValue(auth.headers, "Authorization");
|
|
2986
|
-
const token = authorization === void 0 ? void 0 : bearerToken(authorization) ?? authorization;
|
|
2987
|
-
if (token === void 0 || token === authorization) return auth;
|
|
2988
|
-
return { ...auth, headers: { ...auth.headers, Authorization: token } };
|
|
120
|
+
function isRecord(value) {
|
|
121
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2989
122
|
}
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
await guard();
|
|
2994
|
-
const payload = await fetchProviderJson(
|
|
2995
|
-
zaiMonitorUrl(auth.model.baseUrl),
|
|
2996
|
-
zaiMonitorAuth(auth),
|
|
2997
|
-
signal,
|
|
2998
|
-
remainingTimeout2(timeoutMs, startedAt, `fetching ${providerName} quota`),
|
|
2999
|
-
`${providerName} quota endpoint`,
|
|
3000
|
-
{ responseError: zaiResponseError }
|
|
123
|
+
function hasCompleteUsage(value) {
|
|
124
|
+
return ["input", "output", "cacheRead", "cacheWrite"].every(
|
|
125
|
+
(key) => typeof value[key] === "number" && Number.isFinite(value[key])
|
|
3001
126
|
);
|
|
3002
|
-
await guard();
|
|
3003
|
-
const planTimeoutMs = timeoutMs - (Date.now() - startedAt);
|
|
3004
|
-
const plan = await fetchZaiPlan(providerName, auth, signal, planTimeoutMs);
|
|
3005
|
-
return normalizeZaiQuotaPayload(providerId, providerName, payload, Date.now(), plan);
|
|
3006
|
-
}
|
|
3007
|
-
async function fetchZaiPlan(providerName, auth, signal, timeoutMs) {
|
|
3008
|
-
if (timeoutMs <= 0 || signal.aborted) return void 0;
|
|
3009
|
-
try {
|
|
3010
|
-
const payload = await fetchProviderJson(
|
|
3011
|
-
`${zaiOrigin(auth.model.baseUrl)}/api/biz/subscription/list`,
|
|
3012
|
-
zaiMonitorAuth(auth),
|
|
3013
|
-
signal,
|
|
3014
|
-
timeoutMs,
|
|
3015
|
-
`${providerName} plan endpoint`,
|
|
3016
|
-
{ responseError: zaiResponseError }
|
|
3017
|
-
);
|
|
3018
|
-
return normalizeZaiSubscriptionPayload(payload);
|
|
3019
|
-
} catch (error) {
|
|
3020
|
-
if (isAbortError(error)) throw error;
|
|
3021
|
-
return void 0;
|
|
3022
|
-
}
|
|
3023
127
|
}
|
|
3024
|
-
function
|
|
3025
|
-
return
|
|
128
|
+
function costsEqual(left, right) {
|
|
129
|
+
return ["input", "output", "cacheRead", "cacheWrite", "total"].every((key) => left[key] === right[key]);
|
|
3026
130
|
}
|
|
3027
131
|
|
|
3028
132
|
// src/codex-resets.ts
|
|
133
|
+
import { readStoredCredential } from "@earendil-works/pi-coding-agent";
|
|
3029
134
|
var CODEX_RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
|
|
3030
135
|
var CODEX_RESET_CONSUME_URL = `${CODEX_RESET_CREDITS_URL}/consume`;
|
|
3031
136
|
var MAX_RESET_OPTIONS = 32;
|
|
3032
137
|
var MAX_CREDIT_ID_CHARS = 1024;
|
|
3033
|
-
function codexResetCount(
|
|
3034
|
-
const value =
|
|
138
|
+
function codexResetCount(report) {
|
|
139
|
+
const value = report.metrics.find((metric) => metric.id === "reset-credits")?.value;
|
|
3035
140
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
3036
141
|
}
|
|
3037
|
-
function codexResetActionDescription(
|
|
3038
|
-
const count = codexResetCount(
|
|
142
|
+
function codexResetActionDescription(report) {
|
|
143
|
+
const count = codexResetCount(report);
|
|
3039
144
|
if (count === void 0) return "Check reset availability.";
|
|
3040
145
|
if (count === 0) return "No usage limit resets available.";
|
|
3041
146
|
return `You have ${count} ${resetLabel(count)} available.`;
|
|
@@ -3076,7 +181,7 @@ function formatCodexResetOutcome(outcome, remainingCount) {
|
|
|
3076
181
|
function resetLabel(count) {
|
|
3077
182
|
return count === 1 ? "usage limit reset" : "usage limit resets";
|
|
3078
183
|
}
|
|
3079
|
-
async function resolveCodexResetAuth(ctx, salt = AUTH_FINGERPRINT_SALT, credentialReader =
|
|
184
|
+
async function resolveCodexResetAuth(ctx, salt = AUTH_FINGERPRINT_SALT, credentialReader = readStoredCredential, candidateReader) {
|
|
3080
185
|
const model = ctx.model;
|
|
3081
186
|
if (model?.provider !== "openai-codex") {
|
|
3082
187
|
throw new Error("Usage limit resets require the current model to use OpenAI Codex.");
|
|
@@ -3094,7 +199,7 @@ async function resolveCodexResetAuth(ctx, salt = AUTH_FINGERPRINT_SALT, credenti
|
|
|
3094
199
|
if (!auth) throw new Error("No runtime credential is configured for OpenAI Codex.");
|
|
3095
200
|
const offered = candidateReader ? await candidateReader(ctx, "openai-codex") : fallbackOAuthCredentialCandidates("openai-codex", credentialReader);
|
|
3096
201
|
if (!offered.ok) throw new Error("OpenAI Codex OAuth credential discovery failed closed.");
|
|
3097
|
-
const resolvedAccess =
|
|
202
|
+
const resolvedAccess = bearerToken(headerValue(auth.headers, "Authorization")) ?? auth.apiKey;
|
|
3098
203
|
if (!resolvedAccess) throw new Error("OpenAI Codex OAuth credentials were incomplete.");
|
|
3099
204
|
const resolvedAccountId = codexAccountIdFromAccessToken(resolvedAccess);
|
|
3100
205
|
if (!resolvedAccountId) {
|
|
@@ -3149,14 +254,14 @@ async function consumeCodexResetCredit(auth, option, redeemRequestId, signal, ti
|
|
|
3149
254
|
if (!isCodexResetOutcomeCode(code)) {
|
|
3150
255
|
throw new Error("Codex reset consume endpoint returned an unknown outcome code.");
|
|
3151
256
|
}
|
|
3152
|
-
const windowsReset = payload.windows_reset === void 0 ? 0 :
|
|
257
|
+
const windowsReset = payload.windows_reset === void 0 ? 0 : nonnegativeInteger(payload.windows_reset);
|
|
3153
258
|
if (windowsReset === void 0) {
|
|
3154
259
|
throw new Error("Codex reset consume endpoint returned an invalid windows_reset value.");
|
|
3155
260
|
}
|
|
3156
261
|
return { code, windowsReset };
|
|
3157
262
|
}
|
|
3158
263
|
function normalizeCodexResetCreditsPayload(payload) {
|
|
3159
|
-
const availableCount =
|
|
264
|
+
const availableCount = nonnegativeInteger(payload.available_count);
|
|
3160
265
|
if (availableCount === void 0) {
|
|
3161
266
|
throw new Error("Codex reset credits response returned an invalid available_count.");
|
|
3162
267
|
}
|
|
@@ -3164,7 +269,7 @@ function normalizeCodexResetCreditsPayload(payload) {
|
|
|
3164
269
|
if (rawCredits !== void 0 && !Array.isArray(rawCredits)) {
|
|
3165
270
|
throw new Error("Codex reset credits response returned invalid credits.");
|
|
3166
271
|
}
|
|
3167
|
-
const options = (rawCredits ?? []).map(
|
|
272
|
+
const options = (rawCredits ?? []).map(asObject).filter((credit) => Boolean(credit)).filter((credit) => credit.status === "available" && credit.reset_type === "codex_rate_limits").map(normalizeResetOption).sort((left, right) => (left.expiresAt ?? Number.MAX_SAFE_INTEGER) - (right.expiresAt ?? Number.MAX_SAFE_INTEGER)).slice(0, Math.min(availableCount, MAX_RESET_OPTIONS));
|
|
3168
273
|
if (availableCount > 0 && options.length === 0) {
|
|
3169
274
|
options.push(genericCodexResetOption());
|
|
3170
275
|
}
|
|
@@ -3177,7 +282,7 @@ function selectCodexResetCredential(candidates, resolvedAccess, resolvedAccountI
|
|
|
3177
282
|
const matches = /* @__PURE__ */ new Map();
|
|
3178
283
|
for (const candidate of candidates) {
|
|
3179
284
|
try {
|
|
3180
|
-
const credential =
|
|
285
|
+
const credential = asObject(candidate);
|
|
3181
286
|
if (credential?.type !== "oauth") continue;
|
|
3182
287
|
sawOAuth = true;
|
|
3183
288
|
const storedAccess = asNonemptyString(credential.access);
|
|
@@ -3216,7 +321,7 @@ function codexAccountIdFromAccessToken(access) {
|
|
|
3216
321
|
const parts = access.split(".");
|
|
3217
322
|
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
3218
323
|
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
3219
|
-
const claims =
|
|
324
|
+
const claims = asObject(asObject(payload)?.["https://api.openai.com/auth"]);
|
|
3220
325
|
return validHeaderValue(claims?.chatgpt_account_id);
|
|
3221
326
|
} catch {
|
|
3222
327
|
return void 0;
|
|
@@ -3248,7 +353,7 @@ function normalizeResetOption(credit) {
|
|
|
3248
353
|
function isCodexResetOutcomeCode(value) {
|
|
3249
354
|
return value === "reset" || value === "nothing_to_reset" || value === "no_credit" || value === "already_redeemed";
|
|
3250
355
|
}
|
|
3251
|
-
function
|
|
356
|
+
function asObject(value) {
|
|
3252
357
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
3253
358
|
return value;
|
|
3254
359
|
}
|
|
@@ -3270,75 +375,85 @@ function validHeaderValue(value) {
|
|
|
3270
375
|
if (/[^\x20-\x7e]/u.test(value)) return void 0;
|
|
3271
376
|
return value;
|
|
3272
377
|
}
|
|
3273
|
-
function
|
|
378
|
+
function nonnegativeInteger(value) {
|
|
3274
379
|
const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
3275
380
|
if (!Number.isSafeInteger(parsed) || parsed < 0) return void 0;
|
|
3276
381
|
return parsed;
|
|
3277
382
|
}
|
|
3278
|
-
function
|
|
383
|
+
function bearerToken(authorization) {
|
|
3279
384
|
return /^Bearer\s+(.+)$/iu.exec(authorization ?? "")?.[1];
|
|
3280
385
|
}
|
|
3281
|
-
function
|
|
386
|
+
function headerValue(headers, name) {
|
|
3282
387
|
return Object.entries(headers).find(([candidate]) => candidate.toLowerCase() === name.toLowerCase())?.[1];
|
|
3283
388
|
}
|
|
3284
389
|
|
|
3285
390
|
// src/format.ts
|
|
3286
391
|
var BAR_SEGMENTS = 20;
|
|
3287
392
|
var VALUE_COLUMN = 29;
|
|
3288
|
-
function formatUsageReport(
|
|
393
|
+
function formatUsageReport(report, displayState) {
|
|
3289
394
|
const stateLabel = displayState === "current" ? "Current" : "Configured";
|
|
3290
|
-
const title =
|
|
395
|
+
const title = report.providerId === "baseten" ? "Baseten Model APIs Spend" : report.providerId === "deepseek" ? "DeepSeek API Balance" : report.providerId === "fireworks" ? "Fireworks API Spend" : report.providerId === "vercel-ai-gateway" ? "Vercel AI Gateway Credits" : report.providerId === "moonshotai" || report.providerId === "moonshotai-cn" ? `${report.providerName} Balance` : report.providerId === "minimax" || report.providerId === "minimax-cn" ? report.source === "minimax-account-balance" ? `${report.providerName} API Balance` : `${report.providerName} Token Plan` : report.source === "openai-chatgpt-auth" ? `${report.providerName} ChatGPT Plan Status` : `${report.providerName} Usage`;
|
|
3291
396
|
const lines = [`${title} \xB7 ${stateLabel}`];
|
|
3292
|
-
if (
|
|
3293
|
-
lines.push(`Semantics: ${
|
|
3294
|
-
if (
|
|
3295
|
-
else if (
|
|
3296
|
-
else if (
|
|
3297
|
-
else if (
|
|
3298
|
-
else if (
|
|
3299
|
-
else if (
|
|
3300
|
-
else if (
|
|
3301
|
-
else if (
|
|
3302
|
-
else if (
|
|
3303
|
-
else if (
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
else if (
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
397
|
+
if (report.accountLabel) lines.push(`Account: ${report.accountLabel}`);
|
|
398
|
+
lines.push(`Semantics: ${report.semantics.label}`, "");
|
|
399
|
+
if (report.source === "openai-chatgpt-companion") formatOpenAICompanionReport(lines, report);
|
|
400
|
+
else if (report.providerId === "baseten") formatBasetenReport(lines, report);
|
|
401
|
+
else if (report.providerId === "openai-codex") formatCodexReport(lines, report);
|
|
402
|
+
else if (report.providerId === "deepseek") formatDeepSeekReport(lines, report);
|
|
403
|
+
else if (report.providerId === "fireworks") formatFireworksReport(lines, report);
|
|
404
|
+
else if (report.providerId === "vercel-ai-gateway") formatVercelAIGatewayReport(lines, report);
|
|
405
|
+
else if (report.providerId === "github-copilot") formatGitHubCopilotReport(lines, report);
|
|
406
|
+
else if (report.providerId === "openrouter") formatOpenRouterReport(lines, report);
|
|
407
|
+
else if (report.providerId === "opencode-go") formatOpenCodeZenReport(lines, report);
|
|
408
|
+
else if (report.providerId === "kimi-coding") formatKimiCodingReport(lines, report);
|
|
409
|
+
else if (report.providerId === "moonshotai" || report.providerId === "moonshotai-cn") {
|
|
410
|
+
formatMoonshotReport(lines, report);
|
|
411
|
+
} else if (report.providerId === "minimax" || report.providerId === "minimax-cn") {
|
|
412
|
+
formatMiniMaxReport(lines, report);
|
|
413
|
+
} else if (report.providerId === "xai") formatXaiReport(lines, report);
|
|
414
|
+
else if (report.providerId === "zai" || report.providerId === "zai-coding-cn") {
|
|
415
|
+
formatZaiReport(lines, report);
|
|
416
|
+
} else formatGenericReport(lines, report);
|
|
417
|
+
if (report.notes) {
|
|
418
|
+
for (const note of report.notes) lines.push(note);
|
|
3313
419
|
}
|
|
3314
420
|
return lines.join("\n").trimEnd();
|
|
3315
421
|
}
|
|
3316
|
-
function formatUsageStatusline(
|
|
3317
|
-
if (
|
|
3318
|
-
if (
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
422
|
+
function formatUsageStatusline(report, model, now = Date.now(), showCodexResetCountdown = true, codexStatusPercentage = "remaining") {
|
|
423
|
+
if (report.providerId === "openai" && report.source === "openai-chatgpt-auth") return "chatgpt usage: web only";
|
|
424
|
+
if (report.source === "openai-chatgpt-companion") {
|
|
425
|
+
return `chatgpt ${["chatgpt-plan", "chatgpt-app"].map((group) => {
|
|
426
|
+
const windows = report.buckets.filter((bucket) => bucket.groupId === group).map((bucket) => {
|
|
427
|
+
const reset = formatResetCountdown(bucket.resetsAt, now);
|
|
428
|
+
return `${bucket.remaining === void 0 ? "unavailable" : `${bucket.remaining.toFixed(0)}%`} ${reset ? `\u21BB ${reset}` : formatWindowLabel(bucket.windowMinutes, "weekly", true)}`;
|
|
429
|
+
});
|
|
430
|
+
return `${group === "chatgpt-plan" ? "plan" : "app"} ${windows.join(" ")}`;
|
|
431
|
+
}).join(" \xB7 ")}`;
|
|
432
|
+
}
|
|
433
|
+
if (report.providerId === "baseten") return formatBasetenStatusline(report);
|
|
434
|
+
if (report.providerId === "openai-codex") {
|
|
435
|
+
return formatCodexStatusline(report, model, now, showCodexResetCountdown, codexStatusPercentage);
|
|
436
|
+
}
|
|
437
|
+
if (report.providerId === "deepseek") return formatDeepSeekStatusline(report);
|
|
438
|
+
if (report.providerId === "fireworks") return formatFireworksStatusline(report);
|
|
439
|
+
if (report.providerId === "vercel-ai-gateway") return formatVercelAIGatewayStatusline(report);
|
|
440
|
+
if (report.providerId === "github-copilot") return formatGitHubCopilotStatusline(report);
|
|
441
|
+
if (report.providerId === "openrouter") {
|
|
442
|
+
const limit = report.buckets.find((bucket) => bucket.id === "key-limit");
|
|
3328
443
|
if (limit?.remaining !== void 0) return `openrouter ${formatUsd(limit.remaining)} left`;
|
|
3329
|
-
const total =
|
|
444
|
+
const total = report.metrics.find((metric) => metric.id === "usage-total");
|
|
3330
445
|
if (typeof total?.value === "number") return `openrouter ${formatUsd(total.value)} used`;
|
|
3331
446
|
}
|
|
3332
|
-
if (
|
|
3333
|
-
if (
|
|
3334
|
-
if (
|
|
3335
|
-
return formatMoonshotStatusline(
|
|
447
|
+
if (report.providerId === "opencode-go") return formatOpenCodeZenStatusline(report);
|
|
448
|
+
if (report.providerId === "kimi-coding") return formatKimiCodingStatusline(report);
|
|
449
|
+
if (report.providerId === "moonshotai" || report.providerId === "moonshotai-cn") {
|
|
450
|
+
return formatMoonshotStatusline(report);
|
|
3336
451
|
}
|
|
3337
|
-
if (
|
|
3338
|
-
return formatMiniMaxStatusline(
|
|
452
|
+
if (report.providerId === "minimax" || report.providerId === "minimax-cn") {
|
|
453
|
+
return formatMiniMaxStatusline(report, model);
|
|
3339
454
|
}
|
|
3340
|
-
if (
|
|
3341
|
-
return formatZaiStatusline(
|
|
455
|
+
if (report.providerId === "zai" || report.providerId === "zai-coding-cn") {
|
|
456
|
+
return formatZaiStatusline(report);
|
|
3342
457
|
}
|
|
3343
458
|
return void 0;
|
|
3344
459
|
}
|
|
@@ -3355,19 +470,19 @@ Selection required: choose this provider's ${state.singularLabel} by viewing it
|
|
|
3355
470
|
${status}: ${state.message}`;
|
|
3356
471
|
}).join("\n\n");
|
|
3357
472
|
}
|
|
3358
|
-
function formatBasetenReport(lines,
|
|
473
|
+
function formatBasetenReport(lines, report) {
|
|
3359
474
|
lines.push(`${"Spend window:".padEnd(VALUE_COLUMN)}Last 30 days`);
|
|
3360
|
-
for (const
|
|
3361
|
-
lines.push(`${`${
|
|
475
|
+
for (const metric of report.metrics) {
|
|
476
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}USD ${metric.value}`);
|
|
3362
477
|
}
|
|
3363
478
|
}
|
|
3364
|
-
function formatBasetenStatusline(
|
|
3365
|
-
const subtotal =
|
|
479
|
+
function formatBasetenStatusline(report) {
|
|
480
|
+
const subtotal = report.metrics.find((metric) => metric.id === "net-subtotal");
|
|
3366
481
|
return subtotal ? `baseten USD ${subtotal.value} net` : "baseten no Model APIs usage";
|
|
3367
482
|
}
|
|
3368
|
-
function formatCodexReport(lines,
|
|
483
|
+
function formatCodexReport(lines, report) {
|
|
3369
484
|
let previousGroup;
|
|
3370
|
-
for (const bucket of
|
|
485
|
+
for (const bucket of report.buckets) {
|
|
3371
486
|
const group = bucket.groupId ?? bucket.id;
|
|
3372
487
|
if (group !== previousGroup && group !== "codex") {
|
|
3373
488
|
lines.push(`${bucket.groupLabel ?? group} limit:`);
|
|
@@ -3377,71 +492,71 @@ function formatCodexReport(lines, report2) {
|
|
|
3377
492
|
const label = `${formatWindowLabel(bucket.windowMinutes, fallback, false)} limit:`;
|
|
3378
493
|
lines.push(`${label.padEnd(VALUE_COLUMN)}${formatPercentBucket(bucket)}`);
|
|
3379
494
|
}
|
|
3380
|
-
for (const
|
|
3381
|
-
if (
|
|
3382
|
-
lines.push(`${"Usage limit resets:".padEnd(VALUE_COLUMN)}${
|
|
3383
|
-
} else if (
|
|
3384
|
-
lines.push(`${"Credits:".padEnd(VALUE_COLUMN)}${formatMetricValue(
|
|
495
|
+
for (const metric of report.metrics) {
|
|
496
|
+
if (metric.id === "reset-credits") {
|
|
497
|
+
lines.push(`${"Usage limit resets:".padEnd(VALUE_COLUMN)}${metric.value} available`);
|
|
498
|
+
} else if (metric.id === "credits") {
|
|
499
|
+
lines.push(`${"Credits:".padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`);
|
|
3385
500
|
}
|
|
3386
501
|
}
|
|
3387
502
|
}
|
|
3388
|
-
function formatDeepSeekReport(lines,
|
|
3389
|
-
const availability =
|
|
503
|
+
function formatDeepSeekReport(lines, report) {
|
|
504
|
+
const availability = report.metrics.find((metric) => metric.id === "api-availability");
|
|
3390
505
|
lines.push(
|
|
3391
506
|
`${"API calls:".padEnd(VALUE_COLUMN)}${availability?.value === "available" ? "Available" : "Unavailable"}`
|
|
3392
507
|
);
|
|
3393
508
|
for (const currency of ["CNY", "USD"]) {
|
|
3394
|
-
const metrics =
|
|
509
|
+
const metrics = report.metrics.filter((metric) => metric.currency === currency);
|
|
3395
510
|
if (metrics.length === 0) continue;
|
|
3396
511
|
lines.push("", `${currency} balance:`);
|
|
3397
|
-
for (const
|
|
3398
|
-
lines.push(`${`${
|
|
512
|
+
for (const metric of metrics) {
|
|
513
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${currency} ${metric.value}`);
|
|
3399
514
|
}
|
|
3400
515
|
}
|
|
3401
516
|
}
|
|
3402
|
-
function formatDeepSeekStatusline(
|
|
3403
|
-
const availability =
|
|
517
|
+
function formatDeepSeekStatusline(report) {
|
|
518
|
+
const availability = report.metrics.find((metric) => metric.id === "api-availability");
|
|
3404
519
|
if (availability?.value !== "available") return "deepseek API unavailable";
|
|
3405
520
|
const totals = ["CNY", "USD"].flatMap((currency) => {
|
|
3406
|
-
const
|
|
3407
|
-
return
|
|
521
|
+
const metric = report.metrics.find((candidate) => candidate.id === `${currency.toLowerCase()}-total`);
|
|
522
|
+
return metric ? [`${currency} ${metric.value}`] : [];
|
|
3408
523
|
});
|
|
3409
524
|
return totals.length > 0 ? `deepseek ${totals.join(" \xB7 ")}` : "deepseek balance unavailable";
|
|
3410
525
|
}
|
|
3411
|
-
function formatFireworksReport(lines,
|
|
526
|
+
function formatFireworksReport(lines, report) {
|
|
3412
527
|
lines.push(`${"Spend window:".padEnd(VALUE_COLUMN)}Last 30 days (rated)`);
|
|
3413
|
-
for (const currency of fireworksCurrencies(
|
|
528
|
+
for (const currency of fireworksCurrencies(report)) {
|
|
3414
529
|
lines.push("", `${currency} rated spend:`);
|
|
3415
|
-
for (const
|
|
3416
|
-
if (
|
|
3417
|
-
lines.push(`${`${
|
|
530
|
+
for (const metric of report.metrics) {
|
|
531
|
+
if (metric.currency !== currency) continue;
|
|
532
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${currency} ${metric.value}`);
|
|
3418
533
|
}
|
|
3419
534
|
}
|
|
3420
535
|
}
|
|
3421
|
-
function formatFireworksStatusline(
|
|
3422
|
-
const totals =
|
|
536
|
+
function formatFireworksStatusline(report) {
|
|
537
|
+
const totals = report.metrics.filter((metric) => metric.id.endsWith("-total"));
|
|
3423
538
|
if (totals.length === 0) return "fireworks no rated usage";
|
|
3424
|
-
return `fireworks ${totals.map((
|
|
539
|
+
return `fireworks ${totals.map((metric) => `${metric.currency} ${metric.value}`).join(" \xB7 ")}`;
|
|
3425
540
|
}
|
|
3426
|
-
function fireworksCurrencies(
|
|
541
|
+
function fireworksCurrencies(report) {
|
|
3427
542
|
const currencies = [];
|
|
3428
|
-
for (const
|
|
3429
|
-
if (!
|
|
3430
|
-
currencies.push(
|
|
543
|
+
for (const metric of report.metrics) {
|
|
544
|
+
if (!metric.currency || currencies.includes(metric.currency)) continue;
|
|
545
|
+
currencies.push(metric.currency);
|
|
3431
546
|
}
|
|
3432
547
|
return currencies;
|
|
3433
548
|
}
|
|
3434
|
-
function formatVercelAIGatewayReport(lines,
|
|
3435
|
-
for (const
|
|
3436
|
-
lines.push(`${`${
|
|
549
|
+
function formatVercelAIGatewayReport(lines, report) {
|
|
550
|
+
for (const metric of report.metrics) {
|
|
551
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}USD ${metric.value}`);
|
|
3437
552
|
}
|
|
3438
553
|
}
|
|
3439
|
-
function formatVercelAIGatewayStatusline(
|
|
3440
|
-
const balance =
|
|
554
|
+
function formatVercelAIGatewayStatusline(report) {
|
|
555
|
+
const balance = report.metrics.find((metric) => metric.id === "credit-balance");
|
|
3441
556
|
return balance ? `vercel USD ${balance.value} left` : "vercel credits unavailable";
|
|
3442
557
|
}
|
|
3443
|
-
function formatGitHubCopilotReport(lines,
|
|
3444
|
-
const quota = findGitHubCopilotQuota(
|
|
558
|
+
function formatGitHubCopilotReport(lines, report) {
|
|
559
|
+
const quota = findGitHubCopilotQuota(report);
|
|
3445
560
|
if (!quota || quota.limit === void 0 || quota.remaining === void 0) {
|
|
3446
561
|
lines.push(`${`${quota?.label ?? "Copilot quota"}:`.padEnd(VALUE_COLUMN)}unlimited`);
|
|
3447
562
|
return;
|
|
@@ -3451,23 +566,23 @@ function formatGitHubCopilotReport(lines, report2) {
|
|
|
3451
566
|
lines.push(
|
|
3452
567
|
`${`${quota.label}:`.padEnd(VALUE_COLUMN)}${quota.remaining} of ${quota.limit} left \xB7 ${percent}%${reset}`
|
|
3453
568
|
);
|
|
3454
|
-
const overage =
|
|
569
|
+
const overage = report.metrics.find((metric) => metric.id === "overage-used");
|
|
3455
570
|
if (typeof overage?.value === "number" && overage.value > 0) {
|
|
3456
571
|
lines.push(`${"Additional usage:".padEnd(VALUE_COLUMN)}${overage.value} ${quota.label}`);
|
|
3457
572
|
}
|
|
3458
573
|
}
|
|
3459
|
-
function formatGitHubCopilotStatusline(
|
|
3460
|
-
const quota = findGitHubCopilotQuota(
|
|
574
|
+
function formatGitHubCopilotStatusline(report) {
|
|
575
|
+
const quota = findGitHubCopilotQuota(report);
|
|
3461
576
|
const kind = compactGitHubCopilotQuotaKind(quota);
|
|
3462
577
|
if (!quota || quota.limit === void 0 || quota.remaining === void 0) {
|
|
3463
578
|
return `copilot ${kind} unlimited`;
|
|
3464
579
|
}
|
|
3465
|
-
const overage =
|
|
580
|
+
const overage = report.metrics.find((metric) => metric.id === "overage-used");
|
|
3466
581
|
const overageSuffix = typeof overage?.value === "number" && overage.value > 0 ? ` +${overage.value} over` : "";
|
|
3467
582
|
return `copilot ${kind === "premium" ? "" : `${kind} `}${quota.remaining}/${quota.limit} ${percentRemaining(quota)}%${overageSuffix}`;
|
|
3468
583
|
}
|
|
3469
|
-
function findGitHubCopilotQuota(
|
|
3470
|
-
return
|
|
584
|
+
function findGitHubCopilotQuota(report) {
|
|
585
|
+
return report.buckets.find((bucket) => ["ai-credits", "premium-requests", "chat-requests"].includes(bucket.id));
|
|
3471
586
|
}
|
|
3472
587
|
function compactGitHubCopilotQuotaKind(bucket) {
|
|
3473
588
|
if (bucket?.id === "ai-credits") return "credits";
|
|
@@ -3476,21 +591,21 @@ function compactGitHubCopilotQuotaKind(bucket) {
|
|
|
3476
591
|
}
|
|
3477
592
|
function percentRemaining(bucket) {
|
|
3478
593
|
if (!bucket.limit || bucket.remaining === void 0) return 0;
|
|
3479
|
-
return Math.round(
|
|
594
|
+
return Math.round(clampPercent(bucket.remaining / bucket.limit * 100));
|
|
3480
595
|
}
|
|
3481
|
-
function formatOpenRouterReport(lines,
|
|
3482
|
-
const limit =
|
|
596
|
+
function formatOpenRouterReport(lines, report) {
|
|
597
|
+
const limit = report.buckets.find((bucket) => bucket.id === "key-limit");
|
|
3483
598
|
if (limit) {
|
|
3484
599
|
const period = limit.period ? ` (${limit.period})` : "";
|
|
3485
600
|
const value = limit.remaining === void 0 ? `${formatUsd(limit.limit ?? 0)} cap; remaining unavailable` : `${formatUsd(limit.remaining)} of ${formatUsd(limit.limit ?? 0)} left`;
|
|
3486
601
|
lines.push(`${`Key limit${period}:`.padEnd(VALUE_COLUMN)}${value}`);
|
|
3487
602
|
}
|
|
3488
|
-
for (const
|
|
3489
|
-
lines.push(`${`${
|
|
603
|
+
for (const metric of report.metrics) {
|
|
604
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`);
|
|
3490
605
|
}
|
|
3491
606
|
}
|
|
3492
|
-
function formatOpenCodeZenReport(lines,
|
|
3493
|
-
for (const bucket of
|
|
607
|
+
function formatOpenCodeZenReport(lines, report) {
|
|
608
|
+
for (const bucket of report.buckets) {
|
|
3494
609
|
if (bucket.unit === "percent" && bucket.used !== void 0) {
|
|
3495
610
|
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${formatPercentBucket(bucket)}`);
|
|
3496
611
|
continue;
|
|
@@ -3500,20 +615,20 @@ function formatOpenCodeZenReport(lines, report2) {
|
|
|
3500
615
|
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${used}% used${reset}`);
|
|
3501
616
|
}
|
|
3502
617
|
}
|
|
3503
|
-
function formatOpenCodeZenStatusline(
|
|
618
|
+
function formatOpenCodeZenStatusline(report) {
|
|
3504
619
|
const parts = ["zen"];
|
|
3505
|
-
for (const bucket of
|
|
620
|
+
for (const bucket of report.buckets) {
|
|
3506
621
|
if (bucket.used === void 0) continue;
|
|
3507
622
|
const compact = bucket.id === "rolling" ? "r" : bucket.id === "weekly" ? "w" : "m";
|
|
3508
|
-
parts.push(`${
|
|
623
|
+
parts.push(`${clampPercent(bucket.used).toFixed(0)}% ${compact}`);
|
|
3509
624
|
}
|
|
3510
625
|
return parts.length > 1 ? parts.join(" ") : void 0;
|
|
3511
626
|
}
|
|
3512
|
-
function formatKimiCodingReport(lines,
|
|
3513
|
-
for (const bucket of
|
|
627
|
+
function formatKimiCodingReport(lines, report) {
|
|
628
|
+
for (const bucket of report.buckets) {
|
|
3514
629
|
const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
|
|
3515
630
|
if (bucket.unit === "percent") {
|
|
3516
|
-
const remaining = bucket.remaining === void 0 ? void 0 : Math.round(
|
|
631
|
+
const remaining = bucket.remaining === void 0 ? void 0 : Math.round(clampPercent(bucket.remaining));
|
|
3517
632
|
const value = bucket.used === void 0 || remaining === void 0 ? "unavailable" : `${100 - remaining}% used \xB7 ${remaining}% left`;
|
|
3518
633
|
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${value}${reset}`);
|
|
3519
634
|
continue;
|
|
@@ -3526,10 +641,10 @@ function formatKimiCodingReport(lines, report2) {
|
|
|
3526
641
|
`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${bucket.used} of ${bucket.limit} used \xB7 ${percentRemaining(bucket)}% left${reset}`
|
|
3527
642
|
);
|
|
3528
643
|
}
|
|
3529
|
-
const balance =
|
|
3530
|
-
const total =
|
|
3531
|
-
const monthlyUsed =
|
|
3532
|
-
const monthlyLimit =
|
|
644
|
+
const balance = report.metrics.find((metric) => metric.id === "booster-balance");
|
|
645
|
+
const total = report.metrics.find((metric) => metric.id === "booster-total");
|
|
646
|
+
const monthlyUsed = report.metrics.find((metric) => metric.id === "booster-monthly-used");
|
|
647
|
+
const monthlyLimit = report.metrics.find((metric) => metric.id === "booster-monthly-limit");
|
|
3533
648
|
if (!balance && !monthlyUsed && !monthlyLimit) return;
|
|
3534
649
|
lines.push("", "Extra usage wallet:");
|
|
3535
650
|
if (balance) {
|
|
@@ -3543,11 +658,11 @@ function formatKimiCodingReport(lines, report2) {
|
|
|
3543
658
|
lines.push(`${"Monthly limit:".padEnd(VALUE_COLUMN)}${formatCurrencyMetric(monthlyLimit)}`);
|
|
3544
659
|
}
|
|
3545
660
|
}
|
|
3546
|
-
function formatKimiCodingStatusline(
|
|
3547
|
-
const fiveHour =
|
|
3548
|
-
const weekly =
|
|
3549
|
-
const monthly =
|
|
3550
|
-
const subWindow = fiveHour ??
|
|
661
|
+
function formatKimiCodingStatusline(report) {
|
|
662
|
+
const fiveHour = report.buckets.find((bucket) => bucket.id === "five-hour");
|
|
663
|
+
const weekly = report.buckets.find((bucket) => bucket.id === "weekly");
|
|
664
|
+
const monthly = report.buckets.find((bucket) => bucket.id === "monthly");
|
|
665
|
+
const subWindow = fiveHour ?? report.buckets.find((bucket) => bucket.id !== "weekly" && bucket.id !== "monthly");
|
|
3551
666
|
const selected = [subWindow, weekly, monthly].filter(
|
|
3552
667
|
(bucket, index, buckets) => bucket !== void 0 && buckets.indexOf(bucket) === index
|
|
3553
668
|
);
|
|
@@ -3556,30 +671,30 @@ function formatKimiCodingStatusline(report2) {
|
|
|
3556
671
|
if (bucket.remaining === void 0 || bucket.unit !== "percent" && !bucket.limit) continue;
|
|
3557
672
|
const fallback = bucket.id === "weekly" ? "weekly" : "5h";
|
|
3558
673
|
const window = bucket.id === "monthly" ? "mo" : formatWindowLabel(bucket.windowMinutes, fallback, true);
|
|
3559
|
-
const remaining = bucket.unit === "percent" ? Math.round(
|
|
674
|
+
const remaining = bucket.unit === "percent" ? Math.round(clampPercent(bucket.remaining)) : percentRemaining(bucket);
|
|
3560
675
|
parts.push(`${remaining}% ${window}`);
|
|
3561
676
|
}
|
|
3562
677
|
return parts.length > 1 ? parts.join(" ") : void 0;
|
|
3563
678
|
}
|
|
3564
|
-
function formatMoonshotReport(lines,
|
|
3565
|
-
for (const
|
|
3566
|
-
lines.push(`${`${
|
|
679
|
+
function formatMoonshotReport(lines, report) {
|
|
680
|
+
for (const metric of report.metrics) {
|
|
681
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${metric.currency} ${metric.value}`);
|
|
3567
682
|
}
|
|
3568
683
|
}
|
|
3569
|
-
function formatMoonshotStatusline(
|
|
3570
|
-
const available =
|
|
684
|
+
function formatMoonshotStatusline(report) {
|
|
685
|
+
const available = report.metrics.find((metric) => metric.id === "available-balance");
|
|
3571
686
|
if (!available) return "moonshot balance unavailable";
|
|
3572
687
|
return `moonshot ${available.currency ?? ""} ${available.value}`.replace(/\s+/gu, " ");
|
|
3573
688
|
}
|
|
3574
|
-
function formatMiniMaxReport(lines,
|
|
3575
|
-
if (
|
|
3576
|
-
for (const
|
|
3577
|
-
lines.push(`${`${
|
|
689
|
+
function formatMiniMaxReport(lines, report) {
|
|
690
|
+
if (report.source === "minimax-account-balance") {
|
|
691
|
+
for (const metric of report.metrics) {
|
|
692
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${metric.currency} ${metric.value}`);
|
|
3578
693
|
}
|
|
3579
694
|
return;
|
|
3580
695
|
}
|
|
3581
696
|
let previousGroup;
|
|
3582
|
-
for (const bucket of
|
|
697
|
+
for (const bucket of report.buckets) {
|
|
3583
698
|
if (bucket.groupId !== previousGroup) lines.push(`${bucket.groupLabel ?? "Token Plan"}:`);
|
|
3584
699
|
previousGroup = bucket.groupId;
|
|
3585
700
|
const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
|
|
@@ -3587,15 +702,15 @@ function formatMiniMaxReport(lines, report2) {
|
|
|
3587
702
|
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${value}`);
|
|
3588
703
|
}
|
|
3589
704
|
}
|
|
3590
|
-
function formatMiniMaxStatusline(
|
|
3591
|
-
const prefix =
|
|
3592
|
-
if (
|
|
3593
|
-
const available =
|
|
705
|
+
function formatMiniMaxStatusline(report, model) {
|
|
706
|
+
const prefix = report.providerId === "minimax-cn" ? "minimax cn" : "minimax";
|
|
707
|
+
if (report.source === "minimax-account-balance") {
|
|
708
|
+
const available = report.metrics.find((metric) => metric.id === "available-balance");
|
|
3594
709
|
return available ? `${prefix} ${available.currency} ${available.value}` : void 0;
|
|
3595
710
|
}
|
|
3596
|
-
const selectedGroup = selectMiniMaxGroup(
|
|
711
|
+
const selectedGroup = selectMiniMaxGroup(report, model);
|
|
3597
712
|
if (!selectedGroup) return void 0;
|
|
3598
|
-
const selected =
|
|
713
|
+
const selected = report.buckets.filter((bucket) => bucket.groupId === selectedGroup);
|
|
3599
714
|
const parts = [prefix];
|
|
3600
715
|
for (const bucket of selected) {
|
|
3601
716
|
const fallback = bucket.id.endsWith(":weekly") ? "weekly" : "5h";
|
|
@@ -3613,16 +728,16 @@ function formatMiniMaxStatusline(report2, model) {
|
|
|
3613
728
|
}
|
|
3614
729
|
return parts.length > 1 ? parts.join(" ") : void 0;
|
|
3615
730
|
}
|
|
3616
|
-
function selectMiniMaxGroup(
|
|
731
|
+
function selectMiniMaxGroup(report, model) {
|
|
3617
732
|
const groups = [
|
|
3618
|
-
...new Set(
|
|
733
|
+
...new Set(report.buckets.map((bucket) => bucket.groupId).filter((group) => group !== void 0))
|
|
3619
734
|
];
|
|
3620
735
|
if (groups.length <= 1) return groups[0];
|
|
3621
|
-
if (model && model.provider !==
|
|
736
|
+
if (model && model.provider !== report.providerId) return void 0;
|
|
3622
737
|
if (model) {
|
|
3623
738
|
const modelKeys = [model.id, model.name].map(normalizeMiniMaxModelKey).filter((key) => key !== void 0);
|
|
3624
739
|
const candidates = groups.map((group) => {
|
|
3625
|
-
const bucket =
|
|
740
|
+
const bucket = report.buckets.find((candidate) => candidate.groupId === group);
|
|
3626
741
|
const patterns = [bucket?.groupLabel, ...bucket?.modelKeys ?? [], group].map(normalizeMiniMaxModelKey).filter((key) => key !== void 0);
|
|
3627
742
|
return { group, patterns };
|
|
3628
743
|
});
|
|
@@ -3653,10 +768,10 @@ function wildcardKeyMatches(pattern, value) {
|
|
|
3653
768
|
const last = segments.at(-1);
|
|
3654
769
|
return pattern.endsWith("*") || last !== void 0 && value.endsWith(last);
|
|
3655
770
|
}
|
|
3656
|
-
function formatZaiStatusline(
|
|
771
|
+
function formatZaiStatusline(report) {
|
|
3657
772
|
const selected = [
|
|
3658
|
-
|
|
3659
|
-
|
|
773
|
+
report.buckets.find((bucket) => bucket.id === "five-hour"),
|
|
774
|
+
report.buckets.find((bucket) => bucket.id === "weekly")
|
|
3660
775
|
];
|
|
3661
776
|
const parts = ["zai"];
|
|
3662
777
|
for (const bucket of selected) {
|
|
@@ -3666,15 +781,15 @@ function formatZaiStatusline(report2) {
|
|
|
3666
781
|
}
|
|
3667
782
|
return parts.length > 1 ? parts.join(" ") : void 0;
|
|
3668
783
|
}
|
|
3669
|
-
function formatCurrencyMetric(
|
|
3670
|
-
if (typeof
|
|
3671
|
-
if (!
|
|
3672
|
-
if (
|
|
3673
|
-
if (
|
|
3674
|
-
return `${
|
|
784
|
+
function formatCurrencyMetric(metric) {
|
|
785
|
+
if (typeof metric.value !== "number") return String(metric.value);
|
|
786
|
+
if (!metric.currency) return "unavailable";
|
|
787
|
+
if (metric.currency === "USD") return `$${metric.value.toFixed(2)}`;
|
|
788
|
+
if (metric.currency === "CNY") return `\xA5${metric.value.toFixed(2)}`;
|
|
789
|
+
return `${metric.value.toFixed(2)} ${metric.currency}`;
|
|
3675
790
|
}
|
|
3676
|
-
function formatXaiReport(lines,
|
|
3677
|
-
const included =
|
|
791
|
+
function formatXaiReport(lines, report) {
|
|
792
|
+
const included = report.buckets.find((bucket) => bucket.id === "included-allowance");
|
|
3678
793
|
if (included) {
|
|
3679
794
|
let value = "unavailable";
|
|
3680
795
|
if (included.unit === "percent" && included.used !== void 0) {
|
|
@@ -3689,18 +804,18 @@ function formatXaiReport(lines, report2) {
|
|
|
3689
804
|
const reset = included.resetsAt ? ` (resets ${formatReset(included.resetsAt)})` : "";
|
|
3690
805
|
lines.push(`${"Included allowance:".padEnd(VALUE_COLUMN)}${value}${period}${reset}`);
|
|
3691
806
|
}
|
|
3692
|
-
const onDemand =
|
|
807
|
+
const onDemand = report.buckets.find((bucket) => bucket.id === "on-demand");
|
|
3693
808
|
if (onDemand) {
|
|
3694
809
|
let value = onDemand.used === void 0 ? "usage unavailable" : `${formatUsd(onDemand.used)} used`;
|
|
3695
810
|
if (onDemand.limit !== void 0) value += ` of ${formatUsd(onDemand.limit)} cap`;
|
|
3696
811
|
lines.push(`${"On-demand usage:".padEnd(VALUE_COLUMN)}${value}`);
|
|
3697
812
|
}
|
|
3698
|
-
for (const
|
|
3699
|
-
lines.push(`${`${
|
|
813
|
+
for (const metric of report.metrics) {
|
|
814
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`);
|
|
3700
815
|
}
|
|
3701
816
|
}
|
|
3702
|
-
function formatZaiReport(lines,
|
|
3703
|
-
for (const bucket of
|
|
817
|
+
function formatZaiReport(lines, report) {
|
|
818
|
+
for (const bucket of report.buckets) {
|
|
3704
819
|
if (bucket.unit === "percent" && bucket.used !== void 0) {
|
|
3705
820
|
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${formatPercentBucket(bucket)}`);
|
|
3706
821
|
continue;
|
|
@@ -3717,30 +832,42 @@ function formatZaiReport(lines, report2) {
|
|
|
3717
832
|
}
|
|
3718
833
|
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${value}${reset}`);
|
|
3719
834
|
}
|
|
3720
|
-
for (const
|
|
3721
|
-
lines.push(`${`${
|
|
835
|
+
for (const metric of report.metrics) {
|
|
836
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function formatOpenAICompanionReport(lines, report) {
|
|
840
|
+
for (const group of ["chatgpt-plan", "chatgpt-app"]) {
|
|
841
|
+
lines.push(group === "chatgpt-plan" ? "Plan limits:" : "App limits:");
|
|
842
|
+
for (const bucket of report.buckets.filter((bucket2) => bucket2.groupId === group)) {
|
|
843
|
+
lines.push(
|
|
844
|
+
`${formatWindowLabel(bucket.windowMinutes, "weekly", false).padEnd(VALUE_COLUMN)}${formatPercentBucket(bucket)}`
|
|
845
|
+
);
|
|
846
|
+
}
|
|
3722
847
|
}
|
|
848
|
+
for (const metric of report.metrics)
|
|
849
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`);
|
|
3723
850
|
}
|
|
3724
|
-
function formatGenericReport(lines,
|
|
3725
|
-
for (const bucket of
|
|
851
|
+
function formatGenericReport(lines, report) {
|
|
852
|
+
for (const bucket of report.buckets) {
|
|
3726
853
|
lines.push(
|
|
3727
854
|
`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(bucket.remaining ?? bucket.used ?? "unavailable", bucket.unit)}`
|
|
3728
855
|
);
|
|
3729
856
|
}
|
|
3730
|
-
for (const
|
|
3731
|
-
lines.push(`${`${
|
|
857
|
+
for (const metric of report.metrics) {
|
|
858
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`);
|
|
3732
859
|
}
|
|
3733
860
|
}
|
|
3734
|
-
function formatCodexStatusline(
|
|
3735
|
-
const group = selectCodexGroup(
|
|
3736
|
-
if (!group) return formatCodexCreditsStatus(
|
|
3737
|
-
const buckets =
|
|
861
|
+
function formatCodexStatusline(report, model, now = Date.now(), showResetCountdown = true, percentage = "remaining") {
|
|
862
|
+
const group = selectCodexGroup(report, model);
|
|
863
|
+
if (!group) return formatCodexCreditsStatus(report);
|
|
864
|
+
const buckets = report.buckets.filter((bucket) => (bucket.groupId ?? bucket.id) === group);
|
|
3738
865
|
const labelBucket = buckets[0];
|
|
3739
866
|
const parts = [group === "codex" ? "codex" : `codex ${compactLimitLabel(labelBucket?.groupLabel ?? group)}`];
|
|
3740
867
|
for (const bucket of buckets) {
|
|
3741
868
|
if (bucket.remaining === void 0) continue;
|
|
3742
869
|
const displayedPercentage = percentage === "used" ? bucket.used ?? 100 - bucket.remaining : bucket.remaining;
|
|
3743
|
-
const percent = `${
|
|
870
|
+
const percent = `${clampPercent(displayedPercentage).toFixed(0)}%`;
|
|
3744
871
|
const fallback = bucket.id.endsWith(":secondary") ? "weekly" : "5h";
|
|
3745
872
|
const window = formatWindowLabel(bucket.windowMinutes, fallback, true);
|
|
3746
873
|
if (!showResetCountdown) {
|
|
@@ -3750,24 +877,24 @@ function formatCodexStatusline(report2, model, now = Date.now(), showResetCountd
|
|
|
3750
877
|
const reset = formatResetCountdown(bucket.resetsAt, now);
|
|
3751
878
|
parts.push(`${percent} ${reset ? `\u21BB ${reset}` : window}`);
|
|
3752
879
|
}
|
|
3753
|
-
return parts.length > 1 ? parts.join(" ") : formatCodexCreditsStatus(
|
|
880
|
+
return parts.length > 1 ? parts.join(" ") : formatCodexCreditsStatus(report);
|
|
3754
881
|
}
|
|
3755
|
-
function formatCodexCreditsStatus(
|
|
3756
|
-
const credits =
|
|
882
|
+
function formatCodexCreditsStatus(report) {
|
|
883
|
+
const credits = report.metrics.find((metric) => metric.id === "credits");
|
|
3757
884
|
if (!credits) return "codex usage unavailable";
|
|
3758
885
|
if (credits.value === "none") return "codex no credits";
|
|
3759
886
|
if (credits.value === "available") return "codex credits available";
|
|
3760
887
|
if (credits.value === "unlimited") return "codex credits unlimited";
|
|
3761
888
|
return `codex ${formatMetricValue(credits.value, "count")} credits`;
|
|
3762
889
|
}
|
|
3763
|
-
function selectCodexGroup(
|
|
3764
|
-
const groups = [...new Set(
|
|
890
|
+
function selectCodexGroup(report, model) {
|
|
891
|
+
const groups = [...new Set(report.buckets.map((bucket) => bucket.groupId ?? bucket.id))];
|
|
3765
892
|
if (model?.provider !== "openai-codex") {
|
|
3766
893
|
return groups.includes("codex") ? "codex" : groups[0];
|
|
3767
894
|
}
|
|
3768
895
|
const modelKeys = normalizedModelKeys(model);
|
|
3769
896
|
for (const group of groups) {
|
|
3770
|
-
const bucket =
|
|
897
|
+
const bucket = report.buckets.find((candidate) => (candidate.groupId ?? candidate.id) === group);
|
|
3771
898
|
const keys = [group, bucket?.groupLabel, ...bucket?.modelKeys ?? []].map(normalizeKey).filter((key) => key !== void 0);
|
|
3772
899
|
if (keys.some((key) => modelKeys.has(key))) return group;
|
|
3773
900
|
}
|
|
@@ -3815,7 +942,7 @@ function formatPercentBucket(bucket) {
|
|
|
3815
942
|
return `${formatPercentBar(bucket)}${bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : ""}`;
|
|
3816
943
|
}
|
|
3817
944
|
function formatPercentBar(bucket) {
|
|
3818
|
-
const remaining =
|
|
945
|
+
const remaining = clampPercent(bucket.remaining ?? 0);
|
|
3819
946
|
const filled = Math.round(remaining / 100 * BAR_SEGMENTS);
|
|
3820
947
|
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(BAR_SEGMENTS - filled)}] ${remaining.toFixed(0)}% left`;
|
|
3821
948
|
}
|
|
@@ -3843,6 +970,7 @@ function formatResetCountdown(resetsAt, now) {
|
|
|
3843
970
|
}
|
|
3844
971
|
function formatMetricValue(value, unit) {
|
|
3845
972
|
if (unit === "usd" && typeof value === "number") return formatUsd(value);
|
|
973
|
+
if (unit === "percent" && typeof value === "number") return `${value}%`;
|
|
3846
974
|
return String(value);
|
|
3847
975
|
}
|
|
3848
976
|
function formatUsd(value) {
|
|
@@ -3859,7 +987,7 @@ function formatReset(epochSeconds) {
|
|
|
3859
987
|
function capitalize(value) {
|
|
3860
988
|
return `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}`;
|
|
3861
989
|
}
|
|
3862
|
-
function
|
|
990
|
+
function clampPercent(value) {
|
|
3863
991
|
return Math.min(100, Math.max(0, value));
|
|
3864
992
|
}
|
|
3865
993
|
|
|
@@ -3872,6 +1000,7 @@ import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
|
3872
1000
|
var USAGE_SETTINGS_FILE = "pi-usage.json";
|
|
3873
1001
|
var MAX_USAGE_SETTINGS_BYTES = 64 * 1024;
|
|
3874
1002
|
var DEFAULT_USAGE_SETTINGS = Object.freeze({
|
|
1003
|
+
openaiCompanionUsage: false,
|
|
3875
1004
|
codexFastMode: false,
|
|
3876
1005
|
codexStatusResetCountdown: true,
|
|
3877
1006
|
codexStatusPercentage: "remaining",
|
|
@@ -3881,7 +1010,8 @@ function usageSettingsPath() {
|
|
|
3881
1010
|
return join(getAgentDir(), USAGE_SETTINGS_FILE);
|
|
3882
1011
|
}
|
|
3883
1012
|
function normalizeUsageSettings(value) {
|
|
3884
|
-
if (!
|
|
1013
|
+
if (!isRecord2(value)) return void 0;
|
|
1014
|
+
if (Object.hasOwn(value, "openaiCompanionUsage") && typeof value.openaiCompanionUsage !== "boolean") return void 0;
|
|
3885
1015
|
if (Object.hasOwn(value, "codexFastMode") && typeof value.codexFastMode !== "boolean") {
|
|
3886
1016
|
return void 0;
|
|
3887
1017
|
}
|
|
@@ -3901,6 +1031,7 @@ function normalizeUsageSettings(value) {
|
|
|
3901
1031
|
effectiveTargets.fireworks = value.fireworksAccountId;
|
|
3902
1032
|
}
|
|
3903
1033
|
return {
|
|
1034
|
+
openaiCompanionUsage: value.openaiCompanionUsage === true,
|
|
3904
1035
|
codexFastMode: typeof value.codexFastMode === "boolean" ? value.codexFastMode : DEFAULT_USAGE_SETTINGS.codexFastMode,
|
|
3905
1036
|
codexStatusResetCountdown: typeof value.codexStatusResetCountdown === "boolean" ? value.codexStatusResetCountdown : DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown,
|
|
3906
1037
|
codexStatusPercentage: value.codexStatusPercentage === "used" ? "used" : DEFAULT_USAGE_SETTINGS.codexStatusPercentage,
|
|
@@ -3926,7 +1057,7 @@ async function loadUsageSettings(path = usageSettingsPath(), signal) {
|
|
|
3926
1057
|
throwIfAborted(signal);
|
|
3927
1058
|
const document = JSON.parse(text);
|
|
3928
1059
|
const settings = normalizeUsageSettings(document);
|
|
3929
|
-
if (!settings || !
|
|
1060
|
+
if (!settings || !isRecord2(document)) throw new Error("invalid settings shape");
|
|
3930
1061
|
return { kind: "loaded", path, settings, document };
|
|
3931
1062
|
} catch (error) {
|
|
3932
1063
|
if (signal?.aborted) throw error;
|
|
@@ -4105,7 +1236,7 @@ async function chmodPrivate(path) {
|
|
|
4105
1236
|
function throwIfAborted(signal) {
|
|
4106
1237
|
if (signal?.aborted) throw new DOMException("Settings operation aborted", "AbortError");
|
|
4107
1238
|
}
|
|
4108
|
-
function
|
|
1239
|
+
function isRecord2(value) {
|
|
4109
1240
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4110
1241
|
}
|
|
4111
1242
|
function isNodeError(error) {
|
|
@@ -4113,7 +1244,7 @@ function isNodeError(error) {
|
|
|
4113
1244
|
}
|
|
4114
1245
|
function normalizeSelectedTargets(value) {
|
|
4115
1246
|
if (value === void 0) return {};
|
|
4116
|
-
if (!
|
|
1247
|
+
if (!isRecord2(value)) return void 0;
|
|
4117
1248
|
const targets = {};
|
|
4118
1249
|
for (const [providerId, targetId] of Object.entries(value)) {
|
|
4119
1250
|
if (!isProviderId(providerId) || !isBoundedTargetId(targetId)) return void 0;
|
|
@@ -4142,7 +1273,7 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {})
|
|
|
4142
1273
|
try {
|
|
4143
1274
|
await settingsRuntime.update({ codexFastMode: enabled }, signal);
|
|
4144
1275
|
} catch (error) {
|
|
4145
|
-
if (
|
|
1276
|
+
if (isAbortError(error) || isStaleExtensionContextError(error)) return false;
|
|
4146
1277
|
ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
|
|
4147
1278
|
return false;
|
|
4148
1279
|
}
|
|
@@ -4216,7 +1347,7 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {})
|
|
|
4216
1347
|
const key = activeRequestKey(ctx);
|
|
4217
1348
|
if (key && ctx.model) {
|
|
4218
1349
|
pendingFastRequests.set(key, {
|
|
4219
|
-
fastRequested:
|
|
1350
|
+
fastRequested: isRecord3(rewritten) && rewritten.service_tier === "priority",
|
|
4220
1351
|
model: ctx.model
|
|
4221
1352
|
});
|
|
4222
1353
|
}
|
|
@@ -4250,7 +1381,7 @@ function activeRequestKey(ctx) {
|
|
|
4250
1381
|
return model ? `${ctx.sessionManager.getSessionId()}:${model.provider}/${model.id}` : void 0;
|
|
4251
1382
|
}
|
|
4252
1383
|
function consumeFastRequest(ctx, message, pending) {
|
|
4253
|
-
if (!
|
|
1384
|
+
if (!isRecord3(message) || message.role !== "assistant") return NO_FAST_REQUEST;
|
|
4254
1385
|
const key = messageRequestKey(ctx, message);
|
|
4255
1386
|
if (!key) return NO_FAST_REQUEST;
|
|
4256
1387
|
const request = pending.get(key);
|
|
@@ -4261,10 +1392,10 @@ function messageRequestKey(ctx, message) {
|
|
|
4261
1392
|
if (typeof message.provider !== "string" || typeof message.model !== "string") return void 0;
|
|
4262
1393
|
return `${ctx.sessionManager.getSessionId()}:${message.provider}/${message.model}`;
|
|
4263
1394
|
}
|
|
4264
|
-
function
|
|
1395
|
+
function isRecord3(value) {
|
|
4265
1396
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4266
1397
|
}
|
|
4267
|
-
function
|
|
1398
|
+
function isAbortError(error) {
|
|
4268
1399
|
return error instanceof Error && error.name === "AbortError";
|
|
4269
1400
|
}
|
|
4270
1401
|
|
|
@@ -4293,7 +1424,7 @@ function setBoundedMap(map, key, value, limit) {
|
|
|
4293
1424
|
function modelIdentity(model) {
|
|
4294
1425
|
return model ? `${model.provider}/${model.id}` : void 0;
|
|
4295
1426
|
}
|
|
4296
|
-
function
|
|
1427
|
+
function isAbortError2(error) {
|
|
4297
1428
|
return error instanceof Error && error.name === "AbortError";
|
|
4298
1429
|
}
|
|
4299
1430
|
function isTimeoutError(error) {
|
|
@@ -4319,120 +1450,157 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
|
|
|
4319
1450
|
}
|
|
4320
1451
|
const { HorizontalRule, renderBoundedFrame } = await import("@narumitw/pi-tui-kit");
|
|
4321
1452
|
if (parentSignal.aborted || !isCurrent()) return false;
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
1453
|
+
let changed = false;
|
|
1454
|
+
let selectedId;
|
|
1455
|
+
for (; ; ) {
|
|
1456
|
+
const result = await ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
1457
|
+
const localController = new AbortController();
|
|
1458
|
+
const signal = AbortSignal.any([parentSignal, localController.signal]);
|
|
1459
|
+
let closing = false;
|
|
1460
|
+
let saveQueue = Promise.resolve();
|
|
1461
|
+
const state = settingsRuntime.get();
|
|
1462
|
+
const items = [
|
|
1463
|
+
{
|
|
1464
|
+
id: "codexFastMode",
|
|
1465
|
+
label: "Codex Fast mode",
|
|
1466
|
+
description: "Use faster Codex routing at increased plan allowance consumption.",
|
|
1467
|
+
currentValue: state.settings.codexFastMode ? ON : OFF,
|
|
1468
|
+
values: [OFF, ON]
|
|
1469
|
+
},
|
|
1470
|
+
{
|
|
1471
|
+
id: "codexStatusResetCountdown",
|
|
1472
|
+
label: "Codex reset countdown",
|
|
1473
|
+
description: "Show time remaining until each Codex usage limit resets.",
|
|
1474
|
+
currentValue: state.settings.codexStatusResetCountdown ? ON : OFF,
|
|
1475
|
+
values: [OFF, ON]
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
id: "codexStatusPercentage",
|
|
1479
|
+
label: "Codex percentage",
|
|
1480
|
+
description: "Show remaining or used Codex quota in the statusline.",
|
|
1481
|
+
currentValue: settingValueLabel("codexStatusPercentage", state.settings.codexStatusPercentage),
|
|
1482
|
+
values: [REMAINING, USED]
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
id: "openaiCompanionUsage",
|
|
1486
|
+
label: "Experimental ChatGPT companion usage",
|
|
1487
|
+
description: "Read undocumented plan/app usage using a same-account Codex login; matching registration does not prove identity.",
|
|
1488
|
+
currentValue: state.settings.openaiCompanionUsage ? ON : OFF,
|
|
1489
|
+
values: [OFF, ON]
|
|
1490
|
+
}
|
|
1491
|
+
];
|
|
1492
|
+
const rule = new HorizontalRule({ ruleStyle: (text) => theme.fg("border", text) });
|
|
1493
|
+
let settingsList;
|
|
1494
|
+
const close = (result2) => {
|
|
1495
|
+
if (closing) return;
|
|
1496
|
+
closing = true;
|
|
1497
|
+
localController.abort();
|
|
1498
|
+
done(result2);
|
|
1499
|
+
};
|
|
1500
|
+
const cancel = () => close(changed);
|
|
1501
|
+
const queueUpdate = (id, requested, display) => {
|
|
1502
|
+
saveQueue = saveQueue.then(async () => {
|
|
1503
|
+
if (signal.aborted || !isCurrent()) return;
|
|
1504
|
+
const previous = settingsRuntime.get().settings[id];
|
|
1505
|
+
if (settingsRuntime.get().kind === "invalid") {
|
|
1506
|
+
settingsList.updateValue(id, settingValueLabel(id, previous));
|
|
1507
|
+
if (!signal.aborted && isCurrent()) {
|
|
1508
|
+
ctx.ui.notify("Repair pi-usage.json and reload before changing settings.", "error");
|
|
1509
|
+
tui.requestRender();
|
|
1510
|
+
}
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
try {
|
|
1514
|
+
if (id === "openaiCompanionUsage" && requested === true && previous !== true) {
|
|
1515
|
+
selectedId = id;
|
|
1516
|
+
close("consent");
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
const patch = {};
|
|
1520
|
+
patch[id] = requested;
|
|
1521
|
+
await settingsRuntime.update(patch, signal);
|
|
1522
|
+
} catch (error) {
|
|
1523
|
+
if (signal.aborted || !isCurrent()) return;
|
|
1524
|
+
settingsList.updateValue(id, settingValueLabel(id, previous));
|
|
1525
|
+
ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
|
|
4367
1526
|
tui.requestRender();
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
if (previous !== requested) {
|
|
1530
|
+
changed = true;
|
|
1531
|
+
onApplied(id);
|
|
4368
1532
|
}
|
|
4369
|
-
return;
|
|
4370
|
-
}
|
|
4371
|
-
try {
|
|
4372
|
-
const patch = {};
|
|
4373
|
-
patch[id] = requested;
|
|
4374
|
-
await settingsRuntime.update(patch, signal);
|
|
4375
|
-
} catch (error) {
|
|
4376
1533
|
if (signal.aborted || !isCurrent()) return;
|
|
4377
|
-
settingsList.updateValue(id,
|
|
4378
|
-
ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
|
|
1534
|
+
settingsList.updateValue(id, display);
|
|
4379
1535
|
tui.requestRender();
|
|
4380
|
-
return;
|
|
4381
|
-
}
|
|
4382
|
-
if (previous !== requested) {
|
|
4383
|
-
changed = true;
|
|
4384
|
-
onApplied(id);
|
|
4385
|
-
}
|
|
4386
|
-
if (signal.aborted || !isCurrent()) return;
|
|
4387
|
-
settingsList.updateValue(id, display);
|
|
4388
|
-
tui.requestRender();
|
|
4389
|
-
});
|
|
4390
|
-
};
|
|
4391
|
-
settingsList = new SettingsList(
|
|
4392
|
-
items,
|
|
4393
|
-
items.length + 2,
|
|
4394
|
-
getSettingsListTheme(),
|
|
4395
|
-
(id, value) => {
|
|
4396
|
-
if (closing || signal.aborted || !isCurrent()) return;
|
|
4397
|
-
if (id === "codexStatusPercentage") {
|
|
4398
|
-
queueUpdate(id, value === USED ? "used" : "remaining", value);
|
|
4399
|
-
} else if (id === "codexFastMode" || id === "codexStatusResetCountdown") {
|
|
4400
|
-
queueUpdate(id, value === ON, value);
|
|
4401
|
-
}
|
|
4402
|
-
},
|
|
4403
|
-
cancel
|
|
4404
|
-
);
|
|
4405
|
-
parentSignal.addEventListener("abort", cancel, { once: true });
|
|
4406
|
-
return {
|
|
4407
|
-
render(width) {
|
|
4408
|
-
const title = new Text(theme.fg("accent", theme.bold("pi-usage Settings")), 1, 0).render(width);
|
|
4409
|
-
const content = settingsList.render(width);
|
|
4410
|
-
const focusedRow = content.findIndex((line) => /^[→›]\s/u.test(stripVTControlCharacters(line)));
|
|
4411
|
-
const terminalRows = Number.isFinite(tui.terminal?.rows) ? Math.floor(tui.terminal.rows) : 24;
|
|
4412
|
-
const [ruleLine = ""] = rule.render(width);
|
|
4413
|
-
return renderBoundedFrame({
|
|
4414
|
-
width,
|
|
4415
|
-
maxRows: Math.max(1, terminalRows - 3),
|
|
4416
|
-
rule: ruleLine,
|
|
4417
|
-
title,
|
|
4418
|
-
content,
|
|
4419
|
-
priorityRows: focusedRow < 0 ? [] : [focusedRow],
|
|
4420
|
-
focusedRow
|
|
4421
1536
|
});
|
|
4422
|
-
}
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
1537
|
+
};
|
|
1538
|
+
settingsList = new SettingsList(
|
|
1539
|
+
items,
|
|
1540
|
+
items.length + 2,
|
|
1541
|
+
getSettingsListTheme(),
|
|
1542
|
+
(id, value) => {
|
|
1543
|
+
if (closing || signal.aborted || !isCurrent()) return;
|
|
1544
|
+
selectedId = id;
|
|
1545
|
+
if (id === "codexStatusPercentage") {
|
|
1546
|
+
queueUpdate(id, value === USED ? "used" : "remaining", value);
|
|
1547
|
+
} else if (id === "codexFastMode" || id === "codexStatusResetCountdown" || id === "openaiCompanionUsage") {
|
|
1548
|
+
queueUpdate(id, value === ON, value);
|
|
1549
|
+
}
|
|
1550
|
+
},
|
|
1551
|
+
cancel
|
|
1552
|
+
);
|
|
1553
|
+
if (selectedId) settingsList.selectItem(selectedId);
|
|
1554
|
+
parentSignal.addEventListener("abort", cancel, { once: true });
|
|
1555
|
+
return {
|
|
1556
|
+
render(width) {
|
|
1557
|
+
const title = new Text(theme.fg("accent", theme.bold("pi-usage Settings")), 1, 0).render(width);
|
|
1558
|
+
const content = settingsList.render(width);
|
|
1559
|
+
const focusedRow = content.findIndex((line) => /^[→›]\s/u.test(stripVTControlCharacters(line)));
|
|
1560
|
+
const terminalRows = Number.isFinite(tui.terminal?.rows) ? Math.floor(tui.terminal.rows) : 24;
|
|
1561
|
+
const [ruleLine = ""] = rule.render(width);
|
|
1562
|
+
return renderBoundedFrame({
|
|
1563
|
+
width,
|
|
1564
|
+
maxRows: Math.max(1, terminalRows - 3),
|
|
1565
|
+
rule: ruleLine,
|
|
1566
|
+
title,
|
|
1567
|
+
content,
|
|
1568
|
+
priorityRows: focusedRow < 0 ? [] : [focusedRow],
|
|
1569
|
+
focusedRow
|
|
1570
|
+
});
|
|
1571
|
+
},
|
|
1572
|
+
invalidate: () => settingsList.invalidate(),
|
|
1573
|
+
handleInput(data) {
|
|
1574
|
+
if (closing) return;
|
|
1575
|
+
if (matchesKey(data, Key.ctrl("c"))) cancel();
|
|
1576
|
+
else settingsList.handleInput(data);
|
|
1577
|
+
tui.requestRender();
|
|
1578
|
+
},
|
|
1579
|
+
dispose() {
|
|
1580
|
+
localController.abort();
|
|
1581
|
+
parentSignal.removeEventListener("abort", cancel);
|
|
1582
|
+
}
|
|
1583
|
+
};
|
|
1584
|
+
});
|
|
1585
|
+
if (result !== "consent" || parentSignal.aborted || !isCurrent()) return changed;
|
|
1586
|
+
try {
|
|
1587
|
+
const accepted = await ctx.ui.confirm(
|
|
1588
|
+
"Enable experimental ChatGPT companion usage?",
|
|
1589
|
+
"Requires /login openai-codex with the same ChatGPT account/workspace as native OpenAI. Only the companion token and its matching account ID are sent to undocumented ChatGPT usage endpoints. Registration matching is not independent proof of the same user/workspace. No reset or allowance mutations are performed.",
|
|
1590
|
+
{ signal: parentSignal }
|
|
1591
|
+
);
|
|
1592
|
+
if (parentSignal.aborted || !isCurrent()) return changed;
|
|
1593
|
+
if (accepted && !settingsRuntime.get().settings.openaiCompanionUsage) {
|
|
1594
|
+
await settingsRuntime.update({ openaiCompanionUsage: true }, parentSignal);
|
|
1595
|
+
changed = true;
|
|
1596
|
+
onApplied("openaiCompanionUsage");
|
|
4433
1597
|
}
|
|
4434
|
-
}
|
|
4435
|
-
|
|
1598
|
+
} catch (error) {
|
|
1599
|
+
if (parentSignal.aborted || !isCurrent()) return changed;
|
|
1600
|
+
ctx.ui.notify(`Could not enable ChatGPT companion usage: ${errorMessage(error)}`, "error");
|
|
1601
|
+
}
|
|
1602
|
+
if (parentSignal.aborted || !isCurrent()) return changed;
|
|
1603
|
+
}
|
|
4436
1604
|
}
|
|
4437
1605
|
|
|
4438
1606
|
// src/usage.ts
|
|
@@ -4457,10 +1625,19 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4457
1625
|
const credentialCandidates = createOAuthCredentialCandidateReader(pi, credentialReader);
|
|
4458
1626
|
const createRedemptionId = dependencies.createRedemptionId ?? randomUUID2;
|
|
4459
1627
|
const settingsRuntime = dependencies.settingsRuntime ?? createUsageSettingsRuntime();
|
|
1628
|
+
const resolveUsageAuth2 = (ctx, adapter, salt, reader, candidates) => resolveUsageAuth(
|
|
1629
|
+
ctx,
|
|
1630
|
+
adapter,
|
|
1631
|
+
salt,
|
|
1632
|
+
reader,
|
|
1633
|
+
candidates,
|
|
1634
|
+
settingsRuntime.get().settings.openaiCompanionUsage
|
|
1635
|
+
);
|
|
4460
1636
|
const cache = new UsageCache(CACHE_TTL_MS);
|
|
4461
1637
|
const failureBackoff = /* @__PURE__ */ new Map();
|
|
4462
1638
|
const latestQueries = /* @__PURE__ */ new Map();
|
|
4463
1639
|
const activeControllers = /* @__PURE__ */ new Set();
|
|
1640
|
+
const nativeQueryControllers = /* @__PURE__ */ new Set();
|
|
4464
1641
|
let querySequence = 0;
|
|
4465
1642
|
let activeCurrentIdentity;
|
|
4466
1643
|
let sessionActive = false;
|
|
@@ -4540,7 +1717,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4540
1717
|
const value = rawValue ? fastRuntime.decorateStatus(model, rawValue) : void 0;
|
|
4541
1718
|
if (!safeSetStatus(ctx, value)) return;
|
|
4542
1719
|
if (shouldSchedule && sessionActive) scheduleStatusRefresh(ctx, model);
|
|
4543
|
-
if (sessionActive && showCodexResetCountdown && outcome.state.report.buckets.some(
|
|
1720
|
+
if (sessionActive && (showCodexResetCountdown || outcome.state.report.source === "openai-chatgpt-companion") && outcome.state.report.buckets.some(
|
|
4544
1721
|
(bucket) => bucket.resetsAt !== void 0 && Number.isFinite(bucket.resetsAt) && bucket.resetsAt * 1e3 > now
|
|
4545
1722
|
)) {
|
|
4546
1723
|
const generation = statusGeneration;
|
|
@@ -4572,22 +1749,24 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4572
1749
|
}
|
|
4573
1750
|
activeCurrentIdentity = nextIdentity;
|
|
4574
1751
|
};
|
|
4575
|
-
const
|
|
1752
|
+
const queryAdapterStateInternal = async (ctx, adapter, displayState, force, signal, authRetry = 0, deadlineAt = Date.now() + DEFAULT_TIMEOUT_MS) => {
|
|
4576
1753
|
const expectedSessionGeneration = sessionGeneration;
|
|
4577
1754
|
const expectedSessionId = ctx.sessionManager.getSessionId();
|
|
4578
1755
|
const expectedModelIdentity = modelIdentity(ctx.model);
|
|
4579
1756
|
const expectedTargetId = adapter.targets ? settingsRuntime.get().settings.selectedTargets[adapter.id] : void 0;
|
|
1757
|
+
const expectedCompanionUsage = settingsRuntime.get().settings.openaiCompanionUsage;
|
|
4580
1758
|
const providerName = providerDisplayName(ctx, adapter.id);
|
|
4581
1759
|
let auth;
|
|
4582
1760
|
try {
|
|
4583
1761
|
auth = await awaitWithDeadline(
|
|
4584
|
-
|
|
1762
|
+
resolveUsageAuth2(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
4585
1763
|
signal,
|
|
4586
1764
|
Math.max(1, deadlineAt - Date.now()),
|
|
4587
1765
|
`resolving ${adapter.displayName} runtime auth`
|
|
4588
1766
|
);
|
|
4589
1767
|
} catch (error) {
|
|
4590
|
-
if (isStaleExtensionContextError(error) ||
|
|
1768
|
+
if (isStaleExtensionContextError(error) || isAbortError2(error)) throw error;
|
|
1769
|
+
if (adapter.id === "openai") invalidateProviderState(adapter.id);
|
|
4591
1770
|
const authState = error instanceof UnsupportedOpenAIUsageAuthError ? "unsupported" : adapter.id === "openai" && !isTimeoutError(error) ? "failed" : void 0;
|
|
4592
1771
|
if (displayState === "current") {
|
|
4593
1772
|
transitionCurrentIdentity(`${adapter.id}:auth-error`, adapter.id);
|
|
@@ -4616,9 +1795,10 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4616
1795
|
"zai",
|
|
4617
1796
|
"zai-coding-cn"
|
|
4618
1797
|
].includes(adapter.id);
|
|
4619
|
-
const requestContextChanged = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity || adapter.targets !== void 0 && settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedTargetId;
|
|
1798
|
+
const requestContextChanged = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModelIdentity || adapter.id === "openai" && settingsRuntime.get().settings.openaiCompanionUsage !== expectedCompanionUsage || adapter.targets !== void 0 && settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedTargetId;
|
|
4620
1799
|
if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
|
|
4621
1800
|
if (!auth) {
|
|
1801
|
+
if (adapter.id === "openai") invalidateProviderState(adapter.id);
|
|
4622
1802
|
if (displayState === "current") {
|
|
4623
1803
|
transitionCurrentIdentity(`${adapter.id}:unavailable`, adapter.id);
|
|
4624
1804
|
}
|
|
@@ -4633,12 +1813,13 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4633
1813
|
authState: "unavailable"
|
|
4634
1814
|
};
|
|
4635
1815
|
}
|
|
1816
|
+
if (adapter.id === "openai" && !auth.openaiCompanion) invalidateProviderState(adapter.id);
|
|
4636
1817
|
let retryableAuthChanged = false;
|
|
4637
1818
|
const guard = async () => {
|
|
4638
1819
|
if (signal.aborted || requestContextChanged()) throw abortError();
|
|
4639
1820
|
if (!requiresRequestBoundaryGuard) return;
|
|
4640
1821
|
const revalidated = await awaitWithDeadline(
|
|
4641
|
-
|
|
1822
|
+
resolveUsageAuth2(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
4642
1823
|
signal,
|
|
4643
1824
|
Math.max(1, deadlineAt - Date.now()),
|
|
4644
1825
|
`revalidating ${providerName} runtime auth`
|
|
@@ -4733,7 +1914,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4733
1914
|
querySequence += 1;
|
|
4734
1915
|
queryId = querySequence;
|
|
4735
1916
|
setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
|
|
4736
|
-
const
|
|
1917
|
+
const report = await queryProviderUsage(
|
|
4737
1918
|
adapter,
|
|
4738
1919
|
auth,
|
|
4739
1920
|
signal,
|
|
@@ -4742,7 +1923,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4742
1923
|
target.targetId
|
|
4743
1924
|
);
|
|
4744
1925
|
if (requiresRequestBoundaryGuard) await guard();
|
|
4745
|
-
const effectiveReport = { ...
|
|
1926
|
+
const effectiveReport = { ...report, providerName };
|
|
4746
1927
|
if (latestQueries.get(failureKey) === queryId) {
|
|
4747
1928
|
cache.set(adapter.id, queryFingerprint, effectiveReport);
|
|
4748
1929
|
failureBackoff.delete(failureKey);
|
|
@@ -4759,7 +1940,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4759
1940
|
rememberedTargetId: expectedTargetId
|
|
4760
1941
|
};
|
|
4761
1942
|
} catch (error) {
|
|
4762
|
-
if (isStaleExtensionContextError(error) ||
|
|
1943
|
+
if (isStaleExtensionContextError(error) || isAbortError2(error)) throw error;
|
|
4763
1944
|
if (retryableAuthChanged && authRetry === 0 && !signal.aborted && !requestContextChanged() && Date.now() < deadlineAt) {
|
|
4764
1945
|
if (queryId !== void 0 && latestQueries.get(failureKey) === queryId) {
|
|
4765
1946
|
latestQueries.delete(failureKey);
|
|
@@ -4788,6 +1969,24 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4788
1969
|
};
|
|
4789
1970
|
}
|
|
4790
1971
|
};
|
|
1972
|
+
const queryAdapterState = async (...args) => {
|
|
1973
|
+
const [ctx, adapter, displayState, force, signal, ...rest] = args;
|
|
1974
|
+
if (adapter.id !== "openai") return queryAdapterStateInternal(...args);
|
|
1975
|
+
const controller = new AbortController();
|
|
1976
|
+
nativeQueryControllers.add(controller);
|
|
1977
|
+
try {
|
|
1978
|
+
return await queryAdapterStateInternal(
|
|
1979
|
+
ctx,
|
|
1980
|
+
adapter,
|
|
1981
|
+
displayState,
|
|
1982
|
+
force,
|
|
1983
|
+
AbortSignal.any([signal, controller.signal]),
|
|
1984
|
+
...rest
|
|
1985
|
+
);
|
|
1986
|
+
} finally {
|
|
1987
|
+
nativeQueryControllers.delete(controller);
|
|
1988
|
+
}
|
|
1989
|
+
};
|
|
4791
1990
|
const loadTargetChoices = async (ctx, adapter, signal) => {
|
|
4792
1991
|
if (!adapter.targets) throw new Error("Provider does not support usage targets.");
|
|
4793
1992
|
const expectedSessionGeneration = sessionGeneration;
|
|
@@ -4797,7 +1996,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4797
1996
|
const deadlineAt = Date.now() + DEFAULT_TIMEOUT_MS;
|
|
4798
1997
|
const changed = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModel || settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedTargetId;
|
|
4799
1998
|
const auth = await awaitWithDeadline(
|
|
4800
|
-
|
|
1999
|
+
resolveUsageAuth2(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
4801
2000
|
signal,
|
|
4802
2001
|
Math.max(1, deadlineAt - Date.now()),
|
|
4803
2002
|
`resolving ${providerDisplayName(ctx, adapter.id)} runtime auth`
|
|
@@ -4806,7 +2005,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4806
2005
|
const guard = async () => {
|
|
4807
2006
|
if (signal.aborted || changed()) throw abortError();
|
|
4808
2007
|
const revalidated = await awaitWithDeadline(
|
|
4809
|
-
|
|
2008
|
+
resolveUsageAuth2(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
4810
2009
|
signal,
|
|
4811
2010
|
Math.max(1, deadlineAt - Date.now()),
|
|
4812
2011
|
`revalidating ${providerDisplayName(ctx, adapter.id)} runtime auth`
|
|
@@ -4872,7 +2071,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4872
2071
|
};
|
|
4873
2072
|
const startStatusRefresh = (ctx, model, force) => {
|
|
4874
2073
|
void refreshCurrentStatus(ctx, model, force).catch((error) => {
|
|
4875
|
-
if (isStaleExtensionContextError(error) ||
|
|
2074
|
+
if (isStaleExtensionContextError(error) || isAbortError2(error)) return;
|
|
4876
2075
|
const message = errorMessage(error);
|
|
4877
2076
|
console.error("[pi-usage] refresh failed:", message);
|
|
4878
2077
|
safeSetStatus(ctx, `usage err: ${message.slice(0, 50)}`);
|
|
@@ -4909,14 +2108,14 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4909
2108
|
if (!adapter) return false;
|
|
4910
2109
|
try {
|
|
4911
2110
|
const auth = await awaitWithDeadline(
|
|
4912
|
-
|
|
2111
|
+
resolveUsageAuth2(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
4913
2112
|
signal,
|
|
4914
2113
|
DEFAULT_TIMEOUT_MS,
|
|
4915
2114
|
`revalidating ${adapter.displayName} runtime auth`
|
|
4916
2115
|
);
|
|
4917
2116
|
return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) && outcome.authState === "unavailable" && auth === void 0;
|
|
4918
2117
|
} catch (error) {
|
|
4919
|
-
if (
|
|
2118
|
+
if (isAbortError2(error) || isStaleExtensionContextError(error)) throw error;
|
|
4920
2119
|
const unchangedFailure = outcome.authState === "unsupported" && error instanceof UnsupportedOpenAIUsageAuthError || outcome.authState === "failed" && outcome.state.status === "auth-unavailable" && !(error instanceof UnsupportedOpenAIUsageAuthError) && !isTimeoutError(error) && errorMessage(error) === outcome.state.message;
|
|
4921
2120
|
return unchangedFailure && generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId);
|
|
4922
2121
|
}
|
|
@@ -4925,14 +2124,14 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4925
2124
|
if (!adapter) return false;
|
|
4926
2125
|
try {
|
|
4927
2126
|
const auth = await awaitWithDeadline(
|
|
4928
|
-
|
|
2127
|
+
resolveUsageAuth2(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
4929
2128
|
signal,
|
|
4930
2129
|
DEFAULT_TIMEOUT_MS,
|
|
4931
2130
|
`revalidating ${adapter.displayName} runtime auth`
|
|
4932
2131
|
);
|
|
4933
2132
|
return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) && auth?.fingerprint === outcome.fingerprint;
|
|
4934
2133
|
} catch (error) {
|
|
4935
|
-
if (
|
|
2134
|
+
if (isAbortError2(error) || isStaleExtensionContextError(error)) throw error;
|
|
4936
2135
|
return false;
|
|
4937
2136
|
}
|
|
4938
2137
|
};
|
|
@@ -4943,7 +2142,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4943
2142
|
const generation = statusGeneration;
|
|
4944
2143
|
const outcome = await queryAdapterState(ctx, adapter, displayState, force, signal);
|
|
4945
2144
|
if (signal.aborted) throw abortError();
|
|
4946
|
-
if (
|
|
2145
|
+
if (await outcomeStillCurrent(ctx, model, generation, outcome, signal)) return outcome;
|
|
4947
2146
|
force = false;
|
|
4948
2147
|
}
|
|
4949
2148
|
throw new Error("OpenAI runtime authentication kept changing; reopen /usage to retry.");
|
|
@@ -5231,7 +2430,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5231
2430
|
];
|
|
5232
2431
|
return { kind: "stay" };
|
|
5233
2432
|
} catch (error) {
|
|
5234
|
-
if (
|
|
2433
|
+
if (isAbortError2(error) || isStaleExtensionContextError(error)) {
|
|
5235
2434
|
return { kind: "stay" };
|
|
5236
2435
|
}
|
|
5237
2436
|
ctx.ui.notify(`Could not select target: ${errorMessage(error)}`, "error");
|
|
@@ -5245,6 +2444,16 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5245
2444
|
controller.signal,
|
|
5246
2445
|
() => statusGeneration === menuGeneration && !controller.signal.aborted,
|
|
5247
2446
|
(id) => {
|
|
2447
|
+
if (id === "openaiCompanionUsage" && statusGeneration === menuGeneration && !controller.signal.aborted) {
|
|
2448
|
+
for (const active of nativeQueryControllers) active.abort();
|
|
2449
|
+
invalidateProviderState("openai");
|
|
2450
|
+
if (ctx.model?.provider === "openai") {
|
|
2451
|
+
statusController?.abort();
|
|
2452
|
+
statusController = void 0;
|
|
2453
|
+
clearStatusTimers();
|
|
2454
|
+
safeSetStatus(ctx, void 0);
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
5248
2457
|
if ((id === "codexStatusResetCountdown" || id === "codexStatusPercentage") && stableCurrent && statusGeneration === menuGeneration && !controller.signal.aborted) {
|
|
5249
2458
|
publishStableCurrent(ctx, stableCurrent);
|
|
5250
2459
|
}
|
|
@@ -5288,7 +2497,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5288
2497
|
try {
|
|
5289
2498
|
availability = await listCodexResetCredits(auth, signal, DEFAULT_TIMEOUT_MS);
|
|
5290
2499
|
} catch (error) {
|
|
5291
|
-
if (
|
|
2500
|
+
if (isAbortError2(error) || summaryCount === void 0 || summaryCount <= 0) {
|
|
5292
2501
|
throw error;
|
|
5293
2502
|
}
|
|
5294
2503
|
availability = {
|
|
@@ -5321,7 +2530,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5321
2530
|
screen: loaded.availability.availableCount > 0 ? "reset-picker" : "reset-result"
|
|
5322
2531
|
};
|
|
5323
2532
|
} catch (error) {
|
|
5324
|
-
if (
|
|
2533
|
+
if (isAbortError2(error) || isStaleExtensionContextError(error)) {
|
|
5325
2534
|
return { kind: "stay" };
|
|
5326
2535
|
}
|
|
5327
2536
|
ctx.ui.notify(`Couldn't load usage limit resets: ${errorMessage(error)}`, "error");
|
|
@@ -5383,7 +2592,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5383
2592
|
}
|
|
5384
2593
|
return { kind: "to", screen: "reset-result" };
|
|
5385
2594
|
} catch (error) {
|
|
5386
|
-
if (
|
|
2595
|
+
if (isAbortError2(error) || isStaleExtensionContextError(error)) {
|
|
5387
2596
|
return { kind: "close" };
|
|
5388
2597
|
}
|
|
5389
2598
|
resetFailure = `Couldn't reset usage: ${errorMessage(error)}. Try again with the same request.`;
|
|
@@ -5439,6 +2648,15 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5439
2648
|
}
|
|
5440
2649
|
const revalidated = await queryStableCurrent(ctx, false, controller, "Revalidating current usage\u2026");
|
|
5441
2650
|
if (!revalidated) return { kind: "back" };
|
|
2651
|
+
if (adapter.id === "openai") {
|
|
2652
|
+
outcome = await runMenuOperation(
|
|
2653
|
+
ctx,
|
|
2654
|
+
"Revalidating ChatGPT companion usage\u2026",
|
|
2655
|
+
controller.signal,
|
|
2656
|
+
(signal) => queryStableAdapterState(ctx, adapter, "configured", false, signal)
|
|
2657
|
+
);
|
|
2658
|
+
if (!outcome) return { kind: "back" };
|
|
2659
|
+
}
|
|
5442
2660
|
stableCurrent = revalidated;
|
|
5443
2661
|
current = revalidated.outcome;
|
|
5444
2662
|
visibleStates = [
|
|
@@ -5482,6 +2700,22 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5482
2700
|
});
|
|
5483
2701
|
const revalidated = await queryStableCurrent(ctx, false, controller, "Revalidating current usage\u2026");
|
|
5484
2702
|
if (!revalidated) return { kind: "stay" };
|
|
2703
|
+
const nativeIndex = adapters.findIndex(
|
|
2704
|
+
(adapter) => adapter.id === "openai" && adapter.id !== currentProviderId
|
|
2705
|
+
);
|
|
2706
|
+
if (nativeIndex >= 0) {
|
|
2707
|
+
const nativeAdapter = adapters[nativeIndex];
|
|
2708
|
+
if (nativeAdapter) {
|
|
2709
|
+
const native = await runMenuOperation(
|
|
2710
|
+
ctx,
|
|
2711
|
+
"Revalidating ChatGPT companion usage\u2026",
|
|
2712
|
+
controller.signal,
|
|
2713
|
+
(signal) => queryStableAdapterState(ctx, nativeAdapter, "configured", false, signal)
|
|
2714
|
+
);
|
|
2715
|
+
if (!native) return { kind: "stay" };
|
|
2716
|
+
queriedStates[nativeIndex] = native.state;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
5485
2719
|
stableCurrent = revalidated;
|
|
5486
2720
|
current = revalidated.outcome;
|
|
5487
2721
|
visibleStates = [
|
|
@@ -5512,7 +2746,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5512
2746
|
try {
|
|
5513
2747
|
await showMenu(ctx);
|
|
5514
2748
|
} catch (error) {
|
|
5515
|
-
if (isStaleExtensionContextError(error) ||
|
|
2749
|
+
if (isStaleExtensionContextError(error) || isAbortError2(error)) return;
|
|
5516
2750
|
throw error;
|
|
5517
2751
|
}
|
|
5518
2752
|
}
|
|
@@ -5537,6 +2771,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5537
2771
|
startStatusRefresh(ctx, ctx.model, false);
|
|
5538
2772
|
});
|
|
5539
2773
|
pi.on("model_select", (event, ctx) => {
|
|
2774
|
+
for (const controller of nativeQueryControllers) controller.abort();
|
|
5540
2775
|
startStatusRefresh(ctx, event.model, false);
|
|
5541
2776
|
});
|
|
5542
2777
|
pi.on("turn_start", (_event, ctx) => {
|