@omnicross/daemon 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +1707 -303
- package/dist/cli.js +1644 -229
- package/dist/index.cjs +1655 -293
- package/dist/index.d.cts +334 -28
- package/dist/index.d.ts +334 -28
- package/dist/index.js +1598 -230
- 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 AccountAllowanceStore6,
|
|
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 fetchUpstream11, 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,84 @@ 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
|
+
|
|
158
233
|
// src/allowance/AccountAllowanceService.ts
|
|
159
234
|
import {
|
|
160
|
-
getSharedAccountAllowanceStore as
|
|
235
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore5
|
|
161
236
|
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
162
237
|
import {
|
|
163
238
|
getSharedAccountAllowanceScheduling
|
|
@@ -191,13 +266,11 @@ function secondsUntil(instant, now) {
|
|
|
191
266
|
function windowFromPayload(id, payload, now) {
|
|
192
267
|
const usedPercent = finitePercent(payload?.utilization);
|
|
193
268
|
const resetsAt = isoInstant(payload?.resets_at);
|
|
194
|
-
const isSonnet = id === "seven-day-sonnet";
|
|
195
269
|
const isFiveHour = id === "five-hour";
|
|
196
270
|
return {
|
|
197
271
|
id,
|
|
198
|
-
label: isFiveHour ? "5 hours" :
|
|
199
|
-
scope:
|
|
200
|
-
modelFamily: isSonnet ? "sonnet" : void 0,
|
|
272
|
+
label: isFiveHour ? "5 hours" : "7 days",
|
|
273
|
+
scope: "all",
|
|
201
274
|
usedPercent,
|
|
202
275
|
windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
|
|
203
276
|
resetsAt,
|
|
@@ -205,6 +278,44 @@ function windowFromPayload(id, payload, now) {
|
|
|
205
278
|
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
206
279
|
};
|
|
207
280
|
}
|
|
281
|
+
function limitEntryWindow(entries, kind) {
|
|
282
|
+
const entry = entries.find((candidate) => candidate.kind === kind);
|
|
283
|
+
if (!entry) return void 0;
|
|
284
|
+
return { utilization: entry.percent, resets_at: entry.resets_at };
|
|
285
|
+
}
|
|
286
|
+
function slugifyDisplayName(name) {
|
|
287
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
288
|
+
}
|
|
289
|
+
function scopedWeeklyWindows(entries, now) {
|
|
290
|
+
const seen = /* @__PURE__ */ new Set();
|
|
291
|
+
const windows = [];
|
|
292
|
+
for (const entry of entries) {
|
|
293
|
+
if (entry.kind !== "weekly_scoped") continue;
|
|
294
|
+
const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
|
|
295
|
+
if (!displayName) continue;
|
|
296
|
+
const slug = slugifyDisplayName(displayName);
|
|
297
|
+
if (!slug || seen.has(slug)) continue;
|
|
298
|
+
seen.add(slug);
|
|
299
|
+
const usedPercent = finitePercent(entry.percent);
|
|
300
|
+
const resetsAt = isoInstant(entry.resets_at);
|
|
301
|
+
windows.push({
|
|
302
|
+
id: `seven-day-${slug}`,
|
|
303
|
+
label: `7 days \xB7 ${displayName}`,
|
|
304
|
+
scope: "model-family",
|
|
305
|
+
modelFamily: slug,
|
|
306
|
+
usedPercent,
|
|
307
|
+
windowMinutes: 7 * 24 * 60,
|
|
308
|
+
resetsAt,
|
|
309
|
+
remainingSeconds: secondsUntil(resetsAt, now),
|
|
310
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
return windows;
|
|
314
|
+
}
|
|
315
|
+
function parseLimitEntries(raw) {
|
|
316
|
+
if (!Array.isArray(raw)) return [];
|
|
317
|
+
return raw.filter((entry) => !!entry && typeof entry === "object");
|
|
318
|
+
}
|
|
208
319
|
function emptyClaudeWindows(state) {
|
|
209
320
|
return [
|
|
210
321
|
{
|
|
@@ -222,15 +333,6 @@ function emptyClaudeWindows(state) {
|
|
|
222
333
|
usedPercent: null,
|
|
223
334
|
windowMinutes: 7 * 24 * 60,
|
|
224
335
|
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
336
|
}
|
|
235
337
|
];
|
|
236
338
|
}
|
|
@@ -311,6 +413,9 @@ var ClaudeAllowanceCollector = class {
|
|
|
311
413
|
}
|
|
312
414
|
const now = this.now();
|
|
313
415
|
const usage = payload;
|
|
416
|
+
const limitEntries = parseLimitEntries(usage.limits);
|
|
417
|
+
const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
|
|
418
|
+
const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
|
|
314
419
|
const snapshot = {
|
|
315
420
|
providerId: "claude",
|
|
316
421
|
accountId,
|
|
@@ -318,10 +423,10 @@ var ClaudeAllowanceCollector = class {
|
|
|
318
423
|
observedAt: new Date(now).toISOString(),
|
|
319
424
|
expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
|
|
320
425
|
windows: [
|
|
321
|
-
windowFromPayload("five-hour",
|
|
322
|
-
windowFromPayload("seven-day",
|
|
323
|
-
|
|
324
|
-
]
|
|
426
|
+
windowFromPayload("five-hour", fiveHour, now),
|
|
427
|
+
windowFromPayload("seven-day", sevenDay, now),
|
|
428
|
+
...scopedWeeklyWindows(limitEntries, now)
|
|
429
|
+
].slice(0, 8)
|
|
325
430
|
};
|
|
326
431
|
this.store.set(snapshot);
|
|
327
432
|
return snapshot;
|
|
@@ -348,34 +453,635 @@ var ClaudeAllowanceCollector = class {
|
|
|
348
453
|
const existing = this.store.get("claude", accountId, now);
|
|
349
454
|
const snapshot = existing ? {
|
|
350
455
|
...existing,
|
|
351
|
-
expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
|
|
456
|
+
expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
|
|
457
|
+
windows: existing.windows.map((window) => ({
|
|
458
|
+
...window,
|
|
459
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
460
|
+
})),
|
|
461
|
+
lastErrorCode: code
|
|
462
|
+
} : {
|
|
463
|
+
providerId: "claude",
|
|
464
|
+
accountId,
|
|
465
|
+
source: "oauth-usage-api",
|
|
466
|
+
observedAt: new Date(now).toISOString(),
|
|
467
|
+
expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
|
|
468
|
+
windows: emptyClaudeWindows("unavailable"),
|
|
469
|
+
lastErrorCode: code
|
|
470
|
+
};
|
|
471
|
+
this.store.set(snapshot);
|
|
472
|
+
return snapshot;
|
|
473
|
+
}
|
|
474
|
+
unsupportedSnapshot(accountId, now, code = "claude_usage_unsupported_auth") {
|
|
475
|
+
return {
|
|
476
|
+
providerId: "claude",
|
|
477
|
+
accountId,
|
|
478
|
+
source: "oauth-usage-api",
|
|
479
|
+
observedAt: new Date(now).toISOString(),
|
|
480
|
+
windows: emptyClaudeWindows("unsupported"),
|
|
481
|
+
lastErrorCode: code
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
// src/allowance/CodexAllowanceCollector.ts
|
|
487
|
+
import {
|
|
488
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
|
|
489
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
490
|
+
import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
491
|
+
var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
492
|
+
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
493
|
+
var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
|
|
494
|
+
function finiteNumber(value) {
|
|
495
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
496
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
497
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
498
|
+
}
|
|
499
|
+
function finitePercent2(value) {
|
|
500
|
+
const parsed = finiteNumber(value);
|
|
501
|
+
return parsed !== null && parsed <= 100 ? parsed : null;
|
|
502
|
+
}
|
|
503
|
+
function epochMs(value) {
|
|
504
|
+
return value > 1e11 ? value : value * 1e3;
|
|
505
|
+
}
|
|
506
|
+
function secondsUntil2(instant, now) {
|
|
507
|
+
if (!instant) return void 0;
|
|
508
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
509
|
+
}
|
|
510
|
+
function decodeJwtClaims(token) {
|
|
511
|
+
const parts = token.split(".");
|
|
512
|
+
if (parts.length !== 3) return void 0;
|
|
513
|
+
try {
|
|
514
|
+
const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
515
|
+
const parsed = JSON.parse(json2);
|
|
516
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
517
|
+
} catch {
|
|
518
|
+
return void 0;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function chatgptAccountIdFromClaims(claims) {
|
|
522
|
+
const auth = claims?.["https://api.openai.com/auth"];
|
|
523
|
+
if (!auth || typeof auth !== "object") return void 0;
|
|
524
|
+
const accountId = auth.chatgpt_account_id;
|
|
525
|
+
return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
|
|
526
|
+
}
|
|
527
|
+
function resolveCodexChatGptAccountId(tokens) {
|
|
528
|
+
if (tokens.accountId?.trim()) return tokens.accountId.trim();
|
|
529
|
+
if (tokens.idToken) {
|
|
530
|
+
const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
|
|
531
|
+
if (fromIdToken) return fromIdToken;
|
|
532
|
+
}
|
|
533
|
+
if (tokens.accessToken) {
|
|
534
|
+
return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
|
|
535
|
+
}
|
|
536
|
+
return void 0;
|
|
537
|
+
}
|
|
538
|
+
function windowFromPayload2(id, payload, now) {
|
|
539
|
+
const usedPercent = finitePercent2(payload?.used_percent);
|
|
540
|
+
const resetAtSeconds = finiteNumber(payload?.reset_at);
|
|
541
|
+
const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
|
|
542
|
+
const windowSeconds = finiteNumber(payload?.limit_window_seconds);
|
|
543
|
+
const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
|
|
544
|
+
const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
|
|
545
|
+
return {
|
|
546
|
+
id,
|
|
547
|
+
label: id === "primary" ? "Primary" : "Secondary",
|
|
548
|
+
scope: "all",
|
|
549
|
+
usedPercent,
|
|
550
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
551
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
552
|
+
remainingSeconds: secondsUntil2(resetsAt, now),
|
|
553
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
var CodexAllowanceCollector = class {
|
|
557
|
+
constructor(credentials, store = getSharedAccountAllowanceStore2(), fetchImpl = (url, init, accountId) => fetchUpstream2(url, init, { providerId: "codex", accountId, redactBodies: true }), now = Date.now) {
|
|
558
|
+
this.credentials = credentials;
|
|
559
|
+
this.store = store;
|
|
560
|
+
this.fetchImpl = fetchImpl;
|
|
561
|
+
this.now = now;
|
|
562
|
+
}
|
|
563
|
+
credentials;
|
|
564
|
+
store;
|
|
565
|
+
fetchImpl;
|
|
566
|
+
now;
|
|
567
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
568
|
+
async collectMany(accounts, options = {}) {
|
|
569
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
570
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
571
|
+
}
|
|
572
|
+
collect(account, options = {}) {
|
|
573
|
+
const now = this.now();
|
|
574
|
+
const unsupported = account.tokens.authMethod !== "oauth";
|
|
575
|
+
if (unsupported) {
|
|
576
|
+
const existing = this.store.get("codex", account.id, now);
|
|
577
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
578
|
+
return Promise.resolve(existing);
|
|
579
|
+
}
|
|
580
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
581
|
+
this.store.set(snapshot);
|
|
582
|
+
return Promise.resolve(snapshot);
|
|
583
|
+
}
|
|
584
|
+
const cached = this.store.get("codex", account.id, now);
|
|
585
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
586
|
+
return Promise.resolve(cached);
|
|
587
|
+
}
|
|
588
|
+
const running = this.inFlight.get(account.id);
|
|
589
|
+
if (running) return running;
|
|
590
|
+
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));
|
|
591
|
+
this.inFlight.set(account.id, promise);
|
|
592
|
+
return promise;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* A response-header snapshot stays a valid cache hit only while fresh; an
|
|
596
|
+
* active oauth-usage snapshot is honored on the same 5-minute cadence as
|
|
597
|
+
* Claude's (the poll is cheap and quota is the scheduling input).
|
|
598
|
+
*/
|
|
599
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
600
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
601
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
602
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
603
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
604
|
+
}
|
|
605
|
+
async fetchAccount(accountId, tokens) {
|
|
606
|
+
let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
|
|
607
|
+
if (!accessToken) {
|
|
608
|
+
return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
|
|
609
|
+
}
|
|
610
|
+
let response = await this.request(accountId, accessToken, tokens);
|
|
611
|
+
if (response.status === 401) {
|
|
612
|
+
const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
|
|
613
|
+
if (!refreshed) {
|
|
614
|
+
return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
|
|
615
|
+
}
|
|
616
|
+
accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
|
|
617
|
+
if (!accessToken) {
|
|
618
|
+
return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
|
|
619
|
+
}
|
|
620
|
+
response = await this.request(accountId, accessToken, tokens);
|
|
621
|
+
}
|
|
622
|
+
if (response.status === 403) {
|
|
623
|
+
const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
|
|
624
|
+
this.store.set(snapshot2);
|
|
625
|
+
return snapshot2;
|
|
626
|
+
}
|
|
627
|
+
if (!response.ok) {
|
|
628
|
+
return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
|
|
629
|
+
}
|
|
630
|
+
let payload;
|
|
631
|
+
try {
|
|
632
|
+
payload = await response.json();
|
|
633
|
+
} catch {
|
|
634
|
+
return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
|
|
635
|
+
}
|
|
636
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
637
|
+
return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
|
|
638
|
+
}
|
|
639
|
+
const now = this.now();
|
|
640
|
+
const usage = payload.rate_limit;
|
|
641
|
+
const previous = this.store.get("codex", accountId, now);
|
|
642
|
+
const snapshot = {
|
|
643
|
+
providerId: "codex",
|
|
644
|
+
accountId,
|
|
645
|
+
source: "oauth-usage-api",
|
|
646
|
+
observedAt: new Date(now).toISOString(),
|
|
647
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
648
|
+
windows: [
|
|
649
|
+
windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
|
|
650
|
+
windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
|
|
651
|
+
],
|
|
652
|
+
// The wham payload has no ratio field; keep the passively-observed value.
|
|
653
|
+
...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
|
|
654
|
+
};
|
|
655
|
+
this.store.set(snapshot);
|
|
656
|
+
return snapshot;
|
|
657
|
+
}
|
|
658
|
+
request(accountId, accessToken, tokens) {
|
|
659
|
+
const headers = {
|
|
660
|
+
Authorization: `Bearer ${accessToken}`,
|
|
661
|
+
Accept: "application/json",
|
|
662
|
+
"User-Agent": CODEX_CLI_USER_AGENT
|
|
663
|
+
};
|
|
664
|
+
const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
|
|
665
|
+
if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
|
|
666
|
+
return this.fetchImpl(CODEX_USAGE_URL, {
|
|
667
|
+
method: "GET",
|
|
668
|
+
headers,
|
|
669
|
+
signal: AbortSignal.timeout(15e3)
|
|
670
|
+
}, accountId);
|
|
671
|
+
}
|
|
672
|
+
failureSnapshot(accountId, code, now) {
|
|
673
|
+
const existing = this.store.get("codex", accountId, now);
|
|
674
|
+
const snapshot = existing ? {
|
|
675
|
+
...existing,
|
|
676
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
677
|
+
windows: existing.windows.map((window) => ({
|
|
678
|
+
...window,
|
|
679
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
680
|
+
})),
|
|
681
|
+
lastErrorCode: code
|
|
682
|
+
} : {
|
|
683
|
+
providerId: "codex",
|
|
684
|
+
accountId,
|
|
685
|
+
source: "oauth-usage-api",
|
|
686
|
+
observedAt: new Date(now).toISOString(),
|
|
687
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
688
|
+
windows: [
|
|
689
|
+
{ id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
|
|
690
|
+
{ id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
|
|
691
|
+
],
|
|
692
|
+
lastErrorCode: code
|
|
693
|
+
};
|
|
694
|
+
this.store.set(snapshot);
|
|
695
|
+
return snapshot;
|
|
696
|
+
}
|
|
697
|
+
unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
|
|
698
|
+
return {
|
|
699
|
+
providerId: "codex",
|
|
700
|
+
accountId,
|
|
701
|
+
source: "oauth-usage-api",
|
|
702
|
+
observedAt: new Date(now).toISOString(),
|
|
703
|
+
windows: [
|
|
704
|
+
{ id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
|
|
705
|
+
{ id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
|
|
706
|
+
],
|
|
707
|
+
lastErrorCode: code
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// src/allowance/KimiAllowanceCollector.ts
|
|
713
|
+
import {
|
|
714
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore3
|
|
715
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
716
|
+
import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
717
|
+
import { kimiFingerprintHeaders } from "@omnicross/subscriptions";
|
|
718
|
+
var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
719
|
+
var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
|
|
720
|
+
function finiteNumber2(value) {
|
|
721
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
722
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
723
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
724
|
+
}
|
|
725
|
+
function isRecord(value) {
|
|
726
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
727
|
+
}
|
|
728
|
+
function parseResetMs(row, nowMs) {
|
|
729
|
+
for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
|
|
730
|
+
const value = row[key];
|
|
731
|
+
if (typeof value === "string" && value.trim()) {
|
|
732
|
+
const parsed = Date.parse(value);
|
|
733
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
734
|
+
}
|
|
735
|
+
const numeric = finiteNumber2(value);
|
|
736
|
+
if (numeric !== void 0 && numeric > 1e9) {
|
|
737
|
+
return numeric > 1e12 ? numeric : numeric * 1e3;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
|
|
741
|
+
const seconds = finiteNumber2(row[key]);
|
|
742
|
+
if (seconds !== void 0) return nowMs + seconds * 1e3;
|
|
743
|
+
}
|
|
744
|
+
return void 0;
|
|
745
|
+
}
|
|
746
|
+
var MINUTE_MS = 6e4;
|
|
747
|
+
var HOUR_MS = 36e5;
|
|
748
|
+
var DAY_MS = 864e5;
|
|
749
|
+
function canonicalWindow(durationMs) {
|
|
750
|
+
if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
|
|
751
|
+
if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
|
|
752
|
+
if (durationMs > 0 && durationMs % DAY_MS === 0) {
|
|
753
|
+
const days = durationMs / DAY_MS;
|
|
754
|
+
return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
|
|
755
|
+
}
|
|
756
|
+
if (durationMs > 0 && durationMs % HOUR_MS === 0) {
|
|
757
|
+
const hours = durationMs / HOUR_MS;
|
|
758
|
+
return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
|
|
759
|
+
}
|
|
760
|
+
return void 0;
|
|
761
|
+
}
|
|
762
|
+
function secondsUntil3(instant, now) {
|
|
763
|
+
if (!instant) return void 0;
|
|
764
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
765
|
+
}
|
|
766
|
+
function windowFromRow(row, fallback, now) {
|
|
767
|
+
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;
|
|
768
|
+
const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
|
|
769
|
+
return {
|
|
770
|
+
id: fallback.id,
|
|
771
|
+
label: fallback.label,
|
|
772
|
+
scope: "all",
|
|
773
|
+
usedPercent,
|
|
774
|
+
windowMinutes: fallback.minutes,
|
|
775
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
776
|
+
remainingSeconds: secondsUntil3(resetsAt, now),
|
|
777
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
function parseKimiUsagePayload(payload, now) {
|
|
781
|
+
if (!isRecord(payload)) return [];
|
|
782
|
+
const byId = /* @__PURE__ */ new Map();
|
|
783
|
+
const rowFrom = (data) => {
|
|
784
|
+
const limit = finiteNumber2(data["limit"]);
|
|
785
|
+
let used = finiteNumber2(data["used"]);
|
|
786
|
+
const remaining = finiteNumber2(data["remaining"]);
|
|
787
|
+
if (used === void 0 && remaining !== void 0 && limit !== void 0) {
|
|
788
|
+
used = limit - remaining;
|
|
789
|
+
}
|
|
790
|
+
let windowDurationMs;
|
|
791
|
+
const windowData = isRecord(data["window"]) ? data["window"] : void 0;
|
|
792
|
+
const duration = finiteNumber2(windowData?.["duration"]);
|
|
793
|
+
const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
|
|
794
|
+
if (duration !== void 0) {
|
|
795
|
+
if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
|
|
796
|
+
else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
|
|
797
|
+
else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
|
|
798
|
+
else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
|
|
799
|
+
else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
|
|
800
|
+
}
|
|
801
|
+
const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
|
|
802
|
+
return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
|
|
803
|
+
};
|
|
804
|
+
if (isRecord(payload["usage"])) {
|
|
805
|
+
const row = rowFrom(payload["usage"]);
|
|
806
|
+
const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
|
|
807
|
+
byId.set("seven-day", window);
|
|
808
|
+
}
|
|
809
|
+
if (Array.isArray(payload["limits"])) {
|
|
810
|
+
for (const item of payload["limits"]) {
|
|
811
|
+
if (!isRecord(item)) continue;
|
|
812
|
+
const detail = isRecord(item["detail"]) ? item["detail"] : item;
|
|
813
|
+
const row = rowFrom(detail);
|
|
814
|
+
const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
|
|
815
|
+
if (!canonical) continue;
|
|
816
|
+
const window = windowFromRow(row, canonical, now);
|
|
817
|
+
const existing = byId.get(canonical.id);
|
|
818
|
+
if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
|
|
819
|
+
byId.set(canonical.id, window);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
|
|
824
|
+
}
|
|
825
|
+
var KimiAllowanceCollector = class {
|
|
826
|
+
constructor(credentials, store = getSharedAccountAllowanceStore3(), fetchImpl = (url, init, accountId) => fetchUpstream3(url, init, { providerId: "kimi", accountId, redactBodies: true }), now = Date.now) {
|
|
827
|
+
this.credentials = credentials;
|
|
828
|
+
this.store = store;
|
|
829
|
+
this.fetchImpl = fetchImpl;
|
|
830
|
+
this.now = now;
|
|
831
|
+
}
|
|
832
|
+
credentials;
|
|
833
|
+
store;
|
|
834
|
+
fetchImpl;
|
|
835
|
+
now;
|
|
836
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
837
|
+
async collectMany(accounts, options = {}) {
|
|
838
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
839
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
840
|
+
}
|
|
841
|
+
collect(account, options = {}) {
|
|
842
|
+
const now = this.now();
|
|
843
|
+
if (account.tokens.authMethod !== "oauth") {
|
|
844
|
+
const existing = this.store.get("kimi", account.id, now);
|
|
845
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
846
|
+
return Promise.resolve(existing);
|
|
847
|
+
}
|
|
848
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
849
|
+
this.store.set(snapshot);
|
|
850
|
+
return Promise.resolve(snapshot);
|
|
851
|
+
}
|
|
852
|
+
const cached = this.store.get("kimi", account.id, now);
|
|
853
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
854
|
+
return Promise.resolve(cached);
|
|
855
|
+
}
|
|
856
|
+
const running = this.inFlight.get(account.id);
|
|
857
|
+
if (running) return running;
|
|
858
|
+
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));
|
|
859
|
+
this.inFlight.set(account.id, promise);
|
|
860
|
+
return promise;
|
|
861
|
+
}
|
|
862
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
863
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
864
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
865
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
866
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
867
|
+
}
|
|
868
|
+
async fetchAccount(accountId, tokens) {
|
|
869
|
+
let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
|
|
870
|
+
if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
|
|
871
|
+
let response = await this.request(accountId, accessToken, tokens);
|
|
872
|
+
if (response.status === 401) {
|
|
873
|
+
const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
|
|
874
|
+
if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
|
|
875
|
+
accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
|
|
876
|
+
if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
|
|
877
|
+
response = await this.request(accountId, accessToken, tokens);
|
|
878
|
+
}
|
|
879
|
+
if (response.status === 403) {
|
|
880
|
+
const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
|
|
881
|
+
this.store.set(snapshot2);
|
|
882
|
+
return snapshot2;
|
|
883
|
+
}
|
|
884
|
+
if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
|
|
885
|
+
let payload;
|
|
886
|
+
try {
|
|
887
|
+
payload = await response.json();
|
|
888
|
+
} catch {
|
|
889
|
+
return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
|
|
890
|
+
}
|
|
891
|
+
const now = this.now();
|
|
892
|
+
const windows = parseKimiUsagePayload(payload, now);
|
|
893
|
+
const snapshot = {
|
|
894
|
+
providerId: "kimi",
|
|
895
|
+
accountId,
|
|
896
|
+
source: "oauth-usage-api",
|
|
897
|
+
observedAt: new Date(now).toISOString(),
|
|
898
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
899
|
+
windows: windows.length > 0 ? windows : [
|
|
900
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
901
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
902
|
+
],
|
|
903
|
+
...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
|
|
904
|
+
};
|
|
905
|
+
this.store.set(snapshot);
|
|
906
|
+
return snapshot;
|
|
907
|
+
}
|
|
908
|
+
request(accountId, accessToken, tokens) {
|
|
909
|
+
return this.fetchImpl(KIMI_USAGE_URL, {
|
|
910
|
+
method: "GET",
|
|
911
|
+
headers: {
|
|
912
|
+
Authorization: `Bearer ${accessToken}`,
|
|
913
|
+
Accept: "application/json",
|
|
914
|
+
...kimiFingerprintHeaders(tokens.deviceId)
|
|
915
|
+
},
|
|
916
|
+
signal: AbortSignal.timeout(15e3)
|
|
917
|
+
}, accountId);
|
|
918
|
+
}
|
|
919
|
+
failureSnapshot(accountId, code, now) {
|
|
920
|
+
const existing = this.store.get("kimi", accountId, now);
|
|
921
|
+
const snapshot = existing ? {
|
|
922
|
+
...existing,
|
|
923
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
924
|
+
windows: existing.windows.map((window) => ({
|
|
925
|
+
...window,
|
|
926
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
927
|
+
})),
|
|
928
|
+
lastErrorCode: code
|
|
929
|
+
} : {
|
|
930
|
+
providerId: "kimi",
|
|
931
|
+
accountId,
|
|
932
|
+
source: "oauth-usage-api",
|
|
933
|
+
observedAt: new Date(now).toISOString(),
|
|
934
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
935
|
+
windows: [
|
|
936
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
937
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
938
|
+
],
|
|
939
|
+
lastErrorCode: code
|
|
940
|
+
};
|
|
941
|
+
this.store.set(snapshot);
|
|
942
|
+
return snapshot;
|
|
943
|
+
}
|
|
944
|
+
unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
|
|
945
|
+
return {
|
|
946
|
+
providerId: "kimi",
|
|
947
|
+
accountId,
|
|
948
|
+
source: "oauth-usage-api",
|
|
949
|
+
observedAt: new Date(now).toISOString(),
|
|
950
|
+
windows: [
|
|
951
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
|
|
952
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
|
|
953
|
+
],
|
|
954
|
+
lastErrorCode: code
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
// src/allowance/OpenCodeGoAllowanceCollector.ts
|
|
960
|
+
import {
|
|
961
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore4
|
|
962
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
963
|
+
import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
964
|
+
import { normalizeOpenCodeGoBaseUrl } from "@omnicross/subscriptions";
|
|
965
|
+
var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
966
|
+
var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
|
|
967
|
+
function finitePercent3(value) {
|
|
968
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
969
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
970
|
+
return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
|
|
971
|
+
}
|
|
972
|
+
function isoInstant2(value) {
|
|
973
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
974
|
+
const time = Date.parse(value);
|
|
975
|
+
return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
|
|
976
|
+
}
|
|
977
|
+
function secondsUntil4(instant, now) {
|
|
978
|
+
if (!instant) return void 0;
|
|
979
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
980
|
+
}
|
|
981
|
+
function windowFromPayload3(id, label, minutes, payload, now) {
|
|
982
|
+
const statusRateLimited = payload?.status === "rate-limited";
|
|
983
|
+
const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
|
|
984
|
+
const resetsAt = isoInstant2(payload?.resetsAt);
|
|
985
|
+
return {
|
|
986
|
+
id,
|
|
987
|
+
label,
|
|
988
|
+
scope: "all",
|
|
989
|
+
usedPercent,
|
|
990
|
+
windowMinutes: minutes,
|
|
991
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
992
|
+
remainingSeconds: secondsUntil4(resetsAt, now),
|
|
993
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
var OpenCodeGoAllowanceCollector = class {
|
|
997
|
+
constructor(credentials, store = getSharedAccountAllowanceStore4(), fetchImpl = (url, init, accountId) => fetchUpstream4(url, init, { providerId: "opencodego", accountId, redactBodies: true }), now = Date.now) {
|
|
998
|
+
this.credentials = credentials;
|
|
999
|
+
this.store = store;
|
|
1000
|
+
this.fetchImpl = fetchImpl;
|
|
1001
|
+
this.now = now;
|
|
1002
|
+
}
|
|
1003
|
+
credentials;
|
|
1004
|
+
store;
|
|
1005
|
+
fetchImpl;
|
|
1006
|
+
now;
|
|
1007
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1008
|
+
async collectMany(accounts, options = {}) {
|
|
1009
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
1010
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
1011
|
+
}
|
|
1012
|
+
collect(account, options = {}) {
|
|
1013
|
+
const now = this.now();
|
|
1014
|
+
const cached = this.store.get("opencodego", account.id, now);
|
|
1015
|
+
if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
|
|
1016
|
+
return Promise.resolve(cached);
|
|
1017
|
+
}
|
|
1018
|
+
const running = this.inFlight.get(account.id);
|
|
1019
|
+
if (running) return running;
|
|
1020
|
+
const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
|
|
1021
|
+
this.inFlight.set(account.id, promise);
|
|
1022
|
+
return promise;
|
|
1023
|
+
}
|
|
1024
|
+
async fetchAccount(account) {
|
|
1025
|
+
const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
|
|
1026
|
+
if (!apiKey) return this.failureSnapshot(account.id, this.now());
|
|
1027
|
+
const base = account.tokens.baseUrl ? normalizeOpenCodeGoBaseUrl(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
|
|
1028
|
+
const response = await this.fetchImpl(`${base}/v1/usage`, {
|
|
1029
|
+
method: "GET",
|
|
1030
|
+
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
1031
|
+
signal: AbortSignal.timeout(15e3)
|
|
1032
|
+
}, account.id);
|
|
1033
|
+
if (response.status === 401 || response.status === 403) {
|
|
1034
|
+
return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
|
|
1035
|
+
}
|
|
1036
|
+
if (!response.ok) return this.failureSnapshot(account.id, this.now());
|
|
1037
|
+
let payload;
|
|
1038
|
+
try {
|
|
1039
|
+
payload = await response.json();
|
|
1040
|
+
} catch {
|
|
1041
|
+
return this.failureSnapshot(account.id, this.now());
|
|
1042
|
+
}
|
|
1043
|
+
const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
|
|
1044
|
+
const now = this.now();
|
|
1045
|
+
const snapshot = {
|
|
1046
|
+
providerId: "opencodego",
|
|
1047
|
+
accountId: account.id,
|
|
1048
|
+
source: "oauth-usage-api",
|
|
1049
|
+
observedAt: new Date(now).toISOString(),
|
|
1050
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1051
|
+
// Monthly deliberately omitted (module doc).
|
|
1052
|
+
windows: [
|
|
1053
|
+
windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
|
|
1054
|
+
windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
|
|
1055
|
+
]
|
|
1056
|
+
};
|
|
1057
|
+
this.store.set(snapshot);
|
|
1058
|
+
return snapshot;
|
|
1059
|
+
}
|
|
1060
|
+
failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
|
|
1061
|
+
const existing = this.store.get("opencodego", accountId, now);
|
|
1062
|
+
const snapshot = existing ? {
|
|
1063
|
+
...existing,
|
|
1064
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
352
1065
|
windows: existing.windows.map((window) => ({
|
|
353
1066
|
...window,
|
|
354
|
-
state: window.
|
|
1067
|
+
state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
|
|
355
1068
|
})),
|
|
356
1069
|
lastErrorCode: code
|
|
357
1070
|
} : {
|
|
358
|
-
providerId: "
|
|
1071
|
+
providerId: "opencodego",
|
|
359
1072
|
accountId,
|
|
360
1073
|
source: "oauth-usage-api",
|
|
361
1074
|
observedAt: new Date(now).toISOString(),
|
|
362
|
-
expiresAt: new Date(now +
|
|
363
|
-
windows:
|
|
1075
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1076
|
+
windows: [
|
|
1077
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
1078
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
1079
|
+
],
|
|
364
1080
|
lastErrorCode: code
|
|
365
1081
|
};
|
|
366
1082
|
this.store.set(snapshot);
|
|
367
1083
|
return snapshot;
|
|
368
1084
|
}
|
|
369
|
-
unsupportedSnapshot(accountId, now, code = "claude_usage_unsupported_auth") {
|
|
370
|
-
return {
|
|
371
|
-
providerId: "claude",
|
|
372
|
-
accountId,
|
|
373
|
-
source: "oauth-usage-api",
|
|
374
|
-
observedAt: new Date(now).toISOString(),
|
|
375
|
-
windows: emptyClaudeWindows("unsupported"),
|
|
376
|
-
lastErrorCode: code
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
1085
|
};
|
|
380
1086
|
|
|
381
1087
|
// src/allowance/AccountAllowanceService.ts
|
|
@@ -393,26 +1099,30 @@ function codexUnavailable(accountId, now) {
|
|
|
393
1099
|
};
|
|
394
1100
|
}
|
|
395
1101
|
var AccountAllowanceService = class {
|
|
396
|
-
constructor(credentials, store =
|
|
1102
|
+
constructor(credentials, store = getSharedAccountAllowanceStore5(), collector, codexCollector, kimiCollector, opencodegoCollector, now = Date.now) {
|
|
397
1103
|
this.credentials = credentials;
|
|
398
1104
|
this.store = store;
|
|
399
1105
|
this.now = now;
|
|
400
1106
|
this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
|
|
1107
|
+
this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
|
|
1108
|
+
this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
|
|
1109
|
+
this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
|
|
401
1110
|
}
|
|
402
1111
|
credentials;
|
|
403
1112
|
store;
|
|
404
1113
|
now;
|
|
405
1114
|
claudeCollector;
|
|
1115
|
+
codexCollector;
|
|
1116
|
+
kimiCollector;
|
|
1117
|
+
opencodegoCollector;
|
|
406
1118
|
/**
|
|
407
|
-
* Read all/filtered snapshots. Claude's five-minute
|
|
408
|
-
*
|
|
1119
|
+
* Read all/filtered snapshots. Claude's and Codex's five-minute caches are
|
|
1120
|
+
* refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
|
|
1121
|
+
* passive `x-codex-*` header tap still feeds mid-flight updates).
|
|
409
1122
|
*/
|
|
410
1123
|
async list(filter = {}) {
|
|
411
1124
|
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
|
-
]);
|
|
1125
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
416
1126
|
const wantsClaude = !filter.providerId || filter.providerId === "claude";
|
|
417
1127
|
const claudeAccounts = (config.claudeAccounts ?? []).filter(
|
|
418
1128
|
(account) => !filter.accountId || account.id === filter.accountId
|
|
@@ -423,39 +1133,90 @@ var AccountAllowanceService = class {
|
|
|
423
1133
|
(account) => !filter.accountId || account.id === filter.accountId
|
|
424
1134
|
);
|
|
425
1135
|
if (wantsCodex) {
|
|
1136
|
+
await this.codexCollector.collectMany(codexAccounts);
|
|
426
1137
|
for (const account of codexAccounts) {
|
|
427
1138
|
if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
|
|
428
1139
|
}
|
|
429
1140
|
}
|
|
1141
|
+
const wantsKimi = !filter.providerId || filter.providerId === "kimi";
|
|
1142
|
+
const kimiAccounts = (config.kimiAccounts ?? []).filter(
|
|
1143
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
1144
|
+
);
|
|
1145
|
+
if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
|
|
1146
|
+
const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
|
|
1147
|
+
const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
|
|
1148
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
1149
|
+
);
|
|
1150
|
+
if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
|
|
430
1151
|
const known = /* @__PURE__ */ new Set();
|
|
431
1152
|
if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
|
|
432
1153
|
if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
|
|
1154
|
+
if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
|
|
1155
|
+
if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
|
|
433
1156
|
return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
|
|
434
1157
|
}
|
|
1158
|
+
knownAccounts(config) {
|
|
1159
|
+
return [
|
|
1160
|
+
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
1161
|
+
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
|
|
1162
|
+
...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
|
|
1163
|
+
...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id }))
|
|
1164
|
+
];
|
|
1165
|
+
}
|
|
435
1166
|
/** Force-refresh Claude usage for one account or every stored Claude account. */
|
|
436
1167
|
async refreshClaude(accountId) {
|
|
437
1168
|
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
|
-
]);
|
|
1169
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
442
1170
|
const accounts = (config.claudeAccounts ?? []).filter(
|
|
443
1171
|
(account) => !accountId || account.id === accountId
|
|
444
1172
|
);
|
|
445
1173
|
return this.claudeCollector.collectMany(accounts, { force: true });
|
|
446
1174
|
}
|
|
447
1175
|
/**
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
1176
|
+
* Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
|
|
1177
|
+
* every stored Codex account. Replaces the old probe-request workaround —
|
|
1178
|
+
* no quota is spent reading the usage endpoint.
|
|
1179
|
+
*/
|
|
1180
|
+
async refreshCodex(accountId) {
|
|
1181
|
+
const config = await this.credentials.getFullConfig();
|
|
1182
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1183
|
+
const accounts = (config.codexAccounts ?? []).filter(
|
|
1184
|
+
(account) => !accountId || account.id === accountId
|
|
1185
|
+
);
|
|
1186
|
+
return this.codexCollector.collectMany(accounts, { force: true });
|
|
1187
|
+
}
|
|
1188
|
+
/** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
|
|
1189
|
+
async refreshOpenCodeGo(accountId) {
|
|
1190
|
+
const config = await this.credentials.getFullConfig();
|
|
1191
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1192
|
+
const accounts = (config.opencodegoAccounts ?? []).filter(
|
|
1193
|
+
(account) => !accountId || account.id === accountId
|
|
1194
|
+
);
|
|
1195
|
+
return this.opencodegoCollector.collectMany(accounts, { force: true });
|
|
1196
|
+
}
|
|
1197
|
+
/** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
|
|
1198
|
+
async refreshKimi(accountId) {
|
|
1199
|
+
const config = await this.credentials.getFullConfig();
|
|
1200
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1201
|
+
const accounts = (config.kimiAccounts ?? []).filter(
|
|
1202
|
+
(account) => !accountId || account.id === accountId
|
|
1203
|
+
);
|
|
1204
|
+
return this.kimiCollector.collectMany(accounts, { force: true });
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
|
|
1208
|
+
* collectors preserve their cache + per-account in-flight coalescing; a tick
|
|
1209
|
+
* normally performs no network I/O. (Codex joined the warm path when it
|
|
1210
|
+
* gained an active `/wham/usage` collector — the passive `x-codex-*` header
|
|
1211
|
+
* tap alone could not keep the policy fed while idle.)
|
|
451
1212
|
*/
|
|
452
1213
|
async maintainClaudeCache(refreshAheadMs) {
|
|
453
1214
|
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
|
-
]);
|
|
1215
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
458
1216
|
await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
|
|
1217
|
+
await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
|
|
1218
|
+
await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
|
|
1219
|
+
await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
|
|
459
1220
|
}
|
|
460
1221
|
/** Remove a cache row as soon as an account is deleted by the admin path. */
|
|
461
1222
|
removeAccountSnapshot(providerId, accountId) {
|
|
@@ -892,7 +1653,7 @@ import {
|
|
|
892
1653
|
} from "@omnicross/contracts/image-generation-types";
|
|
893
1654
|
import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling2 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
894
1655
|
import { getSharedAccountHealth } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
895
|
-
import { fetchUpstream as
|
|
1656
|
+
import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
896
1657
|
|
|
897
1658
|
// src/image-generation/imagesConfigValidation.ts
|
|
898
1659
|
import { validateImagesServerConfig } from "@omnicross/core/outbound-api";
|
|
@@ -2969,7 +3730,8 @@ var VALID_PROVIDER_IDS = [
|
|
|
2969
3730
|
"claude",
|
|
2970
3731
|
"codex",
|
|
2971
3732
|
"gemini",
|
|
2972
|
-
"opencodego"
|
|
3733
|
+
"opencodego",
|
|
3734
|
+
"kimi"
|
|
2973
3735
|
];
|
|
2974
3736
|
function asSubscriptionProviderId(id) {
|
|
2975
3737
|
return VALID_PROVIDER_IDS.includes(id) ? id : null;
|
|
@@ -3105,6 +3867,18 @@ function validateGemini(body) {
|
|
|
3105
3867
|
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
|
|
3106
3868
|
return out;
|
|
3107
3869
|
}
|
|
3870
|
+
function validateKimi(body) {
|
|
3871
|
+
const authMethod = str(body["authMethod"]);
|
|
3872
|
+
const status = str(body["status"]);
|
|
3873
|
+
if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
|
|
3874
|
+
if (!status || !TOKEN_STATUSES.has(status)) return null;
|
|
3875
|
+
const out = {
|
|
3876
|
+
authMethod,
|
|
3877
|
+
status
|
|
3878
|
+
};
|
|
3879
|
+
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
|
|
3880
|
+
return out;
|
|
3881
|
+
}
|
|
3108
3882
|
function validateOpenCodeGo(body) {
|
|
3109
3883
|
const authMethod = str(body["authMethod"]);
|
|
3110
3884
|
const status = str(body["status"]);
|
|
@@ -3140,6 +3914,8 @@ function validateTokenBody(providerId, body) {
|
|
|
3140
3914
|
return validateGemini(body);
|
|
3141
3915
|
case "opencodego":
|
|
3142
3916
|
return validateOpenCodeGo(body);
|
|
3917
|
+
case "kimi":
|
|
3918
|
+
return validateKimi(body);
|
|
3143
3919
|
default:
|
|
3144
3920
|
return null;
|
|
3145
3921
|
}
|
|
@@ -3169,12 +3945,12 @@ async function statusEntryFor(reader, providerId) {
|
|
|
3169
3945
|
|
|
3170
3946
|
// src/admin/accountsOAuth.ts
|
|
3171
3947
|
var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
|
|
3172
|
-
function
|
|
3948
|
+
function err3(status, message) {
|
|
3173
3949
|
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
3174
3950
|
}
|
|
3175
3951
|
function handleOAuthStart(providerId, deps) {
|
|
3176
3952
|
if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
|
|
3177
|
-
return
|
|
3953
|
+
return err3(400, `oauth not available for provider '${providerId}'`);
|
|
3178
3954
|
}
|
|
3179
3955
|
const flow = providerId === "claude" ? claudeOAuth : geminiOAuth;
|
|
3180
3956
|
const { authUrl, codeVerifier, state } = flow.generateAuthParams();
|
|
@@ -3183,23 +3959,23 @@ function handleOAuthStart(providerId, deps) {
|
|
|
3183
3959
|
}
|
|
3184
3960
|
async function handleOAuthComplete(providerId, body, deps) {
|
|
3185
3961
|
if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
|
|
3186
|
-
return
|
|
3962
|
+
return err3(400, `oauth not available for provider '${providerId}'`);
|
|
3187
3963
|
}
|
|
3188
3964
|
const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
|
|
3189
3965
|
const rawCode = typeof body["code"] === "string" ? body["code"] : "";
|
|
3190
|
-
if (!sessionId) return
|
|
3191
|
-
if (!rawCode) return
|
|
3966
|
+
if (!sessionId) return err3(400, "oauth complete requires { sessionId }");
|
|
3967
|
+
if (!rawCode) return err3(400, "oauth complete requires { code }");
|
|
3192
3968
|
const session = deps.oauthSessions.peek(sessionId);
|
|
3193
|
-
if (!session) return
|
|
3969
|
+
if (!session) return err3(410, "oauth session is unknown, expired, or already used");
|
|
3194
3970
|
if (session.providerId !== providerId) {
|
|
3195
|
-
return
|
|
3971
|
+
return err3(400, `oauth session does not match provider '${providerId}'`);
|
|
3196
3972
|
}
|
|
3197
3973
|
let code = rawCode.trim();
|
|
3198
3974
|
if (providerId === "claude") {
|
|
3199
3975
|
const [splitCode, pastedState] = code.split("#");
|
|
3200
|
-
if (!splitCode) return
|
|
3976
|
+
if (!splitCode) return err3(400, "no authorization code was provided");
|
|
3201
3977
|
if (pastedState && pastedState !== session.state) {
|
|
3202
|
-
return
|
|
3978
|
+
return err3(400, "oauth state did not match (possible CSRF) \u2014 aborting");
|
|
3203
3979
|
}
|
|
3204
3980
|
code = splitCode;
|
|
3205
3981
|
}
|
|
@@ -3209,7 +3985,7 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
3209
3985
|
block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
|
|
3210
3986
|
} catch (exchangeError) {
|
|
3211
3987
|
const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
|
|
3212
|
-
return
|
|
3988
|
+
return err3(502, `oauth token exchange failed for '${providerId}': ${reason}`);
|
|
3213
3989
|
}
|
|
3214
3990
|
deps.oauthSessions.consume(sessionId);
|
|
3215
3991
|
const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
|
|
@@ -3547,8 +4323,8 @@ function errBody(message) {
|
|
|
3547
4323
|
return { error: { type: "admin_api_error", message } };
|
|
3548
4324
|
}
|
|
3549
4325
|
var defaultCommandRunner = (command) => new Promise((resolve10) => {
|
|
3550
|
-
exec(command, { timeout: 18e4 }, (
|
|
3551
|
-
if (
|
|
4326
|
+
exec(command, { timeout: 18e4 }, (err6, _stdout, stderr) => {
|
|
4327
|
+
if (err6) resolve10({ ok: false, error: stderr.trim() || err6.message });
|
|
3552
4328
|
else resolve10({ ok: true });
|
|
3553
4329
|
});
|
|
3554
4330
|
});
|
|
@@ -3594,8 +4370,8 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
3594
4370
|
providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
|
|
3595
4371
|
model: typeof body["model"] === "string" ? body["model"] : void 0
|
|
3596
4372
|
});
|
|
3597
|
-
} catch (
|
|
3598
|
-
return { status: 400, body: errBody(
|
|
4373
|
+
} catch (err6) {
|
|
4374
|
+
return { status: 400, body: errBody(err6 instanceof Error ? err6.message : "no launch target") };
|
|
3599
4375
|
}
|
|
3600
4376
|
const id = randomUUID2();
|
|
3601
4377
|
let leaseId2;
|
|
@@ -3623,9 +4399,9 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
3623
4399
|
} else {
|
|
3624
4400
|
launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
|
|
3625
4401
|
}
|
|
3626
|
-
} catch (
|
|
3627
|
-
const status =
|
|
3628
|
-
return { status, body: errBody(
|
|
4402
|
+
} catch (err6) {
|
|
4403
|
+
const status = err6 instanceof RouteLeaseError2 ? err6.status : 400;
|
|
4404
|
+
return { status, body: errBody(err6 instanceof Error ? err6.message : "failed to build launch env") };
|
|
3629
4405
|
}
|
|
3630
4406
|
const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
|
|
3631
4407
|
const opener = ctx.opener ?? defaultTerminalOpener;
|
|
@@ -3653,9 +4429,9 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
3653
4429
|
onFailure: onSessionEnd
|
|
3654
4430
|
});
|
|
3655
4431
|
if (cleanup) openerCleanup = cleanup;
|
|
3656
|
-
} catch (
|
|
4432
|
+
} catch (err6) {
|
|
3657
4433
|
onSessionEnd();
|
|
3658
|
-
return { status: 500, body: errBody(
|
|
4434
|
+
return { status: 500, body: errBody(err6 instanceof Error ? err6.message : "failed to open terminal") };
|
|
3659
4435
|
}
|
|
3660
4436
|
if (ended) {
|
|
3661
4437
|
openerCleanup?.();
|
|
@@ -4204,7 +4980,7 @@ async function handleSearchQuery(req, res, deps) {
|
|
|
4204
4980
|
// src/admin/searchAdminView.ts
|
|
4205
4981
|
var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
|
|
4206
4982
|
var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
|
|
4207
|
-
function
|
|
4983
|
+
function isRecord2(value) {
|
|
4208
4984
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4209
4985
|
}
|
|
4210
4986
|
function redactSearchServerConfig(search) {
|
|
@@ -4254,13 +5030,13 @@ function resolveSecretField(entry, field, stored) {
|
|
|
4254
5030
|
else delete entry[field];
|
|
4255
5031
|
}
|
|
4256
5032
|
function preserveSearchSecrets(incoming, current) {
|
|
4257
|
-
if (!
|
|
5033
|
+
if (!isRecord2(incoming)) return incoming;
|
|
4258
5034
|
const section = { ...incoming };
|
|
4259
5035
|
const providersValue = section["providers"];
|
|
4260
|
-
if (!
|
|
5036
|
+
if (!isRecord2(providersValue)) return section;
|
|
4261
5037
|
const providers = {};
|
|
4262
5038
|
for (const [id, entryValue] of Object.entries(providersValue)) {
|
|
4263
|
-
if (!
|
|
5039
|
+
if (!isRecord2(entryValue)) {
|
|
4264
5040
|
providers[id] = entryValue;
|
|
4265
5041
|
continue;
|
|
4266
5042
|
}
|
|
@@ -4338,7 +5114,7 @@ function parseKeyPolicyBody(body) {
|
|
|
4338
5114
|
var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
|
|
4339
5115
|
var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
|
|
4340
5116
|
var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
|
|
4341
|
-
function
|
|
5117
|
+
function isRecord3(value) {
|
|
4342
5118
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4343
5119
|
}
|
|
4344
5120
|
function nonBlank(value) {
|
|
@@ -4358,7 +5134,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4358
5134
|
const ids = /* @__PURE__ */ new Set();
|
|
4359
5135
|
raw.forEach((entry, index) => {
|
|
4360
5136
|
const path2 = `bindings[${index}]`;
|
|
4361
|
-
if (!
|
|
5137
|
+
if (!isRecord3(entry)) {
|
|
4362
5138
|
errors.push(`${path2} must be an object`);
|
|
4363
5139
|
return;
|
|
4364
5140
|
}
|
|
@@ -4387,12 +5163,12 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4387
5163
|
} else if (entry.modelMappings.length > 100) {
|
|
4388
5164
|
errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
|
|
4389
5165
|
} else if (entry.modelMappings.some(
|
|
4390
|
-
(mapping) => !
|
|
5166
|
+
(mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
|
|
4391
5167
|
)) {
|
|
4392
5168
|
errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
|
|
4393
5169
|
}
|
|
4394
5170
|
}
|
|
4395
|
-
if (!
|
|
5171
|
+
if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
|
|
4396
5172
|
errors.push(`${path2}.target is invalid`);
|
|
4397
5173
|
} else {
|
|
4398
5174
|
if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
|
|
@@ -4407,7 +5183,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4407
5183
|
}
|
|
4408
5184
|
}
|
|
4409
5185
|
if (entry.modelMap !== void 0) {
|
|
4410
|
-
if (!
|
|
5186
|
+
if (!isRecord3(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
|
|
4411
5187
|
errors.push(`${path2}.modelMap must contain string values`);
|
|
4412
5188
|
}
|
|
4413
5189
|
}
|
|
@@ -4717,7 +5493,8 @@ var PROVIDER_KEYS = {
|
|
|
4717
5493
|
block: "opencodego",
|
|
4718
5494
|
accounts: "opencodegoAccounts",
|
|
4719
5495
|
active: "activeOpencodegoAccountId"
|
|
4720
|
-
}
|
|
5496
|
+
},
|
|
5497
|
+
kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
|
|
4721
5498
|
};
|
|
4722
5499
|
function clone(value) {
|
|
4723
5500
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -5239,7 +6016,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
|
|
|
5239
6016
|
}
|
|
5240
6017
|
|
|
5241
6018
|
// src/admin/adminMigration.ts
|
|
5242
|
-
function
|
|
6019
|
+
function err4(status, message) {
|
|
5243
6020
|
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
5244
6021
|
}
|
|
5245
6022
|
async function handleExport(body, deps) {
|
|
@@ -5249,30 +6026,30 @@ async function handleExport(body, deps) {
|
|
|
5249
6026
|
return { status: 200, body: { pack, version: BUNDLE_VERSION } };
|
|
5250
6027
|
} catch (error) {
|
|
5251
6028
|
if (error instanceof WeakPassphraseError) {
|
|
5252
|
-
return
|
|
6029
|
+
return err4(400, error.message);
|
|
5253
6030
|
}
|
|
5254
|
-
return
|
|
6031
|
+
return err4(500, "failed to build the migration pack");
|
|
5255
6032
|
}
|
|
5256
6033
|
}
|
|
5257
6034
|
async function handleImport(body, deps) {
|
|
5258
6035
|
const blob = typeof body["blob"] === "string" ? body["blob"] : "";
|
|
5259
6036
|
const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
|
|
5260
6037
|
const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
|
|
5261
|
-
if (!blob) return
|
|
6038
|
+
if (!blob) return err4(400, "import requires { blob }");
|
|
5262
6039
|
try {
|
|
5263
6040
|
const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
|
|
5264
6041
|
return { status: 200, body: counts };
|
|
5265
6042
|
} catch (error) {
|
|
5266
6043
|
if (error instanceof WeakPassphraseError) {
|
|
5267
|
-
return
|
|
6044
|
+
return err4(400, error.message);
|
|
5268
6045
|
}
|
|
5269
|
-
return
|
|
6046
|
+
return err4(400, error instanceof Error ? error.message : "import failed");
|
|
5270
6047
|
}
|
|
5271
6048
|
}
|
|
5272
6049
|
|
|
5273
6050
|
// src/admin/usagePricing.ts
|
|
5274
6051
|
import { getSharedUsageThroughputTracker } from "@omnicross/core/usage";
|
|
5275
|
-
var
|
|
6052
|
+
var err5 = (status, message) => ({
|
|
5276
6053
|
status,
|
|
5277
6054
|
body: { error: { type: "admin_api_error", message } }
|
|
5278
6055
|
});
|
|
@@ -5285,7 +6062,7 @@ function parseRange(query2) {
|
|
|
5285
6062
|
const startTs = parseFiniteInt(query2.get("startTs"));
|
|
5286
6063
|
const endTs = parseFiniteInt(query2.get("endTs"));
|
|
5287
6064
|
if (startTs === null || endTs === null) {
|
|
5288
|
-
return
|
|
6065
|
+
return err5(400, "startTs and endTs are required finite-integer unix-millis query params");
|
|
5289
6066
|
}
|
|
5290
6067
|
return { startTs, endTs };
|
|
5291
6068
|
}
|
|
@@ -5310,14 +6087,14 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
5310
6087
|
case "timeseries": {
|
|
5311
6088
|
const bucket = query2.get("bucket");
|
|
5312
6089
|
if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
|
|
5313
|
-
return
|
|
6090
|
+
return err5(400, "bucket must be one of 'hour', 'day', 'month'");
|
|
5314
6091
|
}
|
|
5315
6092
|
const now = Date.now();
|
|
5316
6093
|
const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
|
|
5317
6094
|
if (clamped.startTs < clamped.endTs) {
|
|
5318
6095
|
const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
|
|
5319
6096
|
if (projected > MAX_TIMESERIES_BUCKETS) {
|
|
5320
|
-
return
|
|
6097
|
+
return err5(
|
|
5321
6098
|
400,
|
|
5322
6099
|
`requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
|
|
5323
6100
|
);
|
|
@@ -5340,7 +6117,7 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
5340
6117
|
};
|
|
5341
6118
|
}
|
|
5342
6119
|
default:
|
|
5343
|
-
return
|
|
6120
|
+
return err5(404, `unknown usage view '${view ?? ""}'`);
|
|
5344
6121
|
}
|
|
5345
6122
|
}
|
|
5346
6123
|
function poolKeyLabels(cfg) {
|
|
@@ -5389,7 +6166,7 @@ async function handlePricingList(deps) {
|
|
|
5389
6166
|
async function handlePricingUpsert(body, deps) {
|
|
5390
6167
|
const input = parsePricingEntryInput(body);
|
|
5391
6168
|
if (!input) {
|
|
5392
|
-
return
|
|
6169
|
+
return err5(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
|
|
5393
6170
|
}
|
|
5394
6171
|
const entry = await deps.pricingEngine.upsertManual(input);
|
|
5395
6172
|
return { status: 200, body: { entry } };
|
|
@@ -5398,7 +6175,7 @@ async function handlePricingDelete(query2, deps) {
|
|
|
5398
6175
|
const providerId = query2.get("providerId")?.trim() ?? "";
|
|
5399
6176
|
const modelId = query2.get("modelId")?.trim() ?? "";
|
|
5400
6177
|
if (!providerId || !modelId) {
|
|
5401
|
-
return
|
|
6178
|
+
return err5(400, "delete requires providerId and modelId query params");
|
|
5402
6179
|
}
|
|
5403
6180
|
const deleted = await deps.pricingStore.delete(providerId, modelId);
|
|
5404
6181
|
if (deleted) await deps.pricingEngine.invalidateCache();
|
|
@@ -5418,13 +6195,13 @@ async function handlePricingFetchLatest(deps) {
|
|
|
5418
6195
|
}
|
|
5419
6196
|
};
|
|
5420
6197
|
} catch (e) {
|
|
5421
|
-
return
|
|
6198
|
+
return err5(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
5422
6199
|
}
|
|
5423
6200
|
}
|
|
5424
6201
|
async function handlePricingResolveConflicts(body, deps) {
|
|
5425
6202
|
const raw = body["resolutions"];
|
|
5426
6203
|
if (!Array.isArray(raw)) {
|
|
5427
|
-
return
|
|
6204
|
+
return err5(400, "resolve-conflicts requires { resolutions: [...] }");
|
|
5428
6205
|
}
|
|
5429
6206
|
const currentRows = await deps.pricingStore.getAll();
|
|
5430
6207
|
const userEditedKeys = new Set(
|
|
@@ -5434,21 +6211,21 @@ async function handlePricingResolveConflicts(body, deps) {
|
|
|
5434
6211
|
const pendingIncoming = /* @__PURE__ */ new Map();
|
|
5435
6212
|
let staleCount = 0;
|
|
5436
6213
|
for (const item of raw) {
|
|
5437
|
-
if (!item || typeof item !== "object") return
|
|
6214
|
+
if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
|
|
5438
6215
|
const r = item;
|
|
5439
6216
|
const action = r["action"];
|
|
5440
6217
|
if (action !== "overwrite" && action !== "skip") {
|
|
5441
|
-
return
|
|
6218
|
+
return err5(400, "resolution action must be 'overwrite' or 'skip'");
|
|
5442
6219
|
}
|
|
5443
6220
|
const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
|
|
5444
6221
|
const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
|
|
5445
6222
|
if (!providerId || !modelId) {
|
|
5446
|
-
return
|
|
6223
|
+
return err5(400, "each resolution requires top-level providerId and modelId");
|
|
5447
6224
|
}
|
|
5448
6225
|
const incoming = parsePricingEntryInput(r["incoming"]);
|
|
5449
|
-
if (!incoming) return
|
|
6226
|
+
if (!incoming) return err5(400, "each resolution must echo a valid incoming pricing entry");
|
|
5450
6227
|
if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
|
|
5451
|
-
return
|
|
6228
|
+
return err5(400, "resolution providerId/modelId must match the echoed incoming entry");
|
|
5452
6229
|
}
|
|
5453
6230
|
const key = `${providerId}::${modelId}`;
|
|
5454
6231
|
if (action === "overwrite" && !userEditedKeys.has(key)) {
|
|
@@ -5493,7 +6270,7 @@ function query(req) {
|
|
|
5493
6270
|
}
|
|
5494
6271
|
function allowanceProvider(value) {
|
|
5495
6272
|
if (!value) return void 0;
|
|
5496
|
-
return value === "claude" || value === "codex" ? value : null;
|
|
6273
|
+
return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
|
|
5497
6274
|
}
|
|
5498
6275
|
async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
5499
6276
|
if (!service) return writeError2(res, 501, "account allowance service is not available");
|
|
@@ -5507,7 +6284,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5507
6284
|
const params = query(req);
|
|
5508
6285
|
const pathProvider = rest.length >= 2 ? rest[0] : null;
|
|
5509
6286
|
const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
|
|
5510
|
-
if (providerId === null)
|
|
6287
|
+
if (providerId === null) {
|
|
6288
|
+
return writeError2(res, 400, "providerId must be claude, codex, kimi, or opencodego");
|
|
6289
|
+
}
|
|
5511
6290
|
const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
|
|
5512
6291
|
const allowances = await service.list({ providerId, accountId });
|
|
5513
6292
|
return writeJson3(res, 200, { allowances });
|
|
@@ -5517,10 +6296,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5517
6296
|
const requestedProvider = allowanceProvider(
|
|
5518
6297
|
typeof body["providerId"] === "string" ? body["providerId"] : "claude"
|
|
5519
6298
|
);
|
|
5520
|
-
if (requestedProvider !== "claude") {
|
|
5521
|
-
return writeError2(res, 400, "only Claude allowances support explicit refresh");
|
|
5522
|
-
}
|
|
5523
6299
|
const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
|
|
6300
|
+
if (requestedProvider === "codex") {
|
|
6301
|
+
if (!service.refreshCodex) {
|
|
6302
|
+
return writeError2(res, 501, "codex allowance refresh is not available");
|
|
6303
|
+
}
|
|
6304
|
+
const allowances2 = await service.refreshCodex(accountId);
|
|
6305
|
+
if (accountId && allowances2.length === 0) {
|
|
6306
|
+
return writeError2(res, 404, `Codex account '${accountId}' not found`);
|
|
6307
|
+
}
|
|
6308
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
6309
|
+
}
|
|
6310
|
+
if (requestedProvider === "kimi") {
|
|
6311
|
+
if (!service.refreshKimi) {
|
|
6312
|
+
return writeError2(res, 501, "kimi allowance refresh is not available");
|
|
6313
|
+
}
|
|
6314
|
+
const allowances2 = await service.refreshKimi(accountId);
|
|
6315
|
+
if (accountId && allowances2.length === 0) {
|
|
6316
|
+
return writeError2(res, 404, `Kimi account '${accountId}' not found`);
|
|
6317
|
+
}
|
|
6318
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
6319
|
+
}
|
|
6320
|
+
if (requestedProvider === "opencodego") {
|
|
6321
|
+
if (!service.refreshOpenCodeGo) {
|
|
6322
|
+
return writeError2(res, 501, "opencodego allowance refresh is not available");
|
|
6323
|
+
}
|
|
6324
|
+
const allowances2 = await service.refreshOpenCodeGo(accountId);
|
|
6325
|
+
if (accountId && allowances2.length === 0) {
|
|
6326
|
+
return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
|
|
6327
|
+
}
|
|
6328
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
6329
|
+
}
|
|
5524
6330
|
const allowances = await service.refreshClaude(accountId);
|
|
5525
6331
|
if (accountId && allowances.length === 0) {
|
|
5526
6332
|
return writeError2(res, 404, `Claude account '${accountId}' not found`);
|
|
@@ -5691,8 +6497,8 @@ async function handleAdminApi(req, res, path2, deps) {
|
|
|
5691
6497
|
default:
|
|
5692
6498
|
return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
|
|
5693
6499
|
}
|
|
5694
|
-
} catch (
|
|
5695
|
-
writeJsonError(res, 500,
|
|
6500
|
+
} catch (err6) {
|
|
6501
|
+
writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
|
|
5696
6502
|
}
|
|
5697
6503
|
}
|
|
5698
6504
|
function requestQuery(req) {
|
|
@@ -5762,6 +6568,9 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
5762
6568
|
if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
|
|
5763
6569
|
return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
5764
6570
|
}
|
|
6571
|
+
if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
|
|
6572
|
+
return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
|
|
6573
|
+
}
|
|
5765
6574
|
if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
|
|
5766
6575
|
return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
5767
6576
|
}
|
|
@@ -5860,7 +6669,7 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
5860
6669
|
try {
|
|
5861
6670
|
const headers = { Accept: "application/json" };
|
|
5862
6671
|
if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
|
|
5863
|
-
const response = await
|
|
6672
|
+
const response = await fetchUpstream5(url, { method: "GET", headers }, { providerId: "byo" });
|
|
5864
6673
|
if (!response.ok) {
|
|
5865
6674
|
const text = await response.text().catch(() => "");
|
|
5866
6675
|
let message = text.slice(0, 300);
|
|
@@ -5877,8 +6686,8 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
5877
6686
|
const data = await response.json();
|
|
5878
6687
|
const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
|
|
5879
6688
|
return writeJson4(res, 200, { models });
|
|
5880
|
-
} catch (
|
|
5881
|
-
const message =
|
|
6689
|
+
} catch (err6) {
|
|
6690
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
5882
6691
|
return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
|
|
5883
6692
|
}
|
|
5884
6693
|
}
|
|
@@ -5919,7 +6728,7 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
5919
6728
|
}
|
|
5920
6729
|
const startedAt = Date.now();
|
|
5921
6730
|
try {
|
|
5922
|
-
const response = await
|
|
6731
|
+
const response = await fetchUpstream5(
|
|
5923
6732
|
url,
|
|
5924
6733
|
{ method: "POST", headers, body: JSON.stringify(payload) },
|
|
5925
6734
|
{ providerId: "byo" }
|
|
@@ -5941,8 +6750,8 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
5941
6750
|
latencyMs,
|
|
5942
6751
|
sample: extractSampleText(text, row.apiFormat)
|
|
5943
6752
|
});
|
|
5944
|
-
} catch (
|
|
5945
|
-
const message =
|
|
6753
|
+
} catch (err6) {
|
|
6754
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
5946
6755
|
return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
|
|
5947
6756
|
}
|
|
5948
6757
|
}
|
|
@@ -5984,7 +6793,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
|
|
|
5984
6793
|
const row = cfg.providers.find((p) => p.id === id);
|
|
5985
6794
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
5986
6795
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
5987
|
-
|
|
6796
|
+
const views = toPoolKeyView(row, cooldown, deps);
|
|
6797
|
+
if (deps.providerKeyQuota) {
|
|
6798
|
+
const quotas = await Promise.allSettled(
|
|
6799
|
+
views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
|
|
6800
|
+
);
|
|
6801
|
+
views.forEach((view, index) => {
|
|
6802
|
+
const settled = quotas[index];
|
|
6803
|
+
if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
|
|
6804
|
+
});
|
|
6805
|
+
}
|
|
6806
|
+
return writeJson4(res, 200, { keys: views });
|
|
6807
|
+
}
|
|
6808
|
+
async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
|
|
6809
|
+
if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
|
|
6810
|
+
if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
|
|
6811
|
+
const row = cfg.providers.find((p) => p.id === id);
|
|
6812
|
+
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6813
|
+
try {
|
|
6814
|
+
const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
|
|
6815
|
+
if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
|
|
6816
|
+
return writeJson4(res, 200, { quota });
|
|
6817
|
+
} catch {
|
|
6818
|
+
return writeJsonError(res, 502, "quota refresh failed");
|
|
6819
|
+
}
|
|
5988
6820
|
}
|
|
5989
6821
|
function parsePoolKeyInput(body, existing) {
|
|
5990
6822
|
const out = {};
|
|
@@ -6729,12 +7561,12 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6729
7561
|
}
|
|
6730
7562
|
return writeJson4(res, 200, { ok: true, affected: result.affected });
|
|
6731
7563
|
}
|
|
6732
|
-
if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
|
|
6733
|
-
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
7564
|
+
if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[3] === "status") {
|
|
7565
|
+
const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : handleKimiOAuthStatus(rest[2], deps);
|
|
6734
7566
|
return writeJson4(res, result.status, result.body);
|
|
6735
7567
|
}
|
|
6736
|
-
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
6737
|
-
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
7568
|
+
if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[2]) {
|
|
7569
|
+
const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : handleKimiOAuthCancel(rest[2], deps);
|
|
6738
7570
|
return writeJson4(res, result.status, result.body);
|
|
6739
7571
|
}
|
|
6740
7572
|
if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
|
|
@@ -6787,7 +7619,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6787
7619
|
return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
6788
7620
|
}
|
|
6789
7621
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
|
|
6790
|
-
|
|
7622
|
+
if (providerId === "codex") {
|
|
7623
|
+
const result2 = handleCodexOAuthStart(deps);
|
|
7624
|
+
return writeJson4(res, result2.status, result2.body);
|
|
7625
|
+
}
|
|
7626
|
+
if (providerId === "kimi") {
|
|
7627
|
+
const result2 = await handleKimiOAuthStart(deps);
|
|
7628
|
+
return writeJson4(res, result2.status, result2.body);
|
|
7629
|
+
}
|
|
7630
|
+
const result = handleOAuthStart(providerId, deps);
|
|
6791
7631
|
return writeJson4(res, result.status, result.body);
|
|
6792
7632
|
}
|
|
6793
7633
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
|
|
@@ -7281,12 +8121,12 @@ async function handlePlayground(req, res, method, deps) {
|
|
|
7281
8121
|
const payload = body["body"];
|
|
7282
8122
|
const status = deps.outboundApiServer.getStatus();
|
|
7283
8123
|
if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
|
|
7284
|
-
const path2 = resolvePlaygroundPath(endpoint,
|
|
8124
|
+
const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
|
|
7285
8125
|
if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
|
|
7286
8126
|
const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
7287
8127
|
await proxyToOutbound(res, status.port, path2, key, upstreamBody);
|
|
7288
8128
|
}
|
|
7289
|
-
function
|
|
8129
|
+
function isRecord4(v) {
|
|
7290
8130
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
7291
8131
|
}
|
|
7292
8132
|
function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
@@ -7315,8 +8155,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
|
7315
8155
|
});
|
|
7316
8156
|
}
|
|
7317
8157
|
);
|
|
7318
|
-
upstream.on("error", (
|
|
7319
|
-
if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${
|
|
8158
|
+
upstream.on("error", (err6) => {
|
|
8159
|
+
if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err6.message}`);
|
|
7320
8160
|
else res.end();
|
|
7321
8161
|
resolve10();
|
|
7322
8162
|
});
|
|
@@ -7421,7 +8261,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
7421
8261
|
}
|
|
7422
8262
|
|
|
7423
8263
|
// src/admin/version.ts
|
|
7424
|
-
var DAEMON_VERSION = true ? "0.3.
|
|
8264
|
+
var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
|
|
7425
8265
|
|
|
7426
8266
|
// src/admin/AdminServer.ts
|
|
7427
8267
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -7464,13 +8304,13 @@ var AdminServer = class {
|
|
|
7464
8304
|
const server = http2.createServer((req, res) => {
|
|
7465
8305
|
this.onRequest(req, res);
|
|
7466
8306
|
});
|
|
7467
|
-
const onError = (
|
|
7468
|
-
if (
|
|
8307
|
+
const onError = (err6) => {
|
|
8308
|
+
if (err6.code === "EADDRINUSE" && port !== 0) {
|
|
7469
8309
|
server.removeListener("error", onError);
|
|
7470
8310
|
this.listen(bindAddr, 0).then(resolve10, reject);
|
|
7471
8311
|
return;
|
|
7472
8312
|
}
|
|
7473
|
-
reject(
|
|
8313
|
+
reject(err6);
|
|
7474
8314
|
};
|
|
7475
8315
|
server.on("error", onError);
|
|
7476
8316
|
server.listen(port, bindAddr, () => {
|
|
@@ -7488,8 +8328,8 @@ var AdminServer = class {
|
|
|
7488
8328
|
}
|
|
7489
8329
|
/** Per-request handler: auth gate (when a token is set) → routing. */
|
|
7490
8330
|
onRequest(req, res) {
|
|
7491
|
-
void this.dispatch(req, res).catch((
|
|
7492
|
-
const message =
|
|
8331
|
+
void this.dispatch(req, res).catch((err6) => {
|
|
8332
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
7493
8333
|
this.deps.logger.error("[AdminServer] unhandled error:", message);
|
|
7494
8334
|
if (!res.headersSent) {
|
|
7495
8335
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -7753,18 +8593,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
|
|
|
7753
8593
|
return;
|
|
7754
8594
|
}
|
|
7755
8595
|
signal?.addEventListener("abort", abort, { once: true });
|
|
7756
|
-
server.on("error", (
|
|
8596
|
+
server.on("error", (err6) => {
|
|
7757
8597
|
if (settled) return;
|
|
7758
8598
|
settled = true;
|
|
7759
8599
|
clearTimeout(timer);
|
|
7760
|
-
if (
|
|
8600
|
+
if (err6.code === "EADDRINUSE") {
|
|
7761
8601
|
reject(
|
|
7762
8602
|
new Error(
|
|
7763
8603
|
`login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
|
|
7764
8604
|
)
|
|
7765
8605
|
);
|
|
7766
8606
|
} else {
|
|
7767
|
-
reject(
|
|
8607
|
+
reject(err6);
|
|
7768
8608
|
}
|
|
7769
8609
|
});
|
|
7770
8610
|
const timer = setTimeout(() => {
|
|
@@ -7839,6 +8679,411 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
|
|
|
7839
8679
|
};
|
|
7840
8680
|
}
|
|
7841
8681
|
|
|
8682
|
+
// src/allowance/ProviderKeyQuotaService.ts
|
|
8683
|
+
import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
8684
|
+
|
|
8685
|
+
// src/allowance/ProviderKeyQuota.ts
|
|
8686
|
+
var MINUTE_MS2 = 6e4;
|
|
8687
|
+
var HOUR_MS2 = 60 * MINUTE_MS2;
|
|
8688
|
+
var DAY_MS2 = 24 * HOUR_MS2;
|
|
8689
|
+
var WEEK_MS = 7 * DAY_MS2;
|
|
8690
|
+
var MONTH_MS = 30 * DAY_MS2;
|
|
8691
|
+
function finiteNumber3(value) {
|
|
8692
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
8693
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
8694
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
8695
|
+
}
|
|
8696
|
+
function finitePercent4(value) {
|
|
8697
|
+
const parsed = finiteNumber3(value);
|
|
8698
|
+
return parsed !== void 0 && parsed <= 100 ? parsed : null;
|
|
8699
|
+
}
|
|
8700
|
+
function isoInstant3(value) {
|
|
8701
|
+
if (typeof value === "string" && value.trim()) {
|
|
8702
|
+
const time = Date.parse(value);
|
|
8703
|
+
if (Number.isFinite(time)) return new Date(time).toISOString();
|
|
8704
|
+
}
|
|
8705
|
+
const numeric = finiteNumber3(value);
|
|
8706
|
+
if (numeric !== void 0 && numeric > 1e9) {
|
|
8707
|
+
const ms = numeric > 1e12 ? numeric : numeric * 1e3;
|
|
8708
|
+
return new Date(ms).toISOString();
|
|
8709
|
+
}
|
|
8710
|
+
return void 0;
|
|
8711
|
+
}
|
|
8712
|
+
function secondsUntil5(instant, now) {
|
|
8713
|
+
if (!instant) return void 0;
|
|
8714
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
8715
|
+
}
|
|
8716
|
+
function isRecord5(value) {
|
|
8717
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8718
|
+
}
|
|
8719
|
+
function detectProviderKeyQuotaAdapter(baseUrl) {
|
|
8720
|
+
if (!baseUrl) return null;
|
|
8721
|
+
let url;
|
|
8722
|
+
try {
|
|
8723
|
+
url = new URL(baseUrl);
|
|
8724
|
+
} catch {
|
|
8725
|
+
return null;
|
|
8726
|
+
}
|
|
8727
|
+
const host = url.hostname.toLowerCase();
|
|
8728
|
+
const path2 = url.pathname.toLowerCase();
|
|
8729
|
+
if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
|
|
8730
|
+
return "zai";
|
|
8731
|
+
}
|
|
8732
|
+
if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
|
|
8733
|
+
// anthropic `/anthropic` rows are excluded (their usage impl is unverified).
|
|
8734
|
+
(path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
|
|
8735
|
+
return "minimax-token-plan";
|
|
8736
|
+
}
|
|
8737
|
+
if (host === "api.code.umans.ai") return "umans";
|
|
8738
|
+
if (host === "api.synthetic.new") return "synthetic";
|
|
8739
|
+
return null;
|
|
8740
|
+
}
|
|
8741
|
+
function providerKeyQuotaUrl(adapter, baseUrl) {
|
|
8742
|
+
const origin = new URL(baseUrl).origin;
|
|
8743
|
+
if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
|
|
8744
|
+
if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
|
|
8745
|
+
if (adapter === "umans") return `${origin}/v1/usage`;
|
|
8746
|
+
return `${origin}/v2/quotas`;
|
|
8747
|
+
}
|
|
8748
|
+
function providerKeyQuotaAuthHeader(adapter, key) {
|
|
8749
|
+
return adapter === "zai" ? key : `Bearer ${key}`;
|
|
8750
|
+
}
|
|
8751
|
+
function zaiWindowDurationMs(item) {
|
|
8752
|
+
const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
|
|
8753
|
+
switch (item.unit) {
|
|
8754
|
+
case 3:
|
|
8755
|
+
return count * HOUR_MS2;
|
|
8756
|
+
case 4:
|
|
8757
|
+
return count * DAY_MS2;
|
|
8758
|
+
case 5:
|
|
8759
|
+
return count * MONTH_MS;
|
|
8760
|
+
case 6:
|
|
8761
|
+
return WEEK_MS;
|
|
8762
|
+
default:
|
|
8763
|
+
return void 0;
|
|
8764
|
+
}
|
|
8765
|
+
}
|
|
8766
|
+
function zaiWindowIdLabel(durationMs) {
|
|
8767
|
+
if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
|
|
8768
|
+
if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
|
|
8769
|
+
if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
|
|
8770
|
+
if (durationMs !== void 0 && durationMs % DAY_MS2 === 0) {
|
|
8771
|
+
const days = durationMs / DAY_MS2;
|
|
8772
|
+
return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
|
|
8773
|
+
}
|
|
8774
|
+
if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
|
|
8775
|
+
const hours = durationMs / HOUR_MS2;
|
|
8776
|
+
return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
|
|
8777
|
+
}
|
|
8778
|
+
return { id: "quota", label: "Quota" };
|
|
8779
|
+
}
|
|
8780
|
+
function parseZaiQuotaPayload(payload, now) {
|
|
8781
|
+
if (!isRecord5(payload)) return null;
|
|
8782
|
+
const data = isRecord5(payload["data"]) ? payload["data"] : payload;
|
|
8783
|
+
if (payload["success"] === false) return null;
|
|
8784
|
+
const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
|
|
8785
|
+
const byWindow = /* @__PURE__ */ new Map();
|
|
8786
|
+
for (const raw of limits) {
|
|
8787
|
+
if (!isRecord5(raw)) continue;
|
|
8788
|
+
const item = raw;
|
|
8789
|
+
if (item.type === void 0) continue;
|
|
8790
|
+
const details = raw["usageDetails"];
|
|
8791
|
+
if (Array.isArray(details) && details.some((d) => isRecord5(d) && d["modelCode"] === "zread")) {
|
|
8792
|
+
continue;
|
|
8793
|
+
}
|
|
8794
|
+
const durationMs = zaiWindowDurationMs(item);
|
|
8795
|
+
const { id, label } = zaiWindowIdLabel(durationMs);
|
|
8796
|
+
const limit = finiteNumber3(item.usage);
|
|
8797
|
+
const used = finiteNumber3(item.currentValue);
|
|
8798
|
+
const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
|
|
8799
|
+
const fromPercentage = finitePercent4(item.percentage) ?? void 0;
|
|
8800
|
+
const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
|
|
8801
|
+
if (usedPercent === void 0) continue;
|
|
8802
|
+
const resetsAt = isoInstant3(item.nextResetTime);
|
|
8803
|
+
const candidate = {
|
|
8804
|
+
id,
|
|
8805
|
+
label,
|
|
8806
|
+
scope: "all",
|
|
8807
|
+
usedPercent,
|
|
8808
|
+
...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS2) } : {},
|
|
8809
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8810
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8811
|
+
state: "fresh"
|
|
8812
|
+
};
|
|
8813
|
+
const existing = byWindow.get(id);
|
|
8814
|
+
if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
|
|
8815
|
+
byWindow.set(id, candidate);
|
|
8816
|
+
}
|
|
8817
|
+
}
|
|
8818
|
+
const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
|
|
8819
|
+
return windows.length > 0 ? windows.slice(0, 4) : null;
|
|
8820
|
+
}
|
|
8821
|
+
var MINIMAX_STATUS_EXHAUSTED = 2;
|
|
8822
|
+
var MINIMAX_SHARED_BUCKET = "general";
|
|
8823
|
+
function parseMiniMaxBucket(value) {
|
|
8824
|
+
if (!isRecord5(value)) return null;
|
|
8825
|
+
const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
|
|
8826
|
+
if (!modelName) return null;
|
|
8827
|
+
const instant = (v) => {
|
|
8828
|
+
const n = finiteNumber3(v);
|
|
8829
|
+
return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
|
|
8830
|
+
};
|
|
8831
|
+
return {
|
|
8832
|
+
modelName,
|
|
8833
|
+
intervalEnd: instant(value["end_time"]),
|
|
8834
|
+
intervalRemainingPercent: finiteNumber3(value["current_interval_remaining_percent"]),
|
|
8835
|
+
intervalStatus: finiteNumber3(value["current_interval_status"]),
|
|
8836
|
+
weeklyEnd: instant(value["weekly_end_time"]),
|
|
8837
|
+
weeklyRemainingPercent: finiteNumber3(value["current_weekly_remaining_percent"]),
|
|
8838
|
+
weeklyStatus: finiteNumber3(value["current_weekly_status"])
|
|
8839
|
+
};
|
|
8840
|
+
}
|
|
8841
|
+
function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
|
|
8842
|
+
const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
|
|
8843
|
+
const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
|
|
8844
|
+
return {
|
|
8845
|
+
id,
|
|
8846
|
+
label,
|
|
8847
|
+
scope: "all",
|
|
8848
|
+
usedPercent,
|
|
8849
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
8850
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8851
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8852
|
+
state: usedPercent !== null ? "fresh" : "unavailable"
|
|
8853
|
+
};
|
|
8854
|
+
}
|
|
8855
|
+
function parseMiniMaxTokenPlanPayload(payload, now) {
|
|
8856
|
+
if (!isRecord5(payload)) return null;
|
|
8857
|
+
const baseResp = payload["base_resp"];
|
|
8858
|
+
if (!isRecord5(baseResp) || baseResp["status_code"] !== 0) return null;
|
|
8859
|
+
const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
|
|
8860
|
+
let general = null;
|
|
8861
|
+
for (const raw of buckets) {
|
|
8862
|
+
const bucket = parseMiniMaxBucket(raw);
|
|
8863
|
+
if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
|
|
8864
|
+
general = bucket;
|
|
8865
|
+
break;
|
|
8866
|
+
}
|
|
8867
|
+
}
|
|
8868
|
+
if (!general) return null;
|
|
8869
|
+
return [
|
|
8870
|
+
minimaxWindow(
|
|
8871
|
+
"five-hour",
|
|
8872
|
+
"5 hours",
|
|
8873
|
+
5 * 60,
|
|
8874
|
+
general.intervalEnd,
|
|
8875
|
+
general.intervalRemainingPercent,
|
|
8876
|
+
general.intervalStatus,
|
|
8877
|
+
now
|
|
8878
|
+
),
|
|
8879
|
+
minimaxWindow(
|
|
8880
|
+
"seven-day",
|
|
8881
|
+
"7 days",
|
|
8882
|
+
Math.round(WEEK_MS / MINUTE_MS2),
|
|
8883
|
+
general.weeklyEnd,
|
|
8884
|
+
general.weeklyRemainingPercent,
|
|
8885
|
+
general.weeklyStatus,
|
|
8886
|
+
now
|
|
8887
|
+
)
|
|
8888
|
+
];
|
|
8889
|
+
}
|
|
8890
|
+
function parseUmansUsagePayload(payload, now) {
|
|
8891
|
+
if (!isRecord5(payload)) return null;
|
|
8892
|
+
const limits = isRecord5(payload["limits"]) ? payload["limits"] : void 0;
|
|
8893
|
+
const requests = limits && isRecord5(limits["requests"]) ? limits["requests"] : void 0;
|
|
8894
|
+
const usage = isRecord5(payload["usage"]) ? payload["usage"] : void 0;
|
|
8895
|
+
const window = isRecord5(payload["window"]) ? payload["window"] : void 0;
|
|
8896
|
+
const hardCap = finiteNumber3(requests?.["hard_cap"]);
|
|
8897
|
+
const softLimit = finiteNumber3(requests?.["limit"]);
|
|
8898
|
+
const requestsInWindow = finiteNumber3(usage?.["requests_in_window"]);
|
|
8899
|
+
const weightedInWindow = finiteNumber3(usage?.["weighted_in_window"]);
|
|
8900
|
+
const resetsAt = isoInstant3(window?.["resets_at"]);
|
|
8901
|
+
let usedPercent = null;
|
|
8902
|
+
if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
|
|
8903
|
+
usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
|
|
8904
|
+
} else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
|
|
8905
|
+
usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
|
|
8906
|
+
}
|
|
8907
|
+
if (usedPercent === null && resetsAt === void 0) return null;
|
|
8908
|
+
return [
|
|
8909
|
+
{
|
|
8910
|
+
id: "five-hour",
|
|
8911
|
+
label: "5 hours",
|
|
8912
|
+
scope: "all",
|
|
8913
|
+
usedPercent,
|
|
8914
|
+
windowMinutes: 5 * 60,
|
|
8915
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8916
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8917
|
+
state: "fresh"
|
|
8918
|
+
}
|
|
8919
|
+
];
|
|
8920
|
+
}
|
|
8921
|
+
function parseSyntheticQuotasPayload(payload, now) {
|
|
8922
|
+
if (!isRecord5(payload)) return null;
|
|
8923
|
+
const fiveHour = isRecord5(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
|
|
8924
|
+
const weekly = isRecord5(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
|
|
8925
|
+
const windows = [];
|
|
8926
|
+
if (fiveHour) {
|
|
8927
|
+
const max = finiteNumber3(fiveHour["max"]);
|
|
8928
|
+
const remaining = finiteNumber3(fiveHour["remaining"]);
|
|
8929
|
+
const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
|
|
8930
|
+
const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
|
|
8931
|
+
windows.push({
|
|
8932
|
+
id: "five-hour",
|
|
8933
|
+
label: "5 hours",
|
|
8934
|
+
scope: "all",
|
|
8935
|
+
usedPercent,
|
|
8936
|
+
windowMinutes: 5 * 60,
|
|
8937
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8938
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8939
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
8940
|
+
});
|
|
8941
|
+
}
|
|
8942
|
+
if (weekly) {
|
|
8943
|
+
const percentRemaining = finiteNumber3(weekly["percentRemaining"]);
|
|
8944
|
+
const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
|
|
8945
|
+
const resetsAt = isoInstant3(weekly["nextRegenAt"]);
|
|
8946
|
+
windows.push({
|
|
8947
|
+
id: "seven-day",
|
|
8948
|
+
label: "7 days",
|
|
8949
|
+
scope: "all",
|
|
8950
|
+
usedPercent,
|
|
8951
|
+
windowMinutes: 7 * 24 * 60,
|
|
8952
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8953
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8954
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
8955
|
+
});
|
|
8956
|
+
}
|
|
8957
|
+
return windows.length > 0 ? windows : null;
|
|
8958
|
+
}
|
|
8959
|
+
|
|
8960
|
+
// src/allowance/ProviderKeyQuotaService.ts
|
|
8961
|
+
function parseQuotaPayload(adapter, payload, now) {
|
|
8962
|
+
switch (adapter) {
|
|
8963
|
+
case "zai":
|
|
8964
|
+
return parseZaiQuotaPayload(payload, now);
|
|
8965
|
+
case "minimax-token-plan":
|
|
8966
|
+
return parseMiniMaxTokenPlanPayload(payload, now);
|
|
8967
|
+
case "umans":
|
|
8968
|
+
return parseUmansUsagePayload(payload, now);
|
|
8969
|
+
case "synthetic":
|
|
8970
|
+
return parseSyntheticQuotasPayload(payload, now);
|
|
8971
|
+
}
|
|
8972
|
+
}
|
|
8973
|
+
var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
|
|
8974
|
+
function resolvedBaseUrl(row) {
|
|
8975
|
+
const modes = row.apiModes ?? [];
|
|
8976
|
+
const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
|
|
8977
|
+
const fallback = modes[0];
|
|
8978
|
+
const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
|
|
8979
|
+
return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
|
|
8980
|
+
}
|
|
8981
|
+
function rowKeyEntries(row) {
|
|
8982
|
+
const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
|
|
8983
|
+
if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
|
|
8984
|
+
if (row.apiKey.length > 0) {
|
|
8985
|
+
return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
|
|
8986
|
+
}
|
|
8987
|
+
return [];
|
|
8988
|
+
}
|
|
8989
|
+
var ProviderKeyQuotaService = class {
|
|
8990
|
+
constructor(box, fetchImpl = (url, init) => fetchUpstream6(url, init, { redactBodies: true }), now = Date.now) {
|
|
8991
|
+
this.box = box;
|
|
8992
|
+
this.fetchImpl = fetchImpl;
|
|
8993
|
+
this.now = now;
|
|
8994
|
+
}
|
|
8995
|
+
box;
|
|
8996
|
+
fetchImpl;
|
|
8997
|
+
now;
|
|
8998
|
+
cache = /* @__PURE__ */ new Map();
|
|
8999
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
9000
|
+
/**
|
|
9001
|
+
* Quota for one key of a provider row, or `null` when the row has no quota
|
|
9002
|
+
* adapter / no such key. Cache-first; concurrent reads share one flight.
|
|
9003
|
+
*/
|
|
9004
|
+
async quotaFor(row, keyId, options = {}) {
|
|
9005
|
+
const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
|
|
9006
|
+
if (!adapter) return null;
|
|
9007
|
+
const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
|
|
9008
|
+
if (!entry) return null;
|
|
9009
|
+
const cacheKey = `${row.id}\0${keyId}`;
|
|
9010
|
+
const now = this.now();
|
|
9011
|
+
const cached = this.cache.get(cacheKey);
|
|
9012
|
+
if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
|
|
9013
|
+
const running = this.inFlight.get(cacheKey);
|
|
9014
|
+
if (running) return running;
|
|
9015
|
+
const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
|
|
9016
|
+
void error;
|
|
9017
|
+
const previous = this.cache.get(cacheKey);
|
|
9018
|
+
if (previous) {
|
|
9019
|
+
const degraded = {
|
|
9020
|
+
...previous,
|
|
9021
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9022
|
+
windows: previous.windows.map((window) => ({
|
|
9023
|
+
...window,
|
|
9024
|
+
state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
|
|
9025
|
+
})),
|
|
9026
|
+
errorCode: "quota_request_failed"
|
|
9027
|
+
};
|
|
9028
|
+
this.cache.set(cacheKey, degraded);
|
|
9029
|
+
return degraded;
|
|
9030
|
+
}
|
|
9031
|
+
return null;
|
|
9032
|
+
}).finally(() => this.inFlight.delete(cacheKey));
|
|
9033
|
+
this.inFlight.set(cacheKey, promise);
|
|
9034
|
+
return promise;
|
|
9035
|
+
}
|
|
9036
|
+
/** Drop cached rows for a provider (key added/removed/rotated). */
|
|
9037
|
+
invalidateProvider(providerRowId) {
|
|
9038
|
+
for (const key of this.cache.keys()) {
|
|
9039
|
+
if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
|
|
9040
|
+
}
|
|
9041
|
+
}
|
|
9042
|
+
async fetchQuota(adapter, row, rawKey, cacheKey) {
|
|
9043
|
+
const baseUrl = resolvedBaseUrl(row);
|
|
9044
|
+
const url = providerKeyQuotaUrl(adapter, baseUrl);
|
|
9045
|
+
const key = this.box.decryptMaybe(rawKey);
|
|
9046
|
+
const now = this.now();
|
|
9047
|
+
const response = await this.fetchImpl(url, {
|
|
9048
|
+
method: "GET",
|
|
9049
|
+
headers: {
|
|
9050
|
+
Authorization: providerKeyQuotaAuthHeader(adapter, key),
|
|
9051
|
+
Accept: "application/json",
|
|
9052
|
+
"Content-Type": "application/json"
|
|
9053
|
+
},
|
|
9054
|
+
signal: AbortSignal.timeout(15e3)
|
|
9055
|
+
});
|
|
9056
|
+
if (response.status === 401 || response.status === 403) {
|
|
9057
|
+
const snapshot2 = {
|
|
9058
|
+
adapter,
|
|
9059
|
+
observedAt: new Date(now).toISOString(),
|
|
9060
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9061
|
+
windows: [],
|
|
9062
|
+
errorCode: "quota_unauthorized"
|
|
9063
|
+
};
|
|
9064
|
+
this.cache.set(cacheKey, snapshot2);
|
|
9065
|
+
return snapshot2;
|
|
9066
|
+
}
|
|
9067
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
9068
|
+
let payload;
|
|
9069
|
+
try {
|
|
9070
|
+
payload = await response.json();
|
|
9071
|
+
} catch {
|
|
9072
|
+
throw new Error("invalid JSON");
|
|
9073
|
+
}
|
|
9074
|
+
const windows = parseQuotaPayload(adapter, payload, now);
|
|
9075
|
+
const snapshot = {
|
|
9076
|
+
adapter,
|
|
9077
|
+
observedAt: new Date(now).toISOString(),
|
|
9078
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9079
|
+
windows: windows ?? [],
|
|
9080
|
+
...windows ? {} : { errorCode: "quota_unavailable" }
|
|
9081
|
+
};
|
|
9082
|
+
this.cache.set(cacheKey, snapshot);
|
|
9083
|
+
return snapshot;
|
|
9084
|
+
}
|
|
9085
|
+
};
|
|
9086
|
+
|
|
7842
9087
|
// src/commands/paths.ts
|
|
7843
9088
|
import { dirname as dirname5, join as join5 } from "path";
|
|
7844
9089
|
function defaultVouchersPath(configPath) {
|
|
@@ -13988,21 +15233,23 @@ function bucketLabel(bucketStartTs, bucket) {
|
|
|
13988
15233
|
}
|
|
13989
15234
|
|
|
13990
15235
|
// src/ports/JsonOutboundKeyDb.ts
|
|
15236
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
15237
|
+
import {
|
|
15238
|
+
validateOutboundPermissions as validateOutboundPermissions3
|
|
15239
|
+
} from "@omnicross/core";
|
|
15240
|
+
|
|
15241
|
+
// src/ports/atomicFile.ts
|
|
13991
15242
|
import { randomBytes as randomBytes11 } from "crypto";
|
|
13992
15243
|
import {
|
|
13993
15244
|
closeSync as closeSync7,
|
|
13994
15245
|
existsSync as existsSync16,
|
|
13995
15246
|
fsyncSync as fsyncSync7,
|
|
13996
15247
|
openSync as openSync7,
|
|
13997
|
-
readFileSync as readFileSync13,
|
|
13998
15248
|
renameSync as renameSync9,
|
|
13999
15249
|
unlinkSync as unlinkSync11,
|
|
14000
15250
|
writeFileSync as writeFileSync12
|
|
14001
15251
|
} from "fs";
|
|
14002
15252
|
import { basename as basename8, dirname as dirname14, join as join16 } from "path";
|
|
14003
|
-
import {
|
|
14004
|
-
validateOutboundPermissions as validateOutboundPermissions3
|
|
14005
|
-
} from "@omnicross/core";
|
|
14006
15253
|
function atomicReplaceUtf8(targetPath, contents) {
|
|
14007
15254
|
const tempPath = join16(
|
|
14008
15255
|
dirname14(targetPath),
|
|
@@ -14032,6 +15279,8 @@ function atomicReplaceUtf8(targetPath, contents) {
|
|
|
14032
15279
|
throw error;
|
|
14033
15280
|
}
|
|
14034
15281
|
}
|
|
15282
|
+
|
|
15283
|
+
// src/ports/JsonOutboundKeyDb.ts
|
|
14035
15284
|
var JsonOutboundKeyDb = class {
|
|
14036
15285
|
/**
|
|
14037
15286
|
* @param secretBox OPTIONAL reversible-secret codec. When present, a created
|
|
@@ -14174,7 +15423,7 @@ var JsonOutboundKeyDb = class {
|
|
|
14174
15423
|
}
|
|
14175
15424
|
/** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14176
15425
|
readRows() {
|
|
14177
|
-
if (!
|
|
15426
|
+
if (!existsSync17(this.keysPath)) return [];
|
|
14178
15427
|
try {
|
|
14179
15428
|
const parsed = JSON.parse(readFileSync13(this.keysPath, "utf8"));
|
|
14180
15429
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -14193,7 +15442,7 @@ function applyPolicyField(row, field, value) {
|
|
|
14193
15442
|
}
|
|
14194
15443
|
|
|
14195
15444
|
// src/ports/JsonPricingStore.ts
|
|
14196
|
-
import { existsSync as
|
|
15445
|
+
import { existsSync as existsSync18, readFileSync as readFileSync14, renameSync as renameSync10, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
|
|
14197
15446
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
14198
15447
|
var JsonPricingStore = class {
|
|
14199
15448
|
constructor(pricingPath) {
|
|
@@ -14208,7 +15457,7 @@ var JsonPricingStore = class {
|
|
|
14208
15457
|
* otherwise unusable pricing table after a crash or manual file edit.
|
|
14209
15458
|
*/
|
|
14210
15459
|
hasUsableSnapshot() {
|
|
14211
|
-
if (!
|
|
15460
|
+
if (!existsSync18(this.pricingPath)) return false;
|
|
14212
15461
|
try {
|
|
14213
15462
|
const parsed = JSON.parse(readFileSync14(this.pricingPath, "utf8"));
|
|
14214
15463
|
return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
|
|
@@ -14323,7 +15572,7 @@ var JsonPricingStore = class {
|
|
|
14323
15572
|
}
|
|
14324
15573
|
/** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14325
15574
|
readRows() {
|
|
14326
|
-
if (!
|
|
15575
|
+
if (!existsSync18(this.pricingPath)) return [];
|
|
14327
15576
|
try {
|
|
14328
15577
|
const parsed = JSON.parse(readFileSync14(this.pricingPath, "utf8"));
|
|
14329
15578
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -14355,7 +15604,7 @@ function isUsablePricingRow(value) {
|
|
|
14355
15604
|
}
|
|
14356
15605
|
|
|
14357
15606
|
// src/pricing/PricingRefreshScheduler.ts
|
|
14358
|
-
import { existsSync as
|
|
15607
|
+
import { existsSync as existsSync19, readFileSync as readFileSync15, renameSync as renameSync11, writeFileSync as writeFileSync14 } from "fs";
|
|
14359
15608
|
var EMPTY_STATE2 = {
|
|
14360
15609
|
lastAttemptAt: null,
|
|
14361
15610
|
lastSuccessAt: null,
|
|
@@ -14393,7 +15642,7 @@ var PricingRefreshScheduler = class {
|
|
|
14393
15642
|
this.timer = null;
|
|
14394
15643
|
}
|
|
14395
15644
|
getState() {
|
|
14396
|
-
if (!
|
|
15645
|
+
if (!existsSync19(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
|
|
14397
15646
|
try {
|
|
14398
15647
|
const value = JSON.parse(readFileSync15(this.statePath, "utf8"));
|
|
14399
15648
|
return {
|
|
@@ -14458,7 +15707,7 @@ function finiteOrNull(value) {
|
|
|
14458
15707
|
}
|
|
14459
15708
|
|
|
14460
15709
|
// src/ports/JsonVoucherDb.ts
|
|
14461
|
-
import { existsSync as
|
|
15710
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16, writeFileSync as writeFileSync15 } from "fs";
|
|
14462
15711
|
var JsonVoucherDb = class {
|
|
14463
15712
|
constructor(vouchersPath) {
|
|
14464
15713
|
this.vouchersPath = vouchersPath;
|
|
@@ -14536,7 +15785,7 @@ var JsonVoucherDb = class {
|
|
|
14536
15785
|
}
|
|
14537
15786
|
/** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14538
15787
|
readRows() {
|
|
14539
|
-
if (!
|
|
15788
|
+
if (!existsSync20(this.vouchersPath)) return [];
|
|
14540
15789
|
try {
|
|
14541
15790
|
const parsed = JSON.parse(readFileSync16(this.vouchersPath, "utf8"));
|
|
14542
15791
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -14550,16 +15799,17 @@ var JsonVoucherDb = class {
|
|
|
14550
15799
|
};
|
|
14551
15800
|
|
|
14552
15801
|
// src/ports/JsonSubscriptionCredentialStore.ts
|
|
14553
|
-
import { existsSync as
|
|
15802
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync6, readFileSync as readFileSync18, renameSync as renameSync12 } from "fs";
|
|
14554
15803
|
import { dirname as dirname15 } from "path";
|
|
14555
15804
|
import { getSharedAccountHealth as getSharedAccountHealth2 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
14556
15805
|
import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling3 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
14557
|
-
import { fetchUpstream as
|
|
15806
|
+
import { fetchUpstream as fetchUpstream7 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
14558
15807
|
import { getSharedIdentityStore as getSharedIdentityStore2 } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
|
|
14559
15808
|
import {
|
|
14560
15809
|
claudeOAuth as claudeOAuth2,
|
|
14561
15810
|
codexOAuth as codexOAuth2,
|
|
14562
|
-
geminiOAuth as geminiOAuth2
|
|
15811
|
+
geminiOAuth as geminiOAuth2,
|
|
15812
|
+
kimiOAuth as kimiOAuth2
|
|
14563
15813
|
} from "@omnicross/subscriptions";
|
|
14564
15814
|
|
|
14565
15815
|
// src/ports/account-sync.ts
|
|
@@ -14604,7 +15854,7 @@ function findDuplicateCredentialIds(accounts) {
|
|
|
14604
15854
|
}
|
|
14605
15855
|
|
|
14606
15856
|
// src/ports/external-cli-credentials.ts
|
|
14607
|
-
import { existsSync as
|
|
15857
|
+
import { existsSync as existsSync21, readFileSync as readFileSync17 } from "fs";
|
|
14608
15858
|
import { homedir as homedir4 } from "os";
|
|
14609
15859
|
import { join as join17 } from "path";
|
|
14610
15860
|
function externalStorePath(provider, home = homedir4()) {
|
|
@@ -14657,7 +15907,7 @@ function parseCodexTokensEnvelope(raw) {
|
|
|
14657
15907
|
}
|
|
14658
15908
|
function readExternalCliCredentials(provider, home = homedir4()) {
|
|
14659
15909
|
const path2 = externalStorePath(provider, home);
|
|
14660
|
-
if (!
|
|
15910
|
+
if (!existsSync21(path2)) return null;
|
|
14661
15911
|
let raw;
|
|
14662
15912
|
try {
|
|
14663
15913
|
const parsed = JSON.parse(readFileSync17(path2, "utf8"));
|
|
@@ -14683,16 +15933,18 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14683
15933
|
* as on relay refresh egresses from the SAME proxy IP as the
|
|
14684
15934
|
* account's traffic. NOT used by any read/write path.
|
|
14685
15935
|
*/
|
|
14686
|
-
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
|
|
15936
|
+
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
|
|
14687
15937
|
this.tokensPath = tokensPath;
|
|
14688
15938
|
this.box = box;
|
|
14689
15939
|
this.fetchImpl = fetchImpl;
|
|
14690
15940
|
this.externalCliReader = externalCliReader;
|
|
15941
|
+
this.atomicReplace = atomicReplace;
|
|
14691
15942
|
}
|
|
14692
15943
|
tokensPath;
|
|
14693
15944
|
box;
|
|
14694
15945
|
fetchImpl;
|
|
14695
15946
|
externalCliReader;
|
|
15947
|
+
atomicReplace;
|
|
14696
15948
|
/**
|
|
14697
15949
|
* The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
|
|
14698
15950
|
* TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
|
|
@@ -14706,7 +15958,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14706
15958
|
* a plaintext token pair into `upstream-trace.jsonl`.
|
|
14707
15959
|
*/
|
|
14708
15960
|
buildRefreshFetch(providerId, accountId) {
|
|
14709
|
-
return this.fetchImpl ?? ((url, init) =>
|
|
15961
|
+
return this.fetchImpl ?? ((url, init) => fetchUpstream7(url, init, { providerId, accountId, redactBodies: true }));
|
|
14710
15962
|
}
|
|
14711
15963
|
/**
|
|
14712
15964
|
* In-flight refresh coalescing. OAuth refresh tokens are
|
|
@@ -14747,7 +15999,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14747
15999
|
* other hot reads. Never returns token material.
|
|
14748
16000
|
*/
|
|
14749
16001
|
getAccountProxy(providerId, accountId) {
|
|
14750
|
-
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
|
|
16002
|
+
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
|
|
14751
16003
|
return void 0;
|
|
14752
16004
|
}
|
|
14753
16005
|
return getAccountProxy(this.readConfig(), providerId, accountId);
|
|
@@ -14766,7 +16018,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14766
16018
|
const fingerprintOn = identityStore.isEnabled();
|
|
14767
16019
|
const now = Date.now();
|
|
14768
16020
|
const out = {};
|
|
14769
|
-
for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
|
|
16021
|
+
for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
|
|
14770
16022
|
const sanitized = sanitizeAccounts(config, provider);
|
|
14771
16023
|
if (sanitized.length === 0) continue;
|
|
14772
16024
|
for (const account of sanitized) {
|
|
@@ -14924,6 +16176,47 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14924
16176
|
}
|
|
14925
16177
|
});
|
|
14926
16178
|
}
|
|
16179
|
+
/**
|
|
16180
|
+
* Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
|
|
16181
|
+
* Kimi ROTATES the refresh token, so the response's pair is written back
|
|
16182
|
+
* whole; the account's stable `deviceId` (fingerprint header input) is
|
|
16183
|
+
* preserved. The refresh call carries the CLI fingerprint headers. HONEST
|
|
16184
|
+
* `false` when no refresh_token.
|
|
16185
|
+
*/
|
|
16186
|
+
async refreshKimiToken() {
|
|
16187
|
+
return this.coalesce("kimi:active", async () => {
|
|
16188
|
+
const config = this.readConfig();
|
|
16189
|
+
const active = getActiveAccount(config, "kimi");
|
|
16190
|
+
const kimi = active?.tokens;
|
|
16191
|
+
if (!active || !kimi?.refreshToken) return false;
|
|
16192
|
+
const capturedId = active.id;
|
|
16193
|
+
this.materializeMigration(config);
|
|
16194
|
+
const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
|
|
16195
|
+
try {
|
|
16196
|
+
const result = await kimiOAuth2.refreshAccessToken(
|
|
16197
|
+
kimi.refreshToken,
|
|
16198
|
+
refreshFetch,
|
|
16199
|
+
kimiOAuth2.kimiFingerprintHeaders(kimi.deviceId)
|
|
16200
|
+
);
|
|
16201
|
+
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
16202
|
+
const next = {
|
|
16203
|
+
...kimi,
|
|
16204
|
+
accessToken: result.accessToken,
|
|
16205
|
+
refreshToken: result.refreshToken,
|
|
16206
|
+
expiresAt,
|
|
16207
|
+
status: "authorized",
|
|
16208
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16209
|
+
errorMessage: void 0,
|
|
16210
|
+
syncWarning: void 0
|
|
16211
|
+
};
|
|
16212
|
+
this.writeBackById("kimi", capturedId, next);
|
|
16213
|
+
return true;
|
|
16214
|
+
} catch (error) {
|
|
16215
|
+
this.markExpiredById("kimi", capturedId, kimi, error);
|
|
16216
|
+
return false;
|
|
16217
|
+
}
|
|
16218
|
+
});
|
|
16219
|
+
}
|
|
14927
16220
|
/**
|
|
14928
16221
|
* Refresh a SPECIFIC managed account by id (background scheduler sweep and
|
|
14929
16222
|
* account-pool resolution). It uses only that account's stored refresh
|
|
@@ -14976,7 +16269,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14976
16269
|
}
|
|
14977
16270
|
const oauth = account.tokens;
|
|
14978
16271
|
if (!oauth.accessToken) return null;
|
|
14979
|
-
if (providerId === "codex" || providerId === "gemini") {
|
|
16272
|
+
if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
|
|
14980
16273
|
const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
|
|
14981
16274
|
const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
|
|
14982
16275
|
if (expiringSoon && oauth.refreshToken) {
|
|
@@ -15065,8 +16358,23 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15065
16358
|
}
|
|
15066
16359
|
/** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
|
|
15067
16360
|
async refreshUpstream(provider, refreshToken, accountId) {
|
|
16361
|
+
const refreshFetch = this.buildRefreshFetch(provider, accountId);
|
|
16362
|
+
if (provider === "kimi") {
|
|
16363
|
+
const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
|
|
16364
|
+
const deviceId = account?.tokens?.deviceId;
|
|
16365
|
+
const r2 = await kimiOAuth2.refreshAccessToken(
|
|
16366
|
+
refreshToken,
|
|
16367
|
+
refreshFetch,
|
|
16368
|
+
kimiOAuth2.kimiFingerprintHeaders(deviceId)
|
|
16369
|
+
);
|
|
16370
|
+
return {
|
|
16371
|
+
accessToken: r2.accessToken,
|
|
16372
|
+
refreshToken: r2.refreshToken,
|
|
16373
|
+
expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
|
|
16374
|
+
};
|
|
16375
|
+
}
|
|
15068
16376
|
const flow = provider === "claude" ? claudeOAuth2 : provider === "codex" ? codexOAuth2 : geminiOAuth2;
|
|
15069
|
-
const r = await flow.refreshAccessToken(refreshToken,
|
|
16377
|
+
const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
|
|
15070
16378
|
return {
|
|
15071
16379
|
accessToken: r.accessToken,
|
|
15072
16380
|
refreshToken: r.refreshToken,
|
|
@@ -15229,42 +16537,86 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15229
16537
|
/** Write the merged config to disk as pretty JSON (mkdir parent if needed).
|
|
15230
16538
|
* Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
|
|
15231
16539
|
* `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
|
|
15232
|
-
* write incl. child 4's future refresh writes lands encrypted.
|
|
16540
|
+
* write incl. child 4's future refresh writes lands encrypted.
|
|
16541
|
+
* ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
|
|
16542
|
+
* interrupted write discards only the temp file; the prior `tokens.json`
|
|
16543
|
+
* survives byte-equal (bare `writeFileSync` truncate-writes lost every
|
|
16544
|
+
* account on a mid-write failure, 2026-09-06). */
|
|
15233
16545
|
persist(config) {
|
|
15234
16546
|
mkdirSync6(dirname15(this.tokensPath), { recursive: true });
|
|
15235
16547
|
const encrypted = encryptTokens(config, this.box);
|
|
15236
|
-
|
|
16548
|
+
this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
|
|
15237
16549
|
}
|
|
15238
16550
|
/**
|
|
15239
|
-
* Read + parse `tokens.json`,
|
|
15240
|
-
*
|
|
15241
|
-
*
|
|
16551
|
+
* Read + parse `tokens.json`, then DECRYPT the token-material fields so every
|
|
16552
|
+
* getter returns plaintext (the subscription bearer path is byte-identical).
|
|
16553
|
+
*
|
|
16554
|
+
* A MISSING file is a legitimate first-boot state → minimal `{ updatedAt: '' }`.
|
|
16555
|
+
* A file that EXISTS but cannot be parsed as a JSON object is CORRUPT →
|
|
16556
|
+
* `quarantineCorrupt` moves it aside (once) before the empty config is
|
|
16557
|
+
* returned, so the unreadable accounts survive for manual recovery.
|
|
15242
16558
|
*
|
|
15243
|
-
* The
|
|
15244
|
-
*
|
|
15245
|
-
*
|
|
15246
|
-
*
|
|
15247
|
-
*
|
|
15248
|
-
*
|
|
15249
|
-
* `config.ts loadConfig`, which decrypts outside its parse try.
|
|
16559
|
+
* The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
|
|
16560
|
+
* tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
|
|
16561
|
+
* error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
|
|
16562
|
+
* decrypt would report "no tokens" and silently send the WRONG bearer
|
|
16563
|
+
* upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
|
|
16564
|
+
* its parse try.
|
|
15250
16565
|
*/
|
|
15251
16566
|
readConfig() {
|
|
15252
|
-
if (!
|
|
16567
|
+
if (!existsSync22(this.tokensPath)) return { updatedAt: "" };
|
|
15253
16568
|
let parsed;
|
|
15254
16569
|
try {
|
|
15255
16570
|
const raw = JSON.parse(readFileSync18(this.tokensPath, "utf8"));
|
|
15256
|
-
|
|
16571
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
16572
|
+
return this.quarantineCorrupt("parsed JSON is not an object");
|
|
16573
|
+
}
|
|
16574
|
+
parsed = raw;
|
|
15257
16575
|
} catch {
|
|
15258
|
-
|
|
16576
|
+
return this.quarantineCorrupt("unparseable JSON");
|
|
15259
16577
|
}
|
|
15260
|
-
if (!parsed) return { updatedAt: "" };
|
|
15261
16578
|
const decrypted = decryptTokens(parsed, this.box);
|
|
15262
16579
|
return migrateLazily(decrypted);
|
|
15263
16580
|
}
|
|
16581
|
+
/** One-shot latch: a corrupt file is quarantined (or found unmovable) at
|
|
16582
|
+
* most once per process, so the hot read path never re-attempts or re-logs. */
|
|
16583
|
+
corruptQuarantined = false;
|
|
16584
|
+
/**
|
|
16585
|
+
* Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
|
|
16586
|
+
*
|
|
16587
|
+
* Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
|
|
16588
|
+
* logs loudly (the daemon's stderr log; secret-free — reason + paths only).
|
|
16589
|
+
* The daemon KEEPS SERVING (API-key routing is unaffected; subscription
|
|
16590
|
+
* routing reports no credential, same as an absent file) while the corrupt
|
|
16591
|
+
* bytes survive for manual recovery — and, critically, the NEXT persist
|
|
16592
|
+
* (e.g. the user re-logging in) can no longer overwrite the only copy of
|
|
16593
|
+
* the old accounts, which is exactly how the 2026-09-06 incident turned a
|
|
16594
|
+
* recoverable truncated file into permanent account loss.
|
|
16595
|
+
*
|
|
16596
|
+
* Best-effort: if the rename fails (file locked, permissions), the corrupt
|
|
16597
|
+
* file is left in place and every later read still tolerates it as empty;
|
|
16598
|
+
* the latch still trips so the attempt + log happen exactly once.
|
|
16599
|
+
*/
|
|
16600
|
+
quarantineCorrupt(reason) {
|
|
16601
|
+
if (!this.corruptQuarantined) {
|
|
16602
|
+
this.corruptQuarantined = true;
|
|
16603
|
+
const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
16604
|
+
let moved = false;
|
|
16605
|
+
try {
|
|
16606
|
+
renameSync12(this.tokensPath, backup);
|
|
16607
|
+
moved = true;
|
|
16608
|
+
} catch {
|
|
16609
|
+
}
|
|
16610
|
+
console.error(
|
|
16611
|
+
`[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`)
|
|
16612
|
+
);
|
|
16613
|
+
}
|
|
16614
|
+
return { updatedAt: "" };
|
|
16615
|
+
}
|
|
15264
16616
|
};
|
|
15265
16617
|
|
|
15266
16618
|
// src/AccountHealthProbeScheduler.ts
|
|
15267
|
-
import { fetchUpstream as
|
|
16619
|
+
import { fetchUpstream as fetchUpstream8 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
15268
16620
|
|
|
15269
16621
|
// src/probe/CodexGenerationProbe.ts
|
|
15270
16622
|
import {
|
|
@@ -15406,7 +16758,11 @@ var PROVIDER_PROBE_PLANS = {
|
|
|
15406
16758
|
// billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
|
|
15407
16759
|
codex: { kind: "local" },
|
|
15408
16760
|
gemini: { kind: "local" },
|
|
15409
|
-
opencodego: { kind: "local" }
|
|
16761
|
+
opencodego: { kind: "local" },
|
|
16762
|
+
// Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
|
|
16763
|
+
// collector uses it), but the probe path also needs the fingerprint headers —
|
|
16764
|
+
// keep the probe local until the collector covers the health surface.
|
|
16765
|
+
kimi: { kind: "local" }
|
|
15410
16766
|
};
|
|
15411
16767
|
function probePlanFor(providerId) {
|
|
15412
16768
|
return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
|
|
@@ -15428,7 +16784,7 @@ var AccountHealthProbeScheduler = class {
|
|
|
15428
16784
|
this.logger = logger;
|
|
15429
16785
|
this.config = config;
|
|
15430
16786
|
this.now = opts.now ?? Date.now;
|
|
15431
|
-
this.fetchImpl = opts.fetchImpl ??
|
|
16787
|
+
this.fetchImpl = opts.fetchImpl ?? fetchUpstream8;
|
|
15432
16788
|
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
15433
16789
|
this.planFor = opts.planFor ?? probePlanFor;
|
|
15434
16790
|
}
|
|
@@ -15772,13 +17128,13 @@ var AccountHealthSweeper = class {
|
|
|
15772
17128
|
};
|
|
15773
17129
|
|
|
15774
17130
|
// src/audit/AuditPruneSweeper.ts
|
|
15775
|
-
import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as
|
|
17131
|
+
import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync25, readdirSync as readdirSync6, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
|
|
15776
17132
|
import { join as join20 } from "path";
|
|
15777
17133
|
import { pipeline } from "stream/promises";
|
|
15778
17134
|
import { createGzip } from "zlib";
|
|
15779
17135
|
|
|
15780
17136
|
// src/audit/auditDictionary.ts
|
|
15781
|
-
import { existsSync as
|
|
17137
|
+
import { existsSync as existsSync23, readdirSync as readdirSync4, readFileSync as readFileSync19, renameSync as renameSync13, unlinkSync as unlinkSync12, writeFileSync as writeFileSync16 } from "fs";
|
|
15782
17138
|
import { join as join18 } from "path";
|
|
15783
17139
|
|
|
15784
17140
|
// src/audit/auditBodyStore.ts
|
|
@@ -16036,9 +17392,9 @@ function chooseDictionary(anchors) {
|
|
|
16036
17392
|
var EMPTY = { shards: 0, anchors: 0, savedBytes: 0 };
|
|
16037
17393
|
function compactAuditDay(dayPath) {
|
|
16038
17394
|
const bodiesPath = join18(dayPath, AUDIT_BODIES_DIR);
|
|
16039
|
-
if (!
|
|
17395
|
+
if (!existsSync23(bodiesPath)) return EMPTY;
|
|
16040
17396
|
const dictPath = join18(bodiesPath, AUDIT_DICT_FILE);
|
|
16041
|
-
if (
|
|
17397
|
+
if (existsSync23(dictPath) || existsSync23(`${dictPath}.gz`)) return EMPTY;
|
|
16042
17398
|
const shardFiles = plainShards(bodiesPath);
|
|
16043
17399
|
if (shardFiles.length < 2) return EMPTY;
|
|
16044
17400
|
const loaded = /* @__PURE__ */ new Map();
|
|
@@ -16063,7 +17419,7 @@ function compactAuditDay(dayPath) {
|
|
|
16063
17419
|
ts: 0,
|
|
16064
17420
|
req: { base: null, anchor: "dict", pre: 0, suf: 0, ins: dictionary }
|
|
16065
17421
|
};
|
|
16066
|
-
|
|
17422
|
+
writeFileSync16(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
|
|
16067
17423
|
const result = { shards: 0, anchors: 0, savedBytes: 0 };
|
|
16068
17424
|
for (const [file, entries] of loaded) {
|
|
16069
17425
|
let changed = false;
|
|
@@ -16083,11 +17439,11 @@ function compactAuditDay(dayPath) {
|
|
|
16083
17439
|
const target = join18(bodiesPath, file);
|
|
16084
17440
|
const temp = `${target}.compacting`;
|
|
16085
17441
|
try {
|
|
16086
|
-
|
|
16087
|
-
|
|
17442
|
+
writeFileSync16(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
|
|
17443
|
+
renameSync13(temp, target);
|
|
16088
17444
|
} catch {
|
|
16089
17445
|
try {
|
|
16090
|
-
if (
|
|
17446
|
+
if (existsSync23(temp)) unlinkSync12(temp);
|
|
16091
17447
|
} catch {
|
|
16092
17448
|
}
|
|
16093
17449
|
continue;
|
|
@@ -16106,7 +17462,7 @@ function compactAuditDay(dayPath) {
|
|
|
16106
17462
|
}
|
|
16107
17463
|
function compactAllClosedAuditDays(auditDir, now = Date.now) {
|
|
16108
17464
|
const run = { days: 0, shards: 0, savedBytes: 0 };
|
|
16109
|
-
if (!
|
|
17465
|
+
if (!existsSync23(auditDir)) return run;
|
|
16110
17466
|
const today = auditDayDirName(now());
|
|
16111
17467
|
let names;
|
|
16112
17468
|
try {
|
|
@@ -16131,11 +17487,11 @@ function compactAllClosedAuditDays(auditDir, now = Date.now) {
|
|
|
16131
17487
|
// src/audit/auditStats.ts
|
|
16132
17488
|
import {
|
|
16133
17489
|
createReadStream as createReadStream2,
|
|
16134
|
-
existsSync as
|
|
17490
|
+
existsSync as existsSync24,
|
|
16135
17491
|
readFileSync as readFileSync20,
|
|
16136
17492
|
readdirSync as readdirSync5,
|
|
16137
17493
|
statSync as statSync5,
|
|
16138
|
-
writeFileSync as
|
|
17494
|
+
writeFileSync as writeFileSync17
|
|
16139
17495
|
} from "fs";
|
|
16140
17496
|
import { basename as basename9, dirname as dirname16, join as join19 } from "path";
|
|
16141
17497
|
var SIDECAR_VERSION = 1;
|
|
@@ -16145,7 +17501,7 @@ function auditStatsFileName(auditFile) {
|
|
|
16145
17501
|
return auditFile.replace(/\.jsonl$/, ".stats.json");
|
|
16146
17502
|
}
|
|
16147
17503
|
function readPersisted(path2) {
|
|
16148
|
-
if (!
|
|
17504
|
+
if (!existsSync24(path2)) return null;
|
|
16149
17505
|
try {
|
|
16150
17506
|
const value = JSON.parse(readFileSync20(path2, "utf8"));
|
|
16151
17507
|
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 +17533,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
|
|
|
16177
17533
|
minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
|
|
16178
17534
|
maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
|
|
16179
17535
|
};
|
|
16180
|
-
|
|
17536
|
+
writeFileSync17(statsPath, JSON.stringify(next), "utf8");
|
|
16181
17537
|
}
|
|
16182
17538
|
function queryCovers(stats, from, to) {
|
|
16183
17539
|
return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
|
|
@@ -16288,7 +17644,7 @@ function mergePersistedStats(previous, appended) {
|
|
|
16288
17644
|
};
|
|
16289
17645
|
}
|
|
16290
17646
|
async function readAuditStats(auditDir, query2 = {}) {
|
|
16291
|
-
if (!
|
|
17647
|
+
if (!existsSync24(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
|
|
16292
17648
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
16293
17649
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
16294
17650
|
let sources;
|
|
@@ -16301,7 +17657,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16301
17657
|
auditPath: join19(auditDir, name),
|
|
16302
17658
|
statsPath: join19(auditDir, auditStatsFileName(name))
|
|
16303
17659
|
}
|
|
16304
|
-
).filter((source) =>
|
|
17660
|
+
).filter((source) => existsSync24(source.auditPath));
|
|
16305
17661
|
} catch {
|
|
16306
17662
|
return { requestCount: 0, errorCount: 0, complete: false };
|
|
16307
17663
|
}
|
|
@@ -16327,7 +17683,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16327
17683
|
total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
|
|
16328
17684
|
total.complete = total.complete && scanned.filtered.complete;
|
|
16329
17685
|
const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
|
|
16330
|
-
if (current.complete)
|
|
17686
|
+
if (current.complete) writeFileSync17(statsPath, JSON.stringify(current), "utf8");
|
|
16331
17687
|
} catch {
|
|
16332
17688
|
total.complete = false;
|
|
16333
17689
|
}
|
|
@@ -16336,7 +17692,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16336
17692
|
}
|
|
16337
17693
|
|
|
16338
17694
|
// src/audit/AuditPruneSweeper.ts
|
|
16339
|
-
var
|
|
17695
|
+
var DAY_MS3 = 24 * 60 * 6e4;
|
|
16340
17696
|
var SWEEP_INTERVAL_MS2 = 60 * 6e4;
|
|
16341
17697
|
var ARCHIVE_BATCH = 64;
|
|
16342
17698
|
var AuditPruneSweeper = class {
|
|
@@ -16399,8 +17755,8 @@ var AuditPruneSweeper = class {
|
|
|
16399
17755
|
if (!this.config.enabled || this.sweeping) return 0;
|
|
16400
17756
|
this.sweeping = true;
|
|
16401
17757
|
try {
|
|
16402
|
-
if (!
|
|
16403
|
-
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) *
|
|
17758
|
+
if (!existsSync25(this.auditDir)) return 0;
|
|
17759
|
+
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS3;
|
|
16404
17760
|
let removed = 0;
|
|
16405
17761
|
for (const name of readdirSync6(this.auditDir)) {
|
|
16406
17762
|
const dateMs = auditFileDateMs(name);
|
|
@@ -16411,7 +17767,7 @@ var AuditPruneSweeper = class {
|
|
|
16411
17767
|
} else {
|
|
16412
17768
|
unlinkSync13(join20(this.auditDir, name));
|
|
16413
17769
|
const statsPath = join20(this.auditDir, auditStatsFileName(name));
|
|
16414
|
-
if (
|
|
17770
|
+
if (existsSync25(statsPath)) unlinkSync13(statsPath);
|
|
16415
17771
|
}
|
|
16416
17772
|
removed += 1;
|
|
16417
17773
|
} catch (error) {
|
|
@@ -16441,7 +17797,7 @@ var AuditPruneSweeper = class {
|
|
|
16441
17797
|
if (!this.config.enabled || this.archiving) return 0;
|
|
16442
17798
|
this.archiving = true;
|
|
16443
17799
|
try {
|
|
16444
|
-
if (!
|
|
17800
|
+
if (!existsSync25(this.auditDir)) return 0;
|
|
16445
17801
|
const today = this.todayMidnight();
|
|
16446
17802
|
let compressed = 0;
|
|
16447
17803
|
for (const name of readdirSync6(this.auditDir)) {
|
|
@@ -16495,7 +17851,7 @@ var AuditPruneSweeper = class {
|
|
|
16495
17851
|
const source = join20(bodiesPath, shard);
|
|
16496
17852
|
const target = `${source}.gz`;
|
|
16497
17853
|
try {
|
|
16498
|
-
if (
|
|
17854
|
+
if (existsSync25(target)) {
|
|
16499
17855
|
unlinkSync13(source);
|
|
16500
17856
|
continue;
|
|
16501
17857
|
}
|
|
@@ -16504,7 +17860,7 @@ var AuditPruneSweeper = class {
|
|
|
16504
17860
|
compressed += 1;
|
|
16505
17861
|
} catch (error) {
|
|
16506
17862
|
try {
|
|
16507
|
-
if (
|
|
17863
|
+
if (existsSync25(target)) unlinkSync13(target);
|
|
16508
17864
|
} catch {
|
|
16509
17865
|
}
|
|
16510
17866
|
this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
|
|
@@ -16657,7 +18013,7 @@ async function closeAll(writers) {
|
|
|
16657
18013
|
// src/usage/UsagePruneSweeper.ts
|
|
16658
18014
|
import { unlink as unlink3 } from "fs/promises";
|
|
16659
18015
|
import { join as join22 } from "path";
|
|
16660
|
-
var
|
|
18016
|
+
var DAY_MS4 = 24 * 60 * 6e4;
|
|
16661
18017
|
var SWEEP_INTERVAL_MS3 = 60 * 6e4;
|
|
16662
18018
|
var DEFAULT_USAGE_RETENTION_DAYS = 90;
|
|
16663
18019
|
var UsagePruneSweeper = class {
|
|
@@ -16714,7 +18070,7 @@ var UsagePruneSweeper = class {
|
|
|
16714
18070
|
this.sweeping = true;
|
|
16715
18071
|
try {
|
|
16716
18072
|
const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
|
|
16717
|
-
const cutoff = this.todayMidnight() - (retentionDays - 1) *
|
|
18073
|
+
const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
|
|
16718
18074
|
let removed = 0;
|
|
16719
18075
|
for (const entry of await listUsageDays(this.usageDir)) {
|
|
16720
18076
|
if (!entry.hasShard) continue;
|
|
@@ -16772,7 +18128,7 @@ var UsagePruneSweeper = class {
|
|
|
16772
18128
|
};
|
|
16773
18129
|
|
|
16774
18130
|
// src/audit/auditBodyReader.ts
|
|
16775
|
-
import { existsSync as
|
|
18131
|
+
import { existsSync as existsSync26, readdirSync as readdirSync7, readFileSync as readFileSync21, statSync as statSync7 } from "fs";
|
|
16776
18132
|
import { join as join23 } from "path";
|
|
16777
18133
|
import { gunzipSync } from "zlib";
|
|
16778
18134
|
|
|
@@ -16836,7 +18192,7 @@ function forEachLineFromTail(path2, onLine) {
|
|
|
16836
18192
|
function candidateDays(auditDir, ts) {
|
|
16837
18193
|
if (typeof ts === "number" && Number.isFinite(ts)) {
|
|
16838
18194
|
const named = auditDayDirName(ts);
|
|
16839
|
-
if (
|
|
18195
|
+
if (existsSync26(join23(auditDir, named))) return [named];
|
|
16840
18196
|
}
|
|
16841
18197
|
try {
|
|
16842
18198
|
return readdirSync7(auditDir).filter(isAuditDayDir).sort().reverse();
|
|
@@ -16847,9 +18203,9 @@ function candidateDays(auditDir, ts) {
|
|
|
16847
18203
|
function readShard(auditDir, day, sessionKey) {
|
|
16848
18204
|
const base = join23(auditDir, day, AUDIT_BODIES_DIR, auditBodyFileName(sessionKey));
|
|
16849
18205
|
try {
|
|
16850
|
-
if (
|
|
18206
|
+
if (existsSync26(base)) return readFileSync21(base, "utf8");
|
|
16851
18207
|
const gz = `${base}.gz`;
|
|
16852
|
-
if (
|
|
18208
|
+
if (existsSync26(gz)) return gunzipSync(readFileSync21(gz)).toString("utf8");
|
|
16853
18209
|
} catch {
|
|
16854
18210
|
return null;
|
|
16855
18211
|
}
|
|
@@ -16882,8 +18238,8 @@ function withDictionary(auditDir, day, entries) {
|
|
|
16882
18238
|
const base = join23(auditDir, day, AUDIT_BODIES_DIR, AUDIT_DICT_FILE);
|
|
16883
18239
|
let raw = null;
|
|
16884
18240
|
try {
|
|
16885
|
-
if (
|
|
16886
|
-
else if (
|
|
18241
|
+
if (existsSync26(base)) raw = readFileSync21(base, "utf8");
|
|
18242
|
+
else if (existsSync26(`${base}.gz`)) raw = gunzipSync(readFileSync21(`${base}.gz`)).toString("utf8");
|
|
16887
18243
|
} catch {
|
|
16888
18244
|
return entries;
|
|
16889
18245
|
}
|
|
@@ -16916,7 +18272,7 @@ function reconstructRequest(entries, entry) {
|
|
|
16916
18272
|
}
|
|
16917
18273
|
function readAuditBody(auditDir, query2) {
|
|
16918
18274
|
if (!isSafeSessionKey(query2.sessionKey) || !query2.id) return {};
|
|
16919
|
-
if (!
|
|
18275
|
+
if (!existsSync26(auditDir)) return {};
|
|
16920
18276
|
for (const day of candidateDays(auditDir, query2.ts)) {
|
|
16921
18277
|
const raw = readShard(auditDir, day, query2.sessionKey);
|
|
16922
18278
|
if (raw === null) continue;
|
|
@@ -16963,7 +18319,7 @@ function readLegacyInlineBody(auditDir, id) {
|
|
|
16963
18319
|
}
|
|
16964
18320
|
|
|
16965
18321
|
// src/audit/auditReader.ts
|
|
16966
|
-
import { existsSync as
|
|
18322
|
+
import { existsSync as existsSync27, readdirSync as readdirSync8 } from "fs";
|
|
16967
18323
|
import { join as join24 } from "path";
|
|
16968
18324
|
var DEFAULT_LIMIT = 200;
|
|
16969
18325
|
var MAX_LIMIT = 2e3;
|
|
@@ -16981,7 +18337,7 @@ function daySources(auditDir) {
|
|
|
16981
18337
|
if (dateMs === null) continue;
|
|
16982
18338
|
if (AUDIT_DAY_DIR_RE.test(name)) {
|
|
16983
18339
|
const path2 = join24(auditDir, name, AUDIT_META_FILE);
|
|
16984
|
-
if (
|
|
18340
|
+
if (existsSync27(path2)) sources.push({ path: path2, dateMs });
|
|
16985
18341
|
} else if (AUDIT_FILE_RE.test(name)) {
|
|
16986
18342
|
sources.push({ path: join24(auditDir, name), dateMs });
|
|
16987
18343
|
}
|
|
@@ -16999,7 +18355,7 @@ function toMetaRecord(record) {
|
|
|
16999
18355
|
return { ...meta, hasBody: true };
|
|
17000
18356
|
}
|
|
17001
18357
|
function readAuditRecords(auditDir, query2 = {}) {
|
|
17002
|
-
if (!
|
|
18358
|
+
if (!existsSync27(auditDir)) return [];
|
|
17003
18359
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
17004
18360
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
17005
18361
|
const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
|
|
@@ -17027,7 +18383,7 @@ function readAuditRecords(auditDir, query2 = {}) {
|
|
|
17027
18383
|
}
|
|
17028
18384
|
|
|
17029
18385
|
// src/audit/AuditWriter.ts
|
|
17030
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
18386
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync28, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
|
|
17031
18387
|
import { join as join25 } from "path";
|
|
17032
18388
|
var AuditWriter = class {
|
|
17033
18389
|
constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
|
|
@@ -17085,7 +18441,7 @@ var AuditWriter = class {
|
|
|
17085
18441
|
const { requestBody: _req, responseBody: _res, ...meta } = record;
|
|
17086
18442
|
const file = join25(dayPath, AUDIT_META_FILE);
|
|
17087
18443
|
const line = JSON.stringify(meta) + "\n";
|
|
17088
|
-
const bytesBefore =
|
|
18444
|
+
const bytesBefore = existsSync28(file) ? statSync8(file).size : 0;
|
|
17089
18445
|
appendFileSync2(file, line, "utf8");
|
|
17090
18446
|
try {
|
|
17091
18447
|
updateAuditStatsAfterAppend(
|
|
@@ -17133,7 +18489,7 @@ var AuditWriter = class {
|
|
|
17133
18489
|
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync8 } from "fs";
|
|
17134
18490
|
import { createHmac as createHmac5 } from "crypto";
|
|
17135
18491
|
import { join as join26 } from "path";
|
|
17136
|
-
import { fetchUpstream as
|
|
18492
|
+
import { fetchUpstream as fetchUpstream9 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
17137
18493
|
|
|
17138
18494
|
// src/billing/billingFiles.ts
|
|
17139
18495
|
var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
|
|
@@ -17156,7 +18512,7 @@ var BillingPublisher = class {
|
|
|
17156
18512
|
constructor(billingDir, logger, opts = {}) {
|
|
17157
18513
|
this.billingDir = billingDir;
|
|
17158
18514
|
this.logger = logger;
|
|
17159
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) =>
|
|
18515
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream9(url, init));
|
|
17160
18516
|
this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
|
|
17161
18517
|
this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
|
|
17162
18518
|
this.now = opts.now ?? Date.now;
|
|
@@ -17269,11 +18625,11 @@ var BillingPublisher = class {
|
|
|
17269
18625
|
};
|
|
17270
18626
|
|
|
17271
18627
|
// src/billing/billingReader.ts
|
|
17272
|
-
import { existsSync as
|
|
18628
|
+
import { existsSync as existsSync29, readdirSync as readdirSync9, readFileSync as readFileSync22 } from "fs";
|
|
17273
18629
|
import { join as join27 } from "path";
|
|
17274
18630
|
function readBillingLedger(billingDir) {
|
|
17275
18631
|
const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
|
|
17276
|
-
if (!
|
|
18632
|
+
if (!existsSync29(billingDir)) return view;
|
|
17277
18633
|
let files;
|
|
17278
18634
|
try {
|
|
17279
18635
|
files = readdirSync9(billingDir);
|
|
@@ -17406,7 +18762,7 @@ var BillingRetrySweeper = class {
|
|
|
17406
18762
|
// src/TokenRefreshScheduler.ts
|
|
17407
18763
|
var REFRESH_LEAD_MS2 = 5 * 6e4;
|
|
17408
18764
|
var SWEEP_INTERVAL_MS5 = 6e4;
|
|
17409
|
-
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
|
|
18765
|
+
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
|
|
17410
18766
|
var TokenRefreshScheduler = class {
|
|
17411
18767
|
constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
|
|
17412
18768
|
this.store = store;
|
|
@@ -17489,6 +18845,8 @@ var TokenRefreshScheduler = class {
|
|
|
17489
18845
|
return this.store.refreshCodexToken();
|
|
17490
18846
|
case "gemini":
|
|
17491
18847
|
return this.store.refreshGeminiToken();
|
|
18848
|
+
case "kimi":
|
|
18849
|
+
return this.store.refreshKimiToken();
|
|
17492
18850
|
}
|
|
17493
18851
|
}
|
|
17494
18852
|
};
|
|
@@ -17567,7 +18925,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
|
|
|
17567
18925
|
|
|
17568
18926
|
// src/webhook/WebhookDispatcher.ts
|
|
17569
18927
|
import { createHmac as createHmac6 } from "crypto";
|
|
17570
|
-
import { fetchUpstream as
|
|
18928
|
+
import { fetchUpstream as fetchUpstream10 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
17571
18929
|
var WEBHOOK_MAX_ATTEMPTS = 3;
|
|
17572
18930
|
var WEBHOOK_QUEUE_MAX = 1e3;
|
|
17573
18931
|
var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
|
|
@@ -17587,7 +18945,7 @@ var WebhookDispatcher = class {
|
|
|
17587
18945
|
sleep;
|
|
17588
18946
|
now;
|
|
17589
18947
|
constructor(opts = {}) {
|
|
17590
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) =>
|
|
18948
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream10(url, init));
|
|
17591
18949
|
this.logger = opts.logger;
|
|
17592
18950
|
this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
|
|
17593
18951
|
this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
|
|
@@ -17673,8 +19031,8 @@ var WebhookDispatcher = class {
|
|
|
17673
19031
|
signal: AbortSignal.timeout(this.timeoutMs)
|
|
17674
19032
|
});
|
|
17675
19033
|
return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
|
|
17676
|
-
} catch (
|
|
17677
|
-
return { ok: false, error:
|
|
19034
|
+
} catch (err6) {
|
|
19035
|
+
return { ok: false, error: err6 instanceof Error ? err6.message : String(err6) };
|
|
17678
19036
|
}
|
|
17679
19037
|
}
|
|
17680
19038
|
/**
|
|
@@ -17816,7 +19174,7 @@ function buildDaemon(config, paths) {
|
|
|
17816
19174
|
setSecretBox(secretBox3);
|
|
17817
19175
|
setSecretBox2(secretBox3);
|
|
17818
19176
|
const decryptedConfig = decryptConfigSecrets(config, secretBox3);
|
|
17819
|
-
const accountAllowanceStore = new
|
|
19177
|
+
const accountAllowanceStore = new AccountAllowanceStore6(
|
|
17820
19178
|
Date.now,
|
|
17821
19179
|
void 0,
|
|
17822
19180
|
new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
|
|
@@ -17861,6 +19219,7 @@ function buildDaemon(config, paths) {
|
|
|
17861
19219
|
);
|
|
17862
19220
|
setGeminiCodeAssistResolver(getGeminiCodeAssistProjectResolver());
|
|
17863
19221
|
const autoDisableStore = new AutoDisableStore();
|
|
19222
|
+
const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
|
|
17864
19223
|
const apiKeyPool = new ApiKeyPoolService(
|
|
17865
19224
|
createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
|
|
17866
19225
|
resolveEnvKey,
|
|
@@ -17877,7 +19236,7 @@ function buildDaemon(config, paths) {
|
|
|
17877
19236
|
const pricingEngine = new PricingEngine(pricingStore, logger, {
|
|
17878
19237
|
// Catalog egress follows the same global/env proxy policy as every other
|
|
17879
19238
|
// daemon upstream call; no provider/account override applies here.
|
|
17880
|
-
fetchImpl: ((input, init) =>
|
|
19239
|
+
fetchImpl: ((input, init) => fetchUpstream11(String(input), init ?? {}))
|
|
17881
19240
|
});
|
|
17882
19241
|
const pricingRefreshScheduler = new PricingRefreshScheduler(
|
|
17883
19242
|
pricingEngine,
|
|
@@ -18141,6 +19500,11 @@ function buildDaemon(config, paths) {
|
|
|
18141
19500
|
// values themselves NEVER leave (masked via `maskProviderApiKey`).
|
|
18142
19501
|
apiKeyPool,
|
|
18143
19502
|
autoDisableStore,
|
|
19503
|
+
// BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
|
|
19504
|
+
// read-through cached same-key usage probe surfaced on the keys view. The
|
|
19505
|
+
// key plaintext is resolved + decrypted inside the service and never
|
|
19506
|
+
// crosses back out.
|
|
19507
|
+
providerKeyQuota: providerKeyQuotaService,
|
|
18144
19508
|
// Interactive OAuth login over admin HTTP (app-parity child 4, design
|
|
18145
19509
|
// D1/D2-a). The in-memory pending-session store (NEVER serialized), the
|
|
18146
19510
|
// injected token-exchange fetch (global `fetch` here; mocked in tests), and a
|
|
@@ -18157,7 +19521,7 @@ function buildDaemon(config, paths) {
|
|
|
18157
19521
|
// — `server.proxy.byProvider[...]` was silently skipped — and the call was
|
|
18158
19522
|
// excluded from the upstream trace, so a failing login left no evidence.
|
|
18159
19523
|
// `redactBodies` keeps the code/verifier + minted token out of that trace.
|
|
18160
|
-
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) =>
|
|
19524
|
+
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream11(url, init, { providerId, redactBodies: true }),
|
|
18161
19525
|
subscriptionAccountAppender: credentialStore,
|
|
18162
19526
|
// Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
|
|
18163
19527
|
// + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
|
|
@@ -18165,6 +19529,10 @@ function buildDaemon(config, paths) {
|
|
|
18165
19529
|
// can inject a mock so no real port is bound.
|
|
18166
19530
|
codexSessions: new CodexOAuthSessionStore(),
|
|
18167
19531
|
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
|
|
19532
|
+
// Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
|
|
19533
|
+
// paste; the app shows the verification URL + user code and polls the
|
|
19534
|
+
// token-free status). Token captured + persisted daemon-side.
|
|
19535
|
+
kimiSessions: new CodexOAuthSessionStore(),
|
|
18168
19536
|
// Migration pack (app-parity child 6, design D2/D3) — the concrete credential
|
|
18169
19537
|
// store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
|
|
18170
19538
|
// the multi-account append (`appendProviderAccount`, import re-encrypts at-
|
|
@@ -18223,7 +19591,7 @@ function buildDaemon(config, paths) {
|
|
|
18223
19591
|
});
|
|
18224
19592
|
const webhookDispatcher = new WebhookDispatcher({
|
|
18225
19593
|
logger,
|
|
18226
|
-
fetchImpl: (url, init) =>
|
|
19594
|
+
fetchImpl: (url, init) => fetchUpstream11(url, init)
|
|
18227
19595
|
});
|
|
18228
19596
|
setWebhookRuntime(webhookDispatcher, getSharedAccountHealth4());
|
|
18229
19597
|
const auditWriter = new AuditWriter(auditDir, logger);
|
|
@@ -18320,7 +19688,7 @@ function resetDaemonSingletonsForTests() {
|
|
|
18320
19688
|
}
|
|
18321
19689
|
function isTokensStoreReadable(tokensPath) {
|
|
18322
19690
|
try {
|
|
18323
|
-
if (!
|
|
19691
|
+
if (!existsSync30(tokensPath)) return true;
|
|
18324
19692
|
accessSync(tokensPath, fsConstants.R_OK);
|
|
18325
19693
|
return true;
|
|
18326
19694
|
} catch {
|