@omnicross/daemon 0.3.0 → 0.4.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/dist/cli.cjs +2787 -359
- package/dist/cli.js +2743 -297
- package/dist/index.cjs +2717 -429
- package/dist/index.d.cts +486 -28
- package/dist/index.d.ts +486 -28
- package/dist/index.js +2583 -284
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/bootstrap.ts
|
|
2
|
-
import { accessSync, constants as fsConstants, existsSync as
|
|
2
|
+
import { accessSync, constants as fsConstants, existsSync as existsSync30, mkdirSync as mkdirSync9 } from "fs";
|
|
3
3
|
import { dirname as dirname17 } from "path";
|
|
4
4
|
import { DEFAULT_AUDIT_CONFIG } from "@omnicross/contracts/audit-types";
|
|
5
5
|
import { DEFAULT_BILLING_CONFIG } from "@omnicross/contracts/billing-types";
|
|
@@ -18,14 +18,14 @@ import { setSubscriptionRegistryForOutbound } from "@omnicross/core/outbound-api
|
|
|
18
18
|
import { getSharedAccountHealth as getSharedAccountHealth4 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
19
19
|
import {
|
|
20
20
|
__resetSharedAccountAllowanceStoreForTests,
|
|
21
|
-
AccountAllowanceStore as
|
|
21
|
+
AccountAllowanceStore as AccountAllowanceStore8,
|
|
22
22
|
setSharedAccountAllowanceStore
|
|
23
23
|
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
24
24
|
import {
|
|
25
25
|
__resetSharedAccountAllowanceSchedulingForTests,
|
|
26
26
|
getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling5
|
|
27
27
|
} from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
28
|
-
import { fetchUpstream as
|
|
28
|
+
import { fetchUpstream as fetchUpstream13, setUpstreamProxyResolver } from "@omnicross/core/pipeline/upstreamFetch";
|
|
29
29
|
import { __resetSharedIdentityStoreForTests } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
|
|
30
30
|
import { setGeminiCodeAssistResolver } from "@omnicross/core/ports/gemini-code-assist-resolver";
|
|
31
31
|
import {
|
|
@@ -155,9 +155,263 @@ function handleCodexOAuthStatus(sessionId, deps) {
|
|
|
155
155
|
return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
// src/admin/accountsKimiOAuth.ts
|
|
159
|
+
import { kimiOAuth } from "@omnicross/subscriptions";
|
|
160
|
+
function err2(status, message) {
|
|
161
|
+
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
162
|
+
}
|
|
163
|
+
var DEFAULT_KIMI_OAUTH_TTL_MS = 15 * 6e4;
|
|
164
|
+
async function handleKimiOAuthStart(deps) {
|
|
165
|
+
if (deps.kimiSessions.isBusy()) {
|
|
166
|
+
return err2(409, "a kimi sign-in is already in progress \u2014 finish it in the browser or cancel it");
|
|
167
|
+
}
|
|
168
|
+
const fetchImpl = deps.oauthExchangeFetch("kimi");
|
|
169
|
+
const deviceId = kimiOAuth.generateKimiDeviceId();
|
|
170
|
+
const fingerprint = kimiOAuth.kimiFingerprintHeaders(deviceId);
|
|
171
|
+
let authorization;
|
|
172
|
+
try {
|
|
173
|
+
authorization = await kimiOAuth.requestDeviceAuthorization(fetchImpl, fingerprint);
|
|
174
|
+
} catch (e) {
|
|
175
|
+
const reason = e instanceof Error ? e.message : "device authorization failed";
|
|
176
|
+
return err2(502, `kimi device authorization failed: ${reason}`);
|
|
177
|
+
}
|
|
178
|
+
const { sessionId, signal } = deps.kimiSessions.begin();
|
|
179
|
+
void runKimiDevicePoll(sessionId, authorization.deviceCode, deviceId, fingerprint, signal, deps).catch(() => deps.kimiSessions.settle(sessionId, "error", "kimi sign-in failed"));
|
|
180
|
+
return {
|
|
181
|
+
status: 200,
|
|
182
|
+
body: {
|
|
183
|
+
authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
|
|
184
|
+
userCode: authorization.userCode,
|
|
185
|
+
sessionId
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
async function runKimiDevicePoll(sessionId, deviceCode, deviceId, fingerprint, signal, deps) {
|
|
190
|
+
const fetchImpl = deps.oauthExchangeFetch("kimi");
|
|
191
|
+
const result = await kimiOAuth.awaitDeviceToken(
|
|
192
|
+
{ userCode: "", deviceCode, verificationUri: "" },
|
|
193
|
+
fetchImpl,
|
|
194
|
+
{
|
|
195
|
+
fingerprint,
|
|
196
|
+
deadlineMs: DEFAULT_KIMI_OAUTH_TTL_MS,
|
|
197
|
+
sleep: (ms) => new Promise((resolve10, reject) => {
|
|
198
|
+
const onAbort = () => {
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
reject(new Error("login: cancelled"));
|
|
201
|
+
};
|
|
202
|
+
const timer = setTimeout(() => {
|
|
203
|
+
signal.removeEventListener("abort", onAbort);
|
|
204
|
+
resolve10();
|
|
205
|
+
}, ms);
|
|
206
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
const block = {
|
|
211
|
+
authMethod: "oauth",
|
|
212
|
+
status: "authorized",
|
|
213
|
+
accessToken: result.accessToken,
|
|
214
|
+
refreshToken: result.refreshToken,
|
|
215
|
+
expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
|
|
216
|
+
accountId: kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
|
|
217
|
+
deviceId,
|
|
218
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
219
|
+
};
|
|
220
|
+
await deps.subscriptionAccountAppender.appendProviderAccount("kimi", block);
|
|
221
|
+
deps.kimiSessions.settle(sessionId, "done");
|
|
222
|
+
}
|
|
223
|
+
function handleKimiOAuthCancel(sessionId, deps) {
|
|
224
|
+
if (!deps.kimiSessions.cancel(sessionId)) return err2(404, "unknown or expired kimi sign-in session");
|
|
225
|
+
return { status: 200, body: { ok: true } };
|
|
226
|
+
}
|
|
227
|
+
function handleKimiOAuthStatus(sessionId, deps) {
|
|
228
|
+
const s = deps.kimiSessions.get(sessionId);
|
|
229
|
+
if (!s) return err2(404, "unknown or expired kimi sign-in session");
|
|
230
|
+
return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/admin/accountsGrokOAuth.ts
|
|
234
|
+
import { grokOAuth } from "@omnicross/subscriptions";
|
|
235
|
+
function err3(status, message) {
|
|
236
|
+
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
237
|
+
}
|
|
238
|
+
var DEFAULT_GROK_OAUTH_TTL_MS = 15 * 6e4;
|
|
239
|
+
async function handleGrokOAuthStart(deps) {
|
|
240
|
+
if (deps.grokSessions.isBusy()) {
|
|
241
|
+
return err3(409, "a grok sign-in is already in progress \u2014 finish it in the browser or cancel it");
|
|
242
|
+
}
|
|
243
|
+
const fetchImpl = deps.oauthExchangeFetch("grok");
|
|
244
|
+
let tokenEndpoint;
|
|
245
|
+
try {
|
|
246
|
+
tokenEndpoint = await grokOAuth.resolveGrokTokenEndpoint(fetchImpl);
|
|
247
|
+
} catch (e) {
|
|
248
|
+
const reason = e instanceof Error ? e.message : "OIDC discovery failed";
|
|
249
|
+
return err3(502, `grok token-endpoint discovery failed: ${reason}`);
|
|
250
|
+
}
|
|
251
|
+
let authorization;
|
|
252
|
+
try {
|
|
253
|
+
authorization = await grokOAuth.requestGrokDeviceAuthorization(fetchImpl);
|
|
254
|
+
} catch (e) {
|
|
255
|
+
const reason = e instanceof Error ? e.message : "device authorization failed";
|
|
256
|
+
return err3(502, `grok device authorization failed: ${reason}`);
|
|
257
|
+
}
|
|
258
|
+
const { sessionId, signal } = deps.grokSessions.begin();
|
|
259
|
+
void runGrokDevicePoll(sessionId, tokenEndpoint, authorization.deviceCode, signal, deps).catch((e) => {
|
|
260
|
+
const reason = e instanceof Error ? e.message : "grok sign-in failed";
|
|
261
|
+
deps.grokSessions.settle(sessionId, "error", reason);
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
status: 200,
|
|
265
|
+
body: {
|
|
266
|
+
authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
|
|
267
|
+
userCode: authorization.userCode,
|
|
268
|
+
sessionId
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
async function runGrokDevicePoll(sessionId, tokenEndpoint, deviceCode, signal, deps) {
|
|
273
|
+
const fetchImpl = deps.oauthExchangeFetch("grok");
|
|
274
|
+
const result = await grokOAuth.awaitGrokDeviceToken(
|
|
275
|
+
{ userCode: "", deviceCode, verificationUri: "" },
|
|
276
|
+
tokenEndpoint,
|
|
277
|
+
fetchImpl,
|
|
278
|
+
{
|
|
279
|
+
deadlineMs: DEFAULT_GROK_OAUTH_TTL_MS,
|
|
280
|
+
sleep: (ms) => new Promise((resolve10, reject) => {
|
|
281
|
+
const onAbort = () => {
|
|
282
|
+
clearTimeout(timer);
|
|
283
|
+
reject(new Error("login: cancelled"));
|
|
284
|
+
};
|
|
285
|
+
const timer = setTimeout(() => {
|
|
286
|
+
signal.removeEventListener("abort", onAbort);
|
|
287
|
+
resolve10();
|
|
288
|
+
}, ms);
|
|
289
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
);
|
|
293
|
+
const block = {
|
|
294
|
+
authMethod: "oauth",
|
|
295
|
+
status: "authorized",
|
|
296
|
+
accessToken: result.accessToken,
|
|
297
|
+
refreshToken: result.refreshToken,
|
|
298
|
+
expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
|
|
299
|
+
accountId: grokOAuth.grokAccountIdFromAccessToken(result.accessToken),
|
|
300
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
301
|
+
};
|
|
302
|
+
await deps.subscriptionAccountAppender.appendProviderAccount("grok", block);
|
|
303
|
+
deps.grokSessions.settle(sessionId, "done");
|
|
304
|
+
}
|
|
305
|
+
function handleGrokOAuthCancel(sessionId, deps) {
|
|
306
|
+
if (!deps.grokSessions.cancel(sessionId)) return err3(404, "unknown or expired grok sign-in session");
|
|
307
|
+
return { status: 200, body: { ok: true } };
|
|
308
|
+
}
|
|
309
|
+
function handleGrokOAuthStatus(sessionId, deps) {
|
|
310
|
+
const s = deps.grokSessions.get(sessionId);
|
|
311
|
+
if (!s) return err3(404, "unknown or expired grok sign-in session");
|
|
312
|
+
return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/admin/accountsCopilotOAuth.ts
|
|
316
|
+
import { copilotOAuth } from "@omnicross/subscriptions";
|
|
317
|
+
function err4(status, message) {
|
|
318
|
+
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
319
|
+
}
|
|
320
|
+
var DEFAULT_COPILOT_OAUTH_TTL_MS = 15 * 6e4;
|
|
321
|
+
async function handleCopilotOAuthStart(deps, enterpriseUrlInput) {
|
|
322
|
+
if (deps.copilotSessions.isBusy()) {
|
|
323
|
+
return err4(409, "a copilot sign-in is already in progress \u2014 finish it in the browser or cancel it");
|
|
324
|
+
}
|
|
325
|
+
let enterpriseUrl;
|
|
326
|
+
if (typeof enterpriseUrlInput === "string" && enterpriseUrlInput.trim()) {
|
|
327
|
+
try {
|
|
328
|
+
enterpriseUrl = copilotOAuth.normalizeCopilotEnterpriseDomain(enterpriseUrlInput);
|
|
329
|
+
} catch (e) {
|
|
330
|
+
const reason = e instanceof Error ? e.message : "invalid GitHub Enterprise domain";
|
|
331
|
+
return err4(400, `copilot ${reason}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const fetchImpl = deps.oauthExchangeFetch("copilot");
|
|
335
|
+
let authorization;
|
|
336
|
+
try {
|
|
337
|
+
authorization = await copilotOAuth.requestCopilotDeviceAuthorization(fetchImpl, enterpriseUrl);
|
|
338
|
+
} catch (e) {
|
|
339
|
+
const reason = e instanceof Error ? e.message : "device authorization failed";
|
|
340
|
+
return err4(502, `copilot device authorization failed: ${reason}`);
|
|
341
|
+
}
|
|
342
|
+
const { sessionId, signal } = deps.copilotSessions.begin();
|
|
343
|
+
void runCopilotDevicePoll(sessionId, authorization.deviceCode, signal, deps, enterpriseUrl).catch((e) => {
|
|
344
|
+
const reason = e instanceof Error ? e.message : "copilot sign-in failed";
|
|
345
|
+
deps.copilotSessions.settle(sessionId, "error", reason);
|
|
346
|
+
});
|
|
347
|
+
return {
|
|
348
|
+
status: 200,
|
|
349
|
+
body: {
|
|
350
|
+
authUrl: authorization.verificationUri,
|
|
351
|
+
userCode: authorization.userCode,
|
|
352
|
+
sessionId,
|
|
353
|
+
...enterpriseUrl ? { enterpriseUrl } : {}
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
async function runCopilotDevicePoll(sessionId, deviceCode, signal, deps, enterpriseUrl) {
|
|
358
|
+
const fetchImpl = deps.oauthExchangeFetch("copilot");
|
|
359
|
+
const result = await copilotOAuth.awaitCopilotDeviceToken(
|
|
360
|
+
{ userCode: "", deviceCode, verificationUri: "", interval: 5, expiresIn: 900 },
|
|
361
|
+
fetchImpl,
|
|
362
|
+
{
|
|
363
|
+
deadlineMs: DEFAULT_COPILOT_OAUTH_TTL_MS,
|
|
364
|
+
...enterpriseUrl ? { enterpriseUrl } : {},
|
|
365
|
+
sleep: (ms) => new Promise((resolve10, reject) => {
|
|
366
|
+
const onAbort = () => {
|
|
367
|
+
clearTimeout(timer);
|
|
368
|
+
reject(new Error("login: cancelled"));
|
|
369
|
+
};
|
|
370
|
+
const timer = setTimeout(() => {
|
|
371
|
+
signal.removeEventListener("abort", onAbort);
|
|
372
|
+
resolve10();
|
|
373
|
+
}, ms);
|
|
374
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
const identity = await copilotOAuth.fetchCopilotIdentity(result.accessToken, fetchImpl, enterpriseUrl);
|
|
379
|
+
const apiEndpoint = await copilotOAuth.discoverCopilotApiEndpoint(result.accessToken, fetchImpl, enterpriseUrl);
|
|
380
|
+
await copilotOAuth.enableAllCopilotModels(
|
|
381
|
+
result.accessToken,
|
|
382
|
+
{ apiEndpoint, ...enterpriseUrl ? { enterpriseUrl } : {} },
|
|
383
|
+
fetchImpl
|
|
384
|
+
);
|
|
385
|
+
const block = {
|
|
386
|
+
authMethod: "oauth",
|
|
387
|
+
status: "authorized",
|
|
388
|
+
accessToken: result.accessToken,
|
|
389
|
+
refreshToken: result.accessToken,
|
|
390
|
+
expiresAt: new Date(Date.now() + copilotOAuth.COPILOT_FAR_FUTURE_MS).toISOString(),
|
|
391
|
+
...identity.accountId ? { accountId: identity.accountId } : {},
|
|
392
|
+
...identity.email ? { email: identity.email } : {},
|
|
393
|
+
...apiEndpoint ? { apiEndpoint } : {},
|
|
394
|
+
...enterpriseUrl ? { enterpriseUrl } : {},
|
|
395
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
396
|
+
};
|
|
397
|
+
await deps.subscriptionAccountAppender.appendProviderAccount("copilot", block);
|
|
398
|
+
deps.copilotSessions.settle(sessionId, "done");
|
|
399
|
+
}
|
|
400
|
+
function handleCopilotOAuthCancel(sessionId, deps) {
|
|
401
|
+
if (!deps.copilotSessions.cancel(sessionId)) {
|
|
402
|
+
return err4(404, "unknown or expired copilot sign-in session");
|
|
403
|
+
}
|
|
404
|
+
return { status: 200, body: { ok: true } };
|
|
405
|
+
}
|
|
406
|
+
function handleCopilotOAuthStatus(sessionId, deps) {
|
|
407
|
+
const s = deps.copilotSessions.get(sessionId);
|
|
408
|
+
if (!s) return err4(404, "unknown or expired copilot sign-in session");
|
|
409
|
+
return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
|
|
410
|
+
}
|
|
411
|
+
|
|
158
412
|
// src/allowance/AccountAllowanceService.ts
|
|
159
413
|
import {
|
|
160
|
-
getSharedAccountAllowanceStore as
|
|
414
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore7
|
|
161
415
|
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
162
416
|
import {
|
|
163
417
|
getSharedAccountAllowanceScheduling
|
|
@@ -191,13 +445,11 @@ function secondsUntil(instant, now) {
|
|
|
191
445
|
function windowFromPayload(id, payload, now) {
|
|
192
446
|
const usedPercent = finitePercent(payload?.utilization);
|
|
193
447
|
const resetsAt = isoInstant(payload?.resets_at);
|
|
194
|
-
const isSonnet = id === "seven-day-sonnet";
|
|
195
448
|
const isFiveHour = id === "five-hour";
|
|
196
449
|
return {
|
|
197
450
|
id,
|
|
198
|
-
label: isFiveHour ? "5 hours" :
|
|
199
|
-
scope:
|
|
200
|
-
modelFamily: isSonnet ? "sonnet" : void 0,
|
|
451
|
+
label: isFiveHour ? "5 hours" : "7 days",
|
|
452
|
+
scope: "all",
|
|
201
453
|
usedPercent,
|
|
202
454
|
windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
|
|
203
455
|
resetsAt,
|
|
@@ -205,6 +457,44 @@ function windowFromPayload(id, payload, now) {
|
|
|
205
457
|
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
206
458
|
};
|
|
207
459
|
}
|
|
460
|
+
function limitEntryWindow(entries, kind) {
|
|
461
|
+
const entry = entries.find((candidate) => candidate.kind === kind);
|
|
462
|
+
if (!entry) return void 0;
|
|
463
|
+
return { utilization: entry.percent, resets_at: entry.resets_at };
|
|
464
|
+
}
|
|
465
|
+
function slugifyDisplayName(name) {
|
|
466
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
467
|
+
}
|
|
468
|
+
function scopedWeeklyWindows(entries, now) {
|
|
469
|
+
const seen = /* @__PURE__ */ new Set();
|
|
470
|
+
const windows = [];
|
|
471
|
+
for (const entry of entries) {
|
|
472
|
+
if (entry.kind !== "weekly_scoped") continue;
|
|
473
|
+
const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
|
|
474
|
+
if (!displayName) continue;
|
|
475
|
+
const slug = slugifyDisplayName(displayName);
|
|
476
|
+
if (!slug || seen.has(slug)) continue;
|
|
477
|
+
seen.add(slug);
|
|
478
|
+
const usedPercent = finitePercent(entry.percent);
|
|
479
|
+
const resetsAt = isoInstant(entry.resets_at);
|
|
480
|
+
windows.push({
|
|
481
|
+
id: `seven-day-${slug}`,
|
|
482
|
+
label: `7 days \xB7 ${displayName}`,
|
|
483
|
+
scope: "model-family",
|
|
484
|
+
modelFamily: slug,
|
|
485
|
+
usedPercent,
|
|
486
|
+
windowMinutes: 7 * 24 * 60,
|
|
487
|
+
resetsAt,
|
|
488
|
+
remainingSeconds: secondsUntil(resetsAt, now),
|
|
489
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
return windows;
|
|
493
|
+
}
|
|
494
|
+
function parseLimitEntries(raw) {
|
|
495
|
+
if (!Array.isArray(raw)) return [];
|
|
496
|
+
return raw.filter((entry) => !!entry && typeof entry === "object");
|
|
497
|
+
}
|
|
208
498
|
function emptyClaudeWindows(state) {
|
|
209
499
|
return [
|
|
210
500
|
{
|
|
@@ -222,159 +512,1233 @@ function emptyClaudeWindows(state) {
|
|
|
222
512
|
usedPercent: null,
|
|
223
513
|
windowMinutes: 7 * 24 * 60,
|
|
224
514
|
state
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
id: "seven-day-sonnet",
|
|
228
|
-
label: "7 days \xB7 Sonnet",
|
|
229
|
-
scope: "model-family",
|
|
230
|
-
modelFamily: "sonnet",
|
|
231
|
-
usedPercent: null,
|
|
232
|
-
windowMinutes: 7 * 24 * 60,
|
|
233
|
-
state
|
|
234
515
|
}
|
|
235
|
-
];
|
|
516
|
+
];
|
|
517
|
+
}
|
|
518
|
+
function hasHeader(headers, name) {
|
|
519
|
+
const wanted = name.toLowerCase();
|
|
520
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === wanted);
|
|
521
|
+
}
|
|
522
|
+
var ClaudeAllowanceCollector = class {
|
|
523
|
+
constructor(credentials, store = getSharedAccountAllowanceStore(), fetchImpl = (url, init, accountId) => fetchUpstream(url, init, { providerId: "claude", accountId }), identityStore = getSharedIdentityStore(), now = Date.now) {
|
|
524
|
+
this.credentials = credentials;
|
|
525
|
+
this.store = store;
|
|
526
|
+
this.fetchImpl = fetchImpl;
|
|
527
|
+
this.identityStore = identityStore;
|
|
528
|
+
this.now = now;
|
|
529
|
+
}
|
|
530
|
+
credentials;
|
|
531
|
+
store;
|
|
532
|
+
fetchImpl;
|
|
533
|
+
identityStore;
|
|
534
|
+
now;
|
|
535
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
536
|
+
async collectMany(accounts, options = {}) {
|
|
537
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
538
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
539
|
+
}
|
|
540
|
+
collect(account, options = {}) {
|
|
541
|
+
const now = this.now();
|
|
542
|
+
const unsupported = account.tokens.isSetupToken || account.tokens.authMethod !== "oauth";
|
|
543
|
+
if (unsupported) {
|
|
544
|
+
const existing = this.store.get("claude", account.id, now);
|
|
545
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) return Promise.resolve(existing);
|
|
546
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
547
|
+
this.store.set(snapshot);
|
|
548
|
+
return Promise.resolve(snapshot);
|
|
549
|
+
}
|
|
550
|
+
const cached = this.store.get("claude", account.id, now);
|
|
551
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) return Promise.resolve(cached);
|
|
552
|
+
const running = this.inFlight.get(account.id);
|
|
553
|
+
if (running) return running;
|
|
554
|
+
const promise = this.fetchAccount(account.id).catch(() => this.failureSnapshot(account.id, "claude_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
|
|
555
|
+
this.inFlight.set(account.id, promise);
|
|
556
|
+
return promise;
|
|
557
|
+
}
|
|
558
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
559
|
+
if (snapshot.source !== "oauth-usage-api") return false;
|
|
560
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
561
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
562
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
563
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
564
|
+
}
|
|
565
|
+
async fetchAccount(accountId) {
|
|
566
|
+
let token = await this.credentials.getAccessTokenForAccount("claude", accountId);
|
|
567
|
+
if (!token) return this.failureSnapshot(accountId, "claude_usage_token_unavailable", this.now());
|
|
568
|
+
let response = await this.request(accountId, token);
|
|
569
|
+
if (response.status === 401) {
|
|
570
|
+
const refreshed = await this.credentials.refreshAccountToken("claude", accountId);
|
|
571
|
+
if (!refreshed) return this.failureSnapshot(accountId, "claude_usage_unauthorized", this.now());
|
|
572
|
+
token = await this.credentials.getAccessTokenForAccount("claude", accountId);
|
|
573
|
+
if (!token) return this.failureSnapshot(accountId, "claude_usage_token_unavailable", this.now());
|
|
574
|
+
response = await this.request(accountId, token);
|
|
575
|
+
}
|
|
576
|
+
if (response.status === 403) {
|
|
577
|
+
const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "claude_usage_unsupported");
|
|
578
|
+
this.store.set(snapshot2);
|
|
579
|
+
return snapshot2;
|
|
580
|
+
}
|
|
581
|
+
if (!response.ok) {
|
|
582
|
+
return this.failureSnapshot(accountId, "claude_usage_http_error", this.now());
|
|
583
|
+
}
|
|
584
|
+
let payload;
|
|
585
|
+
try {
|
|
586
|
+
payload = await response.json();
|
|
587
|
+
} catch {
|
|
588
|
+
return this.failureSnapshot(accountId, "claude_usage_invalid_response", this.now());
|
|
589
|
+
}
|
|
590
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
591
|
+
return this.failureSnapshot(accountId, "claude_usage_invalid_response", this.now());
|
|
592
|
+
}
|
|
593
|
+
const now = this.now();
|
|
594
|
+
const usage = payload;
|
|
595
|
+
const limitEntries = parseLimitEntries(usage.limits);
|
|
596
|
+
const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
|
|
597
|
+
const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
|
|
598
|
+
const snapshot = {
|
|
599
|
+
providerId: "claude",
|
|
600
|
+
accountId,
|
|
601
|
+
source: "oauth-usage-api",
|
|
602
|
+
observedAt: new Date(now).toISOString(),
|
|
603
|
+
expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
|
|
604
|
+
windows: [
|
|
605
|
+
windowFromPayload("five-hour", fiveHour, now),
|
|
606
|
+
windowFromPayload("seven-day", sevenDay, now),
|
|
607
|
+
...scopedWeeklyWindows(limitEntries, now)
|
|
608
|
+
].slice(0, 8)
|
|
609
|
+
};
|
|
610
|
+
this.store.set(snapshot);
|
|
611
|
+
return snapshot;
|
|
612
|
+
}
|
|
613
|
+
request(accountId, token) {
|
|
614
|
+
const headers = {
|
|
615
|
+
Authorization: `Bearer ${token}`,
|
|
616
|
+
Accept: "application/json",
|
|
617
|
+
"Content-Type": "application/json",
|
|
618
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
619
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
620
|
+
};
|
|
621
|
+
applyFingerprint(this.identityStore, headers, "claude", accountId, void 0);
|
|
622
|
+
if (!hasHeader(headers, "user-agent")) {
|
|
623
|
+
headers["User-Agent"] = "claude-cli/2.0.53 (external, cli)";
|
|
624
|
+
}
|
|
625
|
+
return this.fetchImpl(CLAUDE_USAGE_URL, {
|
|
626
|
+
method: "GET",
|
|
627
|
+
headers,
|
|
628
|
+
signal: AbortSignal.timeout(15e3)
|
|
629
|
+
}, accountId);
|
|
630
|
+
}
|
|
631
|
+
failureSnapshot(accountId, code, now) {
|
|
632
|
+
const existing = this.store.get("claude", accountId, now);
|
|
633
|
+
const snapshot = existing ? {
|
|
634
|
+
...existing,
|
|
635
|
+
expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
|
|
636
|
+
windows: existing.windows.map((window) => ({
|
|
637
|
+
...window,
|
|
638
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
639
|
+
})),
|
|
640
|
+
lastErrorCode: code
|
|
641
|
+
} : {
|
|
642
|
+
providerId: "claude",
|
|
643
|
+
accountId,
|
|
644
|
+
source: "oauth-usage-api",
|
|
645
|
+
observedAt: new Date(now).toISOString(),
|
|
646
|
+
expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
|
|
647
|
+
windows: emptyClaudeWindows("unavailable"),
|
|
648
|
+
lastErrorCode: code
|
|
649
|
+
};
|
|
650
|
+
this.store.set(snapshot);
|
|
651
|
+
return snapshot;
|
|
652
|
+
}
|
|
653
|
+
unsupportedSnapshot(accountId, now, code = "claude_usage_unsupported_auth") {
|
|
654
|
+
return {
|
|
655
|
+
providerId: "claude",
|
|
656
|
+
accountId,
|
|
657
|
+
source: "oauth-usage-api",
|
|
658
|
+
observedAt: new Date(now).toISOString(),
|
|
659
|
+
windows: emptyClaudeWindows("unsupported"),
|
|
660
|
+
lastErrorCode: code
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
// src/allowance/CodexAllowanceCollector.ts
|
|
666
|
+
import {
|
|
667
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
|
|
668
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
669
|
+
import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
670
|
+
var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
671
|
+
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
672
|
+
var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
|
|
673
|
+
function finiteNumber(value) {
|
|
674
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
675
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
676
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
677
|
+
}
|
|
678
|
+
function finitePercent2(value) {
|
|
679
|
+
const parsed = finiteNumber(value);
|
|
680
|
+
return parsed !== null && parsed <= 100 ? parsed : null;
|
|
681
|
+
}
|
|
682
|
+
function epochMs(value) {
|
|
683
|
+
return value > 1e11 ? value : value * 1e3;
|
|
684
|
+
}
|
|
685
|
+
function secondsUntil2(instant, now) {
|
|
686
|
+
if (!instant) return void 0;
|
|
687
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
688
|
+
}
|
|
689
|
+
function decodeJwtClaims(token) {
|
|
690
|
+
const parts = token.split(".");
|
|
691
|
+
if (parts.length !== 3) return void 0;
|
|
692
|
+
try {
|
|
693
|
+
const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
694
|
+
const parsed = JSON.parse(json2);
|
|
695
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
696
|
+
} catch {
|
|
697
|
+
return void 0;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
function chatgptAccountIdFromClaims(claims) {
|
|
701
|
+
const auth = claims?.["https://api.openai.com/auth"];
|
|
702
|
+
if (!auth || typeof auth !== "object") return void 0;
|
|
703
|
+
const accountId = auth.chatgpt_account_id;
|
|
704
|
+
return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
|
|
705
|
+
}
|
|
706
|
+
function resolveCodexChatGptAccountId(tokens) {
|
|
707
|
+
if (tokens.accountId?.trim()) return tokens.accountId.trim();
|
|
708
|
+
if (tokens.idToken) {
|
|
709
|
+
const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
|
|
710
|
+
if (fromIdToken) return fromIdToken;
|
|
711
|
+
}
|
|
712
|
+
if (tokens.accessToken) {
|
|
713
|
+
return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
|
|
714
|
+
}
|
|
715
|
+
return void 0;
|
|
716
|
+
}
|
|
717
|
+
function windowFromPayload2(id, payload, now) {
|
|
718
|
+
const usedPercent = finitePercent2(payload?.used_percent);
|
|
719
|
+
const resetAtSeconds = finiteNumber(payload?.reset_at);
|
|
720
|
+
const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
|
|
721
|
+
const windowSeconds = finiteNumber(payload?.limit_window_seconds);
|
|
722
|
+
const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
|
|
723
|
+
const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
|
|
724
|
+
return {
|
|
725
|
+
id,
|
|
726
|
+
label: id === "primary" ? "Primary" : "Secondary",
|
|
727
|
+
scope: "all",
|
|
728
|
+
usedPercent,
|
|
729
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
730
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
731
|
+
remainingSeconds: secondsUntil2(resetsAt, now),
|
|
732
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
var CodexAllowanceCollector = class {
|
|
736
|
+
constructor(credentials, store = getSharedAccountAllowanceStore2(), fetchImpl = (url, init, accountId) => fetchUpstream2(url, init, { providerId: "codex", accountId, redactBodies: true }), now = Date.now) {
|
|
737
|
+
this.credentials = credentials;
|
|
738
|
+
this.store = store;
|
|
739
|
+
this.fetchImpl = fetchImpl;
|
|
740
|
+
this.now = now;
|
|
741
|
+
}
|
|
742
|
+
credentials;
|
|
743
|
+
store;
|
|
744
|
+
fetchImpl;
|
|
745
|
+
now;
|
|
746
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
747
|
+
async collectMany(accounts, options = {}) {
|
|
748
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
749
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
750
|
+
}
|
|
751
|
+
collect(account, options = {}) {
|
|
752
|
+
const now = this.now();
|
|
753
|
+
const unsupported = account.tokens.authMethod !== "oauth";
|
|
754
|
+
if (unsupported) {
|
|
755
|
+
const existing = this.store.get("codex", account.id, now);
|
|
756
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
757
|
+
return Promise.resolve(existing);
|
|
758
|
+
}
|
|
759
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
760
|
+
this.store.set(snapshot);
|
|
761
|
+
return Promise.resolve(snapshot);
|
|
762
|
+
}
|
|
763
|
+
const cached = this.store.get("codex", account.id, now);
|
|
764
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
765
|
+
return Promise.resolve(cached);
|
|
766
|
+
}
|
|
767
|
+
const running = this.inFlight.get(account.id);
|
|
768
|
+
if (running) return running;
|
|
769
|
+
const promise = this.fetchAccount(account.id, account.tokens).catch(() => this.failureSnapshot(account.id, "codex_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
|
|
770
|
+
this.inFlight.set(account.id, promise);
|
|
771
|
+
return promise;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* A response-header snapshot stays a valid cache hit only while fresh; an
|
|
775
|
+
* active oauth-usage snapshot is honored on the same 5-minute cadence as
|
|
776
|
+
* Claude's (the poll is cheap and quota is the scheduling input).
|
|
777
|
+
*/
|
|
778
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
779
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
780
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
781
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
782
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
783
|
+
}
|
|
784
|
+
async fetchAccount(accountId, tokens) {
|
|
785
|
+
let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
|
|
786
|
+
if (!accessToken) {
|
|
787
|
+
return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
|
|
788
|
+
}
|
|
789
|
+
let response = await this.request(accountId, accessToken, tokens);
|
|
790
|
+
if (response.status === 401) {
|
|
791
|
+
const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
|
|
792
|
+
if (!refreshed) {
|
|
793
|
+
return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
|
|
794
|
+
}
|
|
795
|
+
accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
|
|
796
|
+
if (!accessToken) {
|
|
797
|
+
return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
|
|
798
|
+
}
|
|
799
|
+
response = await this.request(accountId, accessToken, tokens);
|
|
800
|
+
}
|
|
801
|
+
if (response.status === 403) {
|
|
802
|
+
const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
|
|
803
|
+
this.store.set(snapshot2);
|
|
804
|
+
return snapshot2;
|
|
805
|
+
}
|
|
806
|
+
if (!response.ok) {
|
|
807
|
+
return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
|
|
808
|
+
}
|
|
809
|
+
let payload;
|
|
810
|
+
try {
|
|
811
|
+
payload = await response.json();
|
|
812
|
+
} catch {
|
|
813
|
+
return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
|
|
814
|
+
}
|
|
815
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
816
|
+
return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
|
|
817
|
+
}
|
|
818
|
+
const now = this.now();
|
|
819
|
+
const usage = payload.rate_limit;
|
|
820
|
+
const previous = this.store.get("codex", accountId, now);
|
|
821
|
+
const snapshot = {
|
|
822
|
+
providerId: "codex",
|
|
823
|
+
accountId,
|
|
824
|
+
source: "oauth-usage-api",
|
|
825
|
+
observedAt: new Date(now).toISOString(),
|
|
826
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
827
|
+
windows: [
|
|
828
|
+
windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
|
|
829
|
+
windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
|
|
830
|
+
],
|
|
831
|
+
// The wham payload has no ratio field; keep the passively-observed value.
|
|
832
|
+
...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
|
|
833
|
+
};
|
|
834
|
+
this.store.set(snapshot);
|
|
835
|
+
return snapshot;
|
|
836
|
+
}
|
|
837
|
+
request(accountId, accessToken, tokens) {
|
|
838
|
+
const headers = {
|
|
839
|
+
Authorization: `Bearer ${accessToken}`,
|
|
840
|
+
Accept: "application/json",
|
|
841
|
+
"User-Agent": CODEX_CLI_USER_AGENT
|
|
842
|
+
};
|
|
843
|
+
const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
|
|
844
|
+
if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
|
|
845
|
+
return this.fetchImpl(CODEX_USAGE_URL, {
|
|
846
|
+
method: "GET",
|
|
847
|
+
headers,
|
|
848
|
+
signal: AbortSignal.timeout(15e3)
|
|
849
|
+
}, accountId);
|
|
850
|
+
}
|
|
851
|
+
failureSnapshot(accountId, code, now) {
|
|
852
|
+
const existing = this.store.get("codex", accountId, now);
|
|
853
|
+
const snapshot = existing ? {
|
|
854
|
+
...existing,
|
|
855
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
856
|
+
windows: existing.windows.map((window) => ({
|
|
857
|
+
...window,
|
|
858
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
859
|
+
})),
|
|
860
|
+
lastErrorCode: code
|
|
861
|
+
} : {
|
|
862
|
+
providerId: "codex",
|
|
863
|
+
accountId,
|
|
864
|
+
source: "oauth-usage-api",
|
|
865
|
+
observedAt: new Date(now).toISOString(),
|
|
866
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
867
|
+
windows: [
|
|
868
|
+
{ id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
|
|
869
|
+
{ id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
|
|
870
|
+
],
|
|
871
|
+
lastErrorCode: code
|
|
872
|
+
};
|
|
873
|
+
this.store.set(snapshot);
|
|
874
|
+
return snapshot;
|
|
875
|
+
}
|
|
876
|
+
unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
|
|
877
|
+
return {
|
|
878
|
+
providerId: "codex",
|
|
879
|
+
accountId,
|
|
880
|
+
source: "oauth-usage-api",
|
|
881
|
+
observedAt: new Date(now).toISOString(),
|
|
882
|
+
windows: [
|
|
883
|
+
{ id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
|
|
884
|
+
{ id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
|
|
885
|
+
],
|
|
886
|
+
lastErrorCode: code
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
// src/allowance/KimiAllowanceCollector.ts
|
|
892
|
+
import {
|
|
893
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore3
|
|
894
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
895
|
+
import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
896
|
+
import { kimiFingerprintHeaders } from "@omnicross/subscriptions";
|
|
897
|
+
var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
898
|
+
var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
|
|
899
|
+
function finiteNumber2(value) {
|
|
900
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
901
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
902
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
903
|
+
}
|
|
904
|
+
function isRecord(value) {
|
|
905
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
906
|
+
}
|
|
907
|
+
function parseResetMs(row, nowMs) {
|
|
908
|
+
for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
|
|
909
|
+
const value = row[key];
|
|
910
|
+
if (typeof value === "string" && value.trim()) {
|
|
911
|
+
const parsed = Date.parse(value);
|
|
912
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
913
|
+
}
|
|
914
|
+
const numeric = finiteNumber2(value);
|
|
915
|
+
if (numeric !== void 0 && numeric > 1e9) {
|
|
916
|
+
return numeric > 1e12 ? numeric : numeric * 1e3;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
|
|
920
|
+
const seconds = finiteNumber2(row[key]);
|
|
921
|
+
if (seconds !== void 0) return nowMs + seconds * 1e3;
|
|
922
|
+
}
|
|
923
|
+
return void 0;
|
|
924
|
+
}
|
|
925
|
+
var MINUTE_MS = 6e4;
|
|
926
|
+
var HOUR_MS = 36e5;
|
|
927
|
+
var DAY_MS = 864e5;
|
|
928
|
+
function canonicalWindow(durationMs) {
|
|
929
|
+
if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
|
|
930
|
+
if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
|
|
931
|
+
if (durationMs > 0 && durationMs % DAY_MS === 0) {
|
|
932
|
+
const days = durationMs / DAY_MS;
|
|
933
|
+
return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
|
|
934
|
+
}
|
|
935
|
+
if (durationMs > 0 && durationMs % HOUR_MS === 0) {
|
|
936
|
+
const hours = durationMs / HOUR_MS;
|
|
937
|
+
return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
|
|
938
|
+
}
|
|
939
|
+
return void 0;
|
|
940
|
+
}
|
|
941
|
+
function secondsUntil3(instant, now) {
|
|
942
|
+
if (!instant) return void 0;
|
|
943
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
944
|
+
}
|
|
945
|
+
function windowFromRow(row, fallback, now) {
|
|
946
|
+
const usedPercent = row?.limit !== void 0 && row.limit > 0 && row.used !== void 0 ? Math.round(Math.min(100, row.used / row.limit * 100) * 10) / 10 : null;
|
|
947
|
+
const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
|
|
948
|
+
return {
|
|
949
|
+
id: fallback.id,
|
|
950
|
+
label: fallback.label,
|
|
951
|
+
scope: "all",
|
|
952
|
+
usedPercent,
|
|
953
|
+
windowMinutes: fallback.minutes,
|
|
954
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
955
|
+
remainingSeconds: secondsUntil3(resetsAt, now),
|
|
956
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
function parseKimiUsagePayload(payload, now) {
|
|
960
|
+
if (!isRecord(payload)) return [];
|
|
961
|
+
const byId = /* @__PURE__ */ new Map();
|
|
962
|
+
const rowFrom = (data) => {
|
|
963
|
+
const limit = finiteNumber2(data["limit"]);
|
|
964
|
+
let used = finiteNumber2(data["used"]);
|
|
965
|
+
const remaining = finiteNumber2(data["remaining"]);
|
|
966
|
+
if (used === void 0 && remaining !== void 0 && limit !== void 0) {
|
|
967
|
+
used = limit - remaining;
|
|
968
|
+
}
|
|
969
|
+
let windowDurationMs;
|
|
970
|
+
const windowData = isRecord(data["window"]) ? data["window"] : void 0;
|
|
971
|
+
const duration = finiteNumber2(windowData?.["duration"]);
|
|
972
|
+
const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
|
|
973
|
+
if (duration !== void 0) {
|
|
974
|
+
if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
|
|
975
|
+
else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
|
|
976
|
+
else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
|
|
977
|
+
else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
|
|
978
|
+
else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
|
|
979
|
+
}
|
|
980
|
+
const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
|
|
981
|
+
return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
|
|
982
|
+
};
|
|
983
|
+
if (isRecord(payload["usage"])) {
|
|
984
|
+
const row = rowFrom(payload["usage"]);
|
|
985
|
+
const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
|
|
986
|
+
byId.set("seven-day", window);
|
|
987
|
+
}
|
|
988
|
+
if (Array.isArray(payload["limits"])) {
|
|
989
|
+
for (const item of payload["limits"]) {
|
|
990
|
+
if (!isRecord(item)) continue;
|
|
991
|
+
const detail = isRecord(item["detail"]) ? item["detail"] : item;
|
|
992
|
+
const row = rowFrom(detail);
|
|
993
|
+
const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
|
|
994
|
+
if (!canonical) continue;
|
|
995
|
+
const window = windowFromRow(row, canonical, now);
|
|
996
|
+
const existing = byId.get(canonical.id);
|
|
997
|
+
if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
|
|
998
|
+
byId.set(canonical.id, window);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
|
|
1003
|
+
}
|
|
1004
|
+
var KimiAllowanceCollector = class {
|
|
1005
|
+
constructor(credentials, store = getSharedAccountAllowanceStore3(), fetchImpl = (url, init, accountId) => fetchUpstream3(url, init, { providerId: "kimi", accountId, redactBodies: true }), now = Date.now) {
|
|
1006
|
+
this.credentials = credentials;
|
|
1007
|
+
this.store = store;
|
|
1008
|
+
this.fetchImpl = fetchImpl;
|
|
1009
|
+
this.now = now;
|
|
1010
|
+
}
|
|
1011
|
+
credentials;
|
|
1012
|
+
store;
|
|
1013
|
+
fetchImpl;
|
|
1014
|
+
now;
|
|
1015
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1016
|
+
async collectMany(accounts, options = {}) {
|
|
1017
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
1018
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
1019
|
+
}
|
|
1020
|
+
collect(account, options = {}) {
|
|
1021
|
+
const now = this.now();
|
|
1022
|
+
if (account.tokens.authMethod !== "oauth") {
|
|
1023
|
+
const existing = this.store.get("kimi", account.id, now);
|
|
1024
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
1025
|
+
return Promise.resolve(existing);
|
|
1026
|
+
}
|
|
1027
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
1028
|
+
this.store.set(snapshot);
|
|
1029
|
+
return Promise.resolve(snapshot);
|
|
1030
|
+
}
|
|
1031
|
+
const cached = this.store.get("kimi", account.id, now);
|
|
1032
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
1033
|
+
return Promise.resolve(cached);
|
|
1034
|
+
}
|
|
1035
|
+
const running = this.inFlight.get(account.id);
|
|
1036
|
+
if (running) return running;
|
|
1037
|
+
const promise = this.fetchAccount(account.id, account.tokens).catch(() => this.failureSnapshot(account.id, "kimi_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
|
|
1038
|
+
this.inFlight.set(account.id, promise);
|
|
1039
|
+
return promise;
|
|
1040
|
+
}
|
|
1041
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
1042
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
1043
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
1044
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
1045
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
1046
|
+
}
|
|
1047
|
+
async fetchAccount(accountId, tokens) {
|
|
1048
|
+
let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
|
|
1049
|
+
if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
|
|
1050
|
+
let response = await this.request(accountId, accessToken, tokens);
|
|
1051
|
+
if (response.status === 401) {
|
|
1052
|
+
const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
|
|
1053
|
+
if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
|
|
1054
|
+
accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
|
|
1055
|
+
if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
|
|
1056
|
+
response = await this.request(accountId, accessToken, tokens);
|
|
1057
|
+
}
|
|
1058
|
+
if (response.status === 403) {
|
|
1059
|
+
const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
|
|
1060
|
+
this.store.set(snapshot2);
|
|
1061
|
+
return snapshot2;
|
|
1062
|
+
}
|
|
1063
|
+
if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
|
|
1064
|
+
let payload;
|
|
1065
|
+
try {
|
|
1066
|
+
payload = await response.json();
|
|
1067
|
+
} catch {
|
|
1068
|
+
return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
|
|
1069
|
+
}
|
|
1070
|
+
const now = this.now();
|
|
1071
|
+
const windows = parseKimiUsagePayload(payload, now);
|
|
1072
|
+
const snapshot = {
|
|
1073
|
+
providerId: "kimi",
|
|
1074
|
+
accountId,
|
|
1075
|
+
source: "oauth-usage-api",
|
|
1076
|
+
observedAt: new Date(now).toISOString(),
|
|
1077
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1078
|
+
windows: windows.length > 0 ? windows : [
|
|
1079
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
1080
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
1081
|
+
],
|
|
1082
|
+
...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
|
|
1083
|
+
};
|
|
1084
|
+
this.store.set(snapshot);
|
|
1085
|
+
return snapshot;
|
|
1086
|
+
}
|
|
1087
|
+
request(accountId, accessToken, tokens) {
|
|
1088
|
+
return this.fetchImpl(KIMI_USAGE_URL, {
|
|
1089
|
+
method: "GET",
|
|
1090
|
+
headers: {
|
|
1091
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1092
|
+
Accept: "application/json",
|
|
1093
|
+
...kimiFingerprintHeaders(tokens.deviceId)
|
|
1094
|
+
},
|
|
1095
|
+
signal: AbortSignal.timeout(15e3)
|
|
1096
|
+
}, accountId);
|
|
1097
|
+
}
|
|
1098
|
+
failureSnapshot(accountId, code, now) {
|
|
1099
|
+
const existing = this.store.get("kimi", accountId, now);
|
|
1100
|
+
const snapshot = existing ? {
|
|
1101
|
+
...existing,
|
|
1102
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1103
|
+
windows: existing.windows.map((window) => ({
|
|
1104
|
+
...window,
|
|
1105
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
1106
|
+
})),
|
|
1107
|
+
lastErrorCode: code
|
|
1108
|
+
} : {
|
|
1109
|
+
providerId: "kimi",
|
|
1110
|
+
accountId,
|
|
1111
|
+
source: "oauth-usage-api",
|
|
1112
|
+
observedAt: new Date(now).toISOString(),
|
|
1113
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1114
|
+
windows: [
|
|
1115
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
1116
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
1117
|
+
],
|
|
1118
|
+
lastErrorCode: code
|
|
1119
|
+
};
|
|
1120
|
+
this.store.set(snapshot);
|
|
1121
|
+
return snapshot;
|
|
1122
|
+
}
|
|
1123
|
+
unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
|
|
1124
|
+
return {
|
|
1125
|
+
providerId: "kimi",
|
|
1126
|
+
accountId,
|
|
1127
|
+
source: "oauth-usage-api",
|
|
1128
|
+
observedAt: new Date(now).toISOString(),
|
|
1129
|
+
windows: [
|
|
1130
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
|
|
1131
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
|
|
1132
|
+
],
|
|
1133
|
+
lastErrorCode: code
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
// src/allowance/GrokAllowanceCollector.ts
|
|
1139
|
+
import {
|
|
1140
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore4
|
|
1141
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
1142
|
+
import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
1143
|
+
var GROK_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
1144
|
+
var GROK_BILLING_BASE = "https://cli-chat-proxy.grok.com";
|
|
1145
|
+
var GROK_BILLING_CREDITS_URL = `${GROK_BILLING_BASE}/v1/billing?format=credits`;
|
|
1146
|
+
var GROK_BILLING_MONTHLY_URL = `${GROK_BILLING_BASE}/v1/billing`;
|
|
1147
|
+
function isRecord2(value) {
|
|
1148
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1149
|
+
}
|
|
1150
|
+
function finiteNumber3(value) {
|
|
1151
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
1152
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1153
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
1154
|
+
}
|
|
1155
|
+
function percent(value) {
|
|
1156
|
+
const parsed = finiteNumber3(value);
|
|
1157
|
+
return parsed !== void 0 && parsed <= 100 ? parsed : void 0;
|
|
1158
|
+
}
|
|
1159
|
+
function onDemandAmount(value) {
|
|
1160
|
+
return isRecord2(value) ? finiteNumber3(value["val"]) : void 0;
|
|
1161
|
+
}
|
|
1162
|
+
function confirmsNoMonthlyQuota(raw) {
|
|
1163
|
+
const limit = onDemandAmount(raw["monthlyLimit"]);
|
|
1164
|
+
if (limit !== void 0) return limit === 0;
|
|
1165
|
+
return parseWeeklyConfig(raw)?.inferredPercent === true;
|
|
1166
|
+
}
|
|
1167
|
+
function parseWeeklyConfig(raw) {
|
|
1168
|
+
const period = isRecord2(raw["currentPeriod"]) ? raw["currentPeriod"] : void 0;
|
|
1169
|
+
if (!period) return null;
|
|
1170
|
+
const start = typeof period["start"] === "string" ? Date.parse(period["start"]) : Number.NaN;
|
|
1171
|
+
const end = typeof period["end"] === "string" ? Date.parse(period["end"]) : Number.NaN;
|
|
1172
|
+
const type = typeof period["type"] === "string" ? period["type"] : "";
|
|
1173
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return null;
|
|
1174
|
+
if (!type.toUpperCase().includes("WEEK")) return null;
|
|
1175
|
+
const inferred = raw["creditUsagePercent"] === void 0 || raw["creditUsagePercent"] === null;
|
|
1176
|
+
let creditUsagePercent;
|
|
1177
|
+
if (inferred) {
|
|
1178
|
+
creditUsagePercent = end > Date.now() ? 0 : void 0;
|
|
1179
|
+
} else {
|
|
1180
|
+
creditUsagePercent = percent(raw["creditUsagePercent"]);
|
|
1181
|
+
}
|
|
1182
|
+
if (creditUsagePercent === void 0) return null;
|
|
1183
|
+
return {
|
|
1184
|
+
creditUsagePercent,
|
|
1185
|
+
inferredPercent: inferred,
|
|
1186
|
+
resetsAtMs: end,
|
|
1187
|
+
unified: raw["isUnifiedBillingUser"] === true
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
function parseMonthlyConfig(raw) {
|
|
1191
|
+
const start = typeof raw["billingPeriodStart"] === "string" ? Date.parse(raw["billingPeriodStart"]) : Number.NaN;
|
|
1192
|
+
const end = typeof raw["billingPeriodEnd"] === "string" ? Date.parse(raw["billingPeriodEnd"]) : Number.NaN;
|
|
1193
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || end <= start) return null;
|
|
1194
|
+
const limit = onDemandAmount(raw["monthlyLimit"]);
|
|
1195
|
+
const used = onDemandAmount(raw["used"]);
|
|
1196
|
+
if (limit === void 0 || limit <= 0 || used === void 0) return null;
|
|
1197
|
+
return { used, limit, periodStartMs: start, periodEndMs: end };
|
|
1198
|
+
}
|
|
1199
|
+
function secondsUntil4(instant, now) {
|
|
1200
|
+
if (!instant) return void 0;
|
|
1201
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
1202
|
+
}
|
|
1203
|
+
var MINUTE_MS2 = 6e4;
|
|
1204
|
+
var DAY_MS2 = 864e5;
|
|
1205
|
+
var WEEK_MINUTES = 7 * 24 * 60;
|
|
1206
|
+
function weeklyWindow(config, now) {
|
|
1207
|
+
const resetsAt = new Date(config.resetsAtMs).toISOString();
|
|
1208
|
+
return {
|
|
1209
|
+
id: "seven-day",
|
|
1210
|
+
label: "7 days",
|
|
1211
|
+
scope: "all",
|
|
1212
|
+
usedPercent: config.creditUsagePercent,
|
|
1213
|
+
windowMinutes: WEEK_MINUTES,
|
|
1214
|
+
resetsAt,
|
|
1215
|
+
remainingSeconds: secondsUntil4(resetsAt, now),
|
|
1216
|
+
state: "fresh"
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
function monthlyWindow(config, now) {
|
|
1220
|
+
const resetsAt = new Date(config.periodEndMs).toISOString();
|
|
1221
|
+
const days = Math.max(1, Math.round((config.periodEndMs - config.periodStartMs) / DAY_MS2));
|
|
1222
|
+
return {
|
|
1223
|
+
id: "thirty-day",
|
|
1224
|
+
label: days === 30 || days === 31 ? "30 days" : `${days} days`,
|
|
1225
|
+
scope: "all",
|
|
1226
|
+
usedPercent: Math.round(Math.min(100, config.used / config.limit * 100) * 10) / 10,
|
|
1227
|
+
windowMinutes: Math.round((config.periodEndMs - config.periodStartMs) / MINUTE_MS2),
|
|
1228
|
+
resetsAt,
|
|
1229
|
+
remainingSeconds: secondsUntil4(resetsAt, now),
|
|
1230
|
+
state: "fresh"
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
function onDemandWindow(raw) {
|
|
1234
|
+
const cap = onDemandAmount(raw["onDemandCap"]);
|
|
1235
|
+
const used = onDemandAmount(raw["onDemandUsed"]);
|
|
1236
|
+
if (cap === void 0 || cap <= 0 || used === void 0) return null;
|
|
1237
|
+
return {
|
|
1238
|
+
id: "on-demand",
|
|
1239
|
+
label: "On-demand",
|
|
1240
|
+
scope: "all",
|
|
1241
|
+
usedPercent: Math.round(Math.min(100, used / cap * 100) * 10) / 10,
|
|
1242
|
+
state: "fresh"
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
async function probeBilling(url, accessToken, accountId, fetchImpl) {
|
|
1246
|
+
try {
|
|
1247
|
+
const response = await fetchImpl(url, {
|
|
1248
|
+
method: "GET",
|
|
1249
|
+
headers: {
|
|
1250
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1251
|
+
Accept: "application/json",
|
|
1252
|
+
"X-XAI-Token-Auth": "xai-grok-cli"
|
|
1253
|
+
},
|
|
1254
|
+
redirect: "error",
|
|
1255
|
+
signal: AbortSignal.timeout(15e3)
|
|
1256
|
+
}, accountId);
|
|
1257
|
+
if (!response.ok) return { status: response.status, payload: null };
|
|
1258
|
+
const payload = await response.json();
|
|
1259
|
+
return { status: response.status, payload: isRecord2(payload) ? payload : null };
|
|
1260
|
+
} catch {
|
|
1261
|
+
return { status: 0, payload: null };
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
function parseGrokBillingPayloads(creditsPayload, monthlyPayload, now) {
|
|
1265
|
+
const creditsConfig = isRecord2(creditsPayload?.["config"]) ? creditsPayload["config"] : null;
|
|
1266
|
+
const monthlyConfig = isRecord2(monthlyPayload?.["config"]) ? monthlyPayload["config"] : null;
|
|
1267
|
+
let weekly = creditsConfig ? parseWeeklyConfig(creditsConfig) : null;
|
|
1268
|
+
const unifiedFlag = creditsConfig?.["isUnifiedBillingUser"] === true;
|
|
1269
|
+
let monthly = monthlyConfig ? parseMonthlyConfig(monthlyConfig) : null;
|
|
1270
|
+
if (weekly?.inferredPercent && unifiedFlag) {
|
|
1271
|
+
if (monthly) {
|
|
1272
|
+
weekly = null;
|
|
1273
|
+
} else if (!monthlyConfig || !confirmsNoMonthlyQuota(monthlyConfig)) {
|
|
1274
|
+
weekly = null;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
const windows = [];
|
|
1278
|
+
if (weekly) windows.push(weeklyWindow(weekly, now));
|
|
1279
|
+
if (monthly) windows.push(monthlyWindow(monthly, now));
|
|
1280
|
+
const onDemandSource = monthly && monthlyConfig ? monthlyConfig : creditsConfig;
|
|
1281
|
+
const onDemand = onDemandSource ? onDemandWindow(onDemandSource) : null;
|
|
1282
|
+
if (onDemand) windows.push(onDemand);
|
|
1283
|
+
return windows.length > 0 ? windows : null;
|
|
1284
|
+
}
|
|
1285
|
+
var GrokAllowanceCollector = class {
|
|
1286
|
+
constructor(credentials, store = getSharedAccountAllowanceStore4(), fetchImpl = (url, init, accountId) => fetchUpstream4(url, init, { providerId: "grok", accountId, redactBodies: true }), now = Date.now) {
|
|
1287
|
+
this.credentials = credentials;
|
|
1288
|
+
this.store = store;
|
|
1289
|
+
this.fetchImpl = fetchImpl;
|
|
1290
|
+
this.now = now;
|
|
1291
|
+
}
|
|
1292
|
+
credentials;
|
|
1293
|
+
store;
|
|
1294
|
+
fetchImpl;
|
|
1295
|
+
now;
|
|
1296
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1297
|
+
async collectMany(accounts, options = {}) {
|
|
1298
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
1299
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
1300
|
+
}
|
|
1301
|
+
collect(account, options = {}) {
|
|
1302
|
+
const now = this.now();
|
|
1303
|
+
if (account.tokens.authMethod !== "oauth") {
|
|
1304
|
+
const existing = this.store.get("grok", account.id, now);
|
|
1305
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
1306
|
+
return Promise.resolve(existing);
|
|
1307
|
+
}
|
|
1308
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
1309
|
+
this.store.set(snapshot);
|
|
1310
|
+
return Promise.resolve(snapshot);
|
|
1311
|
+
}
|
|
1312
|
+
const cached = this.store.get("grok", account.id, now);
|
|
1313
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
1314
|
+
return Promise.resolve(cached);
|
|
1315
|
+
}
|
|
1316
|
+
const running = this.inFlight.get(account.id);
|
|
1317
|
+
if (running) return running;
|
|
1318
|
+
const promise = this.fetchAccount(account.id).catch(() => this.failureSnapshot(account.id, "grok_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
|
|
1319
|
+
this.inFlight.set(account.id, promise);
|
|
1320
|
+
return promise;
|
|
1321
|
+
}
|
|
1322
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
1323
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
1324
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
1325
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
1326
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
1327
|
+
}
|
|
1328
|
+
async fetchAccount(accountId) {
|
|
1329
|
+
const probe = async () => {
|
|
1330
|
+
const accessToken = await this.credentials.getAccessTokenForAccount("grok", accountId);
|
|
1331
|
+
if (!accessToken) return { unauthorized: true, windows: null };
|
|
1332
|
+
const credits = await probeBilling(GROK_BILLING_CREDITS_URL, accessToken, accountId, this.fetchImpl);
|
|
1333
|
+
if (credits.status === 401 || credits.status === 403) return { unauthorized: true, windows: null };
|
|
1334
|
+
const creditsConfig = isRecord2(credits.payload?.["config"]) ? credits.payload["config"] : null;
|
|
1335
|
+
const weekly = creditsConfig ? parseWeeklyConfig(creditsConfig) : null;
|
|
1336
|
+
const monthly = !weekly || creditsConfig?.["isUnifiedBillingUser"] === true ? await probeBilling(GROK_BILLING_MONTHLY_URL, accessToken, accountId, this.fetchImpl) : { status: 200, payload: null };
|
|
1337
|
+
if (monthly.status === 401 || monthly.status === 403) return { unauthorized: true, windows: null };
|
|
1338
|
+
return {
|
|
1339
|
+
unauthorized: false,
|
|
1340
|
+
windows: parseGrokBillingPayloads(credits.payload, monthly.payload, this.now())
|
|
1341
|
+
};
|
|
1342
|
+
};
|
|
1343
|
+
let result = await probe();
|
|
1344
|
+
if (result.unauthorized) {
|
|
1345
|
+
const refreshed = await this.credentials.refreshAccountToken("grok", accountId);
|
|
1346
|
+
if (!refreshed) return this.failureSnapshot(accountId, "grok_usage_unauthorized", this.now());
|
|
1347
|
+
result = await probe();
|
|
1348
|
+
if (result.unauthorized) {
|
|
1349
|
+
return this.failureSnapshot(accountId, "grok_usage_unauthorized", this.now());
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
const now = this.now();
|
|
1353
|
+
if (result.windows && result.windows.length > 0) {
|
|
1354
|
+
const snapshot = {
|
|
1355
|
+
providerId: "grok",
|
|
1356
|
+
accountId,
|
|
1357
|
+
source: "oauth-usage-api",
|
|
1358
|
+
observedAt: new Date(now).toISOString(),
|
|
1359
|
+
expiresAt: new Date(now + GROK_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1360
|
+
windows: result.windows
|
|
1361
|
+
};
|
|
1362
|
+
this.store.set(snapshot);
|
|
1363
|
+
return snapshot;
|
|
1364
|
+
}
|
|
1365
|
+
return this.failureSnapshot(accountId, "grok_usage_invalid_response", now);
|
|
1366
|
+
}
|
|
1367
|
+
failureSnapshot(accountId, code, now) {
|
|
1368
|
+
const existing = this.store.get("grok", accountId, now);
|
|
1369
|
+
const snapshot = existing ? {
|
|
1370
|
+
...existing,
|
|
1371
|
+
expiresAt: new Date(now + GROK_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1372
|
+
windows: existing.windows.map((window) => ({
|
|
1373
|
+
...window,
|
|
1374
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
1375
|
+
})),
|
|
1376
|
+
lastErrorCode: code
|
|
1377
|
+
} : {
|
|
1378
|
+
providerId: "grok",
|
|
1379
|
+
accountId,
|
|
1380
|
+
source: "oauth-usage-api",
|
|
1381
|
+
observedAt: new Date(now).toISOString(),
|
|
1382
|
+
expiresAt: new Date(now + GROK_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1383
|
+
windows: [
|
|
1384
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" },
|
|
1385
|
+
{ id: "thirty-day", label: "30 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
1386
|
+
],
|
|
1387
|
+
lastErrorCode: code
|
|
1388
|
+
};
|
|
1389
|
+
this.store.set(snapshot);
|
|
1390
|
+
return snapshot;
|
|
1391
|
+
}
|
|
1392
|
+
unsupportedSnapshot(accountId, now) {
|
|
1393
|
+
return {
|
|
1394
|
+
providerId: "grok",
|
|
1395
|
+
accountId,
|
|
1396
|
+
source: "oauth-usage-api",
|
|
1397
|
+
observedAt: new Date(now).toISOString(),
|
|
1398
|
+
windows: [
|
|
1399
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" },
|
|
1400
|
+
{ id: "thirty-day", label: "30 days", scope: "all", usedPercent: null, state: "unsupported" }
|
|
1401
|
+
],
|
|
1402
|
+
lastErrorCode: "grok_usage_unsupported_auth"
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
|
|
1407
|
+
// src/allowance/CopilotAllowanceCollector.ts
|
|
1408
|
+
import {
|
|
1409
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore5
|
|
1410
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
1411
|
+
import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
1412
|
+
import { COPILOT_GITHUB_HEADERS, copilotGitHubApiBase } from "@omnicross/subscriptions";
|
|
1413
|
+
var COPILOT_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
1414
|
+
function isRecord3(value) {
|
|
1415
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1416
|
+
}
|
|
1417
|
+
function finiteNumber4(value) {
|
|
1418
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
1419
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1420
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
1421
|
+
}
|
|
1422
|
+
function booleanValue(value) {
|
|
1423
|
+
if (typeof value === "boolean") return value;
|
|
1424
|
+
if (value === "true") return true;
|
|
1425
|
+
if (value === "false") return false;
|
|
1426
|
+
return void 0;
|
|
1427
|
+
}
|
|
1428
|
+
function parseQuotaDetail(value) {
|
|
1429
|
+
if (!isRecord3(value)) return null;
|
|
1430
|
+
const entitlement = finiteNumber4(value["entitlement"]);
|
|
1431
|
+
const remaining = finiteNumber4(value["remaining"]);
|
|
1432
|
+
const percentRemaining = finiteNumber4(value["percent_remaining"]);
|
|
1433
|
+
const unlimited = booleanValue(value["unlimited"]);
|
|
1434
|
+
if (entitlement === void 0 || remaining === void 0 || percentRemaining === void 0 || unlimited === void 0) {
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1437
|
+
return { entitlement, remaining, percentRemaining, unlimited };
|
|
1438
|
+
}
|
|
1439
|
+
function secondsUntil5(instant, now) {
|
|
1440
|
+
if (!instant) return void 0;
|
|
1441
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
1442
|
+
}
|
|
1443
|
+
function parseCopilotUserPayload(payload, now) {
|
|
1444
|
+
if (!isRecord3(payload)) return null;
|
|
1445
|
+
const snapshots = isRecord3(payload["quota_snapshots"]) ? payload["quota_snapshots"] : void 0;
|
|
1446
|
+
if (!snapshots) return null;
|
|
1447
|
+
const resetRaw = payload["quota_reset_date"];
|
|
1448
|
+
const resetsAt = typeof resetRaw === "string" && resetRaw.trim() && Number.isFinite(Date.parse(resetRaw)) ? new Date(Date.parse(resetRaw)).toISOString() : void 0;
|
|
1449
|
+
const windows = [];
|
|
1450
|
+
const premium = parseQuotaDetail(snapshots["premium_interactions"]);
|
|
1451
|
+
if (premium) {
|
|
1452
|
+
const usedPercent = premium.unlimited ? 0 : premium.entitlement > 0 ? Math.round(Math.min(100, (premium.entitlement - premium.remaining) / premium.entitlement * 100) * 10) / 10 : finiteNumber4(premium.percentRemaining) !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - premium.percentRemaining)) * 10) / 10 : null;
|
|
1453
|
+
if (usedPercent !== null) {
|
|
1454
|
+
windows.push({
|
|
1455
|
+
id: "thirty-day",
|
|
1456
|
+
label: "Monthly",
|
|
1457
|
+
scope: "all",
|
|
1458
|
+
usedPercent,
|
|
1459
|
+
windowMinutes: 30 * 24 * 60,
|
|
1460
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
1461
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
1462
|
+
state: "fresh"
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
const chat = parseQuotaDetail(snapshots["chat"]);
|
|
1467
|
+
if (chat && !chat.unlimited && chat.entitlement > 0) {
|
|
1468
|
+
const usedPercent = Math.round(Math.min(100, (chat.entitlement - chat.remaining) / chat.entitlement * 100) * 10) / 10;
|
|
1469
|
+
windows.push({
|
|
1470
|
+
id: "chat-monthly",
|
|
1471
|
+
label: "Chat (monthly)",
|
|
1472
|
+
scope: "all",
|
|
1473
|
+
usedPercent,
|
|
1474
|
+
windowMinutes: 30 * 24 * 60,
|
|
1475
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
1476
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
1477
|
+
state: "fresh"
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
return windows.length > 0 ? windows : null;
|
|
236
1481
|
}
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
return Object.keys(headers).some((key) => key.toLowerCase() === wanted);
|
|
1482
|
+
function githubApiBase(tokens) {
|
|
1483
|
+
return copilotGitHubApiBase(tokens.enterpriseUrl);
|
|
240
1484
|
}
|
|
241
|
-
var
|
|
242
|
-
constructor(credentials, store =
|
|
1485
|
+
var CopilotAllowanceCollector = class {
|
|
1486
|
+
constructor(credentials, store = getSharedAccountAllowanceStore5(), fetchImpl = (url, init, accountId) => fetchUpstream5(url, init, { providerId: "copilot", accountId, redactBodies: true }), now = Date.now) {
|
|
243
1487
|
this.credentials = credentials;
|
|
244
1488
|
this.store = store;
|
|
245
1489
|
this.fetchImpl = fetchImpl;
|
|
246
|
-
this.identityStore = identityStore;
|
|
247
1490
|
this.now = now;
|
|
248
1491
|
}
|
|
249
1492
|
credentials;
|
|
250
1493
|
store;
|
|
251
1494
|
fetchImpl;
|
|
252
|
-
identityStore;
|
|
253
1495
|
now;
|
|
254
1496
|
inFlight = /* @__PURE__ */ new Map();
|
|
255
1497
|
async collectMany(accounts, options = {}) {
|
|
256
|
-
const settled = await Promise.allSettled(
|
|
1498
|
+
const settled = await Promise.allSettled(
|
|
1499
|
+
accounts.map((account) => this.collect(account, options))
|
|
1500
|
+
);
|
|
257
1501
|
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
258
1502
|
}
|
|
259
1503
|
collect(account, options = {}) {
|
|
260
1504
|
const now = this.now();
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
1505
|
+
if (account.tokens.authMethod !== "oauth") {
|
|
1506
|
+
const existing = this.store.get("copilot", account.id, now);
|
|
1507
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
1508
|
+
return Promise.resolve(existing);
|
|
1509
|
+
}
|
|
265
1510
|
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
266
1511
|
this.store.set(snapshot);
|
|
267
1512
|
return Promise.resolve(snapshot);
|
|
268
1513
|
}
|
|
269
|
-
const cached = this.store.get("
|
|
270
|
-
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs))
|
|
1514
|
+
const cached = this.store.get("copilot", account.id, now);
|
|
1515
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
1516
|
+
return Promise.resolve(cached);
|
|
1517
|
+
}
|
|
271
1518
|
const running = this.inFlight.get(account.id);
|
|
272
1519
|
if (running) return running;
|
|
273
|
-
const promise = this.fetchAccount(account.id).catch(() => this.failureSnapshot(account.id, "
|
|
1520
|
+
const promise = this.fetchAccount(account.id, account.tokens).catch(() => this.failureSnapshot(account.id, "copilot_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
|
|
274
1521
|
this.inFlight.set(account.id, promise);
|
|
275
1522
|
return promise;
|
|
276
1523
|
}
|
|
277
1524
|
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
278
|
-
if (snapshot.source !== "oauth-usage-api") return false;
|
|
279
1525
|
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
280
1526
|
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
281
1527
|
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
282
1528
|
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
283
1529
|
}
|
|
284
|
-
async fetchAccount(accountId) {
|
|
285
|
-
let
|
|
286
|
-
if (!
|
|
287
|
-
let response = await this.request(accountId,
|
|
288
|
-
if (response.status === 401) {
|
|
289
|
-
const refreshed = await this.credentials.refreshAccountToken("
|
|
290
|
-
if (!refreshed) return this.failureSnapshot(accountId, "
|
|
291
|
-
|
|
292
|
-
if (!
|
|
293
|
-
response = await this.request(accountId,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
this.store.set(snapshot2);
|
|
298
|
-
return snapshot2;
|
|
299
|
-
}
|
|
300
|
-
if (!response.ok) {
|
|
301
|
-
return this.failureSnapshot(accountId, "claude_usage_http_error", this.now());
|
|
1530
|
+
async fetchAccount(accountId, tokens) {
|
|
1531
|
+
let accessToken = await this.credentials.getAccessTokenForAccount("copilot", accountId);
|
|
1532
|
+
if (!accessToken) return this.failureSnapshot(accountId, "copilot_usage_token_unavailable", this.now());
|
|
1533
|
+
let response = await this.request(accountId, accessToken, tokens);
|
|
1534
|
+
if (response.status === 401 || response.status === 403) {
|
|
1535
|
+
const refreshed = await this.credentials.refreshAccountToken("copilot", accountId);
|
|
1536
|
+
if (!refreshed) return this.failureSnapshot(accountId, "copilot_usage_unauthorized", this.now());
|
|
1537
|
+
accessToken = await this.credentials.getAccessTokenForAccount("copilot", accountId);
|
|
1538
|
+
if (!accessToken) return this.failureSnapshot(accountId, "copilot_usage_token_unavailable", this.now());
|
|
1539
|
+
response = await this.request(accountId, accessToken, tokens);
|
|
1540
|
+
if (response.status === 401 || response.status === 403) {
|
|
1541
|
+
return this.failureSnapshot(accountId, "copilot_usage_unauthorized", this.now());
|
|
1542
|
+
}
|
|
302
1543
|
}
|
|
1544
|
+
if (!response.ok) return this.failureSnapshot(accountId, "copilot_usage_http_error", this.now());
|
|
303
1545
|
let payload;
|
|
304
1546
|
try {
|
|
305
1547
|
payload = await response.json();
|
|
306
1548
|
} catch {
|
|
307
|
-
return this.failureSnapshot(accountId, "
|
|
308
|
-
}
|
|
309
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
310
|
-
return this.failureSnapshot(accountId, "claude_usage_invalid_response", this.now());
|
|
1549
|
+
return this.failureSnapshot(accountId, "copilot_usage_invalid_response", this.now());
|
|
311
1550
|
}
|
|
312
1551
|
const now = this.now();
|
|
313
|
-
const
|
|
1552
|
+
const windows = parseCopilotUserPayload(payload, now);
|
|
314
1553
|
const snapshot = {
|
|
315
|
-
providerId: "
|
|
1554
|
+
providerId: "copilot",
|
|
316
1555
|
accountId,
|
|
317
1556
|
source: "oauth-usage-api",
|
|
318
1557
|
observedAt: new Date(now).toISOString(),
|
|
319
|
-
expiresAt: new Date(now +
|
|
320
|
-
windows: [
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
]
|
|
1558
|
+
expiresAt: new Date(now + COPILOT_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1559
|
+
windows: windows ?? [
|
|
1560
|
+
{ id: "thirty-day", label: "Monthly", scope: "all", usedPercent: null, state: "unavailable" }
|
|
1561
|
+
],
|
|
1562
|
+
...windows ? {} : { lastErrorCode: "copilot_usage_invalid_response" }
|
|
325
1563
|
};
|
|
326
1564
|
this.store.set(snapshot);
|
|
327
1565
|
return snapshot;
|
|
328
1566
|
}
|
|
329
|
-
request(accountId,
|
|
330
|
-
|
|
331
|
-
Authorization: `Bearer ${token}`,
|
|
332
|
-
Accept: "application/json",
|
|
333
|
-
"Content-Type": "application/json",
|
|
334
|
-
"anthropic-beta": "oauth-2025-04-20",
|
|
335
|
-
"Accept-Language": "en-US,en;q=0.9"
|
|
336
|
-
};
|
|
337
|
-
applyFingerprint(this.identityStore, headers, "claude", accountId, void 0);
|
|
338
|
-
if (!hasHeader(headers, "user-agent")) {
|
|
339
|
-
headers["User-Agent"] = "claude-cli/2.0.53 (external, cli)";
|
|
340
|
-
}
|
|
341
|
-
return this.fetchImpl(CLAUDE_USAGE_URL, {
|
|
1567
|
+
request(accountId, accessToken, tokens) {
|
|
1568
|
+
return this.fetchImpl(`${githubApiBase(tokens)}/copilot_internal/user`, {
|
|
342
1569
|
method: "GET",
|
|
343
|
-
headers
|
|
1570
|
+
headers: {
|
|
1571
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1572
|
+
Accept: "application/json",
|
|
1573
|
+
"Content-Type": "application/json",
|
|
1574
|
+
...COPILOT_GITHUB_HEADERS
|
|
1575
|
+
},
|
|
344
1576
|
signal: AbortSignal.timeout(15e3)
|
|
345
1577
|
}, accountId);
|
|
346
1578
|
}
|
|
347
1579
|
failureSnapshot(accountId, code, now) {
|
|
348
|
-
const existing = this.store.get("
|
|
1580
|
+
const existing = this.store.get("copilot", accountId, now);
|
|
349
1581
|
const snapshot = existing ? {
|
|
350
1582
|
...existing,
|
|
351
|
-
expiresAt: new Date(now +
|
|
1583
|
+
expiresAt: new Date(now + COPILOT_ALLOWANCE_CACHE_MS).toISOString(),
|
|
352
1584
|
windows: existing.windows.map((window) => ({
|
|
353
1585
|
...window,
|
|
354
1586
|
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
355
1587
|
})),
|
|
356
1588
|
lastErrorCode: code
|
|
357
1589
|
} : {
|
|
358
|
-
providerId: "
|
|
1590
|
+
providerId: "copilot",
|
|
359
1591
|
accountId,
|
|
360
1592
|
source: "oauth-usage-api",
|
|
361
1593
|
observedAt: new Date(now).toISOString(),
|
|
362
|
-
expiresAt: new Date(now +
|
|
363
|
-
windows:
|
|
1594
|
+
expiresAt: new Date(now + COPILOT_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1595
|
+
windows: [
|
|
1596
|
+
{ id: "thirty-day", label: "Monthly", scope: "all", usedPercent: null, state: "unavailable" }
|
|
1597
|
+
],
|
|
364
1598
|
lastErrorCode: code
|
|
365
1599
|
};
|
|
366
1600
|
this.store.set(snapshot);
|
|
367
1601
|
return snapshot;
|
|
368
1602
|
}
|
|
369
|
-
unsupportedSnapshot(accountId, now
|
|
1603
|
+
unsupportedSnapshot(accountId, now) {
|
|
370
1604
|
return {
|
|
371
|
-
providerId: "
|
|
1605
|
+
providerId: "copilot",
|
|
372
1606
|
accountId,
|
|
373
1607
|
source: "oauth-usage-api",
|
|
374
1608
|
observedAt: new Date(now).toISOString(),
|
|
375
|
-
windows:
|
|
1609
|
+
windows: [
|
|
1610
|
+
{ id: "thirty-day", label: "Monthly", scope: "all", usedPercent: null, state: "unsupported" }
|
|
1611
|
+
],
|
|
1612
|
+
lastErrorCode: "copilot_usage_unsupported_auth"
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
|
|
1617
|
+
// src/allowance/OpenCodeGoAllowanceCollector.ts
|
|
1618
|
+
import {
|
|
1619
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore6
|
|
1620
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
1621
|
+
import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
1622
|
+
import { normalizeOpenCodeGoBaseUrl } from "@omnicross/subscriptions";
|
|
1623
|
+
var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
1624
|
+
var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
|
|
1625
|
+
function finitePercent3(value) {
|
|
1626
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
1627
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1628
|
+
return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
|
|
1629
|
+
}
|
|
1630
|
+
function isoInstant2(value) {
|
|
1631
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
1632
|
+
const time = Date.parse(value);
|
|
1633
|
+
return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
|
|
1634
|
+
}
|
|
1635
|
+
function secondsUntil6(instant, now) {
|
|
1636
|
+
if (!instant) return void 0;
|
|
1637
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
1638
|
+
}
|
|
1639
|
+
function windowFromPayload3(id, label, minutes, payload, now) {
|
|
1640
|
+
const statusRateLimited = payload?.status === "rate-limited";
|
|
1641
|
+
const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
|
|
1642
|
+
const resetsAt = isoInstant2(payload?.resetsAt);
|
|
1643
|
+
return {
|
|
1644
|
+
id,
|
|
1645
|
+
label,
|
|
1646
|
+
scope: "all",
|
|
1647
|
+
usedPercent,
|
|
1648
|
+
windowMinutes: minutes,
|
|
1649
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
1650
|
+
remainingSeconds: secondsUntil6(resetsAt, now),
|
|
1651
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
1652
|
+
};
|
|
1653
|
+
}
|
|
1654
|
+
var OpenCodeGoAllowanceCollector = class {
|
|
1655
|
+
constructor(credentials, store = getSharedAccountAllowanceStore6(), fetchImpl = (url, init, accountId) => fetchUpstream6(url, init, { providerId: "opencodego", accountId, redactBodies: true }), now = Date.now) {
|
|
1656
|
+
this.credentials = credentials;
|
|
1657
|
+
this.store = store;
|
|
1658
|
+
this.fetchImpl = fetchImpl;
|
|
1659
|
+
this.now = now;
|
|
1660
|
+
}
|
|
1661
|
+
credentials;
|
|
1662
|
+
store;
|
|
1663
|
+
fetchImpl;
|
|
1664
|
+
now;
|
|
1665
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1666
|
+
async collectMany(accounts, options = {}) {
|
|
1667
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
1668
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
1669
|
+
}
|
|
1670
|
+
collect(account, options = {}) {
|
|
1671
|
+
const now = this.now();
|
|
1672
|
+
const cached = this.store.get("opencodego", account.id, now);
|
|
1673
|
+
if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
|
|
1674
|
+
return Promise.resolve(cached);
|
|
1675
|
+
}
|
|
1676
|
+
const running = this.inFlight.get(account.id);
|
|
1677
|
+
if (running) return running;
|
|
1678
|
+
const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
|
|
1679
|
+
this.inFlight.set(account.id, promise);
|
|
1680
|
+
return promise;
|
|
1681
|
+
}
|
|
1682
|
+
async fetchAccount(account) {
|
|
1683
|
+
const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
|
|
1684
|
+
if (!apiKey) return this.failureSnapshot(account.id, this.now());
|
|
1685
|
+
const base = account.tokens.baseUrl ? normalizeOpenCodeGoBaseUrl(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
|
|
1686
|
+
const response = await this.fetchImpl(`${base}/v1/usage`, {
|
|
1687
|
+
method: "GET",
|
|
1688
|
+
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
1689
|
+
signal: AbortSignal.timeout(15e3)
|
|
1690
|
+
}, account.id);
|
|
1691
|
+
if (response.status === 401 || response.status === 403) {
|
|
1692
|
+
return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
|
|
1693
|
+
}
|
|
1694
|
+
if (!response.ok) return this.failureSnapshot(account.id, this.now());
|
|
1695
|
+
let payload;
|
|
1696
|
+
try {
|
|
1697
|
+
payload = await response.json();
|
|
1698
|
+
} catch {
|
|
1699
|
+
return this.failureSnapshot(account.id, this.now());
|
|
1700
|
+
}
|
|
1701
|
+
const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
|
|
1702
|
+
const now = this.now();
|
|
1703
|
+
const snapshot = {
|
|
1704
|
+
providerId: "opencodego",
|
|
1705
|
+
accountId: account.id,
|
|
1706
|
+
source: "oauth-usage-api",
|
|
1707
|
+
observedAt: new Date(now).toISOString(),
|
|
1708
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1709
|
+
// Monthly deliberately omitted (module doc).
|
|
1710
|
+
windows: [
|
|
1711
|
+
windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
|
|
1712
|
+
windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
|
|
1713
|
+
]
|
|
1714
|
+
};
|
|
1715
|
+
this.store.set(snapshot);
|
|
1716
|
+
return snapshot;
|
|
1717
|
+
}
|
|
1718
|
+
failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
|
|
1719
|
+
const existing = this.store.get("opencodego", accountId, now);
|
|
1720
|
+
const snapshot = existing ? {
|
|
1721
|
+
...existing,
|
|
1722
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1723
|
+
windows: existing.windows.map((window) => ({
|
|
1724
|
+
...window,
|
|
1725
|
+
state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
|
|
1726
|
+
})),
|
|
1727
|
+
lastErrorCode: code
|
|
1728
|
+
} : {
|
|
1729
|
+
providerId: "opencodego",
|
|
1730
|
+
accountId,
|
|
1731
|
+
source: "oauth-usage-api",
|
|
1732
|
+
observedAt: new Date(now).toISOString(),
|
|
1733
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1734
|
+
windows: [
|
|
1735
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
1736
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
1737
|
+
],
|
|
376
1738
|
lastErrorCode: code
|
|
377
1739
|
};
|
|
1740
|
+
this.store.set(snapshot);
|
|
1741
|
+
return snapshot;
|
|
378
1742
|
}
|
|
379
1743
|
};
|
|
380
1744
|
|
|
@@ -393,26 +1757,34 @@ function codexUnavailable(accountId, now) {
|
|
|
393
1757
|
};
|
|
394
1758
|
}
|
|
395
1759
|
var AccountAllowanceService = class {
|
|
396
|
-
constructor(credentials, store =
|
|
1760
|
+
constructor(credentials, store = getSharedAccountAllowanceStore7(), collector, codexCollector, kimiCollector, opencodegoCollector, grokCollector, copilotCollector, now = Date.now) {
|
|
397
1761
|
this.credentials = credentials;
|
|
398
1762
|
this.store = store;
|
|
399
1763
|
this.now = now;
|
|
400
1764
|
this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
|
|
1765
|
+
this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
|
|
1766
|
+
this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
|
|
1767
|
+
this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
|
|
1768
|
+
this.grokCollector = grokCollector ?? new GrokAllowanceCollector(credentials, store);
|
|
1769
|
+
this.copilotCollector = copilotCollector ?? new CopilotAllowanceCollector(credentials, store);
|
|
401
1770
|
}
|
|
402
1771
|
credentials;
|
|
403
1772
|
store;
|
|
404
1773
|
now;
|
|
405
1774
|
claudeCollector;
|
|
1775
|
+
codexCollector;
|
|
1776
|
+
kimiCollector;
|
|
1777
|
+
grokCollector;
|
|
1778
|
+
copilotCollector;
|
|
1779
|
+
opencodegoCollector;
|
|
406
1780
|
/**
|
|
407
|
-
* Read all/filtered snapshots. Claude's five-minute
|
|
408
|
-
*
|
|
1781
|
+
* Read all/filtered snapshots. Claude's and Codex's five-minute caches are
|
|
1782
|
+
* refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
|
|
1783
|
+
* passive `x-codex-*` header tap still feeds mid-flight updates).
|
|
409
1784
|
*/
|
|
410
1785
|
async list(filter = {}) {
|
|
411
1786
|
const config = await this.credentials.getFullConfig();
|
|
412
|
-
this.store.pruneToKnownAccounts(
|
|
413
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
414
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
415
|
-
]);
|
|
1787
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
416
1788
|
const wantsClaude = !filter.providerId || filter.providerId === "claude";
|
|
417
1789
|
const claudeAccounts = (config.claudeAccounts ?? []).filter(
|
|
418
1790
|
(account) => !filter.accountId || account.id === filter.accountId
|
|
@@ -423,39 +1795,124 @@ var AccountAllowanceService = class {
|
|
|
423
1795
|
(account) => !filter.accountId || account.id === filter.accountId
|
|
424
1796
|
);
|
|
425
1797
|
if (wantsCodex) {
|
|
1798
|
+
await this.codexCollector.collectMany(codexAccounts);
|
|
426
1799
|
for (const account of codexAccounts) {
|
|
427
1800
|
if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
|
|
428
1801
|
}
|
|
429
1802
|
}
|
|
1803
|
+
const wantsKimi = !filter.providerId || filter.providerId === "kimi";
|
|
1804
|
+
const kimiAccounts = (config.kimiAccounts ?? []).filter(
|
|
1805
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
1806
|
+
);
|
|
1807
|
+
if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
|
|
1808
|
+
const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
|
|
1809
|
+
const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
|
|
1810
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
1811
|
+
);
|
|
1812
|
+
if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
|
|
1813
|
+
const wantsGrok = !filter.providerId || filter.providerId === "grok";
|
|
1814
|
+
const grokAccounts = (config.grokAccounts ?? []).filter(
|
|
1815
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
1816
|
+
);
|
|
1817
|
+
if (wantsGrok) await this.grokCollector.collectMany(grokAccounts);
|
|
1818
|
+
const wantsCopilot = !filter.providerId || filter.providerId === "copilot";
|
|
1819
|
+
const copilotAccounts = (config.copilotAccounts ?? []).filter(
|
|
1820
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
1821
|
+
);
|
|
1822
|
+
if (wantsCopilot) await this.copilotCollector.collectMany(copilotAccounts);
|
|
430
1823
|
const known = /* @__PURE__ */ new Set();
|
|
431
1824
|
if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
|
|
432
1825
|
if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
|
|
1826
|
+
if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
|
|
1827
|
+
if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
|
|
1828
|
+
if (wantsGrok) for (const account of grokAccounts) known.add(`grok\0${account.id}`);
|
|
1829
|
+
if (wantsCopilot) for (const account of copilotAccounts) known.add(`copilot\0${account.id}`);
|
|
433
1830
|
return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
|
|
434
1831
|
}
|
|
1832
|
+
knownAccounts(config) {
|
|
1833
|
+
return [
|
|
1834
|
+
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
1835
|
+
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
|
|
1836
|
+
...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
|
|
1837
|
+
...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id })),
|
|
1838
|
+
...(config.grokAccounts ?? []).map((account) => ({ providerId: "grok", accountId: account.id })),
|
|
1839
|
+
...(config.copilotAccounts ?? []).map((account) => ({ providerId: "copilot", accountId: account.id }))
|
|
1840
|
+
];
|
|
1841
|
+
}
|
|
435
1842
|
/** Force-refresh Claude usage for one account or every stored Claude account. */
|
|
436
1843
|
async refreshClaude(accountId) {
|
|
437
1844
|
const config = await this.credentials.getFullConfig();
|
|
438
|
-
this.store.pruneToKnownAccounts(
|
|
439
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
440
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
441
|
-
]);
|
|
1845
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
442
1846
|
const accounts = (config.claudeAccounts ?? []).filter(
|
|
443
1847
|
(account) => !accountId || account.id === accountId
|
|
444
1848
|
);
|
|
445
1849
|
return this.claudeCollector.collectMany(accounts, { force: true });
|
|
446
1850
|
}
|
|
447
1851
|
/**
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
1852
|
+
* Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
|
|
1853
|
+
* every stored Codex account. Replaces the old probe-request workaround —
|
|
1854
|
+
* no quota is spent reading the usage endpoint.
|
|
1855
|
+
*/
|
|
1856
|
+
async refreshCodex(accountId) {
|
|
1857
|
+
const config = await this.credentials.getFullConfig();
|
|
1858
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1859
|
+
const accounts = (config.codexAccounts ?? []).filter(
|
|
1860
|
+
(account) => !accountId || account.id === accountId
|
|
1861
|
+
);
|
|
1862
|
+
return this.codexCollector.collectMany(accounts, { force: true });
|
|
1863
|
+
}
|
|
1864
|
+
/** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
|
|
1865
|
+
async refreshOpenCodeGo(accountId) {
|
|
1866
|
+
const config = await this.credentials.getFullConfig();
|
|
1867
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1868
|
+
const accounts = (config.opencodegoAccounts ?? []).filter(
|
|
1869
|
+
(account) => !accountId || account.id === accountId
|
|
1870
|
+
);
|
|
1871
|
+
return this.opencodegoCollector.collectMany(accounts, { force: true });
|
|
1872
|
+
}
|
|
1873
|
+
/** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
|
|
1874
|
+
async refreshKimi(accountId) {
|
|
1875
|
+
const config = await this.credentials.getFullConfig();
|
|
1876
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1877
|
+
const accounts = (config.kimiAccounts ?? []).filter(
|
|
1878
|
+
(account) => !accountId || account.id === accountId
|
|
1879
|
+
);
|
|
1880
|
+
return this.kimiCollector.collectMany(accounts, { force: true });
|
|
1881
|
+
}
|
|
1882
|
+
/** Force-refresh Copilot usage (copilot_internal/user) for one/all accounts. */
|
|
1883
|
+
async refreshCopilot(accountId) {
|
|
1884
|
+
const config = await this.credentials.getFullConfig();
|
|
1885
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1886
|
+
const accounts = (config.copilotAccounts ?? []).filter(
|
|
1887
|
+
(account) => !accountId || account.id === accountId
|
|
1888
|
+
);
|
|
1889
|
+
return this.copilotCollector.collectMany(accounts, { force: true });
|
|
1890
|
+
}
|
|
1891
|
+
/** Force-refresh Grok usage (CLI billing proxy) for one/all accounts. */
|
|
1892
|
+
async refreshGrok(accountId) {
|
|
1893
|
+
const config = await this.credentials.getFullConfig();
|
|
1894
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1895
|
+
const accounts = (config.grokAccounts ?? []).filter(
|
|
1896
|
+
(account) => !accountId || account.id === accountId
|
|
1897
|
+
);
|
|
1898
|
+
return this.grokCollector.collectMany(accounts, { force: true });
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
|
|
1902
|
+
* collectors preserve their cache + per-account in-flight coalescing; a tick
|
|
1903
|
+
* normally performs no network I/O. (Codex joined the warm path when it
|
|
1904
|
+
* gained an active `/wham/usage` collector — the passive `x-codex-*` header
|
|
1905
|
+
* tap alone could not keep the policy fed while idle.)
|
|
451
1906
|
*/
|
|
452
1907
|
async maintainClaudeCache(refreshAheadMs) {
|
|
453
1908
|
const config = await this.credentials.getFullConfig();
|
|
454
|
-
this.store.pruneToKnownAccounts(
|
|
455
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
456
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
457
|
-
]);
|
|
1909
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
458
1910
|
await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
|
|
1911
|
+
await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
|
|
1912
|
+
await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
|
|
1913
|
+
await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
|
|
1914
|
+
await this.grokCollector.collectMany(config.grokAccounts ?? [], { refreshAheadMs });
|
|
1915
|
+
await this.copilotCollector.collectMany(config.copilotAccounts ?? [], { refreshAheadMs });
|
|
459
1916
|
}
|
|
460
1917
|
/** Remove a cache row as soon as an account is deleted by the admin path. */
|
|
461
1918
|
removeAccountSnapshot(providerId, accountId) {
|
|
@@ -892,7 +2349,8 @@ import {
|
|
|
892
2349
|
} from "@omnicross/contracts/image-generation-types";
|
|
893
2350
|
import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling2 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
894
2351
|
import { getSharedAccountHealth } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
895
|
-
import { fetchUpstream as
|
|
2352
|
+
import { fetchUpstream as fetchUpstream7 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
2353
|
+
import { mergeExtraHeaders } from "@omnicross/core";
|
|
896
2354
|
|
|
897
2355
|
// src/image-generation/imagesConfigValidation.ts
|
|
898
2356
|
import { validateImagesServerConfig } from "@omnicross/core/outbound-api";
|
|
@@ -1205,6 +2663,7 @@ async function applyServerConfigTransaction(current, next, deps) {
|
|
|
1205
2663
|
|
|
1206
2664
|
// src/config.ts
|
|
1207
2665
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
2666
|
+
import { EXTRA_HEADER_RESERVED_NAMES } from "@omnicross/core";
|
|
1208
2667
|
|
|
1209
2668
|
// src/secrets/envelope.ts
|
|
1210
2669
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes2 } from "crypto";
|
|
@@ -1616,6 +3075,18 @@ var FORMAT_AXIS_TRANSFORMERS = [
|
|
|
1616
3075
|
"openai-response",
|
|
1617
3076
|
"gemini-code-assist"
|
|
1618
3077
|
];
|
|
3078
|
+
function validateExtraHeaders(raw) {
|
|
3079
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
3080
|
+
const reserved = EXTRA_HEADER_RESERVED_NAMES;
|
|
3081
|
+
const out = {};
|
|
3082
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
3083
|
+
if (!name.trim()) continue;
|
|
3084
|
+
if (typeof value !== "string") continue;
|
|
3085
|
+
if (reserved.has(name.toLowerCase())) continue;
|
|
3086
|
+
out[name] = value;
|
|
3087
|
+
}
|
|
3088
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
3089
|
+
}
|
|
1619
3090
|
function validateApiKeys(raw) {
|
|
1620
3091
|
if (!Array.isArray(raw)) return void 0;
|
|
1621
3092
|
const out = [];
|
|
@@ -1829,6 +3300,9 @@ function validateProvider(raw, index) {
|
|
|
1829
3300
|
apiVersion,
|
|
1830
3301
|
maxConcurrency,
|
|
1831
3302
|
modelsEndpoint,
|
|
3303
|
+
// Static extra headers: load-guard (reserved names dropped), collapse-to-
|
|
3304
|
+
// undefined; enforced by the outbound header funnel + admin probes.
|
|
3305
|
+
extraHeaders: validateExtraHeaders(p["extraHeaders"]),
|
|
1832
3306
|
// Provider transformer config (app-parity child 5): load-guard, collapse-to-
|
|
1833
3307
|
// undefined; non-secret; ENFORCED via resolveTransformerChain (parity-2 child 2).
|
|
1834
3308
|
// Format-axis entries are stripped by `migrateFormatAxis` — `use[]` is the
|
|
@@ -2798,7 +4272,8 @@ function listMappablePresets() {
|
|
|
2798
4272
|
description: preset.description,
|
|
2799
4273
|
features: preset.features,
|
|
2800
4274
|
website: preset.website,
|
|
2801
|
-
modelsEndpoint: preset.modelsEndpoint
|
|
4275
|
+
modelsEndpoint: preset.modelsEndpoint,
|
|
4276
|
+
extraHeaders: preset.extraHeaders
|
|
2802
4277
|
});
|
|
2803
4278
|
}
|
|
2804
4279
|
return { mappable, excluded };
|
|
@@ -2969,7 +4444,10 @@ var VALID_PROVIDER_IDS = [
|
|
|
2969
4444
|
"claude",
|
|
2970
4445
|
"codex",
|
|
2971
4446
|
"gemini",
|
|
2972
|
-
"opencodego"
|
|
4447
|
+
"opencodego",
|
|
4448
|
+
"kimi",
|
|
4449
|
+
"grok",
|
|
4450
|
+
"copilot"
|
|
2973
4451
|
];
|
|
2974
4452
|
function asSubscriptionProviderId(id) {
|
|
2975
4453
|
return VALID_PROVIDER_IDS.includes(id) ? id : null;
|
|
@@ -3093,7 +4571,43 @@ function validateCodex(body) {
|
|
|
3093
4571
|
]);
|
|
3094
4572
|
return out;
|
|
3095
4573
|
}
|
|
3096
|
-
function validateGemini(body) {
|
|
4574
|
+
function validateGemini(body) {
|
|
4575
|
+
const authMethod = str(body["authMethod"]);
|
|
4576
|
+
const status = str(body["status"]);
|
|
4577
|
+
if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
|
|
4578
|
+
if (!status || !TOKEN_STATUSES.has(status)) return null;
|
|
4579
|
+
const out = {
|
|
4580
|
+
authMethod,
|
|
4581
|
+
status
|
|
4582
|
+
};
|
|
4583
|
+
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
|
|
4584
|
+
return out;
|
|
4585
|
+
}
|
|
4586
|
+
function validateKimi(body) {
|
|
4587
|
+
const authMethod = str(body["authMethod"]);
|
|
4588
|
+
const status = str(body["status"]);
|
|
4589
|
+
if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
|
|
4590
|
+
if (!status || !TOKEN_STATUSES.has(status)) return null;
|
|
4591
|
+
const out = {
|
|
4592
|
+
authMethod,
|
|
4593
|
+
status
|
|
4594
|
+
};
|
|
4595
|
+
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
|
|
4596
|
+
return out;
|
|
4597
|
+
}
|
|
4598
|
+
function validateGrok(body) {
|
|
4599
|
+
const authMethod = str(body["authMethod"]);
|
|
4600
|
+
const status = str(body["status"]);
|
|
4601
|
+
if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
|
|
4602
|
+
if (!status || !TOKEN_STATUSES.has(status)) return null;
|
|
4603
|
+
const out = {
|
|
4604
|
+
authMethod,
|
|
4605
|
+
status
|
|
4606
|
+
};
|
|
4607
|
+
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "lastRefreshedAt", "errorMessage"]);
|
|
4608
|
+
return out;
|
|
4609
|
+
}
|
|
4610
|
+
function validateCopilot(body) {
|
|
3097
4611
|
const authMethod = str(body["authMethod"]);
|
|
3098
4612
|
const status = str(body["status"]);
|
|
3099
4613
|
if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
|
|
@@ -3102,7 +4616,17 @@ function validateGemini(body) {
|
|
|
3102
4616
|
authMethod,
|
|
3103
4617
|
status
|
|
3104
4618
|
};
|
|
3105
|
-
copyOptional(out, body, [
|
|
4619
|
+
copyOptional(out, body, [
|
|
4620
|
+
"accessToken",
|
|
4621
|
+
"refreshToken",
|
|
4622
|
+
"expiresAt",
|
|
4623
|
+
"accountId",
|
|
4624
|
+
"email",
|
|
4625
|
+
"apiEndpoint",
|
|
4626
|
+
"enterpriseUrl",
|
|
4627
|
+
"lastRefreshedAt",
|
|
4628
|
+
"errorMessage"
|
|
4629
|
+
]);
|
|
3106
4630
|
return out;
|
|
3107
4631
|
}
|
|
3108
4632
|
function validateOpenCodeGo(body) {
|
|
@@ -3140,6 +4664,12 @@ function validateTokenBody(providerId, body) {
|
|
|
3140
4664
|
return validateGemini(body);
|
|
3141
4665
|
case "opencodego":
|
|
3142
4666
|
return validateOpenCodeGo(body);
|
|
4667
|
+
case "kimi":
|
|
4668
|
+
return validateKimi(body);
|
|
4669
|
+
case "grok":
|
|
4670
|
+
return validateGrok(body);
|
|
4671
|
+
case "copilot":
|
|
4672
|
+
return validateCopilot(body);
|
|
3143
4673
|
default:
|
|
3144
4674
|
return null;
|
|
3145
4675
|
}
|
|
@@ -3169,12 +4699,12 @@ async function statusEntryFor(reader, providerId) {
|
|
|
3169
4699
|
|
|
3170
4700
|
// src/admin/accountsOAuth.ts
|
|
3171
4701
|
var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
|
|
3172
|
-
function
|
|
4702
|
+
function err5(status, message) {
|
|
3173
4703
|
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
3174
4704
|
}
|
|
3175
4705
|
function handleOAuthStart(providerId, deps) {
|
|
3176
4706
|
if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
|
|
3177
|
-
return
|
|
4707
|
+
return err5(400, `oauth not available for provider '${providerId}'`);
|
|
3178
4708
|
}
|
|
3179
4709
|
const flow = providerId === "claude" ? claudeOAuth : geminiOAuth;
|
|
3180
4710
|
const { authUrl, codeVerifier, state } = flow.generateAuthParams();
|
|
@@ -3183,23 +4713,23 @@ function handleOAuthStart(providerId, deps) {
|
|
|
3183
4713
|
}
|
|
3184
4714
|
async function handleOAuthComplete(providerId, body, deps) {
|
|
3185
4715
|
if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
|
|
3186
|
-
return
|
|
4716
|
+
return err5(400, `oauth not available for provider '${providerId}'`);
|
|
3187
4717
|
}
|
|
3188
4718
|
const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
|
|
3189
4719
|
const rawCode = typeof body["code"] === "string" ? body["code"] : "";
|
|
3190
|
-
if (!sessionId) return
|
|
3191
|
-
if (!rawCode) return
|
|
4720
|
+
if (!sessionId) return err5(400, "oauth complete requires { sessionId }");
|
|
4721
|
+
if (!rawCode) return err5(400, "oauth complete requires { code }");
|
|
3192
4722
|
const session = deps.oauthSessions.peek(sessionId);
|
|
3193
|
-
if (!session) return
|
|
4723
|
+
if (!session) return err5(410, "oauth session is unknown, expired, or already used");
|
|
3194
4724
|
if (session.providerId !== providerId) {
|
|
3195
|
-
return
|
|
4725
|
+
return err5(400, `oauth session does not match provider '${providerId}'`);
|
|
3196
4726
|
}
|
|
3197
4727
|
let code = rawCode.trim();
|
|
3198
4728
|
if (providerId === "claude") {
|
|
3199
4729
|
const [splitCode, pastedState] = code.split("#");
|
|
3200
|
-
if (!splitCode) return
|
|
4730
|
+
if (!splitCode) return err5(400, "no authorization code was provided");
|
|
3201
4731
|
if (pastedState && pastedState !== session.state) {
|
|
3202
|
-
return
|
|
4732
|
+
return err5(400, "oauth state did not match (possible CSRF) \u2014 aborting");
|
|
3203
4733
|
}
|
|
3204
4734
|
code = splitCode;
|
|
3205
4735
|
}
|
|
@@ -3209,7 +4739,7 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
3209
4739
|
block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
|
|
3210
4740
|
} catch (exchangeError) {
|
|
3211
4741
|
const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
|
|
3212
|
-
return
|
|
4742
|
+
return err5(502, `oauth token exchange failed for '${providerId}': ${reason}`);
|
|
3213
4743
|
}
|
|
3214
4744
|
deps.oauthSessions.consume(sessionId);
|
|
3215
4745
|
const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
|
|
@@ -3547,8 +5077,8 @@ function errBody(message) {
|
|
|
3547
5077
|
return { error: { type: "admin_api_error", message } };
|
|
3548
5078
|
}
|
|
3549
5079
|
var defaultCommandRunner = (command) => new Promise((resolve10) => {
|
|
3550
|
-
exec(command, { timeout: 18e4 }, (
|
|
3551
|
-
if (
|
|
5080
|
+
exec(command, { timeout: 18e4 }, (err8, _stdout, stderr) => {
|
|
5081
|
+
if (err8) resolve10({ ok: false, error: stderr.trim() || err8.message });
|
|
3552
5082
|
else resolve10({ ok: true });
|
|
3553
5083
|
});
|
|
3554
5084
|
});
|
|
@@ -3594,8 +5124,8 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
3594
5124
|
providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
|
|
3595
5125
|
model: typeof body["model"] === "string" ? body["model"] : void 0
|
|
3596
5126
|
});
|
|
3597
|
-
} catch (
|
|
3598
|
-
return { status: 400, body: errBody(
|
|
5127
|
+
} catch (err8) {
|
|
5128
|
+
return { status: 400, body: errBody(err8 instanceof Error ? err8.message : "no launch target") };
|
|
3599
5129
|
}
|
|
3600
5130
|
const id = randomUUID2();
|
|
3601
5131
|
let leaseId2;
|
|
@@ -3623,9 +5153,9 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
3623
5153
|
} else {
|
|
3624
5154
|
launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
|
|
3625
5155
|
}
|
|
3626
|
-
} catch (
|
|
3627
|
-
const status =
|
|
3628
|
-
return { status, body: errBody(
|
|
5156
|
+
} catch (err8) {
|
|
5157
|
+
const status = err8 instanceof RouteLeaseError2 ? err8.status : 400;
|
|
5158
|
+
return { status, body: errBody(err8 instanceof Error ? err8.message : "failed to build launch env") };
|
|
3629
5159
|
}
|
|
3630
5160
|
const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
|
|
3631
5161
|
const opener = ctx.opener ?? defaultTerminalOpener;
|
|
@@ -3653,9 +5183,9 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
3653
5183
|
onFailure: onSessionEnd
|
|
3654
5184
|
});
|
|
3655
5185
|
if (cleanup) openerCleanup = cleanup;
|
|
3656
|
-
} catch (
|
|
5186
|
+
} catch (err8) {
|
|
3657
5187
|
onSessionEnd();
|
|
3658
|
-
return { status: 500, body: errBody(
|
|
5188
|
+
return { status: 500, body: errBody(err8 instanceof Error ? err8.message : "failed to open terminal") };
|
|
3659
5189
|
}
|
|
3660
5190
|
if (ended) {
|
|
3661
5191
|
openerCleanup?.();
|
|
@@ -4204,7 +5734,7 @@ async function handleSearchQuery(req, res, deps) {
|
|
|
4204
5734
|
// src/admin/searchAdminView.ts
|
|
4205
5735
|
var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
|
|
4206
5736
|
var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
|
|
4207
|
-
function
|
|
5737
|
+
function isRecord4(value) {
|
|
4208
5738
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4209
5739
|
}
|
|
4210
5740
|
function redactSearchServerConfig(search) {
|
|
@@ -4254,13 +5784,13 @@ function resolveSecretField(entry, field, stored) {
|
|
|
4254
5784
|
else delete entry[field];
|
|
4255
5785
|
}
|
|
4256
5786
|
function preserveSearchSecrets(incoming, current) {
|
|
4257
|
-
if (!
|
|
5787
|
+
if (!isRecord4(incoming)) return incoming;
|
|
4258
5788
|
const section = { ...incoming };
|
|
4259
5789
|
const providersValue = section["providers"];
|
|
4260
|
-
if (!
|
|
5790
|
+
if (!isRecord4(providersValue)) return section;
|
|
4261
5791
|
const providers = {};
|
|
4262
5792
|
for (const [id, entryValue] of Object.entries(providersValue)) {
|
|
4263
|
-
if (!
|
|
5793
|
+
if (!isRecord4(entryValue)) {
|
|
4264
5794
|
providers[id] = entryValue;
|
|
4265
5795
|
continue;
|
|
4266
5796
|
}
|
|
@@ -4338,7 +5868,7 @@ function parseKeyPolicyBody(body) {
|
|
|
4338
5868
|
var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
|
|
4339
5869
|
var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
|
|
4340
5870
|
var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
|
|
4341
|
-
function
|
|
5871
|
+
function isRecord5(value) {
|
|
4342
5872
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4343
5873
|
}
|
|
4344
5874
|
function nonBlank(value) {
|
|
@@ -4358,7 +5888,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4358
5888
|
const ids = /* @__PURE__ */ new Set();
|
|
4359
5889
|
raw.forEach((entry, index) => {
|
|
4360
5890
|
const path2 = `bindings[${index}]`;
|
|
4361
|
-
if (!
|
|
5891
|
+
if (!isRecord5(entry)) {
|
|
4362
5892
|
errors.push(`${path2} must be an object`);
|
|
4363
5893
|
return;
|
|
4364
5894
|
}
|
|
@@ -4387,12 +5917,12 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4387
5917
|
} else if (entry.modelMappings.length > 100) {
|
|
4388
5918
|
errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
|
|
4389
5919
|
} else if (entry.modelMappings.some(
|
|
4390
|
-
(mapping) => !
|
|
5920
|
+
(mapping) => !isRecord5(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
|
|
4391
5921
|
)) {
|
|
4392
5922
|
errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
|
|
4393
5923
|
}
|
|
4394
5924
|
}
|
|
4395
|
-
if (!
|
|
5925
|
+
if (!isRecord5(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
|
|
4396
5926
|
errors.push(`${path2}.target is invalid`);
|
|
4397
5927
|
} else {
|
|
4398
5928
|
if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
|
|
@@ -4407,7 +5937,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4407
5937
|
}
|
|
4408
5938
|
}
|
|
4409
5939
|
if (entry.modelMap !== void 0) {
|
|
4410
|
-
if (!
|
|
5940
|
+
if (!isRecord5(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
|
|
4411
5941
|
errors.push(`${path2}.modelMap must contain string values`);
|
|
4412
5942
|
}
|
|
4413
5943
|
}
|
|
@@ -4717,7 +6247,10 @@ var PROVIDER_KEYS = {
|
|
|
4717
6247
|
block: "opencodego",
|
|
4718
6248
|
accounts: "opencodegoAccounts",
|
|
4719
6249
|
active: "activeOpencodegoAccountId"
|
|
4720
|
-
}
|
|
6250
|
+
},
|
|
6251
|
+
kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" },
|
|
6252
|
+
grok: { block: "grok", accounts: "grokAccounts", active: "activeGrokAccountId" },
|
|
6253
|
+
copilot: { block: "copilot", accounts: "copilotAccounts", active: "activeCopilotAccountId" }
|
|
4721
6254
|
};
|
|
4722
6255
|
function clone(value) {
|
|
4723
6256
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -5239,7 +6772,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
|
|
|
5239
6772
|
}
|
|
5240
6773
|
|
|
5241
6774
|
// src/admin/adminMigration.ts
|
|
5242
|
-
function
|
|
6775
|
+
function err6(status, message) {
|
|
5243
6776
|
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
5244
6777
|
}
|
|
5245
6778
|
async function handleExport(body, deps) {
|
|
@@ -5249,30 +6782,30 @@ async function handleExport(body, deps) {
|
|
|
5249
6782
|
return { status: 200, body: { pack, version: BUNDLE_VERSION } };
|
|
5250
6783
|
} catch (error) {
|
|
5251
6784
|
if (error instanceof WeakPassphraseError) {
|
|
5252
|
-
return
|
|
6785
|
+
return err6(400, error.message);
|
|
5253
6786
|
}
|
|
5254
|
-
return
|
|
6787
|
+
return err6(500, "failed to build the migration pack");
|
|
5255
6788
|
}
|
|
5256
6789
|
}
|
|
5257
6790
|
async function handleImport(body, deps) {
|
|
5258
6791
|
const blob = typeof body["blob"] === "string" ? body["blob"] : "";
|
|
5259
6792
|
const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
|
|
5260
6793
|
const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
|
|
5261
|
-
if (!blob) return
|
|
6794
|
+
if (!blob) return err6(400, "import requires { blob }");
|
|
5262
6795
|
try {
|
|
5263
6796
|
const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
|
|
5264
6797
|
return { status: 200, body: counts };
|
|
5265
6798
|
} catch (error) {
|
|
5266
6799
|
if (error instanceof WeakPassphraseError) {
|
|
5267
|
-
return
|
|
6800
|
+
return err6(400, error.message);
|
|
5268
6801
|
}
|
|
5269
|
-
return
|
|
6802
|
+
return err6(400, error instanceof Error ? error.message : "import failed");
|
|
5270
6803
|
}
|
|
5271
6804
|
}
|
|
5272
6805
|
|
|
5273
6806
|
// src/admin/usagePricing.ts
|
|
5274
6807
|
import { getSharedUsageThroughputTracker } from "@omnicross/core/usage";
|
|
5275
|
-
var
|
|
6808
|
+
var err7 = (status, message) => ({
|
|
5276
6809
|
status,
|
|
5277
6810
|
body: { error: { type: "admin_api_error", message } }
|
|
5278
6811
|
});
|
|
@@ -5285,7 +6818,7 @@ function parseRange(query2) {
|
|
|
5285
6818
|
const startTs = parseFiniteInt(query2.get("startTs"));
|
|
5286
6819
|
const endTs = parseFiniteInt(query2.get("endTs"));
|
|
5287
6820
|
if (startTs === null || endTs === null) {
|
|
5288
|
-
return
|
|
6821
|
+
return err7(400, "startTs and endTs are required finite-integer unix-millis query params");
|
|
5289
6822
|
}
|
|
5290
6823
|
return { startTs, endTs };
|
|
5291
6824
|
}
|
|
@@ -5310,14 +6843,14 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
5310
6843
|
case "timeseries": {
|
|
5311
6844
|
const bucket = query2.get("bucket");
|
|
5312
6845
|
if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
|
|
5313
|
-
return
|
|
6846
|
+
return err7(400, "bucket must be one of 'hour', 'day', 'month'");
|
|
5314
6847
|
}
|
|
5315
6848
|
const now = Date.now();
|
|
5316
6849
|
const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
|
|
5317
6850
|
if (clamped.startTs < clamped.endTs) {
|
|
5318
6851
|
const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
|
|
5319
6852
|
if (projected > MAX_TIMESERIES_BUCKETS) {
|
|
5320
|
-
return
|
|
6853
|
+
return err7(
|
|
5321
6854
|
400,
|
|
5322
6855
|
`requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
|
|
5323
6856
|
);
|
|
@@ -5340,7 +6873,7 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
5340
6873
|
};
|
|
5341
6874
|
}
|
|
5342
6875
|
default:
|
|
5343
|
-
return
|
|
6876
|
+
return err7(404, `unknown usage view '${view ?? ""}'`);
|
|
5344
6877
|
}
|
|
5345
6878
|
}
|
|
5346
6879
|
function poolKeyLabels(cfg) {
|
|
@@ -5389,7 +6922,7 @@ async function handlePricingList(deps) {
|
|
|
5389
6922
|
async function handlePricingUpsert(body, deps) {
|
|
5390
6923
|
const input = parsePricingEntryInput(body);
|
|
5391
6924
|
if (!input) {
|
|
5392
|
-
return
|
|
6925
|
+
return err7(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
|
|
5393
6926
|
}
|
|
5394
6927
|
const entry = await deps.pricingEngine.upsertManual(input);
|
|
5395
6928
|
return { status: 200, body: { entry } };
|
|
@@ -5398,7 +6931,7 @@ async function handlePricingDelete(query2, deps) {
|
|
|
5398
6931
|
const providerId = query2.get("providerId")?.trim() ?? "";
|
|
5399
6932
|
const modelId = query2.get("modelId")?.trim() ?? "";
|
|
5400
6933
|
if (!providerId || !modelId) {
|
|
5401
|
-
return
|
|
6934
|
+
return err7(400, "delete requires providerId and modelId query params");
|
|
5402
6935
|
}
|
|
5403
6936
|
const deleted = await deps.pricingStore.delete(providerId, modelId);
|
|
5404
6937
|
if (deleted) await deps.pricingEngine.invalidateCache();
|
|
@@ -5418,13 +6951,13 @@ async function handlePricingFetchLatest(deps) {
|
|
|
5418
6951
|
}
|
|
5419
6952
|
};
|
|
5420
6953
|
} catch (e) {
|
|
5421
|
-
return
|
|
6954
|
+
return err7(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
5422
6955
|
}
|
|
5423
6956
|
}
|
|
5424
6957
|
async function handlePricingResolveConflicts(body, deps) {
|
|
5425
6958
|
const raw = body["resolutions"];
|
|
5426
6959
|
if (!Array.isArray(raw)) {
|
|
5427
|
-
return
|
|
6960
|
+
return err7(400, "resolve-conflicts requires { resolutions: [...] }");
|
|
5428
6961
|
}
|
|
5429
6962
|
const currentRows = await deps.pricingStore.getAll();
|
|
5430
6963
|
const userEditedKeys = new Set(
|
|
@@ -5434,21 +6967,21 @@ async function handlePricingResolveConflicts(body, deps) {
|
|
|
5434
6967
|
const pendingIncoming = /* @__PURE__ */ new Map();
|
|
5435
6968
|
let staleCount = 0;
|
|
5436
6969
|
for (const item of raw) {
|
|
5437
|
-
if (!item || typeof item !== "object") return
|
|
6970
|
+
if (!item || typeof item !== "object") return err7(400, "invalid resolution entry");
|
|
5438
6971
|
const r = item;
|
|
5439
6972
|
const action = r["action"];
|
|
5440
6973
|
if (action !== "overwrite" && action !== "skip") {
|
|
5441
|
-
return
|
|
6974
|
+
return err7(400, "resolution action must be 'overwrite' or 'skip'");
|
|
5442
6975
|
}
|
|
5443
6976
|
const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
|
|
5444
6977
|
const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
|
|
5445
6978
|
if (!providerId || !modelId) {
|
|
5446
|
-
return
|
|
6979
|
+
return err7(400, "each resolution requires top-level providerId and modelId");
|
|
5447
6980
|
}
|
|
5448
6981
|
const incoming = parsePricingEntryInput(r["incoming"]);
|
|
5449
|
-
if (!incoming) return
|
|
6982
|
+
if (!incoming) return err7(400, "each resolution must echo a valid incoming pricing entry");
|
|
5450
6983
|
if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
|
|
5451
|
-
return
|
|
6984
|
+
return err7(400, "resolution providerId/modelId must match the echoed incoming entry");
|
|
5452
6985
|
}
|
|
5453
6986
|
const key = `${providerId}::${modelId}`;
|
|
5454
6987
|
if (action === "overwrite" && !userEditedKeys.has(key)) {
|
|
@@ -5493,7 +7026,7 @@ function query(req) {
|
|
|
5493
7026
|
}
|
|
5494
7027
|
function allowanceProvider(value) {
|
|
5495
7028
|
if (!value) return void 0;
|
|
5496
|
-
return value === "claude" || value === "codex" ? value : null;
|
|
7029
|
+
return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" || value === "grok" || value === "copilot" ? value : null;
|
|
5497
7030
|
}
|
|
5498
7031
|
async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
5499
7032
|
if (!service) return writeError2(res, 501, "account allowance service is not available");
|
|
@@ -5507,7 +7040,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5507
7040
|
const params = query(req);
|
|
5508
7041
|
const pathProvider = rest.length >= 2 ? rest[0] : null;
|
|
5509
7042
|
const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
|
|
5510
|
-
if (providerId === null)
|
|
7043
|
+
if (providerId === null) {
|
|
7044
|
+
return writeError2(res, 400, "providerId must be claude, codex, kimi, opencodego, grok, or copilot");
|
|
7045
|
+
}
|
|
5511
7046
|
const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
|
|
5512
7047
|
const allowances = await service.list({ providerId, accountId });
|
|
5513
7048
|
return writeJson3(res, 200, { allowances });
|
|
@@ -5517,10 +7052,57 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5517
7052
|
const requestedProvider = allowanceProvider(
|
|
5518
7053
|
typeof body["providerId"] === "string" ? body["providerId"] : "claude"
|
|
5519
7054
|
);
|
|
5520
|
-
if (requestedProvider !== "claude") {
|
|
5521
|
-
return writeError2(res, 400, "only Claude allowances support explicit refresh");
|
|
5522
|
-
}
|
|
5523
7055
|
const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
|
|
7056
|
+
if (requestedProvider === "codex") {
|
|
7057
|
+
if (!service.refreshCodex) {
|
|
7058
|
+
return writeError2(res, 501, "codex allowance refresh is not available");
|
|
7059
|
+
}
|
|
7060
|
+
const allowances2 = await service.refreshCodex(accountId);
|
|
7061
|
+
if (accountId && allowances2.length === 0) {
|
|
7062
|
+
return writeError2(res, 404, `Codex account '${accountId}' not found`);
|
|
7063
|
+
}
|
|
7064
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
7065
|
+
}
|
|
7066
|
+
if (requestedProvider === "kimi") {
|
|
7067
|
+
if (!service.refreshKimi) {
|
|
7068
|
+
return writeError2(res, 501, "kimi allowance refresh is not available");
|
|
7069
|
+
}
|
|
7070
|
+
const allowances2 = await service.refreshKimi(accountId);
|
|
7071
|
+
if (accountId && allowances2.length === 0) {
|
|
7072
|
+
return writeError2(res, 404, `Kimi account '${accountId}' not found`);
|
|
7073
|
+
}
|
|
7074
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
7075
|
+
}
|
|
7076
|
+
if (requestedProvider === "opencodego") {
|
|
7077
|
+
if (!service.refreshOpenCodeGo) {
|
|
7078
|
+
return writeError2(res, 501, "opencodego allowance refresh is not available");
|
|
7079
|
+
}
|
|
7080
|
+
const allowances2 = await service.refreshOpenCodeGo(accountId);
|
|
7081
|
+
if (accountId && allowances2.length === 0) {
|
|
7082
|
+
return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
|
|
7083
|
+
}
|
|
7084
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
7085
|
+
}
|
|
7086
|
+
if (requestedProvider === "copilot") {
|
|
7087
|
+
if (!service.refreshCopilot) {
|
|
7088
|
+
return writeError2(res, 501, "copilot allowance refresh is not available");
|
|
7089
|
+
}
|
|
7090
|
+
const allowances2 = await service.refreshCopilot(accountId);
|
|
7091
|
+
if (accountId && allowances2.length === 0) {
|
|
7092
|
+
return writeError2(res, 404, `Copilot account '${accountId}' not found`);
|
|
7093
|
+
}
|
|
7094
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
7095
|
+
}
|
|
7096
|
+
if (requestedProvider === "grok") {
|
|
7097
|
+
if (!service.refreshGrok) {
|
|
7098
|
+
return writeError2(res, 501, "grok allowance refresh is not available");
|
|
7099
|
+
}
|
|
7100
|
+
const allowances2 = await service.refreshGrok(accountId);
|
|
7101
|
+
if (accountId && allowances2.length === 0) {
|
|
7102
|
+
return writeError2(res, 404, `Grok account '${accountId}' not found`);
|
|
7103
|
+
}
|
|
7104
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
7105
|
+
}
|
|
5524
7106
|
const allowances = await service.refreshClaude(accountId);
|
|
5525
7107
|
if (accountId && allowances.length === 0) {
|
|
5526
7108
|
return writeError2(res, 404, `Claude account '${accountId}' not found`);
|
|
@@ -5622,6 +7204,9 @@ function toProviderView(row) {
|
|
|
5622
7204
|
apiVersion: row.apiVersion,
|
|
5623
7205
|
maxConcurrency: row.maxConcurrency,
|
|
5624
7206
|
modelsEndpoint: row.modelsEndpoint,
|
|
7207
|
+
// Static extra headers round-trip VERBATIM (non-secret identity values;
|
|
7208
|
+
// auth/content names were already dropped at the write/load gate).
|
|
7209
|
+
extraHeaders: row.extraHeaders,
|
|
5625
7210
|
// app-parity child 5: transformer config round-trips VERBATIM (non-secret —
|
|
5626
7211
|
// transform-rule names + options, no key material; absent stays absent).
|
|
5627
7212
|
transformer: row.transformer,
|
|
@@ -5691,8 +7276,8 @@ async function handleAdminApi(req, res, path2, deps) {
|
|
|
5691
7276
|
default:
|
|
5692
7277
|
return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
|
|
5693
7278
|
}
|
|
5694
|
-
} catch (
|
|
5695
|
-
writeJsonError(res, 500,
|
|
7279
|
+
} catch (err8) {
|
|
7280
|
+
writeJsonError(res, 500, err8 instanceof Error ? err8.message : String(err8));
|
|
5696
7281
|
}
|
|
5697
7282
|
}
|
|
5698
7283
|
function requestQuery(req) {
|
|
@@ -5762,6 +7347,9 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
5762
7347
|
if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
|
|
5763
7348
|
return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
5764
7349
|
}
|
|
7350
|
+
if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
|
|
7351
|
+
return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
|
|
7352
|
+
}
|
|
5765
7353
|
if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
|
|
5766
7354
|
return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
5767
7355
|
}
|
|
@@ -5847,6 +7435,9 @@ async function handleProviderReorder(req, res, cfg, deps) {
|
|
|
5847
7435
|
persistProviders(cfg, deps);
|
|
5848
7436
|
return writeJson4(res, 200, { ok: true, providers: cfg.providers.map(toProviderView) });
|
|
5849
7437
|
}
|
|
7438
|
+
function expandRowExtraHeaders(row) {
|
|
7439
|
+
return mergeExtraHeaders({}, row.extraHeaders);
|
|
7440
|
+
}
|
|
5850
7441
|
async function handleDiscoverModels(res, id, cfg) {
|
|
5851
7442
|
if (!id) return writeJsonError(res, 400, "provider id required in path");
|
|
5852
7443
|
const row = cfg.providers.find((p) => p.id === id);
|
|
@@ -5860,7 +7451,8 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
5860
7451
|
try {
|
|
5861
7452
|
const headers = { Accept: "application/json" };
|
|
5862
7453
|
if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
|
|
5863
|
-
|
|
7454
|
+
Object.assign(headers, expandRowExtraHeaders(row));
|
|
7455
|
+
const response = await fetchUpstream7(url, { method: "GET", headers }, { providerId: "byo" });
|
|
5864
7456
|
if (!response.ok) {
|
|
5865
7457
|
const text = await response.text().catch(() => "");
|
|
5866
7458
|
let message = text.slice(0, 300);
|
|
@@ -5877,8 +7469,8 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
5877
7469
|
const data = await response.json();
|
|
5878
7470
|
const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
|
|
5879
7471
|
return writeJson4(res, 200, { models });
|
|
5880
|
-
} catch (
|
|
5881
|
-
const message =
|
|
7472
|
+
} catch (err8) {
|
|
7473
|
+
const message = err8 instanceof Error ? err8.message : String(err8);
|
|
5882
7474
|
return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
|
|
5883
7475
|
}
|
|
5884
7476
|
}
|
|
@@ -5917,9 +7509,10 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
5917
7509
|
messages: [{ role: "user", content: prompt }]
|
|
5918
7510
|
};
|
|
5919
7511
|
}
|
|
7512
|
+
Object.assign(headers, expandRowExtraHeaders(row));
|
|
5920
7513
|
const startedAt = Date.now();
|
|
5921
7514
|
try {
|
|
5922
|
-
const response = await
|
|
7515
|
+
const response = await fetchUpstream7(
|
|
5923
7516
|
url,
|
|
5924
7517
|
{ method: "POST", headers, body: JSON.stringify(payload) },
|
|
5925
7518
|
{ providerId: "byo" }
|
|
@@ -5941,8 +7534,8 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
5941
7534
|
latencyMs,
|
|
5942
7535
|
sample: extractSampleText(text, row.apiFormat)
|
|
5943
7536
|
});
|
|
5944
|
-
} catch (
|
|
5945
|
-
const message =
|
|
7537
|
+
} catch (err8) {
|
|
7538
|
+
const message = err8 instanceof Error ? err8.message : String(err8);
|
|
5946
7539
|
return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
|
|
5947
7540
|
}
|
|
5948
7541
|
}
|
|
@@ -5984,7 +7577,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
|
|
|
5984
7577
|
const row = cfg.providers.find((p) => p.id === id);
|
|
5985
7578
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
5986
7579
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
5987
|
-
|
|
7580
|
+
const views = toPoolKeyView(row, cooldown, deps);
|
|
7581
|
+
if (deps.providerKeyQuota) {
|
|
7582
|
+
const quotas = await Promise.allSettled(
|
|
7583
|
+
views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
|
|
7584
|
+
);
|
|
7585
|
+
views.forEach((view, index) => {
|
|
7586
|
+
const settled = quotas[index];
|
|
7587
|
+
if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
|
|
7588
|
+
});
|
|
7589
|
+
}
|
|
7590
|
+
return writeJson4(res, 200, { keys: views });
|
|
7591
|
+
}
|
|
7592
|
+
async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
|
|
7593
|
+
if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
|
|
7594
|
+
if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
|
|
7595
|
+
const row = cfg.providers.find((p) => p.id === id);
|
|
7596
|
+
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
7597
|
+
try {
|
|
7598
|
+
const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
|
|
7599
|
+
if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
|
|
7600
|
+
return writeJson4(res, 200, { quota });
|
|
7601
|
+
} catch {
|
|
7602
|
+
return writeJsonError(res, 502, "quota refresh failed");
|
|
7603
|
+
}
|
|
5988
7604
|
}
|
|
5989
7605
|
function parsePoolKeyInput(body, existing) {
|
|
5990
7606
|
const out = {};
|
|
@@ -6201,6 +7817,7 @@ function parseProviderInput(body, existing) {
|
|
|
6201
7817
|
const apiVersion = typeof body["apiVersion"] === "string" && body["apiVersion"].length > 0 ? body["apiVersion"] : body["apiVersion"] === null ? void 0 : existing?.apiVersion;
|
|
6202
7818
|
const modelsEndpoint = typeof body["modelsEndpoint"] === "string" && body["modelsEndpoint"].length > 0 ? body["modelsEndpoint"] : body["modelsEndpoint"] === null ? void 0 : existing?.modelsEndpoint;
|
|
6203
7819
|
const maxConcurrency = typeof body["maxConcurrency"] === "number" && Number.isFinite(body["maxConcurrency"]) ? body["maxConcurrency"] : body["maxConcurrency"] === null ? void 0 : existing?.maxConcurrency;
|
|
7820
|
+
const extraHeaders = body["extraHeaders"] === null ? void 0 : body["extraHeaders"] === void 0 ? existing?.extraHeaders : validateExtraHeaders(body["extraHeaders"]);
|
|
6204
7821
|
const transformer = body["transformer"] === null ? void 0 : parseTransformerInput(body["transformer"], existing?.transformer);
|
|
6205
7822
|
const codingPlan = body["codingPlan"] === null ? void 0 : body["codingPlan"] === void 0 ? existing?.codingPlan : body["codingPlan"] && typeof body["codingPlan"] === "object" && !Array.isArray(body["codingPlan"]) ? parseCodingPlanInput(body["codingPlan"], existing?.codingPlan) : existing?.codingPlan;
|
|
6206
7823
|
const apiModes = body["apiModes"] === null ? void 0 : parseApiModesInput(body["apiModes"], existing?.apiModes);
|
|
@@ -6226,6 +7843,7 @@ function parseProviderInput(body, existing) {
|
|
|
6226
7843
|
apiVersion,
|
|
6227
7844
|
maxConcurrency,
|
|
6228
7845
|
modelsEndpoint,
|
|
7846
|
+
extraHeaders,
|
|
6229
7847
|
transformer: migrated.transformer,
|
|
6230
7848
|
codingPlan,
|
|
6231
7849
|
apiModes,
|
|
@@ -6247,7 +7865,10 @@ function handlePresets(res, method) {
|
|
|
6247
7865
|
description: p.description,
|
|
6248
7866
|
features: p.features,
|
|
6249
7867
|
website: p.website,
|
|
6250
|
-
modelsEndpoint: p.modelsEndpoint
|
|
7868
|
+
modelsEndpoint: p.modelsEndpoint,
|
|
7869
|
+
// Static extra headers ride along so `addFromPreset` can seed them onto the
|
|
7870
|
+
// row (the write gateway re-validates via the shared allowlist).
|
|
7871
|
+
extraHeaders: p.extraHeaders
|
|
6251
7872
|
}));
|
|
6252
7873
|
return writeJson4(res, 200, { presets, excluded });
|
|
6253
7874
|
}
|
|
@@ -6729,12 +8350,12 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6729
8350
|
}
|
|
6730
8351
|
return writeJson4(res, 200, { ok: true, affected: result.affected });
|
|
6731
8352
|
}
|
|
6732
|
-
if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
|
|
6733
|
-
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
8353
|
+
if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi" || rest[0] === "grok" || rest[0] === "copilot") && rest[1] === "oauth" && rest[3] === "status") {
|
|
8354
|
+
const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : rest[0] === "kimi" ? handleKimiOAuthStatus(rest[2], deps) : rest[0] === "grok" ? handleGrokOAuthStatus(rest[2], deps) : handleCopilotOAuthStatus(rest[2], deps);
|
|
6734
8355
|
return writeJson4(res, result.status, result.body);
|
|
6735
8356
|
}
|
|
6736
|
-
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
6737
|
-
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
8357
|
+
if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi" || rest[0] === "grok" || rest[0] === "copilot") && rest[1] === "oauth" && rest[2]) {
|
|
8358
|
+
const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : rest[0] === "kimi" ? handleKimiOAuthCancel(rest[2], deps) : rest[0] === "grok" ? handleGrokOAuthCancel(rest[2], deps) : handleCopilotOAuthCancel(rest[2], deps);
|
|
6738
8359
|
return writeJson4(res, result.status, result.body);
|
|
6739
8360
|
}
|
|
6740
8361
|
if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
|
|
@@ -6787,7 +8408,24 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6787
8408
|
return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
6788
8409
|
}
|
|
6789
8410
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
|
|
6790
|
-
|
|
8411
|
+
if (providerId === "codex") {
|
|
8412
|
+
const result2 = handleCodexOAuthStart(deps);
|
|
8413
|
+
return writeJson4(res, result2.status, result2.body);
|
|
8414
|
+
}
|
|
8415
|
+
if (providerId === "kimi") {
|
|
8416
|
+
const result2 = await handleKimiOAuthStart(deps);
|
|
8417
|
+
return writeJson4(res, result2.status, result2.body);
|
|
8418
|
+
}
|
|
8419
|
+
if (providerId === "grok") {
|
|
8420
|
+
const result2 = await handleGrokOAuthStart(deps);
|
|
8421
|
+
return writeJson4(res, result2.status, result2.body);
|
|
8422
|
+
}
|
|
8423
|
+
if (providerId === "copilot") {
|
|
8424
|
+
const body2 = await readJsonBody4(req);
|
|
8425
|
+
const result2 = await handleCopilotOAuthStart(deps, body2["enterpriseUrl"]);
|
|
8426
|
+
return writeJson4(res, result2.status, result2.body);
|
|
8427
|
+
}
|
|
8428
|
+
const result = handleOAuthStart(providerId, deps);
|
|
6791
8429
|
return writeJson4(res, result.status, result.body);
|
|
6792
8430
|
}
|
|
6793
8431
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
|
|
@@ -7281,12 +8919,12 @@ async function handlePlayground(req, res, method, deps) {
|
|
|
7281
8919
|
const payload = body["body"];
|
|
7282
8920
|
const status = deps.outboundApiServer.getStatus();
|
|
7283
8921
|
if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
|
|
7284
|
-
const path2 = resolvePlaygroundPath(endpoint,
|
|
8922
|
+
const path2 = resolvePlaygroundPath(endpoint, isRecord6(payload) ? payload : {});
|
|
7285
8923
|
if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
|
|
7286
8924
|
const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
7287
8925
|
await proxyToOutbound(res, status.port, path2, key, upstreamBody);
|
|
7288
8926
|
}
|
|
7289
|
-
function
|
|
8927
|
+
function isRecord6(v) {
|
|
7290
8928
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
7291
8929
|
}
|
|
7292
8930
|
function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
@@ -7315,8 +8953,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
|
7315
8953
|
});
|
|
7316
8954
|
}
|
|
7317
8955
|
);
|
|
7318
|
-
upstream.on("error", (
|
|
7319
|
-
if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${
|
|
8956
|
+
upstream.on("error", (err8) => {
|
|
8957
|
+
if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err8.message}`);
|
|
7320
8958
|
else res.end();
|
|
7321
8959
|
resolve10();
|
|
7322
8960
|
});
|
|
@@ -7421,7 +9059,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
7421
9059
|
}
|
|
7422
9060
|
|
|
7423
9061
|
// src/admin/version.ts
|
|
7424
|
-
var DAEMON_VERSION = true ? "0.
|
|
9062
|
+
var DAEMON_VERSION = true ? "0.4.0" : "0.0.0-dev";
|
|
7425
9063
|
|
|
7426
9064
|
// src/admin/AdminServer.ts
|
|
7427
9065
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -7464,13 +9102,13 @@ var AdminServer = class {
|
|
|
7464
9102
|
const server = http2.createServer((req, res) => {
|
|
7465
9103
|
this.onRequest(req, res);
|
|
7466
9104
|
});
|
|
7467
|
-
const onError = (
|
|
7468
|
-
if (
|
|
9105
|
+
const onError = (err8) => {
|
|
9106
|
+
if (err8.code === "EADDRINUSE" && port !== 0) {
|
|
7469
9107
|
server.removeListener("error", onError);
|
|
7470
9108
|
this.listen(bindAddr, 0).then(resolve10, reject);
|
|
7471
9109
|
return;
|
|
7472
9110
|
}
|
|
7473
|
-
reject(
|
|
9111
|
+
reject(err8);
|
|
7474
9112
|
};
|
|
7475
9113
|
server.on("error", onError);
|
|
7476
9114
|
server.listen(port, bindAddr, () => {
|
|
@@ -7488,8 +9126,8 @@ var AdminServer = class {
|
|
|
7488
9126
|
}
|
|
7489
9127
|
/** Per-request handler: auth gate (when a token is set) → routing. */
|
|
7490
9128
|
onRequest(req, res) {
|
|
7491
|
-
void this.dispatch(req, res).catch((
|
|
7492
|
-
const message =
|
|
9129
|
+
void this.dispatch(req, res).catch((err8) => {
|
|
9130
|
+
const message = err8 instanceof Error ? err8.message : String(err8);
|
|
7493
9131
|
this.deps.logger.error("[AdminServer] unhandled error:", message);
|
|
7494
9132
|
if (!res.headersSent) {
|
|
7495
9133
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -7753,18 +9391,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
|
|
|
7753
9391
|
return;
|
|
7754
9392
|
}
|
|
7755
9393
|
signal?.addEventListener("abort", abort, { once: true });
|
|
7756
|
-
server.on("error", (
|
|
9394
|
+
server.on("error", (err8) => {
|
|
7757
9395
|
if (settled) return;
|
|
7758
9396
|
settled = true;
|
|
7759
9397
|
clearTimeout(timer);
|
|
7760
|
-
if (
|
|
9398
|
+
if (err8.code === "EADDRINUSE") {
|
|
7761
9399
|
reject(
|
|
7762
9400
|
new Error(
|
|
7763
9401
|
`login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
|
|
7764
9402
|
)
|
|
7765
9403
|
);
|
|
7766
9404
|
} else {
|
|
7767
|
-
reject(
|
|
9405
|
+
reject(err8);
|
|
7768
9406
|
}
|
|
7769
9407
|
});
|
|
7770
9408
|
const timer = setTimeout(() => {
|
|
@@ -7839,6 +9477,449 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
|
|
|
7839
9477
|
};
|
|
7840
9478
|
}
|
|
7841
9479
|
|
|
9480
|
+
// src/allowance/ProviderKeyQuotaService.ts
|
|
9481
|
+
import { mergeExtraHeaders as mergeExtraHeaders2 } from "@omnicross/core";
|
|
9482
|
+
import { fetchUpstream as fetchUpstream8 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
9483
|
+
|
|
9484
|
+
// src/allowance/ProviderKeyQuota.ts
|
|
9485
|
+
var MINUTE_MS3 = 6e4;
|
|
9486
|
+
var HOUR_MS2 = 60 * MINUTE_MS3;
|
|
9487
|
+
var DAY_MS3 = 24 * HOUR_MS2;
|
|
9488
|
+
var WEEK_MS = 7 * DAY_MS3;
|
|
9489
|
+
var MONTH_MS = 30 * DAY_MS3;
|
|
9490
|
+
function finiteNumber5(value) {
|
|
9491
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
9492
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
9493
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
9494
|
+
}
|
|
9495
|
+
function finitePercent4(value) {
|
|
9496
|
+
const parsed = finiteNumber5(value);
|
|
9497
|
+
return parsed !== void 0 && parsed <= 100 ? parsed : null;
|
|
9498
|
+
}
|
|
9499
|
+
function isoInstant3(value) {
|
|
9500
|
+
if (typeof value === "string" && value.trim()) {
|
|
9501
|
+
const time = Date.parse(value);
|
|
9502
|
+
if (Number.isFinite(time)) return new Date(time).toISOString();
|
|
9503
|
+
}
|
|
9504
|
+
const numeric = finiteNumber5(value);
|
|
9505
|
+
if (numeric !== void 0 && numeric > 1e9) {
|
|
9506
|
+
const ms = numeric > 1e12 ? numeric : numeric * 1e3;
|
|
9507
|
+
return new Date(ms).toISOString();
|
|
9508
|
+
}
|
|
9509
|
+
return void 0;
|
|
9510
|
+
}
|
|
9511
|
+
function secondsUntil7(instant, now) {
|
|
9512
|
+
if (!instant) return void 0;
|
|
9513
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
9514
|
+
}
|
|
9515
|
+
function isRecord7(value) {
|
|
9516
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
9517
|
+
}
|
|
9518
|
+
function detectProviderKeyQuotaAdapter(baseUrl) {
|
|
9519
|
+
if (!baseUrl) return null;
|
|
9520
|
+
let url;
|
|
9521
|
+
try {
|
|
9522
|
+
url = new URL(baseUrl);
|
|
9523
|
+
} catch {
|
|
9524
|
+
return null;
|
|
9525
|
+
}
|
|
9526
|
+
const host = url.hostname.toLowerCase();
|
|
9527
|
+
const path2 = url.pathname.toLowerCase();
|
|
9528
|
+
if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
|
|
9529
|
+
return "zai";
|
|
9530
|
+
}
|
|
9531
|
+
if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
|
|
9532
|
+
// anthropic `/anthropic` rows are excluded (their usage impl is unverified).
|
|
9533
|
+
(path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
|
|
9534
|
+
return "minimax-token-plan";
|
|
9535
|
+
}
|
|
9536
|
+
if (host === "api.code.umans.ai") return "umans";
|
|
9537
|
+
if (host === "api.synthetic.new") return "synthetic";
|
|
9538
|
+
if (host === "api.cline.bot") return "cline-pass";
|
|
9539
|
+
return null;
|
|
9540
|
+
}
|
|
9541
|
+
function providerKeyQuotaUrl(adapter, baseUrl) {
|
|
9542
|
+
const origin = new URL(baseUrl).origin;
|
|
9543
|
+
if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
|
|
9544
|
+
if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
|
|
9545
|
+
if (adapter === "umans") return `${origin}/v1/usage`;
|
|
9546
|
+
if (adapter === "cline-pass") return `${origin}/api/v1/users/me/plan/usage-limits`;
|
|
9547
|
+
return `${origin}/v2/quotas`;
|
|
9548
|
+
}
|
|
9549
|
+
function providerKeyQuotaAuthHeader(adapter, key) {
|
|
9550
|
+
return adapter === "zai" ? key : `Bearer ${key}`;
|
|
9551
|
+
}
|
|
9552
|
+
function zaiWindowDurationMs(item) {
|
|
9553
|
+
const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
|
|
9554
|
+
switch (item.unit) {
|
|
9555
|
+
case 3:
|
|
9556
|
+
return count * HOUR_MS2;
|
|
9557
|
+
case 4:
|
|
9558
|
+
return count * DAY_MS3;
|
|
9559
|
+
case 5:
|
|
9560
|
+
return count * MONTH_MS;
|
|
9561
|
+
case 6:
|
|
9562
|
+
return WEEK_MS;
|
|
9563
|
+
default:
|
|
9564
|
+
return void 0;
|
|
9565
|
+
}
|
|
9566
|
+
}
|
|
9567
|
+
function zaiWindowIdLabel(durationMs) {
|
|
9568
|
+
if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
|
|
9569
|
+
if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
|
|
9570
|
+
if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
|
|
9571
|
+
if (durationMs !== void 0 && durationMs % DAY_MS3 === 0) {
|
|
9572
|
+
const days = durationMs / DAY_MS3;
|
|
9573
|
+
return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
|
|
9574
|
+
}
|
|
9575
|
+
if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
|
|
9576
|
+
const hours = durationMs / HOUR_MS2;
|
|
9577
|
+
return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
|
|
9578
|
+
}
|
|
9579
|
+
return { id: "quota", label: "Quota" };
|
|
9580
|
+
}
|
|
9581
|
+
function parseZaiQuotaPayload(payload, now) {
|
|
9582
|
+
if (!isRecord7(payload)) return null;
|
|
9583
|
+
const data = isRecord7(payload["data"]) ? payload["data"] : payload;
|
|
9584
|
+
if (payload["success"] === false) return null;
|
|
9585
|
+
const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
|
|
9586
|
+
const byWindow = /* @__PURE__ */ new Map();
|
|
9587
|
+
for (const raw of limits) {
|
|
9588
|
+
if (!isRecord7(raw)) continue;
|
|
9589
|
+
const item = raw;
|
|
9590
|
+
if (item.type === void 0) continue;
|
|
9591
|
+
const details = raw["usageDetails"];
|
|
9592
|
+
if (Array.isArray(details) && details.some((d) => isRecord7(d) && d["modelCode"] === "zread")) {
|
|
9593
|
+
continue;
|
|
9594
|
+
}
|
|
9595
|
+
const durationMs = zaiWindowDurationMs(item);
|
|
9596
|
+
const { id, label } = zaiWindowIdLabel(durationMs);
|
|
9597
|
+
const limit = finiteNumber5(item.usage);
|
|
9598
|
+
const used = finiteNumber5(item.currentValue);
|
|
9599
|
+
const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
|
|
9600
|
+
const fromPercentage = finitePercent4(item.percentage) ?? void 0;
|
|
9601
|
+
const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
|
|
9602
|
+
if (usedPercent === void 0) continue;
|
|
9603
|
+
const resetsAt = isoInstant3(item.nextResetTime);
|
|
9604
|
+
const candidate = {
|
|
9605
|
+
id,
|
|
9606
|
+
label,
|
|
9607
|
+
scope: "all",
|
|
9608
|
+
usedPercent,
|
|
9609
|
+
...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS3) } : {},
|
|
9610
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9611
|
+
remainingSeconds: secondsUntil7(resetsAt, now),
|
|
9612
|
+
state: "fresh"
|
|
9613
|
+
};
|
|
9614
|
+
const existing = byWindow.get(id);
|
|
9615
|
+
if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
|
|
9616
|
+
byWindow.set(id, candidate);
|
|
9617
|
+
}
|
|
9618
|
+
}
|
|
9619
|
+
const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
|
|
9620
|
+
return windows.length > 0 ? windows.slice(0, 4) : null;
|
|
9621
|
+
}
|
|
9622
|
+
var MINIMAX_STATUS_EXHAUSTED = 2;
|
|
9623
|
+
var MINIMAX_SHARED_BUCKET = "general";
|
|
9624
|
+
function parseMiniMaxBucket(value) {
|
|
9625
|
+
if (!isRecord7(value)) return null;
|
|
9626
|
+
const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
|
|
9627
|
+
if (!modelName) return null;
|
|
9628
|
+
const instant = (v) => {
|
|
9629
|
+
const n = finiteNumber5(v);
|
|
9630
|
+
return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
|
|
9631
|
+
};
|
|
9632
|
+
return {
|
|
9633
|
+
modelName,
|
|
9634
|
+
intervalEnd: instant(value["end_time"]),
|
|
9635
|
+
intervalRemainingPercent: finiteNumber5(value["current_interval_remaining_percent"]),
|
|
9636
|
+
intervalStatus: finiteNumber5(value["current_interval_status"]),
|
|
9637
|
+
weeklyEnd: instant(value["weekly_end_time"]),
|
|
9638
|
+
weeklyRemainingPercent: finiteNumber5(value["current_weekly_remaining_percent"]),
|
|
9639
|
+
weeklyStatus: finiteNumber5(value["current_weekly_status"])
|
|
9640
|
+
};
|
|
9641
|
+
}
|
|
9642
|
+
function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
|
|
9643
|
+
const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
|
|
9644
|
+
const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
|
|
9645
|
+
return {
|
|
9646
|
+
id,
|
|
9647
|
+
label,
|
|
9648
|
+
scope: "all",
|
|
9649
|
+
usedPercent,
|
|
9650
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
9651
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9652
|
+
remainingSeconds: secondsUntil7(resetsAt, now),
|
|
9653
|
+
state: usedPercent !== null ? "fresh" : "unavailable"
|
|
9654
|
+
};
|
|
9655
|
+
}
|
|
9656
|
+
function parseMiniMaxTokenPlanPayload(payload, now) {
|
|
9657
|
+
if (!isRecord7(payload)) return null;
|
|
9658
|
+
const baseResp = payload["base_resp"];
|
|
9659
|
+
if (!isRecord7(baseResp) || baseResp["status_code"] !== 0) return null;
|
|
9660
|
+
const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
|
|
9661
|
+
let general = null;
|
|
9662
|
+
for (const raw of buckets) {
|
|
9663
|
+
const bucket = parseMiniMaxBucket(raw);
|
|
9664
|
+
if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
|
|
9665
|
+
general = bucket;
|
|
9666
|
+
break;
|
|
9667
|
+
}
|
|
9668
|
+
}
|
|
9669
|
+
if (!general) return null;
|
|
9670
|
+
return [
|
|
9671
|
+
minimaxWindow(
|
|
9672
|
+
"five-hour",
|
|
9673
|
+
"5 hours",
|
|
9674
|
+
5 * 60,
|
|
9675
|
+
general.intervalEnd,
|
|
9676
|
+
general.intervalRemainingPercent,
|
|
9677
|
+
general.intervalStatus,
|
|
9678
|
+
now
|
|
9679
|
+
),
|
|
9680
|
+
minimaxWindow(
|
|
9681
|
+
"seven-day",
|
|
9682
|
+
"7 days",
|
|
9683
|
+
Math.round(WEEK_MS / MINUTE_MS3),
|
|
9684
|
+
general.weeklyEnd,
|
|
9685
|
+
general.weeklyRemainingPercent,
|
|
9686
|
+
general.weeklyStatus,
|
|
9687
|
+
now
|
|
9688
|
+
)
|
|
9689
|
+
];
|
|
9690
|
+
}
|
|
9691
|
+
function parseUmansUsagePayload(payload, now) {
|
|
9692
|
+
if (!isRecord7(payload)) return null;
|
|
9693
|
+
const limits = isRecord7(payload["limits"]) ? payload["limits"] : void 0;
|
|
9694
|
+
const requests = limits && isRecord7(limits["requests"]) ? limits["requests"] : void 0;
|
|
9695
|
+
const usage = isRecord7(payload["usage"]) ? payload["usage"] : void 0;
|
|
9696
|
+
const window = isRecord7(payload["window"]) ? payload["window"] : void 0;
|
|
9697
|
+
const hardCap = finiteNumber5(requests?.["hard_cap"]);
|
|
9698
|
+
const softLimit = finiteNumber5(requests?.["limit"]);
|
|
9699
|
+
const requestsInWindow = finiteNumber5(usage?.["requests_in_window"]);
|
|
9700
|
+
const weightedInWindow = finiteNumber5(usage?.["weighted_in_window"]);
|
|
9701
|
+
const resetsAt = isoInstant3(window?.["resets_at"]);
|
|
9702
|
+
let usedPercent = null;
|
|
9703
|
+
if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
|
|
9704
|
+
usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
|
|
9705
|
+
} else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
|
|
9706
|
+
usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
|
|
9707
|
+
}
|
|
9708
|
+
if (usedPercent === null && resetsAt === void 0) return null;
|
|
9709
|
+
return [
|
|
9710
|
+
{
|
|
9711
|
+
id: "five-hour",
|
|
9712
|
+
label: "5 hours",
|
|
9713
|
+
scope: "all",
|
|
9714
|
+
usedPercent,
|
|
9715
|
+
windowMinutes: 5 * 60,
|
|
9716
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9717
|
+
remainingSeconds: secondsUntil7(resetsAt, now),
|
|
9718
|
+
state: "fresh"
|
|
9719
|
+
}
|
|
9720
|
+
];
|
|
9721
|
+
}
|
|
9722
|
+
function parseSyntheticQuotasPayload(payload, now) {
|
|
9723
|
+
if (!isRecord7(payload)) return null;
|
|
9724
|
+
const fiveHour = isRecord7(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
|
|
9725
|
+
const weekly = isRecord7(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
|
|
9726
|
+
const windows = [];
|
|
9727
|
+
if (fiveHour) {
|
|
9728
|
+
const max = finiteNumber5(fiveHour["max"]);
|
|
9729
|
+
const remaining = finiteNumber5(fiveHour["remaining"]);
|
|
9730
|
+
const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
|
|
9731
|
+
const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
|
|
9732
|
+
windows.push({
|
|
9733
|
+
id: "five-hour",
|
|
9734
|
+
label: "5 hours",
|
|
9735
|
+
scope: "all",
|
|
9736
|
+
usedPercent,
|
|
9737
|
+
windowMinutes: 5 * 60,
|
|
9738
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9739
|
+
remainingSeconds: secondsUntil7(resetsAt, now),
|
|
9740
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
9741
|
+
});
|
|
9742
|
+
}
|
|
9743
|
+
if (weekly) {
|
|
9744
|
+
const percentRemaining = finiteNumber5(weekly["percentRemaining"]);
|
|
9745
|
+
const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
|
|
9746
|
+
const resetsAt = isoInstant3(weekly["nextRegenAt"]);
|
|
9747
|
+
windows.push({
|
|
9748
|
+
id: "seven-day",
|
|
9749
|
+
label: "7 days",
|
|
9750
|
+
scope: "all",
|
|
9751
|
+
usedPercent,
|
|
9752
|
+
windowMinutes: 7 * 24 * 60,
|
|
9753
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9754
|
+
remainingSeconds: secondsUntil7(resetsAt, now),
|
|
9755
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
9756
|
+
});
|
|
9757
|
+
}
|
|
9758
|
+
return windows.length > 0 ? windows : null;
|
|
9759
|
+
}
|
|
9760
|
+
var CLINE_WINDOW_CONFIG = {
|
|
9761
|
+
five_hour: { id: "five-hour", label: "5 hours", minutes: 5 * 60 },
|
|
9762
|
+
weekly: { id: "seven-day", label: "7 days", minutes: 7 * 24 * 60 },
|
|
9763
|
+
monthly: { id: "thirty-day", label: "30 days", minutes: 30 * 24 * 60 }
|
|
9764
|
+
};
|
|
9765
|
+
function parseClinePassUsageLimitsPayload(payload, now) {
|
|
9766
|
+
if (!isRecord7(payload)) return null;
|
|
9767
|
+
const data = isRecord7(payload["data"]) ? payload["data"] : payload;
|
|
9768
|
+
const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
|
|
9769
|
+
const windows = [];
|
|
9770
|
+
for (const raw of limits) {
|
|
9771
|
+
if (!isRecord7(raw)) continue;
|
|
9772
|
+
const config = CLINE_WINDOW_CONFIG[typeof raw["type"] === "string" ? raw["type"] : ""];
|
|
9773
|
+
if (!config) continue;
|
|
9774
|
+
const usedPercent = finitePercent4(raw["percentUsed"]);
|
|
9775
|
+
if (usedPercent === null) continue;
|
|
9776
|
+
const resetsAt = isoInstant3(raw["resetsAt"]);
|
|
9777
|
+
windows.push({
|
|
9778
|
+
id: config.id,
|
|
9779
|
+
label: config.label,
|
|
9780
|
+
scope: "all",
|
|
9781
|
+
usedPercent,
|
|
9782
|
+
windowMinutes: config.minutes,
|
|
9783
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9784
|
+
remainingSeconds: secondsUntil7(resetsAt, now),
|
|
9785
|
+
state: "fresh"
|
|
9786
|
+
});
|
|
9787
|
+
}
|
|
9788
|
+
return windows.length > 0 ? windows : null;
|
|
9789
|
+
}
|
|
9790
|
+
|
|
9791
|
+
// src/allowance/ProviderKeyQuotaService.ts
|
|
9792
|
+
function parseQuotaPayload(adapter, payload, now) {
|
|
9793
|
+
switch (adapter) {
|
|
9794
|
+
case "zai":
|
|
9795
|
+
return parseZaiQuotaPayload(payload, now);
|
|
9796
|
+
case "minimax-token-plan":
|
|
9797
|
+
return parseMiniMaxTokenPlanPayload(payload, now);
|
|
9798
|
+
case "umans":
|
|
9799
|
+
return parseUmansUsagePayload(payload, now);
|
|
9800
|
+
case "synthetic":
|
|
9801
|
+
return parseSyntheticQuotasPayload(payload, now);
|
|
9802
|
+
case "cline-pass":
|
|
9803
|
+
return parseClinePassUsageLimitsPayload(payload, now);
|
|
9804
|
+
}
|
|
9805
|
+
}
|
|
9806
|
+
var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
|
|
9807
|
+
function resolvedBaseUrl(row) {
|
|
9808
|
+
const modes = row.apiModes ?? [];
|
|
9809
|
+
const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
|
|
9810
|
+
const fallback = modes[0];
|
|
9811
|
+
const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
|
|
9812
|
+
return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
|
|
9813
|
+
}
|
|
9814
|
+
function rowKeyEntries(row) {
|
|
9815
|
+
const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
|
|
9816
|
+
if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
|
|
9817
|
+
if (row.apiKey.length > 0) {
|
|
9818
|
+
return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
|
|
9819
|
+
}
|
|
9820
|
+
return [];
|
|
9821
|
+
}
|
|
9822
|
+
var ProviderKeyQuotaService = class {
|
|
9823
|
+
constructor(box, fetchImpl = (url, init) => fetchUpstream8(url, init, { redactBodies: true }), now = Date.now) {
|
|
9824
|
+
this.box = box;
|
|
9825
|
+
this.fetchImpl = fetchImpl;
|
|
9826
|
+
this.now = now;
|
|
9827
|
+
}
|
|
9828
|
+
box;
|
|
9829
|
+
fetchImpl;
|
|
9830
|
+
now;
|
|
9831
|
+
cache = /* @__PURE__ */ new Map();
|
|
9832
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
9833
|
+
/**
|
|
9834
|
+
* Quota for one key of a provider row, or `null` when the row has no quota
|
|
9835
|
+
* adapter / no such key. Cache-first; concurrent reads share one flight.
|
|
9836
|
+
*/
|
|
9837
|
+
async quotaFor(row, keyId, options = {}) {
|
|
9838
|
+
const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
|
|
9839
|
+
if (!adapter) return null;
|
|
9840
|
+
const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
|
|
9841
|
+
if (!entry) return null;
|
|
9842
|
+
const cacheKey = `${row.id}\0${keyId}`;
|
|
9843
|
+
const now = this.now();
|
|
9844
|
+
const cached = this.cache.get(cacheKey);
|
|
9845
|
+
if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
|
|
9846
|
+
const running = this.inFlight.get(cacheKey);
|
|
9847
|
+
if (running) return running;
|
|
9848
|
+
const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
|
|
9849
|
+
void error;
|
|
9850
|
+
const previous = this.cache.get(cacheKey);
|
|
9851
|
+
if (previous) {
|
|
9852
|
+
const degraded = {
|
|
9853
|
+
...previous,
|
|
9854
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9855
|
+
windows: previous.windows.map((window) => ({
|
|
9856
|
+
...window,
|
|
9857
|
+
state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
|
|
9858
|
+
})),
|
|
9859
|
+
errorCode: "quota_request_failed"
|
|
9860
|
+
};
|
|
9861
|
+
this.cache.set(cacheKey, degraded);
|
|
9862
|
+
return degraded;
|
|
9863
|
+
}
|
|
9864
|
+
return null;
|
|
9865
|
+
}).finally(() => this.inFlight.delete(cacheKey));
|
|
9866
|
+
this.inFlight.set(cacheKey, promise);
|
|
9867
|
+
return promise;
|
|
9868
|
+
}
|
|
9869
|
+
/** Drop cached rows for a provider (key added/removed/rotated). */
|
|
9870
|
+
invalidateProvider(providerRowId) {
|
|
9871
|
+
for (const key of this.cache.keys()) {
|
|
9872
|
+
if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
|
|
9873
|
+
}
|
|
9874
|
+
}
|
|
9875
|
+
async fetchQuota(adapter, row, rawKey, cacheKey) {
|
|
9876
|
+
const baseUrl = resolvedBaseUrl(row);
|
|
9877
|
+
const url = providerKeyQuotaUrl(adapter, baseUrl);
|
|
9878
|
+
const key = this.box.decryptMaybe(rawKey);
|
|
9879
|
+
const now = this.now();
|
|
9880
|
+
const response = await this.fetchImpl(url, {
|
|
9881
|
+
method: "GET",
|
|
9882
|
+
headers: {
|
|
9883
|
+
Authorization: providerKeyQuotaAuthHeader(adapter, key),
|
|
9884
|
+
Accept: "application/json",
|
|
9885
|
+
"Content-Type": "application/json",
|
|
9886
|
+
// The row's static identity headers ride along — the Cline usage
|
|
9887
|
+
// endpoint sits behind the SAME client-identity 403 gate as inference.
|
|
9888
|
+
...mergeExtraHeaders2({}, row.extraHeaders)
|
|
9889
|
+
},
|
|
9890
|
+
signal: AbortSignal.timeout(15e3)
|
|
9891
|
+
});
|
|
9892
|
+
if (response.status === 401 || response.status === 403) {
|
|
9893
|
+
const snapshot2 = {
|
|
9894
|
+
adapter,
|
|
9895
|
+
observedAt: new Date(now).toISOString(),
|
|
9896
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9897
|
+
windows: [],
|
|
9898
|
+
errorCode: "quota_unauthorized"
|
|
9899
|
+
};
|
|
9900
|
+
this.cache.set(cacheKey, snapshot2);
|
|
9901
|
+
return snapshot2;
|
|
9902
|
+
}
|
|
9903
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
9904
|
+
let payload;
|
|
9905
|
+
try {
|
|
9906
|
+
payload = await response.json();
|
|
9907
|
+
} catch {
|
|
9908
|
+
throw new Error("invalid JSON");
|
|
9909
|
+
}
|
|
9910
|
+
const windows = parseQuotaPayload(adapter, payload, now);
|
|
9911
|
+
const snapshot = {
|
|
9912
|
+
adapter,
|
|
9913
|
+
observedAt: new Date(now).toISOString(),
|
|
9914
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9915
|
+
windows: windows ?? [],
|
|
9916
|
+
...windows ? {} : { errorCode: "quota_unavailable" }
|
|
9917
|
+
};
|
|
9918
|
+
this.cache.set(cacheKey, snapshot);
|
|
9919
|
+
return snapshot;
|
|
9920
|
+
}
|
|
9921
|
+
};
|
|
9922
|
+
|
|
7842
9923
|
// src/commands/paths.ts
|
|
7843
9924
|
import { dirname as dirname5, join as join5 } from "path";
|
|
7844
9925
|
function defaultVouchersPath(configPath) {
|
|
@@ -12609,6 +14690,10 @@ function toLLMProvider(row) {
|
|
|
12609
14690
|
// `parseProviderInput`), so customizations are preserved (the row value wins).
|
|
12610
14691
|
apiModes: row.apiModes,
|
|
12611
14692
|
selectedApiModeId: row.selectedApiModeId,
|
|
14693
|
+
// Static extra request headers ride along verbatim (load-guarded — no
|
|
14694
|
+
// auth/content names); core's `getProviderHeaders` merges them into every
|
|
14695
|
+
// BYO request, and the same-format relay path inherits that funnel.
|
|
14696
|
+
extraHeaders: row.extraHeaders,
|
|
12612
14697
|
// Official-Anthropic signature handling only matters for the Anthropic
|
|
12613
14698
|
// ingress (deferred → 502); leave it off for the BYO transform path.
|
|
12614
14699
|
isOfficial: false
|
|
@@ -13988,21 +16073,23 @@ function bucketLabel(bucketStartTs, bucket) {
|
|
|
13988
16073
|
}
|
|
13989
16074
|
|
|
13990
16075
|
// src/ports/JsonOutboundKeyDb.ts
|
|
16076
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
16077
|
+
import {
|
|
16078
|
+
validateOutboundPermissions as validateOutboundPermissions3
|
|
16079
|
+
} from "@omnicross/core";
|
|
16080
|
+
|
|
16081
|
+
// src/ports/atomicFile.ts
|
|
13991
16082
|
import { randomBytes as randomBytes11 } from "crypto";
|
|
13992
16083
|
import {
|
|
13993
16084
|
closeSync as closeSync7,
|
|
13994
16085
|
existsSync as existsSync16,
|
|
13995
16086
|
fsyncSync as fsyncSync7,
|
|
13996
16087
|
openSync as openSync7,
|
|
13997
|
-
readFileSync as readFileSync13,
|
|
13998
16088
|
renameSync as renameSync9,
|
|
13999
16089
|
unlinkSync as unlinkSync11,
|
|
14000
16090
|
writeFileSync as writeFileSync12
|
|
14001
16091
|
} from "fs";
|
|
14002
16092
|
import { basename as basename8, dirname as dirname14, join as join16 } from "path";
|
|
14003
|
-
import {
|
|
14004
|
-
validateOutboundPermissions as validateOutboundPermissions3
|
|
14005
|
-
} from "@omnicross/core";
|
|
14006
16093
|
function atomicReplaceUtf8(targetPath, contents) {
|
|
14007
16094
|
const tempPath = join16(
|
|
14008
16095
|
dirname14(targetPath),
|
|
@@ -14032,6 +16119,8 @@ function atomicReplaceUtf8(targetPath, contents) {
|
|
|
14032
16119
|
throw error;
|
|
14033
16120
|
}
|
|
14034
16121
|
}
|
|
16122
|
+
|
|
16123
|
+
// src/ports/JsonOutboundKeyDb.ts
|
|
14035
16124
|
var JsonOutboundKeyDb = class {
|
|
14036
16125
|
/**
|
|
14037
16126
|
* @param secretBox OPTIONAL reversible-secret codec. When present, a created
|
|
@@ -14174,7 +16263,7 @@ var JsonOutboundKeyDb = class {
|
|
|
14174
16263
|
}
|
|
14175
16264
|
/** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14176
16265
|
readRows() {
|
|
14177
|
-
if (!
|
|
16266
|
+
if (!existsSync17(this.keysPath)) return [];
|
|
14178
16267
|
try {
|
|
14179
16268
|
const parsed = JSON.parse(readFileSync13(this.keysPath, "utf8"));
|
|
14180
16269
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -14193,7 +16282,7 @@ function applyPolicyField(row, field, value) {
|
|
|
14193
16282
|
}
|
|
14194
16283
|
|
|
14195
16284
|
// src/ports/JsonPricingStore.ts
|
|
14196
|
-
import { existsSync as
|
|
16285
|
+
import { existsSync as existsSync18, readFileSync as readFileSync14, renameSync as renameSync10, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
|
|
14197
16286
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
14198
16287
|
var JsonPricingStore = class {
|
|
14199
16288
|
constructor(pricingPath) {
|
|
@@ -14208,7 +16297,7 @@ var JsonPricingStore = class {
|
|
|
14208
16297
|
* otherwise unusable pricing table after a crash or manual file edit.
|
|
14209
16298
|
*/
|
|
14210
16299
|
hasUsableSnapshot() {
|
|
14211
|
-
if (!
|
|
16300
|
+
if (!existsSync18(this.pricingPath)) return false;
|
|
14212
16301
|
try {
|
|
14213
16302
|
const parsed = JSON.parse(readFileSync14(this.pricingPath, "utf8"));
|
|
14214
16303
|
return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
|
|
@@ -14323,7 +16412,7 @@ var JsonPricingStore = class {
|
|
|
14323
16412
|
}
|
|
14324
16413
|
/** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14325
16414
|
readRows() {
|
|
14326
|
-
if (!
|
|
16415
|
+
if (!existsSync18(this.pricingPath)) return [];
|
|
14327
16416
|
try {
|
|
14328
16417
|
const parsed = JSON.parse(readFileSync14(this.pricingPath, "utf8"));
|
|
14329
16418
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -14355,7 +16444,7 @@ function isUsablePricingRow(value) {
|
|
|
14355
16444
|
}
|
|
14356
16445
|
|
|
14357
16446
|
// src/pricing/PricingRefreshScheduler.ts
|
|
14358
|
-
import { existsSync as
|
|
16447
|
+
import { existsSync as existsSync19, readFileSync as readFileSync15, renameSync as renameSync11, writeFileSync as writeFileSync14 } from "fs";
|
|
14359
16448
|
var EMPTY_STATE2 = {
|
|
14360
16449
|
lastAttemptAt: null,
|
|
14361
16450
|
lastSuccessAt: null,
|
|
@@ -14393,7 +16482,7 @@ var PricingRefreshScheduler = class {
|
|
|
14393
16482
|
this.timer = null;
|
|
14394
16483
|
}
|
|
14395
16484
|
getState() {
|
|
14396
|
-
if (!
|
|
16485
|
+
if (!existsSync19(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
|
|
14397
16486
|
try {
|
|
14398
16487
|
const value = JSON.parse(readFileSync15(this.statePath, "utf8"));
|
|
14399
16488
|
return {
|
|
@@ -14458,7 +16547,7 @@ function finiteOrNull(value) {
|
|
|
14458
16547
|
}
|
|
14459
16548
|
|
|
14460
16549
|
// src/ports/JsonVoucherDb.ts
|
|
14461
|
-
import { existsSync as
|
|
16550
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16, writeFileSync as writeFileSync15 } from "fs";
|
|
14462
16551
|
var JsonVoucherDb = class {
|
|
14463
16552
|
constructor(vouchersPath) {
|
|
14464
16553
|
this.vouchersPath = vouchersPath;
|
|
@@ -14536,7 +16625,7 @@ var JsonVoucherDb = class {
|
|
|
14536
16625
|
}
|
|
14537
16626
|
/** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14538
16627
|
readRows() {
|
|
14539
|
-
if (!
|
|
16628
|
+
if (!existsSync20(this.vouchersPath)) return [];
|
|
14540
16629
|
try {
|
|
14541
16630
|
const parsed = JSON.parse(readFileSync16(this.vouchersPath, "utf8"));
|
|
14542
16631
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -14550,16 +16639,18 @@ var JsonVoucherDb = class {
|
|
|
14550
16639
|
};
|
|
14551
16640
|
|
|
14552
16641
|
// src/ports/JsonSubscriptionCredentialStore.ts
|
|
14553
|
-
import { existsSync as
|
|
16642
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync6, readFileSync as readFileSync18, renameSync as renameSync12 } from "fs";
|
|
14554
16643
|
import { dirname as dirname15 } from "path";
|
|
14555
16644
|
import { getSharedAccountHealth as getSharedAccountHealth2 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
14556
16645
|
import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling3 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
14557
|
-
import { fetchUpstream as
|
|
16646
|
+
import { fetchUpstream as fetchUpstream9 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
14558
16647
|
import { getSharedIdentityStore as getSharedIdentityStore2 } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
|
|
14559
16648
|
import {
|
|
14560
16649
|
claudeOAuth as claudeOAuth2,
|
|
14561
16650
|
codexOAuth as codexOAuth2,
|
|
14562
|
-
geminiOAuth as geminiOAuth2
|
|
16651
|
+
geminiOAuth as geminiOAuth2,
|
|
16652
|
+
grokOAuth as grokOAuth2,
|
|
16653
|
+
kimiOAuth as kimiOAuth2
|
|
14563
16654
|
} from "@omnicross/subscriptions";
|
|
14564
16655
|
|
|
14565
16656
|
// src/ports/account-sync.ts
|
|
@@ -14604,7 +16695,7 @@ function findDuplicateCredentialIds(accounts) {
|
|
|
14604
16695
|
}
|
|
14605
16696
|
|
|
14606
16697
|
// src/ports/external-cli-credentials.ts
|
|
14607
|
-
import { existsSync as
|
|
16698
|
+
import { existsSync as existsSync21, readFileSync as readFileSync17 } from "fs";
|
|
14608
16699
|
import { homedir as homedir4 } from "os";
|
|
14609
16700
|
import { join as join17 } from "path";
|
|
14610
16701
|
function externalStorePath(provider, home = homedir4()) {
|
|
@@ -14657,7 +16748,7 @@ function parseCodexTokensEnvelope(raw) {
|
|
|
14657
16748
|
}
|
|
14658
16749
|
function readExternalCliCredentials(provider, home = homedir4()) {
|
|
14659
16750
|
const path2 = externalStorePath(provider, home);
|
|
14660
|
-
if (!
|
|
16751
|
+
if (!existsSync21(path2)) return null;
|
|
14661
16752
|
let raw;
|
|
14662
16753
|
try {
|
|
14663
16754
|
const parsed = JSON.parse(readFileSync17(path2, "utf8"));
|
|
@@ -14683,16 +16774,18 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14683
16774
|
* as on relay refresh egresses from the SAME proxy IP as the
|
|
14684
16775
|
* account's traffic. NOT used by any read/write path.
|
|
14685
16776
|
*/
|
|
14686
|
-
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
|
|
16777
|
+
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
|
|
14687
16778
|
this.tokensPath = tokensPath;
|
|
14688
16779
|
this.box = box;
|
|
14689
16780
|
this.fetchImpl = fetchImpl;
|
|
14690
16781
|
this.externalCliReader = externalCliReader;
|
|
16782
|
+
this.atomicReplace = atomicReplace;
|
|
14691
16783
|
}
|
|
14692
16784
|
tokensPath;
|
|
14693
16785
|
box;
|
|
14694
16786
|
fetchImpl;
|
|
14695
16787
|
externalCliReader;
|
|
16788
|
+
atomicReplace;
|
|
14696
16789
|
/**
|
|
14697
16790
|
* The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
|
|
14698
16791
|
* TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
|
|
@@ -14706,7 +16799,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14706
16799
|
* a plaintext token pair into `upstream-trace.jsonl`.
|
|
14707
16800
|
*/
|
|
14708
16801
|
buildRefreshFetch(providerId, accountId) {
|
|
14709
|
-
return this.fetchImpl ?? ((url, init) =>
|
|
16802
|
+
return this.fetchImpl ?? ((url, init) => fetchUpstream9(url, init, { providerId, accountId, redactBodies: true }));
|
|
14710
16803
|
}
|
|
14711
16804
|
/**
|
|
14712
16805
|
* In-flight refresh coalescing. OAuth refresh tokens are
|
|
@@ -14747,7 +16840,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14747
16840
|
* other hot reads. Never returns token material.
|
|
14748
16841
|
*/
|
|
14749
16842
|
getAccountProxy(providerId, accountId) {
|
|
14750
|
-
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
|
|
16843
|
+
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi" && providerId !== "grok" && providerId !== "copilot") {
|
|
14751
16844
|
return void 0;
|
|
14752
16845
|
}
|
|
14753
16846
|
return getAccountProxy(this.readConfig(), providerId, accountId);
|
|
@@ -14766,7 +16859,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14766
16859
|
const fingerprintOn = identityStore.isEnabled();
|
|
14767
16860
|
const now = Date.now();
|
|
14768
16861
|
const out = {};
|
|
14769
|
-
for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
|
|
16862
|
+
for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi", "grok", "copilot"]) {
|
|
14770
16863
|
const sanitized = sanitizeAccounts(config, provider);
|
|
14771
16864
|
if (sanitized.length === 0) continue;
|
|
14772
16865
|
for (const account of sanitized) {
|
|
@@ -14924,6 +17017,107 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14924
17017
|
}
|
|
14925
17018
|
});
|
|
14926
17019
|
}
|
|
17020
|
+
/**
|
|
17021
|
+
* Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
|
|
17022
|
+
* Kimi ROTATES the refresh token, so the response's pair is written back
|
|
17023
|
+
* whole; the account's stable `deviceId` (fingerprint header input) is
|
|
17024
|
+
* preserved. The refresh call carries the CLI fingerprint headers. HONEST
|
|
17025
|
+
* `false` when no refresh_token.
|
|
17026
|
+
*/
|
|
17027
|
+
async refreshKimiToken() {
|
|
17028
|
+
return this.coalesce("kimi:active", async () => {
|
|
17029
|
+
const config = this.readConfig();
|
|
17030
|
+
const active = getActiveAccount(config, "kimi");
|
|
17031
|
+
const kimi = active?.tokens;
|
|
17032
|
+
if (!active || !kimi?.refreshToken) return false;
|
|
17033
|
+
const capturedId = active.id;
|
|
17034
|
+
this.materializeMigration(config);
|
|
17035
|
+
const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
|
|
17036
|
+
try {
|
|
17037
|
+
const result = await kimiOAuth2.refreshAccessToken(
|
|
17038
|
+
kimi.refreshToken,
|
|
17039
|
+
refreshFetch,
|
|
17040
|
+
kimiOAuth2.kimiFingerprintHeaders(kimi.deviceId)
|
|
17041
|
+
);
|
|
17042
|
+
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
17043
|
+
const next = {
|
|
17044
|
+
...kimi,
|
|
17045
|
+
accessToken: result.accessToken,
|
|
17046
|
+
refreshToken: result.refreshToken,
|
|
17047
|
+
expiresAt,
|
|
17048
|
+
status: "authorized",
|
|
17049
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17050
|
+
errorMessage: void 0,
|
|
17051
|
+
syncWarning: void 0
|
|
17052
|
+
};
|
|
17053
|
+
this.writeBackById("kimi", capturedId, next);
|
|
17054
|
+
return true;
|
|
17055
|
+
} catch (error) {
|
|
17056
|
+
this.markExpiredById("kimi", capturedId, kimi, error);
|
|
17057
|
+
return false;
|
|
17058
|
+
}
|
|
17059
|
+
});
|
|
17060
|
+
}
|
|
17061
|
+
/**
|
|
17062
|
+
* Refresh the Grok (xAI SuperGrok) OAuth access token. The token endpoint is
|
|
17063
|
+
* resolved through OIDC discovery on every refresh (process-cached 1h by the
|
|
17064
|
+
* flow module) so a rotated endpoint document is picked up without a daemon
|
|
17065
|
+
* restart. HONEST `false` when no refresh_token.
|
|
17066
|
+
*/
|
|
17067
|
+
async refreshGrokToken() {
|
|
17068
|
+
return this.coalesce("grok:active", async () => {
|
|
17069
|
+
const config = this.readConfig();
|
|
17070
|
+
const active = getActiveAccount(config, "grok");
|
|
17071
|
+
const grok = active?.tokens;
|
|
17072
|
+
if (!active || !grok?.refreshToken) return false;
|
|
17073
|
+
const capturedId = active.id;
|
|
17074
|
+
this.materializeMigration(config);
|
|
17075
|
+
const refreshFetch = this.buildRefreshFetch("grok", capturedId);
|
|
17076
|
+
try {
|
|
17077
|
+
const tokenEndpoint = await grokOAuth2.resolveGrokTokenEndpoint(refreshFetch);
|
|
17078
|
+
const result = await grokOAuth2.refreshGrokAccessToken(grok.refreshToken, tokenEndpoint, refreshFetch);
|
|
17079
|
+
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
17080
|
+
const next = {
|
|
17081
|
+
...grok,
|
|
17082
|
+
accessToken: result.accessToken,
|
|
17083
|
+
refreshToken: result.refreshToken,
|
|
17084
|
+
expiresAt,
|
|
17085
|
+
status: "authorized",
|
|
17086
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17087
|
+
errorMessage: void 0,
|
|
17088
|
+
syncWarning: void 0
|
|
17089
|
+
};
|
|
17090
|
+
this.writeBackById("grok", capturedId, next);
|
|
17091
|
+
return true;
|
|
17092
|
+
} catch (error) {
|
|
17093
|
+
this.markExpiredById("grok", capturedId, grok, error);
|
|
17094
|
+
return false;
|
|
17095
|
+
}
|
|
17096
|
+
});
|
|
17097
|
+
}
|
|
17098
|
+
/**
|
|
17099
|
+
* "Refresh" a GitHub Copilot token — there is nothing to refresh (ghu_
|
|
17100
|
+
* tokens are long-lived with no exchange endpoint). A call here means the
|
|
17101
|
+
* strategy saw a 401 (the token was revoked); mark the account `expired`
|
|
17102
|
+
* with a re-authenticate message and return `false` (the proxy then declines
|
|
17103
|
+
* the retry instead of looping on a dead token).
|
|
17104
|
+
*/
|
|
17105
|
+
async refreshCopilotToken() {
|
|
17106
|
+
return this.coalesce("copilot:active", async () => {
|
|
17107
|
+
const config = this.readConfig();
|
|
17108
|
+
const active = getActiveAccount(config, "copilot");
|
|
17109
|
+
const copilot = active?.tokens;
|
|
17110
|
+
if (!active || !copilot?.accessToken) return false;
|
|
17111
|
+
this.materializeMigration(config);
|
|
17112
|
+
this.markExpiredById(
|
|
17113
|
+
"copilot",
|
|
17114
|
+
active.id,
|
|
17115
|
+
copilot,
|
|
17116
|
+
new Error("GitHub Copilot tokens cannot be refreshed \u2014 re-authenticate the account")
|
|
17117
|
+
);
|
|
17118
|
+
return false;
|
|
17119
|
+
});
|
|
17120
|
+
}
|
|
14927
17121
|
/**
|
|
14928
17122
|
* Refresh a SPECIFIC managed account by id (background scheduler sweep and
|
|
14929
17123
|
* account-pool resolution). It uses only that account's stored refresh
|
|
@@ -14976,7 +17170,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14976
17170
|
}
|
|
14977
17171
|
const oauth = account.tokens;
|
|
14978
17172
|
if (!oauth.accessToken) return null;
|
|
14979
|
-
if (providerId === "codex" || providerId === "gemini") {
|
|
17173
|
+
if (providerId === "codex" || providerId === "gemini" || providerId === "kimi" || providerId === "grok" || providerId === "copilot") {
|
|
14980
17174
|
const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
|
|
14981
17175
|
const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
|
|
14982
17176
|
if (expiringSoon && oauth.refreshToken) {
|
|
@@ -15065,8 +17259,35 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15065
17259
|
}
|
|
15066
17260
|
/** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
|
|
15067
17261
|
async refreshUpstream(provider, refreshToken, accountId) {
|
|
17262
|
+
const refreshFetch = this.buildRefreshFetch(provider, accountId);
|
|
17263
|
+
if (provider === "kimi") {
|
|
17264
|
+
const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
|
|
17265
|
+
const deviceId = account?.tokens?.deviceId;
|
|
17266
|
+
const r2 = await kimiOAuth2.refreshAccessToken(
|
|
17267
|
+
refreshToken,
|
|
17268
|
+
refreshFetch,
|
|
17269
|
+
kimiOAuth2.kimiFingerprintHeaders(deviceId)
|
|
17270
|
+
);
|
|
17271
|
+
return {
|
|
17272
|
+
accessToken: r2.accessToken,
|
|
17273
|
+
refreshToken: r2.refreshToken,
|
|
17274
|
+
expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
|
|
17275
|
+
};
|
|
17276
|
+
}
|
|
17277
|
+
if (provider === "grok") {
|
|
17278
|
+
const tokenEndpoint = await grokOAuth2.resolveGrokTokenEndpoint(refreshFetch);
|
|
17279
|
+
const r2 = await grokOAuth2.refreshGrokAccessToken(refreshToken, tokenEndpoint, refreshFetch);
|
|
17280
|
+
return {
|
|
17281
|
+
accessToken: r2.accessToken,
|
|
17282
|
+
refreshToken: r2.refreshToken,
|
|
17283
|
+
expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
|
|
17284
|
+
};
|
|
17285
|
+
}
|
|
17286
|
+
if (provider === "copilot") {
|
|
17287
|
+
throw new Error("GitHub Copilot tokens cannot be refreshed \u2014 re-authenticate the account");
|
|
17288
|
+
}
|
|
15068
17289
|
const flow = provider === "claude" ? claudeOAuth2 : provider === "codex" ? codexOAuth2 : geminiOAuth2;
|
|
15069
|
-
const r = await flow.refreshAccessToken(refreshToken,
|
|
17290
|
+
const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
|
|
15070
17291
|
return {
|
|
15071
17292
|
accessToken: r.accessToken,
|
|
15072
17293
|
refreshToken: r.refreshToken,
|
|
@@ -15229,42 +17450,86 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15229
17450
|
/** Write the merged config to disk as pretty JSON (mkdir parent if needed).
|
|
15230
17451
|
* Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
|
|
15231
17452
|
* `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
|
|
15232
|
-
* write incl. child 4's future refresh writes lands encrypted.
|
|
17453
|
+
* write incl. child 4's future refresh writes lands encrypted.
|
|
17454
|
+
* ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
|
|
17455
|
+
* interrupted write discards only the temp file; the prior `tokens.json`
|
|
17456
|
+
* survives byte-equal (bare `writeFileSync` truncate-writes lost every
|
|
17457
|
+
* account on a mid-write failure, 2026-09-06). */
|
|
15233
17458
|
persist(config) {
|
|
15234
17459
|
mkdirSync6(dirname15(this.tokensPath), { recursive: true });
|
|
15235
17460
|
const encrypted = encryptTokens(config, this.box);
|
|
15236
|
-
|
|
17461
|
+
this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
|
|
15237
17462
|
}
|
|
15238
17463
|
/**
|
|
15239
|
-
* Read + parse `tokens.json`,
|
|
15240
|
-
*
|
|
15241
|
-
*
|
|
17464
|
+
* Read + parse `tokens.json`, then DECRYPT the token-material fields so every
|
|
17465
|
+
* getter returns plaintext (the subscription bearer path is byte-identical).
|
|
17466
|
+
*
|
|
17467
|
+
* A MISSING file is a legitimate first-boot state → minimal `{ updatedAt: '' }`.
|
|
17468
|
+
* A file that EXISTS but cannot be parsed as a JSON object is CORRUPT →
|
|
17469
|
+
* `quarantineCorrupt` moves it aside (once) before the empty config is
|
|
17470
|
+
* returned, so the unreadable accounts survive for manual recovery.
|
|
15242
17471
|
*
|
|
15243
|
-
* The
|
|
15244
|
-
*
|
|
15245
|
-
*
|
|
15246
|
-
*
|
|
15247
|
-
*
|
|
15248
|
-
*
|
|
15249
|
-
* `config.ts loadConfig`, which decrypts outside its parse try.
|
|
17472
|
+
* The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
|
|
17473
|
+
* tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
|
|
17474
|
+
* error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
|
|
17475
|
+
* decrypt would report "no tokens" and silently send the WRONG bearer
|
|
17476
|
+
* upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
|
|
17477
|
+
* its parse try.
|
|
15250
17478
|
*/
|
|
15251
17479
|
readConfig() {
|
|
15252
|
-
if (!
|
|
17480
|
+
if (!existsSync22(this.tokensPath)) return { updatedAt: "" };
|
|
15253
17481
|
let parsed;
|
|
15254
17482
|
try {
|
|
15255
17483
|
const raw = JSON.parse(readFileSync18(this.tokensPath, "utf8"));
|
|
15256
|
-
|
|
17484
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
17485
|
+
return this.quarantineCorrupt("parsed JSON is not an object");
|
|
17486
|
+
}
|
|
17487
|
+
parsed = raw;
|
|
15257
17488
|
} catch {
|
|
15258
|
-
|
|
17489
|
+
return this.quarantineCorrupt("unparseable JSON");
|
|
15259
17490
|
}
|
|
15260
|
-
if (!parsed) return { updatedAt: "" };
|
|
15261
17491
|
const decrypted = decryptTokens(parsed, this.box);
|
|
15262
17492
|
return migrateLazily(decrypted);
|
|
15263
17493
|
}
|
|
17494
|
+
/** One-shot latch: a corrupt file is quarantined (or found unmovable) at
|
|
17495
|
+
* most once per process, so the hot read path never re-attempts or re-logs. */
|
|
17496
|
+
corruptQuarantined = false;
|
|
17497
|
+
/**
|
|
17498
|
+
* Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
|
|
17499
|
+
*
|
|
17500
|
+
* Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
|
|
17501
|
+
* logs loudly (the daemon's stderr log; secret-free — reason + paths only).
|
|
17502
|
+
* The daemon KEEPS SERVING (API-key routing is unaffected; subscription
|
|
17503
|
+
* routing reports no credential, same as an absent file) while the corrupt
|
|
17504
|
+
* bytes survive for manual recovery — and, critically, the NEXT persist
|
|
17505
|
+
* (e.g. the user re-logging in) can no longer overwrite the only copy of
|
|
17506
|
+
* the old accounts, which is exactly how the 2026-09-06 incident turned a
|
|
17507
|
+
* recoverable truncated file into permanent account loss.
|
|
17508
|
+
*
|
|
17509
|
+
* Best-effort: if the rename fails (file locked, permissions), the corrupt
|
|
17510
|
+
* file is left in place and every later read still tolerates it as empty;
|
|
17511
|
+
* the latch still trips so the attempt + log happen exactly once.
|
|
17512
|
+
*/
|
|
17513
|
+
quarantineCorrupt(reason) {
|
|
17514
|
+
if (!this.corruptQuarantined) {
|
|
17515
|
+
this.corruptQuarantined = true;
|
|
17516
|
+
const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
17517
|
+
let moved = false;
|
|
17518
|
+
try {
|
|
17519
|
+
renameSync12(this.tokensPath, backup);
|
|
17520
|
+
moved = true;
|
|
17521
|
+
} catch {
|
|
17522
|
+
}
|
|
17523
|
+
console.error(
|
|
17524
|
+
`[JsonSubscriptionCredentialStore] tokens.json is corrupt (${reason}); ` + (moved ? `moved to '${backup}' and treated as empty \u2014 recover accounts from that backup before re-adding them` : `could not move '${this.tokensPath}' \u2014 treated as empty`)
|
|
17525
|
+
);
|
|
17526
|
+
}
|
|
17527
|
+
return { updatedAt: "" };
|
|
17528
|
+
}
|
|
15264
17529
|
};
|
|
15265
17530
|
|
|
15266
17531
|
// src/AccountHealthProbeScheduler.ts
|
|
15267
|
-
import { fetchUpstream as
|
|
17532
|
+
import { fetchUpstream as fetchUpstream10 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
15268
17533
|
|
|
15269
17534
|
// src/probe/CodexGenerationProbe.ts
|
|
15270
17535
|
import {
|
|
@@ -15406,7 +17671,20 @@ var PROVIDER_PROBE_PLANS = {
|
|
|
15406
17671
|
// billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
|
|
15407
17672
|
codex: { kind: "local" },
|
|
15408
17673
|
gemini: { kind: "local" },
|
|
15409
|
-
opencodego: { kind: "local" }
|
|
17674
|
+
opencodego: { kind: "local" },
|
|
17675
|
+
// Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
|
|
17676
|
+
// collector uses it), but the probe path also needs the fingerprint headers —
|
|
17677
|
+
// keep the probe local until the collector covers the health surface.
|
|
17678
|
+
kimi: { kind: "local" },
|
|
17679
|
+
// Grok's billing proxy is a verified FREE authed GET (the allowance collector
|
|
17680
|
+
// uses it) but it REJECTS non-OAuth credentials and sits on a separate host
|
|
17681
|
+
// with its own product-gate header — keep the probe local, the collector
|
|
17682
|
+
// owns the health surface.
|
|
17683
|
+
grok: { kind: "local" },
|
|
17684
|
+
// The Copilot quota endpoint (copilot_internal/user) is a verified FREE
|
|
17685
|
+
// authed GET but lives on api.github.com with its own auth dialect and a
|
|
17686
|
+
// monthly-only window — the allowance collector owns the health surface.
|
|
17687
|
+
copilot: { kind: "local" }
|
|
15410
17688
|
};
|
|
15411
17689
|
function probePlanFor(providerId) {
|
|
15412
17690
|
return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
|
|
@@ -15428,7 +17706,7 @@ var AccountHealthProbeScheduler = class {
|
|
|
15428
17706
|
this.logger = logger;
|
|
15429
17707
|
this.config = config;
|
|
15430
17708
|
this.now = opts.now ?? Date.now;
|
|
15431
|
-
this.fetchImpl = opts.fetchImpl ??
|
|
17709
|
+
this.fetchImpl = opts.fetchImpl ?? fetchUpstream10;
|
|
15432
17710
|
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
15433
17711
|
this.planFor = opts.planFor ?? probePlanFor;
|
|
15434
17712
|
}
|
|
@@ -15772,13 +18050,13 @@ var AccountHealthSweeper = class {
|
|
|
15772
18050
|
};
|
|
15773
18051
|
|
|
15774
18052
|
// src/audit/AuditPruneSweeper.ts
|
|
15775
|
-
import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as
|
|
18053
|
+
import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync25, readdirSync as readdirSync6, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
|
|
15776
18054
|
import { join as join20 } from "path";
|
|
15777
18055
|
import { pipeline } from "stream/promises";
|
|
15778
18056
|
import { createGzip } from "zlib";
|
|
15779
18057
|
|
|
15780
18058
|
// src/audit/auditDictionary.ts
|
|
15781
|
-
import { existsSync as
|
|
18059
|
+
import { existsSync as existsSync23, readdirSync as readdirSync4, readFileSync as readFileSync19, renameSync as renameSync13, unlinkSync as unlinkSync12, writeFileSync as writeFileSync16 } from "fs";
|
|
15782
18060
|
import { join as join18 } from "path";
|
|
15783
18061
|
|
|
15784
18062
|
// src/audit/auditBodyStore.ts
|
|
@@ -16036,9 +18314,9 @@ function chooseDictionary(anchors) {
|
|
|
16036
18314
|
var EMPTY = { shards: 0, anchors: 0, savedBytes: 0 };
|
|
16037
18315
|
function compactAuditDay(dayPath) {
|
|
16038
18316
|
const bodiesPath = join18(dayPath, AUDIT_BODIES_DIR);
|
|
16039
|
-
if (!
|
|
18317
|
+
if (!existsSync23(bodiesPath)) return EMPTY;
|
|
16040
18318
|
const dictPath = join18(bodiesPath, AUDIT_DICT_FILE);
|
|
16041
|
-
if (
|
|
18319
|
+
if (existsSync23(dictPath) || existsSync23(`${dictPath}.gz`)) return EMPTY;
|
|
16042
18320
|
const shardFiles = plainShards(bodiesPath);
|
|
16043
18321
|
if (shardFiles.length < 2) return EMPTY;
|
|
16044
18322
|
const loaded = /* @__PURE__ */ new Map();
|
|
@@ -16063,7 +18341,7 @@ function compactAuditDay(dayPath) {
|
|
|
16063
18341
|
ts: 0,
|
|
16064
18342
|
req: { base: null, anchor: "dict", pre: 0, suf: 0, ins: dictionary }
|
|
16065
18343
|
};
|
|
16066
|
-
|
|
18344
|
+
writeFileSync16(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
|
|
16067
18345
|
const result = { shards: 0, anchors: 0, savedBytes: 0 };
|
|
16068
18346
|
for (const [file, entries] of loaded) {
|
|
16069
18347
|
let changed = false;
|
|
@@ -16083,11 +18361,11 @@ function compactAuditDay(dayPath) {
|
|
|
16083
18361
|
const target = join18(bodiesPath, file);
|
|
16084
18362
|
const temp = `${target}.compacting`;
|
|
16085
18363
|
try {
|
|
16086
|
-
|
|
16087
|
-
|
|
18364
|
+
writeFileSync16(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
|
|
18365
|
+
renameSync13(temp, target);
|
|
16088
18366
|
} catch {
|
|
16089
18367
|
try {
|
|
16090
|
-
if (
|
|
18368
|
+
if (existsSync23(temp)) unlinkSync12(temp);
|
|
16091
18369
|
} catch {
|
|
16092
18370
|
}
|
|
16093
18371
|
continue;
|
|
@@ -16106,7 +18384,7 @@ function compactAuditDay(dayPath) {
|
|
|
16106
18384
|
}
|
|
16107
18385
|
function compactAllClosedAuditDays(auditDir, now = Date.now) {
|
|
16108
18386
|
const run = { days: 0, shards: 0, savedBytes: 0 };
|
|
16109
|
-
if (!
|
|
18387
|
+
if (!existsSync23(auditDir)) return run;
|
|
16110
18388
|
const today = auditDayDirName(now());
|
|
16111
18389
|
let names;
|
|
16112
18390
|
try {
|
|
@@ -16131,11 +18409,11 @@ function compactAllClosedAuditDays(auditDir, now = Date.now) {
|
|
|
16131
18409
|
// src/audit/auditStats.ts
|
|
16132
18410
|
import {
|
|
16133
18411
|
createReadStream as createReadStream2,
|
|
16134
|
-
existsSync as
|
|
18412
|
+
existsSync as existsSync24,
|
|
16135
18413
|
readFileSync as readFileSync20,
|
|
16136
18414
|
readdirSync as readdirSync5,
|
|
16137
18415
|
statSync as statSync5,
|
|
16138
|
-
writeFileSync as
|
|
18416
|
+
writeFileSync as writeFileSync17
|
|
16139
18417
|
} from "fs";
|
|
16140
18418
|
import { basename as basename9, dirname as dirname16, join as join19 } from "path";
|
|
16141
18419
|
var SIDECAR_VERSION = 1;
|
|
@@ -16145,7 +18423,7 @@ function auditStatsFileName(auditFile) {
|
|
|
16145
18423
|
return auditFile.replace(/\.jsonl$/, ".stats.json");
|
|
16146
18424
|
}
|
|
16147
18425
|
function readPersisted(path2) {
|
|
16148
|
-
if (!
|
|
18426
|
+
if (!existsSync24(path2)) return null;
|
|
16149
18427
|
try {
|
|
16150
18428
|
const value = JSON.parse(readFileSync20(path2, "utf8"));
|
|
16151
18429
|
if (value.version !== SIDECAR_VERSION || !Number.isSafeInteger(value.auditBytes) || (value.auditBytes ?? -1) < 0 || !Number.isSafeInteger(value.requestCount) || (value.requestCount ?? -1) < 0 || !Number.isSafeInteger(value.errorCount) || (value.errorCount ?? -1) < 0 || (value.errorCount ?? 0) > (value.requestCount ?? -1) || typeof value.complete !== "boolean" || value.minTs !== null && !Number.isFinite(value.minTs) || value.maxTs !== null && !Number.isFinite(value.maxTs)) {
|
|
@@ -16177,7 +18455,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
|
|
|
16177
18455
|
minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
|
|
16178
18456
|
maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
|
|
16179
18457
|
};
|
|
16180
|
-
|
|
18458
|
+
writeFileSync17(statsPath, JSON.stringify(next), "utf8");
|
|
16181
18459
|
}
|
|
16182
18460
|
function queryCovers(stats, from, to) {
|
|
16183
18461
|
return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
|
|
@@ -16288,7 +18566,7 @@ function mergePersistedStats(previous, appended) {
|
|
|
16288
18566
|
};
|
|
16289
18567
|
}
|
|
16290
18568
|
async function readAuditStats(auditDir, query2 = {}) {
|
|
16291
|
-
if (!
|
|
18569
|
+
if (!existsSync24(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
|
|
16292
18570
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
16293
18571
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
16294
18572
|
let sources;
|
|
@@ -16301,7 +18579,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16301
18579
|
auditPath: join19(auditDir, name),
|
|
16302
18580
|
statsPath: join19(auditDir, auditStatsFileName(name))
|
|
16303
18581
|
}
|
|
16304
|
-
).filter((source) =>
|
|
18582
|
+
).filter((source) => existsSync24(source.auditPath));
|
|
16305
18583
|
} catch {
|
|
16306
18584
|
return { requestCount: 0, errorCount: 0, complete: false };
|
|
16307
18585
|
}
|
|
@@ -16327,7 +18605,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16327
18605
|
total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
|
|
16328
18606
|
total.complete = total.complete && scanned.filtered.complete;
|
|
16329
18607
|
const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
|
|
16330
|
-
if (current.complete)
|
|
18608
|
+
if (current.complete) writeFileSync17(statsPath, JSON.stringify(current), "utf8");
|
|
16331
18609
|
} catch {
|
|
16332
18610
|
total.complete = false;
|
|
16333
18611
|
}
|
|
@@ -16336,7 +18614,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16336
18614
|
}
|
|
16337
18615
|
|
|
16338
18616
|
// src/audit/AuditPruneSweeper.ts
|
|
16339
|
-
var
|
|
18617
|
+
var DAY_MS4 = 24 * 60 * 6e4;
|
|
16340
18618
|
var SWEEP_INTERVAL_MS2 = 60 * 6e4;
|
|
16341
18619
|
var ARCHIVE_BATCH = 64;
|
|
16342
18620
|
var AuditPruneSweeper = class {
|
|
@@ -16399,8 +18677,8 @@ var AuditPruneSweeper = class {
|
|
|
16399
18677
|
if (!this.config.enabled || this.sweeping) return 0;
|
|
16400
18678
|
this.sweeping = true;
|
|
16401
18679
|
try {
|
|
16402
|
-
if (!
|
|
16403
|
-
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) *
|
|
18680
|
+
if (!existsSync25(this.auditDir)) return 0;
|
|
18681
|
+
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS4;
|
|
16404
18682
|
let removed = 0;
|
|
16405
18683
|
for (const name of readdirSync6(this.auditDir)) {
|
|
16406
18684
|
const dateMs = auditFileDateMs(name);
|
|
@@ -16411,7 +18689,7 @@ var AuditPruneSweeper = class {
|
|
|
16411
18689
|
} else {
|
|
16412
18690
|
unlinkSync13(join20(this.auditDir, name));
|
|
16413
18691
|
const statsPath = join20(this.auditDir, auditStatsFileName(name));
|
|
16414
|
-
if (
|
|
18692
|
+
if (existsSync25(statsPath)) unlinkSync13(statsPath);
|
|
16415
18693
|
}
|
|
16416
18694
|
removed += 1;
|
|
16417
18695
|
} catch (error) {
|
|
@@ -16441,7 +18719,7 @@ var AuditPruneSweeper = class {
|
|
|
16441
18719
|
if (!this.config.enabled || this.archiving) return 0;
|
|
16442
18720
|
this.archiving = true;
|
|
16443
18721
|
try {
|
|
16444
|
-
if (!
|
|
18722
|
+
if (!existsSync25(this.auditDir)) return 0;
|
|
16445
18723
|
const today = this.todayMidnight();
|
|
16446
18724
|
let compressed = 0;
|
|
16447
18725
|
for (const name of readdirSync6(this.auditDir)) {
|
|
@@ -16495,7 +18773,7 @@ var AuditPruneSweeper = class {
|
|
|
16495
18773
|
const source = join20(bodiesPath, shard);
|
|
16496
18774
|
const target = `${source}.gz`;
|
|
16497
18775
|
try {
|
|
16498
|
-
if (
|
|
18776
|
+
if (existsSync25(target)) {
|
|
16499
18777
|
unlinkSync13(source);
|
|
16500
18778
|
continue;
|
|
16501
18779
|
}
|
|
@@ -16504,7 +18782,7 @@ var AuditPruneSweeper = class {
|
|
|
16504
18782
|
compressed += 1;
|
|
16505
18783
|
} catch (error) {
|
|
16506
18784
|
try {
|
|
16507
|
-
if (
|
|
18785
|
+
if (existsSync25(target)) unlinkSync13(target);
|
|
16508
18786
|
} catch {
|
|
16509
18787
|
}
|
|
16510
18788
|
this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
|
|
@@ -16657,7 +18935,7 @@ async function closeAll(writers) {
|
|
|
16657
18935
|
// src/usage/UsagePruneSweeper.ts
|
|
16658
18936
|
import { unlink as unlink3 } from "fs/promises";
|
|
16659
18937
|
import { join as join22 } from "path";
|
|
16660
|
-
var
|
|
18938
|
+
var DAY_MS5 = 24 * 60 * 6e4;
|
|
16661
18939
|
var SWEEP_INTERVAL_MS3 = 60 * 6e4;
|
|
16662
18940
|
var DEFAULT_USAGE_RETENTION_DAYS = 90;
|
|
16663
18941
|
var UsagePruneSweeper = class {
|
|
@@ -16714,7 +18992,7 @@ var UsagePruneSweeper = class {
|
|
|
16714
18992
|
this.sweeping = true;
|
|
16715
18993
|
try {
|
|
16716
18994
|
const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
|
|
16717
|
-
const cutoff = this.todayMidnight() - (retentionDays - 1) *
|
|
18995
|
+
const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS5;
|
|
16718
18996
|
let removed = 0;
|
|
16719
18997
|
for (const entry of await listUsageDays(this.usageDir)) {
|
|
16720
18998
|
if (!entry.hasShard) continue;
|
|
@@ -16772,7 +19050,7 @@ var UsagePruneSweeper = class {
|
|
|
16772
19050
|
};
|
|
16773
19051
|
|
|
16774
19052
|
// src/audit/auditBodyReader.ts
|
|
16775
|
-
import { existsSync as
|
|
19053
|
+
import { existsSync as existsSync26, readdirSync as readdirSync7, readFileSync as readFileSync21, statSync as statSync7 } from "fs";
|
|
16776
19054
|
import { join as join23 } from "path";
|
|
16777
19055
|
import { gunzipSync } from "zlib";
|
|
16778
19056
|
|
|
@@ -16836,7 +19114,7 @@ function forEachLineFromTail(path2, onLine) {
|
|
|
16836
19114
|
function candidateDays(auditDir, ts) {
|
|
16837
19115
|
if (typeof ts === "number" && Number.isFinite(ts)) {
|
|
16838
19116
|
const named = auditDayDirName(ts);
|
|
16839
|
-
if (
|
|
19117
|
+
if (existsSync26(join23(auditDir, named))) return [named];
|
|
16840
19118
|
}
|
|
16841
19119
|
try {
|
|
16842
19120
|
return readdirSync7(auditDir).filter(isAuditDayDir).sort().reverse();
|
|
@@ -16847,9 +19125,9 @@ function candidateDays(auditDir, ts) {
|
|
|
16847
19125
|
function readShard(auditDir, day, sessionKey) {
|
|
16848
19126
|
const base = join23(auditDir, day, AUDIT_BODIES_DIR, auditBodyFileName(sessionKey));
|
|
16849
19127
|
try {
|
|
16850
|
-
if (
|
|
19128
|
+
if (existsSync26(base)) return readFileSync21(base, "utf8");
|
|
16851
19129
|
const gz = `${base}.gz`;
|
|
16852
|
-
if (
|
|
19130
|
+
if (existsSync26(gz)) return gunzipSync(readFileSync21(gz)).toString("utf8");
|
|
16853
19131
|
} catch {
|
|
16854
19132
|
return null;
|
|
16855
19133
|
}
|
|
@@ -16882,8 +19160,8 @@ function withDictionary(auditDir, day, entries) {
|
|
|
16882
19160
|
const base = join23(auditDir, day, AUDIT_BODIES_DIR, AUDIT_DICT_FILE);
|
|
16883
19161
|
let raw = null;
|
|
16884
19162
|
try {
|
|
16885
|
-
if (
|
|
16886
|
-
else if (
|
|
19163
|
+
if (existsSync26(base)) raw = readFileSync21(base, "utf8");
|
|
19164
|
+
else if (existsSync26(`${base}.gz`)) raw = gunzipSync(readFileSync21(`${base}.gz`)).toString("utf8");
|
|
16887
19165
|
} catch {
|
|
16888
19166
|
return entries;
|
|
16889
19167
|
}
|
|
@@ -16916,7 +19194,7 @@ function reconstructRequest(entries, entry) {
|
|
|
16916
19194
|
}
|
|
16917
19195
|
function readAuditBody(auditDir, query2) {
|
|
16918
19196
|
if (!isSafeSessionKey(query2.sessionKey) || !query2.id) return {};
|
|
16919
|
-
if (!
|
|
19197
|
+
if (!existsSync26(auditDir)) return {};
|
|
16920
19198
|
for (const day of candidateDays(auditDir, query2.ts)) {
|
|
16921
19199
|
const raw = readShard(auditDir, day, query2.sessionKey);
|
|
16922
19200
|
if (raw === null) continue;
|
|
@@ -16963,7 +19241,7 @@ function readLegacyInlineBody(auditDir, id) {
|
|
|
16963
19241
|
}
|
|
16964
19242
|
|
|
16965
19243
|
// src/audit/auditReader.ts
|
|
16966
|
-
import { existsSync as
|
|
19244
|
+
import { existsSync as existsSync27, readdirSync as readdirSync8 } from "fs";
|
|
16967
19245
|
import { join as join24 } from "path";
|
|
16968
19246
|
var DEFAULT_LIMIT = 200;
|
|
16969
19247
|
var MAX_LIMIT = 2e3;
|
|
@@ -16981,7 +19259,7 @@ function daySources(auditDir) {
|
|
|
16981
19259
|
if (dateMs === null) continue;
|
|
16982
19260
|
if (AUDIT_DAY_DIR_RE.test(name)) {
|
|
16983
19261
|
const path2 = join24(auditDir, name, AUDIT_META_FILE);
|
|
16984
|
-
if (
|
|
19262
|
+
if (existsSync27(path2)) sources.push({ path: path2, dateMs });
|
|
16985
19263
|
} else if (AUDIT_FILE_RE.test(name)) {
|
|
16986
19264
|
sources.push({ path: join24(auditDir, name), dateMs });
|
|
16987
19265
|
}
|
|
@@ -16999,7 +19277,7 @@ function toMetaRecord(record) {
|
|
|
16999
19277
|
return { ...meta, hasBody: true };
|
|
17000
19278
|
}
|
|
17001
19279
|
function readAuditRecords(auditDir, query2 = {}) {
|
|
17002
|
-
if (!
|
|
19280
|
+
if (!existsSync27(auditDir)) return [];
|
|
17003
19281
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
17004
19282
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
17005
19283
|
const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
|
|
@@ -17027,7 +19305,7 @@ function readAuditRecords(auditDir, query2 = {}) {
|
|
|
17027
19305
|
}
|
|
17028
19306
|
|
|
17029
19307
|
// src/audit/AuditWriter.ts
|
|
17030
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
19308
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync28, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
|
|
17031
19309
|
import { join as join25 } from "path";
|
|
17032
19310
|
var AuditWriter = class {
|
|
17033
19311
|
constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
|
|
@@ -17085,7 +19363,7 @@ var AuditWriter = class {
|
|
|
17085
19363
|
const { requestBody: _req, responseBody: _res, ...meta } = record;
|
|
17086
19364
|
const file = join25(dayPath, AUDIT_META_FILE);
|
|
17087
19365
|
const line = JSON.stringify(meta) + "\n";
|
|
17088
|
-
const bytesBefore =
|
|
19366
|
+
const bytesBefore = existsSync28(file) ? statSync8(file).size : 0;
|
|
17089
19367
|
appendFileSync2(file, line, "utf8");
|
|
17090
19368
|
try {
|
|
17091
19369
|
updateAuditStatsAfterAppend(
|
|
@@ -17133,7 +19411,7 @@ var AuditWriter = class {
|
|
|
17133
19411
|
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync8 } from "fs";
|
|
17134
19412
|
import { createHmac as createHmac5 } from "crypto";
|
|
17135
19413
|
import { join as join26 } from "path";
|
|
17136
|
-
import { fetchUpstream as
|
|
19414
|
+
import { fetchUpstream as fetchUpstream11 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
17137
19415
|
|
|
17138
19416
|
// src/billing/billingFiles.ts
|
|
17139
19417
|
var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
|
|
@@ -17156,7 +19434,7 @@ var BillingPublisher = class {
|
|
|
17156
19434
|
constructor(billingDir, logger, opts = {}) {
|
|
17157
19435
|
this.billingDir = billingDir;
|
|
17158
19436
|
this.logger = logger;
|
|
17159
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) =>
|
|
19437
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream11(url, init));
|
|
17160
19438
|
this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
|
|
17161
19439
|
this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
|
|
17162
19440
|
this.now = opts.now ?? Date.now;
|
|
@@ -17269,11 +19547,11 @@ var BillingPublisher = class {
|
|
|
17269
19547
|
};
|
|
17270
19548
|
|
|
17271
19549
|
// src/billing/billingReader.ts
|
|
17272
|
-
import { existsSync as
|
|
19550
|
+
import { existsSync as existsSync29, readdirSync as readdirSync9, readFileSync as readFileSync22 } from "fs";
|
|
17273
19551
|
import { join as join27 } from "path";
|
|
17274
19552
|
function readBillingLedger(billingDir) {
|
|
17275
19553
|
const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
|
|
17276
|
-
if (!
|
|
19554
|
+
if (!existsSync29(billingDir)) return view;
|
|
17277
19555
|
let files;
|
|
17278
19556
|
try {
|
|
17279
19557
|
files = readdirSync9(billingDir);
|
|
@@ -17406,7 +19684,7 @@ var BillingRetrySweeper = class {
|
|
|
17406
19684
|
// src/TokenRefreshScheduler.ts
|
|
17407
19685
|
var REFRESH_LEAD_MS2 = 5 * 6e4;
|
|
17408
19686
|
var SWEEP_INTERVAL_MS5 = 6e4;
|
|
17409
|
-
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
|
|
19687
|
+
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi", "grok", "copilot"];
|
|
17410
19688
|
var TokenRefreshScheduler = class {
|
|
17411
19689
|
constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
|
|
17412
19690
|
this.store = store;
|
|
@@ -17489,6 +19767,14 @@ var TokenRefreshScheduler = class {
|
|
|
17489
19767
|
return this.store.refreshCodexToken();
|
|
17490
19768
|
case "gemini":
|
|
17491
19769
|
return this.store.refreshGeminiToken();
|
|
19770
|
+
case "kimi":
|
|
19771
|
+
return this.store.refreshKimiToken();
|
|
19772
|
+
case "grok":
|
|
19773
|
+
return this.store.refreshGrokToken();
|
|
19774
|
+
// ghu_ tokens never near-expire (far-future expiresAt), so the sweep
|
|
19775
|
+
// never reaches this — the branch exists for union totality.
|
|
19776
|
+
case "copilot":
|
|
19777
|
+
return this.store.refreshCopilotToken();
|
|
17492
19778
|
}
|
|
17493
19779
|
}
|
|
17494
19780
|
};
|
|
@@ -17567,7 +19853,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
|
|
|
17567
19853
|
|
|
17568
19854
|
// src/webhook/WebhookDispatcher.ts
|
|
17569
19855
|
import { createHmac as createHmac6 } from "crypto";
|
|
17570
|
-
import { fetchUpstream as
|
|
19856
|
+
import { fetchUpstream as fetchUpstream12 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
17571
19857
|
var WEBHOOK_MAX_ATTEMPTS = 3;
|
|
17572
19858
|
var WEBHOOK_QUEUE_MAX = 1e3;
|
|
17573
19859
|
var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
|
|
@@ -17587,7 +19873,7 @@ var WebhookDispatcher = class {
|
|
|
17587
19873
|
sleep;
|
|
17588
19874
|
now;
|
|
17589
19875
|
constructor(opts = {}) {
|
|
17590
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) =>
|
|
19876
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream12(url, init));
|
|
17591
19877
|
this.logger = opts.logger;
|
|
17592
19878
|
this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
|
|
17593
19879
|
this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
|
|
@@ -17673,8 +19959,8 @@ var WebhookDispatcher = class {
|
|
|
17673
19959
|
signal: AbortSignal.timeout(this.timeoutMs)
|
|
17674
19960
|
});
|
|
17675
19961
|
return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
|
|
17676
|
-
} catch (
|
|
17677
|
-
return { ok: false, error:
|
|
19962
|
+
} catch (err8) {
|
|
19963
|
+
return { ok: false, error: err8 instanceof Error ? err8.message : String(err8) };
|
|
17678
19964
|
}
|
|
17679
19965
|
}
|
|
17680
19966
|
/**
|
|
@@ -17816,7 +20102,7 @@ function buildDaemon(config, paths) {
|
|
|
17816
20102
|
setSecretBox(secretBox3);
|
|
17817
20103
|
setSecretBox2(secretBox3);
|
|
17818
20104
|
const decryptedConfig = decryptConfigSecrets(config, secretBox3);
|
|
17819
|
-
const accountAllowanceStore = new
|
|
20105
|
+
const accountAllowanceStore = new AccountAllowanceStore8(
|
|
17820
20106
|
Date.now,
|
|
17821
20107
|
void 0,
|
|
17822
20108
|
new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
|
|
@@ -17861,6 +20147,7 @@ function buildDaemon(config, paths) {
|
|
|
17861
20147
|
);
|
|
17862
20148
|
setGeminiCodeAssistResolver(getGeminiCodeAssistProjectResolver());
|
|
17863
20149
|
const autoDisableStore = new AutoDisableStore();
|
|
20150
|
+
const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
|
|
17864
20151
|
const apiKeyPool = new ApiKeyPoolService(
|
|
17865
20152
|
createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
|
|
17866
20153
|
resolveEnvKey,
|
|
@@ -17877,7 +20164,7 @@ function buildDaemon(config, paths) {
|
|
|
17877
20164
|
const pricingEngine = new PricingEngine(pricingStore, logger, {
|
|
17878
20165
|
// Catalog egress follows the same global/env proxy policy as every other
|
|
17879
20166
|
// daemon upstream call; no provider/account override applies here.
|
|
17880
|
-
fetchImpl: ((input, init) =>
|
|
20167
|
+
fetchImpl: ((input, init) => fetchUpstream13(String(input), init ?? {}))
|
|
17881
20168
|
});
|
|
17882
20169
|
const pricingRefreshScheduler = new PricingRefreshScheduler(
|
|
17883
20170
|
pricingEngine,
|
|
@@ -18141,6 +20428,11 @@ function buildDaemon(config, paths) {
|
|
|
18141
20428
|
// values themselves NEVER leave (masked via `maskProviderApiKey`).
|
|
18142
20429
|
apiKeyPool,
|
|
18143
20430
|
autoDisableStore,
|
|
20431
|
+
// BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
|
|
20432
|
+
// read-through cached same-key usage probe surfaced on the keys view. The
|
|
20433
|
+
// key plaintext is resolved + decrypted inside the service and never
|
|
20434
|
+
// crosses back out.
|
|
20435
|
+
providerKeyQuota: providerKeyQuotaService,
|
|
18144
20436
|
// Interactive OAuth login over admin HTTP (app-parity child 4, design
|
|
18145
20437
|
// D1/D2-a). The in-memory pending-session store (NEVER serialized), the
|
|
18146
20438
|
// injected token-exchange fetch (global `fetch` here; mocked in tests), and a
|
|
@@ -18157,7 +20449,7 @@ function buildDaemon(config, paths) {
|
|
|
18157
20449
|
// — `server.proxy.byProvider[...]` was silently skipped — and the call was
|
|
18158
20450
|
// excluded from the upstream trace, so a failing login left no evidence.
|
|
18159
20451
|
// `redactBodies` keeps the code/verifier + minted token out of that trace.
|
|
18160
|
-
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) =>
|
|
20452
|
+
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream13(url, init, { providerId, redactBodies: true }),
|
|
18161
20453
|
subscriptionAccountAppender: credentialStore,
|
|
18162
20454
|
// Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
|
|
18163
20455
|
// + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
|
|
@@ -18165,6 +20457,13 @@ function buildDaemon(config, paths) {
|
|
|
18165
20457
|
// can inject a mock so no real port is bound.
|
|
18166
20458
|
codexSessions: new CodexOAuthSessionStore(),
|
|
18167
20459
|
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
|
|
20460
|
+
// Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
|
|
20461
|
+
// paste; the app shows the verification URL + user code and polls the
|
|
20462
|
+
// token-free status). Token captured + persisted daemon-side.
|
|
20463
|
+
kimiSessions: new CodexOAuthSessionStore(),
|
|
20464
|
+
// Grok interactive OAuth — the same async DEVICE-CODE shape as kimi.
|
|
20465
|
+
grokSessions: new CodexOAuthSessionStore(),
|
|
20466
|
+
copilotSessions: new CodexOAuthSessionStore(),
|
|
18168
20467
|
// Migration pack (app-parity child 6, design D2/D3) — the concrete credential
|
|
18169
20468
|
// store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
|
|
18170
20469
|
// the multi-account append (`appendProviderAccount`, import re-encrypts at-
|
|
@@ -18223,7 +20522,7 @@ function buildDaemon(config, paths) {
|
|
|
18223
20522
|
});
|
|
18224
20523
|
const webhookDispatcher = new WebhookDispatcher({
|
|
18225
20524
|
logger,
|
|
18226
|
-
fetchImpl: (url, init) =>
|
|
20525
|
+
fetchImpl: (url, init) => fetchUpstream13(url, init)
|
|
18227
20526
|
});
|
|
18228
20527
|
setWebhookRuntime(webhookDispatcher, getSharedAccountHealth4());
|
|
18229
20528
|
const auditWriter = new AuditWriter(auditDir, logger);
|
|
@@ -18320,7 +20619,7 @@ function resetDaemonSingletonsForTests() {
|
|
|
18320
20619
|
}
|
|
18321
20620
|
function isTokensStoreReadable(tokensPath) {
|
|
18322
20621
|
try {
|
|
18323
|
-
if (!
|
|
20622
|
+
if (!existsSync30(tokensPath)) return true;
|
|
18324
20623
|
accessSync(tokensPath, fsConstants.R_OK);
|
|
18325
20624
|
return true;
|
|
18326
20625
|
} catch {
|