@narumitw/pi-usage 0.61.1 → 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 +46 -3
- 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 +563 -3226
- package/dist/index.ts.map +4 -4
- package/docs/providers.md +50 -1
- package/package.json +5 -5
- package/src/format.ts +33 -2
- package/src/openai-companion-auth.ts +103 -0
- package/src/providers/openai-chatgpt.ts +122 -0
- package/src/providers/openai-companion-usage.ts +146 -0
- package/src/query.ts +28 -3
- package/src/settings.ts +5 -0
- package/src/types.ts +4 -0
- package/src/usage-settings-ui.ts +51 -9
- package/src/usage.ts +141 -12
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";
|
|
@@ -24,2933 +70,77 @@ function codexFastAvailability(model, enabled) {
|
|
|
24
70
|
return {
|
|
25
71
|
kind: "unavailable",
|
|
26
72
|
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/opencode-zen.ts
|
|
1433
|
-
var ZEN_WINDOWS = [
|
|
1434
|
-
{ key: "rolling", label: "Rolling" },
|
|
1435
|
-
{ key: "weekly", label: "Weekly" },
|
|
1436
|
-
{ key: "monthly", label: "Monthly" }
|
|
1437
|
-
];
|
|
1438
|
-
function normalizeOpenCodeZenPayload(payload, capturedAt) {
|
|
1439
|
-
const usage = asObject9(payload.usage);
|
|
1440
|
-
if (!usage) throw new Error("OpenCode Zen usage response was not an object.");
|
|
1441
|
-
const buckets = [];
|
|
1442
|
-
const notes = [];
|
|
1443
|
-
for (const window of ZEN_WINDOWS) {
|
|
1444
|
-
const raw = asObject9(usage[window.key]);
|
|
1445
|
-
if (!raw) continue;
|
|
1446
|
-
const status = asString3(raw.status);
|
|
1447
|
-
if (status !== "ok" && status !== "rate-limited") {
|
|
1448
|
-
notes.push(`${window.label} window unavailable (${status ?? "unknown status"}).`);
|
|
1449
|
-
continue;
|
|
1450
|
-
}
|
|
1451
|
-
const used = asNonnegativeNumber2(raw.percent);
|
|
1452
|
-
if (used === void 0) continue;
|
|
1453
|
-
const resetsAt = asEpochSeconds(raw.resetsAt);
|
|
1454
|
-
buckets.push({
|
|
1455
|
-
id: window.key,
|
|
1456
|
-
label: `${window.label} window`,
|
|
1457
|
-
used,
|
|
1458
|
-
remaining: 100 - clampPercent2(used),
|
|
1459
|
-
limit: 100,
|
|
1460
|
-
unit: "percent",
|
|
1461
|
-
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
1462
|
-
});
|
|
1463
|
-
}
|
|
1464
|
-
if (buckets.length === 0) {
|
|
1465
|
-
throw new Error("OpenCode Zen usage endpoint returned no displayable usage data.");
|
|
1466
|
-
}
|
|
1467
|
-
return {
|
|
1468
|
-
providerId: "opencode-go",
|
|
1469
|
-
providerName: "OpenCode Go",
|
|
1470
|
-
capturedAt,
|
|
1471
|
-
source: "opencode-zen-usage",
|
|
1472
|
-
semantics: {
|
|
1473
|
-
kind: "consumer-subscription",
|
|
1474
|
-
label: "OpenCode Zen plan usage"
|
|
1475
|
-
},
|
|
1476
|
-
buckets,
|
|
1477
|
-
metrics: [],
|
|
1478
|
-
...notes.length > 0 ? { notes } : {}
|
|
1479
|
-
};
|
|
1480
|
-
}
|
|
1481
|
-
function asObject9(value) {
|
|
1482
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1483
|
-
return value;
|
|
1484
|
-
}
|
|
1485
|
-
function asString3(value) {
|
|
1486
|
-
if (typeof value !== "string") return void 0;
|
|
1487
|
-
return sanitizeDisplayText(value, 80) || void 0;
|
|
1488
|
-
}
|
|
1489
|
-
function asNonnegativeNumber2(value) {
|
|
1490
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
1491
|
-
return value;
|
|
1492
|
-
}
|
|
1493
|
-
function asEpochSeconds(value) {
|
|
1494
|
-
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
1495
|
-
const parsed = Date.parse(value);
|
|
1496
|
-
if (Number.isNaN(parsed)) return void 0;
|
|
1497
|
-
return Math.floor(parsed / 1e3);
|
|
1498
|
-
}
|
|
1499
|
-
function clampPercent2(value) {
|
|
1500
|
-
return Math.min(100, Math.max(0, value));
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
// src/providers/openrouter.ts
|
|
1504
|
-
function normalizeOpenRouterKeyPayload(payload, capturedAt) {
|
|
1505
|
-
const data = asObject10(payload.data);
|
|
1506
|
-
if (!data) throw new Error("OpenRouter key response data was not an object.");
|
|
1507
|
-
const limit = asNonnegativeNumber3(data.limit);
|
|
1508
|
-
const remaining = asNonnegativeNumber3(data.limit_remaining);
|
|
1509
|
-
const period = asString4(data.limit_reset);
|
|
1510
|
-
const totalUsage = asNonnegativeNumber3(data.usage);
|
|
1511
|
-
const buckets = [];
|
|
1512
|
-
if (limit !== void 0) {
|
|
1513
|
-
buckets.push({
|
|
1514
|
-
id: "key-limit",
|
|
1515
|
-
label: "Key limit",
|
|
1516
|
-
...remaining !== void 0 ? { used: Math.max(0, limit - remaining), remaining } : {},
|
|
1517
|
-
limit,
|
|
1518
|
-
unit: "usd",
|
|
1519
|
-
...period ? { period } : {}
|
|
1520
|
-
});
|
|
1521
|
-
}
|
|
1522
|
-
const metrics = [];
|
|
1523
|
-
addUsageMetric(metrics, "usage-daily", "Usage today", data.usage_daily);
|
|
1524
|
-
addUsageMetric(metrics, "usage-weekly", "Usage this week", data.usage_weekly);
|
|
1525
|
-
addUsageMetric(metrics, "usage-monthly", "Usage this month", data.usage_monthly);
|
|
1526
|
-
addUsageMetric(metrics, "usage-total", "All-time usage", totalUsage);
|
|
1527
|
-
if (buckets.length === 0 && metrics.length === 0) {
|
|
1528
|
-
throw new Error("OpenRouter key response returned no displayable usage data.");
|
|
1529
|
-
}
|
|
1530
|
-
const notes = [];
|
|
1531
|
-
if (data.limit === null) notes.push("No per-key spend cap");
|
|
1532
|
-
else if (limit === void 0) notes.push("Per-key spend cap unavailable");
|
|
1533
|
-
if (data.is_free_tier === true) notes.push("Free-tier API key");
|
|
1534
|
-
return {
|
|
1535
|
-
providerId: "openrouter",
|
|
1536
|
-
providerName: "OpenRouter",
|
|
1537
|
-
capturedAt,
|
|
1538
|
-
source: "openrouter-key",
|
|
1539
|
-
semantics: { kind: "api-key", label: "API-key spend limits" },
|
|
1540
|
-
accountLabel: asString4(data.label),
|
|
1541
|
-
buckets,
|
|
1542
|
-
metrics,
|
|
1543
|
-
...notes.length > 0 ? { notes } : {}
|
|
1544
|
-
};
|
|
1545
|
-
}
|
|
1546
|
-
function addUsageMetric(metrics, id, label, value) {
|
|
1547
|
-
const amount2 = typeof value === "number" ? asNonnegativeNumber3(value) : void 0;
|
|
1548
|
-
if (amount2 === void 0) return;
|
|
1549
|
-
metrics.push({ id, label, value: amount2, unit: "usd" });
|
|
1550
|
-
}
|
|
1551
|
-
function asObject10(value) {
|
|
1552
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1553
|
-
return value;
|
|
1554
|
-
}
|
|
1555
|
-
function asString4(value) {
|
|
1556
|
-
if (typeof value !== "string") return void 0;
|
|
1557
|
-
return sanitizeDisplayText(value, 80) || void 0;
|
|
1558
|
-
}
|
|
1559
|
-
function asNonnegativeNumber3(value) {
|
|
1560
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
1561
|
-
return value;
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
// src/providers/vercel-ai-gateway.ts
|
|
1565
|
-
var DECIMAL_AMOUNT3 = /^(?:0|[1-9]\d*)(?:\.\d+)?$/u;
|
|
1566
|
-
function normalizeVercelAIGatewayCreditsPayload(payload, capturedAt) {
|
|
1567
|
-
const balance = decimalAmount3(payload.balance, "balance");
|
|
1568
|
-
const totalUsed = decimalAmount3(payload.total_used, "total used");
|
|
1569
|
-
const metrics = [
|
|
1570
|
-
{
|
|
1571
|
-
id: "credit-balance",
|
|
1572
|
-
label: "Credit balance",
|
|
1573
|
-
value: balance,
|
|
1574
|
-
unit: "currency",
|
|
1575
|
-
currency: "USD"
|
|
1576
|
-
},
|
|
1577
|
-
{
|
|
1578
|
-
id: "lifetime-spend",
|
|
1579
|
-
label: "Lifetime spend",
|
|
1580
|
-
value: totalUsed,
|
|
1581
|
-
unit: "currency",
|
|
1582
|
-
currency: "USD"
|
|
1583
|
-
}
|
|
1584
|
-
];
|
|
1585
|
-
return {
|
|
1586
|
-
providerId: "vercel-ai-gateway",
|
|
1587
|
-
providerName: "Vercel AI Gateway",
|
|
1588
|
-
capturedAt,
|
|
1589
|
-
source: "vercel-ai-gateway-credits",
|
|
1590
|
-
semantics: { kind: "api-key", label: "AI Gateway credits and lifetime spend" },
|
|
1591
|
-
buckets: [],
|
|
1592
|
-
metrics
|
|
1593
|
-
};
|
|
1594
|
-
}
|
|
1595
|
-
function decimalAmount3(value, label) {
|
|
1596
|
-
if (typeof value !== "string" || value.length > 64 || !DECIMAL_AMOUNT3.test(value)) {
|
|
1597
|
-
throw new Error(`Vercel AI Gateway ${label} was not a valid nonnegative amount.`);
|
|
1598
|
-
}
|
|
1599
|
-
return value;
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
// src/providers/xai.ts
|
|
1603
|
-
var MAX_SAFE_CENTS = Number.MAX_SAFE_INTEGER;
|
|
1604
|
-
function normalizeXaiBillingPayload(payload, subscriptionTier, capturedAt) {
|
|
1605
|
-
const configValue = payload.config;
|
|
1606
|
-
if (configValue !== null && configValue !== void 0 && !isRecord2(configValue)) {
|
|
1607
|
-
throw new Error("xAI billing response config was not an object or null.");
|
|
1608
|
-
}
|
|
1609
|
-
const config = isRecord2(configValue) ? configValue : void 0;
|
|
1610
|
-
const buckets = [];
|
|
1611
|
-
const metrics = [];
|
|
1612
|
-
const notes = [];
|
|
1613
|
-
if (config) {
|
|
1614
|
-
const period = normalizePeriod(config.currentPeriod, config.billingPeriodStart, config.billingPeriodEnd);
|
|
1615
|
-
const preferredPercent = optionalPercent2(config.creditUsagePercent, "creditUsagePercent");
|
|
1616
|
-
if (preferredPercent !== void 0) {
|
|
1617
|
-
buckets.push({
|
|
1618
|
-
id: "included-allowance",
|
|
1619
|
-
label: "Included allowance",
|
|
1620
|
-
used: preferredPercent,
|
|
1621
|
-
remaining: 100 - preferredPercent,
|
|
1622
|
-
unit: "percent",
|
|
1623
|
-
...period
|
|
1624
|
-
});
|
|
1625
|
-
} else {
|
|
1626
|
-
const limit = optionalUsd(config.monthlyLimit, "monthlyLimit");
|
|
1627
|
-
const used = optionalUsd(config.used, "used");
|
|
1628
|
-
if (limit !== void 0 || used !== void 0) {
|
|
1629
|
-
buckets.push({
|
|
1630
|
-
id: "included-allowance",
|
|
1631
|
-
label: "Included allowance",
|
|
1632
|
-
...limit !== void 0 ? { limit } : {},
|
|
1633
|
-
...used !== void 0 ? { used } : {},
|
|
1634
|
-
...limit !== void 0 && used !== void 0 ? { remaining: limit - used } : {},
|
|
1635
|
-
unit: "usd",
|
|
1636
|
-
...period
|
|
1637
|
-
});
|
|
1638
|
-
} else if (period.period || period.resetsAt !== void 0) {
|
|
1639
|
-
buckets.push({
|
|
1640
|
-
id: "included-allowance",
|
|
1641
|
-
label: "Included allowance",
|
|
1642
|
-
unit: "percent",
|
|
1643
|
-
...period
|
|
1644
|
-
});
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
const onDemandCap = optionalUsd(config.onDemandCap, "onDemandCap");
|
|
1648
|
-
const onDemandUsed = optionalUsd(config.onDemandUsed, "onDemandUsed");
|
|
1649
|
-
if (onDemandCap !== void 0 || onDemandUsed !== void 0) {
|
|
1650
|
-
buckets.push({
|
|
1651
|
-
id: "on-demand",
|
|
1652
|
-
label: "On-demand usage",
|
|
1653
|
-
...onDemandCap !== void 0 ? { limit: onDemandCap } : {},
|
|
1654
|
-
...onDemandUsed !== void 0 ? { used: onDemandUsed } : {},
|
|
1655
|
-
...onDemandCap !== void 0 && onDemandUsed !== void 0 ? { remaining: onDemandCap - onDemandUsed } : {},
|
|
1656
|
-
unit: "usd"
|
|
1657
|
-
});
|
|
1658
|
-
}
|
|
1659
|
-
const prepaidBalance = optionalUsd(config.prepaidBalance, "prepaidBalance");
|
|
1660
|
-
if (prepaidBalance !== void 0) {
|
|
1661
|
-
metrics.push({
|
|
1662
|
-
id: "prepaid-balance",
|
|
1663
|
-
label: "Prepaid balance",
|
|
1664
|
-
value: prepaidBalance,
|
|
1665
|
-
unit: "usd"
|
|
1666
|
-
});
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
const tier = optionalTier(subscriptionTier);
|
|
1670
|
-
if (tier) metrics.push({ id: "subscription-tier", label: "Plan tier", value: tier });
|
|
1671
|
-
if (!config) notes.push("No xAI consumer billing configuration is available for this account.");
|
|
1672
|
-
else if (buckets.length === 0 && metrics.length === 0) {
|
|
1673
|
-
notes.push("The xAI consumer billing response contained no displayable usage fields.");
|
|
1674
|
-
}
|
|
1675
|
-
return {
|
|
1676
|
-
providerId: "xai",
|
|
1677
|
-
providerName: "xAI",
|
|
1678
|
-
capturedAt,
|
|
1679
|
-
source: "cli-chat-proxy.grok.com consumer billing",
|
|
1680
|
-
semantics: {
|
|
1681
|
-
kind: "consumer-subscription",
|
|
1682
|
-
label: "xAI consumer subscription usage"
|
|
1683
|
-
},
|
|
1684
|
-
buckets,
|
|
1685
|
-
metrics,
|
|
1686
|
-
...notes.length > 0 ? { notes } : {}
|
|
1687
|
-
};
|
|
1688
|
-
}
|
|
1689
|
-
function normalizePeriod(currentPeriod, legacyStart, legacyEnd) {
|
|
1690
|
-
if (currentPeriod !== void 0 && currentPeriod !== null && !isRecord2(currentPeriod)) {
|
|
1691
|
-
throw new Error("xAI billing currentPeriod was not an object or null.");
|
|
1692
|
-
}
|
|
1693
|
-
if (isRecord2(currentPeriod)) {
|
|
1694
|
-
const type = optionalString(currentPeriod.type, "currentPeriod.type");
|
|
1695
|
-
const start2 = optionalTimestamp(currentPeriod.start, "currentPeriod.start");
|
|
1696
|
-
const end2 = optionalTimestamp(currentPeriod.end, "currentPeriod.end");
|
|
1697
|
-
return {
|
|
1698
|
-
...periodLabel(type, start2) ? { period: periodLabel(type, start2) } : {},
|
|
1699
|
-
...end2 !== void 0 ? { resetsAt: end2 } : {}
|
|
1700
|
-
};
|
|
1701
|
-
}
|
|
1702
|
-
const start = optionalTimestamp(legacyStart, "billingPeriodStart");
|
|
1703
|
-
const end = optionalTimestamp(legacyEnd, "billingPeriodEnd");
|
|
1704
|
-
return {
|
|
1705
|
-
...start !== void 0 ? { period: "Monthly" } : {},
|
|
1706
|
-
...end !== void 0 ? { resetsAt: end } : {}
|
|
1707
|
-
};
|
|
1708
|
-
}
|
|
1709
|
-
function periodLabel(type, start) {
|
|
1710
|
-
if (type === "USAGE_PERIOD_TYPE_WEEKLY") return "Weekly";
|
|
1711
|
-
if (type === "USAGE_PERIOD_TYPE_MONTHLY") return "Monthly";
|
|
1712
|
-
if (type) return sanitizeDisplayText(type.replace(/^USAGE_PERIOD_TYPE_/u, "").replaceAll("_", " "), 40);
|
|
1713
|
-
return start === void 0 ? void 0 : "Current period";
|
|
1714
|
-
}
|
|
1715
|
-
function optionalUsd(value, field) {
|
|
1716
|
-
if (value === void 0 || value === null) return void 0;
|
|
1717
|
-
if (!isRecord2(value)) throw new Error(`xAI billing ${field} was not a cent wrapper.`);
|
|
1718
|
-
const cents = value.val === void 0 ? 0 : value.val;
|
|
1719
|
-
if (!Number.isSafeInteger(cents) || Math.abs(cents) > MAX_SAFE_CENTS) {
|
|
1720
|
-
throw new Error(`xAI billing ${field}.val was not a safe signed integer.`);
|
|
1721
|
-
}
|
|
1722
|
-
return cents / 100;
|
|
1723
|
-
}
|
|
1724
|
-
function optionalPercent2(value, field) {
|
|
1725
|
-
if (value === void 0 || value === null) return void 0;
|
|
1726
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 100) {
|
|
1727
|
-
throw new Error(`xAI billing ${field} was outside 0\u2013100.`);
|
|
1728
|
-
}
|
|
1729
|
-
return value;
|
|
1730
|
-
}
|
|
1731
|
-
function optionalTimestamp(value, field) {
|
|
1732
|
-
if (value === void 0 || value === null) return void 0;
|
|
1733
|
-
if (typeof value !== "string" || value.length > 80) {
|
|
1734
|
-
throw new Error(`xAI billing ${field} was not a bounded timestamp.`);
|
|
1735
|
-
}
|
|
1736
|
-
const milliseconds = Date.parse(value);
|
|
1737
|
-
if (!Number.isFinite(milliseconds)) throw new Error(`xAI billing ${field} was invalid.`);
|
|
1738
|
-
return Math.floor(milliseconds / 1e3);
|
|
1739
|
-
}
|
|
1740
|
-
function optionalString(value, field) {
|
|
1741
|
-
if (value === void 0 || value === null) return void 0;
|
|
1742
|
-
if (typeof value !== "string" || value.length > 80) {
|
|
1743
|
-
throw new Error(`xAI billing ${field} was not a bounded string.`);
|
|
1744
|
-
}
|
|
1745
|
-
return value;
|
|
1746
|
-
}
|
|
1747
|
-
function optionalTier(value) {
|
|
1748
|
-
if (value === void 0 || value === null) return void 0;
|
|
1749
|
-
if (typeof value !== "string" || value.length > 160) {
|
|
1750
|
-
throw new Error("xAI subscription tier was not a bounded string or null.");
|
|
1751
|
-
}
|
|
1752
|
-
return sanitizeDisplayText(value, 80) || void 0;
|
|
1753
|
-
}
|
|
1754
|
-
function isRecord2(value) {
|
|
1755
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
// src/providers/zai-errors.ts
|
|
1759
|
-
var ERROR_MESSAGES = {
|
|
1760
|
-
"1000": "Authentication failed. Check your API key.",
|
|
1761
|
-
"1001": "Authentication header missing. Check your API key.",
|
|
1762
|
-
"1003": "Authentication token expired. Obtain a new token.",
|
|
1763
|
-
"1005": "Two-factor authentication required.",
|
|
1764
|
-
"1113": "Insufficient balance or no resource package. Recharge your account.",
|
|
1765
|
-
"1200": "API call error. Try again later.",
|
|
1766
|
-
"1210": "Invalid API parameter. Check the API documentation.",
|
|
1767
|
-
"1211": "Unknown model. Check the model ID.",
|
|
1768
|
-
"1212": "This model does not support the requested method.",
|
|
1769
|
-
"1213": "A required parameter is missing.",
|
|
1770
|
-
"1214": "Invalid parameter. Check the API documentation.",
|
|
1771
|
-
"1215": "Conflicting parameters. Check the API documentation.",
|
|
1772
|
-
"1220": "Access denied. Check your permissions.",
|
|
1773
|
-
"1221": "This API has been taken offline.",
|
|
1774
|
-
"1222": "This API does not exist.",
|
|
1775
|
-
"1230": "API processing error. Try again later.",
|
|
1776
|
-
"1234": "Network error. Try again later.",
|
|
1777
|
-
"1261": "Prompt too long. Reduce the input length.",
|
|
1778
|
-
"1301": "Content rejected by the safety policy.",
|
|
1779
|
-
"1302": "Request rate limit reached. Try again later.",
|
|
1780
|
-
"1305": "Service overloaded. Try again later.",
|
|
1781
|
-
"1308": "Usage limit reached. Wait for the quota reset.",
|
|
1782
|
-
"1309": "GLM Coding Plan expired. Renew your subscription.",
|
|
1783
|
-
"1310": "Weekly or monthly limit exhausted. Wait for the quota reset.",
|
|
1784
|
-
"1311": "Your subscription does not include this model.",
|
|
1785
|
-
"1313": "Request frequency restricted by the Fair Usage Policy. Contact support.",
|
|
1786
|
-
"1314": "Enterprise package expired. Contact your administrator.",
|
|
1787
|
-
"1315": "This API key requires an enterprise coding package scenario. Replace the key.",
|
|
1788
|
-
"1316": "5-hour limit reached; insufficient balance for extra usage. Wait for reset.",
|
|
1789
|
-
"1317": "7-day limit reached; insufficient balance for extra usage. Wait for reset.",
|
|
1790
|
-
"1318": "5-hour limit reached; extra usage blocked by monthly spend limit.",
|
|
1791
|
-
"1319": "7-day limit reached; extra usage blocked by monthly spend limit.",
|
|
1792
|
-
"1320": "5-hour limit reached; extra usage blocked by monthly spend limit.",
|
|
1793
|
-
"1321": "7-day limit reached; extra usage blocked by monthly spend limit."
|
|
1794
|
-
};
|
|
1795
|
-
var HTTP_MESSAGES = {
|
|
1796
|
-
400: "Invalid request. Check the API documentation.",
|
|
1797
|
-
401: "Authentication failed. Check your API key.",
|
|
1798
|
-
403: "Access denied. Check your permissions.",
|
|
1799
|
-
429: "Request or usage limit reached. Try again later.",
|
|
1800
|
-
500: "Internal error. Try again later."
|
|
1801
|
-
};
|
|
1802
|
-
function zaiPayloadError(payload) {
|
|
1803
|
-
const object = asObject11(payload);
|
|
1804
|
-
if (!object) return void 0;
|
|
1805
|
-
const nested = asObject11(object.error);
|
|
1806
|
-
const rawCode = nested?.code === void 0 ? object.code : nested.code;
|
|
1807
|
-
const code = errorCode(rawCode);
|
|
1808
|
-
if (object.error === void 0 && object.success !== false && (rawCode === void 0 || code === "0" || code === "200")) {
|
|
1809
|
-
return void 0;
|
|
1810
|
-
}
|
|
1811
|
-
return code && code !== "0" && code !== "200" ? `Z.AI ${code}: ${ERROR_MESSAGES[code] ?? "API request failed."}` : "Z.AI: API request failed.";
|
|
1812
|
-
}
|
|
1813
|
-
function zaiResponseError(status, text) {
|
|
1814
|
-
let payload;
|
|
1815
|
-
try {
|
|
1816
|
-
payload = JSON.parse(text);
|
|
1817
|
-
} catch {
|
|
1818
|
-
if (status >= 200 && status < 300) return "Z.AI: Invalid JSON response.";
|
|
1819
|
-
}
|
|
1820
|
-
const error = zaiPayloadError(payload);
|
|
1821
|
-
if (error) return error;
|
|
1822
|
-
if (status < 200 || status >= 300) {
|
|
1823
|
-
return `Z.AI HTTP ${status}: ${HTTP_MESSAGES[status] ?? "API request failed."}`;
|
|
1824
|
-
}
|
|
1825
|
-
return void 0;
|
|
1826
|
-
}
|
|
1827
|
-
function errorCode(value) {
|
|
1828
|
-
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 9999) {
|
|
1829
|
-
return String(value);
|
|
1830
|
-
}
|
|
1831
|
-
return typeof value === "string" && /^(?:0|[1-9]\d{0,3})$/u.test(value) ? value : void 0;
|
|
1832
|
-
}
|
|
1833
|
-
function asObject11(value) {
|
|
1834
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
|
-
// src/providers/zai.ts
|
|
1838
|
-
var FIVE_HOUR_WINDOW_MINUTES2 = 300;
|
|
1839
|
-
var WEEKLY_WINDOW_MINUTES2 = 10080;
|
|
1840
|
-
function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt, plan) {
|
|
1841
|
-
const error = zaiPayloadError(payload);
|
|
1842
|
-
if (error) throw new Error(error);
|
|
1843
|
-
const data = asObject12(payload.data);
|
|
1844
|
-
if (!data) throw new Error("Z.AI quota response data was not an object.");
|
|
1845
|
-
const limits = Array.isArray(data.limits) ? data.limits : [];
|
|
1846
|
-
const buckets = [];
|
|
1847
|
-
const metrics = [];
|
|
1848
|
-
for (const raw of limits) {
|
|
1849
|
-
const limit = asObject12(raw);
|
|
1850
|
-
if (!limit) continue;
|
|
1851
|
-
const type = asString5(limit.type);
|
|
1852
|
-
const unit = asNonnegativeNumber4(limit.unit);
|
|
1853
|
-
const isPlanUsage = type === "TOKENS_LIMIT" || type === "CREDIT_LIMIT";
|
|
1854
|
-
if (type === "TIME_LIMIT") {
|
|
1855
|
-
addCountBucket(buckets, limit, "mcp-monthly", "MCP monthly allowance");
|
|
1856
|
-
addUsageDetailMetrics(metrics, limit.usageDetails);
|
|
1857
|
-
} else if (isPlanUsage && unit === 3) {
|
|
1858
|
-
addPercentBucket(buckets, limit, "five-hour", sessionWindowLabel(limit), sessionWindowMinutes(limit));
|
|
1859
|
-
} else if (isPlanUsage && unit === 6) {
|
|
1860
|
-
const used = asNonnegativeNumber4(limit.currentValue);
|
|
1861
|
-
const quota = asNonnegativeNumber4(limit.usage);
|
|
1862
|
-
if (used !== void 0 && quota !== void 0) {
|
|
1863
|
-
addCountBucket(buckets, limit, "weekly", "Weekly window", weeklyWindowMinutes(limit));
|
|
1864
|
-
} else {
|
|
1865
|
-
addPercentBucket(buckets, limit, "weekly", "Weekly window", weeklyWindowMinutes(limit));
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
if (buckets.length === 0) {
|
|
1870
|
-
throw new Error("Z.AI quota endpoint returned no displayable usage data.");
|
|
1871
|
-
}
|
|
1872
|
-
const notes = [];
|
|
1873
|
-
const level = asString5(data.level);
|
|
1874
|
-
const planLabel = plan?.name ?? level;
|
|
1875
|
-
if (planLabel) {
|
|
1876
|
-
notes.push(plan?.renewsAt ? `Plan: ${planLabel} \xB7 renews ${plan.renewsAt}` : `Plan: ${planLabel}`);
|
|
1877
|
-
}
|
|
1878
|
-
return {
|
|
1879
|
-
providerId,
|
|
1880
|
-
providerName,
|
|
1881
|
-
capturedAt,
|
|
1882
|
-
source: "zai-quota",
|
|
1883
|
-
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
1884
|
-
buckets,
|
|
1885
|
-
metrics,
|
|
1886
|
-
...notes.length > 0 ? { notes } : {}
|
|
1887
|
-
};
|
|
1888
|
-
}
|
|
1889
|
-
function normalizeZaiSubscriptionPayload(payload) {
|
|
1890
|
-
if (zaiPayloadError(payload)) return void 0;
|
|
1891
|
-
if (!Array.isArray(payload.data)) return void 0;
|
|
1892
|
-
const candidates = [];
|
|
1893
|
-
for (const raw of payload.data) {
|
|
1894
|
-
const entry = asObject12(raw);
|
|
1895
|
-
if (!entry) continue;
|
|
1896
|
-
const name = asString5(entry.productName);
|
|
1897
|
-
if (!name) continue;
|
|
1898
|
-
const renewsAt = planRenewalDate(entry.nextRenewTime);
|
|
1899
|
-
const status = asString5(entry.status)?.toUpperCase();
|
|
1900
|
-
const inCurrentPeriod = asBoolean(entry.inCurrentPeriod);
|
|
1901
|
-
candidates.push({
|
|
1902
|
-
plan: { name, ...renewsAt !== void 0 ? { renewsAt } : {} },
|
|
1903
|
-
...status !== void 0 ? { status } : {},
|
|
1904
|
-
...inCurrentPeriod !== void 0 ? { inCurrentPeriod } : {}
|
|
1905
|
-
});
|
|
1906
|
-
}
|
|
1907
|
-
const hasStateMetadata = candidates.some(
|
|
1908
|
-
(candidate) => candidate.status !== void 0 || candidate.inCurrentPeriod !== void 0
|
|
1909
|
-
);
|
|
1910
|
-
if (!hasStateMetadata) return candidates[0]?.plan;
|
|
1911
|
-
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;
|
|
1912
|
-
}
|
|
1913
|
-
function planRenewalDate(value) {
|
|
1914
|
-
if (typeof value === "string" && /^\d{4}-\d{2}-\d{2}/u.test(value)) return value.slice(0, 10);
|
|
1915
|
-
const millis = asNonnegativeNumber4(value);
|
|
1916
|
-
if (millis === void 0 || millis === 0) return void 0;
|
|
1917
|
-
return new Date(millis).toISOString().slice(0, 10);
|
|
1918
|
-
}
|
|
1919
|
-
function sessionWindowMinutes(limit) {
|
|
1920
|
-
const hours = asPositiveNumber(limit.number);
|
|
1921
|
-
return hours === void 0 ? FIVE_HOUR_WINDOW_MINUTES2 : Math.round(hours * 60);
|
|
1922
|
-
}
|
|
1923
|
-
function sessionWindowLabel(limit) {
|
|
1924
|
-
const minutes = sessionWindowMinutes(limit);
|
|
1925
|
-
return minutes === FIVE_HOUR_WINDOW_MINUTES2 ? "5h window" : `${Math.round(minutes / 60)}h window`;
|
|
1926
|
-
}
|
|
1927
|
-
function weeklyWindowMinutes(limit) {
|
|
1928
|
-
const weeks = asPositiveNumber(limit.number);
|
|
1929
|
-
return weeks === void 0 ? WEEKLY_WINDOW_MINUTES2 : Math.round(weeks * WEEKLY_WINDOW_MINUTES2);
|
|
1930
|
-
}
|
|
1931
|
-
function addPercentBucket(buckets, limit, id, label, windowMinutes) {
|
|
1932
|
-
const used = asNonnegativeNumber4(limit.percentage);
|
|
1933
|
-
if (used === void 0) return;
|
|
1934
|
-
const percent = clampPercent3(used);
|
|
1935
|
-
const resetsAt = asEpochSeconds2(limit.nextResetTime);
|
|
1936
|
-
buckets.push({
|
|
1937
|
-
id,
|
|
1938
|
-
label,
|
|
1939
|
-
used: percent,
|
|
1940
|
-
remaining: 100 - percent,
|
|
1941
|
-
limit: 100,
|
|
1942
|
-
unit: "percent",
|
|
1943
|
-
windowMinutes,
|
|
1944
|
-
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
1945
|
-
});
|
|
1946
|
-
}
|
|
1947
|
-
function addCountBucket(buckets, limit, id, label, windowMinutes) {
|
|
1948
|
-
const used = asNonnegativeNumber4(limit.currentValue);
|
|
1949
|
-
const quota = asNonnegativeNumber4(limit.usage);
|
|
1950
|
-
if (used === void 0 || quota === void 0) return;
|
|
1951
|
-
const resetsAt = asEpochSeconds2(limit.nextResetTime);
|
|
1952
|
-
buckets.push({
|
|
1953
|
-
id,
|
|
1954
|
-
label,
|
|
1955
|
-
used,
|
|
1956
|
-
remaining: Math.max(0, quota - used),
|
|
1957
|
-
limit: quota,
|
|
1958
|
-
unit: "count",
|
|
1959
|
-
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
1960
|
-
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
1961
|
-
});
|
|
1962
|
-
}
|
|
1963
|
-
function addUsageDetailMetrics(metrics, value) {
|
|
1964
|
-
if (!Array.isArray(value)) return;
|
|
1965
|
-
for (const raw of value) {
|
|
1966
|
-
const detail = asObject12(raw);
|
|
1967
|
-
if (!detail) continue;
|
|
1968
|
-
const label = asString5(detail.modelCode);
|
|
1969
|
-
const usage = asNonnegativeNumber4(detail.usage);
|
|
1970
|
-
if (!label || usage === void 0) continue;
|
|
1971
|
-
metrics.push({ id: `mcp-${kebabCase(label)}`, label, value: usage, unit: "count" });
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
function asObject12(value) {
|
|
1975
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1976
|
-
return value;
|
|
1977
|
-
}
|
|
1978
|
-
function asString5(value) {
|
|
1979
|
-
if (typeof value !== "string") return void 0;
|
|
1980
|
-
return sanitizeDisplayText(value, 80) || void 0;
|
|
1981
|
-
}
|
|
1982
|
-
function asNonnegativeNumber4(value) {
|
|
1983
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
|
|
1984
|
-
return value;
|
|
1985
|
-
}
|
|
1986
|
-
function asPositiveNumber(value) {
|
|
1987
|
-
const number = asNonnegativeNumber4(value);
|
|
1988
|
-
return number !== void 0 && number > 0 ? number : void 0;
|
|
1989
|
-
}
|
|
1990
|
-
function asBoolean(value) {
|
|
1991
|
-
if (typeof value === "boolean") return value;
|
|
1992
|
-
if (value === 1) return true;
|
|
1993
|
-
if (value === 0) return false;
|
|
1994
|
-
return void 0;
|
|
1995
|
-
}
|
|
1996
|
-
function asEpochSeconds2(value) {
|
|
1997
|
-
const millis = asNonnegativeNumber4(value);
|
|
1998
|
-
if (millis === void 0) return void 0;
|
|
1999
|
-
return Math.floor(millis / 1e3);
|
|
2000
|
-
}
|
|
2001
|
-
function kebabCase(label) {
|
|
2002
|
-
return label.toLowerCase().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "") || "tool";
|
|
2003
|
-
}
|
|
2004
|
-
function clampPercent3(value) {
|
|
2005
|
-
return Math.min(100, Math.max(0, value));
|
|
2006
|
-
}
|
|
2007
|
-
|
|
2008
|
-
// src/usage-targets.ts
|
|
2009
|
-
var MAX_TARGETS = 1e3;
|
|
2010
|
-
var MAX_TARGET_ID_CHARS = 256;
|
|
2011
|
-
var MAX_TARGET_LABEL_CHARS = 120;
|
|
2012
|
-
var MAX_TARGET_DESCRIPTION_CHARS = 180;
|
|
2013
|
-
async function resolveUsageTarget(adapter, auth, rememberedTargetId, signal, timeoutMs, guard) {
|
|
2014
|
-
if (!adapter.targets) return { kind: "selected" };
|
|
2015
|
-
if (rememberedTargetId !== void 0 && !isBoundedTargetId(rememberedTargetId)) {
|
|
2016
|
-
throw new Error(`The remembered ${adapter.targets.singularLabel} identifier was invalid.`);
|
|
2017
|
-
}
|
|
2018
|
-
const choices = await listUsageTargets(adapter, auth, signal, timeoutMs, guard);
|
|
2019
|
-
if (rememberedTargetId) {
|
|
2020
|
-
return choices.some((choice) => choice.id === rememberedTargetId) ? { kind: "selected", targetId: rememberedTargetId } : { kind: "selection-required", choices };
|
|
2021
|
-
}
|
|
2022
|
-
if (choices.length === 1) return { kind: "selected", targetId: choices[0]?.id };
|
|
2023
|
-
return { kind: "selection-required", choices };
|
|
2024
|
-
}
|
|
2025
|
-
async function listUsageTargets(adapter, auth, signal, timeoutMs, guard) {
|
|
2026
|
-
if (!adapter.targets) return [];
|
|
2027
|
-
const startedAt = Date.now();
|
|
2028
|
-
await guard();
|
|
2029
|
-
const listed = await adapter.targets.list(auth, signal, remainingTargetTimeout(timeoutMs, startedAt), guard);
|
|
2030
|
-
await guard();
|
|
2031
|
-
remainingTargetTimeout(timeoutMs, startedAt);
|
|
2032
|
-
const choices = normalizeUsageTargets(listed);
|
|
2033
|
-
if (choices.length === 0) {
|
|
2034
|
-
throw new Error(`${adapter.targets.pluralLabel} discovery returned no choices.`);
|
|
2035
|
-
}
|
|
2036
|
-
return choices;
|
|
2037
|
-
}
|
|
2038
|
-
function normalizeUsageTargets(targets) {
|
|
2039
|
-
if (!Array.isArray(targets)) throw new Error("Target discovery did not return a choices array.");
|
|
2040
|
-
if (targets.length > MAX_TARGETS) {
|
|
2041
|
-
throw new Error(`Target discovery exceeded ${MAX_TARGETS} choices.`);
|
|
2042
|
-
}
|
|
2043
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2044
|
-
return targets.map((target) => {
|
|
2045
|
-
if (!target || typeof target !== "object" || Array.isArray(target)) {
|
|
2046
|
-
throw new Error("Target discovery returned an invalid choice.");
|
|
2047
|
-
}
|
|
2048
|
-
const { id, label, description } = target;
|
|
2049
|
-
if (!isBoundedTargetId(id)) throw new Error("Target discovery returned an invalid ID.");
|
|
2050
|
-
if (seen.has(id)) throw new Error(`Target discovery repeated ${id}.`);
|
|
2051
|
-
seen.add(id);
|
|
2052
|
-
if (typeof label !== "string") {
|
|
2053
|
-
throw new Error("Target discovery returned an invalid display label.");
|
|
2054
|
-
}
|
|
2055
|
-
if (description !== void 0 && typeof description !== "string") {
|
|
2056
|
-
throw new Error("Target discovery returned an invalid description.");
|
|
2057
|
-
}
|
|
2058
|
-
const safeLabel2 = sanitizeDisplayText(label, MAX_TARGET_LABEL_CHARS);
|
|
2059
|
-
if (!safeLabel2) throw new Error("Target discovery returned an empty display label.");
|
|
2060
|
-
const safeDescription = description ? sanitizeDisplayText(description, MAX_TARGET_DESCRIPTION_CHARS) : void 0;
|
|
2061
|
-
return { id, label: safeLabel2, ...safeDescription ? { description: safeDescription } : {} };
|
|
2062
|
-
});
|
|
2063
|
-
}
|
|
2064
|
-
function createUsageTargetSelectOptions(targets) {
|
|
2065
|
-
const normalized = normalizeUsageTargets(targets);
|
|
2066
|
-
const ids = /* @__PURE__ */ new Map();
|
|
2067
|
-
const options = normalized.map((target) => {
|
|
2068
|
-
const base = target.description ? `${target.label} \u2014 ${target.description}` : target.label;
|
|
2069
|
-
let option = base;
|
|
2070
|
-
if (ids.has(option)) {
|
|
2071
|
-
const safeId = sanitizeDisplayText(target.id, 80) || "target";
|
|
2072
|
-
option = `${base} \xB7 ${safeId}`;
|
|
2073
|
-
let duplicate = 2;
|
|
2074
|
-
while (ids.has(option)) {
|
|
2075
|
-
option = `${base} \xB7 ${safeId} (${duplicate})`;
|
|
2076
|
-
duplicate += 1;
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
|
-
ids.set(option, target.id);
|
|
2080
|
-
return option;
|
|
2081
|
-
});
|
|
2082
|
-
return { options, targetIdFor: (option) => ids.get(option) };
|
|
2083
|
-
}
|
|
2084
|
-
function remainingTargetTimeout(timeoutMs, startedAt) {
|
|
2085
|
-
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
2086
|
-
if (remaining <= 0) throw new Error("Timed out while discovering provider targets.");
|
|
2087
|
-
return remaining;
|
|
2088
|
-
}
|
|
2089
|
-
function isBoundedTargetId(value) {
|
|
2090
|
-
return typeof value === "string" && value.length > 0 && value.length <= MAX_TARGET_ID_CHARS && ![...value].some((character) => {
|
|
2091
|
-
const codePoint = character.codePointAt(0) ?? 0;
|
|
2092
|
-
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
|
|
2093
|
-
});
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
// src/query.ts
|
|
2097
|
-
var BASETEN_BILLING_USAGE_URL = "https://api.baseten.co/v1/billing/usage_summary";
|
|
2098
|
-
var BASETEN_USAGE_WINDOW_DAYS = 30;
|
|
2099
|
-
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
2100
|
-
var DEEPSEEK_BALANCE_URL = "https://api.deepseek.com/user/balance";
|
|
2101
|
-
var GITHUB_COPILOT_USAGE_URL = "https://api.github.com/copilot_internal/user";
|
|
2102
|
-
var OPENROUTER_KEY_URL = "https://openrouter.ai/api/v1/key";
|
|
2103
|
-
var VERCEL_AI_GATEWAY_CREDITS_URL = "https://ai-gateway.vercel.sh/v1/credits";
|
|
2104
|
-
var OPENCODE_GO_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
|
|
2105
|
-
var KIMI_CODING_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
|
|
2106
|
-
var MINIMAX_API_ROOTS = Object.freeze({
|
|
2107
|
-
minimax: "https://api.minimax.io",
|
|
2108
|
-
"minimax-cn": "https://api.minimaxi.com"
|
|
2109
|
-
});
|
|
2110
|
-
var MOONSHOT_BALANCE_URLS = Object.freeze({
|
|
2111
|
-
moonshotai: "https://api.moonshot.ai/v1/users/me/balance",
|
|
2112
|
-
"moonshotai-cn": "https://api.moonshot.cn/v1/users/me/balance"
|
|
2113
|
-
});
|
|
2114
|
-
var SHARED_MOONSHOT_ENV_VAR = "MOONSHOT_API_KEY";
|
|
2115
|
-
var XAI_USER_URL = "https://cli-chat-proxy.grok.com/v1/user?include=subscription";
|
|
2116
|
-
var XAI_BILLING_URL = "https://cli-chat-proxy.grok.com/v1/billing?format=credits";
|
|
2117
|
-
var XAI_CLIENT_HEADERS = Object.freeze({
|
|
2118
|
-
"X-XAI-Token-Auth": "xai-grok-cli",
|
|
2119
|
-
"x-grok-client-version": "1.0.10",
|
|
2120
|
-
"x-grok-client-mode": "interactive"
|
|
2121
|
-
});
|
|
2122
|
-
var MAX_SUCCESS_BODY_BYTES = 64 * 1024;
|
|
2123
|
-
var MAX_ERROR_BODY_BYTES = 4 * 1024;
|
|
2124
|
-
var AUTH_FINGERPRINT_SALT = randomBytes(32);
|
|
2125
|
-
var SUPPORTED_ADAPTERS = [
|
|
2126
|
-
{
|
|
2127
|
-
id: "baseten",
|
|
2128
|
-
displayName: "Baseten",
|
|
2129
|
-
semantics: { kind: "api-key", label: "Organization Model APIs spend" },
|
|
2130
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2131
|
-
if (!guard) throw new Error("Baseten billing usage requires request-boundary revalidation.");
|
|
2132
|
-
const startedAt = Date.now();
|
|
2133
|
-
await guard();
|
|
2134
|
-
const windowAt = Date.now();
|
|
2135
|
-
const payload = await fetchProviderJson(
|
|
2136
|
-
basetenBillingUsageUrl(windowAt),
|
|
2137
|
-
auth,
|
|
2138
|
-
signal,
|
|
2139
|
-
remainingTimeout2(timeoutMs, startedAt, "fetching Baseten billing usage"),
|
|
2140
|
-
"Baseten billing usage endpoint",
|
|
2141
|
-
{ redirect: "error" }
|
|
2142
|
-
);
|
|
2143
|
-
await guard();
|
|
2144
|
-
return normalizeBasetenBillingUsagePayload(payload, Date.now());
|
|
2145
|
-
}
|
|
2146
|
-
},
|
|
2147
|
-
{
|
|
2148
|
-
id: "openai-codex",
|
|
2149
|
-
displayName: "OpenAI Codex",
|
|
2150
|
-
semantics: {
|
|
2151
|
-
kind: "consumer-subscription",
|
|
2152
|
-
label: "ChatGPT subscription limits"
|
|
2153
|
-
},
|
|
2154
|
-
async query(auth, signal, timeoutMs) {
|
|
2155
|
-
const payload = await fetchProviderJson(CODEX_USAGE_URL, auth, signal, timeoutMs, "Codex usage endpoint");
|
|
2156
|
-
return normalizeCodexBackendPayload(payload, Date.now());
|
|
2157
|
-
}
|
|
2158
|
-
},
|
|
2159
|
-
{
|
|
2160
|
-
id: "deepseek",
|
|
2161
|
-
displayName: "DeepSeek",
|
|
2162
|
-
semantics: { kind: "api-key", label: "DeepSeek API balance" },
|
|
2163
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2164
|
-
if (!guard) throw new Error("DeepSeek API balance requires request-boundary revalidation.");
|
|
2165
|
-
const startedAt = Date.now();
|
|
2166
|
-
await guard();
|
|
2167
|
-
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
2168
|
-
if (remainingMs <= 0) throw new Error("Timed out while revalidating DeepSeek runtime auth.");
|
|
2169
|
-
const payload = await fetchProviderJson(
|
|
2170
|
-
DEEPSEEK_BALANCE_URL,
|
|
2171
|
-
auth,
|
|
2172
|
-
signal,
|
|
2173
|
-
remainingMs,
|
|
2174
|
-
"DeepSeek API balance endpoint",
|
|
2175
|
-
{ redirect: "error" }
|
|
2176
|
-
);
|
|
2177
|
-
return normalizeDeepSeekBalancePayload(payload, Date.now());
|
|
2178
|
-
}
|
|
2179
|
-
},
|
|
2180
|
-
{
|
|
2181
|
-
id: "github-copilot",
|
|
2182
|
-
displayName: "GitHub Copilot",
|
|
2183
|
-
semantics: {
|
|
2184
|
-
kind: "consumer-subscription",
|
|
2185
|
-
label: "GitHub Copilot account allowance"
|
|
2186
|
-
},
|
|
2187
|
-
async query(auth, signal, timeoutMs) {
|
|
2188
|
-
const payload = await fetchProviderJson(
|
|
2189
|
-
GITHUB_COPILOT_USAGE_URL,
|
|
2190
|
-
auth,
|
|
2191
|
-
signal,
|
|
2192
|
-
timeoutMs,
|
|
2193
|
-
"GitHub Copilot usage endpoint"
|
|
2194
|
-
);
|
|
2195
|
-
return normalizeGitHubCopilotUsagePayload(payload, Date.now());
|
|
2196
|
-
}
|
|
2197
|
-
},
|
|
2198
|
-
{
|
|
2199
|
-
id: "openrouter",
|
|
2200
|
-
displayName: "OpenRouter",
|
|
2201
|
-
semantics: { kind: "api-key", label: "API-key spend limits" },
|
|
2202
|
-
async query(auth, signal, timeoutMs) {
|
|
2203
|
-
const payload = await fetchProviderJson(OPENROUTER_KEY_URL, auth, signal, timeoutMs, "OpenRouter key endpoint");
|
|
2204
|
-
return normalizeOpenRouterKeyPayload(payload, Date.now());
|
|
2205
|
-
}
|
|
2206
|
-
},
|
|
2207
|
-
{
|
|
2208
|
-
id: "vercel-ai-gateway",
|
|
2209
|
-
displayName: "Vercel AI Gateway",
|
|
2210
|
-
semantics: { kind: "api-key", label: "AI Gateway credits and lifetime spend" },
|
|
2211
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2212
|
-
if (!guard) throw new Error("Vercel AI Gateway usage requires request-boundary revalidation.");
|
|
2213
|
-
const startedAt = Date.now();
|
|
2214
|
-
await guard();
|
|
2215
|
-
const payload = await fetchProviderJson(
|
|
2216
|
-
VERCEL_AI_GATEWAY_CREDITS_URL,
|
|
2217
|
-
auth,
|
|
2218
|
-
signal,
|
|
2219
|
-
remainingTimeout2(timeoutMs, startedAt, "fetching Vercel AI Gateway credits"),
|
|
2220
|
-
"Vercel AI Gateway credits endpoint",
|
|
2221
|
-
{ redirect: "error" }
|
|
2222
|
-
);
|
|
2223
|
-
await guard();
|
|
2224
|
-
return normalizeVercelAIGatewayCreditsPayload(payload, Date.now());
|
|
2225
|
-
}
|
|
2226
|
-
},
|
|
2227
|
-
createFireworksAdapter(fetchProviderJson),
|
|
2228
|
-
{
|
|
2229
|
-
id: "opencode-go",
|
|
2230
|
-
displayName: "OpenCode Go",
|
|
2231
|
-
semantics: { kind: "consumer-subscription", label: "OpenCode Zen plan usage" },
|
|
2232
|
-
async query(auth, signal, timeoutMs) {
|
|
2233
|
-
const payload = await fetchProviderJson(
|
|
2234
|
-
OPENCODE_GO_USAGE_URL,
|
|
2235
|
-
auth,
|
|
2236
|
-
signal,
|
|
2237
|
-
timeoutMs,
|
|
2238
|
-
"OpenCode Zen usage endpoint"
|
|
2239
|
-
);
|
|
2240
|
-
return normalizeOpenCodeZenPayload(payload, Date.now());
|
|
2241
|
-
}
|
|
2242
|
-
},
|
|
2243
|
-
{
|
|
2244
|
-
id: "kimi-coding",
|
|
2245
|
-
displayName: "Kimi For Coding",
|
|
2246
|
-
semantics: { kind: "consumer-subscription", label: "Kimi Coding Plan usage" },
|
|
2247
|
-
async query(auth, signal, timeoutMs) {
|
|
2248
|
-
const payload = await fetchProviderJson(
|
|
2249
|
-
KIMI_CODING_USAGE_URL,
|
|
2250
|
-
auth,
|
|
2251
|
-
signal,
|
|
2252
|
-
timeoutMs,
|
|
2253
|
-
"Kimi Coding usage endpoint",
|
|
2254
|
-
{ redirect: "error" }
|
|
2255
|
-
);
|
|
2256
|
-
return normalizeKimiCodingUsagePayload(payload, Date.now());
|
|
2257
|
-
}
|
|
2258
|
-
},
|
|
2259
|
-
{
|
|
2260
|
-
id: "minimax",
|
|
2261
|
-
displayName: "MiniMax",
|
|
2262
|
-
semantics: { kind: "consumer-subscription", label: "MiniMax usage" },
|
|
2263
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2264
|
-
return queryMiniMaxUsage("minimax", auth, signal, timeoutMs, guard);
|
|
2265
|
-
}
|
|
2266
|
-
},
|
|
2267
|
-
{
|
|
2268
|
-
id: "minimax-cn",
|
|
2269
|
-
displayName: "MiniMax CN",
|
|
2270
|
-
semantics: { kind: "consumer-subscription", label: "MiniMax usage" },
|
|
2271
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2272
|
-
return queryMiniMaxUsage("minimax-cn", auth, signal, timeoutMs, guard);
|
|
2273
|
-
}
|
|
2274
|
-
},
|
|
2275
|
-
{
|
|
2276
|
-
id: "moonshotai",
|
|
2277
|
-
displayName: "Moonshot AI",
|
|
2278
|
-
semantics: { kind: "api-key", label: "Moonshot API account balance" },
|
|
2279
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2280
|
-
return queryMoonshotBalance("moonshotai", auth, signal, timeoutMs, guard);
|
|
2281
|
-
}
|
|
2282
|
-
},
|
|
2283
|
-
{
|
|
2284
|
-
id: "moonshotai-cn",
|
|
2285
|
-
displayName: "Moonshot AI CN",
|
|
2286
|
-
semantics: { kind: "api-key", label: "Moonshot API account balance" },
|
|
2287
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2288
|
-
return queryMoonshotBalance("moonshotai-cn", auth, signal, timeoutMs, guard);
|
|
2289
|
-
}
|
|
2290
|
-
},
|
|
2291
|
-
{
|
|
2292
|
-
id: "zai",
|
|
2293
|
-
displayName: "Z.AI",
|
|
2294
|
-
invalidateCacheOnFailure: true,
|
|
2295
|
-
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
2296
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2297
|
-
return queryZaiUsage("zai", "Z.AI", auth, signal, timeoutMs, guard);
|
|
2298
|
-
}
|
|
2299
|
-
},
|
|
2300
|
-
{
|
|
2301
|
-
id: "zai-coding-cn",
|
|
2302
|
-
displayName: "Z.AI Coding CN",
|
|
2303
|
-
invalidateCacheOnFailure: true,
|
|
2304
|
-
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
2305
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2306
|
-
return queryZaiUsage("zai-coding-cn", "Z.AI Coding CN", auth, signal, timeoutMs, guard);
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
|
-
];
|
|
2310
|
-
var XAI_ADAPTER = {
|
|
2311
|
-
id: "xai",
|
|
2312
|
-
displayName: "xAI",
|
|
2313
|
-
semantics: {
|
|
2314
|
-
kind: "consumer-subscription",
|
|
2315
|
-
label: "xAI consumer subscription usage"
|
|
2316
|
-
},
|
|
2317
|
-
publishesStatusline: false,
|
|
2318
|
-
async query(auth, signal, timeoutMs, guard) {
|
|
2319
|
-
if (!guard) throw new Error("xAI usage requires request-boundary revalidation.");
|
|
2320
|
-
const startedAt = Date.now();
|
|
2321
|
-
const clientAuth = {
|
|
2322
|
-
...auth,
|
|
2323
|
-
headers: { ...auth.headers, ...XAI_CLIENT_HEADERS }
|
|
2324
|
-
};
|
|
2325
|
-
await guard();
|
|
2326
|
-
const userPayload = await fetchProviderJson(
|
|
2327
|
-
XAI_USER_URL,
|
|
2328
|
-
clientAuth,
|
|
2329
|
-
signal,
|
|
2330
|
-
remainingTimeout2(timeoutMs, startedAt),
|
|
2331
|
-
"xAI consumer identity endpoint",
|
|
2332
|
-
{ redirect: "error", userAgent: false }
|
|
2333
|
-
);
|
|
2334
|
-
await guard();
|
|
2335
|
-
const userId = validatedXaiUserId(userPayload.userId);
|
|
2336
|
-
const billingAuth = {
|
|
2337
|
-
...clientAuth,
|
|
2338
|
-
headers: { ...clientAuth.headers, "x-userid": userId },
|
|
2339
|
-
secrets: [...clientAuth.secrets, userId]
|
|
2340
|
-
};
|
|
2341
|
-
await guard();
|
|
2342
|
-
const billingPayload = await fetchProviderJson(
|
|
2343
|
-
XAI_BILLING_URL,
|
|
2344
|
-
billingAuth,
|
|
2345
|
-
signal,
|
|
2346
|
-
remainingTimeout2(timeoutMs, startedAt),
|
|
2347
|
-
"xAI consumer billing endpoint",
|
|
2348
|
-
{ redirect: "error", userAgent: false }
|
|
2349
|
-
);
|
|
2350
|
-
await guard();
|
|
2351
|
-
return normalizeXaiBillingPayload(billingPayload, userPayload.subscriptionTier, Date.now());
|
|
2352
|
-
}
|
|
2353
|
-
};
|
|
2354
|
-
function usageAdapters() {
|
|
2355
|
-
return [...SUPPORTED_ADAPTERS, XAI_ADAPTER];
|
|
2356
|
-
}
|
|
2357
|
-
function adapterForProvider(providerId) {
|
|
2358
|
-
return usageAdapters().find((adapter) => adapter.id === providerId);
|
|
2359
|
-
}
|
|
2360
|
-
function isStaleExtensionContextError(error) {
|
|
2361
|
-
return error instanceof Error && error.message.includes("This extension ctx is stale after session replacement or reload");
|
|
2362
|
-
}
|
|
2363
|
-
async function resolveUsageAuth(ctx, adapter, salt = AUTH_FINGERPRINT_SALT, credentialReader = readStoredCredential2, candidateReader) {
|
|
2364
|
-
if (ctx.model?.provider === adapter.id && !hasOfficialOrigin(ctx.model, adapter.id)) {
|
|
2365
|
-
throw new Error(
|
|
2366
|
-
`${adapter.displayName} usage cannot send a custom provider base URL credential to the official usage endpoint.`
|
|
2367
|
-
);
|
|
2368
|
-
}
|
|
2369
|
-
const model = candidateModels(ctx, adapter.id).find((candidate) => hasOfficialOrigin(candidate, adapter.id));
|
|
2370
|
-
if (!model) return void 0;
|
|
2371
|
-
if (["github-copilot", "xai"].includes(adapter.id) && candidateReader?.waitUntilReady && !await candidateReader.waitUntilReady(ctx, adapter.id)) {
|
|
2372
|
-
throw new Error(`${adapter.displayName} OAuth credential readiness failed closed.`);
|
|
2373
|
-
}
|
|
2374
|
-
const registry = ctx.modelRegistry;
|
|
2375
|
-
const provider = registry.getProvider?.(adapter.id);
|
|
2376
|
-
if (provider?.baseUrl && !hasOfficialUrlOrigin(provider.baseUrl, adapter.id)) {
|
|
2377
|
-
throw new Error(
|
|
2378
|
-
`${adapter.displayName} usage cannot send an overridden provider credential to the official usage endpoint.`
|
|
2379
|
-
);
|
|
2380
|
-
}
|
|
2381
|
-
let modelAuth;
|
|
2382
|
-
const currentModel = ctx.model?.provider === adapter.id ? ctx.model : void 0;
|
|
2383
|
-
const resolveCurrentModelAuth = async () => {
|
|
2384
|
-
if (!currentModel || typeof registry.getApiKeyAndHeaders !== "function") return void 0;
|
|
2385
|
-
const result = await registry.getApiKeyAndHeaders(currentModel);
|
|
2386
|
-
if (!result.ok) throw new Error(redactUsageError(result.error));
|
|
2387
|
-
return authorizationFrom(result) ? result : void 0;
|
|
2388
|
-
};
|
|
2389
|
-
const resolveSelectedAuthLast = ["deepseek", "minimax", "minimax-cn"].includes(adapter.id);
|
|
2390
|
-
if (!resolveSelectedAuthLast) modelAuth = await resolveCurrentModelAuth();
|
|
2391
|
-
if (typeof registry.getProviderAuth !== "function") {
|
|
2392
|
-
throw new Error("pi-usage requires Pi 0.81.0 or newer to validate resolved provider auth.");
|
|
2393
|
-
}
|
|
2394
|
-
if (!moonshotProviderAuthIsAllowed(ctx, adapter.id)) return void 0;
|
|
2395
|
-
const providerResult = await registry.getProviderAuth(adapter.id);
|
|
2396
|
-
if (!moonshotProviderAuthIsAllowed(ctx, adapter.id)) return void 0;
|
|
2397
|
-
if (providerResult?.auth.baseUrl && !hasOfficialUrlOrigin(providerResult.auth.baseUrl, adapter.id)) {
|
|
2398
|
-
throw new Error(
|
|
2399
|
-
`${adapter.displayName} usage cannot send a proxy-resolved credential to the official usage endpoint.`
|
|
2400
|
-
);
|
|
2401
|
-
}
|
|
2402
|
-
if (resolveSelectedAuthLast) modelAuth = await resolveCurrentModelAuth();
|
|
2403
|
-
if (modelAuth?.baseUrl && !hasOfficialUrlOrigin(modelAuth.baseUrl, adapter.id)) {
|
|
2404
|
-
throw new Error(
|
|
2405
|
-
`${adapter.displayName} usage cannot send model-resolved proxy credentials to the official usage endpoint.`
|
|
2406
|
-
);
|
|
2407
|
-
}
|
|
2408
|
-
const auth = modelAuth ?? providerResult?.auth;
|
|
2409
|
-
if (!auth) return void 0;
|
|
2410
|
-
const finalize = (resolved) => {
|
|
2411
|
-
const preservedAuth = { ...providerResult?.auth ?? auth };
|
|
2412
|
-
const env = providerResult?.env ?? modelAuth?.env;
|
|
2413
|
-
const source = providerResult?.source;
|
|
2414
|
-
const effectiveBaseUrl = modelAuth?.baseUrl ?? providerResult?.auth.baseUrl ?? provider?.baseUrl ?? model.baseUrl;
|
|
2415
|
-
const redactionInputs = [
|
|
2416
|
-
preservedAuth.apiKey,
|
|
2417
|
-
...Object.values(preservedAuth.headers ?? {}),
|
|
2418
|
-
...Object.values(env ?? {}),
|
|
2419
|
-
modelAuth?.apiKey,
|
|
2420
|
-
...Object.values(modelAuth?.headers ?? {})
|
|
2421
|
-
].filter((value) => typeof value === "string" && value.length > 0);
|
|
2422
|
-
return {
|
|
2423
|
-
...resolved,
|
|
2424
|
-
auth: preservedAuth,
|
|
2425
|
-
...env ? { env: { ...env } } : {},
|
|
2426
|
-
...source ? { source } : {},
|
|
2427
|
-
effectiveBaseUrl,
|
|
2428
|
-
secrets: [.../* @__PURE__ */ new Set([...resolved.secrets, ...redactionInputs])],
|
|
2429
|
-
fingerprint: fingerprintResolvedAuth(
|
|
2430
|
-
{
|
|
2431
|
-
apiKey: resolved.apiKey,
|
|
2432
|
-
headers: resolved.headers,
|
|
2433
|
-
baseUrl: effectiveBaseUrl,
|
|
2434
|
-
env,
|
|
2435
|
-
source,
|
|
2436
|
-
providerAuth: preservedAuth
|
|
2437
|
-
},
|
|
2438
|
-
salt
|
|
2439
|
-
)
|
|
2440
|
-
};
|
|
2441
|
-
};
|
|
2442
|
-
if (adapter.id === "github-copilot") {
|
|
2443
|
-
const offered = candidateReader ? await candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
|
|
2444
|
-
if (!offered.ok) {
|
|
2445
|
-
throw new Error("GitHub Copilot OAuth credential discovery failed closed.");
|
|
2446
|
-
}
|
|
2447
|
-
return finalize(resolveGitHubCopilotUsageAuth(auth, model, salt, offered.candidates, offered.offeredCount === 0));
|
|
2448
|
-
}
|
|
2449
|
-
if (adapter.id === "xai") {
|
|
2450
|
-
const offered = candidateReader ? await candidateReader(ctx, adapter.id) : fallbackOAuthCredentialCandidates(adapter.id, credentialReader);
|
|
2451
|
-
if (!offered.ok) throw new Error("xAI OAuth credential discovery failed closed.");
|
|
2452
|
-
return finalize(resolveXaiUsageAuth(auth, model, salt, offered.candidates));
|
|
2453
|
-
}
|
|
2454
|
-
if (adapter.id === "deepseek") {
|
|
2455
|
-
const resolvedAuthorization = authorizationFrom(auth);
|
|
2456
|
-
const access = bearerToken(resolvedAuthorization);
|
|
2457
|
-
if (!access) throw new Error("DeepSeek API balance requires Bearer authentication.");
|
|
2458
|
-
const authorization2 = `Bearer ${access}`;
|
|
2459
|
-
const headers2 = { Authorization: authorization2 };
|
|
2460
|
-
return finalize({
|
|
2461
|
-
apiKey: access,
|
|
2462
|
-
headers: headers2,
|
|
2463
|
-
fingerprint: "",
|
|
2464
|
-
secrets: [
|
|
2465
|
-
access,
|
|
2466
|
-
auth.apiKey,
|
|
2467
|
-
headerValue(auth.headers, "Authorization"),
|
|
2468
|
-
resolvedAuthorization,
|
|
2469
|
-
authorization2
|
|
2470
|
-
].filter((value) => Boolean(value)),
|
|
2471
|
-
model
|
|
2472
|
-
});
|
|
2473
|
-
}
|
|
2474
|
-
const authorization = authorizationFrom(auth);
|
|
2475
|
-
if (!authorization) return void 0;
|
|
2476
|
-
const headers = { Authorization: authorization };
|
|
2477
|
-
const secrets = [auth.apiKey, headerValue(auth.headers, "Authorization"), authorization].filter(
|
|
2478
|
-
(value) => Boolean(value)
|
|
2479
|
-
);
|
|
2480
|
-
return finalize({
|
|
2481
|
-
apiKey: auth.apiKey,
|
|
2482
|
-
headers,
|
|
2483
|
-
fingerprint: "",
|
|
2484
|
-
secrets,
|
|
2485
|
-
model
|
|
2486
|
-
});
|
|
2487
|
-
}
|
|
2488
|
-
async function queryProviderUsage(adapter, auth, signal, timeoutMs, guard, targetOrSettings) {
|
|
2489
|
-
const startedAt = Date.now();
|
|
2490
|
-
let targetId = typeof targetOrSettings === "string" ? targetOrSettings : adapter.id === "fireworks" ? targetOrSettings?.fireworksAccountId : void 0;
|
|
2491
|
-
let resolvedLegacyFireworksTarget = false;
|
|
2492
|
-
try {
|
|
2493
|
-
if (adapter.id === "fireworks" && typeof targetOrSettings !== "string" && adapter.targets && guard) {
|
|
2494
|
-
const target = await resolveUsageTarget(
|
|
2495
|
-
adapter,
|
|
2496
|
-
auth,
|
|
2497
|
-
targetId,
|
|
2498
|
-
signal,
|
|
2499
|
-
remainingTimeout2(timeoutMs, startedAt, "resolving the Fireworks account"),
|
|
2500
|
-
guard
|
|
2501
|
-
);
|
|
2502
|
-
if (target.kind === "selection-required") {
|
|
2503
|
-
throw new Error("Fireworks account selection is required.");
|
|
2504
|
-
}
|
|
2505
|
-
targetId = target.targetId;
|
|
2506
|
-
resolvedLegacyFireworksTarget = true;
|
|
2507
|
-
}
|
|
2508
|
-
return await adapter.query(
|
|
2509
|
-
auth,
|
|
2510
|
-
signal,
|
|
2511
|
-
resolvedLegacyFireworksTarget ? remainingTimeout2(timeoutMs, startedAt, `querying ${adapter.displayName} usage`) : timeoutMs,
|
|
2512
|
-
guard,
|
|
2513
|
-
targetId
|
|
2514
|
-
);
|
|
2515
|
-
} catch (error) {
|
|
2516
|
-
if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
|
|
2517
|
-
throw new Error(redactUsageError(errorMessage(error), auth.secrets));
|
|
2518
|
-
}
|
|
2519
|
-
}
|
|
2520
|
-
function providerIsConfigured(ctx, providerId) {
|
|
2521
|
-
try {
|
|
2522
|
-
const status = ctx.modelRegistry.getProviderAuthStatus(providerId);
|
|
2523
|
-
return status.configured && moonshotProviderAuthSourceIsAllowed(ctx, providerId, status.source, status.label);
|
|
2524
|
-
} catch {
|
|
2525
|
-
return !isMoonshotSiblingProvider(ctx, providerId) && candidateModels(ctx, providerId).length > 0;
|
|
2526
|
-
}
|
|
2527
|
-
}
|
|
2528
|
-
function moonshotProviderAuthIsAllowed(ctx, providerId) {
|
|
2529
|
-
if (!isMoonshotSiblingProvider(ctx, providerId)) return true;
|
|
2530
|
-
try {
|
|
2531
|
-
const status = ctx.modelRegistry.getProviderAuthStatus(providerId);
|
|
2532
|
-
return moonshotProviderAuthSourceIsAllowed(ctx, providerId, status.source, status.label);
|
|
2533
|
-
} catch {
|
|
2534
|
-
return false;
|
|
73
|
+
};
|
|
2535
74
|
}
|
|
75
|
+
return { kind: "available", enabled };
|
|
2536
76
|
}
|
|
2537
|
-
function
|
|
2538
|
-
|
|
2539
|
-
if (source === void 0) return false;
|
|
2540
|
-
if (source !== "environment") return true;
|
|
2541
|
-
return label !== void 0 && !label.split(",").map((name) => name.trim()).includes(SHARED_MOONSHOT_ENV_VAR);
|
|
2542
|
-
}
|
|
2543
|
-
function isMoonshotSiblingProvider(ctx, providerId) {
|
|
2544
|
-
return (providerId === "moonshotai" || providerId === "moonshotai-cn") && ctx.model?.provider !== providerId;
|
|
2545
|
-
}
|
|
2546
|
-
function candidateModels(ctx, providerId) {
|
|
2547
|
-
const candidates = [];
|
|
2548
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2549
|
-
const add = (model) => {
|
|
2550
|
-
if (!model || model.provider !== providerId) return;
|
|
2551
|
-
const key = `${model.provider}/${model.id}`;
|
|
2552
|
-
if (seen.has(key)) return;
|
|
2553
|
-
seen.add(key);
|
|
2554
|
-
candidates.push(model);
|
|
2555
|
-
};
|
|
2556
|
-
add(ctx.model);
|
|
2557
|
-
for (const model of ctx.modelRegistry.getAvailable()) add(model);
|
|
2558
|
-
for (const model of ctx.modelRegistry.getAll()) add(model);
|
|
2559
|
-
return candidates;
|
|
2560
|
-
}
|
|
2561
|
-
async function fetchProviderJson(url, auth, signal, timeoutMs, description, request = {}) {
|
|
2562
|
-
const controller = new AbortController();
|
|
2563
|
-
let timedOut = false;
|
|
2564
|
-
const abortFromCaller = () => controller.abort();
|
|
2565
|
-
if (signal.aborted) controller.abort();
|
|
2566
|
-
else signal.addEventListener("abort", abortFromCaller, { once: true });
|
|
2567
|
-
const timeout = setTimeout(() => {
|
|
2568
|
-
timedOut = true;
|
|
2569
|
-
controller.abort();
|
|
2570
|
-
}, timeoutMs);
|
|
2571
|
-
try {
|
|
2572
|
-
const headers = { ...auth.headers };
|
|
2573
|
-
if (request.userAgent !== false && !hasHeader(headers, "User-Agent")) {
|
|
2574
|
-
headers["User-Agent"] = "pi-usage";
|
|
2575
|
-
}
|
|
2576
|
-
if (request.body && !hasHeader(headers, "Content-Type")) {
|
|
2577
|
-
headers["Content-Type"] = "application/json";
|
|
2578
|
-
}
|
|
2579
|
-
const response = await fetch(url, {
|
|
2580
|
-
method: request.method ?? "GET",
|
|
2581
|
-
headers,
|
|
2582
|
-
...request.body ? { body: JSON.stringify(request.body) } : {},
|
|
2583
|
-
...request.redirect ? { redirect: request.redirect } : {},
|
|
2584
|
-
signal: controller.signal
|
|
2585
|
-
});
|
|
2586
|
-
if (response.redirected) throw new Error(`${description} refused a redirected response.`);
|
|
2587
|
-
if (controller.signal.aborted) throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
2588
|
-
const text = await readBoundedResponse(
|
|
2589
|
-
response,
|
|
2590
|
-
response.ok ? MAX_SUCCESS_BODY_BYTES : MAX_ERROR_BODY_BYTES,
|
|
2591
|
-
!response.ok,
|
|
2592
|
-
description,
|
|
2593
|
-
controller.signal
|
|
2594
|
-
);
|
|
2595
|
-
if (controller.signal.aborted) throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
2596
|
-
const responseError = request.responseError?.(response.status, text);
|
|
2597
|
-
if (responseError) throw new Error(responseError);
|
|
2598
|
-
if (!response.ok) {
|
|
2599
|
-
throw new Error(
|
|
2600
|
-
`${description} returned ${response.status} ${response.statusText}: ${redactUsageError(text, auth.secrets)}`
|
|
2601
|
-
);
|
|
2602
|
-
}
|
|
2603
|
-
let parsed;
|
|
2604
|
-
try {
|
|
2605
|
-
parsed = JSON.parse(text);
|
|
2606
|
-
} catch (error) {
|
|
2607
|
-
throw new Error(`${description} returned invalid JSON: ${errorMessage(error)}`);
|
|
2608
|
-
}
|
|
2609
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2610
|
-
throw new Error(`${description} response was not an object.`);
|
|
2611
|
-
}
|
|
2612
|
-
return parsed;
|
|
2613
|
-
} catch (error) {
|
|
2614
|
-
if (timedOut) {
|
|
2615
|
-
throw new Error(`Timed out after ${Math.round(timeoutMs / 1e3)}s while fetching usage.`);
|
|
2616
|
-
}
|
|
2617
|
-
if (signal.aborted) throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
2618
|
-
throw error;
|
|
2619
|
-
} finally {
|
|
2620
|
-
clearTimeout(timeout);
|
|
2621
|
-
signal.removeEventListener("abort", abortFromCaller);
|
|
2622
|
-
}
|
|
77
|
+
function codexFastIsEffective(model, enabled) {
|
|
78
|
+
return codexFastAvailability(model, enabled).kind === "available" && enabled;
|
|
2623
79
|
}
|
|
2624
|
-
|
|
2625
|
-
if (!
|
|
2626
|
-
|
|
2627
|
-
const chunks = [];
|
|
2628
|
-
let total = 0;
|
|
2629
|
-
let truncated = false;
|
|
2630
|
-
const abort = () => void reader.cancel().catch(() => void 0);
|
|
2631
|
-
if (signal.aborted) abort();
|
|
2632
|
-
else signal.addEventListener("abort", abort, { once: true });
|
|
2633
|
-
try {
|
|
2634
|
-
while (true) {
|
|
2635
|
-
const { done, value } = await reader.read();
|
|
2636
|
-
if (done) break;
|
|
2637
|
-
const remaining = maxBytes - total;
|
|
2638
|
-
if (value.byteLength > remaining) {
|
|
2639
|
-
if (remaining > 0) chunks.push(value.subarray(0, remaining));
|
|
2640
|
-
total = maxBytes;
|
|
2641
|
-
truncated = true;
|
|
2642
|
-
await reader.cancel();
|
|
2643
|
-
break;
|
|
2644
|
-
}
|
|
2645
|
-
chunks.push(value);
|
|
2646
|
-
total += value.byteLength;
|
|
2647
|
-
}
|
|
2648
|
-
} finally {
|
|
2649
|
-
signal.removeEventListener("abort", abort);
|
|
2650
|
-
reader.releaseLock();
|
|
2651
|
-
}
|
|
2652
|
-
if (truncated && !truncateOverflow) {
|
|
2653
|
-
throw new Error(`${description} response exceeded ${maxBytes} bytes.`);
|
|
2654
|
-
}
|
|
2655
|
-
const body = new Uint8Array(total);
|
|
2656
|
-
let offset = 0;
|
|
2657
|
-
for (const chunk of chunks) {
|
|
2658
|
-
body.set(chunk, offset);
|
|
2659
|
-
offset += chunk.byteLength;
|
|
2660
|
-
}
|
|
2661
|
-
const text = new TextDecoder().decode(body);
|
|
2662
|
-
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;
|
|
2663
83
|
}
|
|
2664
|
-
function
|
|
2665
|
-
const
|
|
2666
|
-
if (!
|
|
2667
|
-
|
|
2668
|
-
let sawMatchingAccess = false;
|
|
2669
|
-
let sawIncompleteMatch = false;
|
|
2670
|
-
const matches = [];
|
|
2671
|
-
for (const candidate of candidates) {
|
|
2672
|
-
try {
|
|
2673
|
-
const credential = asObject13(candidate);
|
|
2674
|
-
if (credential?.type !== "oauth") continue;
|
|
2675
|
-
sawOAuth = true;
|
|
2676
|
-
if (credential.access !== resolvedAccess) continue;
|
|
2677
|
-
sawMatchingAccess = true;
|
|
2678
|
-
if (typeof credential.access !== "string" || !credential.access || typeof credential.refresh !== "string" || !credential.refresh || typeof credential.expires !== "number" || !Number.isFinite(credential.expires)) {
|
|
2679
|
-
sawIncompleteMatch = true;
|
|
2680
|
-
continue;
|
|
2681
|
-
}
|
|
2682
|
-
matches.push({ access: credential.access, refresh: credential.refresh });
|
|
2683
|
-
} catch {
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
if (sawIncompleteMatch) throw new Error("The matching xAI OAuth credential was incomplete.");
|
|
2687
|
-
if (matches.length > 1) {
|
|
2688
|
-
throw new Error("Multiple OAuth credentials match the active xAI runtime account.");
|
|
2689
|
-
}
|
|
2690
|
-
const match = matches[0];
|
|
2691
|
-
if (!match) {
|
|
2692
|
-
if (!sawOAuth) {
|
|
2693
|
-
throw new Error(
|
|
2694
|
-
"xAI consumer usage requires the OAuth subscription account configured through Pi /login; XAI_API_KEY users can review API spend at console.x.ai."
|
|
2695
|
-
);
|
|
2696
|
-
}
|
|
2697
|
-
if (sawMatchingAccess) throw new Error("The matching xAI OAuth credential was incomplete.");
|
|
2698
|
-
throw new Error("The active xAI runtime account does not match Pi's stored OAuth account.");
|
|
2699
|
-
}
|
|
2700
|
-
const authorization = `Bearer ${match.access}`;
|
|
2701
|
-
const headers = { Authorization: authorization };
|
|
2702
|
-
return {
|
|
2703
|
-
apiKey: match.access,
|
|
2704
|
-
headers,
|
|
2705
|
-
fingerprint: fingerprintResolvedAuth({ headers }, salt),
|
|
2706
|
-
secrets: [
|
|
2707
|
-
match.access,
|
|
2708
|
-
match.refresh,
|
|
2709
|
-
resolvedAccess,
|
|
2710
|
-
auth.apiKey,
|
|
2711
|
-
headerValue(auth.headers, "Authorization"),
|
|
2712
|
-
authorization
|
|
2713
|
-
].filter((value) => Boolean(value)),
|
|
2714
|
-
model
|
|
2715
|
-
};
|
|
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 };
|
|
2716
88
|
}
|
|
2717
|
-
function
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
let sawOAuth = false;
|
|
2721
|
-
let sawMatchingAccess = false;
|
|
2722
|
-
let sawIncompleteMatch = false;
|
|
2723
|
-
let sawEnterpriseMatch = false;
|
|
2724
|
-
const matches = /* @__PURE__ */ new Map();
|
|
2725
|
-
for (const candidate of candidates) {
|
|
2726
|
-
try {
|
|
2727
|
-
const credential = asObject13(candidate);
|
|
2728
|
-
if (credential?.type !== "oauth") continue;
|
|
2729
|
-
sawOAuth = true;
|
|
2730
|
-
const storedAccess2 = typeof credential.access === "string" && credential.access ? credential.access : void 0;
|
|
2731
|
-
if (storedAccess2 !== resolvedAccess) continue;
|
|
2732
|
-
sawMatchingAccess = true;
|
|
2733
|
-
const enterpriseUrl = credential.enterpriseUrl;
|
|
2734
|
-
if (typeof enterpriseUrl === "string" && enterpriseUrl && !isPublicGitHubDomain(enterpriseUrl)) {
|
|
2735
|
-
sawEnterpriseMatch = true;
|
|
2736
|
-
continue;
|
|
2737
|
-
}
|
|
2738
|
-
const refresh2 = typeof credential.refresh === "string" && credential.refresh ? credential.refresh : void 0;
|
|
2739
|
-
if (!refresh2) {
|
|
2740
|
-
sawIncompleteMatch = true;
|
|
2741
|
-
continue;
|
|
2742
|
-
}
|
|
2743
|
-
matches.set(`${storedAccess2.length}:${storedAccess2}${refresh2}`, { refresh: refresh2, storedAccess: storedAccess2 });
|
|
2744
|
-
} catch {
|
|
2745
|
-
}
|
|
2746
|
-
}
|
|
2747
|
-
if (sawEnterpriseMatch) {
|
|
2748
|
-
throw new Error("GitHub Copilot usage does not yet support GitHub Enterprise accounts.");
|
|
2749
|
-
}
|
|
2750
|
-
if (sawIncompleteMatch) throw new Error("GitHub Copilot OAuth credentials were incomplete.");
|
|
2751
|
-
if (matches.size > 1) {
|
|
2752
|
-
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;
|
|
2753
92
|
}
|
|
2754
|
-
const
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
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."
|
|
2764
|
-
);
|
|
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;
|
|
2765
102
|
}
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
const headers = {
|
|
2769
|
-
Authorization: authorization,
|
|
2770
|
-
"X-GitHub-Api-Version": "2025-05-01"
|
|
2771
|
-
};
|
|
2772
|
-
return {
|
|
2773
|
-
apiKey: refresh,
|
|
2774
|
-
headers,
|
|
2775
|
-
fingerprint: fingerprintResolvedAuth({ headers }, salt),
|
|
2776
|
-
secrets: [refresh, storedAccess, resolvedAccess, authorization],
|
|
2777
|
-
model
|
|
2778
|
-
};
|
|
2779
|
-
}
|
|
2780
|
-
function authorizationFrom(auth) {
|
|
2781
|
-
return headerValue(auth.headers, "Authorization") ?? (auth.apiKey ? `Bearer ${auth.apiKey}` : void 0);
|
|
2782
|
-
}
|
|
2783
|
-
function bearerToken(authorization) {
|
|
2784
|
-
const match = /^Bearer\s+(.+)$/iu.exec(authorization ?? "");
|
|
2785
|
-
return match?.[1];
|
|
103
|
+
if (costsEqual(cost, correctedCost)) return void 0;
|
|
104
|
+
return { ...message, usage: correctedUsage };
|
|
2786
105
|
}
|
|
2787
|
-
function
|
|
2788
|
-
if (!
|
|
2789
|
-
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)}`;
|
|
2790
109
|
}
|
|
2791
|
-
function
|
|
2792
|
-
|
|
2793
|
-
const url = new URL(value.includes("://") ? value : `https://${value}`);
|
|
2794
|
-
return url.hostname.toLowerCase() === "github.com";
|
|
2795
|
-
} catch {
|
|
110
|
+
function isOfficialCodexModel(model) {
|
|
111
|
+
if (model?.provider !== "openai-codex" || !hasApi(model, "openai-codex-responses")) {
|
|
2796
112
|
return false;
|
|
2797
113
|
}
|
|
2798
|
-
}
|
|
2799
|
-
function hasOfficialOrigin(model, providerId) {
|
|
2800
|
-
return hasOfficialUrlOrigin(model.baseUrl, providerId);
|
|
2801
|
-
}
|
|
2802
|
-
function hasOfficialUrlOrigin(value, providerId) {
|
|
2803
114
|
try {
|
|
2804
|
-
|
|
2805
|
-
if (providerId === "baseten") {
|
|
2806
|
-
return ["https://inference.baseten.co", "https://api.baseten.co"].includes(url.origin);
|
|
2807
|
-
}
|
|
2808
|
-
if (providerId === "openai-codex") return url.origin === "https://chatgpt.com";
|
|
2809
|
-
if (providerId === "deepseek") return url.origin === "https://api.deepseek.com";
|
|
2810
|
-
if (providerId === "fireworks") return url.origin === "https://api.fireworks.ai";
|
|
2811
|
-
if (providerId === "openrouter") return url.origin === "https://openrouter.ai";
|
|
2812
|
-
if (providerId === "vercel-ai-gateway") return url.origin === "https://ai-gateway.vercel.sh";
|
|
2813
|
-
if (providerId === "opencode-go") return url.origin === "https://opencode.ai";
|
|
2814
|
-
if (providerId === "kimi-coding") return url.origin === "https://api.kimi.com";
|
|
2815
|
-
if (providerId === "minimax") return url.origin === "https://api.minimax.io";
|
|
2816
|
-
if (providerId === "minimax-cn") return url.origin === "https://api.minimaxi.com";
|
|
2817
|
-
if (providerId === "moonshotai") return url.origin === "https://api.moonshot.ai";
|
|
2818
|
-
if (providerId === "moonshotai-cn") return url.origin === "https://api.moonshot.cn";
|
|
2819
|
-
if (providerId === "xai") return url.origin === "https://api.x.ai";
|
|
2820
|
-
if (providerId === "zai") return url.origin === "https://api.z.ai";
|
|
2821
|
-
if (providerId === "zai-coding-cn") return url.origin === "https://open.bigmodel.cn";
|
|
2822
|
-
if (providerId === "github-copilot") {
|
|
2823
|
-
return url.protocol === "https:" && /^api\.[a-z0-9-]+\.githubcopilot\.com$/u.test(url.hostname);
|
|
2824
|
-
}
|
|
2825
|
-
return false;
|
|
115
|
+
return new URL(model.baseUrl).origin === "https://chatgpt.com";
|
|
2826
116
|
} catch {
|
|
2827
117
|
return false;
|
|
2828
118
|
}
|
|
2829
119
|
}
|
|
2830
|
-
function
|
|
2831
|
-
|
|
2832
|
-
return entry?.[1] ?? void 0;
|
|
2833
|
-
}
|
|
2834
|
-
function hasHeader(headers, name) {
|
|
2835
|
-
return Object.keys(headers).some((key) => key.toLowerCase() === name.toLowerCase());
|
|
2836
|
-
}
|
|
2837
|
-
function validatedXaiUserId(value) {
|
|
2838
|
-
if (typeof value !== "string" || !/^[A-Za-z0-9._~-]{1,128}$/u.test(value)) {
|
|
2839
|
-
throw new Error("xAI consumer identity returned an unsafe canonical user ID.");
|
|
2840
|
-
}
|
|
2841
|
-
return value;
|
|
2842
|
-
}
|
|
2843
|
-
function basetenBillingUsageUrl(windowAt) {
|
|
2844
|
-
const url = new URL(BASETEN_BILLING_USAGE_URL);
|
|
2845
|
-
url.searchParams.set(
|
|
2846
|
-
"start_date",
|
|
2847
|
-
new Date(windowAt - BASETEN_USAGE_WINDOW_DAYS * 24 * 60 * 60 * 1e3).toISOString()
|
|
2848
|
-
);
|
|
2849
|
-
url.searchParams.set("end_date", new Date(windowAt).toISOString());
|
|
2850
|
-
return url.toString();
|
|
2851
|
-
}
|
|
2852
|
-
async function queryMiniMaxUsage(providerId, auth, signal, timeoutMs, guard) {
|
|
2853
|
-
if (!guard) throw new Error("MiniMax usage requires request-boundary revalidation.");
|
|
2854
|
-
const apiKey = bearerToken(headerValue(auth.headers, "Authorization")) ?? auth.apiKey;
|
|
2855
|
-
if (!apiKey) throw new Error("MiniMax runtime API key was unavailable.");
|
|
2856
|
-
const kind = miniMaxUsageKind(apiKey);
|
|
2857
|
-
const path = kind === "account-balance" ? "/account/query_balance" : "/v1/token_plan/remains";
|
|
2858
|
-
const startedAt = Date.now();
|
|
2859
|
-
await guard();
|
|
2860
|
-
const payload = await fetchProviderJson(
|
|
2861
|
-
`${MINIMAX_API_ROOTS[providerId]}${path}`,
|
|
2862
|
-
auth,
|
|
2863
|
-
signal,
|
|
2864
|
-
remainingTimeout2(timeoutMs, startedAt, "fetching MiniMax usage"),
|
|
2865
|
-
"MiniMax usage endpoint",
|
|
2866
|
-
{ redirect: "error" }
|
|
2867
|
-
);
|
|
2868
|
-
await guard();
|
|
2869
|
-
return normalizeMiniMaxUsagePayload(providerId, kind, payload, Date.now());
|
|
2870
|
-
}
|
|
2871
|
-
async function queryMoonshotBalance(providerId, auth, signal, timeoutMs, guard) {
|
|
2872
|
-
if (!guard) throw new Error("Moonshot AI balance requires request-boundary revalidation.");
|
|
2873
|
-
const startedAt = Date.now();
|
|
2874
|
-
await guard();
|
|
2875
|
-
const payload = await fetchProviderJson(
|
|
2876
|
-
MOONSHOT_BALANCE_URLS[providerId],
|
|
2877
|
-
auth,
|
|
2878
|
-
signal,
|
|
2879
|
-
remainingTimeout2(timeoutMs, startedAt, "fetching Moonshot AI balance"),
|
|
2880
|
-
"Moonshot AI balance endpoint",
|
|
2881
|
-
{ redirect: "error" }
|
|
2882
|
-
);
|
|
2883
|
-
await guard();
|
|
2884
|
-
return normalizeMoonshotBalancePayload(providerId, payload, Date.now());
|
|
2885
|
-
}
|
|
2886
|
-
function remainingTimeout2(timeoutMs, startedAt, description = "fetching xAI consumer usage") {
|
|
2887
|
-
const remaining = timeoutMs - (Date.now() - startedAt);
|
|
2888
|
-
if (remaining <= 0) throw new Error(`Timed out while ${description}.`);
|
|
2889
|
-
return remaining;
|
|
2890
|
-
}
|
|
2891
|
-
function zaiOrigin(baseUrl) {
|
|
2892
|
-
const base = baseUrl?.trim();
|
|
2893
|
-
if (!base) throw new Error("Z.AI model base URL is unavailable.");
|
|
2894
|
-
return new URL(base).origin;
|
|
2895
|
-
}
|
|
2896
|
-
function zaiMonitorUrl(baseUrl) {
|
|
2897
|
-
return `${zaiOrigin(baseUrl)}/api/monitor/usage/quota/limit`;
|
|
2898
|
-
}
|
|
2899
|
-
function zaiMonitorAuth(auth) {
|
|
2900
|
-
const authorization = headerValue(auth.headers, "Authorization");
|
|
2901
|
-
const token = authorization === void 0 ? void 0 : bearerToken(authorization) ?? authorization;
|
|
2902
|
-
if (token === void 0 || token === authorization) return auth;
|
|
2903
|
-
return { ...auth, headers: { ...auth.headers, Authorization: token } };
|
|
120
|
+
function isRecord(value) {
|
|
121
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2904
122
|
}
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
await guard();
|
|
2909
|
-
const payload = await fetchProviderJson(
|
|
2910
|
-
zaiMonitorUrl(auth.model.baseUrl),
|
|
2911
|
-
zaiMonitorAuth(auth),
|
|
2912
|
-
signal,
|
|
2913
|
-
remainingTimeout2(timeoutMs, startedAt, `fetching ${providerName} quota`),
|
|
2914
|
-
`${providerName} quota endpoint`,
|
|
2915
|
-
{ responseError: zaiResponseError }
|
|
123
|
+
function hasCompleteUsage(value) {
|
|
124
|
+
return ["input", "output", "cacheRead", "cacheWrite"].every(
|
|
125
|
+
(key) => typeof value[key] === "number" && Number.isFinite(value[key])
|
|
2916
126
|
);
|
|
2917
|
-
await guard();
|
|
2918
|
-
const planTimeoutMs = timeoutMs - (Date.now() - startedAt);
|
|
2919
|
-
const plan = await fetchZaiPlan(providerName, auth, signal, planTimeoutMs);
|
|
2920
|
-
return normalizeZaiQuotaPayload(providerId, providerName, payload, Date.now(), plan);
|
|
2921
|
-
}
|
|
2922
|
-
async function fetchZaiPlan(providerName, auth, signal, timeoutMs) {
|
|
2923
|
-
if (timeoutMs <= 0 || signal.aborted) return void 0;
|
|
2924
|
-
try {
|
|
2925
|
-
const payload = await fetchProviderJson(
|
|
2926
|
-
`${zaiOrigin(auth.model.baseUrl)}/api/biz/subscription/list`,
|
|
2927
|
-
zaiMonitorAuth(auth),
|
|
2928
|
-
signal,
|
|
2929
|
-
timeoutMs,
|
|
2930
|
-
`${providerName} plan endpoint`,
|
|
2931
|
-
{ responseError: zaiResponseError }
|
|
2932
|
-
);
|
|
2933
|
-
return normalizeZaiSubscriptionPayload(payload);
|
|
2934
|
-
} catch (error) {
|
|
2935
|
-
if (isAbortError(error)) throw error;
|
|
2936
|
-
return void 0;
|
|
2937
|
-
}
|
|
2938
127
|
}
|
|
2939
|
-
function
|
|
2940
|
-
return
|
|
128
|
+
function costsEqual(left, right) {
|
|
129
|
+
return ["input", "output", "cacheRead", "cacheWrite", "total"].every((key) => left[key] === right[key]);
|
|
2941
130
|
}
|
|
2942
131
|
|
|
2943
132
|
// src/codex-resets.ts
|
|
133
|
+
import { readStoredCredential } from "@earendil-works/pi-coding-agent";
|
|
2944
134
|
var CODEX_RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
|
|
2945
135
|
var CODEX_RESET_CONSUME_URL = `${CODEX_RESET_CREDITS_URL}/consume`;
|
|
2946
136
|
var MAX_RESET_OPTIONS = 32;
|
|
2947
137
|
var MAX_CREDIT_ID_CHARS = 1024;
|
|
2948
|
-
function codexResetCount(
|
|
2949
|
-
const value =
|
|
138
|
+
function codexResetCount(report) {
|
|
139
|
+
const value = report.metrics.find((metric) => metric.id === "reset-credits")?.value;
|
|
2950
140
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
2951
141
|
}
|
|
2952
|
-
function codexResetActionDescription(
|
|
2953
|
-
const count = codexResetCount(
|
|
142
|
+
function codexResetActionDescription(report) {
|
|
143
|
+
const count = codexResetCount(report);
|
|
2954
144
|
if (count === void 0) return "Check reset availability.";
|
|
2955
145
|
if (count === 0) return "No usage limit resets available.";
|
|
2956
146
|
return `You have ${count} ${resetLabel(count)} available.`;
|
|
@@ -2991,7 +181,7 @@ function formatCodexResetOutcome(outcome, remainingCount) {
|
|
|
2991
181
|
function resetLabel(count) {
|
|
2992
182
|
return count === 1 ? "usage limit reset" : "usage limit resets";
|
|
2993
183
|
}
|
|
2994
|
-
async function resolveCodexResetAuth(ctx, salt = AUTH_FINGERPRINT_SALT, credentialReader =
|
|
184
|
+
async function resolveCodexResetAuth(ctx, salt = AUTH_FINGERPRINT_SALT, credentialReader = readStoredCredential, candidateReader) {
|
|
2995
185
|
const model = ctx.model;
|
|
2996
186
|
if (model?.provider !== "openai-codex") {
|
|
2997
187
|
throw new Error("Usage limit resets require the current model to use OpenAI Codex.");
|
|
@@ -3009,7 +199,7 @@ async function resolveCodexResetAuth(ctx, salt = AUTH_FINGERPRINT_SALT, credenti
|
|
|
3009
199
|
if (!auth) throw new Error("No runtime credential is configured for OpenAI Codex.");
|
|
3010
200
|
const offered = candidateReader ? await candidateReader(ctx, "openai-codex") : fallbackOAuthCredentialCandidates("openai-codex", credentialReader);
|
|
3011
201
|
if (!offered.ok) throw new Error("OpenAI Codex OAuth credential discovery failed closed.");
|
|
3012
|
-
const resolvedAccess =
|
|
202
|
+
const resolvedAccess = bearerToken(headerValue(auth.headers, "Authorization")) ?? auth.apiKey;
|
|
3013
203
|
if (!resolvedAccess) throw new Error("OpenAI Codex OAuth credentials were incomplete.");
|
|
3014
204
|
const resolvedAccountId = codexAccountIdFromAccessToken(resolvedAccess);
|
|
3015
205
|
if (!resolvedAccountId) {
|
|
@@ -3064,14 +254,14 @@ async function consumeCodexResetCredit(auth, option, redeemRequestId, signal, ti
|
|
|
3064
254
|
if (!isCodexResetOutcomeCode(code)) {
|
|
3065
255
|
throw new Error("Codex reset consume endpoint returned an unknown outcome code.");
|
|
3066
256
|
}
|
|
3067
|
-
const windowsReset = payload.windows_reset === void 0 ? 0 :
|
|
257
|
+
const windowsReset = payload.windows_reset === void 0 ? 0 : nonnegativeInteger(payload.windows_reset);
|
|
3068
258
|
if (windowsReset === void 0) {
|
|
3069
259
|
throw new Error("Codex reset consume endpoint returned an invalid windows_reset value.");
|
|
3070
260
|
}
|
|
3071
261
|
return { code, windowsReset };
|
|
3072
262
|
}
|
|
3073
263
|
function normalizeCodexResetCreditsPayload(payload) {
|
|
3074
|
-
const availableCount =
|
|
264
|
+
const availableCount = nonnegativeInteger(payload.available_count);
|
|
3075
265
|
if (availableCount === void 0) {
|
|
3076
266
|
throw new Error("Codex reset credits response returned an invalid available_count.");
|
|
3077
267
|
}
|
|
@@ -3079,7 +269,7 @@ function normalizeCodexResetCreditsPayload(payload) {
|
|
|
3079
269
|
if (rawCredits !== void 0 && !Array.isArray(rawCredits)) {
|
|
3080
270
|
throw new Error("Codex reset credits response returned invalid credits.");
|
|
3081
271
|
}
|
|
3082
|
-
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));
|
|
3083
273
|
if (availableCount > 0 && options.length === 0) {
|
|
3084
274
|
options.push(genericCodexResetOption());
|
|
3085
275
|
}
|
|
@@ -3092,7 +282,7 @@ function selectCodexResetCredential(candidates, resolvedAccess, resolvedAccountI
|
|
|
3092
282
|
const matches = /* @__PURE__ */ new Map();
|
|
3093
283
|
for (const candidate of candidates) {
|
|
3094
284
|
try {
|
|
3095
|
-
const credential =
|
|
285
|
+
const credential = asObject(candidate);
|
|
3096
286
|
if (credential?.type !== "oauth") continue;
|
|
3097
287
|
sawOAuth = true;
|
|
3098
288
|
const storedAccess = asNonemptyString(credential.access);
|
|
@@ -3131,7 +321,7 @@ function codexAccountIdFromAccessToken(access) {
|
|
|
3131
321
|
const parts = access.split(".");
|
|
3132
322
|
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
3133
323
|
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
3134
|
-
const claims =
|
|
324
|
+
const claims = asObject(asObject(payload)?.["https://api.openai.com/auth"]);
|
|
3135
325
|
return validHeaderValue(claims?.chatgpt_account_id);
|
|
3136
326
|
} catch {
|
|
3137
327
|
return void 0;
|
|
@@ -3163,7 +353,7 @@ function normalizeResetOption(credit) {
|
|
|
3163
353
|
function isCodexResetOutcomeCode(value) {
|
|
3164
354
|
return value === "reset" || value === "nothing_to_reset" || value === "no_credit" || value === "already_redeemed";
|
|
3165
355
|
}
|
|
3166
|
-
function
|
|
356
|
+
function asObject(value) {
|
|
3167
357
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
3168
358
|
return value;
|
|
3169
359
|
}
|
|
@@ -3185,74 +375,85 @@ function validHeaderValue(value) {
|
|
|
3185
375
|
if (/[^\x20-\x7e]/u.test(value)) return void 0;
|
|
3186
376
|
return value;
|
|
3187
377
|
}
|
|
3188
|
-
function
|
|
378
|
+
function nonnegativeInteger(value) {
|
|
3189
379
|
const parsed = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value) : Number.NaN;
|
|
3190
380
|
if (!Number.isSafeInteger(parsed) || parsed < 0) return void 0;
|
|
3191
381
|
return parsed;
|
|
3192
382
|
}
|
|
3193
|
-
function
|
|
383
|
+
function bearerToken(authorization) {
|
|
3194
384
|
return /^Bearer\s+(.+)$/iu.exec(authorization ?? "")?.[1];
|
|
3195
385
|
}
|
|
3196
|
-
function
|
|
386
|
+
function headerValue(headers, name) {
|
|
3197
387
|
return Object.entries(headers).find(([candidate]) => candidate.toLowerCase() === name.toLowerCase())?.[1];
|
|
3198
388
|
}
|
|
3199
389
|
|
|
3200
390
|
// src/format.ts
|
|
3201
391
|
var BAR_SEGMENTS = 20;
|
|
3202
392
|
var VALUE_COLUMN = 29;
|
|
3203
|
-
function formatUsageReport(
|
|
393
|
+
function formatUsageReport(report, displayState) {
|
|
3204
394
|
const stateLabel = displayState === "current" ? "Current" : "Configured";
|
|
3205
|
-
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`;
|
|
3206
396
|
const lines = [`${title} \xB7 ${stateLabel}`];
|
|
3207
|
-
if (
|
|
3208
|
-
lines.push(`Semantics: ${
|
|
3209
|
-
if (
|
|
3210
|
-
else if (
|
|
3211
|
-
else if (
|
|
3212
|
-
else if (
|
|
3213
|
-
else if (
|
|
3214
|
-
else if (
|
|
3215
|
-
else if (
|
|
3216
|
-
else if (
|
|
3217
|
-
else if (
|
|
3218
|
-
else if (
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
else if (
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
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);
|
|
3228
419
|
}
|
|
3229
420
|
return lines.join("\n").trimEnd();
|
|
3230
421
|
}
|
|
3231
|
-
function formatUsageStatusline(
|
|
3232
|
-
if (
|
|
3233
|
-
if (
|
|
3234
|
-
return
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
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");
|
|
3242
443
|
if (limit?.remaining !== void 0) return `openrouter ${formatUsd(limit.remaining)} left`;
|
|
3243
|
-
const total =
|
|
444
|
+
const total = report.metrics.find((metric) => metric.id === "usage-total");
|
|
3244
445
|
if (typeof total?.value === "number") return `openrouter ${formatUsd(total.value)} used`;
|
|
3245
446
|
}
|
|
3246
|
-
if (
|
|
3247
|
-
if (
|
|
3248
|
-
if (
|
|
3249
|
-
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);
|
|
3250
451
|
}
|
|
3251
|
-
if (
|
|
3252
|
-
return formatMiniMaxStatusline(
|
|
452
|
+
if (report.providerId === "minimax" || report.providerId === "minimax-cn") {
|
|
453
|
+
return formatMiniMaxStatusline(report, model);
|
|
3253
454
|
}
|
|
3254
|
-
if (
|
|
3255
|
-
return formatZaiStatusline(
|
|
455
|
+
if (report.providerId === "zai" || report.providerId === "zai-coding-cn") {
|
|
456
|
+
return formatZaiStatusline(report);
|
|
3256
457
|
}
|
|
3257
458
|
return void 0;
|
|
3258
459
|
}
|
|
@@ -3269,19 +470,19 @@ Selection required: choose this provider's ${state.singularLabel} by viewing it
|
|
|
3269
470
|
${status}: ${state.message}`;
|
|
3270
471
|
}).join("\n\n");
|
|
3271
472
|
}
|
|
3272
|
-
function formatBasetenReport(lines,
|
|
473
|
+
function formatBasetenReport(lines, report) {
|
|
3273
474
|
lines.push(`${"Spend window:".padEnd(VALUE_COLUMN)}Last 30 days`);
|
|
3274
|
-
for (const
|
|
3275
|
-
lines.push(`${`${
|
|
475
|
+
for (const metric of report.metrics) {
|
|
476
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}USD ${metric.value}`);
|
|
3276
477
|
}
|
|
3277
478
|
}
|
|
3278
|
-
function formatBasetenStatusline(
|
|
3279
|
-
const subtotal =
|
|
479
|
+
function formatBasetenStatusline(report) {
|
|
480
|
+
const subtotal = report.metrics.find((metric) => metric.id === "net-subtotal");
|
|
3280
481
|
return subtotal ? `baseten USD ${subtotal.value} net` : "baseten no Model APIs usage";
|
|
3281
482
|
}
|
|
3282
|
-
function formatCodexReport(lines,
|
|
483
|
+
function formatCodexReport(lines, report) {
|
|
3283
484
|
let previousGroup;
|
|
3284
|
-
for (const bucket of
|
|
485
|
+
for (const bucket of report.buckets) {
|
|
3285
486
|
const group = bucket.groupId ?? bucket.id;
|
|
3286
487
|
if (group !== previousGroup && group !== "codex") {
|
|
3287
488
|
lines.push(`${bucket.groupLabel ?? group} limit:`);
|
|
@@ -3291,71 +492,71 @@ function formatCodexReport(lines, report2) {
|
|
|
3291
492
|
const label = `${formatWindowLabel(bucket.windowMinutes, fallback, false)} limit:`;
|
|
3292
493
|
lines.push(`${label.padEnd(VALUE_COLUMN)}${formatPercentBucket(bucket)}`);
|
|
3293
494
|
}
|
|
3294
|
-
for (const
|
|
3295
|
-
if (
|
|
3296
|
-
lines.push(`${"Usage limit resets:".padEnd(VALUE_COLUMN)}${
|
|
3297
|
-
} else if (
|
|
3298
|
-
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)}`);
|
|
3299
500
|
}
|
|
3300
501
|
}
|
|
3301
502
|
}
|
|
3302
|
-
function formatDeepSeekReport(lines,
|
|
3303
|
-
const availability =
|
|
503
|
+
function formatDeepSeekReport(lines, report) {
|
|
504
|
+
const availability = report.metrics.find((metric) => metric.id === "api-availability");
|
|
3304
505
|
lines.push(
|
|
3305
506
|
`${"API calls:".padEnd(VALUE_COLUMN)}${availability?.value === "available" ? "Available" : "Unavailable"}`
|
|
3306
507
|
);
|
|
3307
508
|
for (const currency of ["CNY", "USD"]) {
|
|
3308
|
-
const metrics =
|
|
509
|
+
const metrics = report.metrics.filter((metric) => metric.currency === currency);
|
|
3309
510
|
if (metrics.length === 0) continue;
|
|
3310
511
|
lines.push("", `${currency} balance:`);
|
|
3311
|
-
for (const
|
|
3312
|
-
lines.push(`${`${
|
|
512
|
+
for (const metric of metrics) {
|
|
513
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${currency} ${metric.value}`);
|
|
3313
514
|
}
|
|
3314
515
|
}
|
|
3315
516
|
}
|
|
3316
|
-
function formatDeepSeekStatusline(
|
|
3317
|
-
const availability =
|
|
517
|
+
function formatDeepSeekStatusline(report) {
|
|
518
|
+
const availability = report.metrics.find((metric) => metric.id === "api-availability");
|
|
3318
519
|
if (availability?.value !== "available") return "deepseek API unavailable";
|
|
3319
520
|
const totals = ["CNY", "USD"].flatMap((currency) => {
|
|
3320
|
-
const
|
|
3321
|
-
return
|
|
521
|
+
const metric = report.metrics.find((candidate) => candidate.id === `${currency.toLowerCase()}-total`);
|
|
522
|
+
return metric ? [`${currency} ${metric.value}`] : [];
|
|
3322
523
|
});
|
|
3323
524
|
return totals.length > 0 ? `deepseek ${totals.join(" \xB7 ")}` : "deepseek balance unavailable";
|
|
3324
525
|
}
|
|
3325
|
-
function formatFireworksReport(lines,
|
|
526
|
+
function formatFireworksReport(lines, report) {
|
|
3326
527
|
lines.push(`${"Spend window:".padEnd(VALUE_COLUMN)}Last 30 days (rated)`);
|
|
3327
|
-
for (const currency of fireworksCurrencies(
|
|
528
|
+
for (const currency of fireworksCurrencies(report)) {
|
|
3328
529
|
lines.push("", `${currency} rated spend:`);
|
|
3329
|
-
for (const
|
|
3330
|
-
if (
|
|
3331
|
-
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}`);
|
|
3332
533
|
}
|
|
3333
534
|
}
|
|
3334
535
|
}
|
|
3335
|
-
function formatFireworksStatusline(
|
|
3336
|
-
const totals =
|
|
536
|
+
function formatFireworksStatusline(report) {
|
|
537
|
+
const totals = report.metrics.filter((metric) => metric.id.endsWith("-total"));
|
|
3337
538
|
if (totals.length === 0) return "fireworks no rated usage";
|
|
3338
|
-
return `fireworks ${totals.map((
|
|
539
|
+
return `fireworks ${totals.map((metric) => `${metric.currency} ${metric.value}`).join(" \xB7 ")}`;
|
|
3339
540
|
}
|
|
3340
|
-
function fireworksCurrencies(
|
|
541
|
+
function fireworksCurrencies(report) {
|
|
3341
542
|
const currencies = [];
|
|
3342
|
-
for (const
|
|
3343
|
-
if (!
|
|
3344
|
-
currencies.push(
|
|
543
|
+
for (const metric of report.metrics) {
|
|
544
|
+
if (!metric.currency || currencies.includes(metric.currency)) continue;
|
|
545
|
+
currencies.push(metric.currency);
|
|
3345
546
|
}
|
|
3346
547
|
return currencies;
|
|
3347
548
|
}
|
|
3348
|
-
function formatVercelAIGatewayReport(lines,
|
|
3349
|
-
for (const
|
|
3350
|
-
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}`);
|
|
3351
552
|
}
|
|
3352
553
|
}
|
|
3353
|
-
function formatVercelAIGatewayStatusline(
|
|
3354
|
-
const balance =
|
|
554
|
+
function formatVercelAIGatewayStatusline(report) {
|
|
555
|
+
const balance = report.metrics.find((metric) => metric.id === "credit-balance");
|
|
3355
556
|
return balance ? `vercel USD ${balance.value} left` : "vercel credits unavailable";
|
|
3356
557
|
}
|
|
3357
|
-
function formatGitHubCopilotReport(lines,
|
|
3358
|
-
const quota = findGitHubCopilotQuota(
|
|
558
|
+
function formatGitHubCopilotReport(lines, report) {
|
|
559
|
+
const quota = findGitHubCopilotQuota(report);
|
|
3359
560
|
if (!quota || quota.limit === void 0 || quota.remaining === void 0) {
|
|
3360
561
|
lines.push(`${`${quota?.label ?? "Copilot quota"}:`.padEnd(VALUE_COLUMN)}unlimited`);
|
|
3361
562
|
return;
|
|
@@ -3365,23 +566,23 @@ function formatGitHubCopilotReport(lines, report2) {
|
|
|
3365
566
|
lines.push(
|
|
3366
567
|
`${`${quota.label}:`.padEnd(VALUE_COLUMN)}${quota.remaining} of ${quota.limit} left \xB7 ${percent}%${reset}`
|
|
3367
568
|
);
|
|
3368
|
-
const overage =
|
|
569
|
+
const overage = report.metrics.find((metric) => metric.id === "overage-used");
|
|
3369
570
|
if (typeof overage?.value === "number" && overage.value > 0) {
|
|
3370
571
|
lines.push(`${"Additional usage:".padEnd(VALUE_COLUMN)}${overage.value} ${quota.label}`);
|
|
3371
572
|
}
|
|
3372
573
|
}
|
|
3373
|
-
function formatGitHubCopilotStatusline(
|
|
3374
|
-
const quota = findGitHubCopilotQuota(
|
|
574
|
+
function formatGitHubCopilotStatusline(report) {
|
|
575
|
+
const quota = findGitHubCopilotQuota(report);
|
|
3375
576
|
const kind = compactGitHubCopilotQuotaKind(quota);
|
|
3376
577
|
if (!quota || quota.limit === void 0 || quota.remaining === void 0) {
|
|
3377
578
|
return `copilot ${kind} unlimited`;
|
|
3378
579
|
}
|
|
3379
|
-
const overage =
|
|
580
|
+
const overage = report.metrics.find((metric) => metric.id === "overage-used");
|
|
3380
581
|
const overageSuffix = typeof overage?.value === "number" && overage.value > 0 ? ` +${overage.value} over` : "";
|
|
3381
582
|
return `copilot ${kind === "premium" ? "" : `${kind} `}${quota.remaining}/${quota.limit} ${percentRemaining(quota)}%${overageSuffix}`;
|
|
3382
583
|
}
|
|
3383
|
-
function findGitHubCopilotQuota(
|
|
3384
|
-
return
|
|
584
|
+
function findGitHubCopilotQuota(report) {
|
|
585
|
+
return report.buckets.find((bucket) => ["ai-credits", "premium-requests", "chat-requests"].includes(bucket.id));
|
|
3385
586
|
}
|
|
3386
587
|
function compactGitHubCopilotQuotaKind(bucket) {
|
|
3387
588
|
if (bucket?.id === "ai-credits") return "credits";
|
|
@@ -3390,21 +591,21 @@ function compactGitHubCopilotQuotaKind(bucket) {
|
|
|
3390
591
|
}
|
|
3391
592
|
function percentRemaining(bucket) {
|
|
3392
593
|
if (!bucket.limit || bucket.remaining === void 0) return 0;
|
|
3393
|
-
return Math.round(
|
|
594
|
+
return Math.round(clampPercent(bucket.remaining / bucket.limit * 100));
|
|
3394
595
|
}
|
|
3395
|
-
function formatOpenRouterReport(lines,
|
|
3396
|
-
const limit =
|
|
596
|
+
function formatOpenRouterReport(lines, report) {
|
|
597
|
+
const limit = report.buckets.find((bucket) => bucket.id === "key-limit");
|
|
3397
598
|
if (limit) {
|
|
3398
599
|
const period = limit.period ? ` (${limit.period})` : "";
|
|
3399
600
|
const value = limit.remaining === void 0 ? `${formatUsd(limit.limit ?? 0)} cap; remaining unavailable` : `${formatUsd(limit.remaining)} of ${formatUsd(limit.limit ?? 0)} left`;
|
|
3400
601
|
lines.push(`${`Key limit${period}:`.padEnd(VALUE_COLUMN)}${value}`);
|
|
3401
602
|
}
|
|
3402
|
-
for (const
|
|
3403
|
-
lines.push(`${`${
|
|
603
|
+
for (const metric of report.metrics) {
|
|
604
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`);
|
|
3404
605
|
}
|
|
3405
606
|
}
|
|
3406
|
-
function formatOpenCodeZenReport(lines,
|
|
3407
|
-
for (const bucket of
|
|
607
|
+
function formatOpenCodeZenReport(lines, report) {
|
|
608
|
+
for (const bucket of report.buckets) {
|
|
3408
609
|
if (bucket.unit === "percent" && bucket.used !== void 0) {
|
|
3409
610
|
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${formatPercentBucket(bucket)}`);
|
|
3410
611
|
continue;
|
|
@@ -3414,20 +615,20 @@ function formatOpenCodeZenReport(lines, report2) {
|
|
|
3414
615
|
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${used}% used${reset}`);
|
|
3415
616
|
}
|
|
3416
617
|
}
|
|
3417
|
-
function formatOpenCodeZenStatusline(
|
|
618
|
+
function formatOpenCodeZenStatusline(report) {
|
|
3418
619
|
const parts = ["zen"];
|
|
3419
|
-
for (const bucket of
|
|
620
|
+
for (const bucket of report.buckets) {
|
|
3420
621
|
if (bucket.used === void 0) continue;
|
|
3421
622
|
const compact = bucket.id === "rolling" ? "r" : bucket.id === "weekly" ? "w" : "m";
|
|
3422
|
-
parts.push(`${
|
|
623
|
+
parts.push(`${clampPercent(bucket.used).toFixed(0)}% ${compact}`);
|
|
3423
624
|
}
|
|
3424
625
|
return parts.length > 1 ? parts.join(" ") : void 0;
|
|
3425
626
|
}
|
|
3426
|
-
function formatKimiCodingReport(lines,
|
|
3427
|
-
for (const bucket of
|
|
627
|
+
function formatKimiCodingReport(lines, report) {
|
|
628
|
+
for (const bucket of report.buckets) {
|
|
3428
629
|
const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
|
|
3429
630
|
if (bucket.unit === "percent") {
|
|
3430
|
-
const remaining = bucket.remaining === void 0 ? void 0 : Math.round(
|
|
631
|
+
const remaining = bucket.remaining === void 0 ? void 0 : Math.round(clampPercent(bucket.remaining));
|
|
3431
632
|
const value = bucket.used === void 0 || remaining === void 0 ? "unavailable" : `${100 - remaining}% used \xB7 ${remaining}% left`;
|
|
3432
633
|
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${value}${reset}`);
|
|
3433
634
|
continue;
|
|
@@ -3440,10 +641,10 @@ function formatKimiCodingReport(lines, report2) {
|
|
|
3440
641
|
`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${bucket.used} of ${bucket.limit} used \xB7 ${percentRemaining(bucket)}% left${reset}`
|
|
3441
642
|
);
|
|
3442
643
|
}
|
|
3443
|
-
const balance =
|
|
3444
|
-
const total =
|
|
3445
|
-
const monthlyUsed =
|
|
3446
|
-
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");
|
|
3447
648
|
if (!balance && !monthlyUsed && !monthlyLimit) return;
|
|
3448
649
|
lines.push("", "Extra usage wallet:");
|
|
3449
650
|
if (balance) {
|
|
@@ -3457,11 +658,11 @@ function formatKimiCodingReport(lines, report2) {
|
|
|
3457
658
|
lines.push(`${"Monthly limit:".padEnd(VALUE_COLUMN)}${formatCurrencyMetric(monthlyLimit)}`);
|
|
3458
659
|
}
|
|
3459
660
|
}
|
|
3460
|
-
function formatKimiCodingStatusline(
|
|
3461
|
-
const fiveHour =
|
|
3462
|
-
const weekly =
|
|
3463
|
-
const monthly =
|
|
3464
|
-
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");
|
|
3465
666
|
const selected = [subWindow, weekly, monthly].filter(
|
|
3466
667
|
(bucket, index, buckets) => bucket !== void 0 && buckets.indexOf(bucket) === index
|
|
3467
668
|
);
|
|
@@ -3470,30 +671,30 @@ function formatKimiCodingStatusline(report2) {
|
|
|
3470
671
|
if (bucket.remaining === void 0 || bucket.unit !== "percent" && !bucket.limit) continue;
|
|
3471
672
|
const fallback = bucket.id === "weekly" ? "weekly" : "5h";
|
|
3472
673
|
const window = bucket.id === "monthly" ? "mo" : formatWindowLabel(bucket.windowMinutes, fallback, true);
|
|
3473
|
-
const remaining = bucket.unit === "percent" ? Math.round(
|
|
674
|
+
const remaining = bucket.unit === "percent" ? Math.round(clampPercent(bucket.remaining)) : percentRemaining(bucket);
|
|
3474
675
|
parts.push(`${remaining}% ${window}`);
|
|
3475
676
|
}
|
|
3476
677
|
return parts.length > 1 ? parts.join(" ") : void 0;
|
|
3477
678
|
}
|
|
3478
|
-
function formatMoonshotReport(lines,
|
|
3479
|
-
for (const
|
|
3480
|
-
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}`);
|
|
3481
682
|
}
|
|
3482
683
|
}
|
|
3483
|
-
function formatMoonshotStatusline(
|
|
3484
|
-
const available =
|
|
684
|
+
function formatMoonshotStatusline(report) {
|
|
685
|
+
const available = report.metrics.find((metric) => metric.id === "available-balance");
|
|
3485
686
|
if (!available) return "moonshot balance unavailable";
|
|
3486
687
|
return `moonshot ${available.currency ?? ""} ${available.value}`.replace(/\s+/gu, " ");
|
|
3487
688
|
}
|
|
3488
|
-
function formatMiniMaxReport(lines,
|
|
3489
|
-
if (
|
|
3490
|
-
for (const
|
|
3491
|
-
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}`);
|
|
3492
693
|
}
|
|
3493
694
|
return;
|
|
3494
695
|
}
|
|
3495
696
|
let previousGroup;
|
|
3496
|
-
for (const bucket of
|
|
697
|
+
for (const bucket of report.buckets) {
|
|
3497
698
|
if (bucket.groupId !== previousGroup) lines.push(`${bucket.groupLabel ?? "Token Plan"}:`);
|
|
3498
699
|
previousGroup = bucket.groupId;
|
|
3499
700
|
const reset = bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : "";
|
|
@@ -3501,15 +702,15 @@ function formatMiniMaxReport(lines, report2) {
|
|
|
3501
702
|
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${value}`);
|
|
3502
703
|
}
|
|
3503
704
|
}
|
|
3504
|
-
function formatMiniMaxStatusline(
|
|
3505
|
-
const prefix =
|
|
3506
|
-
if (
|
|
3507
|
-
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");
|
|
3508
709
|
return available ? `${prefix} ${available.currency} ${available.value}` : void 0;
|
|
3509
710
|
}
|
|
3510
|
-
const selectedGroup = selectMiniMaxGroup(
|
|
711
|
+
const selectedGroup = selectMiniMaxGroup(report, model);
|
|
3511
712
|
if (!selectedGroup) return void 0;
|
|
3512
|
-
const selected =
|
|
713
|
+
const selected = report.buckets.filter((bucket) => bucket.groupId === selectedGroup);
|
|
3513
714
|
const parts = [prefix];
|
|
3514
715
|
for (const bucket of selected) {
|
|
3515
716
|
const fallback = bucket.id.endsWith(":weekly") ? "weekly" : "5h";
|
|
@@ -3527,16 +728,16 @@ function formatMiniMaxStatusline(report2, model) {
|
|
|
3527
728
|
}
|
|
3528
729
|
return parts.length > 1 ? parts.join(" ") : void 0;
|
|
3529
730
|
}
|
|
3530
|
-
function selectMiniMaxGroup(
|
|
731
|
+
function selectMiniMaxGroup(report, model) {
|
|
3531
732
|
const groups = [
|
|
3532
|
-
...new Set(
|
|
733
|
+
...new Set(report.buckets.map((bucket) => bucket.groupId).filter((group) => group !== void 0))
|
|
3533
734
|
];
|
|
3534
735
|
if (groups.length <= 1) return groups[0];
|
|
3535
|
-
if (model && model.provider !==
|
|
736
|
+
if (model && model.provider !== report.providerId) return void 0;
|
|
3536
737
|
if (model) {
|
|
3537
738
|
const modelKeys = [model.id, model.name].map(normalizeMiniMaxModelKey).filter((key) => key !== void 0);
|
|
3538
739
|
const candidates = groups.map((group) => {
|
|
3539
|
-
const bucket =
|
|
740
|
+
const bucket = report.buckets.find((candidate) => candidate.groupId === group);
|
|
3540
741
|
const patterns = [bucket?.groupLabel, ...bucket?.modelKeys ?? [], group].map(normalizeMiniMaxModelKey).filter((key) => key !== void 0);
|
|
3541
742
|
return { group, patterns };
|
|
3542
743
|
});
|
|
@@ -3567,10 +768,10 @@ function wildcardKeyMatches(pattern, value) {
|
|
|
3567
768
|
const last = segments.at(-1);
|
|
3568
769
|
return pattern.endsWith("*") || last !== void 0 && value.endsWith(last);
|
|
3569
770
|
}
|
|
3570
|
-
function formatZaiStatusline(
|
|
771
|
+
function formatZaiStatusline(report) {
|
|
3571
772
|
const selected = [
|
|
3572
|
-
|
|
3573
|
-
|
|
773
|
+
report.buckets.find((bucket) => bucket.id === "five-hour"),
|
|
774
|
+
report.buckets.find((bucket) => bucket.id === "weekly")
|
|
3574
775
|
];
|
|
3575
776
|
const parts = ["zai"];
|
|
3576
777
|
for (const bucket of selected) {
|
|
@@ -3580,15 +781,15 @@ function formatZaiStatusline(report2) {
|
|
|
3580
781
|
}
|
|
3581
782
|
return parts.length > 1 ? parts.join(" ") : void 0;
|
|
3582
783
|
}
|
|
3583
|
-
function formatCurrencyMetric(
|
|
3584
|
-
if (typeof
|
|
3585
|
-
if (!
|
|
3586
|
-
if (
|
|
3587
|
-
if (
|
|
3588
|
-
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}`;
|
|
3589
790
|
}
|
|
3590
|
-
function formatXaiReport(lines,
|
|
3591
|
-
const included =
|
|
791
|
+
function formatXaiReport(lines, report) {
|
|
792
|
+
const included = report.buckets.find((bucket) => bucket.id === "included-allowance");
|
|
3592
793
|
if (included) {
|
|
3593
794
|
let value = "unavailable";
|
|
3594
795
|
if (included.unit === "percent" && included.used !== void 0) {
|
|
@@ -3603,18 +804,18 @@ function formatXaiReport(lines, report2) {
|
|
|
3603
804
|
const reset = included.resetsAt ? ` (resets ${formatReset(included.resetsAt)})` : "";
|
|
3604
805
|
lines.push(`${"Included allowance:".padEnd(VALUE_COLUMN)}${value}${period}${reset}`);
|
|
3605
806
|
}
|
|
3606
|
-
const onDemand =
|
|
807
|
+
const onDemand = report.buckets.find((bucket) => bucket.id === "on-demand");
|
|
3607
808
|
if (onDemand) {
|
|
3608
809
|
let value = onDemand.used === void 0 ? "usage unavailable" : `${formatUsd(onDemand.used)} used`;
|
|
3609
810
|
if (onDemand.limit !== void 0) value += ` of ${formatUsd(onDemand.limit)} cap`;
|
|
3610
811
|
lines.push(`${"On-demand usage:".padEnd(VALUE_COLUMN)}${value}`);
|
|
3611
812
|
}
|
|
3612
|
-
for (const
|
|
3613
|
-
lines.push(`${`${
|
|
813
|
+
for (const metric of report.metrics) {
|
|
814
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`);
|
|
3614
815
|
}
|
|
3615
816
|
}
|
|
3616
|
-
function formatZaiReport(lines,
|
|
3617
|
-
for (const bucket of
|
|
817
|
+
function formatZaiReport(lines, report) {
|
|
818
|
+
for (const bucket of report.buckets) {
|
|
3618
819
|
if (bucket.unit === "percent" && bucket.used !== void 0) {
|
|
3619
820
|
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${formatPercentBucket(bucket)}`);
|
|
3620
821
|
continue;
|
|
@@ -3631,30 +832,42 @@ function formatZaiReport(lines, report2) {
|
|
|
3631
832
|
}
|
|
3632
833
|
lines.push(`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${value}${reset}`);
|
|
3633
834
|
}
|
|
3634
|
-
for (const
|
|
3635
|
-
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
|
+
}
|
|
3636
847
|
}
|
|
848
|
+
for (const metric of report.metrics)
|
|
849
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`);
|
|
3637
850
|
}
|
|
3638
|
-
function formatGenericReport(lines,
|
|
3639
|
-
for (const bucket of
|
|
851
|
+
function formatGenericReport(lines, report) {
|
|
852
|
+
for (const bucket of report.buckets) {
|
|
3640
853
|
lines.push(
|
|
3641
854
|
`${`${bucket.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(bucket.remaining ?? bucket.used ?? "unavailable", bucket.unit)}`
|
|
3642
855
|
);
|
|
3643
856
|
}
|
|
3644
|
-
for (const
|
|
3645
|
-
lines.push(`${`${
|
|
857
|
+
for (const metric of report.metrics) {
|
|
858
|
+
lines.push(`${`${metric.label}:`.padEnd(VALUE_COLUMN)}${formatMetricValue(metric.value, metric.unit)}`);
|
|
3646
859
|
}
|
|
3647
860
|
}
|
|
3648
|
-
function formatCodexStatusline(
|
|
3649
|
-
const group = selectCodexGroup(
|
|
3650
|
-
if (!group) return formatCodexCreditsStatus(
|
|
3651
|
-
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);
|
|
3652
865
|
const labelBucket = buckets[0];
|
|
3653
866
|
const parts = [group === "codex" ? "codex" : `codex ${compactLimitLabel(labelBucket?.groupLabel ?? group)}`];
|
|
3654
867
|
for (const bucket of buckets) {
|
|
3655
868
|
if (bucket.remaining === void 0) continue;
|
|
3656
869
|
const displayedPercentage = percentage === "used" ? bucket.used ?? 100 - bucket.remaining : bucket.remaining;
|
|
3657
|
-
const percent = `${
|
|
870
|
+
const percent = `${clampPercent(displayedPercentage).toFixed(0)}%`;
|
|
3658
871
|
const fallback = bucket.id.endsWith(":secondary") ? "weekly" : "5h";
|
|
3659
872
|
const window = formatWindowLabel(bucket.windowMinutes, fallback, true);
|
|
3660
873
|
if (!showResetCountdown) {
|
|
@@ -3664,24 +877,24 @@ function formatCodexStatusline(report2, model, now = Date.now(), showResetCountd
|
|
|
3664
877
|
const reset = formatResetCountdown(bucket.resetsAt, now);
|
|
3665
878
|
parts.push(`${percent} ${reset ? `\u21BB ${reset}` : window}`);
|
|
3666
879
|
}
|
|
3667
|
-
return parts.length > 1 ? parts.join(" ") : formatCodexCreditsStatus(
|
|
880
|
+
return parts.length > 1 ? parts.join(" ") : formatCodexCreditsStatus(report);
|
|
3668
881
|
}
|
|
3669
|
-
function formatCodexCreditsStatus(
|
|
3670
|
-
const credits =
|
|
882
|
+
function formatCodexCreditsStatus(report) {
|
|
883
|
+
const credits = report.metrics.find((metric) => metric.id === "credits");
|
|
3671
884
|
if (!credits) return "codex usage unavailable";
|
|
3672
885
|
if (credits.value === "none") return "codex no credits";
|
|
3673
886
|
if (credits.value === "available") return "codex credits available";
|
|
3674
887
|
if (credits.value === "unlimited") return "codex credits unlimited";
|
|
3675
888
|
return `codex ${formatMetricValue(credits.value, "count")} credits`;
|
|
3676
889
|
}
|
|
3677
|
-
function selectCodexGroup(
|
|
3678
|
-
const groups = [...new Set(
|
|
890
|
+
function selectCodexGroup(report, model) {
|
|
891
|
+
const groups = [...new Set(report.buckets.map((bucket) => bucket.groupId ?? bucket.id))];
|
|
3679
892
|
if (model?.provider !== "openai-codex") {
|
|
3680
893
|
return groups.includes("codex") ? "codex" : groups[0];
|
|
3681
894
|
}
|
|
3682
895
|
const modelKeys = normalizedModelKeys(model);
|
|
3683
896
|
for (const group of groups) {
|
|
3684
|
-
const bucket =
|
|
897
|
+
const bucket = report.buckets.find((candidate) => (candidate.groupId ?? candidate.id) === group);
|
|
3685
898
|
const keys = [group, bucket?.groupLabel, ...bucket?.modelKeys ?? []].map(normalizeKey).filter((key) => key !== void 0);
|
|
3686
899
|
if (keys.some((key) => modelKeys.has(key))) return group;
|
|
3687
900
|
}
|
|
@@ -3729,7 +942,7 @@ function formatPercentBucket(bucket) {
|
|
|
3729
942
|
return `${formatPercentBar(bucket)}${bucket.resetsAt ? ` (resets ${formatReset(bucket.resetsAt)})` : ""}`;
|
|
3730
943
|
}
|
|
3731
944
|
function formatPercentBar(bucket) {
|
|
3732
|
-
const remaining =
|
|
945
|
+
const remaining = clampPercent(bucket.remaining ?? 0);
|
|
3733
946
|
const filled = Math.round(remaining / 100 * BAR_SEGMENTS);
|
|
3734
947
|
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(BAR_SEGMENTS - filled)}] ${remaining.toFixed(0)}% left`;
|
|
3735
948
|
}
|
|
@@ -3757,6 +970,7 @@ function formatResetCountdown(resetsAt, now) {
|
|
|
3757
970
|
}
|
|
3758
971
|
function formatMetricValue(value, unit) {
|
|
3759
972
|
if (unit === "usd" && typeof value === "number") return formatUsd(value);
|
|
973
|
+
if (unit === "percent" && typeof value === "number") return `${value}%`;
|
|
3760
974
|
return String(value);
|
|
3761
975
|
}
|
|
3762
976
|
function formatUsd(value) {
|
|
@@ -3773,7 +987,7 @@ function formatReset(epochSeconds) {
|
|
|
3773
987
|
function capitalize(value) {
|
|
3774
988
|
return `${value[0]?.toUpperCase() ?? ""}${value.slice(1)}`;
|
|
3775
989
|
}
|
|
3776
|
-
function
|
|
990
|
+
function clampPercent(value) {
|
|
3777
991
|
return Math.min(100, Math.max(0, value));
|
|
3778
992
|
}
|
|
3779
993
|
|
|
@@ -3786,6 +1000,7 @@ import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
|
3786
1000
|
var USAGE_SETTINGS_FILE = "pi-usage.json";
|
|
3787
1001
|
var MAX_USAGE_SETTINGS_BYTES = 64 * 1024;
|
|
3788
1002
|
var DEFAULT_USAGE_SETTINGS = Object.freeze({
|
|
1003
|
+
openaiCompanionUsage: false,
|
|
3789
1004
|
codexFastMode: false,
|
|
3790
1005
|
codexStatusResetCountdown: true,
|
|
3791
1006
|
codexStatusPercentage: "remaining",
|
|
@@ -3795,7 +1010,8 @@ function usageSettingsPath() {
|
|
|
3795
1010
|
return join(getAgentDir(), USAGE_SETTINGS_FILE);
|
|
3796
1011
|
}
|
|
3797
1012
|
function normalizeUsageSettings(value) {
|
|
3798
|
-
if (!
|
|
1013
|
+
if (!isRecord2(value)) return void 0;
|
|
1014
|
+
if (Object.hasOwn(value, "openaiCompanionUsage") && typeof value.openaiCompanionUsage !== "boolean") return void 0;
|
|
3799
1015
|
if (Object.hasOwn(value, "codexFastMode") && typeof value.codexFastMode !== "boolean") {
|
|
3800
1016
|
return void 0;
|
|
3801
1017
|
}
|
|
@@ -3815,6 +1031,7 @@ function normalizeUsageSettings(value) {
|
|
|
3815
1031
|
effectiveTargets.fireworks = value.fireworksAccountId;
|
|
3816
1032
|
}
|
|
3817
1033
|
return {
|
|
1034
|
+
openaiCompanionUsage: value.openaiCompanionUsage === true,
|
|
3818
1035
|
codexFastMode: typeof value.codexFastMode === "boolean" ? value.codexFastMode : DEFAULT_USAGE_SETTINGS.codexFastMode,
|
|
3819
1036
|
codexStatusResetCountdown: typeof value.codexStatusResetCountdown === "boolean" ? value.codexStatusResetCountdown : DEFAULT_USAGE_SETTINGS.codexStatusResetCountdown,
|
|
3820
1037
|
codexStatusPercentage: value.codexStatusPercentage === "used" ? "used" : DEFAULT_USAGE_SETTINGS.codexStatusPercentage,
|
|
@@ -3840,7 +1057,7 @@ async function loadUsageSettings(path = usageSettingsPath(), signal) {
|
|
|
3840
1057
|
throwIfAborted(signal);
|
|
3841
1058
|
const document = JSON.parse(text);
|
|
3842
1059
|
const settings = normalizeUsageSettings(document);
|
|
3843
|
-
if (!settings || !
|
|
1060
|
+
if (!settings || !isRecord2(document)) throw new Error("invalid settings shape");
|
|
3844
1061
|
return { kind: "loaded", path, settings, document };
|
|
3845
1062
|
} catch (error) {
|
|
3846
1063
|
if (signal?.aborted) throw error;
|
|
@@ -4019,7 +1236,7 @@ async function chmodPrivate(path) {
|
|
|
4019
1236
|
function throwIfAborted(signal) {
|
|
4020
1237
|
if (signal?.aborted) throw new DOMException("Settings operation aborted", "AbortError");
|
|
4021
1238
|
}
|
|
4022
|
-
function
|
|
1239
|
+
function isRecord2(value) {
|
|
4023
1240
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4024
1241
|
}
|
|
4025
1242
|
function isNodeError(error) {
|
|
@@ -4027,7 +1244,7 @@ function isNodeError(error) {
|
|
|
4027
1244
|
}
|
|
4028
1245
|
function normalizeSelectedTargets(value) {
|
|
4029
1246
|
if (value === void 0) return {};
|
|
4030
|
-
if (!
|
|
1247
|
+
if (!isRecord2(value)) return void 0;
|
|
4031
1248
|
const targets = {};
|
|
4032
1249
|
for (const [providerId, targetId] of Object.entries(value)) {
|
|
4033
1250
|
if (!isProviderId(providerId) || !isBoundedTargetId(targetId)) return void 0;
|
|
@@ -4056,7 +1273,7 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {})
|
|
|
4056
1273
|
try {
|
|
4057
1274
|
await settingsRuntime.update({ codexFastMode: enabled }, signal);
|
|
4058
1275
|
} catch (error) {
|
|
4059
|
-
if (
|
|
1276
|
+
if (isAbortError(error) || isStaleExtensionContextError(error)) return false;
|
|
4060
1277
|
ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
|
|
4061
1278
|
return false;
|
|
4062
1279
|
}
|
|
@@ -4130,7 +1347,7 @@ function registerCodexFastMode(pi, settingsRuntime, refreshStatus, options = {})
|
|
|
4130
1347
|
const key = activeRequestKey(ctx);
|
|
4131
1348
|
if (key && ctx.model) {
|
|
4132
1349
|
pendingFastRequests.set(key, {
|
|
4133
|
-
fastRequested:
|
|
1350
|
+
fastRequested: isRecord3(rewritten) && rewritten.service_tier === "priority",
|
|
4134
1351
|
model: ctx.model
|
|
4135
1352
|
});
|
|
4136
1353
|
}
|
|
@@ -4164,7 +1381,7 @@ function activeRequestKey(ctx) {
|
|
|
4164
1381
|
return model ? `${ctx.sessionManager.getSessionId()}:${model.provider}/${model.id}` : void 0;
|
|
4165
1382
|
}
|
|
4166
1383
|
function consumeFastRequest(ctx, message, pending) {
|
|
4167
|
-
if (!
|
|
1384
|
+
if (!isRecord3(message) || message.role !== "assistant") return NO_FAST_REQUEST;
|
|
4168
1385
|
const key = messageRequestKey(ctx, message);
|
|
4169
1386
|
if (!key) return NO_FAST_REQUEST;
|
|
4170
1387
|
const request = pending.get(key);
|
|
@@ -4175,10 +1392,10 @@ function messageRequestKey(ctx, message) {
|
|
|
4175
1392
|
if (typeof message.provider !== "string" || typeof message.model !== "string") return void 0;
|
|
4176
1393
|
return `${ctx.sessionManager.getSessionId()}:${message.provider}/${message.model}`;
|
|
4177
1394
|
}
|
|
4178
|
-
function
|
|
1395
|
+
function isRecord3(value) {
|
|
4179
1396
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4180
1397
|
}
|
|
4181
|
-
function
|
|
1398
|
+
function isAbortError(error) {
|
|
4182
1399
|
return error instanceof Error && error.name === "AbortError";
|
|
4183
1400
|
}
|
|
4184
1401
|
|
|
@@ -4207,7 +1424,7 @@ function setBoundedMap(map, key, value, limit) {
|
|
|
4207
1424
|
function modelIdentity(model) {
|
|
4208
1425
|
return model ? `${model.provider}/${model.id}` : void 0;
|
|
4209
1426
|
}
|
|
4210
|
-
function
|
|
1427
|
+
function isAbortError2(error) {
|
|
4211
1428
|
return error instanceof Error && error.name === "AbortError";
|
|
4212
1429
|
}
|
|
4213
1430
|
function isTimeoutError(error) {
|
|
@@ -4233,120 +1450,157 @@ async function showUsageSettings(ctx, settingsRuntime, parentSignal, isCurrent,
|
|
|
4233
1450
|
}
|
|
4234
1451
|
const { HorizontalRule, renderBoundedFrame } = await import("@narumitw/pi-tui-kit");
|
|
4235
1452
|
if (parentSignal.aborted || !isCurrent()) return false;
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
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");
|
|
4281
1526
|
tui.requestRender();
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
if (previous !== requested) {
|
|
1530
|
+
changed = true;
|
|
1531
|
+
onApplied(id);
|
|
4282
1532
|
}
|
|
4283
|
-
return;
|
|
4284
|
-
}
|
|
4285
|
-
try {
|
|
4286
|
-
const patch = {};
|
|
4287
|
-
patch[id] = requested;
|
|
4288
|
-
await settingsRuntime.update(patch, signal);
|
|
4289
|
-
} catch (error) {
|
|
4290
1533
|
if (signal.aborted || !isCurrent()) return;
|
|
4291
|
-
settingsList.updateValue(id,
|
|
4292
|
-
ctx.ui.notify(`Could not save pi-usage.json: ${errorMessage(error)}`, "error");
|
|
1534
|
+
settingsList.updateValue(id, display);
|
|
4293
1535
|
tui.requestRender();
|
|
4294
|
-
return;
|
|
4295
|
-
}
|
|
4296
|
-
if (previous !== requested) {
|
|
4297
|
-
changed = true;
|
|
4298
|
-
onApplied(id);
|
|
4299
|
-
}
|
|
4300
|
-
if (signal.aborted || !isCurrent()) return;
|
|
4301
|
-
settingsList.updateValue(id, display);
|
|
4302
|
-
tui.requestRender();
|
|
4303
|
-
});
|
|
4304
|
-
};
|
|
4305
|
-
settingsList = new SettingsList(
|
|
4306
|
-
items,
|
|
4307
|
-
items.length + 2,
|
|
4308
|
-
getSettingsListTheme(),
|
|
4309
|
-
(id, value) => {
|
|
4310
|
-
if (closing || signal.aborted || !isCurrent()) return;
|
|
4311
|
-
if (id === "codexStatusPercentage") {
|
|
4312
|
-
queueUpdate(id, value === USED ? "used" : "remaining", value);
|
|
4313
|
-
} else if (id === "codexFastMode" || id === "codexStatusResetCountdown") {
|
|
4314
|
-
queueUpdate(id, value === ON, value);
|
|
4315
|
-
}
|
|
4316
|
-
},
|
|
4317
|
-
cancel
|
|
4318
|
-
);
|
|
4319
|
-
parentSignal.addEventListener("abort", cancel, { once: true });
|
|
4320
|
-
return {
|
|
4321
|
-
render(width) {
|
|
4322
|
-
const title = new Text(theme.fg("accent", theme.bold("pi-usage Settings")), 1, 0).render(width);
|
|
4323
|
-
const content = settingsList.render(width);
|
|
4324
|
-
const focusedRow = content.findIndex((line) => /^[→›]\s/u.test(stripVTControlCharacters(line)));
|
|
4325
|
-
const terminalRows = Number.isFinite(tui.terminal?.rows) ? Math.floor(tui.terminal.rows) : 24;
|
|
4326
|
-
const [ruleLine = ""] = rule.render(width);
|
|
4327
|
-
return renderBoundedFrame({
|
|
4328
|
-
width,
|
|
4329
|
-
maxRows: Math.max(1, terminalRows - 3),
|
|
4330
|
-
rule: ruleLine,
|
|
4331
|
-
title,
|
|
4332
|
-
content,
|
|
4333
|
-
priorityRows: focusedRow < 0 ? [] : [focusedRow],
|
|
4334
|
-
focusedRow
|
|
4335
1536
|
});
|
|
4336
|
-
}
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
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");
|
|
4347
1597
|
}
|
|
4348
|
-
}
|
|
4349
|
-
|
|
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
|
+
}
|
|
4350
1604
|
}
|
|
4351
1605
|
|
|
4352
1606
|
// src/usage.ts
|
|
@@ -4371,10 +1625,19 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4371
1625
|
const credentialCandidates = createOAuthCredentialCandidateReader(pi, credentialReader);
|
|
4372
1626
|
const createRedemptionId = dependencies.createRedemptionId ?? randomUUID2;
|
|
4373
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
|
+
);
|
|
4374
1636
|
const cache = new UsageCache(CACHE_TTL_MS);
|
|
4375
1637
|
const failureBackoff = /* @__PURE__ */ new Map();
|
|
4376
1638
|
const latestQueries = /* @__PURE__ */ new Map();
|
|
4377
1639
|
const activeControllers = /* @__PURE__ */ new Set();
|
|
1640
|
+
const nativeQueryControllers = /* @__PURE__ */ new Set();
|
|
4378
1641
|
let querySequence = 0;
|
|
4379
1642
|
let activeCurrentIdentity;
|
|
4380
1643
|
let sessionActive = false;
|
|
@@ -4454,7 +1717,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4454
1717
|
const value = rawValue ? fastRuntime.decorateStatus(model, rawValue) : void 0;
|
|
4455
1718
|
if (!safeSetStatus(ctx, value)) return;
|
|
4456
1719
|
if (shouldSchedule && sessionActive) scheduleStatusRefresh(ctx, model);
|
|
4457
|
-
if (sessionActive && showCodexResetCountdown && outcome.state.report.buckets.some(
|
|
1720
|
+
if (sessionActive && (showCodexResetCountdown || outcome.state.report.source === "openai-chatgpt-companion") && outcome.state.report.buckets.some(
|
|
4458
1721
|
(bucket) => bucket.resetsAt !== void 0 && Number.isFinite(bucket.resetsAt) && bucket.resetsAt * 1e3 > now
|
|
4459
1722
|
)) {
|
|
4460
1723
|
const generation = statusGeneration;
|
|
@@ -4486,22 +1749,25 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4486
1749
|
}
|
|
4487
1750
|
activeCurrentIdentity = nextIdentity;
|
|
4488
1751
|
};
|
|
4489
|
-
const
|
|
1752
|
+
const queryAdapterStateInternal = async (ctx, adapter, displayState, force, signal, authRetry = 0, deadlineAt = Date.now() + DEFAULT_TIMEOUT_MS) => {
|
|
4490
1753
|
const expectedSessionGeneration = sessionGeneration;
|
|
4491
1754
|
const expectedSessionId = ctx.sessionManager.getSessionId();
|
|
4492
1755
|
const expectedModelIdentity = modelIdentity(ctx.model);
|
|
4493
1756
|
const expectedTargetId = adapter.targets ? settingsRuntime.get().settings.selectedTargets[adapter.id] : void 0;
|
|
1757
|
+
const expectedCompanionUsage = settingsRuntime.get().settings.openaiCompanionUsage;
|
|
4494
1758
|
const providerName = providerDisplayName(ctx, adapter.id);
|
|
4495
1759
|
let auth;
|
|
4496
1760
|
try {
|
|
4497
1761
|
auth = await awaitWithDeadline(
|
|
4498
|
-
|
|
1762
|
+
resolveUsageAuth2(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
4499
1763
|
signal,
|
|
4500
1764
|
Math.max(1, deadlineAt - Date.now()),
|
|
4501
1765
|
`resolving ${adapter.displayName} runtime auth`
|
|
4502
1766
|
);
|
|
4503
1767
|
} catch (error) {
|
|
4504
|
-
if (isStaleExtensionContextError(error) ||
|
|
1768
|
+
if (isStaleExtensionContextError(error) || isAbortError2(error)) throw error;
|
|
1769
|
+
if (adapter.id === "openai") invalidateProviderState(adapter.id);
|
|
1770
|
+
const authState = error instanceof UnsupportedOpenAIUsageAuthError ? "unsupported" : adapter.id === "openai" && !isTimeoutError(error) ? "failed" : void 0;
|
|
4505
1771
|
if (displayState === "current") {
|
|
4506
1772
|
transitionCurrentIdentity(`${adapter.id}:auth-error`, adapter.id);
|
|
4507
1773
|
}
|
|
@@ -4510,9 +1776,10 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4510
1776
|
providerId: adapter.id,
|
|
4511
1777
|
providerName,
|
|
4512
1778
|
displayState,
|
|
4513
|
-
status: isTimeoutError(error) ? "query-failed" : "auth-unavailable",
|
|
1779
|
+
status: authState === "unsupported" ? "unsupported" : isTimeoutError(error) ? "query-failed" : "auth-unavailable",
|
|
4514
1780
|
message: errorMessage(error)
|
|
4515
|
-
}
|
|
1781
|
+
},
|
|
1782
|
+
...authState ? { authState } : {}
|
|
4516
1783
|
};
|
|
4517
1784
|
}
|
|
4518
1785
|
const requiresRequestBoundaryGuard = adapter.targets !== void 0 || [
|
|
@@ -4522,14 +1789,16 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4522
1789
|
"minimax-cn",
|
|
4523
1790
|
"moonshotai",
|
|
4524
1791
|
"moonshotai-cn",
|
|
1792
|
+
"openai",
|
|
4525
1793
|
"vercel-ai-gateway",
|
|
4526
1794
|
"xai",
|
|
4527
1795
|
"zai",
|
|
4528
1796
|
"zai-coding-cn"
|
|
4529
1797
|
].includes(adapter.id);
|
|
4530
|
-
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;
|
|
4531
1799
|
if (requiresRequestBoundaryGuard && requestContextChanged()) throw abortError();
|
|
4532
1800
|
if (!auth) {
|
|
1801
|
+
if (adapter.id === "openai") invalidateProviderState(adapter.id);
|
|
4533
1802
|
if (displayState === "current") {
|
|
4534
1803
|
transitionCurrentIdentity(`${adapter.id}:unavailable`, adapter.id);
|
|
4535
1804
|
}
|
|
@@ -4544,12 +1813,13 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4544
1813
|
authState: "unavailable"
|
|
4545
1814
|
};
|
|
4546
1815
|
}
|
|
1816
|
+
if (adapter.id === "openai" && !auth.openaiCompanion) invalidateProviderState(adapter.id);
|
|
4547
1817
|
let retryableAuthChanged = false;
|
|
4548
1818
|
const guard = async () => {
|
|
4549
1819
|
if (signal.aborted || requestContextChanged()) throw abortError();
|
|
4550
1820
|
if (!requiresRequestBoundaryGuard) return;
|
|
4551
1821
|
const revalidated = await awaitWithDeadline(
|
|
4552
|
-
|
|
1822
|
+
resolveUsageAuth2(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
4553
1823
|
signal,
|
|
4554
1824
|
Math.max(1, deadlineAt - Date.now()),
|
|
4555
1825
|
`revalidating ${providerName} runtime auth`
|
|
@@ -4644,7 +1914,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4644
1914
|
querySequence += 1;
|
|
4645
1915
|
queryId = querySequence;
|
|
4646
1916
|
setBoundedMap(latestQueries, failureKey, queryId, MAX_ACCOUNT_STATES);
|
|
4647
|
-
const
|
|
1917
|
+
const report = await queryProviderUsage(
|
|
4648
1918
|
adapter,
|
|
4649
1919
|
auth,
|
|
4650
1920
|
signal,
|
|
@@ -4653,7 +1923,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4653
1923
|
target.targetId
|
|
4654
1924
|
);
|
|
4655
1925
|
if (requiresRequestBoundaryGuard) await guard();
|
|
4656
|
-
const effectiveReport = { ...
|
|
1926
|
+
const effectiveReport = { ...report, providerName };
|
|
4657
1927
|
if (latestQueries.get(failureKey) === queryId) {
|
|
4658
1928
|
cache.set(adapter.id, queryFingerprint, effectiveReport);
|
|
4659
1929
|
failureBackoff.delete(failureKey);
|
|
@@ -4670,7 +1940,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4670
1940
|
rememberedTargetId: expectedTargetId
|
|
4671
1941
|
};
|
|
4672
1942
|
} catch (error) {
|
|
4673
|
-
if (isStaleExtensionContextError(error) ||
|
|
1943
|
+
if (isStaleExtensionContextError(error) || isAbortError2(error)) throw error;
|
|
4674
1944
|
if (retryableAuthChanged && authRetry === 0 && !signal.aborted && !requestContextChanged() && Date.now() < deadlineAt) {
|
|
4675
1945
|
if (queryId !== void 0 && latestQueries.get(failureKey) === queryId) {
|
|
4676
1946
|
latestQueries.delete(failureKey);
|
|
@@ -4699,6 +1969,24 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4699
1969
|
};
|
|
4700
1970
|
}
|
|
4701
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
|
+
};
|
|
4702
1990
|
const loadTargetChoices = async (ctx, adapter, signal) => {
|
|
4703
1991
|
if (!adapter.targets) throw new Error("Provider does not support usage targets.");
|
|
4704
1992
|
const expectedSessionGeneration = sessionGeneration;
|
|
@@ -4708,7 +1996,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4708
1996
|
const deadlineAt = Date.now() + DEFAULT_TIMEOUT_MS;
|
|
4709
1997
|
const changed = () => expectedSessionGeneration !== sessionGeneration || ctx.sessionManager.getSessionId() !== expectedSessionId || modelIdentity(ctx.model) !== expectedModel || settingsRuntime.get().settings.selectedTargets[adapter.id] !== expectedTargetId;
|
|
4710
1998
|
const auth = await awaitWithDeadline(
|
|
4711
|
-
|
|
1999
|
+
resolveUsageAuth2(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
4712
2000
|
signal,
|
|
4713
2001
|
Math.max(1, deadlineAt - Date.now()),
|
|
4714
2002
|
`resolving ${providerDisplayName(ctx, adapter.id)} runtime auth`
|
|
@@ -4717,7 +2005,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4717
2005
|
const guard = async () => {
|
|
4718
2006
|
if (signal.aborted || changed()) throw abortError();
|
|
4719
2007
|
const revalidated = await awaitWithDeadline(
|
|
4720
|
-
|
|
2008
|
+
resolveUsageAuth2(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
4721
2009
|
signal,
|
|
4722
2010
|
Math.max(1, deadlineAt - Date.now()),
|
|
4723
2011
|
`revalidating ${providerDisplayName(ctx, adapter.id)} runtime auth`
|
|
@@ -4783,7 +2071,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4783
2071
|
};
|
|
4784
2072
|
const startStatusRefresh = (ctx, model, force) => {
|
|
4785
2073
|
void refreshCurrentStatus(ctx, model, force).catch((error) => {
|
|
4786
|
-
if (isStaleExtensionContextError(error) ||
|
|
2074
|
+
if (isStaleExtensionContextError(error) || isAbortError2(error)) return;
|
|
4787
2075
|
const message = errorMessage(error);
|
|
4788
2076
|
console.error("[pi-usage] refresh failed:", message);
|
|
4789
2077
|
safeSetStatus(ctx, `usage err: ${message.slice(0, 50)}`);
|
|
@@ -4813,39 +2101,52 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4813
2101
|
if (generation !== statusGeneration || modelIdentity(ctx.model) !== modelIdentity(model)) {
|
|
4814
2102
|
return false;
|
|
4815
2103
|
}
|
|
4816
|
-
const adapter = adapterForProvider(
|
|
2104
|
+
const adapter = adapterForProvider(outcome.state.providerId);
|
|
4817
2105
|
const selectionStillCurrent = !adapter?.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId;
|
|
4818
2106
|
if (!selectionStillCurrent) return false;
|
|
4819
|
-
if (outcome.authState
|
|
2107
|
+
if (outcome.authState) {
|
|
4820
2108
|
if (!adapter) return false;
|
|
4821
2109
|
try {
|
|
4822
2110
|
const auth = await awaitWithDeadline(
|
|
4823
|
-
|
|
2111
|
+
resolveUsageAuth2(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
4824
2112
|
signal,
|
|
4825
2113
|
DEFAULT_TIMEOUT_MS,
|
|
4826
2114
|
`revalidating ${adapter.displayName} runtime auth`
|
|
4827
2115
|
);
|
|
4828
|
-
return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) && auth === void 0;
|
|
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;
|
|
4829
2117
|
} catch (error) {
|
|
4830
|
-
if (
|
|
4831
|
-
|
|
2118
|
+
if (isAbortError2(error) || isStaleExtensionContextError(error)) throw error;
|
|
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;
|
|
2120
|
+
return unchangedFailure && generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId);
|
|
4832
2121
|
}
|
|
4833
2122
|
}
|
|
4834
2123
|
if (!outcome.fingerprint) return true;
|
|
4835
2124
|
if (!adapter) return false;
|
|
4836
2125
|
try {
|
|
4837
2126
|
const auth = await awaitWithDeadline(
|
|
4838
|
-
|
|
2127
|
+
resolveUsageAuth2(ctx, adapter, void 0, credentialReader, credentialCandidates),
|
|
4839
2128
|
signal,
|
|
4840
2129
|
DEFAULT_TIMEOUT_MS,
|
|
4841
2130
|
`revalidating ${adapter.displayName} runtime auth`
|
|
4842
2131
|
);
|
|
4843
2132
|
return generation === statusGeneration && modelIdentity(ctx.model) === modelIdentity(model) && (!adapter.targets || settingsRuntime.get().settings.selectedTargets[adapter.id] === outcome.rememberedTargetId) && auth?.fingerprint === outcome.fingerprint;
|
|
4844
2133
|
} catch (error) {
|
|
4845
|
-
if (
|
|
2134
|
+
if (isAbortError2(error) || isStaleExtensionContextError(error)) throw error;
|
|
4846
2135
|
return false;
|
|
4847
2136
|
}
|
|
4848
2137
|
};
|
|
2138
|
+
const queryStableAdapterState = async (ctx, adapter, displayState, force, signal) => {
|
|
2139
|
+
if (adapter.id !== "openai") return queryAdapterState(ctx, adapter, displayState, force, signal);
|
|
2140
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
2141
|
+
const model = ctx.model;
|
|
2142
|
+
const generation = statusGeneration;
|
|
2143
|
+
const outcome = await queryAdapterState(ctx, adapter, displayState, force, signal);
|
|
2144
|
+
if (signal.aborted) throw abortError();
|
|
2145
|
+
if (await outcomeStillCurrent(ctx, model, generation, outcome, signal)) return outcome;
|
|
2146
|
+
force = false;
|
|
2147
|
+
}
|
|
2148
|
+
throw new Error("OpenAI runtime authentication kept changing; reopen /usage to retry.");
|
|
2149
|
+
};
|
|
4849
2150
|
const queryStableCurrent = async (ctx, force, controller, label) => {
|
|
4850
2151
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
4851
2152
|
const model = ctx.model;
|
|
@@ -5117,7 +2418,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5117
2418
|
ctx,
|
|
5118
2419
|
`Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`,
|
|
5119
2420
|
controller.signal,
|
|
5120
|
-
(signal) =>
|
|
2421
|
+
(signal) => queryStableAdapterState(ctx, adapter, "configured", true, signal)
|
|
5121
2422
|
);
|
|
5122
2423
|
if (!outcome) return { kind: "stay" };
|
|
5123
2424
|
const revalidated = await queryStableCurrent(ctx, false, controller, "Revalidating current usage\u2026");
|
|
@@ -5129,7 +2430,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5129
2430
|
];
|
|
5130
2431
|
return { kind: "stay" };
|
|
5131
2432
|
} catch (error) {
|
|
5132
|
-
if (
|
|
2433
|
+
if (isAbortError2(error) || isStaleExtensionContextError(error)) {
|
|
5133
2434
|
return { kind: "stay" };
|
|
5134
2435
|
}
|
|
5135
2436
|
ctx.ui.notify(`Could not select target: ${errorMessage(error)}`, "error");
|
|
@@ -5143,6 +2444,16 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5143
2444
|
controller.signal,
|
|
5144
2445
|
() => statusGeneration === menuGeneration && !controller.signal.aborted,
|
|
5145
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
|
+
}
|
|
5146
2457
|
if ((id === "codexStatusResetCountdown" || id === "codexStatusPercentage") && stableCurrent && statusGeneration === menuGeneration && !controller.signal.aborted) {
|
|
5147
2458
|
publishStableCurrent(ctx, stableCurrent);
|
|
5148
2459
|
}
|
|
@@ -5186,7 +2497,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5186
2497
|
try {
|
|
5187
2498
|
availability = await listCodexResetCredits(auth, signal, DEFAULT_TIMEOUT_MS);
|
|
5188
2499
|
} catch (error) {
|
|
5189
|
-
if (
|
|
2500
|
+
if (isAbortError2(error) || summaryCount === void 0 || summaryCount <= 0) {
|
|
5190
2501
|
throw error;
|
|
5191
2502
|
}
|
|
5192
2503
|
availability = {
|
|
@@ -5219,7 +2530,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5219
2530
|
screen: loaded.availability.availableCount > 0 ? "reset-picker" : "reset-result"
|
|
5220
2531
|
};
|
|
5221
2532
|
} catch (error) {
|
|
5222
|
-
if (
|
|
2533
|
+
if (isAbortError2(error) || isStaleExtensionContextError(error)) {
|
|
5223
2534
|
return { kind: "stay" };
|
|
5224
2535
|
}
|
|
5225
2536
|
ctx.ui.notify(`Couldn't load usage limit resets: ${errorMessage(error)}`, "error");
|
|
@@ -5281,7 +2592,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5281
2592
|
}
|
|
5282
2593
|
return { kind: "to", screen: "reset-result" };
|
|
5283
2594
|
} catch (error) {
|
|
5284
|
-
if (
|
|
2595
|
+
if (isAbortError2(error) || isStaleExtensionContextError(error)) {
|
|
5285
2596
|
return { kind: "close" };
|
|
5286
2597
|
}
|
|
5287
2598
|
resetFailure = `Couldn't reset usage: ${errorMessage(error)}. Try again with the same request.`;
|
|
@@ -5320,7 +2631,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5320
2631
|
ctx,
|
|
5321
2632
|
`Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`,
|
|
5322
2633
|
controller.signal,
|
|
5323
|
-
(signal) =>
|
|
2634
|
+
(signal) => queryStableAdapterState(ctx, adapter, "configured", false, signal)
|
|
5324
2635
|
);
|
|
5325
2636
|
if (!outcome) return { kind: "back" };
|
|
5326
2637
|
if (outcome.state.status === "selection-required") {
|
|
@@ -5331,12 +2642,21 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5331
2642
|
ctx,
|
|
5332
2643
|
`Checking ${providerDisplayName(ctx, adapter.id)} usage\u2026`,
|
|
5333
2644
|
controller.signal,
|
|
5334
|
-
(signal) =>
|
|
2645
|
+
(signal) => queryStableAdapterState(ctx, adapter, "configured", true, signal)
|
|
5335
2646
|
);
|
|
5336
2647
|
if (!outcome) return { kind: "back" };
|
|
5337
2648
|
}
|
|
5338
2649
|
const revalidated = await queryStableCurrent(ctx, false, controller, "Revalidating current usage\u2026");
|
|
5339
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
|
+
}
|
|
5340
2660
|
stableCurrent = revalidated;
|
|
5341
2661
|
current = revalidated.outcome;
|
|
5342
2662
|
visibleStates = [
|
|
@@ -5354,7 +2674,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5354
2674
|
(signal) => runWithConcurrency(
|
|
5355
2675
|
adapters,
|
|
5356
2676
|
ALL_PROVIDER_CONCURRENCY,
|
|
5357
|
-
(adapter, _index, workerSignal) =>
|
|
2677
|
+
(adapter, _index, workerSignal) => queryStableAdapterState(
|
|
5358
2678
|
ctx,
|
|
5359
2679
|
adapter,
|
|
5360
2680
|
adapter.id === currentProviderId ? "current" : "configured",
|
|
@@ -5380,6 +2700,22 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5380
2700
|
});
|
|
5381
2701
|
const revalidated = await queryStableCurrent(ctx, false, controller, "Revalidating current usage\u2026");
|
|
5382
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
|
+
}
|
|
5383
2719
|
stableCurrent = revalidated;
|
|
5384
2720
|
current = revalidated.outcome;
|
|
5385
2721
|
visibleStates = [
|
|
@@ -5410,7 +2746,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5410
2746
|
try {
|
|
5411
2747
|
await showMenu(ctx);
|
|
5412
2748
|
} catch (error) {
|
|
5413
|
-
if (isStaleExtensionContextError(error) ||
|
|
2749
|
+
if (isStaleExtensionContextError(error) || isAbortError2(error)) return;
|
|
5414
2750
|
throw error;
|
|
5415
2751
|
}
|
|
5416
2752
|
}
|
|
@@ -5435,6 +2771,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
5435
2771
|
startStatusRefresh(ctx, ctx.model, false);
|
|
5436
2772
|
});
|
|
5437
2773
|
pi.on("model_select", (event, ctx) => {
|
|
2774
|
+
for (const controller of nativeQueryControllers) controller.abort();
|
|
5438
2775
|
startStatusRefresh(ctx, event.model, false);
|
|
5439
2776
|
});
|
|
5440
2777
|
pi.on("turn_start", (_event, ctx) => {
|