@omnicross/daemon 0.2.1 → 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/LICENSE +21 -21
- package/README.md +19 -19
- package/dist/cli.cjs +1761 -337
- package/dist/cli.js +1698 -260
- package/dist/index.cjs +1702 -320
- package/dist/index.d.cts +334 -28
- package/dist/index.d.ts +334 -28
- package/dist/index.js +1637 -246
- package/package.json +6 -6
package/dist/cli.js
CHANGED
|
@@ -1151,7 +1151,7 @@ import {
|
|
|
1151
1151
|
} from "@omnicross/core/search";
|
|
1152
1152
|
|
|
1153
1153
|
// src/bootstrap.ts
|
|
1154
|
-
import { accessSync, constants as fsConstants, existsSync as
|
|
1154
|
+
import { accessSync, constants as fsConstants, existsSync as existsSync30, mkdirSync as mkdirSync9 } from "fs";
|
|
1155
1155
|
import { dirname as dirname17 } from "path";
|
|
1156
1156
|
import { DEFAULT_AUDIT_CONFIG } from "@omnicross/contracts/audit-types";
|
|
1157
1157
|
import { DEFAULT_BILLING_CONFIG } from "@omnicross/contracts/billing-types";
|
|
@@ -1170,14 +1170,14 @@ import { setSubscriptionRegistryForOutbound } from "@omnicross/core/outbound-api
|
|
|
1170
1170
|
import { getSharedAccountHealth as getSharedAccountHealth4 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
1171
1171
|
import {
|
|
1172
1172
|
__resetSharedAccountAllowanceStoreForTests,
|
|
1173
|
-
AccountAllowanceStore as
|
|
1173
|
+
AccountAllowanceStore as AccountAllowanceStore6,
|
|
1174
1174
|
setSharedAccountAllowanceStore
|
|
1175
1175
|
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
1176
1176
|
import {
|
|
1177
1177
|
__resetSharedAccountAllowanceSchedulingForTests,
|
|
1178
1178
|
getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling5
|
|
1179
1179
|
} from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
1180
|
-
import { fetchUpstream as
|
|
1180
|
+
import { fetchUpstream as fetchUpstream11, setUpstreamProxyResolver } from "@omnicross/core/pipeline/upstreamFetch";
|
|
1181
1181
|
import { __resetSharedIdentityStoreForTests } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
|
|
1182
1182
|
import { setGeminiCodeAssistResolver } from "@omnicross/core/ports/gemini-code-assist-resolver";
|
|
1183
1183
|
import {
|
|
@@ -1307,9 +1307,84 @@ function handleCodexOAuthStatus(sessionId, deps) {
|
|
|
1307
1307
|
return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
|
|
1308
1308
|
}
|
|
1309
1309
|
|
|
1310
|
+
// src/admin/accountsKimiOAuth.ts
|
|
1311
|
+
import { kimiOAuth } from "@omnicross/subscriptions";
|
|
1312
|
+
function err2(status, message) {
|
|
1313
|
+
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
1314
|
+
}
|
|
1315
|
+
var DEFAULT_KIMI_OAUTH_TTL_MS = 15 * 6e4;
|
|
1316
|
+
async function handleKimiOAuthStart(deps) {
|
|
1317
|
+
if (deps.kimiSessions.isBusy()) {
|
|
1318
|
+
return err2(409, "a kimi sign-in is already in progress \u2014 finish it in the browser or cancel it");
|
|
1319
|
+
}
|
|
1320
|
+
const fetchImpl = deps.oauthExchangeFetch("kimi");
|
|
1321
|
+
const deviceId = kimiOAuth.generateKimiDeviceId();
|
|
1322
|
+
const fingerprint = kimiOAuth.kimiFingerprintHeaders(deviceId);
|
|
1323
|
+
let authorization;
|
|
1324
|
+
try {
|
|
1325
|
+
authorization = await kimiOAuth.requestDeviceAuthorization(fetchImpl, fingerprint);
|
|
1326
|
+
} catch (e) {
|
|
1327
|
+
const reason = e instanceof Error ? e.message : "device authorization failed";
|
|
1328
|
+
return err2(502, `kimi device authorization failed: ${reason}`);
|
|
1329
|
+
}
|
|
1330
|
+
const { sessionId, signal } = deps.kimiSessions.begin();
|
|
1331
|
+
void runKimiDevicePoll(sessionId, authorization.deviceCode, deviceId, fingerprint, signal, deps).catch(() => deps.kimiSessions.settle(sessionId, "error", "kimi sign-in failed"));
|
|
1332
|
+
return {
|
|
1333
|
+
status: 200,
|
|
1334
|
+
body: {
|
|
1335
|
+
authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
|
|
1336
|
+
userCode: authorization.userCode,
|
|
1337
|
+
sessionId
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
async function runKimiDevicePoll(sessionId, deviceCode, deviceId, fingerprint, signal, deps) {
|
|
1342
|
+
const fetchImpl = deps.oauthExchangeFetch("kimi");
|
|
1343
|
+
const result = await kimiOAuth.awaitDeviceToken(
|
|
1344
|
+
{ userCode: "", deviceCode, verificationUri: "" },
|
|
1345
|
+
fetchImpl,
|
|
1346
|
+
{
|
|
1347
|
+
fingerprint,
|
|
1348
|
+
deadlineMs: DEFAULT_KIMI_OAUTH_TTL_MS,
|
|
1349
|
+
sleep: (ms) => new Promise((resolve11, reject) => {
|
|
1350
|
+
const onAbort = () => {
|
|
1351
|
+
clearTimeout(timer);
|
|
1352
|
+
reject(new Error("login: cancelled"));
|
|
1353
|
+
};
|
|
1354
|
+
const timer = setTimeout(() => {
|
|
1355
|
+
signal.removeEventListener("abort", onAbort);
|
|
1356
|
+
resolve11();
|
|
1357
|
+
}, ms);
|
|
1358
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1359
|
+
})
|
|
1360
|
+
}
|
|
1361
|
+
);
|
|
1362
|
+
const block = {
|
|
1363
|
+
authMethod: "oauth",
|
|
1364
|
+
status: "authorized",
|
|
1365
|
+
accessToken: result.accessToken,
|
|
1366
|
+
refreshToken: result.refreshToken,
|
|
1367
|
+
expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
|
|
1368
|
+
accountId: kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
|
|
1369
|
+
deviceId,
|
|
1370
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1371
|
+
};
|
|
1372
|
+
await deps.subscriptionAccountAppender.appendProviderAccount("kimi", block);
|
|
1373
|
+
deps.kimiSessions.settle(sessionId, "done");
|
|
1374
|
+
}
|
|
1375
|
+
function handleKimiOAuthCancel(sessionId, deps) {
|
|
1376
|
+
if (!deps.kimiSessions.cancel(sessionId)) return err2(404, "unknown or expired kimi sign-in session");
|
|
1377
|
+
return { status: 200, body: { ok: true } };
|
|
1378
|
+
}
|
|
1379
|
+
function handleKimiOAuthStatus(sessionId, deps) {
|
|
1380
|
+
const s = deps.kimiSessions.get(sessionId);
|
|
1381
|
+
if (!s) return err2(404, "unknown or expired kimi sign-in session");
|
|
1382
|
+
return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1310
1385
|
// src/allowance/AccountAllowanceService.ts
|
|
1311
1386
|
import {
|
|
1312
|
-
getSharedAccountAllowanceStore as
|
|
1387
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore5
|
|
1313
1388
|
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
1314
1389
|
import {
|
|
1315
1390
|
getSharedAccountAllowanceScheduling
|
|
@@ -1343,13 +1418,11 @@ function secondsUntil(instant, now) {
|
|
|
1343
1418
|
function windowFromPayload(id, payload, now) {
|
|
1344
1419
|
const usedPercent = finitePercent(payload?.utilization);
|
|
1345
1420
|
const resetsAt = isoInstant(payload?.resets_at);
|
|
1346
|
-
const isSonnet = id === "seven-day-sonnet";
|
|
1347
1421
|
const isFiveHour = id === "five-hour";
|
|
1348
1422
|
return {
|
|
1349
1423
|
id,
|
|
1350
|
-
label: isFiveHour ? "5 hours" :
|
|
1351
|
-
scope:
|
|
1352
|
-
modelFamily: isSonnet ? "sonnet" : void 0,
|
|
1424
|
+
label: isFiveHour ? "5 hours" : "7 days",
|
|
1425
|
+
scope: "all",
|
|
1353
1426
|
usedPercent,
|
|
1354
1427
|
windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
|
|
1355
1428
|
resetsAt,
|
|
@@ -1357,6 +1430,44 @@ function windowFromPayload(id, payload, now) {
|
|
|
1357
1430
|
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
1358
1431
|
};
|
|
1359
1432
|
}
|
|
1433
|
+
function limitEntryWindow(entries, kind) {
|
|
1434
|
+
const entry = entries.find((candidate) => candidate.kind === kind);
|
|
1435
|
+
if (!entry) return void 0;
|
|
1436
|
+
return { utilization: entry.percent, resets_at: entry.resets_at };
|
|
1437
|
+
}
|
|
1438
|
+
function slugifyDisplayName(name) {
|
|
1439
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1440
|
+
}
|
|
1441
|
+
function scopedWeeklyWindows(entries, now) {
|
|
1442
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1443
|
+
const windows = [];
|
|
1444
|
+
for (const entry of entries) {
|
|
1445
|
+
if (entry.kind !== "weekly_scoped") continue;
|
|
1446
|
+
const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
|
|
1447
|
+
if (!displayName) continue;
|
|
1448
|
+
const slug = slugifyDisplayName(displayName);
|
|
1449
|
+
if (!slug || seen.has(slug)) continue;
|
|
1450
|
+
seen.add(slug);
|
|
1451
|
+
const usedPercent = finitePercent(entry.percent);
|
|
1452
|
+
const resetsAt = isoInstant(entry.resets_at);
|
|
1453
|
+
windows.push({
|
|
1454
|
+
id: `seven-day-${slug}`,
|
|
1455
|
+
label: `7 days \xB7 ${displayName}`,
|
|
1456
|
+
scope: "model-family",
|
|
1457
|
+
modelFamily: slug,
|
|
1458
|
+
usedPercent,
|
|
1459
|
+
windowMinutes: 7 * 24 * 60,
|
|
1460
|
+
resetsAt,
|
|
1461
|
+
remainingSeconds: secondsUntil(resetsAt, now),
|
|
1462
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
return windows;
|
|
1466
|
+
}
|
|
1467
|
+
function parseLimitEntries(raw) {
|
|
1468
|
+
if (!Array.isArray(raw)) return [];
|
|
1469
|
+
return raw.filter((entry) => !!entry && typeof entry === "object");
|
|
1470
|
+
}
|
|
1360
1471
|
function emptyClaudeWindows(state) {
|
|
1361
1472
|
return [
|
|
1362
1473
|
{
|
|
@@ -1374,15 +1485,6 @@ function emptyClaudeWindows(state) {
|
|
|
1374
1485
|
usedPercent: null,
|
|
1375
1486
|
windowMinutes: 7 * 24 * 60,
|
|
1376
1487
|
state
|
|
1377
|
-
},
|
|
1378
|
-
{
|
|
1379
|
-
id: "seven-day-sonnet",
|
|
1380
|
-
label: "7 days \xB7 Sonnet",
|
|
1381
|
-
scope: "model-family",
|
|
1382
|
-
modelFamily: "sonnet",
|
|
1383
|
-
usedPercent: null,
|
|
1384
|
-
windowMinutes: 7 * 24 * 60,
|
|
1385
|
-
state
|
|
1386
1488
|
}
|
|
1387
1489
|
];
|
|
1388
1490
|
}
|
|
@@ -1463,6 +1565,9 @@ var ClaudeAllowanceCollector = class {
|
|
|
1463
1565
|
}
|
|
1464
1566
|
const now = this.now();
|
|
1465
1567
|
const usage = payload;
|
|
1568
|
+
const limitEntries = parseLimitEntries(usage.limits);
|
|
1569
|
+
const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
|
|
1570
|
+
const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
|
|
1466
1571
|
const snapshot = {
|
|
1467
1572
|
providerId: "claude",
|
|
1468
1573
|
accountId,
|
|
@@ -1470,10 +1575,10 @@ var ClaudeAllowanceCollector = class {
|
|
|
1470
1575
|
observedAt: new Date(now).toISOString(),
|
|
1471
1576
|
expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1472
1577
|
windows: [
|
|
1473
|
-
windowFromPayload("five-hour",
|
|
1474
|
-
windowFromPayload("seven-day",
|
|
1475
|
-
|
|
1476
|
-
]
|
|
1578
|
+
windowFromPayload("five-hour", fiveHour, now),
|
|
1579
|
+
windowFromPayload("seven-day", sevenDay, now),
|
|
1580
|
+
...scopedWeeklyWindows(limitEntries, now)
|
|
1581
|
+
].slice(0, 8)
|
|
1477
1582
|
};
|
|
1478
1583
|
this.store.set(snapshot);
|
|
1479
1584
|
return snapshot;
|
|
@@ -1530,6 +1635,607 @@ var ClaudeAllowanceCollector = class {
|
|
|
1530
1635
|
}
|
|
1531
1636
|
};
|
|
1532
1637
|
|
|
1638
|
+
// src/allowance/CodexAllowanceCollector.ts
|
|
1639
|
+
import {
|
|
1640
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
|
|
1641
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
1642
|
+
import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
1643
|
+
var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
1644
|
+
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
1645
|
+
var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
|
|
1646
|
+
function finiteNumber(value) {
|
|
1647
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
1648
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1649
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
1650
|
+
}
|
|
1651
|
+
function finitePercent2(value) {
|
|
1652
|
+
const parsed = finiteNumber(value);
|
|
1653
|
+
return parsed !== null && parsed <= 100 ? parsed : null;
|
|
1654
|
+
}
|
|
1655
|
+
function epochMs(value) {
|
|
1656
|
+
return value > 1e11 ? value : value * 1e3;
|
|
1657
|
+
}
|
|
1658
|
+
function secondsUntil2(instant, now) {
|
|
1659
|
+
if (!instant) return void 0;
|
|
1660
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
1661
|
+
}
|
|
1662
|
+
function decodeJwtClaims(token) {
|
|
1663
|
+
const parts = token.split(".");
|
|
1664
|
+
if (parts.length !== 3) return void 0;
|
|
1665
|
+
try {
|
|
1666
|
+
const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
1667
|
+
const parsed = JSON.parse(json2);
|
|
1668
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
1669
|
+
} catch {
|
|
1670
|
+
return void 0;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
function chatgptAccountIdFromClaims(claims) {
|
|
1674
|
+
const auth = claims?.["https://api.openai.com/auth"];
|
|
1675
|
+
if (!auth || typeof auth !== "object") return void 0;
|
|
1676
|
+
const accountId = auth.chatgpt_account_id;
|
|
1677
|
+
return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
|
|
1678
|
+
}
|
|
1679
|
+
function resolveCodexChatGptAccountId(tokens) {
|
|
1680
|
+
if (tokens.accountId?.trim()) return tokens.accountId.trim();
|
|
1681
|
+
if (tokens.idToken) {
|
|
1682
|
+
const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
|
|
1683
|
+
if (fromIdToken) return fromIdToken;
|
|
1684
|
+
}
|
|
1685
|
+
if (tokens.accessToken) {
|
|
1686
|
+
return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
|
|
1687
|
+
}
|
|
1688
|
+
return void 0;
|
|
1689
|
+
}
|
|
1690
|
+
function windowFromPayload2(id, payload, now) {
|
|
1691
|
+
const usedPercent = finitePercent2(payload?.used_percent);
|
|
1692
|
+
const resetAtSeconds = finiteNumber(payload?.reset_at);
|
|
1693
|
+
const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
|
|
1694
|
+
const windowSeconds = finiteNumber(payload?.limit_window_seconds);
|
|
1695
|
+
const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
|
|
1696
|
+
const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
|
|
1697
|
+
return {
|
|
1698
|
+
id,
|
|
1699
|
+
label: id === "primary" ? "Primary" : "Secondary",
|
|
1700
|
+
scope: "all",
|
|
1701
|
+
usedPercent,
|
|
1702
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
1703
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
1704
|
+
remainingSeconds: secondsUntil2(resetsAt, now),
|
|
1705
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
var CodexAllowanceCollector = class {
|
|
1709
|
+
constructor(credentials, store = getSharedAccountAllowanceStore2(), fetchImpl = (url, init, accountId) => fetchUpstream2(url, init, { providerId: "codex", accountId, redactBodies: true }), now = Date.now) {
|
|
1710
|
+
this.credentials = credentials;
|
|
1711
|
+
this.store = store;
|
|
1712
|
+
this.fetchImpl = fetchImpl;
|
|
1713
|
+
this.now = now;
|
|
1714
|
+
}
|
|
1715
|
+
credentials;
|
|
1716
|
+
store;
|
|
1717
|
+
fetchImpl;
|
|
1718
|
+
now;
|
|
1719
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1720
|
+
async collectMany(accounts, options = {}) {
|
|
1721
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
1722
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
1723
|
+
}
|
|
1724
|
+
collect(account, options = {}) {
|
|
1725
|
+
const now = this.now();
|
|
1726
|
+
const unsupported = account.tokens.authMethod !== "oauth";
|
|
1727
|
+
if (unsupported) {
|
|
1728
|
+
const existing = this.store.get("codex", account.id, now);
|
|
1729
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
1730
|
+
return Promise.resolve(existing);
|
|
1731
|
+
}
|
|
1732
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
1733
|
+
this.store.set(snapshot);
|
|
1734
|
+
return Promise.resolve(snapshot);
|
|
1735
|
+
}
|
|
1736
|
+
const cached = this.store.get("codex", account.id, now);
|
|
1737
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
1738
|
+
return Promise.resolve(cached);
|
|
1739
|
+
}
|
|
1740
|
+
const running = this.inFlight.get(account.id);
|
|
1741
|
+
if (running) return running;
|
|
1742
|
+
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));
|
|
1743
|
+
this.inFlight.set(account.id, promise);
|
|
1744
|
+
return promise;
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* A response-header snapshot stays a valid cache hit only while fresh; an
|
|
1748
|
+
* active oauth-usage snapshot is honored on the same 5-minute cadence as
|
|
1749
|
+
* Claude's (the poll is cheap and quota is the scheduling input).
|
|
1750
|
+
*/
|
|
1751
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
1752
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
1753
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
1754
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
1755
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
1756
|
+
}
|
|
1757
|
+
async fetchAccount(accountId, tokens) {
|
|
1758
|
+
let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
|
|
1759
|
+
if (!accessToken) {
|
|
1760
|
+
return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
|
|
1761
|
+
}
|
|
1762
|
+
let response = await this.request(accountId, accessToken, tokens);
|
|
1763
|
+
if (response.status === 401) {
|
|
1764
|
+
const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
|
|
1765
|
+
if (!refreshed) {
|
|
1766
|
+
return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
|
|
1767
|
+
}
|
|
1768
|
+
accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
|
|
1769
|
+
if (!accessToken) {
|
|
1770
|
+
return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
|
|
1771
|
+
}
|
|
1772
|
+
response = await this.request(accountId, accessToken, tokens);
|
|
1773
|
+
}
|
|
1774
|
+
if (response.status === 403) {
|
|
1775
|
+
const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
|
|
1776
|
+
this.store.set(snapshot2);
|
|
1777
|
+
return snapshot2;
|
|
1778
|
+
}
|
|
1779
|
+
if (!response.ok) {
|
|
1780
|
+
return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
|
|
1781
|
+
}
|
|
1782
|
+
let payload;
|
|
1783
|
+
try {
|
|
1784
|
+
payload = await response.json();
|
|
1785
|
+
} catch {
|
|
1786
|
+
return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
|
|
1787
|
+
}
|
|
1788
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
1789
|
+
return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
|
|
1790
|
+
}
|
|
1791
|
+
const now = this.now();
|
|
1792
|
+
const usage = payload.rate_limit;
|
|
1793
|
+
const previous = this.store.get("codex", accountId, now);
|
|
1794
|
+
const snapshot = {
|
|
1795
|
+
providerId: "codex",
|
|
1796
|
+
accountId,
|
|
1797
|
+
source: "oauth-usage-api",
|
|
1798
|
+
observedAt: new Date(now).toISOString(),
|
|
1799
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1800
|
+
windows: [
|
|
1801
|
+
windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
|
|
1802
|
+
windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
|
|
1803
|
+
],
|
|
1804
|
+
// The wham payload has no ratio field; keep the passively-observed value.
|
|
1805
|
+
...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
|
|
1806
|
+
};
|
|
1807
|
+
this.store.set(snapshot);
|
|
1808
|
+
return snapshot;
|
|
1809
|
+
}
|
|
1810
|
+
request(accountId, accessToken, tokens) {
|
|
1811
|
+
const headers = {
|
|
1812
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1813
|
+
Accept: "application/json",
|
|
1814
|
+
"User-Agent": CODEX_CLI_USER_AGENT
|
|
1815
|
+
};
|
|
1816
|
+
const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
|
|
1817
|
+
if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
|
|
1818
|
+
return this.fetchImpl(CODEX_USAGE_URL, {
|
|
1819
|
+
method: "GET",
|
|
1820
|
+
headers,
|
|
1821
|
+
signal: AbortSignal.timeout(15e3)
|
|
1822
|
+
}, accountId);
|
|
1823
|
+
}
|
|
1824
|
+
failureSnapshot(accountId, code, now) {
|
|
1825
|
+
const existing = this.store.get("codex", accountId, now);
|
|
1826
|
+
const snapshot = existing ? {
|
|
1827
|
+
...existing,
|
|
1828
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1829
|
+
windows: existing.windows.map((window) => ({
|
|
1830
|
+
...window,
|
|
1831
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
1832
|
+
})),
|
|
1833
|
+
lastErrorCode: code
|
|
1834
|
+
} : {
|
|
1835
|
+
providerId: "codex",
|
|
1836
|
+
accountId,
|
|
1837
|
+
source: "oauth-usage-api",
|
|
1838
|
+
observedAt: new Date(now).toISOString(),
|
|
1839
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1840
|
+
windows: [
|
|
1841
|
+
{ id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
|
|
1842
|
+
{ id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
|
|
1843
|
+
],
|
|
1844
|
+
lastErrorCode: code
|
|
1845
|
+
};
|
|
1846
|
+
this.store.set(snapshot);
|
|
1847
|
+
return snapshot;
|
|
1848
|
+
}
|
|
1849
|
+
unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
|
|
1850
|
+
return {
|
|
1851
|
+
providerId: "codex",
|
|
1852
|
+
accountId,
|
|
1853
|
+
source: "oauth-usage-api",
|
|
1854
|
+
observedAt: new Date(now).toISOString(),
|
|
1855
|
+
windows: [
|
|
1856
|
+
{ id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
|
|
1857
|
+
{ id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
|
|
1858
|
+
],
|
|
1859
|
+
lastErrorCode: code
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
};
|
|
1863
|
+
|
|
1864
|
+
// src/allowance/KimiAllowanceCollector.ts
|
|
1865
|
+
import {
|
|
1866
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore3
|
|
1867
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
1868
|
+
import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
1869
|
+
import { kimiFingerprintHeaders } from "@omnicross/subscriptions";
|
|
1870
|
+
var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
1871
|
+
var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
|
|
1872
|
+
function finiteNumber2(value) {
|
|
1873
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
1874
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1875
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
1876
|
+
}
|
|
1877
|
+
function isRecord(value) {
|
|
1878
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1879
|
+
}
|
|
1880
|
+
function parseResetMs(row, nowMs) {
|
|
1881
|
+
for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
|
|
1882
|
+
const value = row[key];
|
|
1883
|
+
if (typeof value === "string" && value.trim()) {
|
|
1884
|
+
const parsed = Date.parse(value);
|
|
1885
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
1886
|
+
}
|
|
1887
|
+
const numeric = finiteNumber2(value);
|
|
1888
|
+
if (numeric !== void 0 && numeric > 1e9) {
|
|
1889
|
+
return numeric > 1e12 ? numeric : numeric * 1e3;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
|
|
1893
|
+
const seconds = finiteNumber2(row[key]);
|
|
1894
|
+
if (seconds !== void 0) return nowMs + seconds * 1e3;
|
|
1895
|
+
}
|
|
1896
|
+
return void 0;
|
|
1897
|
+
}
|
|
1898
|
+
var MINUTE_MS = 6e4;
|
|
1899
|
+
var HOUR_MS = 36e5;
|
|
1900
|
+
var DAY_MS = 864e5;
|
|
1901
|
+
function canonicalWindow(durationMs) {
|
|
1902
|
+
if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
|
|
1903
|
+
if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
|
|
1904
|
+
if (durationMs > 0 && durationMs % DAY_MS === 0) {
|
|
1905
|
+
const days = durationMs / DAY_MS;
|
|
1906
|
+
return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
|
|
1907
|
+
}
|
|
1908
|
+
if (durationMs > 0 && durationMs % HOUR_MS === 0) {
|
|
1909
|
+
const hours = durationMs / HOUR_MS;
|
|
1910
|
+
return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
|
|
1911
|
+
}
|
|
1912
|
+
return void 0;
|
|
1913
|
+
}
|
|
1914
|
+
function secondsUntil3(instant, now) {
|
|
1915
|
+
if (!instant) return void 0;
|
|
1916
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
1917
|
+
}
|
|
1918
|
+
function windowFromRow(row, fallback, now) {
|
|
1919
|
+
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;
|
|
1920
|
+
const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
|
|
1921
|
+
return {
|
|
1922
|
+
id: fallback.id,
|
|
1923
|
+
label: fallback.label,
|
|
1924
|
+
scope: "all",
|
|
1925
|
+
usedPercent,
|
|
1926
|
+
windowMinutes: fallback.minutes,
|
|
1927
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
1928
|
+
remainingSeconds: secondsUntil3(resetsAt, now),
|
|
1929
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
function parseKimiUsagePayload(payload, now) {
|
|
1933
|
+
if (!isRecord(payload)) return [];
|
|
1934
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1935
|
+
const rowFrom = (data) => {
|
|
1936
|
+
const limit = finiteNumber2(data["limit"]);
|
|
1937
|
+
let used = finiteNumber2(data["used"]);
|
|
1938
|
+
const remaining = finiteNumber2(data["remaining"]);
|
|
1939
|
+
if (used === void 0 && remaining !== void 0 && limit !== void 0) {
|
|
1940
|
+
used = limit - remaining;
|
|
1941
|
+
}
|
|
1942
|
+
let windowDurationMs;
|
|
1943
|
+
const windowData = isRecord(data["window"]) ? data["window"] : void 0;
|
|
1944
|
+
const duration = finiteNumber2(windowData?.["duration"]);
|
|
1945
|
+
const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
|
|
1946
|
+
if (duration !== void 0) {
|
|
1947
|
+
if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
|
|
1948
|
+
else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
|
|
1949
|
+
else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
|
|
1950
|
+
else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
|
|
1951
|
+
else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
|
|
1952
|
+
}
|
|
1953
|
+
const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
|
|
1954
|
+
return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
|
|
1955
|
+
};
|
|
1956
|
+
if (isRecord(payload["usage"])) {
|
|
1957
|
+
const row = rowFrom(payload["usage"]);
|
|
1958
|
+
const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
|
|
1959
|
+
byId.set("seven-day", window);
|
|
1960
|
+
}
|
|
1961
|
+
if (Array.isArray(payload["limits"])) {
|
|
1962
|
+
for (const item of payload["limits"]) {
|
|
1963
|
+
if (!isRecord(item)) continue;
|
|
1964
|
+
const detail = isRecord(item["detail"]) ? item["detail"] : item;
|
|
1965
|
+
const row = rowFrom(detail);
|
|
1966
|
+
const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
|
|
1967
|
+
if (!canonical) continue;
|
|
1968
|
+
const window = windowFromRow(row, canonical, now);
|
|
1969
|
+
const existing = byId.get(canonical.id);
|
|
1970
|
+
if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
|
|
1971
|
+
byId.set(canonical.id, window);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
|
|
1976
|
+
}
|
|
1977
|
+
var KimiAllowanceCollector = class {
|
|
1978
|
+
constructor(credentials, store = getSharedAccountAllowanceStore3(), fetchImpl = (url, init, accountId) => fetchUpstream3(url, init, { providerId: "kimi", accountId, redactBodies: true }), now = Date.now) {
|
|
1979
|
+
this.credentials = credentials;
|
|
1980
|
+
this.store = store;
|
|
1981
|
+
this.fetchImpl = fetchImpl;
|
|
1982
|
+
this.now = now;
|
|
1983
|
+
}
|
|
1984
|
+
credentials;
|
|
1985
|
+
store;
|
|
1986
|
+
fetchImpl;
|
|
1987
|
+
now;
|
|
1988
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1989
|
+
async collectMany(accounts, options = {}) {
|
|
1990
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
1991
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
1992
|
+
}
|
|
1993
|
+
collect(account, options = {}) {
|
|
1994
|
+
const now = this.now();
|
|
1995
|
+
if (account.tokens.authMethod !== "oauth") {
|
|
1996
|
+
const existing = this.store.get("kimi", account.id, now);
|
|
1997
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
1998
|
+
return Promise.resolve(existing);
|
|
1999
|
+
}
|
|
2000
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
2001
|
+
this.store.set(snapshot);
|
|
2002
|
+
return Promise.resolve(snapshot);
|
|
2003
|
+
}
|
|
2004
|
+
const cached = this.store.get("kimi", account.id, now);
|
|
2005
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
2006
|
+
return Promise.resolve(cached);
|
|
2007
|
+
}
|
|
2008
|
+
const running = this.inFlight.get(account.id);
|
|
2009
|
+
if (running) return running;
|
|
2010
|
+
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));
|
|
2011
|
+
this.inFlight.set(account.id, promise);
|
|
2012
|
+
return promise;
|
|
2013
|
+
}
|
|
2014
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
2015
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
2016
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
2017
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
2018
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
2019
|
+
}
|
|
2020
|
+
async fetchAccount(accountId, tokens) {
|
|
2021
|
+
let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
|
|
2022
|
+
if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
|
|
2023
|
+
let response = await this.request(accountId, accessToken, tokens);
|
|
2024
|
+
if (response.status === 401) {
|
|
2025
|
+
const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
|
|
2026
|
+
if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
|
|
2027
|
+
accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
|
|
2028
|
+
if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
|
|
2029
|
+
response = await this.request(accountId, accessToken, tokens);
|
|
2030
|
+
}
|
|
2031
|
+
if (response.status === 403) {
|
|
2032
|
+
const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
|
|
2033
|
+
this.store.set(snapshot2);
|
|
2034
|
+
return snapshot2;
|
|
2035
|
+
}
|
|
2036
|
+
if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
|
|
2037
|
+
let payload;
|
|
2038
|
+
try {
|
|
2039
|
+
payload = await response.json();
|
|
2040
|
+
} catch {
|
|
2041
|
+
return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
|
|
2042
|
+
}
|
|
2043
|
+
const now = this.now();
|
|
2044
|
+
const windows = parseKimiUsagePayload(payload, now);
|
|
2045
|
+
const snapshot = {
|
|
2046
|
+
providerId: "kimi",
|
|
2047
|
+
accountId,
|
|
2048
|
+
source: "oauth-usage-api",
|
|
2049
|
+
observedAt: new Date(now).toISOString(),
|
|
2050
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
2051
|
+
windows: windows.length > 0 ? windows : [
|
|
2052
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
2053
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
2054
|
+
],
|
|
2055
|
+
...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
|
|
2056
|
+
};
|
|
2057
|
+
this.store.set(snapshot);
|
|
2058
|
+
return snapshot;
|
|
2059
|
+
}
|
|
2060
|
+
request(accountId, accessToken, tokens) {
|
|
2061
|
+
return this.fetchImpl(KIMI_USAGE_URL, {
|
|
2062
|
+
method: "GET",
|
|
2063
|
+
headers: {
|
|
2064
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2065
|
+
Accept: "application/json",
|
|
2066
|
+
...kimiFingerprintHeaders(tokens.deviceId)
|
|
2067
|
+
},
|
|
2068
|
+
signal: AbortSignal.timeout(15e3)
|
|
2069
|
+
}, accountId);
|
|
2070
|
+
}
|
|
2071
|
+
failureSnapshot(accountId, code, now) {
|
|
2072
|
+
const existing = this.store.get("kimi", accountId, now);
|
|
2073
|
+
const snapshot = existing ? {
|
|
2074
|
+
...existing,
|
|
2075
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
2076
|
+
windows: existing.windows.map((window) => ({
|
|
2077
|
+
...window,
|
|
2078
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
2079
|
+
})),
|
|
2080
|
+
lastErrorCode: code
|
|
2081
|
+
} : {
|
|
2082
|
+
providerId: "kimi",
|
|
2083
|
+
accountId,
|
|
2084
|
+
source: "oauth-usage-api",
|
|
2085
|
+
observedAt: new Date(now).toISOString(),
|
|
2086
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
2087
|
+
windows: [
|
|
2088
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
2089
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
2090
|
+
],
|
|
2091
|
+
lastErrorCode: code
|
|
2092
|
+
};
|
|
2093
|
+
this.store.set(snapshot);
|
|
2094
|
+
return snapshot;
|
|
2095
|
+
}
|
|
2096
|
+
unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
|
|
2097
|
+
return {
|
|
2098
|
+
providerId: "kimi",
|
|
2099
|
+
accountId,
|
|
2100
|
+
source: "oauth-usage-api",
|
|
2101
|
+
observedAt: new Date(now).toISOString(),
|
|
2102
|
+
windows: [
|
|
2103
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
|
|
2104
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
|
|
2105
|
+
],
|
|
2106
|
+
lastErrorCode: code
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
};
|
|
2110
|
+
|
|
2111
|
+
// src/allowance/OpenCodeGoAllowanceCollector.ts
|
|
2112
|
+
import {
|
|
2113
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore4
|
|
2114
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
2115
|
+
import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
2116
|
+
import { normalizeOpenCodeGoBaseUrl } from "@omnicross/subscriptions";
|
|
2117
|
+
var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
2118
|
+
var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
|
|
2119
|
+
function finitePercent3(value) {
|
|
2120
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
2121
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
2122
|
+
return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
|
|
2123
|
+
}
|
|
2124
|
+
function isoInstant2(value) {
|
|
2125
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
2126
|
+
const time = Date.parse(value);
|
|
2127
|
+
return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
|
|
2128
|
+
}
|
|
2129
|
+
function secondsUntil4(instant, now) {
|
|
2130
|
+
if (!instant) return void 0;
|
|
2131
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
2132
|
+
}
|
|
2133
|
+
function windowFromPayload3(id, label, minutes, payload, now) {
|
|
2134
|
+
const statusRateLimited = payload?.status === "rate-limited";
|
|
2135
|
+
const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
|
|
2136
|
+
const resetsAt = isoInstant2(payload?.resetsAt);
|
|
2137
|
+
return {
|
|
2138
|
+
id,
|
|
2139
|
+
label,
|
|
2140
|
+
scope: "all",
|
|
2141
|
+
usedPercent,
|
|
2142
|
+
windowMinutes: minutes,
|
|
2143
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
2144
|
+
remainingSeconds: secondsUntil4(resetsAt, now),
|
|
2145
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
var OpenCodeGoAllowanceCollector = class {
|
|
2149
|
+
constructor(credentials, store = getSharedAccountAllowanceStore4(), fetchImpl = (url, init, accountId) => fetchUpstream4(url, init, { providerId: "opencodego", accountId, redactBodies: true }), now = Date.now) {
|
|
2150
|
+
this.credentials = credentials;
|
|
2151
|
+
this.store = store;
|
|
2152
|
+
this.fetchImpl = fetchImpl;
|
|
2153
|
+
this.now = now;
|
|
2154
|
+
}
|
|
2155
|
+
credentials;
|
|
2156
|
+
store;
|
|
2157
|
+
fetchImpl;
|
|
2158
|
+
now;
|
|
2159
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
2160
|
+
async collectMany(accounts, options = {}) {
|
|
2161
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
2162
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
2163
|
+
}
|
|
2164
|
+
collect(account, options = {}) {
|
|
2165
|
+
const now = this.now();
|
|
2166
|
+
const cached = this.store.get("opencodego", account.id, now);
|
|
2167
|
+
if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
|
|
2168
|
+
return Promise.resolve(cached);
|
|
2169
|
+
}
|
|
2170
|
+
const running = this.inFlight.get(account.id);
|
|
2171
|
+
if (running) return running;
|
|
2172
|
+
const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
|
|
2173
|
+
this.inFlight.set(account.id, promise);
|
|
2174
|
+
return promise;
|
|
2175
|
+
}
|
|
2176
|
+
async fetchAccount(account) {
|
|
2177
|
+
const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
|
|
2178
|
+
if (!apiKey) return this.failureSnapshot(account.id, this.now());
|
|
2179
|
+
const base = account.tokens.baseUrl ? normalizeOpenCodeGoBaseUrl(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
|
|
2180
|
+
const response = await this.fetchImpl(`${base}/v1/usage`, {
|
|
2181
|
+
method: "GET",
|
|
2182
|
+
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
2183
|
+
signal: AbortSignal.timeout(15e3)
|
|
2184
|
+
}, account.id);
|
|
2185
|
+
if (response.status === 401 || response.status === 403) {
|
|
2186
|
+
return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
|
|
2187
|
+
}
|
|
2188
|
+
if (!response.ok) return this.failureSnapshot(account.id, this.now());
|
|
2189
|
+
let payload;
|
|
2190
|
+
try {
|
|
2191
|
+
payload = await response.json();
|
|
2192
|
+
} catch {
|
|
2193
|
+
return this.failureSnapshot(account.id, this.now());
|
|
2194
|
+
}
|
|
2195
|
+
const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
|
|
2196
|
+
const now = this.now();
|
|
2197
|
+
const snapshot = {
|
|
2198
|
+
providerId: "opencodego",
|
|
2199
|
+
accountId: account.id,
|
|
2200
|
+
source: "oauth-usage-api",
|
|
2201
|
+
observedAt: new Date(now).toISOString(),
|
|
2202
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
2203
|
+
// Monthly deliberately omitted (module doc).
|
|
2204
|
+
windows: [
|
|
2205
|
+
windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
|
|
2206
|
+
windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
|
|
2207
|
+
]
|
|
2208
|
+
};
|
|
2209
|
+
this.store.set(snapshot);
|
|
2210
|
+
return snapshot;
|
|
2211
|
+
}
|
|
2212
|
+
failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
|
|
2213
|
+
const existing = this.store.get("opencodego", accountId, now);
|
|
2214
|
+
const snapshot = existing ? {
|
|
2215
|
+
...existing,
|
|
2216
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
2217
|
+
windows: existing.windows.map((window) => ({
|
|
2218
|
+
...window,
|
|
2219
|
+
state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
|
|
2220
|
+
})),
|
|
2221
|
+
lastErrorCode: code
|
|
2222
|
+
} : {
|
|
2223
|
+
providerId: "opencodego",
|
|
2224
|
+
accountId,
|
|
2225
|
+
source: "oauth-usage-api",
|
|
2226
|
+
observedAt: new Date(now).toISOString(),
|
|
2227
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
2228
|
+
windows: [
|
|
2229
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
2230
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
2231
|
+
],
|
|
2232
|
+
lastErrorCode: code
|
|
2233
|
+
};
|
|
2234
|
+
this.store.set(snapshot);
|
|
2235
|
+
return snapshot;
|
|
2236
|
+
}
|
|
2237
|
+
};
|
|
2238
|
+
|
|
1533
2239
|
// src/allowance/AccountAllowanceService.ts
|
|
1534
2240
|
function codexUnavailable(accountId, now) {
|
|
1535
2241
|
return {
|
|
@@ -1545,26 +2251,30 @@ function codexUnavailable(accountId, now) {
|
|
|
1545
2251
|
};
|
|
1546
2252
|
}
|
|
1547
2253
|
var AccountAllowanceService = class {
|
|
1548
|
-
constructor(credentials, store =
|
|
2254
|
+
constructor(credentials, store = getSharedAccountAllowanceStore5(), collector, codexCollector, kimiCollector, opencodegoCollector, now = Date.now) {
|
|
1549
2255
|
this.credentials = credentials;
|
|
1550
2256
|
this.store = store;
|
|
1551
2257
|
this.now = now;
|
|
1552
2258
|
this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
|
|
2259
|
+
this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
|
|
2260
|
+
this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
|
|
2261
|
+
this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
|
|
1553
2262
|
}
|
|
1554
2263
|
credentials;
|
|
1555
2264
|
store;
|
|
1556
2265
|
now;
|
|
1557
2266
|
claudeCollector;
|
|
2267
|
+
codexCollector;
|
|
2268
|
+
kimiCollector;
|
|
2269
|
+
opencodegoCollector;
|
|
1558
2270
|
/**
|
|
1559
|
-
* Read all/filtered snapshots. Claude's five-minute
|
|
1560
|
-
*
|
|
2271
|
+
* Read all/filtered snapshots. Claude's and Codex's five-minute caches are
|
|
2272
|
+
* refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
|
|
2273
|
+
* passive `x-codex-*` header tap still feeds mid-flight updates).
|
|
1561
2274
|
*/
|
|
1562
2275
|
async list(filter = {}) {
|
|
1563
2276
|
const config = await this.credentials.getFullConfig();
|
|
1564
|
-
this.store.pruneToKnownAccounts(
|
|
1565
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
1566
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
1567
|
-
]);
|
|
2277
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1568
2278
|
const wantsClaude = !filter.providerId || filter.providerId === "claude";
|
|
1569
2279
|
const claudeAccounts = (config.claudeAccounts ?? []).filter(
|
|
1570
2280
|
(account) => !filter.accountId || account.id === filter.accountId
|
|
@@ -1575,39 +2285,90 @@ var AccountAllowanceService = class {
|
|
|
1575
2285
|
(account) => !filter.accountId || account.id === filter.accountId
|
|
1576
2286
|
);
|
|
1577
2287
|
if (wantsCodex) {
|
|
2288
|
+
await this.codexCollector.collectMany(codexAccounts);
|
|
1578
2289
|
for (const account of codexAccounts) {
|
|
1579
2290
|
if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
|
|
1580
2291
|
}
|
|
1581
2292
|
}
|
|
2293
|
+
const wantsKimi = !filter.providerId || filter.providerId === "kimi";
|
|
2294
|
+
const kimiAccounts = (config.kimiAccounts ?? []).filter(
|
|
2295
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
2296
|
+
);
|
|
2297
|
+
if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
|
|
2298
|
+
const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
|
|
2299
|
+
const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
|
|
2300
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
2301
|
+
);
|
|
2302
|
+
if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
|
|
1582
2303
|
const known = /* @__PURE__ */ new Set();
|
|
1583
2304
|
if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
|
|
1584
2305
|
if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
|
|
2306
|
+
if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
|
|
2307
|
+
if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
|
|
1585
2308
|
return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
|
|
1586
2309
|
}
|
|
2310
|
+
knownAccounts(config) {
|
|
2311
|
+
return [
|
|
2312
|
+
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
2313
|
+
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
|
|
2314
|
+
...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
|
|
2315
|
+
...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id }))
|
|
2316
|
+
];
|
|
2317
|
+
}
|
|
1587
2318
|
/** Force-refresh Claude usage for one account or every stored Claude account. */
|
|
1588
2319
|
async refreshClaude(accountId) {
|
|
1589
2320
|
const config = await this.credentials.getFullConfig();
|
|
1590
|
-
this.store.pruneToKnownAccounts(
|
|
1591
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
1592
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
1593
|
-
]);
|
|
2321
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1594
2322
|
const accounts = (config.claudeAccounts ?? []).filter(
|
|
1595
2323
|
(account) => !accountId || account.id === accountId
|
|
1596
2324
|
);
|
|
1597
|
-
return this.claudeCollector.collectMany(accounts, { force: true });
|
|
2325
|
+
return this.claudeCollector.collectMany(accounts, { force: true });
|
|
2326
|
+
}
|
|
2327
|
+
/**
|
|
2328
|
+
* Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
|
|
2329
|
+
* every stored Codex account. Replaces the old probe-request workaround —
|
|
2330
|
+
* no quota is spent reading the usage endpoint.
|
|
2331
|
+
*/
|
|
2332
|
+
async refreshCodex(accountId) {
|
|
2333
|
+
const config = await this.credentials.getFullConfig();
|
|
2334
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
2335
|
+
const accounts = (config.codexAccounts ?? []).filter(
|
|
2336
|
+
(account) => !accountId || account.id === accountId
|
|
2337
|
+
);
|
|
2338
|
+
return this.codexCollector.collectMany(accounts, { force: true });
|
|
2339
|
+
}
|
|
2340
|
+
/** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
|
|
2341
|
+
async refreshOpenCodeGo(accountId) {
|
|
2342
|
+
const config = await this.credentials.getFullConfig();
|
|
2343
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
2344
|
+
const accounts = (config.opencodegoAccounts ?? []).filter(
|
|
2345
|
+
(account) => !accountId || account.id === accountId
|
|
2346
|
+
);
|
|
2347
|
+
return this.opencodegoCollector.collectMany(accounts, { force: true });
|
|
2348
|
+
}
|
|
2349
|
+
/** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
|
|
2350
|
+
async refreshKimi(accountId) {
|
|
2351
|
+
const config = await this.credentials.getFullConfig();
|
|
2352
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
2353
|
+
const accounts = (config.kimiAccounts ?? []).filter(
|
|
2354
|
+
(account) => !accountId || account.id === accountId
|
|
2355
|
+
);
|
|
2356
|
+
return this.kimiCollector.collectMany(accounts, { force: true });
|
|
1598
2357
|
}
|
|
1599
2358
|
/**
|
|
1600
|
-
* Keep Claude snapshots warm for allowance-aware routing.
|
|
1601
|
-
*
|
|
1602
|
-
*
|
|
2359
|
+
* Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
|
|
2360
|
+
* collectors preserve their cache + per-account in-flight coalescing; a tick
|
|
2361
|
+
* normally performs no network I/O. (Codex joined the warm path when it
|
|
2362
|
+
* gained an active `/wham/usage` collector — the passive `x-codex-*` header
|
|
2363
|
+
* tap alone could not keep the policy fed while idle.)
|
|
1603
2364
|
*/
|
|
1604
2365
|
async maintainClaudeCache(refreshAheadMs) {
|
|
1605
2366
|
const config = await this.credentials.getFullConfig();
|
|
1606
|
-
this.store.pruneToKnownAccounts(
|
|
1607
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
1608
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
1609
|
-
]);
|
|
2367
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1610
2368
|
await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
|
|
2369
|
+
await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
|
|
2370
|
+
await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
|
|
2371
|
+
await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
|
|
1611
2372
|
}
|
|
1612
2373
|
/** Remove a cache row as soon as an account is deleted by the admin path. */
|
|
1613
2374
|
removeAccountSnapshot(providerId, accountId) {
|
|
@@ -2037,7 +2798,7 @@ import {
|
|
|
2037
2798
|
} from "@omnicross/contracts/image-generation-types";
|
|
2038
2799
|
import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling2 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
2039
2800
|
import { getSharedAccountHealth } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
2040
|
-
import { fetchUpstream as
|
|
2801
|
+
import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
2041
2802
|
|
|
2042
2803
|
// src/image-generation/imagesConfigValidation.ts
|
|
2043
2804
|
import { validateImagesServerConfig } from "@omnicross/core/outbound-api";
|
|
@@ -3779,7 +4540,8 @@ var VALID_PROVIDER_IDS = [
|
|
|
3779
4540
|
"claude",
|
|
3780
4541
|
"codex",
|
|
3781
4542
|
"gemini",
|
|
3782
|
-
"opencodego"
|
|
4543
|
+
"opencodego",
|
|
4544
|
+
"kimi"
|
|
3783
4545
|
];
|
|
3784
4546
|
function asSubscriptionProviderId(id) {
|
|
3785
4547
|
return VALID_PROVIDER_IDS.includes(id) ? id : null;
|
|
@@ -3915,6 +4677,18 @@ function validateGemini(body) {
|
|
|
3915
4677
|
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
|
|
3916
4678
|
return out;
|
|
3917
4679
|
}
|
|
4680
|
+
function validateKimi(body) {
|
|
4681
|
+
const authMethod = str(body["authMethod"]);
|
|
4682
|
+
const status = str(body["status"]);
|
|
4683
|
+
if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
|
|
4684
|
+
if (!status || !TOKEN_STATUSES.has(status)) return null;
|
|
4685
|
+
const out = {
|
|
4686
|
+
authMethod,
|
|
4687
|
+
status
|
|
4688
|
+
};
|
|
4689
|
+
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
|
|
4690
|
+
return out;
|
|
4691
|
+
}
|
|
3918
4692
|
function validateOpenCodeGo(body) {
|
|
3919
4693
|
const authMethod = str(body["authMethod"]);
|
|
3920
4694
|
const status = str(body["status"]);
|
|
@@ -3950,6 +4724,8 @@ function validateTokenBody(providerId, body) {
|
|
|
3950
4724
|
return validateGemini(body);
|
|
3951
4725
|
case "opencodego":
|
|
3952
4726
|
return validateOpenCodeGo(body);
|
|
4727
|
+
case "kimi":
|
|
4728
|
+
return validateKimi(body);
|
|
3953
4729
|
default:
|
|
3954
4730
|
return null;
|
|
3955
4731
|
}
|
|
@@ -3979,12 +4755,12 @@ async function statusEntryFor(reader, providerId) {
|
|
|
3979
4755
|
|
|
3980
4756
|
// src/admin/accountsOAuth.ts
|
|
3981
4757
|
var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
|
|
3982
|
-
function
|
|
4758
|
+
function err3(status, message) {
|
|
3983
4759
|
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
3984
4760
|
}
|
|
3985
4761
|
function handleOAuthStart(providerId, deps) {
|
|
3986
4762
|
if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
|
|
3987
|
-
return
|
|
4763
|
+
return err3(400, `oauth not available for provider '${providerId}'`);
|
|
3988
4764
|
}
|
|
3989
4765
|
const flow = providerId === "claude" ? claudeOAuth : geminiOAuth;
|
|
3990
4766
|
const { authUrl, codeVerifier, state } = flow.generateAuthParams();
|
|
@@ -3993,23 +4769,23 @@ function handleOAuthStart(providerId, deps) {
|
|
|
3993
4769
|
}
|
|
3994
4770
|
async function handleOAuthComplete(providerId, body, deps) {
|
|
3995
4771
|
if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
|
|
3996
|
-
return
|
|
4772
|
+
return err3(400, `oauth not available for provider '${providerId}'`);
|
|
3997
4773
|
}
|
|
3998
4774
|
const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
|
|
3999
4775
|
const rawCode = typeof body["code"] === "string" ? body["code"] : "";
|
|
4000
|
-
if (!sessionId) return
|
|
4001
|
-
if (!rawCode) return
|
|
4776
|
+
if (!sessionId) return err3(400, "oauth complete requires { sessionId }");
|
|
4777
|
+
if (!rawCode) return err3(400, "oauth complete requires { code }");
|
|
4002
4778
|
const session = deps.oauthSessions.peek(sessionId);
|
|
4003
|
-
if (!session) return
|
|
4779
|
+
if (!session) return err3(410, "oauth session is unknown, expired, or already used");
|
|
4004
4780
|
if (session.providerId !== providerId) {
|
|
4005
|
-
return
|
|
4781
|
+
return err3(400, `oauth session does not match provider '${providerId}'`);
|
|
4006
4782
|
}
|
|
4007
4783
|
let code = rawCode.trim();
|
|
4008
4784
|
if (providerId === "claude") {
|
|
4009
4785
|
const [splitCode, pastedState] = code.split("#");
|
|
4010
|
-
if (!splitCode) return
|
|
4786
|
+
if (!splitCode) return err3(400, "no authorization code was provided");
|
|
4011
4787
|
if (pastedState && pastedState !== session.state) {
|
|
4012
|
-
return
|
|
4788
|
+
return err3(400, "oauth state did not match (possible CSRF) \u2014 aborting");
|
|
4013
4789
|
}
|
|
4014
4790
|
code = splitCode;
|
|
4015
4791
|
}
|
|
@@ -4019,7 +4795,7 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
4019
4795
|
block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
|
|
4020
4796
|
} catch (exchangeError) {
|
|
4021
4797
|
const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
|
|
4022
|
-
return
|
|
4798
|
+
return err3(502, `oauth token exchange failed for '${providerId}': ${reason}`);
|
|
4023
4799
|
}
|
|
4024
4800
|
deps.oauthSessions.consume(sessionId);
|
|
4025
4801
|
const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
|
|
@@ -4357,8 +5133,8 @@ function errBody(message) {
|
|
|
4357
5133
|
return { error: { type: "admin_api_error", message } };
|
|
4358
5134
|
}
|
|
4359
5135
|
var defaultCommandRunner = (command) => new Promise((resolve11) => {
|
|
4360
|
-
exec(command, { timeout: 18e4 }, (
|
|
4361
|
-
if (
|
|
5136
|
+
exec(command, { timeout: 18e4 }, (err6, _stdout, stderr) => {
|
|
5137
|
+
if (err6) resolve11({ ok: false, error: stderr.trim() || err6.message });
|
|
4362
5138
|
else resolve11({ ok: true });
|
|
4363
5139
|
});
|
|
4364
5140
|
});
|
|
@@ -4404,8 +5180,8 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
4404
5180
|
providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
|
|
4405
5181
|
model: typeof body["model"] === "string" ? body["model"] : void 0
|
|
4406
5182
|
});
|
|
4407
|
-
} catch (
|
|
4408
|
-
return { status: 400, body: errBody(
|
|
5183
|
+
} catch (err6) {
|
|
5184
|
+
return { status: 400, body: errBody(err6 instanceof Error ? err6.message : "no launch target") };
|
|
4409
5185
|
}
|
|
4410
5186
|
const id = randomUUID2();
|
|
4411
5187
|
let leaseId2;
|
|
@@ -4433,9 +5209,9 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
4433
5209
|
} else {
|
|
4434
5210
|
launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
|
|
4435
5211
|
}
|
|
4436
|
-
} catch (
|
|
4437
|
-
const status =
|
|
4438
|
-
return { status, body: errBody(
|
|
5212
|
+
} catch (err6) {
|
|
5213
|
+
const status = err6 instanceof RouteLeaseError2 ? err6.status : 400;
|
|
5214
|
+
return { status, body: errBody(err6 instanceof Error ? err6.message : "failed to build launch env") };
|
|
4439
5215
|
}
|
|
4440
5216
|
const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
|
|
4441
5217
|
const opener = ctx.opener ?? defaultTerminalOpener;
|
|
@@ -4463,9 +5239,9 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
4463
5239
|
onFailure: onSessionEnd
|
|
4464
5240
|
});
|
|
4465
5241
|
if (cleanup) openerCleanup = cleanup;
|
|
4466
|
-
} catch (
|
|
5242
|
+
} catch (err6) {
|
|
4467
5243
|
onSessionEnd();
|
|
4468
|
-
return { status: 500, body: errBody(
|
|
5244
|
+
return { status: 500, body: errBody(err6 instanceof Error ? err6.message : "failed to open terminal") };
|
|
4469
5245
|
}
|
|
4470
5246
|
if (ended) {
|
|
4471
5247
|
openerCleanup?.();
|
|
@@ -4599,7 +5375,8 @@ async function handleDashboard(deps) {
|
|
|
4599
5375
|
// src/admin/searchAdminApi.ts
|
|
4600
5376
|
import { DEFAULT_SEARCH_SERVER_CONFIG, loadServerConfig } from "@omnicross/core/outbound-api";
|
|
4601
5377
|
import { apiSearchContributions as apiSearchContributions2 } from "@omnicross/core/search/api";
|
|
4602
|
-
import { builtinHttpSearchContributions as builtinHttpSearchContributions3, createSearchHttpTransport } from "@omnicross/core/search/http";
|
|
5378
|
+
import { builtinHttpSearchContributions as builtinHttpSearchContributions3, createSearchHttpTransport as createSearchHttpTransport2 } from "@omnicross/core/search/http";
|
|
5379
|
+
import { createSearchRuntime as createSearchRuntime2 } from "@omnicross/core/search";
|
|
4603
5380
|
|
|
4604
5381
|
// src/search/searchDoctorProjection.ts
|
|
4605
5382
|
import { toSearchErrorShape } from "@omnicross/contracts/search-types";
|
|
@@ -4665,7 +5442,7 @@ function buildSearchDoctorSnapshot(contributions = builtinHttpSearchContribution
|
|
|
4665
5442
|
}
|
|
4666
5443
|
return rows;
|
|
4667
5444
|
}
|
|
4668
|
-
var SEARCH_DOCTOR_QUERY = "
|
|
5445
|
+
var SEARCH_DOCTOR_QUERY = "MDN HTTP headers documentation";
|
|
4669
5446
|
function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
|
|
4670
5447
|
if (outcome.kind === "results") {
|
|
4671
5448
|
if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
|
|
@@ -4724,9 +5501,13 @@ async function runSearchLiveChecks(contributions, now = () => (/* @__PURE__ */ n
|
|
|
4724
5501
|
}
|
|
4725
5502
|
|
|
4726
5503
|
// src/search/SearchAssembly.ts
|
|
5504
|
+
import { resolveUpstreamDispatcher } from "@omnicross/core/pipeline/upstreamFetch";
|
|
4727
5505
|
import { createSearchRuntime } from "@omnicross/core/search";
|
|
4728
5506
|
import { apiSearchContributions } from "@omnicross/core/search/api";
|
|
4729
|
-
import {
|
|
5507
|
+
import {
|
|
5508
|
+
builtinHttpSearchContributions as builtinHttpSearchContributions2,
|
|
5509
|
+
createSearchHttpTransport
|
|
5510
|
+
} from "@omnicross/core/search/http";
|
|
4730
5511
|
function searchEgressPolicyFrom(config) {
|
|
4731
5512
|
const hosts = config.egress.allowedPrivateHosts;
|
|
4732
5513
|
return hosts.length > 0 ? { allowedPrivateHosts: [...hosts] } : {};
|
|
@@ -4740,11 +5521,24 @@ function searchPolicyFrom(config) {
|
|
|
4740
5521
|
...maxAttempts !== void 0 ? { maxAttempts } : {}
|
|
4741
5522
|
};
|
|
4742
5523
|
}
|
|
5524
|
+
function resolveSearchUpstreamDispatcher(url) {
|
|
5525
|
+
return resolveUpstreamDispatcher({ url });
|
|
5526
|
+
}
|
|
5527
|
+
var searchUpstreamProxyConfig = createUpstreamProxyResolver();
|
|
5528
|
+
function resolveSearchUpstreamProxyConfig(url) {
|
|
5529
|
+
return searchUpstreamProxyConfig({ url });
|
|
5530
|
+
}
|
|
4743
5531
|
function searchContributionsFrom(config) {
|
|
4744
5532
|
return [
|
|
4745
|
-
...builtinHttpSearchContributions2(
|
|
5533
|
+
...builtinHttpSearchContributions2(
|
|
5534
|
+
createSearchHttpTransport({
|
|
5535
|
+
resolveProxyDispatcher: resolveSearchUpstreamDispatcher,
|
|
5536
|
+
resolveProxyConfig: resolveSearchUpstreamProxyConfig
|
|
5537
|
+
})
|
|
5538
|
+
),
|
|
4746
5539
|
...apiSearchContributions(config.providers, {
|
|
4747
|
-
egressPolicy: searchEgressPolicyFrom(config)
|
|
5540
|
+
egressPolicy: searchEgressPolicyFrom(config),
|
|
5541
|
+
resolveProxyDispatcher: resolveSearchUpstreamDispatcher
|
|
4748
5542
|
})
|
|
4749
5543
|
];
|
|
4750
5544
|
}
|
|
@@ -4888,6 +5682,18 @@ async function handleSearchDiagnostics(res, deps) {
|
|
|
4888
5682
|
};
|
|
4889
5683
|
return writeJson(res, 200, { diagnostics: snapshot });
|
|
4890
5684
|
}
|
|
5685
|
+
function persistedSearchContributions(search, fetchImpl) {
|
|
5686
|
+
if (fetchImpl) {
|
|
5687
|
+
const egressPolicy = searchEgressPolicyFrom(search);
|
|
5688
|
+
return [
|
|
5689
|
+
...builtinHttpSearchContributions3(
|
|
5690
|
+
createSearchHttpTransport2({ fetch: fetchImpl, egressPolicy })
|
|
5691
|
+
),
|
|
5692
|
+
...apiSearchContributions2(search.providers, { egressPolicy, fetchImpl })
|
|
5693
|
+
];
|
|
5694
|
+
}
|
|
5695
|
+
return searchContributionsFrom(search);
|
|
5696
|
+
}
|
|
4891
5697
|
async function handleSearchTest(req, res, deps) {
|
|
4892
5698
|
const status = deps.searchStatus;
|
|
4893
5699
|
const body = await readBodyOrReject(req, res);
|
|
@@ -4905,16 +5711,8 @@ async function handleSearchTest(req, res, deps) {
|
|
|
4905
5711
|
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4906
5712
|
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4907
5713
|
}
|
|
4908
|
-
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4909
5714
|
const fetchImpl = status.testFetch;
|
|
4910
|
-
const
|
|
4911
|
-
const contributions = [
|
|
4912
|
-
...builtinHttpSearchContributions3(transport),
|
|
4913
|
-
...apiSearchContributions2(search.providers, {
|
|
4914
|
-
egressPolicy,
|
|
4915
|
-
...fetchImpl ? { fetchImpl } : {}
|
|
4916
|
-
})
|
|
4917
|
-
];
|
|
5715
|
+
const contributions = persistedSearchContributions(search, fetchImpl);
|
|
4918
5716
|
const contribution = contributions.find((c) => c.id === providerId);
|
|
4919
5717
|
if (!contribution) {
|
|
4920
5718
|
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
@@ -4962,41 +5760,42 @@ async function handleSearchQuery(req, res, deps) {
|
|
|
4962
5760
|
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4963
5761
|
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4964
5762
|
}
|
|
4965
|
-
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4966
5763
|
const fetchImpl = status.testFetch;
|
|
4967
|
-
const
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
}
|
|
5764
|
+
const runtime = createSearchRuntime2({
|
|
5765
|
+
contributions: persistedSearchContributions(search, fetchImpl),
|
|
5766
|
+
policy: {
|
|
5767
|
+
...searchPolicyFrom(search),
|
|
5768
|
+
// The panel always walks: it answers "does a search WORK for this
|
|
5769
|
+
// operator", not "does this one provider behave" — that is `/test`'s
|
|
5770
|
+
// job. The persisted policy's allowlist still bounds the walk.
|
|
5771
|
+
fallbackEnabled: true,
|
|
5772
|
+
preferred: providerId
|
|
5773
|
+
}
|
|
5774
|
+
});
|
|
4979
5775
|
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4980
5776
|
try {
|
|
4981
|
-
const
|
|
5777
|
+
const orchestrated = await runtime.search({ query: query2, options: { maxResults: 5 } });
|
|
5778
|
+
const results = orchestrated.results;
|
|
4982
5779
|
const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
|
|
4983
5780
|
title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
|
|
4984
5781
|
url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
|
|
4985
5782
|
content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
|
|
4986
5783
|
}));
|
|
4987
|
-
const diagnostic = sanitized.length === 0 ? { providerId:
|
|
4988
|
-
|
|
5784
|
+
const diagnostic = sanitized.length === 0 ? { providerId: orchestrated.providerId, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
|
|
5785
|
+
orchestrated.providerId,
|
|
4989
5786
|
{ kind: "results", count: sanitized.length },
|
|
4990
5787
|
checkedAt
|
|
4991
5788
|
);
|
|
4992
5789
|
const response = {
|
|
4993
5790
|
diagnostic,
|
|
5791
|
+
providerUsed: orchestrated.providerId,
|
|
5792
|
+
fallbackCount: orchestrated.fallbackCount,
|
|
4994
5793
|
resultCount: sanitized.length,
|
|
4995
5794
|
results: sanitized
|
|
4996
5795
|
};
|
|
4997
5796
|
return writeJson(res, 200, { result: response });
|
|
4998
5797
|
} catch (error) {
|
|
4999
|
-
const diagnostic = classifyLiveSearchOutcome(
|
|
5798
|
+
const diagnostic = classifyLiveSearchOutcome(providerId, { kind: "failure", error }, checkedAt);
|
|
5000
5799
|
const response = { diagnostic };
|
|
5001
5800
|
return writeJson(res, 200, { result: response });
|
|
5002
5801
|
}
|
|
@@ -5005,7 +5804,7 @@ async function handleSearchQuery(req, res, deps) {
|
|
|
5005
5804
|
// src/admin/searchAdminView.ts
|
|
5006
5805
|
var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
|
|
5007
5806
|
var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
|
|
5008
|
-
function
|
|
5807
|
+
function isRecord2(value) {
|
|
5009
5808
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5010
5809
|
}
|
|
5011
5810
|
function redactSearchServerConfig(search) {
|
|
@@ -5055,13 +5854,13 @@ function resolveSecretField(entry, field, stored) {
|
|
|
5055
5854
|
else delete entry[field];
|
|
5056
5855
|
}
|
|
5057
5856
|
function preserveSearchSecrets(incoming, current) {
|
|
5058
|
-
if (!
|
|
5857
|
+
if (!isRecord2(incoming)) return incoming;
|
|
5059
5858
|
const section = { ...incoming };
|
|
5060
5859
|
const providersValue = section["providers"];
|
|
5061
|
-
if (!
|
|
5860
|
+
if (!isRecord2(providersValue)) return section;
|
|
5062
5861
|
const providers = {};
|
|
5063
5862
|
for (const [id, entryValue] of Object.entries(providersValue)) {
|
|
5064
|
-
if (!
|
|
5863
|
+
if (!isRecord2(entryValue)) {
|
|
5065
5864
|
providers[id] = entryValue;
|
|
5066
5865
|
continue;
|
|
5067
5866
|
}
|
|
@@ -5139,7 +5938,7 @@ function parseKeyPolicyBody(body) {
|
|
|
5139
5938
|
var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
|
|
5140
5939
|
var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
|
|
5141
5940
|
var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
|
|
5142
|
-
function
|
|
5941
|
+
function isRecord3(value) {
|
|
5143
5942
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5144
5943
|
}
|
|
5145
5944
|
function nonBlank(value) {
|
|
@@ -5159,7 +5958,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
5159
5958
|
const ids = /* @__PURE__ */ new Set();
|
|
5160
5959
|
raw.forEach((entry, index) => {
|
|
5161
5960
|
const path2 = `bindings[${index}]`;
|
|
5162
|
-
if (!
|
|
5961
|
+
if (!isRecord3(entry)) {
|
|
5163
5962
|
errors.push(`${path2} must be an object`);
|
|
5164
5963
|
return;
|
|
5165
5964
|
}
|
|
@@ -5188,12 +5987,12 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
5188
5987
|
} else if (entry.modelMappings.length > 100) {
|
|
5189
5988
|
errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
|
|
5190
5989
|
} else if (entry.modelMappings.some(
|
|
5191
|
-
(mapping) => !
|
|
5990
|
+
(mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
|
|
5192
5991
|
)) {
|
|
5193
5992
|
errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
|
|
5194
5993
|
}
|
|
5195
5994
|
}
|
|
5196
|
-
if (!
|
|
5995
|
+
if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
|
|
5197
5996
|
errors.push(`${path2}.target is invalid`);
|
|
5198
5997
|
} else {
|
|
5199
5998
|
if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
|
|
@@ -5208,7 +6007,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
5208
6007
|
}
|
|
5209
6008
|
}
|
|
5210
6009
|
if (entry.modelMap !== void 0) {
|
|
5211
|
-
if (!
|
|
6010
|
+
if (!isRecord3(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
|
|
5212
6011
|
errors.push(`${path2}.modelMap must contain string values`);
|
|
5213
6012
|
}
|
|
5214
6013
|
}
|
|
@@ -5502,7 +6301,8 @@ var PROVIDER_KEYS = {
|
|
|
5502
6301
|
block: "opencodego",
|
|
5503
6302
|
accounts: "opencodegoAccounts",
|
|
5504
6303
|
active: "activeOpencodegoAccountId"
|
|
5505
|
-
}
|
|
6304
|
+
},
|
|
6305
|
+
kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
|
|
5506
6306
|
};
|
|
5507
6307
|
function clone(value) {
|
|
5508
6308
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -6024,7 +6824,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
|
|
|
6024
6824
|
}
|
|
6025
6825
|
|
|
6026
6826
|
// src/admin/adminMigration.ts
|
|
6027
|
-
function
|
|
6827
|
+
function err4(status, message) {
|
|
6028
6828
|
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
6029
6829
|
}
|
|
6030
6830
|
async function handleExport(body, deps) {
|
|
@@ -6034,30 +6834,30 @@ async function handleExport(body, deps) {
|
|
|
6034
6834
|
return { status: 200, body: { pack, version: BUNDLE_VERSION } };
|
|
6035
6835
|
} catch (error) {
|
|
6036
6836
|
if (error instanceof WeakPassphraseError) {
|
|
6037
|
-
return
|
|
6837
|
+
return err4(400, error.message);
|
|
6038
6838
|
}
|
|
6039
|
-
return
|
|
6839
|
+
return err4(500, "failed to build the migration pack");
|
|
6040
6840
|
}
|
|
6041
6841
|
}
|
|
6042
6842
|
async function handleImport(body, deps) {
|
|
6043
6843
|
const blob = typeof body["blob"] === "string" ? body["blob"] : "";
|
|
6044
6844
|
const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
|
|
6045
6845
|
const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
|
|
6046
|
-
if (!blob) return
|
|
6846
|
+
if (!blob) return err4(400, "import requires { blob }");
|
|
6047
6847
|
try {
|
|
6048
6848
|
const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
|
|
6049
6849
|
return { status: 200, body: counts };
|
|
6050
6850
|
} catch (error) {
|
|
6051
6851
|
if (error instanceof WeakPassphraseError) {
|
|
6052
|
-
return
|
|
6852
|
+
return err4(400, error.message);
|
|
6053
6853
|
}
|
|
6054
|
-
return
|
|
6854
|
+
return err4(400, error instanceof Error ? error.message : "import failed");
|
|
6055
6855
|
}
|
|
6056
6856
|
}
|
|
6057
6857
|
|
|
6058
6858
|
// src/admin/usagePricing.ts
|
|
6059
6859
|
import { getSharedUsageThroughputTracker } from "@omnicross/core/usage";
|
|
6060
|
-
var
|
|
6860
|
+
var err5 = (status, message) => ({
|
|
6061
6861
|
status,
|
|
6062
6862
|
body: { error: { type: "admin_api_error", message } }
|
|
6063
6863
|
});
|
|
@@ -6070,7 +6870,7 @@ function parseRange(query2) {
|
|
|
6070
6870
|
const startTs = parseFiniteInt(query2.get("startTs"));
|
|
6071
6871
|
const endTs = parseFiniteInt(query2.get("endTs"));
|
|
6072
6872
|
if (startTs === null || endTs === null) {
|
|
6073
|
-
return
|
|
6873
|
+
return err5(400, "startTs and endTs are required finite-integer unix-millis query params");
|
|
6074
6874
|
}
|
|
6075
6875
|
return { startTs, endTs };
|
|
6076
6876
|
}
|
|
@@ -6095,14 +6895,14 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
6095
6895
|
case "timeseries": {
|
|
6096
6896
|
const bucket = query2.get("bucket");
|
|
6097
6897
|
if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
|
|
6098
|
-
return
|
|
6898
|
+
return err5(400, "bucket must be one of 'hour', 'day', 'month'");
|
|
6099
6899
|
}
|
|
6100
6900
|
const now = Date.now();
|
|
6101
6901
|
const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
|
|
6102
6902
|
if (clamped.startTs < clamped.endTs) {
|
|
6103
6903
|
const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
|
|
6104
6904
|
if (projected > MAX_TIMESERIES_BUCKETS) {
|
|
6105
|
-
return
|
|
6905
|
+
return err5(
|
|
6106
6906
|
400,
|
|
6107
6907
|
`requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
|
|
6108
6908
|
);
|
|
@@ -6125,7 +6925,7 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
6125
6925
|
};
|
|
6126
6926
|
}
|
|
6127
6927
|
default:
|
|
6128
|
-
return
|
|
6928
|
+
return err5(404, `unknown usage view '${view ?? ""}'`);
|
|
6129
6929
|
}
|
|
6130
6930
|
}
|
|
6131
6931
|
function poolKeyLabels(cfg) {
|
|
@@ -6174,7 +6974,7 @@ async function handlePricingList(deps) {
|
|
|
6174
6974
|
async function handlePricingUpsert(body, deps) {
|
|
6175
6975
|
const input = parsePricingEntryInput(body);
|
|
6176
6976
|
if (!input) {
|
|
6177
|
-
return
|
|
6977
|
+
return err5(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
|
|
6178
6978
|
}
|
|
6179
6979
|
const entry = await deps.pricingEngine.upsertManual(input);
|
|
6180
6980
|
return { status: 200, body: { entry } };
|
|
@@ -6183,7 +6983,7 @@ async function handlePricingDelete(query2, deps) {
|
|
|
6183
6983
|
const providerId = query2.get("providerId")?.trim() ?? "";
|
|
6184
6984
|
const modelId = query2.get("modelId")?.trim() ?? "";
|
|
6185
6985
|
if (!providerId || !modelId) {
|
|
6186
|
-
return
|
|
6986
|
+
return err5(400, "delete requires providerId and modelId query params");
|
|
6187
6987
|
}
|
|
6188
6988
|
const deleted = await deps.pricingStore.delete(providerId, modelId);
|
|
6189
6989
|
if (deleted) await deps.pricingEngine.invalidateCache();
|
|
@@ -6203,13 +7003,13 @@ async function handlePricingFetchLatest(deps) {
|
|
|
6203
7003
|
}
|
|
6204
7004
|
};
|
|
6205
7005
|
} catch (e) {
|
|
6206
|
-
return
|
|
7006
|
+
return err5(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
6207
7007
|
}
|
|
6208
7008
|
}
|
|
6209
7009
|
async function handlePricingResolveConflicts(body, deps) {
|
|
6210
7010
|
const raw = body["resolutions"];
|
|
6211
7011
|
if (!Array.isArray(raw)) {
|
|
6212
|
-
return
|
|
7012
|
+
return err5(400, "resolve-conflicts requires { resolutions: [...] }");
|
|
6213
7013
|
}
|
|
6214
7014
|
const currentRows = await deps.pricingStore.getAll();
|
|
6215
7015
|
const userEditedKeys = new Set(
|
|
@@ -6219,21 +7019,21 @@ async function handlePricingResolveConflicts(body, deps) {
|
|
|
6219
7019
|
const pendingIncoming = /* @__PURE__ */ new Map();
|
|
6220
7020
|
let staleCount = 0;
|
|
6221
7021
|
for (const item of raw) {
|
|
6222
|
-
if (!item || typeof item !== "object") return
|
|
7022
|
+
if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
|
|
6223
7023
|
const r = item;
|
|
6224
7024
|
const action = r["action"];
|
|
6225
7025
|
if (action !== "overwrite" && action !== "skip") {
|
|
6226
|
-
return
|
|
7026
|
+
return err5(400, "resolution action must be 'overwrite' or 'skip'");
|
|
6227
7027
|
}
|
|
6228
7028
|
const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
|
|
6229
7029
|
const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
|
|
6230
7030
|
if (!providerId || !modelId) {
|
|
6231
|
-
return
|
|
7031
|
+
return err5(400, "each resolution requires top-level providerId and modelId");
|
|
6232
7032
|
}
|
|
6233
7033
|
const incoming = parsePricingEntryInput(r["incoming"]);
|
|
6234
|
-
if (!incoming) return
|
|
7034
|
+
if (!incoming) return err5(400, "each resolution must echo a valid incoming pricing entry");
|
|
6235
7035
|
if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
|
|
6236
|
-
return
|
|
7036
|
+
return err5(400, "resolution providerId/modelId must match the echoed incoming entry");
|
|
6237
7037
|
}
|
|
6238
7038
|
const key = `${providerId}::${modelId}`;
|
|
6239
7039
|
if (action === "overwrite" && !userEditedKeys.has(key)) {
|
|
@@ -6278,7 +7078,7 @@ function query(req) {
|
|
|
6278
7078
|
}
|
|
6279
7079
|
function allowanceProvider(value) {
|
|
6280
7080
|
if (!value) return void 0;
|
|
6281
|
-
return value === "claude" || value === "codex" ? value : null;
|
|
7081
|
+
return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
|
|
6282
7082
|
}
|
|
6283
7083
|
async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
6284
7084
|
if (!service) return writeError2(res, 501, "account allowance service is not available");
|
|
@@ -6292,7 +7092,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
6292
7092
|
const params = query(req);
|
|
6293
7093
|
const pathProvider = rest.length >= 2 ? rest[0] : null;
|
|
6294
7094
|
const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
|
|
6295
|
-
if (providerId === null)
|
|
7095
|
+
if (providerId === null) {
|
|
7096
|
+
return writeError2(res, 400, "providerId must be claude, codex, kimi, or opencodego");
|
|
7097
|
+
}
|
|
6296
7098
|
const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
|
|
6297
7099
|
const allowances = await service.list({ providerId, accountId });
|
|
6298
7100
|
return writeJson3(res, 200, { allowances });
|
|
@@ -6302,10 +7104,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
6302
7104
|
const requestedProvider = allowanceProvider(
|
|
6303
7105
|
typeof body["providerId"] === "string" ? body["providerId"] : "claude"
|
|
6304
7106
|
);
|
|
6305
|
-
if (requestedProvider !== "claude") {
|
|
6306
|
-
return writeError2(res, 400, "only Claude allowances support explicit refresh");
|
|
6307
|
-
}
|
|
6308
7107
|
const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
|
|
7108
|
+
if (requestedProvider === "codex") {
|
|
7109
|
+
if (!service.refreshCodex) {
|
|
7110
|
+
return writeError2(res, 501, "codex allowance refresh is not available");
|
|
7111
|
+
}
|
|
7112
|
+
const allowances2 = await service.refreshCodex(accountId);
|
|
7113
|
+
if (accountId && allowances2.length === 0) {
|
|
7114
|
+
return writeError2(res, 404, `Codex account '${accountId}' not found`);
|
|
7115
|
+
}
|
|
7116
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
7117
|
+
}
|
|
7118
|
+
if (requestedProvider === "kimi") {
|
|
7119
|
+
if (!service.refreshKimi) {
|
|
7120
|
+
return writeError2(res, 501, "kimi allowance refresh is not available");
|
|
7121
|
+
}
|
|
7122
|
+
const allowances2 = await service.refreshKimi(accountId);
|
|
7123
|
+
if (accountId && allowances2.length === 0) {
|
|
7124
|
+
return writeError2(res, 404, `Kimi account '${accountId}' not found`);
|
|
7125
|
+
}
|
|
7126
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
7127
|
+
}
|
|
7128
|
+
if (requestedProvider === "opencodego") {
|
|
7129
|
+
if (!service.refreshOpenCodeGo) {
|
|
7130
|
+
return writeError2(res, 501, "opencodego allowance refresh is not available");
|
|
7131
|
+
}
|
|
7132
|
+
const allowances2 = await service.refreshOpenCodeGo(accountId);
|
|
7133
|
+
if (accountId && allowances2.length === 0) {
|
|
7134
|
+
return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
|
|
7135
|
+
}
|
|
7136
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
7137
|
+
}
|
|
6309
7138
|
const allowances = await service.refreshClaude(accountId);
|
|
6310
7139
|
if (accountId && allowances.length === 0) {
|
|
6311
7140
|
return writeError2(res, 404, `Claude account '${accountId}' not found`);
|
|
@@ -6476,8 +7305,8 @@ async function handleAdminApi(req, res, path2, deps) {
|
|
|
6476
7305
|
default:
|
|
6477
7306
|
return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
|
|
6478
7307
|
}
|
|
6479
|
-
} catch (
|
|
6480
|
-
writeJsonError(res, 500,
|
|
7308
|
+
} catch (err6) {
|
|
7309
|
+
writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
|
|
6481
7310
|
}
|
|
6482
7311
|
}
|
|
6483
7312
|
function requestQuery(req) {
|
|
@@ -6547,6 +7376,9 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
6547
7376
|
if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
|
|
6548
7377
|
return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
6549
7378
|
}
|
|
7379
|
+
if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
|
|
7380
|
+
return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
|
|
7381
|
+
}
|
|
6550
7382
|
if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
|
|
6551
7383
|
return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
6552
7384
|
}
|
|
@@ -6645,7 +7477,7 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
6645
7477
|
try {
|
|
6646
7478
|
const headers = { Accept: "application/json" };
|
|
6647
7479
|
if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
|
|
6648
|
-
const response = await
|
|
7480
|
+
const response = await fetchUpstream5(url, { method: "GET", headers }, { providerId: "byo" });
|
|
6649
7481
|
if (!response.ok) {
|
|
6650
7482
|
const text = await response.text().catch(() => "");
|
|
6651
7483
|
let message = text.slice(0, 300);
|
|
@@ -6662,8 +7494,8 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
6662
7494
|
const data = await response.json();
|
|
6663
7495
|
const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
|
|
6664
7496
|
return writeJson4(res, 200, { models });
|
|
6665
|
-
} catch (
|
|
6666
|
-
const message =
|
|
7497
|
+
} catch (err6) {
|
|
7498
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
6667
7499
|
return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
|
|
6668
7500
|
}
|
|
6669
7501
|
}
|
|
@@ -6704,7 +7536,7 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
6704
7536
|
}
|
|
6705
7537
|
const startedAt = Date.now();
|
|
6706
7538
|
try {
|
|
6707
|
-
const response = await
|
|
7539
|
+
const response = await fetchUpstream5(
|
|
6708
7540
|
url,
|
|
6709
7541
|
{ method: "POST", headers, body: JSON.stringify(payload) },
|
|
6710
7542
|
{ providerId: "byo" }
|
|
@@ -6726,8 +7558,8 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
6726
7558
|
latencyMs,
|
|
6727
7559
|
sample: extractSampleText(text, row.apiFormat)
|
|
6728
7560
|
});
|
|
6729
|
-
} catch (
|
|
6730
|
-
const message =
|
|
7561
|
+
} catch (err6) {
|
|
7562
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
6731
7563
|
return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
|
|
6732
7564
|
}
|
|
6733
7565
|
}
|
|
@@ -6769,7 +7601,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
|
|
|
6769
7601
|
const row = cfg.providers.find((p) => p.id === id);
|
|
6770
7602
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6771
7603
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6772
|
-
|
|
7604
|
+
const views = toPoolKeyView(row, cooldown, deps);
|
|
7605
|
+
if (deps.providerKeyQuota) {
|
|
7606
|
+
const quotas = await Promise.allSettled(
|
|
7607
|
+
views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
|
|
7608
|
+
);
|
|
7609
|
+
views.forEach((view, index) => {
|
|
7610
|
+
const settled = quotas[index];
|
|
7611
|
+
if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
|
|
7612
|
+
});
|
|
7613
|
+
}
|
|
7614
|
+
return writeJson4(res, 200, { keys: views });
|
|
7615
|
+
}
|
|
7616
|
+
async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
|
|
7617
|
+
if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
|
|
7618
|
+
if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
|
|
7619
|
+
const row = cfg.providers.find((p) => p.id === id);
|
|
7620
|
+
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
7621
|
+
try {
|
|
7622
|
+
const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
|
|
7623
|
+
if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
|
|
7624
|
+
return writeJson4(res, 200, { quota });
|
|
7625
|
+
} catch {
|
|
7626
|
+
return writeJsonError(res, 502, "quota refresh failed");
|
|
7627
|
+
}
|
|
6773
7628
|
}
|
|
6774
7629
|
function parsePoolKeyInput(body, existing) {
|
|
6775
7630
|
const out = {};
|
|
@@ -7514,12 +8369,12 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7514
8369
|
}
|
|
7515
8370
|
return writeJson4(res, 200, { ok: true, affected: result.affected });
|
|
7516
8371
|
}
|
|
7517
|
-
if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
|
|
7518
|
-
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
8372
|
+
if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[3] === "status") {
|
|
8373
|
+
const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : handleKimiOAuthStatus(rest[2], deps);
|
|
7519
8374
|
return writeJson4(res, result.status, result.body);
|
|
7520
8375
|
}
|
|
7521
|
-
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
7522
|
-
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
8376
|
+
if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[2]) {
|
|
8377
|
+
const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : handleKimiOAuthCancel(rest[2], deps);
|
|
7523
8378
|
return writeJson4(res, result.status, result.body);
|
|
7524
8379
|
}
|
|
7525
8380
|
if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
|
|
@@ -7572,7 +8427,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7572
8427
|
return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
7573
8428
|
}
|
|
7574
8429
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
|
|
7575
|
-
|
|
8430
|
+
if (providerId === "codex") {
|
|
8431
|
+
const result2 = handleCodexOAuthStart(deps);
|
|
8432
|
+
return writeJson4(res, result2.status, result2.body);
|
|
8433
|
+
}
|
|
8434
|
+
if (providerId === "kimi") {
|
|
8435
|
+
const result2 = await handleKimiOAuthStart(deps);
|
|
8436
|
+
return writeJson4(res, result2.status, result2.body);
|
|
8437
|
+
}
|
|
8438
|
+
const result = handleOAuthStart(providerId, deps);
|
|
7576
8439
|
return writeJson4(res, result.status, result.body);
|
|
7577
8440
|
}
|
|
7578
8441
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
|
|
@@ -8066,12 +8929,12 @@ async function handlePlayground(req, res, method, deps) {
|
|
|
8066
8929
|
const payload = body["body"];
|
|
8067
8930
|
const status = deps.outboundApiServer.getStatus();
|
|
8068
8931
|
if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
|
|
8069
|
-
const path2 = resolvePlaygroundPath(endpoint,
|
|
8932
|
+
const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
|
|
8070
8933
|
if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
|
|
8071
8934
|
const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
8072
8935
|
await proxyToOutbound(res, status.port, path2, key, upstreamBody);
|
|
8073
8936
|
}
|
|
8074
|
-
function
|
|
8937
|
+
function isRecord4(v) {
|
|
8075
8938
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
8076
8939
|
}
|
|
8077
8940
|
function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
@@ -8100,8 +8963,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
|
8100
8963
|
});
|
|
8101
8964
|
}
|
|
8102
8965
|
);
|
|
8103
|
-
upstream.on("error", (
|
|
8104
|
-
if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${
|
|
8966
|
+
upstream.on("error", (err6) => {
|
|
8967
|
+
if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err6.message}`);
|
|
8105
8968
|
else res.end();
|
|
8106
8969
|
resolve11();
|
|
8107
8970
|
});
|
|
@@ -8206,7 +9069,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
8206
9069
|
}
|
|
8207
9070
|
|
|
8208
9071
|
// src/admin/version.ts
|
|
8209
|
-
var DAEMON_VERSION = true ? "0.
|
|
9072
|
+
var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
|
|
8210
9073
|
|
|
8211
9074
|
// src/admin/AdminServer.ts
|
|
8212
9075
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -8249,13 +9112,13 @@ var AdminServer = class {
|
|
|
8249
9112
|
const server = http2.createServer((req, res) => {
|
|
8250
9113
|
this.onRequest(req, res);
|
|
8251
9114
|
});
|
|
8252
|
-
const onError = (
|
|
8253
|
-
if (
|
|
9115
|
+
const onError = (err6) => {
|
|
9116
|
+
if (err6.code === "EADDRINUSE" && port !== 0) {
|
|
8254
9117
|
server.removeListener("error", onError);
|
|
8255
9118
|
this.listen(bindAddr, 0).then(resolve11, reject);
|
|
8256
9119
|
return;
|
|
8257
9120
|
}
|
|
8258
|
-
reject(
|
|
9121
|
+
reject(err6);
|
|
8259
9122
|
};
|
|
8260
9123
|
server.on("error", onError);
|
|
8261
9124
|
server.listen(port, bindAddr, () => {
|
|
@@ -8273,8 +9136,8 @@ var AdminServer = class {
|
|
|
8273
9136
|
}
|
|
8274
9137
|
/** Per-request handler: auth gate (when a token is set) → routing. */
|
|
8275
9138
|
onRequest(req, res) {
|
|
8276
|
-
void this.dispatch(req, res).catch((
|
|
8277
|
-
const message =
|
|
9139
|
+
void this.dispatch(req, res).catch((err6) => {
|
|
9140
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
8278
9141
|
this.deps.logger.error("[AdminServer] unhandled error:", message);
|
|
8279
9142
|
if (!res.headersSent) {
|
|
8280
9143
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -8538,18 +9401,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
|
|
|
8538
9401
|
return;
|
|
8539
9402
|
}
|
|
8540
9403
|
signal?.addEventListener("abort", abort, { once: true });
|
|
8541
|
-
server.on("error", (
|
|
9404
|
+
server.on("error", (err6) => {
|
|
8542
9405
|
if (settled) return;
|
|
8543
9406
|
settled = true;
|
|
8544
9407
|
clearTimeout(timer);
|
|
8545
|
-
if (
|
|
9408
|
+
if (err6.code === "EADDRINUSE") {
|
|
8546
9409
|
reject(
|
|
8547
9410
|
new Error(
|
|
8548
9411
|
`login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
|
|
8549
9412
|
)
|
|
8550
9413
|
);
|
|
8551
9414
|
} else {
|
|
8552
|
-
reject(
|
|
9415
|
+
reject(err6);
|
|
8553
9416
|
}
|
|
8554
9417
|
});
|
|
8555
9418
|
const timer = setTimeout(() => {
|
|
@@ -8624,6 +9487,411 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
|
|
|
8624
9487
|
};
|
|
8625
9488
|
}
|
|
8626
9489
|
|
|
9490
|
+
// src/allowance/ProviderKeyQuotaService.ts
|
|
9491
|
+
import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
9492
|
+
|
|
9493
|
+
// src/allowance/ProviderKeyQuota.ts
|
|
9494
|
+
var MINUTE_MS2 = 6e4;
|
|
9495
|
+
var HOUR_MS2 = 60 * MINUTE_MS2;
|
|
9496
|
+
var DAY_MS2 = 24 * HOUR_MS2;
|
|
9497
|
+
var WEEK_MS = 7 * DAY_MS2;
|
|
9498
|
+
var MONTH_MS = 30 * DAY_MS2;
|
|
9499
|
+
function finiteNumber3(value) {
|
|
9500
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
9501
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
9502
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
9503
|
+
}
|
|
9504
|
+
function finitePercent4(value) {
|
|
9505
|
+
const parsed = finiteNumber3(value);
|
|
9506
|
+
return parsed !== void 0 && parsed <= 100 ? parsed : null;
|
|
9507
|
+
}
|
|
9508
|
+
function isoInstant3(value) {
|
|
9509
|
+
if (typeof value === "string" && value.trim()) {
|
|
9510
|
+
const time = Date.parse(value);
|
|
9511
|
+
if (Number.isFinite(time)) return new Date(time).toISOString();
|
|
9512
|
+
}
|
|
9513
|
+
const numeric = finiteNumber3(value);
|
|
9514
|
+
if (numeric !== void 0 && numeric > 1e9) {
|
|
9515
|
+
const ms = numeric > 1e12 ? numeric : numeric * 1e3;
|
|
9516
|
+
return new Date(ms).toISOString();
|
|
9517
|
+
}
|
|
9518
|
+
return void 0;
|
|
9519
|
+
}
|
|
9520
|
+
function secondsUntil5(instant, now) {
|
|
9521
|
+
if (!instant) return void 0;
|
|
9522
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
9523
|
+
}
|
|
9524
|
+
function isRecord5(value) {
|
|
9525
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
9526
|
+
}
|
|
9527
|
+
function detectProviderKeyQuotaAdapter(baseUrl) {
|
|
9528
|
+
if (!baseUrl) return null;
|
|
9529
|
+
let url;
|
|
9530
|
+
try {
|
|
9531
|
+
url = new URL(baseUrl);
|
|
9532
|
+
} catch {
|
|
9533
|
+
return null;
|
|
9534
|
+
}
|
|
9535
|
+
const host = url.hostname.toLowerCase();
|
|
9536
|
+
const path2 = url.pathname.toLowerCase();
|
|
9537
|
+
if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
|
|
9538
|
+
return "zai";
|
|
9539
|
+
}
|
|
9540
|
+
if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
|
|
9541
|
+
// anthropic `/anthropic` rows are excluded (their usage impl is unverified).
|
|
9542
|
+
(path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
|
|
9543
|
+
return "minimax-token-plan";
|
|
9544
|
+
}
|
|
9545
|
+
if (host === "api.code.umans.ai") return "umans";
|
|
9546
|
+
if (host === "api.synthetic.new") return "synthetic";
|
|
9547
|
+
return null;
|
|
9548
|
+
}
|
|
9549
|
+
function providerKeyQuotaUrl(adapter, baseUrl) {
|
|
9550
|
+
const origin = new URL(baseUrl).origin;
|
|
9551
|
+
if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
|
|
9552
|
+
if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
|
|
9553
|
+
if (adapter === "umans") return `${origin}/v1/usage`;
|
|
9554
|
+
return `${origin}/v2/quotas`;
|
|
9555
|
+
}
|
|
9556
|
+
function providerKeyQuotaAuthHeader(adapter, key) {
|
|
9557
|
+
return adapter === "zai" ? key : `Bearer ${key}`;
|
|
9558
|
+
}
|
|
9559
|
+
function zaiWindowDurationMs(item) {
|
|
9560
|
+
const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
|
|
9561
|
+
switch (item.unit) {
|
|
9562
|
+
case 3:
|
|
9563
|
+
return count * HOUR_MS2;
|
|
9564
|
+
case 4:
|
|
9565
|
+
return count * DAY_MS2;
|
|
9566
|
+
case 5:
|
|
9567
|
+
return count * MONTH_MS;
|
|
9568
|
+
case 6:
|
|
9569
|
+
return WEEK_MS;
|
|
9570
|
+
default:
|
|
9571
|
+
return void 0;
|
|
9572
|
+
}
|
|
9573
|
+
}
|
|
9574
|
+
function zaiWindowIdLabel(durationMs) {
|
|
9575
|
+
if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
|
|
9576
|
+
if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
|
|
9577
|
+
if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
|
|
9578
|
+
if (durationMs !== void 0 && durationMs % DAY_MS2 === 0) {
|
|
9579
|
+
const days = durationMs / DAY_MS2;
|
|
9580
|
+
return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
|
|
9581
|
+
}
|
|
9582
|
+
if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
|
|
9583
|
+
const hours = durationMs / HOUR_MS2;
|
|
9584
|
+
return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
|
|
9585
|
+
}
|
|
9586
|
+
return { id: "quota", label: "Quota" };
|
|
9587
|
+
}
|
|
9588
|
+
function parseZaiQuotaPayload(payload, now) {
|
|
9589
|
+
if (!isRecord5(payload)) return null;
|
|
9590
|
+
const data = isRecord5(payload["data"]) ? payload["data"] : payload;
|
|
9591
|
+
if (payload["success"] === false) return null;
|
|
9592
|
+
const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
|
|
9593
|
+
const byWindow = /* @__PURE__ */ new Map();
|
|
9594
|
+
for (const raw of limits) {
|
|
9595
|
+
if (!isRecord5(raw)) continue;
|
|
9596
|
+
const item = raw;
|
|
9597
|
+
if (item.type === void 0) continue;
|
|
9598
|
+
const details = raw["usageDetails"];
|
|
9599
|
+
if (Array.isArray(details) && details.some((d) => isRecord5(d) && d["modelCode"] === "zread")) {
|
|
9600
|
+
continue;
|
|
9601
|
+
}
|
|
9602
|
+
const durationMs = zaiWindowDurationMs(item);
|
|
9603
|
+
const { id, label } = zaiWindowIdLabel(durationMs);
|
|
9604
|
+
const limit = finiteNumber3(item.usage);
|
|
9605
|
+
const used = finiteNumber3(item.currentValue);
|
|
9606
|
+
const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
|
|
9607
|
+
const fromPercentage = finitePercent4(item.percentage) ?? void 0;
|
|
9608
|
+
const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
|
|
9609
|
+
if (usedPercent === void 0) continue;
|
|
9610
|
+
const resetsAt = isoInstant3(item.nextResetTime);
|
|
9611
|
+
const candidate = {
|
|
9612
|
+
id,
|
|
9613
|
+
label,
|
|
9614
|
+
scope: "all",
|
|
9615
|
+
usedPercent,
|
|
9616
|
+
...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS2) } : {},
|
|
9617
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9618
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
9619
|
+
state: "fresh"
|
|
9620
|
+
};
|
|
9621
|
+
const existing = byWindow.get(id);
|
|
9622
|
+
if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
|
|
9623
|
+
byWindow.set(id, candidate);
|
|
9624
|
+
}
|
|
9625
|
+
}
|
|
9626
|
+
const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
|
|
9627
|
+
return windows.length > 0 ? windows.slice(0, 4) : null;
|
|
9628
|
+
}
|
|
9629
|
+
var MINIMAX_STATUS_EXHAUSTED = 2;
|
|
9630
|
+
var MINIMAX_SHARED_BUCKET = "general";
|
|
9631
|
+
function parseMiniMaxBucket(value) {
|
|
9632
|
+
if (!isRecord5(value)) return null;
|
|
9633
|
+
const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
|
|
9634
|
+
if (!modelName) return null;
|
|
9635
|
+
const instant = (v) => {
|
|
9636
|
+
const n = finiteNumber3(v);
|
|
9637
|
+
return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
|
|
9638
|
+
};
|
|
9639
|
+
return {
|
|
9640
|
+
modelName,
|
|
9641
|
+
intervalEnd: instant(value["end_time"]),
|
|
9642
|
+
intervalRemainingPercent: finiteNumber3(value["current_interval_remaining_percent"]),
|
|
9643
|
+
intervalStatus: finiteNumber3(value["current_interval_status"]),
|
|
9644
|
+
weeklyEnd: instant(value["weekly_end_time"]),
|
|
9645
|
+
weeklyRemainingPercent: finiteNumber3(value["current_weekly_remaining_percent"]),
|
|
9646
|
+
weeklyStatus: finiteNumber3(value["current_weekly_status"])
|
|
9647
|
+
};
|
|
9648
|
+
}
|
|
9649
|
+
function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
|
|
9650
|
+
const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
|
|
9651
|
+
const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
|
|
9652
|
+
return {
|
|
9653
|
+
id,
|
|
9654
|
+
label,
|
|
9655
|
+
scope: "all",
|
|
9656
|
+
usedPercent,
|
|
9657
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
9658
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9659
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
9660
|
+
state: usedPercent !== null ? "fresh" : "unavailable"
|
|
9661
|
+
};
|
|
9662
|
+
}
|
|
9663
|
+
function parseMiniMaxTokenPlanPayload(payload, now) {
|
|
9664
|
+
if (!isRecord5(payload)) return null;
|
|
9665
|
+
const baseResp = payload["base_resp"];
|
|
9666
|
+
if (!isRecord5(baseResp) || baseResp["status_code"] !== 0) return null;
|
|
9667
|
+
const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
|
|
9668
|
+
let general = null;
|
|
9669
|
+
for (const raw of buckets) {
|
|
9670
|
+
const bucket = parseMiniMaxBucket(raw);
|
|
9671
|
+
if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
|
|
9672
|
+
general = bucket;
|
|
9673
|
+
break;
|
|
9674
|
+
}
|
|
9675
|
+
}
|
|
9676
|
+
if (!general) return null;
|
|
9677
|
+
return [
|
|
9678
|
+
minimaxWindow(
|
|
9679
|
+
"five-hour",
|
|
9680
|
+
"5 hours",
|
|
9681
|
+
5 * 60,
|
|
9682
|
+
general.intervalEnd,
|
|
9683
|
+
general.intervalRemainingPercent,
|
|
9684
|
+
general.intervalStatus,
|
|
9685
|
+
now
|
|
9686
|
+
),
|
|
9687
|
+
minimaxWindow(
|
|
9688
|
+
"seven-day",
|
|
9689
|
+
"7 days",
|
|
9690
|
+
Math.round(WEEK_MS / MINUTE_MS2),
|
|
9691
|
+
general.weeklyEnd,
|
|
9692
|
+
general.weeklyRemainingPercent,
|
|
9693
|
+
general.weeklyStatus,
|
|
9694
|
+
now
|
|
9695
|
+
)
|
|
9696
|
+
];
|
|
9697
|
+
}
|
|
9698
|
+
function parseUmansUsagePayload(payload, now) {
|
|
9699
|
+
if (!isRecord5(payload)) return null;
|
|
9700
|
+
const limits = isRecord5(payload["limits"]) ? payload["limits"] : void 0;
|
|
9701
|
+
const requests = limits && isRecord5(limits["requests"]) ? limits["requests"] : void 0;
|
|
9702
|
+
const usage = isRecord5(payload["usage"]) ? payload["usage"] : void 0;
|
|
9703
|
+
const window = isRecord5(payload["window"]) ? payload["window"] : void 0;
|
|
9704
|
+
const hardCap = finiteNumber3(requests?.["hard_cap"]);
|
|
9705
|
+
const softLimit = finiteNumber3(requests?.["limit"]);
|
|
9706
|
+
const requestsInWindow = finiteNumber3(usage?.["requests_in_window"]);
|
|
9707
|
+
const weightedInWindow = finiteNumber3(usage?.["weighted_in_window"]);
|
|
9708
|
+
const resetsAt = isoInstant3(window?.["resets_at"]);
|
|
9709
|
+
let usedPercent = null;
|
|
9710
|
+
if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
|
|
9711
|
+
usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
|
|
9712
|
+
} else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
|
|
9713
|
+
usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
|
|
9714
|
+
}
|
|
9715
|
+
if (usedPercent === null && resetsAt === void 0) return null;
|
|
9716
|
+
return [
|
|
9717
|
+
{
|
|
9718
|
+
id: "five-hour",
|
|
9719
|
+
label: "5 hours",
|
|
9720
|
+
scope: "all",
|
|
9721
|
+
usedPercent,
|
|
9722
|
+
windowMinutes: 5 * 60,
|
|
9723
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9724
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
9725
|
+
state: "fresh"
|
|
9726
|
+
}
|
|
9727
|
+
];
|
|
9728
|
+
}
|
|
9729
|
+
function parseSyntheticQuotasPayload(payload, now) {
|
|
9730
|
+
if (!isRecord5(payload)) return null;
|
|
9731
|
+
const fiveHour = isRecord5(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
|
|
9732
|
+
const weekly = isRecord5(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
|
|
9733
|
+
const windows = [];
|
|
9734
|
+
if (fiveHour) {
|
|
9735
|
+
const max = finiteNumber3(fiveHour["max"]);
|
|
9736
|
+
const remaining = finiteNumber3(fiveHour["remaining"]);
|
|
9737
|
+
const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
|
|
9738
|
+
const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
|
|
9739
|
+
windows.push({
|
|
9740
|
+
id: "five-hour",
|
|
9741
|
+
label: "5 hours",
|
|
9742
|
+
scope: "all",
|
|
9743
|
+
usedPercent,
|
|
9744
|
+
windowMinutes: 5 * 60,
|
|
9745
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9746
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
9747
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
9748
|
+
});
|
|
9749
|
+
}
|
|
9750
|
+
if (weekly) {
|
|
9751
|
+
const percentRemaining = finiteNumber3(weekly["percentRemaining"]);
|
|
9752
|
+
const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
|
|
9753
|
+
const resetsAt = isoInstant3(weekly["nextRegenAt"]);
|
|
9754
|
+
windows.push({
|
|
9755
|
+
id: "seven-day",
|
|
9756
|
+
label: "7 days",
|
|
9757
|
+
scope: "all",
|
|
9758
|
+
usedPercent,
|
|
9759
|
+
windowMinutes: 7 * 24 * 60,
|
|
9760
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9761
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
9762
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
9763
|
+
});
|
|
9764
|
+
}
|
|
9765
|
+
return windows.length > 0 ? windows : null;
|
|
9766
|
+
}
|
|
9767
|
+
|
|
9768
|
+
// src/allowance/ProviderKeyQuotaService.ts
|
|
9769
|
+
function parseQuotaPayload(adapter, payload, now) {
|
|
9770
|
+
switch (adapter) {
|
|
9771
|
+
case "zai":
|
|
9772
|
+
return parseZaiQuotaPayload(payload, now);
|
|
9773
|
+
case "minimax-token-plan":
|
|
9774
|
+
return parseMiniMaxTokenPlanPayload(payload, now);
|
|
9775
|
+
case "umans":
|
|
9776
|
+
return parseUmansUsagePayload(payload, now);
|
|
9777
|
+
case "synthetic":
|
|
9778
|
+
return parseSyntheticQuotasPayload(payload, now);
|
|
9779
|
+
}
|
|
9780
|
+
}
|
|
9781
|
+
var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
|
|
9782
|
+
function resolvedBaseUrl(row) {
|
|
9783
|
+
const modes = row.apiModes ?? [];
|
|
9784
|
+
const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
|
|
9785
|
+
const fallback = modes[0];
|
|
9786
|
+
const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
|
|
9787
|
+
return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
|
|
9788
|
+
}
|
|
9789
|
+
function rowKeyEntries(row) {
|
|
9790
|
+
const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
|
|
9791
|
+
if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
|
|
9792
|
+
if (row.apiKey.length > 0) {
|
|
9793
|
+
return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
|
|
9794
|
+
}
|
|
9795
|
+
return [];
|
|
9796
|
+
}
|
|
9797
|
+
var ProviderKeyQuotaService = class {
|
|
9798
|
+
constructor(box, fetchImpl = (url, init) => fetchUpstream6(url, init, { redactBodies: true }), now = Date.now) {
|
|
9799
|
+
this.box = box;
|
|
9800
|
+
this.fetchImpl = fetchImpl;
|
|
9801
|
+
this.now = now;
|
|
9802
|
+
}
|
|
9803
|
+
box;
|
|
9804
|
+
fetchImpl;
|
|
9805
|
+
now;
|
|
9806
|
+
cache = /* @__PURE__ */ new Map();
|
|
9807
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
9808
|
+
/**
|
|
9809
|
+
* Quota for one key of a provider row, or `null` when the row has no quota
|
|
9810
|
+
* adapter / no such key. Cache-first; concurrent reads share one flight.
|
|
9811
|
+
*/
|
|
9812
|
+
async quotaFor(row, keyId, options = {}) {
|
|
9813
|
+
const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
|
|
9814
|
+
if (!adapter) return null;
|
|
9815
|
+
const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
|
|
9816
|
+
if (!entry) return null;
|
|
9817
|
+
const cacheKey = `${row.id}\0${keyId}`;
|
|
9818
|
+
const now = this.now();
|
|
9819
|
+
const cached = this.cache.get(cacheKey);
|
|
9820
|
+
if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
|
|
9821
|
+
const running = this.inFlight.get(cacheKey);
|
|
9822
|
+
if (running) return running;
|
|
9823
|
+
const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
|
|
9824
|
+
void error;
|
|
9825
|
+
const previous = this.cache.get(cacheKey);
|
|
9826
|
+
if (previous) {
|
|
9827
|
+
const degraded = {
|
|
9828
|
+
...previous,
|
|
9829
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9830
|
+
windows: previous.windows.map((window) => ({
|
|
9831
|
+
...window,
|
|
9832
|
+
state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
|
|
9833
|
+
})),
|
|
9834
|
+
errorCode: "quota_request_failed"
|
|
9835
|
+
};
|
|
9836
|
+
this.cache.set(cacheKey, degraded);
|
|
9837
|
+
return degraded;
|
|
9838
|
+
}
|
|
9839
|
+
return null;
|
|
9840
|
+
}).finally(() => this.inFlight.delete(cacheKey));
|
|
9841
|
+
this.inFlight.set(cacheKey, promise);
|
|
9842
|
+
return promise;
|
|
9843
|
+
}
|
|
9844
|
+
/** Drop cached rows for a provider (key added/removed/rotated). */
|
|
9845
|
+
invalidateProvider(providerRowId) {
|
|
9846
|
+
for (const key of this.cache.keys()) {
|
|
9847
|
+
if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
|
|
9848
|
+
}
|
|
9849
|
+
}
|
|
9850
|
+
async fetchQuota(adapter, row, rawKey, cacheKey) {
|
|
9851
|
+
const baseUrl = resolvedBaseUrl(row);
|
|
9852
|
+
const url = providerKeyQuotaUrl(adapter, baseUrl);
|
|
9853
|
+
const key = this.box.decryptMaybe(rawKey);
|
|
9854
|
+
const now = this.now();
|
|
9855
|
+
const response = await this.fetchImpl(url, {
|
|
9856
|
+
method: "GET",
|
|
9857
|
+
headers: {
|
|
9858
|
+
Authorization: providerKeyQuotaAuthHeader(adapter, key),
|
|
9859
|
+
Accept: "application/json",
|
|
9860
|
+
"Content-Type": "application/json"
|
|
9861
|
+
},
|
|
9862
|
+
signal: AbortSignal.timeout(15e3)
|
|
9863
|
+
});
|
|
9864
|
+
if (response.status === 401 || response.status === 403) {
|
|
9865
|
+
const snapshot2 = {
|
|
9866
|
+
adapter,
|
|
9867
|
+
observedAt: new Date(now).toISOString(),
|
|
9868
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9869
|
+
windows: [],
|
|
9870
|
+
errorCode: "quota_unauthorized"
|
|
9871
|
+
};
|
|
9872
|
+
this.cache.set(cacheKey, snapshot2);
|
|
9873
|
+
return snapshot2;
|
|
9874
|
+
}
|
|
9875
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
9876
|
+
let payload;
|
|
9877
|
+
try {
|
|
9878
|
+
payload = await response.json();
|
|
9879
|
+
} catch {
|
|
9880
|
+
throw new Error("invalid JSON");
|
|
9881
|
+
}
|
|
9882
|
+
const windows = parseQuotaPayload(adapter, payload, now);
|
|
9883
|
+
const snapshot = {
|
|
9884
|
+
adapter,
|
|
9885
|
+
observedAt: new Date(now).toISOString(),
|
|
9886
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9887
|
+
windows: windows ?? [],
|
|
9888
|
+
...windows ? {} : { errorCode: "quota_unavailable" }
|
|
9889
|
+
};
|
|
9890
|
+
this.cache.set(cacheKey, snapshot);
|
|
9891
|
+
return snapshot;
|
|
9892
|
+
}
|
|
9893
|
+
};
|
|
9894
|
+
|
|
8627
9895
|
// src/image-generation/ImageDoctorService.ts
|
|
8628
9896
|
import {
|
|
8629
9897
|
normalizeImageGenerationError
|
|
@@ -14740,21 +16008,23 @@ function bucketLabel(bucketStartTs, bucket) {
|
|
|
14740
16008
|
}
|
|
14741
16009
|
|
|
14742
16010
|
// src/ports/JsonOutboundKeyDb.ts
|
|
16011
|
+
import { existsSync as existsSync19, readFileSync as readFileSync15 } from "fs";
|
|
16012
|
+
import {
|
|
16013
|
+
validateOutboundPermissions as validateOutboundPermissions3
|
|
16014
|
+
} from "@omnicross/core";
|
|
16015
|
+
|
|
16016
|
+
// src/ports/atomicFile.ts
|
|
14743
16017
|
import { randomBytes as randomBytes11 } from "crypto";
|
|
14744
16018
|
import {
|
|
14745
16019
|
closeSync as closeSync8,
|
|
14746
16020
|
existsSync as existsSync18,
|
|
14747
16021
|
fsyncSync as fsyncSync7,
|
|
14748
16022
|
openSync as openSync8,
|
|
14749
|
-
readFileSync as readFileSync15,
|
|
14750
16023
|
renameSync as renameSync10,
|
|
14751
16024
|
unlinkSync as unlinkSync12,
|
|
14752
16025
|
writeFileSync as writeFileSync13
|
|
14753
16026
|
} from "fs";
|
|
14754
16027
|
import { basename as basename8, dirname as dirname14, join as join19 } from "path";
|
|
14755
|
-
import {
|
|
14756
|
-
validateOutboundPermissions as validateOutboundPermissions3
|
|
14757
|
-
} from "@omnicross/core";
|
|
14758
16028
|
function atomicReplaceUtf8(targetPath, contents) {
|
|
14759
16029
|
const tempPath = join19(
|
|
14760
16030
|
dirname14(targetPath),
|
|
@@ -14784,6 +16054,8 @@ function atomicReplaceUtf8(targetPath, contents) {
|
|
|
14784
16054
|
throw error;
|
|
14785
16055
|
}
|
|
14786
16056
|
}
|
|
16057
|
+
|
|
16058
|
+
// src/ports/JsonOutboundKeyDb.ts
|
|
14787
16059
|
var JsonOutboundKeyDb = class {
|
|
14788
16060
|
/**
|
|
14789
16061
|
* @param secretBox OPTIONAL reversible-secret codec. When present, a created
|
|
@@ -14926,7 +16198,7 @@ var JsonOutboundKeyDb = class {
|
|
|
14926
16198
|
}
|
|
14927
16199
|
/** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14928
16200
|
readRows() {
|
|
14929
|
-
if (!
|
|
16201
|
+
if (!existsSync19(this.keysPath)) return [];
|
|
14930
16202
|
try {
|
|
14931
16203
|
const parsed = JSON.parse(readFileSync15(this.keysPath, "utf8"));
|
|
14932
16204
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -14945,7 +16217,7 @@ function applyPolicyField(row, field, value) {
|
|
|
14945
16217
|
}
|
|
14946
16218
|
|
|
14947
16219
|
// src/ports/JsonPricingStore.ts
|
|
14948
|
-
import { existsSync as
|
|
16220
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16, renameSync as renameSync11, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "fs";
|
|
14949
16221
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
14950
16222
|
var JsonPricingStore = class {
|
|
14951
16223
|
constructor(pricingPath) {
|
|
@@ -14960,7 +16232,7 @@ var JsonPricingStore = class {
|
|
|
14960
16232
|
* otherwise unusable pricing table after a crash or manual file edit.
|
|
14961
16233
|
*/
|
|
14962
16234
|
hasUsableSnapshot() {
|
|
14963
|
-
if (!
|
|
16235
|
+
if (!existsSync20(this.pricingPath)) return false;
|
|
14964
16236
|
try {
|
|
14965
16237
|
const parsed = JSON.parse(readFileSync16(this.pricingPath, "utf8"));
|
|
14966
16238
|
return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
|
|
@@ -15075,7 +16347,7 @@ var JsonPricingStore = class {
|
|
|
15075
16347
|
}
|
|
15076
16348
|
/** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
|
|
15077
16349
|
readRows() {
|
|
15078
|
-
if (!
|
|
16350
|
+
if (!existsSync20(this.pricingPath)) return [];
|
|
15079
16351
|
try {
|
|
15080
16352
|
const parsed = JSON.parse(readFileSync16(this.pricingPath, "utf8"));
|
|
15081
16353
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -15107,7 +16379,7 @@ function isUsablePricingRow(value) {
|
|
|
15107
16379
|
}
|
|
15108
16380
|
|
|
15109
16381
|
// src/pricing/PricingRefreshScheduler.ts
|
|
15110
|
-
import { existsSync as
|
|
16382
|
+
import { existsSync as existsSync21, readFileSync as readFileSync17, renameSync as renameSync12, writeFileSync as writeFileSync15 } from "fs";
|
|
15111
16383
|
var EMPTY_STATE2 = {
|
|
15112
16384
|
lastAttemptAt: null,
|
|
15113
16385
|
lastSuccessAt: null,
|
|
@@ -15145,7 +16417,7 @@ var PricingRefreshScheduler = class {
|
|
|
15145
16417
|
this.timer = null;
|
|
15146
16418
|
}
|
|
15147
16419
|
getState() {
|
|
15148
|
-
if (!
|
|
16420
|
+
if (!existsSync21(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
|
|
15149
16421
|
try {
|
|
15150
16422
|
const value = JSON.parse(readFileSync17(this.statePath, "utf8"));
|
|
15151
16423
|
return {
|
|
@@ -15210,7 +16482,7 @@ function finiteOrNull(value) {
|
|
|
15210
16482
|
}
|
|
15211
16483
|
|
|
15212
16484
|
// src/ports/JsonVoucherDb.ts
|
|
15213
|
-
import { existsSync as
|
|
16485
|
+
import { existsSync as existsSync22, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "fs";
|
|
15214
16486
|
var JsonVoucherDb = class {
|
|
15215
16487
|
constructor(vouchersPath) {
|
|
15216
16488
|
this.vouchersPath = vouchersPath;
|
|
@@ -15288,7 +16560,7 @@ var JsonVoucherDb = class {
|
|
|
15288
16560
|
}
|
|
15289
16561
|
/** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
|
|
15290
16562
|
readRows() {
|
|
15291
|
-
if (!
|
|
16563
|
+
if (!existsSync22(this.vouchersPath)) return [];
|
|
15292
16564
|
try {
|
|
15293
16565
|
const parsed = JSON.parse(readFileSync18(this.vouchersPath, "utf8"));
|
|
15294
16566
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -15302,16 +16574,17 @@ var JsonVoucherDb = class {
|
|
|
15302
16574
|
};
|
|
15303
16575
|
|
|
15304
16576
|
// src/ports/JsonSubscriptionCredentialStore.ts
|
|
15305
|
-
import { existsSync as
|
|
16577
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync6, readFileSync as readFileSync20, renameSync as renameSync13 } from "fs";
|
|
15306
16578
|
import { dirname as dirname15 } from "path";
|
|
15307
16579
|
import { getSharedAccountHealth as getSharedAccountHealth2 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
15308
16580
|
import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling3 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
15309
|
-
import { fetchUpstream as
|
|
16581
|
+
import { fetchUpstream as fetchUpstream7 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
15310
16582
|
import { getSharedIdentityStore as getSharedIdentityStore2 } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
|
|
15311
16583
|
import {
|
|
15312
16584
|
claudeOAuth as claudeOAuth2,
|
|
15313
16585
|
codexOAuth as codexOAuth2,
|
|
15314
|
-
geminiOAuth as geminiOAuth2
|
|
16586
|
+
geminiOAuth as geminiOAuth2,
|
|
16587
|
+
kimiOAuth as kimiOAuth2
|
|
15315
16588
|
} from "@omnicross/subscriptions";
|
|
15316
16589
|
|
|
15317
16590
|
// src/ports/account-sync.ts
|
|
@@ -15356,7 +16629,7 @@ function findDuplicateCredentialIds(accounts) {
|
|
|
15356
16629
|
}
|
|
15357
16630
|
|
|
15358
16631
|
// src/ports/external-cli-credentials.ts
|
|
15359
|
-
import { existsSync as
|
|
16632
|
+
import { existsSync as existsSync23, readFileSync as readFileSync19 } from "fs";
|
|
15360
16633
|
import { homedir as homedir4 } from "os";
|
|
15361
16634
|
import { join as join20 } from "path";
|
|
15362
16635
|
function externalStorePath(provider, home = homedir4()) {
|
|
@@ -15409,7 +16682,7 @@ function parseCodexTokensEnvelope(raw) {
|
|
|
15409
16682
|
}
|
|
15410
16683
|
function readExternalCliCredentials(provider, home = homedir4()) {
|
|
15411
16684
|
const path2 = externalStorePath(provider, home);
|
|
15412
|
-
if (!
|
|
16685
|
+
if (!existsSync23(path2)) return null;
|
|
15413
16686
|
let raw;
|
|
15414
16687
|
try {
|
|
15415
16688
|
const parsed = JSON.parse(readFileSync19(path2, "utf8"));
|
|
@@ -15435,16 +16708,18 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15435
16708
|
* as on relay refresh egresses from the SAME proxy IP as the
|
|
15436
16709
|
* account's traffic. NOT used by any read/write path.
|
|
15437
16710
|
*/
|
|
15438
|
-
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
|
|
16711
|
+
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
|
|
15439
16712
|
this.tokensPath = tokensPath;
|
|
15440
16713
|
this.box = box;
|
|
15441
16714
|
this.fetchImpl = fetchImpl;
|
|
15442
16715
|
this.externalCliReader = externalCliReader;
|
|
16716
|
+
this.atomicReplace = atomicReplace;
|
|
15443
16717
|
}
|
|
15444
16718
|
tokensPath;
|
|
15445
16719
|
box;
|
|
15446
16720
|
fetchImpl;
|
|
15447
16721
|
externalCliReader;
|
|
16722
|
+
atomicReplace;
|
|
15448
16723
|
/**
|
|
15449
16724
|
* The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
|
|
15450
16725
|
* TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
|
|
@@ -15458,7 +16733,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15458
16733
|
* a plaintext token pair into `upstream-trace.jsonl`.
|
|
15459
16734
|
*/
|
|
15460
16735
|
buildRefreshFetch(providerId, accountId) {
|
|
15461
|
-
return this.fetchImpl ?? ((url, init) =>
|
|
16736
|
+
return this.fetchImpl ?? ((url, init) => fetchUpstream7(url, init, { providerId, accountId, redactBodies: true }));
|
|
15462
16737
|
}
|
|
15463
16738
|
/**
|
|
15464
16739
|
* In-flight refresh coalescing. OAuth refresh tokens are
|
|
@@ -15499,7 +16774,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15499
16774
|
* other hot reads. Never returns token material.
|
|
15500
16775
|
*/
|
|
15501
16776
|
getAccountProxy(providerId, accountId) {
|
|
15502
|
-
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
|
|
16777
|
+
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
|
|
15503
16778
|
return void 0;
|
|
15504
16779
|
}
|
|
15505
16780
|
return getAccountProxy(this.readConfig(), providerId, accountId);
|
|
@@ -15518,7 +16793,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15518
16793
|
const fingerprintOn = identityStore.isEnabled();
|
|
15519
16794
|
const now = Date.now();
|
|
15520
16795
|
const out = {};
|
|
15521
|
-
for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
|
|
16796
|
+
for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
|
|
15522
16797
|
const sanitized = sanitizeAccounts(config, provider);
|
|
15523
16798
|
if (sanitized.length === 0) continue;
|
|
15524
16799
|
for (const account of sanitized) {
|
|
@@ -15676,6 +16951,47 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15676
16951
|
}
|
|
15677
16952
|
});
|
|
15678
16953
|
}
|
|
16954
|
+
/**
|
|
16955
|
+
* Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
|
|
16956
|
+
* Kimi ROTATES the refresh token, so the response's pair is written back
|
|
16957
|
+
* whole; the account's stable `deviceId` (fingerprint header input) is
|
|
16958
|
+
* preserved. The refresh call carries the CLI fingerprint headers. HONEST
|
|
16959
|
+
* `false` when no refresh_token.
|
|
16960
|
+
*/
|
|
16961
|
+
async refreshKimiToken() {
|
|
16962
|
+
return this.coalesce("kimi:active", async () => {
|
|
16963
|
+
const config = this.readConfig();
|
|
16964
|
+
const active = getActiveAccount(config, "kimi");
|
|
16965
|
+
const kimi = active?.tokens;
|
|
16966
|
+
if (!active || !kimi?.refreshToken) return false;
|
|
16967
|
+
const capturedId = active.id;
|
|
16968
|
+
this.materializeMigration(config);
|
|
16969
|
+
const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
|
|
16970
|
+
try {
|
|
16971
|
+
const result = await kimiOAuth2.refreshAccessToken(
|
|
16972
|
+
kimi.refreshToken,
|
|
16973
|
+
refreshFetch,
|
|
16974
|
+
kimiOAuth2.kimiFingerprintHeaders(kimi.deviceId)
|
|
16975
|
+
);
|
|
16976
|
+
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
16977
|
+
const next = {
|
|
16978
|
+
...kimi,
|
|
16979
|
+
accessToken: result.accessToken,
|
|
16980
|
+
refreshToken: result.refreshToken,
|
|
16981
|
+
expiresAt,
|
|
16982
|
+
status: "authorized",
|
|
16983
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16984
|
+
errorMessage: void 0,
|
|
16985
|
+
syncWarning: void 0
|
|
16986
|
+
};
|
|
16987
|
+
this.writeBackById("kimi", capturedId, next);
|
|
16988
|
+
return true;
|
|
16989
|
+
} catch (error) {
|
|
16990
|
+
this.markExpiredById("kimi", capturedId, kimi, error);
|
|
16991
|
+
return false;
|
|
16992
|
+
}
|
|
16993
|
+
});
|
|
16994
|
+
}
|
|
15679
16995
|
/**
|
|
15680
16996
|
* Refresh a SPECIFIC managed account by id (background scheduler sweep and
|
|
15681
16997
|
* account-pool resolution). It uses only that account's stored refresh
|
|
@@ -15728,7 +17044,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15728
17044
|
}
|
|
15729
17045
|
const oauth = account.tokens;
|
|
15730
17046
|
if (!oauth.accessToken) return null;
|
|
15731
|
-
if (providerId === "codex" || providerId === "gemini") {
|
|
17047
|
+
if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
|
|
15732
17048
|
const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
|
|
15733
17049
|
const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
|
|
15734
17050
|
if (expiringSoon && oauth.refreshToken) {
|
|
@@ -15817,8 +17133,23 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15817
17133
|
}
|
|
15818
17134
|
/** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
|
|
15819
17135
|
async refreshUpstream(provider, refreshToken, accountId) {
|
|
17136
|
+
const refreshFetch = this.buildRefreshFetch(provider, accountId);
|
|
17137
|
+
if (provider === "kimi") {
|
|
17138
|
+
const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
|
|
17139
|
+
const deviceId = account?.tokens?.deviceId;
|
|
17140
|
+
const r2 = await kimiOAuth2.refreshAccessToken(
|
|
17141
|
+
refreshToken,
|
|
17142
|
+
refreshFetch,
|
|
17143
|
+
kimiOAuth2.kimiFingerprintHeaders(deviceId)
|
|
17144
|
+
);
|
|
17145
|
+
return {
|
|
17146
|
+
accessToken: r2.accessToken,
|
|
17147
|
+
refreshToken: r2.refreshToken,
|
|
17148
|
+
expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
|
|
17149
|
+
};
|
|
17150
|
+
}
|
|
15820
17151
|
const flow = provider === "claude" ? claudeOAuth2 : provider === "codex" ? codexOAuth2 : geminiOAuth2;
|
|
15821
|
-
const r = await flow.refreshAccessToken(refreshToken,
|
|
17152
|
+
const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
|
|
15822
17153
|
return {
|
|
15823
17154
|
accessToken: r.accessToken,
|
|
15824
17155
|
refreshToken: r.refreshToken,
|
|
@@ -15981,42 +17312,86 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15981
17312
|
/** Write the merged config to disk as pretty JSON (mkdir parent if needed).
|
|
15982
17313
|
* Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
|
|
15983
17314
|
* `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
|
|
15984
|
-
* write incl. child 4's future refresh writes lands encrypted.
|
|
17315
|
+
* write incl. child 4's future refresh writes lands encrypted.
|
|
17316
|
+
* ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
|
|
17317
|
+
* interrupted write discards only the temp file; the prior `tokens.json`
|
|
17318
|
+
* survives byte-equal (bare `writeFileSync` truncate-writes lost every
|
|
17319
|
+
* account on a mid-write failure, 2026-09-06). */
|
|
15985
17320
|
persist(config) {
|
|
15986
17321
|
mkdirSync6(dirname15(this.tokensPath), { recursive: true });
|
|
15987
17322
|
const encrypted = encryptTokens(config, this.box);
|
|
15988
|
-
|
|
17323
|
+
this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
|
|
15989
17324
|
}
|
|
15990
17325
|
/**
|
|
15991
|
-
* Read + parse `tokens.json`,
|
|
15992
|
-
*
|
|
15993
|
-
*
|
|
17326
|
+
* Read + parse `tokens.json`, then DECRYPT the token-material fields so every
|
|
17327
|
+
* getter returns plaintext (the subscription bearer path is byte-identical).
|
|
17328
|
+
*
|
|
17329
|
+
* A MISSING file is a legitimate first-boot state → minimal `{ updatedAt: '' }`.
|
|
17330
|
+
* A file that EXISTS but cannot be parsed as a JSON object is CORRUPT →
|
|
17331
|
+
* `quarantineCorrupt` moves it aside (once) before the empty config is
|
|
17332
|
+
* returned, so the unreadable accounts survive for manual recovery.
|
|
15994
17333
|
*
|
|
15995
|
-
* The
|
|
15996
|
-
*
|
|
15997
|
-
*
|
|
15998
|
-
*
|
|
15999
|
-
*
|
|
16000
|
-
*
|
|
16001
|
-
* `config.ts loadConfig`, which decrypts outside its parse try.
|
|
17334
|
+
* The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
|
|
17335
|
+
* tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
|
|
17336
|
+
* error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
|
|
17337
|
+
* decrypt would report "no tokens" and silently send the WRONG bearer
|
|
17338
|
+
* upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
|
|
17339
|
+
* its parse try.
|
|
16002
17340
|
*/
|
|
16003
17341
|
readConfig() {
|
|
16004
|
-
if (!
|
|
17342
|
+
if (!existsSync24(this.tokensPath)) return { updatedAt: "" };
|
|
16005
17343
|
let parsed;
|
|
16006
17344
|
try {
|
|
16007
17345
|
const raw = JSON.parse(readFileSync20(this.tokensPath, "utf8"));
|
|
16008
|
-
|
|
17346
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
17347
|
+
return this.quarantineCorrupt("parsed JSON is not an object");
|
|
17348
|
+
}
|
|
17349
|
+
parsed = raw;
|
|
16009
17350
|
} catch {
|
|
16010
|
-
|
|
17351
|
+
return this.quarantineCorrupt("unparseable JSON");
|
|
16011
17352
|
}
|
|
16012
|
-
if (!parsed) return { updatedAt: "" };
|
|
16013
17353
|
const decrypted = decryptTokens(parsed, this.box);
|
|
16014
17354
|
return migrateLazily(decrypted);
|
|
16015
17355
|
}
|
|
17356
|
+
/** One-shot latch: a corrupt file is quarantined (or found unmovable) at
|
|
17357
|
+
* most once per process, so the hot read path never re-attempts or re-logs. */
|
|
17358
|
+
corruptQuarantined = false;
|
|
17359
|
+
/**
|
|
17360
|
+
* Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
|
|
17361
|
+
*
|
|
17362
|
+
* Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
|
|
17363
|
+
* logs loudly (the daemon's stderr log; secret-free — reason + paths only).
|
|
17364
|
+
* The daemon KEEPS SERVING (API-key routing is unaffected; subscription
|
|
17365
|
+
* routing reports no credential, same as an absent file) while the corrupt
|
|
17366
|
+
* bytes survive for manual recovery — and, critically, the NEXT persist
|
|
17367
|
+
* (e.g. the user re-logging in) can no longer overwrite the only copy of
|
|
17368
|
+
* the old accounts, which is exactly how the 2026-09-06 incident turned a
|
|
17369
|
+
* recoverable truncated file into permanent account loss.
|
|
17370
|
+
*
|
|
17371
|
+
* Best-effort: if the rename fails (file locked, permissions), the corrupt
|
|
17372
|
+
* file is left in place and every later read still tolerates it as empty;
|
|
17373
|
+
* the latch still trips so the attempt + log happen exactly once.
|
|
17374
|
+
*/
|
|
17375
|
+
quarantineCorrupt(reason) {
|
|
17376
|
+
if (!this.corruptQuarantined) {
|
|
17377
|
+
this.corruptQuarantined = true;
|
|
17378
|
+
const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
17379
|
+
let moved = false;
|
|
17380
|
+
try {
|
|
17381
|
+
renameSync13(this.tokensPath, backup);
|
|
17382
|
+
moved = true;
|
|
17383
|
+
} catch {
|
|
17384
|
+
}
|
|
17385
|
+
console.error(
|
|
17386
|
+
`[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`)
|
|
17387
|
+
);
|
|
17388
|
+
}
|
|
17389
|
+
return { updatedAt: "" };
|
|
17390
|
+
}
|
|
16016
17391
|
};
|
|
16017
17392
|
|
|
16018
17393
|
// src/AccountHealthProbeScheduler.ts
|
|
16019
|
-
import { fetchUpstream as
|
|
17394
|
+
import { fetchUpstream as fetchUpstream8 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
16020
17395
|
|
|
16021
17396
|
// src/probe/CodexGenerationProbe.ts
|
|
16022
17397
|
import {
|
|
@@ -16158,7 +17533,11 @@ var PROVIDER_PROBE_PLANS = {
|
|
|
16158
17533
|
// billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
|
|
16159
17534
|
codex: { kind: "local" },
|
|
16160
17535
|
gemini: { kind: "local" },
|
|
16161
|
-
opencodego: { kind: "local" }
|
|
17536
|
+
opencodego: { kind: "local" },
|
|
17537
|
+
// Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
|
|
17538
|
+
// collector uses it), but the probe path also needs the fingerprint headers —
|
|
17539
|
+
// keep the probe local until the collector covers the health surface.
|
|
17540
|
+
kimi: { kind: "local" }
|
|
16162
17541
|
};
|
|
16163
17542
|
function probePlanFor(providerId) {
|
|
16164
17543
|
return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
|
|
@@ -16180,7 +17559,7 @@ var AccountHealthProbeScheduler = class {
|
|
|
16180
17559
|
this.logger = logger;
|
|
16181
17560
|
this.config = config;
|
|
16182
17561
|
this.now = opts.now ?? Date.now;
|
|
16183
|
-
this.fetchImpl = opts.fetchImpl ??
|
|
17562
|
+
this.fetchImpl = opts.fetchImpl ?? fetchUpstream8;
|
|
16184
17563
|
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
16185
17564
|
this.planFor = opts.planFor ?? probePlanFor;
|
|
16186
17565
|
}
|
|
@@ -16524,7 +17903,7 @@ var AccountHealthSweeper = class {
|
|
|
16524
17903
|
};
|
|
16525
17904
|
|
|
16526
17905
|
// src/audit/AuditPruneSweeper.ts
|
|
16527
|
-
import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as
|
|
17906
|
+
import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync26, readdirSync as readdirSync8, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
|
|
16528
17907
|
import { join as join22 } from "path";
|
|
16529
17908
|
import { pipeline } from "stream/promises";
|
|
16530
17909
|
import { createGzip } from "zlib";
|
|
@@ -16532,11 +17911,11 @@ import { createGzip } from "zlib";
|
|
|
16532
17911
|
// src/audit/auditStats.ts
|
|
16533
17912
|
import {
|
|
16534
17913
|
createReadStream as createReadStream2,
|
|
16535
|
-
existsSync as
|
|
17914
|
+
existsSync as existsSync25,
|
|
16536
17915
|
readFileSync as readFileSync21,
|
|
16537
17916
|
readdirSync as readdirSync7,
|
|
16538
17917
|
statSync as statSync7,
|
|
16539
|
-
writeFileSync as
|
|
17918
|
+
writeFileSync as writeFileSync17
|
|
16540
17919
|
} from "fs";
|
|
16541
17920
|
import { basename as basename9, dirname as dirname16, join as join21 } from "path";
|
|
16542
17921
|
var SIDECAR_VERSION = 1;
|
|
@@ -16546,7 +17925,7 @@ function auditStatsFileName(auditFile) {
|
|
|
16546
17925
|
return auditFile.replace(/\.jsonl$/, ".stats.json");
|
|
16547
17926
|
}
|
|
16548
17927
|
function readPersisted(path2) {
|
|
16549
|
-
if (!
|
|
17928
|
+
if (!existsSync25(path2)) return null;
|
|
16550
17929
|
try {
|
|
16551
17930
|
const value = JSON.parse(readFileSync21(path2, "utf8"));
|
|
16552
17931
|
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)) {
|
|
@@ -16578,7 +17957,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
|
|
|
16578
17957
|
minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
|
|
16579
17958
|
maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
|
|
16580
17959
|
};
|
|
16581
|
-
|
|
17960
|
+
writeFileSync17(statsPath, JSON.stringify(next), "utf8");
|
|
16582
17961
|
}
|
|
16583
17962
|
function queryCovers(stats, from, to) {
|
|
16584
17963
|
return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
|
|
@@ -16689,7 +18068,7 @@ function mergePersistedStats(previous, appended) {
|
|
|
16689
18068
|
};
|
|
16690
18069
|
}
|
|
16691
18070
|
async function readAuditStats(auditDir, query2 = {}) {
|
|
16692
|
-
if (!
|
|
18071
|
+
if (!existsSync25(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
|
|
16693
18072
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
16694
18073
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
16695
18074
|
let sources;
|
|
@@ -16702,7 +18081,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16702
18081
|
auditPath: join21(auditDir, name),
|
|
16703
18082
|
statsPath: join21(auditDir, auditStatsFileName(name))
|
|
16704
18083
|
}
|
|
16705
|
-
).filter((source) =>
|
|
18084
|
+
).filter((source) => existsSync25(source.auditPath));
|
|
16706
18085
|
} catch {
|
|
16707
18086
|
return { requestCount: 0, errorCount: 0, complete: false };
|
|
16708
18087
|
}
|
|
@@ -16728,7 +18107,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16728
18107
|
total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
|
|
16729
18108
|
total.complete = total.complete && scanned.filtered.complete;
|
|
16730
18109
|
const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
|
|
16731
|
-
if (current.complete)
|
|
18110
|
+
if (current.complete) writeFileSync17(statsPath, JSON.stringify(current), "utf8");
|
|
16732
18111
|
} catch {
|
|
16733
18112
|
total.complete = false;
|
|
16734
18113
|
}
|
|
@@ -16737,7 +18116,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16737
18116
|
}
|
|
16738
18117
|
|
|
16739
18118
|
// src/audit/AuditPruneSweeper.ts
|
|
16740
|
-
var
|
|
18119
|
+
var DAY_MS3 = 24 * 60 * 6e4;
|
|
16741
18120
|
var SWEEP_INTERVAL_MS2 = 60 * 6e4;
|
|
16742
18121
|
var ARCHIVE_BATCH = 64;
|
|
16743
18122
|
var AuditPruneSweeper = class {
|
|
@@ -16800,8 +18179,8 @@ var AuditPruneSweeper = class {
|
|
|
16800
18179
|
if (!this.config.enabled || this.sweeping) return 0;
|
|
16801
18180
|
this.sweeping = true;
|
|
16802
18181
|
try {
|
|
16803
|
-
if (!
|
|
16804
|
-
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) *
|
|
18182
|
+
if (!existsSync26(this.auditDir)) return 0;
|
|
18183
|
+
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS3;
|
|
16805
18184
|
let removed = 0;
|
|
16806
18185
|
for (const name of readdirSync8(this.auditDir)) {
|
|
16807
18186
|
const dateMs = auditFileDateMs(name);
|
|
@@ -16812,7 +18191,7 @@ var AuditPruneSweeper = class {
|
|
|
16812
18191
|
} else {
|
|
16813
18192
|
unlinkSync13(join22(this.auditDir, name));
|
|
16814
18193
|
const statsPath = join22(this.auditDir, auditStatsFileName(name));
|
|
16815
|
-
if (
|
|
18194
|
+
if (existsSync26(statsPath)) unlinkSync13(statsPath);
|
|
16816
18195
|
}
|
|
16817
18196
|
removed += 1;
|
|
16818
18197
|
} catch (error) {
|
|
@@ -16842,7 +18221,7 @@ var AuditPruneSweeper = class {
|
|
|
16842
18221
|
if (!this.config.enabled || this.archiving) return 0;
|
|
16843
18222
|
this.archiving = true;
|
|
16844
18223
|
try {
|
|
16845
|
-
if (!
|
|
18224
|
+
if (!existsSync26(this.auditDir)) return 0;
|
|
16846
18225
|
const today = this.todayMidnight();
|
|
16847
18226
|
let compressed = 0;
|
|
16848
18227
|
for (const name of readdirSync8(this.auditDir)) {
|
|
@@ -16896,7 +18275,7 @@ var AuditPruneSweeper = class {
|
|
|
16896
18275
|
const source = join22(bodiesPath, shard);
|
|
16897
18276
|
const target = `${source}.gz`;
|
|
16898
18277
|
try {
|
|
16899
|
-
if (
|
|
18278
|
+
if (existsSync26(target)) {
|
|
16900
18279
|
unlinkSync13(source);
|
|
16901
18280
|
continue;
|
|
16902
18281
|
}
|
|
@@ -16905,7 +18284,7 @@ var AuditPruneSweeper = class {
|
|
|
16905
18284
|
compressed += 1;
|
|
16906
18285
|
} catch (error) {
|
|
16907
18286
|
try {
|
|
16908
|
-
if (
|
|
18287
|
+
if (existsSync26(target)) unlinkSync13(target);
|
|
16909
18288
|
} catch {
|
|
16910
18289
|
}
|
|
16911
18290
|
this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
|
|
@@ -17058,7 +18437,7 @@ async function closeAll(writers) {
|
|
|
17058
18437
|
// src/usage/UsagePruneSweeper.ts
|
|
17059
18438
|
import { unlink as unlink3 } from "fs/promises";
|
|
17060
18439
|
import { join as join24 } from "path";
|
|
17061
|
-
var
|
|
18440
|
+
var DAY_MS4 = 24 * 60 * 6e4;
|
|
17062
18441
|
var SWEEP_INTERVAL_MS3 = 60 * 6e4;
|
|
17063
18442
|
var DEFAULT_USAGE_RETENTION_DAYS = 90;
|
|
17064
18443
|
var UsagePruneSweeper = class {
|
|
@@ -17115,7 +18494,7 @@ var UsagePruneSweeper = class {
|
|
|
17115
18494
|
this.sweeping = true;
|
|
17116
18495
|
try {
|
|
17117
18496
|
const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
|
|
17118
|
-
const cutoff = this.todayMidnight() - (retentionDays - 1) *
|
|
18497
|
+
const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
|
|
17119
18498
|
let removed = 0;
|
|
17120
18499
|
for (const entry of await listUsageDays(this.usageDir)) {
|
|
17121
18500
|
if (!entry.hasShard) continue;
|
|
@@ -17173,7 +18552,7 @@ var UsagePruneSweeper = class {
|
|
|
17173
18552
|
};
|
|
17174
18553
|
|
|
17175
18554
|
// src/audit/auditReader.ts
|
|
17176
|
-
import { existsSync as
|
|
18555
|
+
import { existsSync as existsSync27, readdirSync as readdirSync9 } from "fs";
|
|
17177
18556
|
import { join as join25 } from "path";
|
|
17178
18557
|
var DEFAULT_LIMIT = 200;
|
|
17179
18558
|
var MAX_LIMIT = 2e3;
|
|
@@ -17191,7 +18570,7 @@ function daySources(auditDir) {
|
|
|
17191
18570
|
if (dateMs === null) continue;
|
|
17192
18571
|
if (AUDIT_DAY_DIR_RE.test(name)) {
|
|
17193
18572
|
const path2 = join25(auditDir, name, AUDIT_META_FILE);
|
|
17194
|
-
if (
|
|
18573
|
+
if (existsSync27(path2)) sources.push({ path: path2, dateMs });
|
|
17195
18574
|
} else if (AUDIT_FILE_RE.test(name)) {
|
|
17196
18575
|
sources.push({ path: join25(auditDir, name), dateMs });
|
|
17197
18576
|
}
|
|
@@ -17209,7 +18588,7 @@ function toMetaRecord(record) {
|
|
|
17209
18588
|
return { ...meta, hasBody: true };
|
|
17210
18589
|
}
|
|
17211
18590
|
function readAuditRecords(auditDir, query2 = {}) {
|
|
17212
|
-
if (!
|
|
18591
|
+
if (!existsSync27(auditDir)) return [];
|
|
17213
18592
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
17214
18593
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
17215
18594
|
const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
|
|
@@ -17237,7 +18616,7 @@ function readAuditRecords(auditDir, query2 = {}) {
|
|
|
17237
18616
|
}
|
|
17238
18617
|
|
|
17239
18618
|
// src/audit/AuditWriter.ts
|
|
17240
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
18619
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync28, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
|
|
17241
18620
|
import { join as join26 } from "path";
|
|
17242
18621
|
var AuditWriter = class {
|
|
17243
18622
|
constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
|
|
@@ -17295,7 +18674,7 @@ var AuditWriter = class {
|
|
|
17295
18674
|
const { requestBody: _req, responseBody: _res, ...meta } = record;
|
|
17296
18675
|
const file = join26(dayPath, AUDIT_META_FILE);
|
|
17297
18676
|
const line = JSON.stringify(meta) + "\n";
|
|
17298
|
-
const bytesBefore =
|
|
18677
|
+
const bytesBefore = existsSync28(file) ? statSync8(file).size : 0;
|
|
17299
18678
|
appendFileSync2(file, line, "utf8");
|
|
17300
18679
|
try {
|
|
17301
18680
|
updateAuditStatsAfterAppend(
|
|
@@ -17343,7 +18722,7 @@ var AuditWriter = class {
|
|
|
17343
18722
|
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync8 } from "fs";
|
|
17344
18723
|
import { createHmac as createHmac5 } from "crypto";
|
|
17345
18724
|
import { join as join27 } from "path";
|
|
17346
|
-
import { fetchUpstream as
|
|
18725
|
+
import { fetchUpstream as fetchUpstream9 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
17347
18726
|
|
|
17348
18727
|
// src/billing/billingFiles.ts
|
|
17349
18728
|
var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
|
|
@@ -17366,7 +18745,7 @@ var BillingPublisher = class {
|
|
|
17366
18745
|
constructor(billingDir, logger, opts = {}) {
|
|
17367
18746
|
this.billingDir = billingDir;
|
|
17368
18747
|
this.logger = logger;
|
|
17369
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) =>
|
|
18748
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream9(url, init));
|
|
17370
18749
|
this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
|
|
17371
18750
|
this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
|
|
17372
18751
|
this.now = opts.now ?? Date.now;
|
|
@@ -17479,11 +18858,11 @@ var BillingPublisher = class {
|
|
|
17479
18858
|
};
|
|
17480
18859
|
|
|
17481
18860
|
// src/billing/billingReader.ts
|
|
17482
|
-
import { existsSync as
|
|
18861
|
+
import { existsSync as existsSync29, readdirSync as readdirSync10, readFileSync as readFileSync22 } from "fs";
|
|
17483
18862
|
import { join as join28 } from "path";
|
|
17484
18863
|
function readBillingLedger(billingDir) {
|
|
17485
18864
|
const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
|
|
17486
|
-
if (!
|
|
18865
|
+
if (!existsSync29(billingDir)) return view;
|
|
17487
18866
|
let files;
|
|
17488
18867
|
try {
|
|
17489
18868
|
files = readdirSync10(billingDir);
|
|
@@ -17616,7 +18995,7 @@ var BillingRetrySweeper = class {
|
|
|
17616
18995
|
// src/TokenRefreshScheduler.ts
|
|
17617
18996
|
var REFRESH_LEAD_MS2 = 5 * 6e4;
|
|
17618
18997
|
var SWEEP_INTERVAL_MS5 = 6e4;
|
|
17619
|
-
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
|
|
18998
|
+
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
|
|
17620
18999
|
var TokenRefreshScheduler = class {
|
|
17621
19000
|
constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
|
|
17622
19001
|
this.store = store;
|
|
@@ -17699,6 +19078,8 @@ var TokenRefreshScheduler = class {
|
|
|
17699
19078
|
return this.store.refreshCodexToken();
|
|
17700
19079
|
case "gemini":
|
|
17701
19080
|
return this.store.refreshGeminiToken();
|
|
19081
|
+
case "kimi":
|
|
19082
|
+
return this.store.refreshKimiToken();
|
|
17702
19083
|
}
|
|
17703
19084
|
}
|
|
17704
19085
|
};
|
|
@@ -17777,7 +19158,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
|
|
|
17777
19158
|
|
|
17778
19159
|
// src/webhook/WebhookDispatcher.ts
|
|
17779
19160
|
import { createHmac as createHmac6 } from "crypto";
|
|
17780
|
-
import { fetchUpstream as
|
|
19161
|
+
import { fetchUpstream as fetchUpstream10 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
17781
19162
|
var WEBHOOK_MAX_ATTEMPTS = 3;
|
|
17782
19163
|
var WEBHOOK_QUEUE_MAX = 1e3;
|
|
17783
19164
|
var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
|
|
@@ -17797,7 +19178,7 @@ var WebhookDispatcher = class {
|
|
|
17797
19178
|
sleep;
|
|
17798
19179
|
now;
|
|
17799
19180
|
constructor(opts = {}) {
|
|
17800
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) =>
|
|
19181
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream10(url, init));
|
|
17801
19182
|
this.logger = opts.logger;
|
|
17802
19183
|
this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
|
|
17803
19184
|
this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
|
|
@@ -17883,8 +19264,8 @@ var WebhookDispatcher = class {
|
|
|
17883
19264
|
signal: AbortSignal.timeout(this.timeoutMs)
|
|
17884
19265
|
});
|
|
17885
19266
|
return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
|
|
17886
|
-
} catch (
|
|
17887
|
-
return { ok: false, error:
|
|
19267
|
+
} catch (err6) {
|
|
19268
|
+
return { ok: false, error: err6 instanceof Error ? err6.message : String(err6) };
|
|
17888
19269
|
}
|
|
17889
19270
|
}
|
|
17890
19271
|
/**
|
|
@@ -18021,7 +19402,7 @@ function buildDaemon(config, paths) {
|
|
|
18021
19402
|
setSecretBox(secretBox3);
|
|
18022
19403
|
setSecretBox2(secretBox3);
|
|
18023
19404
|
const decryptedConfig = decryptConfigSecrets(config, secretBox3);
|
|
18024
|
-
const accountAllowanceStore = new
|
|
19405
|
+
const accountAllowanceStore = new AccountAllowanceStore6(
|
|
18025
19406
|
Date.now,
|
|
18026
19407
|
void 0,
|
|
18027
19408
|
new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
|
|
@@ -18066,6 +19447,7 @@ function buildDaemon(config, paths) {
|
|
|
18066
19447
|
);
|
|
18067
19448
|
setGeminiCodeAssistResolver(getGeminiCodeAssistProjectResolver());
|
|
18068
19449
|
const autoDisableStore = new AutoDisableStore();
|
|
19450
|
+
const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
|
|
18069
19451
|
const apiKeyPool = new ApiKeyPoolService(
|
|
18070
19452
|
createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
|
|
18071
19453
|
resolveEnvKey,
|
|
@@ -18082,7 +19464,7 @@ function buildDaemon(config, paths) {
|
|
|
18082
19464
|
const pricingEngine = new PricingEngine(pricingStore, logger, {
|
|
18083
19465
|
// Catalog egress follows the same global/env proxy policy as every other
|
|
18084
19466
|
// daemon upstream call; no provider/account override applies here.
|
|
18085
|
-
fetchImpl: ((input, init) =>
|
|
19467
|
+
fetchImpl: ((input, init) => fetchUpstream11(String(input), init ?? {}))
|
|
18086
19468
|
});
|
|
18087
19469
|
const pricingRefreshScheduler = new PricingRefreshScheduler(
|
|
18088
19470
|
pricingEngine,
|
|
@@ -18346,6 +19728,11 @@ function buildDaemon(config, paths) {
|
|
|
18346
19728
|
// values themselves NEVER leave (masked via `maskProviderApiKey`).
|
|
18347
19729
|
apiKeyPool,
|
|
18348
19730
|
autoDisableStore,
|
|
19731
|
+
// BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
|
|
19732
|
+
// read-through cached same-key usage probe surfaced on the keys view. The
|
|
19733
|
+
// key plaintext is resolved + decrypted inside the service and never
|
|
19734
|
+
// crosses back out.
|
|
19735
|
+
providerKeyQuota: providerKeyQuotaService,
|
|
18349
19736
|
// Interactive OAuth login over admin HTTP (app-parity child 4, design
|
|
18350
19737
|
// D1/D2-a). The in-memory pending-session store (NEVER serialized), the
|
|
18351
19738
|
// injected token-exchange fetch (global `fetch` here; mocked in tests), and a
|
|
@@ -18362,7 +19749,7 @@ function buildDaemon(config, paths) {
|
|
|
18362
19749
|
// — `server.proxy.byProvider[...]` was silently skipped — and the call was
|
|
18363
19750
|
// excluded from the upstream trace, so a failing login left no evidence.
|
|
18364
19751
|
// `redactBodies` keeps the code/verifier + minted token out of that trace.
|
|
18365
|
-
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) =>
|
|
19752
|
+
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream11(url, init, { providerId, redactBodies: true }),
|
|
18366
19753
|
subscriptionAccountAppender: credentialStore,
|
|
18367
19754
|
// Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
|
|
18368
19755
|
// + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
|
|
@@ -18370,6 +19757,10 @@ function buildDaemon(config, paths) {
|
|
|
18370
19757
|
// can inject a mock so no real port is bound.
|
|
18371
19758
|
codexSessions: new CodexOAuthSessionStore(),
|
|
18372
19759
|
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
|
|
19760
|
+
// Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
|
|
19761
|
+
// paste; the app shows the verification URL + user code and polls the
|
|
19762
|
+
// token-free status). Token captured + persisted daemon-side.
|
|
19763
|
+
kimiSessions: new CodexOAuthSessionStore(),
|
|
18373
19764
|
// Migration pack (app-parity child 6, design D2/D3) — the concrete credential
|
|
18374
19765
|
// store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
|
|
18375
19766
|
// the multi-account append (`appendProviderAccount`, import re-encrypts at-
|
|
@@ -18428,7 +19819,7 @@ function buildDaemon(config, paths) {
|
|
|
18428
19819
|
});
|
|
18429
19820
|
const webhookDispatcher = new WebhookDispatcher({
|
|
18430
19821
|
logger,
|
|
18431
|
-
fetchImpl: (url, init) =>
|
|
19822
|
+
fetchImpl: (url, init) => fetchUpstream11(url, init)
|
|
18432
19823
|
});
|
|
18433
19824
|
setWebhookRuntime(webhookDispatcher, getSharedAccountHealth4());
|
|
18434
19825
|
const auditWriter = new AuditWriter(auditDir, logger);
|
|
@@ -18505,7 +19896,7 @@ function buildDaemon(config, paths) {
|
|
|
18505
19896
|
}
|
|
18506
19897
|
function isTokensStoreReadable(tokensPath) {
|
|
18507
19898
|
try {
|
|
18508
|
-
if (!
|
|
19899
|
+
if (!existsSync30(tokensPath)) return true;
|
|
18509
19900
|
accessSync(tokensPath, fsConstants.R_OK);
|
|
18510
19901
|
return true;
|
|
18511
19902
|
} catch {
|
|
@@ -18742,11 +20133,11 @@ async function runLiveProbe(url, key, fetchImpl = fetch) {
|
|
|
18742
20133
|
status: res.status,
|
|
18743
20134
|
estimateHeader: res.headers.get("x-omnicross-count-estimate")
|
|
18744
20135
|
};
|
|
18745
|
-
} catch (
|
|
20136
|
+
} catch (err6) {
|
|
18746
20137
|
return {
|
|
18747
20138
|
status: null,
|
|
18748
20139
|
estimateHeader: null,
|
|
18749
|
-
error:
|
|
20140
|
+
error: err6 instanceof Error ? err6.message : String(err6)
|
|
18750
20141
|
};
|
|
18751
20142
|
}
|
|
18752
20143
|
}
|
|
@@ -19067,7 +20458,7 @@ async function keysRevoke(db, id) {
|
|
|
19067
20458
|
// src/commands/launch.ts
|
|
19068
20459
|
import { spawn as spawn2 } from "child_process";
|
|
19069
20460
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
19070
|
-
import { existsSync as
|
|
20461
|
+
import { existsSync as existsSync31 } from "fs";
|
|
19071
20462
|
import { delimiter as delimiter2, join as join29 } from "path";
|
|
19072
20463
|
import { parseArgs as parseArgs6 } from "util";
|
|
19073
20464
|
import {
|
|
@@ -19115,7 +20506,7 @@ function resolveInPathDefault(candidate) {
|
|
|
19115
20506
|
const segments = (process.env["PATH"] ?? "").split(delimiter2).filter(Boolean);
|
|
19116
20507
|
for (const seg of segments) {
|
|
19117
20508
|
const full = join29(seg, candidate);
|
|
19118
|
-
if (
|
|
20509
|
+
if (existsSync31(full)) return full;
|
|
19119
20510
|
}
|
|
19120
20511
|
return null;
|
|
19121
20512
|
}
|
|
@@ -19156,9 +20547,9 @@ async function runLaunch(argv, deps) {
|
|
|
19156
20547
|
await daemon.llmConfig.ready();
|
|
19157
20548
|
await daemon.migrateUsageStore();
|
|
19158
20549
|
await daemon.providerProxy.start();
|
|
19159
|
-
} catch (
|
|
20550
|
+
} catch (err6) {
|
|
19160
20551
|
await shutdownLaunchDaemon(daemon);
|
|
19161
|
-
throw
|
|
20552
|
+
throw err6;
|
|
19162
20553
|
}
|
|
19163
20554
|
let launch;
|
|
19164
20555
|
try {
|
|
@@ -19166,9 +20557,9 @@ async function runLaunch(argv, deps) {
|
|
|
19166
20557
|
providerId: values.provider,
|
|
19167
20558
|
model: values.model
|
|
19168
20559
|
});
|
|
19169
|
-
} catch (
|
|
20560
|
+
} catch (err6) {
|
|
19170
20561
|
await shutdownLaunchDaemon(daemon);
|
|
19171
|
-
throw
|
|
20562
|
+
throw err6;
|
|
19172
20563
|
}
|
|
19173
20564
|
try {
|
|
19174
20565
|
const plan = buildCliSpawnPlan({
|
|
@@ -19273,9 +20664,9 @@ function spawnCliInherit(plan) {
|
|
|
19273
20664
|
process.removeListener("SIGINT", onSignal);
|
|
19274
20665
|
process.removeListener("SIGTERM", onSignal);
|
|
19275
20666
|
};
|
|
19276
|
-
child.on("error", (
|
|
20667
|
+
child.on("error", (err6) => {
|
|
19277
20668
|
detach();
|
|
19278
|
-
if (
|
|
20669
|
+
if (err6.code === "ENOENT") {
|
|
19279
20670
|
reject(
|
|
19280
20671
|
new Error(
|
|
19281
20672
|
`launch: "${plan.command}" not found on PATH \u2014 install the CLI first.`
|
|
@@ -19283,7 +20674,7 @@ function spawnCliInherit(plan) {
|
|
|
19283
20674
|
);
|
|
19284
20675
|
return;
|
|
19285
20676
|
}
|
|
19286
|
-
reject(
|
|
20677
|
+
reject(err6);
|
|
19287
20678
|
});
|
|
19288
20679
|
child.on("exit", (code, signal) => {
|
|
19289
20680
|
detach();
|
|
@@ -19296,9 +20687,14 @@ function spawnCliInherit(plan) {
|
|
|
19296
20687
|
import { spawn as spawn3 } from "child_process";
|
|
19297
20688
|
import { createInterface as createInterface2 } from "readline";
|
|
19298
20689
|
import { parseArgs as parseArgs7 } from "util";
|
|
19299
|
-
import { fetchUpstream as
|
|
19300
|
-
import {
|
|
19301
|
-
|
|
20690
|
+
import { fetchUpstream as fetchUpstream12, setUpstreamProxyResolver as setUpstreamProxyResolver2 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
20691
|
+
import {
|
|
20692
|
+
claudeOAuth as claudeOAuth3,
|
|
20693
|
+
codexOAuth as codexOAuth3,
|
|
20694
|
+
geminiOAuth as geminiOAuth3,
|
|
20695
|
+
kimiOAuth as kimiOAuth3
|
|
20696
|
+
} from "@omnicross/subscriptions";
|
|
20697
|
+
var PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
|
|
19302
20698
|
async function runLogin(argv, deps) {
|
|
19303
20699
|
const { values, positionals } = parseArgs7({
|
|
19304
20700
|
args: argv,
|
|
@@ -19324,14 +20720,16 @@ async function runLogin(argv, deps) {
|
|
|
19324
20720
|
openBrowser: deps?.openBrowser ?? openBrowser,
|
|
19325
20721
|
promptPaste: deps?.promptPaste ?? promptPaste,
|
|
19326
20722
|
awaitLoopback: deps?.awaitLoopback ?? ((state) => awaitLoopbackCode(state)),
|
|
20723
|
+
awaitKimiDevice: deps?.awaitKimiDevice ?? ((fetchImpl) => runKimiDeviceFlow(fetchImpl, resolvedOpenBrowser)),
|
|
19327
20724
|
tokensFetch: deps?.tokensFetch
|
|
19328
20725
|
};
|
|
20726
|
+
const resolvedOpenBrowser = resolved.openBrowser;
|
|
19329
20727
|
const box = resolveSecretBox(values["master-key-file"]);
|
|
19330
20728
|
setSecretBox(box);
|
|
19331
20729
|
setUpstreamProxyResolver2(createUpstreamProxyResolver());
|
|
19332
20730
|
try {
|
|
19333
20731
|
const tokensPath = defaultTokensPath(values.config);
|
|
19334
|
-
const exchangeFetch = resolved.tokensFetch ?? ((url, init) =>
|
|
20732
|
+
const exchangeFetch = resolved.tokensFetch ?? ((url, init) => fetchUpstream12(url, init, { providerId: provider, redactBodies: true }));
|
|
19335
20733
|
const store = new JsonSubscriptionCredentialStore(tokensPath, box, exchangeFetch);
|
|
19336
20734
|
const expiresAt = await runProviderLogin(
|
|
19337
20735
|
provider,
|
|
@@ -19350,6 +20748,7 @@ async function runLogin(argv, deps) {
|
|
|
19350
20748
|
async function runProviderLogin(provider, store, deps, exchangeFetch, label) {
|
|
19351
20749
|
if (provider === "codex") return loginCodex(store, deps, exchangeFetch, label);
|
|
19352
20750
|
if (provider === "claude") return loginClaude(store, deps, exchangeFetch, label);
|
|
20751
|
+
if (provider === "kimi") return loginKimi(store, deps, exchangeFetch, label);
|
|
19353
20752
|
return loginGemini(store, deps, exchangeFetch, label);
|
|
19354
20753
|
}
|
|
19355
20754
|
async function loginCodex(store, deps, exchangeFetch, label) {
|
|
@@ -19420,6 +20819,45 @@ async function loginGemini(store, deps, exchangeFetch, label) {
|
|
|
19420
20819
|
logMasked("gemini", result.accessToken);
|
|
19421
20820
|
return expiresAt;
|
|
19422
20821
|
}
|
|
20822
|
+
async function runKimiDeviceFlow(exchangeFetch, openBrowserFn) {
|
|
20823
|
+
const deviceId = kimiOAuth3.generateKimiDeviceId();
|
|
20824
|
+
const fingerprint = kimiOAuth3.kimiFingerprintHeaders(deviceId);
|
|
20825
|
+
const authorization = await kimiOAuth3.requestDeviceAuthorization(exchangeFetch, fingerprint);
|
|
20826
|
+
const url = authorization.verificationUriComplete ?? authorization.verificationUri;
|
|
20827
|
+
console.info("Open this URL in your browser and approve the request:");
|
|
20828
|
+
console.info(` ${url}`);
|
|
20829
|
+
if (!authorization.verificationUriComplete) {
|
|
20830
|
+
console.info(` Then enter this code: ${authorization.userCode}`);
|
|
20831
|
+
}
|
|
20832
|
+
await openBrowserFn(url).catch(() => false);
|
|
20833
|
+
const result = await kimiOAuth3.awaitDeviceToken(authorization, exchangeFetch, {
|
|
20834
|
+
fingerprint,
|
|
20835
|
+
onPending: () => process.stdout.write(".")
|
|
20836
|
+
});
|
|
20837
|
+
console.info("");
|
|
20838
|
+
return {
|
|
20839
|
+
...result,
|
|
20840
|
+
accountId: kimiOAuth3.kimiAccountIdFromAccessToken(result.accessToken),
|
|
20841
|
+
deviceId
|
|
20842
|
+
};
|
|
20843
|
+
}
|
|
20844
|
+
async function loginKimi(store, deps, exchangeFetch, label) {
|
|
20845
|
+
const result = await deps.awaitKimiDevice(exchangeFetch);
|
|
20846
|
+
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
20847
|
+
const block = {
|
|
20848
|
+
authMethod: "oauth",
|
|
20849
|
+
status: "authorized",
|
|
20850
|
+
accessToken: result.accessToken,
|
|
20851
|
+
refreshToken: result.refreshToken,
|
|
20852
|
+
expiresAt,
|
|
20853
|
+
...result.accountId ? { accountId: result.accountId } : {},
|
|
20854
|
+
deviceId: result.deviceId,
|
|
20855
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
20856
|
+
};
|
|
20857
|
+
await store.appendProviderAccount("kimi", block, label);
|
|
20858
|
+
logMasked("kimi", result.accessToken);
|
|
20859
|
+
return expiresAt;
|
|
20860
|
+
}
|
|
19423
20861
|
function isLoginProvider(value) {
|
|
19424
20862
|
return PROVIDERS2.includes(value);
|
|
19425
20863
|
}
|
|
@@ -19617,7 +21055,7 @@ function providersRmKey(configPath, providerId, keyId) {
|
|
|
19617
21055
|
}
|
|
19618
21056
|
|
|
19619
21057
|
// src/commands/secrets.ts
|
|
19620
|
-
import { existsSync as
|
|
21058
|
+
import { existsSync as existsSync32, readFileSync as readFileSync24 } from "fs";
|
|
19621
21059
|
import { parseArgs as parseArgs9 } from "util";
|
|
19622
21060
|
async function runSecrets(argv) {
|
|
19623
21061
|
const { values, positionals } = parseArgs9({
|
|
@@ -19690,12 +21128,12 @@ function secretsStatus(args) {
|
|
|
19690
21128
|
reportField("admin.token", cfg.admin.token);
|
|
19691
21129
|
}
|
|
19692
21130
|
const tokensPath = defaultTokensPath(args.config);
|
|
19693
|
-
if (
|
|
21131
|
+
if (existsSync32(tokensPath)) {
|
|
19694
21132
|
console.info(`Secret status for ${tokensPath}:`);
|
|
19695
21133
|
reportTokenFields(tokensPath);
|
|
19696
21134
|
}
|
|
19697
21135
|
const integrationsPath = defaultIntegrationsPath(args.config);
|
|
19698
|
-
if (
|
|
21136
|
+
if (existsSync32(integrationsPath)) {
|
|
19699
21137
|
const state = readRawJson(integrationsPath);
|
|
19700
21138
|
const key = state.gatewayKey;
|
|
19701
21139
|
if (key && typeof key === "object" && !Array.isArray(key)) {
|
|
@@ -19749,8 +21187,8 @@ async function secretsRotate(args) {
|
|
|
19749
21187
|
const integrationsPath = defaultIntegrationsPath(args.config);
|
|
19750
21188
|
try {
|
|
19751
21189
|
cfg = loadConfig(args.config);
|
|
19752
|
-
if (
|
|
19753
|
-
if (
|
|
21190
|
+
if (existsSync32(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
|
|
21191
|
+
if (existsSync32(integrationsPath)) {
|
|
19754
21192
|
integrationsPlain = new IntegrationStateStore(integrationsPath, oldBox).load();
|
|
19755
21193
|
}
|
|
19756
21194
|
} finally {
|
|
@@ -19785,13 +21223,13 @@ function secretsDecrypt(args) {
|
|
|
19785
21223
|
let tokensPlain = null;
|
|
19786
21224
|
try {
|
|
19787
21225
|
cfg = loadConfig(args.config);
|
|
19788
|
-
if (
|
|
21226
|
+
if (existsSync32(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
|
|
19789
21227
|
} finally {
|
|
19790
21228
|
setSecretBox(null);
|
|
19791
21229
|
}
|
|
19792
21230
|
saveConfig(args.config, cfg);
|
|
19793
21231
|
if (tokensPlain) {
|
|
19794
|
-
|
|
21232
|
+
atomicReplaceUtf8(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n");
|
|
19795
21233
|
}
|
|
19796
21234
|
console.info(`Decrypted secrets to plaintext in ${args.config}` + tokensSuffix(args.config));
|
|
19797
21235
|
}
|
|
@@ -19816,13 +21254,13 @@ function readRawJson(path2) {
|
|
|
19816
21254
|
}
|
|
19817
21255
|
function encryptTokensFileInPlace(configPath, box) {
|
|
19818
21256
|
const tokensPath = defaultTokensPath(configPath);
|
|
19819
|
-
if (!
|
|
21257
|
+
if (!existsSync32(tokensPath)) return;
|
|
19820
21258
|
const plain = decryptTokensFile(tokensPath, box);
|
|
19821
21259
|
writeTokensEncrypted(tokensPath, plain, box);
|
|
19822
21260
|
}
|
|
19823
21261
|
function rewriteIntegrationState(configPath, readBox, writeBox) {
|
|
19824
21262
|
const path2 = defaultIntegrationsPath(configPath);
|
|
19825
|
-
if (!
|
|
21263
|
+
if (!existsSync32(path2)) return;
|
|
19826
21264
|
const state = new IntegrationStateStore(path2, readBox).load();
|
|
19827
21265
|
new IntegrationStateStore(path2, writeBox).save(state);
|
|
19828
21266
|
}
|
|
@@ -19835,7 +21273,7 @@ function writeTokensEncrypted(tokensPath, plain, box) {
|
|
|
19835
21273
|
{ updatedAt: "", ...plain },
|
|
19836
21274
|
box
|
|
19837
21275
|
);
|
|
19838
|
-
|
|
21276
|
+
atomicReplaceUtf8(tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
|
|
19839
21277
|
}
|
|
19840
21278
|
var TOKEN_FIELDS2 = {
|
|
19841
21279
|
claude: ["accessToken", "refreshToken"],
|
|
@@ -19858,7 +21296,7 @@ function walkTokens(raw, fn) {
|
|
|
19858
21296
|
return next;
|
|
19859
21297
|
}
|
|
19860
21298
|
function tokensSuffix(configPath) {
|
|
19861
|
-
return
|
|
21299
|
+
return existsSync32(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
|
|
19862
21300
|
}
|
|
19863
21301
|
|
|
19864
21302
|
// src/commands/start.ts
|
|
@@ -20125,7 +21563,7 @@ async function main() {
|
|
|
20125
21563
|
process.exitCode = 1;
|
|
20126
21564
|
}
|
|
20127
21565
|
}
|
|
20128
|
-
main().catch((
|
|
20129
|
-
console.error(
|
|
21566
|
+
main().catch((err6) => {
|
|
21567
|
+
console.error(err6 instanceof Error ? err6.message : String(err6));
|
|
20130
21568
|
process.exitCode = 1;
|
|
20131
21569
|
});
|