@omnicross/daemon 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +1707 -303
- package/dist/cli.js +1644 -229
- package/dist/index.cjs +1655 -293
- package/dist/index.d.cts +334 -28
- package/dist/index.d.ts +334 -28
- package/dist/index.js +1598 -230
- package/package.json +6 -6
package/dist/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
|
-
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
|
-
]);
|
|
2276
|
+
const config = await this.credentials.getFullConfig();
|
|
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
2325
|
return this.claudeCollector.collectMany(accounts, { force: true });
|
|
1598
2326
|
}
|
|
1599
2327
|
/**
|
|
1600
|
-
*
|
|
1601
|
-
*
|
|
1602
|
-
*
|
|
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 });
|
|
2357
|
+
}
|
|
2358
|
+
/**
|
|
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?.();
|
|
@@ -5028,7 +5804,7 @@ async function handleSearchQuery(req, res, deps) {
|
|
|
5028
5804
|
// src/admin/searchAdminView.ts
|
|
5029
5805
|
var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
|
|
5030
5806
|
var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
|
|
5031
|
-
function
|
|
5807
|
+
function isRecord2(value) {
|
|
5032
5808
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5033
5809
|
}
|
|
5034
5810
|
function redactSearchServerConfig(search) {
|
|
@@ -5078,13 +5854,13 @@ function resolveSecretField(entry, field, stored) {
|
|
|
5078
5854
|
else delete entry[field];
|
|
5079
5855
|
}
|
|
5080
5856
|
function preserveSearchSecrets(incoming, current) {
|
|
5081
|
-
if (!
|
|
5857
|
+
if (!isRecord2(incoming)) return incoming;
|
|
5082
5858
|
const section = { ...incoming };
|
|
5083
5859
|
const providersValue = section["providers"];
|
|
5084
|
-
if (!
|
|
5860
|
+
if (!isRecord2(providersValue)) return section;
|
|
5085
5861
|
const providers = {};
|
|
5086
5862
|
for (const [id, entryValue] of Object.entries(providersValue)) {
|
|
5087
|
-
if (!
|
|
5863
|
+
if (!isRecord2(entryValue)) {
|
|
5088
5864
|
providers[id] = entryValue;
|
|
5089
5865
|
continue;
|
|
5090
5866
|
}
|
|
@@ -5162,7 +5938,7 @@ function parseKeyPolicyBody(body) {
|
|
|
5162
5938
|
var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
|
|
5163
5939
|
var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
|
|
5164
5940
|
var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
|
|
5165
|
-
function
|
|
5941
|
+
function isRecord3(value) {
|
|
5166
5942
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5167
5943
|
}
|
|
5168
5944
|
function nonBlank(value) {
|
|
@@ -5182,7 +5958,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
5182
5958
|
const ids = /* @__PURE__ */ new Set();
|
|
5183
5959
|
raw.forEach((entry, index) => {
|
|
5184
5960
|
const path2 = `bindings[${index}]`;
|
|
5185
|
-
if (!
|
|
5961
|
+
if (!isRecord3(entry)) {
|
|
5186
5962
|
errors.push(`${path2} must be an object`);
|
|
5187
5963
|
return;
|
|
5188
5964
|
}
|
|
@@ -5211,12 +5987,12 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
5211
5987
|
} else if (entry.modelMappings.length > 100) {
|
|
5212
5988
|
errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
|
|
5213
5989
|
} else if (entry.modelMappings.some(
|
|
5214
|
-
(mapping) => !
|
|
5990
|
+
(mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
|
|
5215
5991
|
)) {
|
|
5216
5992
|
errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
|
|
5217
5993
|
}
|
|
5218
5994
|
}
|
|
5219
|
-
if (!
|
|
5995
|
+
if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
|
|
5220
5996
|
errors.push(`${path2}.target is invalid`);
|
|
5221
5997
|
} else {
|
|
5222
5998
|
if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
|
|
@@ -5231,7 +6007,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
5231
6007
|
}
|
|
5232
6008
|
}
|
|
5233
6009
|
if (entry.modelMap !== void 0) {
|
|
5234
|
-
if (!
|
|
6010
|
+
if (!isRecord3(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
|
|
5235
6011
|
errors.push(`${path2}.modelMap must contain string values`);
|
|
5236
6012
|
}
|
|
5237
6013
|
}
|
|
@@ -5525,7 +6301,8 @@ var PROVIDER_KEYS = {
|
|
|
5525
6301
|
block: "opencodego",
|
|
5526
6302
|
accounts: "opencodegoAccounts",
|
|
5527
6303
|
active: "activeOpencodegoAccountId"
|
|
5528
|
-
}
|
|
6304
|
+
},
|
|
6305
|
+
kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
|
|
5529
6306
|
};
|
|
5530
6307
|
function clone(value) {
|
|
5531
6308
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -6047,7 +6824,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
|
|
|
6047
6824
|
}
|
|
6048
6825
|
|
|
6049
6826
|
// src/admin/adminMigration.ts
|
|
6050
|
-
function
|
|
6827
|
+
function err4(status, message) {
|
|
6051
6828
|
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
6052
6829
|
}
|
|
6053
6830
|
async function handleExport(body, deps) {
|
|
@@ -6057,30 +6834,30 @@ async function handleExport(body, deps) {
|
|
|
6057
6834
|
return { status: 200, body: { pack, version: BUNDLE_VERSION } };
|
|
6058
6835
|
} catch (error) {
|
|
6059
6836
|
if (error instanceof WeakPassphraseError) {
|
|
6060
|
-
return
|
|
6837
|
+
return err4(400, error.message);
|
|
6061
6838
|
}
|
|
6062
|
-
return
|
|
6839
|
+
return err4(500, "failed to build the migration pack");
|
|
6063
6840
|
}
|
|
6064
6841
|
}
|
|
6065
6842
|
async function handleImport(body, deps) {
|
|
6066
6843
|
const blob = typeof body["blob"] === "string" ? body["blob"] : "";
|
|
6067
6844
|
const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
|
|
6068
6845
|
const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
|
|
6069
|
-
if (!blob) return
|
|
6846
|
+
if (!blob) return err4(400, "import requires { blob }");
|
|
6070
6847
|
try {
|
|
6071
6848
|
const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
|
|
6072
6849
|
return { status: 200, body: counts };
|
|
6073
6850
|
} catch (error) {
|
|
6074
6851
|
if (error instanceof WeakPassphraseError) {
|
|
6075
|
-
return
|
|
6852
|
+
return err4(400, error.message);
|
|
6076
6853
|
}
|
|
6077
|
-
return
|
|
6854
|
+
return err4(400, error instanceof Error ? error.message : "import failed");
|
|
6078
6855
|
}
|
|
6079
6856
|
}
|
|
6080
6857
|
|
|
6081
6858
|
// src/admin/usagePricing.ts
|
|
6082
6859
|
import { getSharedUsageThroughputTracker } from "@omnicross/core/usage";
|
|
6083
|
-
var
|
|
6860
|
+
var err5 = (status, message) => ({
|
|
6084
6861
|
status,
|
|
6085
6862
|
body: { error: { type: "admin_api_error", message } }
|
|
6086
6863
|
});
|
|
@@ -6093,7 +6870,7 @@ function parseRange(query2) {
|
|
|
6093
6870
|
const startTs = parseFiniteInt(query2.get("startTs"));
|
|
6094
6871
|
const endTs = parseFiniteInt(query2.get("endTs"));
|
|
6095
6872
|
if (startTs === null || endTs === null) {
|
|
6096
|
-
return
|
|
6873
|
+
return err5(400, "startTs and endTs are required finite-integer unix-millis query params");
|
|
6097
6874
|
}
|
|
6098
6875
|
return { startTs, endTs };
|
|
6099
6876
|
}
|
|
@@ -6118,14 +6895,14 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
6118
6895
|
case "timeseries": {
|
|
6119
6896
|
const bucket = query2.get("bucket");
|
|
6120
6897
|
if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
|
|
6121
|
-
return
|
|
6898
|
+
return err5(400, "bucket must be one of 'hour', 'day', 'month'");
|
|
6122
6899
|
}
|
|
6123
6900
|
const now = Date.now();
|
|
6124
6901
|
const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
|
|
6125
6902
|
if (clamped.startTs < clamped.endTs) {
|
|
6126
6903
|
const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
|
|
6127
6904
|
if (projected > MAX_TIMESERIES_BUCKETS) {
|
|
6128
|
-
return
|
|
6905
|
+
return err5(
|
|
6129
6906
|
400,
|
|
6130
6907
|
`requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
|
|
6131
6908
|
);
|
|
@@ -6148,7 +6925,7 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
6148
6925
|
};
|
|
6149
6926
|
}
|
|
6150
6927
|
default:
|
|
6151
|
-
return
|
|
6928
|
+
return err5(404, `unknown usage view '${view ?? ""}'`);
|
|
6152
6929
|
}
|
|
6153
6930
|
}
|
|
6154
6931
|
function poolKeyLabels(cfg) {
|
|
@@ -6197,7 +6974,7 @@ async function handlePricingList(deps) {
|
|
|
6197
6974
|
async function handlePricingUpsert(body, deps) {
|
|
6198
6975
|
const input = parsePricingEntryInput(body);
|
|
6199
6976
|
if (!input) {
|
|
6200
|
-
return
|
|
6977
|
+
return err5(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
|
|
6201
6978
|
}
|
|
6202
6979
|
const entry = await deps.pricingEngine.upsertManual(input);
|
|
6203
6980
|
return { status: 200, body: { entry } };
|
|
@@ -6206,7 +6983,7 @@ async function handlePricingDelete(query2, deps) {
|
|
|
6206
6983
|
const providerId = query2.get("providerId")?.trim() ?? "";
|
|
6207
6984
|
const modelId = query2.get("modelId")?.trim() ?? "";
|
|
6208
6985
|
if (!providerId || !modelId) {
|
|
6209
|
-
return
|
|
6986
|
+
return err5(400, "delete requires providerId and modelId query params");
|
|
6210
6987
|
}
|
|
6211
6988
|
const deleted = await deps.pricingStore.delete(providerId, modelId);
|
|
6212
6989
|
if (deleted) await deps.pricingEngine.invalidateCache();
|
|
@@ -6226,13 +7003,13 @@ async function handlePricingFetchLatest(deps) {
|
|
|
6226
7003
|
}
|
|
6227
7004
|
};
|
|
6228
7005
|
} catch (e) {
|
|
6229
|
-
return
|
|
7006
|
+
return err5(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
6230
7007
|
}
|
|
6231
7008
|
}
|
|
6232
7009
|
async function handlePricingResolveConflicts(body, deps) {
|
|
6233
7010
|
const raw = body["resolutions"];
|
|
6234
7011
|
if (!Array.isArray(raw)) {
|
|
6235
|
-
return
|
|
7012
|
+
return err5(400, "resolve-conflicts requires { resolutions: [...] }");
|
|
6236
7013
|
}
|
|
6237
7014
|
const currentRows = await deps.pricingStore.getAll();
|
|
6238
7015
|
const userEditedKeys = new Set(
|
|
@@ -6242,21 +7019,21 @@ async function handlePricingResolveConflicts(body, deps) {
|
|
|
6242
7019
|
const pendingIncoming = /* @__PURE__ */ new Map();
|
|
6243
7020
|
let staleCount = 0;
|
|
6244
7021
|
for (const item of raw) {
|
|
6245
|
-
if (!item || typeof item !== "object") return
|
|
7022
|
+
if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
|
|
6246
7023
|
const r = item;
|
|
6247
7024
|
const action = r["action"];
|
|
6248
7025
|
if (action !== "overwrite" && action !== "skip") {
|
|
6249
|
-
return
|
|
7026
|
+
return err5(400, "resolution action must be 'overwrite' or 'skip'");
|
|
6250
7027
|
}
|
|
6251
7028
|
const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
|
|
6252
7029
|
const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
|
|
6253
7030
|
if (!providerId || !modelId) {
|
|
6254
|
-
return
|
|
7031
|
+
return err5(400, "each resolution requires top-level providerId and modelId");
|
|
6255
7032
|
}
|
|
6256
7033
|
const incoming = parsePricingEntryInput(r["incoming"]);
|
|
6257
|
-
if (!incoming) return
|
|
7034
|
+
if (!incoming) return err5(400, "each resolution must echo a valid incoming pricing entry");
|
|
6258
7035
|
if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
|
|
6259
|
-
return
|
|
7036
|
+
return err5(400, "resolution providerId/modelId must match the echoed incoming entry");
|
|
6260
7037
|
}
|
|
6261
7038
|
const key = `${providerId}::${modelId}`;
|
|
6262
7039
|
if (action === "overwrite" && !userEditedKeys.has(key)) {
|
|
@@ -6301,7 +7078,7 @@ function query(req) {
|
|
|
6301
7078
|
}
|
|
6302
7079
|
function allowanceProvider(value) {
|
|
6303
7080
|
if (!value) return void 0;
|
|
6304
|
-
return value === "claude" || value === "codex" ? value : null;
|
|
7081
|
+
return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
|
|
6305
7082
|
}
|
|
6306
7083
|
async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
6307
7084
|
if (!service) return writeError2(res, 501, "account allowance service is not available");
|
|
@@ -6315,7 +7092,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
6315
7092
|
const params = query(req);
|
|
6316
7093
|
const pathProvider = rest.length >= 2 ? rest[0] : null;
|
|
6317
7094
|
const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
|
|
6318
|
-
if (providerId === null)
|
|
7095
|
+
if (providerId === null) {
|
|
7096
|
+
return writeError2(res, 400, "providerId must be claude, codex, kimi, or opencodego");
|
|
7097
|
+
}
|
|
6319
7098
|
const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
|
|
6320
7099
|
const allowances = await service.list({ providerId, accountId });
|
|
6321
7100
|
return writeJson3(res, 200, { allowances });
|
|
@@ -6325,10 +7104,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
6325
7104
|
const requestedProvider = allowanceProvider(
|
|
6326
7105
|
typeof body["providerId"] === "string" ? body["providerId"] : "claude"
|
|
6327
7106
|
);
|
|
6328
|
-
if (requestedProvider !== "claude") {
|
|
6329
|
-
return writeError2(res, 400, "only Claude allowances support explicit refresh");
|
|
6330
|
-
}
|
|
6331
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
|
+
}
|
|
6332
7138
|
const allowances = await service.refreshClaude(accountId);
|
|
6333
7139
|
if (accountId && allowances.length === 0) {
|
|
6334
7140
|
return writeError2(res, 404, `Claude account '${accountId}' not found`);
|
|
@@ -6499,8 +7305,8 @@ async function handleAdminApi(req, res, path2, deps) {
|
|
|
6499
7305
|
default:
|
|
6500
7306
|
return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
|
|
6501
7307
|
}
|
|
6502
|
-
} catch (
|
|
6503
|
-
writeJsonError(res, 500,
|
|
7308
|
+
} catch (err6) {
|
|
7309
|
+
writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
|
|
6504
7310
|
}
|
|
6505
7311
|
}
|
|
6506
7312
|
function requestQuery(req) {
|
|
@@ -6570,6 +7376,9 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
6570
7376
|
if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
|
|
6571
7377
|
return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
6572
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
|
+
}
|
|
6573
7382
|
if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
|
|
6574
7383
|
return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
6575
7384
|
}
|
|
@@ -6668,7 +7477,7 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
6668
7477
|
try {
|
|
6669
7478
|
const headers = { Accept: "application/json" };
|
|
6670
7479
|
if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
|
|
6671
|
-
const response = await
|
|
7480
|
+
const response = await fetchUpstream5(url, { method: "GET", headers }, { providerId: "byo" });
|
|
6672
7481
|
if (!response.ok) {
|
|
6673
7482
|
const text = await response.text().catch(() => "");
|
|
6674
7483
|
let message = text.slice(0, 300);
|
|
@@ -6685,8 +7494,8 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
6685
7494
|
const data = await response.json();
|
|
6686
7495
|
const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
|
|
6687
7496
|
return writeJson4(res, 200, { models });
|
|
6688
|
-
} catch (
|
|
6689
|
-
const message =
|
|
7497
|
+
} catch (err6) {
|
|
7498
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
6690
7499
|
return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
|
|
6691
7500
|
}
|
|
6692
7501
|
}
|
|
@@ -6727,7 +7536,7 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
6727
7536
|
}
|
|
6728
7537
|
const startedAt = Date.now();
|
|
6729
7538
|
try {
|
|
6730
|
-
const response = await
|
|
7539
|
+
const response = await fetchUpstream5(
|
|
6731
7540
|
url,
|
|
6732
7541
|
{ method: "POST", headers, body: JSON.stringify(payload) },
|
|
6733
7542
|
{ providerId: "byo" }
|
|
@@ -6749,8 +7558,8 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
6749
7558
|
latencyMs,
|
|
6750
7559
|
sample: extractSampleText(text, row.apiFormat)
|
|
6751
7560
|
});
|
|
6752
|
-
} catch (
|
|
6753
|
-
const message =
|
|
7561
|
+
} catch (err6) {
|
|
7562
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
6754
7563
|
return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
|
|
6755
7564
|
}
|
|
6756
7565
|
}
|
|
@@ -6792,7 +7601,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
|
|
|
6792
7601
|
const row = cfg.providers.find((p) => p.id === id);
|
|
6793
7602
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6794
7603
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6795
|
-
|
|
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
|
+
}
|
|
6796
7628
|
}
|
|
6797
7629
|
function parsePoolKeyInput(body, existing) {
|
|
6798
7630
|
const out = {};
|
|
@@ -7537,12 +8369,12 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7537
8369
|
}
|
|
7538
8370
|
return writeJson4(res, 200, { ok: true, affected: result.affected });
|
|
7539
8371
|
}
|
|
7540
|
-
if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
|
|
7541
|
-
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);
|
|
7542
8374
|
return writeJson4(res, result.status, result.body);
|
|
7543
8375
|
}
|
|
7544
|
-
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
7545
|
-
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);
|
|
7546
8378
|
return writeJson4(res, result.status, result.body);
|
|
7547
8379
|
}
|
|
7548
8380
|
if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
|
|
@@ -7595,7 +8427,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7595
8427
|
return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
7596
8428
|
}
|
|
7597
8429
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
|
|
7598
|
-
|
|
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);
|
|
7599
8439
|
return writeJson4(res, result.status, result.body);
|
|
7600
8440
|
}
|
|
7601
8441
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
|
|
@@ -8089,12 +8929,12 @@ async function handlePlayground(req, res, method, deps) {
|
|
|
8089
8929
|
const payload = body["body"];
|
|
8090
8930
|
const status = deps.outboundApiServer.getStatus();
|
|
8091
8931
|
if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
|
|
8092
|
-
const path2 = resolvePlaygroundPath(endpoint,
|
|
8932
|
+
const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
|
|
8093
8933
|
if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
|
|
8094
8934
|
const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
8095
8935
|
await proxyToOutbound(res, status.port, path2, key, upstreamBody);
|
|
8096
8936
|
}
|
|
8097
|
-
function
|
|
8937
|
+
function isRecord4(v) {
|
|
8098
8938
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
8099
8939
|
}
|
|
8100
8940
|
function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
@@ -8123,8 +8963,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
|
8123
8963
|
});
|
|
8124
8964
|
}
|
|
8125
8965
|
);
|
|
8126
|
-
upstream.on("error", (
|
|
8127
|
-
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}`);
|
|
8128
8968
|
else res.end();
|
|
8129
8969
|
resolve11();
|
|
8130
8970
|
});
|
|
@@ -8229,7 +9069,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
8229
9069
|
}
|
|
8230
9070
|
|
|
8231
9071
|
// src/admin/version.ts
|
|
8232
|
-
var DAEMON_VERSION = true ? "0.3.
|
|
9072
|
+
var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
|
|
8233
9073
|
|
|
8234
9074
|
// src/admin/AdminServer.ts
|
|
8235
9075
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -8272,13 +9112,13 @@ var AdminServer = class {
|
|
|
8272
9112
|
const server = http2.createServer((req, res) => {
|
|
8273
9113
|
this.onRequest(req, res);
|
|
8274
9114
|
});
|
|
8275
|
-
const onError = (
|
|
8276
|
-
if (
|
|
9115
|
+
const onError = (err6) => {
|
|
9116
|
+
if (err6.code === "EADDRINUSE" && port !== 0) {
|
|
8277
9117
|
server.removeListener("error", onError);
|
|
8278
9118
|
this.listen(bindAddr, 0).then(resolve11, reject);
|
|
8279
9119
|
return;
|
|
8280
9120
|
}
|
|
8281
|
-
reject(
|
|
9121
|
+
reject(err6);
|
|
8282
9122
|
};
|
|
8283
9123
|
server.on("error", onError);
|
|
8284
9124
|
server.listen(port, bindAddr, () => {
|
|
@@ -8296,8 +9136,8 @@ var AdminServer = class {
|
|
|
8296
9136
|
}
|
|
8297
9137
|
/** Per-request handler: auth gate (when a token is set) → routing. */
|
|
8298
9138
|
onRequest(req, res) {
|
|
8299
|
-
void this.dispatch(req, res).catch((
|
|
8300
|
-
const message =
|
|
9139
|
+
void this.dispatch(req, res).catch((err6) => {
|
|
9140
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
8301
9141
|
this.deps.logger.error("[AdminServer] unhandled error:", message);
|
|
8302
9142
|
if (!res.headersSent) {
|
|
8303
9143
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -8561,18 +9401,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
|
|
|
8561
9401
|
return;
|
|
8562
9402
|
}
|
|
8563
9403
|
signal?.addEventListener("abort", abort, { once: true });
|
|
8564
|
-
server.on("error", (
|
|
9404
|
+
server.on("error", (err6) => {
|
|
8565
9405
|
if (settled) return;
|
|
8566
9406
|
settled = true;
|
|
8567
9407
|
clearTimeout(timer);
|
|
8568
|
-
if (
|
|
9408
|
+
if (err6.code === "EADDRINUSE") {
|
|
8569
9409
|
reject(
|
|
8570
9410
|
new Error(
|
|
8571
9411
|
`login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
|
|
8572
9412
|
)
|
|
8573
9413
|
);
|
|
8574
9414
|
} else {
|
|
8575
|
-
reject(
|
|
9415
|
+
reject(err6);
|
|
8576
9416
|
}
|
|
8577
9417
|
});
|
|
8578
9418
|
const timer = setTimeout(() => {
|
|
@@ -8647,6 +9487,411 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
|
|
|
8647
9487
|
};
|
|
8648
9488
|
}
|
|
8649
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
|
+
|
|
8650
9895
|
// src/image-generation/ImageDoctorService.ts
|
|
8651
9896
|
import {
|
|
8652
9897
|
normalizeImageGenerationError
|
|
@@ -14763,21 +16008,23 @@ function bucketLabel(bucketStartTs, bucket) {
|
|
|
14763
16008
|
}
|
|
14764
16009
|
|
|
14765
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
|
|
14766
16017
|
import { randomBytes as randomBytes11 } from "crypto";
|
|
14767
16018
|
import {
|
|
14768
16019
|
closeSync as closeSync8,
|
|
14769
16020
|
existsSync as existsSync18,
|
|
14770
16021
|
fsyncSync as fsyncSync7,
|
|
14771
16022
|
openSync as openSync8,
|
|
14772
|
-
readFileSync as readFileSync15,
|
|
14773
16023
|
renameSync as renameSync10,
|
|
14774
16024
|
unlinkSync as unlinkSync12,
|
|
14775
16025
|
writeFileSync as writeFileSync13
|
|
14776
16026
|
} from "fs";
|
|
14777
16027
|
import { basename as basename8, dirname as dirname14, join as join19 } from "path";
|
|
14778
|
-
import {
|
|
14779
|
-
validateOutboundPermissions as validateOutboundPermissions3
|
|
14780
|
-
} from "@omnicross/core";
|
|
14781
16028
|
function atomicReplaceUtf8(targetPath, contents) {
|
|
14782
16029
|
const tempPath = join19(
|
|
14783
16030
|
dirname14(targetPath),
|
|
@@ -14807,6 +16054,8 @@ function atomicReplaceUtf8(targetPath, contents) {
|
|
|
14807
16054
|
throw error;
|
|
14808
16055
|
}
|
|
14809
16056
|
}
|
|
16057
|
+
|
|
16058
|
+
// src/ports/JsonOutboundKeyDb.ts
|
|
14810
16059
|
var JsonOutboundKeyDb = class {
|
|
14811
16060
|
/**
|
|
14812
16061
|
* @param secretBox OPTIONAL reversible-secret codec. When present, a created
|
|
@@ -14949,7 +16198,7 @@ var JsonOutboundKeyDb = class {
|
|
|
14949
16198
|
}
|
|
14950
16199
|
/** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14951
16200
|
readRows() {
|
|
14952
|
-
if (!
|
|
16201
|
+
if (!existsSync19(this.keysPath)) return [];
|
|
14953
16202
|
try {
|
|
14954
16203
|
const parsed = JSON.parse(readFileSync15(this.keysPath, "utf8"));
|
|
14955
16204
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -14968,7 +16217,7 @@ function applyPolicyField(row, field, value) {
|
|
|
14968
16217
|
}
|
|
14969
16218
|
|
|
14970
16219
|
// src/ports/JsonPricingStore.ts
|
|
14971
|
-
import { existsSync as
|
|
16220
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16, renameSync as renameSync11, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "fs";
|
|
14972
16221
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
14973
16222
|
var JsonPricingStore = class {
|
|
14974
16223
|
constructor(pricingPath) {
|
|
@@ -14983,7 +16232,7 @@ var JsonPricingStore = class {
|
|
|
14983
16232
|
* otherwise unusable pricing table after a crash or manual file edit.
|
|
14984
16233
|
*/
|
|
14985
16234
|
hasUsableSnapshot() {
|
|
14986
|
-
if (!
|
|
16235
|
+
if (!existsSync20(this.pricingPath)) return false;
|
|
14987
16236
|
try {
|
|
14988
16237
|
const parsed = JSON.parse(readFileSync16(this.pricingPath, "utf8"));
|
|
14989
16238
|
return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
|
|
@@ -15098,7 +16347,7 @@ var JsonPricingStore = class {
|
|
|
15098
16347
|
}
|
|
15099
16348
|
/** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
|
|
15100
16349
|
readRows() {
|
|
15101
|
-
if (!
|
|
16350
|
+
if (!existsSync20(this.pricingPath)) return [];
|
|
15102
16351
|
try {
|
|
15103
16352
|
const parsed = JSON.parse(readFileSync16(this.pricingPath, "utf8"));
|
|
15104
16353
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -15130,7 +16379,7 @@ function isUsablePricingRow(value) {
|
|
|
15130
16379
|
}
|
|
15131
16380
|
|
|
15132
16381
|
// src/pricing/PricingRefreshScheduler.ts
|
|
15133
|
-
import { existsSync as
|
|
16382
|
+
import { existsSync as existsSync21, readFileSync as readFileSync17, renameSync as renameSync12, writeFileSync as writeFileSync15 } from "fs";
|
|
15134
16383
|
var EMPTY_STATE2 = {
|
|
15135
16384
|
lastAttemptAt: null,
|
|
15136
16385
|
lastSuccessAt: null,
|
|
@@ -15168,7 +16417,7 @@ var PricingRefreshScheduler = class {
|
|
|
15168
16417
|
this.timer = null;
|
|
15169
16418
|
}
|
|
15170
16419
|
getState() {
|
|
15171
|
-
if (!
|
|
16420
|
+
if (!existsSync21(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
|
|
15172
16421
|
try {
|
|
15173
16422
|
const value = JSON.parse(readFileSync17(this.statePath, "utf8"));
|
|
15174
16423
|
return {
|
|
@@ -15233,7 +16482,7 @@ function finiteOrNull(value) {
|
|
|
15233
16482
|
}
|
|
15234
16483
|
|
|
15235
16484
|
// src/ports/JsonVoucherDb.ts
|
|
15236
|
-
import { existsSync as
|
|
16485
|
+
import { existsSync as existsSync22, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "fs";
|
|
15237
16486
|
var JsonVoucherDb = class {
|
|
15238
16487
|
constructor(vouchersPath) {
|
|
15239
16488
|
this.vouchersPath = vouchersPath;
|
|
@@ -15311,7 +16560,7 @@ var JsonVoucherDb = class {
|
|
|
15311
16560
|
}
|
|
15312
16561
|
/** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
|
|
15313
16562
|
readRows() {
|
|
15314
|
-
if (!
|
|
16563
|
+
if (!existsSync22(this.vouchersPath)) return [];
|
|
15315
16564
|
try {
|
|
15316
16565
|
const parsed = JSON.parse(readFileSync18(this.vouchersPath, "utf8"));
|
|
15317
16566
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -15325,16 +16574,17 @@ var JsonVoucherDb = class {
|
|
|
15325
16574
|
};
|
|
15326
16575
|
|
|
15327
16576
|
// src/ports/JsonSubscriptionCredentialStore.ts
|
|
15328
|
-
import { existsSync as
|
|
16577
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync6, readFileSync as readFileSync20, renameSync as renameSync13 } from "fs";
|
|
15329
16578
|
import { dirname as dirname15 } from "path";
|
|
15330
16579
|
import { getSharedAccountHealth as getSharedAccountHealth2 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
15331
16580
|
import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling3 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
15332
|
-
import { fetchUpstream as
|
|
16581
|
+
import { fetchUpstream as fetchUpstream7 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
15333
16582
|
import { getSharedIdentityStore as getSharedIdentityStore2 } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
|
|
15334
16583
|
import {
|
|
15335
16584
|
claudeOAuth as claudeOAuth2,
|
|
15336
16585
|
codexOAuth as codexOAuth2,
|
|
15337
|
-
geminiOAuth as geminiOAuth2
|
|
16586
|
+
geminiOAuth as geminiOAuth2,
|
|
16587
|
+
kimiOAuth as kimiOAuth2
|
|
15338
16588
|
} from "@omnicross/subscriptions";
|
|
15339
16589
|
|
|
15340
16590
|
// src/ports/account-sync.ts
|
|
@@ -15379,7 +16629,7 @@ function findDuplicateCredentialIds(accounts) {
|
|
|
15379
16629
|
}
|
|
15380
16630
|
|
|
15381
16631
|
// src/ports/external-cli-credentials.ts
|
|
15382
|
-
import { existsSync as
|
|
16632
|
+
import { existsSync as existsSync23, readFileSync as readFileSync19 } from "fs";
|
|
15383
16633
|
import { homedir as homedir4 } from "os";
|
|
15384
16634
|
import { join as join20 } from "path";
|
|
15385
16635
|
function externalStorePath(provider, home = homedir4()) {
|
|
@@ -15432,7 +16682,7 @@ function parseCodexTokensEnvelope(raw) {
|
|
|
15432
16682
|
}
|
|
15433
16683
|
function readExternalCliCredentials(provider, home = homedir4()) {
|
|
15434
16684
|
const path2 = externalStorePath(provider, home);
|
|
15435
|
-
if (!
|
|
16685
|
+
if (!existsSync23(path2)) return null;
|
|
15436
16686
|
let raw;
|
|
15437
16687
|
try {
|
|
15438
16688
|
const parsed = JSON.parse(readFileSync19(path2, "utf8"));
|
|
@@ -15458,16 +16708,18 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15458
16708
|
* as on relay refresh egresses from the SAME proxy IP as the
|
|
15459
16709
|
* account's traffic. NOT used by any read/write path.
|
|
15460
16710
|
*/
|
|
15461
|
-
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
|
|
16711
|
+
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
|
|
15462
16712
|
this.tokensPath = tokensPath;
|
|
15463
16713
|
this.box = box;
|
|
15464
16714
|
this.fetchImpl = fetchImpl;
|
|
15465
16715
|
this.externalCliReader = externalCliReader;
|
|
16716
|
+
this.atomicReplace = atomicReplace;
|
|
15466
16717
|
}
|
|
15467
16718
|
tokensPath;
|
|
15468
16719
|
box;
|
|
15469
16720
|
fetchImpl;
|
|
15470
16721
|
externalCliReader;
|
|
16722
|
+
atomicReplace;
|
|
15471
16723
|
/**
|
|
15472
16724
|
* The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
|
|
15473
16725
|
* TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
|
|
@@ -15481,7 +16733,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15481
16733
|
* a plaintext token pair into `upstream-trace.jsonl`.
|
|
15482
16734
|
*/
|
|
15483
16735
|
buildRefreshFetch(providerId, accountId) {
|
|
15484
|
-
return this.fetchImpl ?? ((url, init) =>
|
|
16736
|
+
return this.fetchImpl ?? ((url, init) => fetchUpstream7(url, init, { providerId, accountId, redactBodies: true }));
|
|
15485
16737
|
}
|
|
15486
16738
|
/**
|
|
15487
16739
|
* In-flight refresh coalescing. OAuth refresh tokens are
|
|
@@ -15522,7 +16774,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15522
16774
|
* other hot reads. Never returns token material.
|
|
15523
16775
|
*/
|
|
15524
16776
|
getAccountProxy(providerId, accountId) {
|
|
15525
|
-
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
|
|
16777
|
+
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
|
|
15526
16778
|
return void 0;
|
|
15527
16779
|
}
|
|
15528
16780
|
return getAccountProxy(this.readConfig(), providerId, accountId);
|
|
@@ -15541,7 +16793,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15541
16793
|
const fingerprintOn = identityStore.isEnabled();
|
|
15542
16794
|
const now = Date.now();
|
|
15543
16795
|
const out = {};
|
|
15544
|
-
for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
|
|
16796
|
+
for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
|
|
15545
16797
|
const sanitized = sanitizeAccounts(config, provider);
|
|
15546
16798
|
if (sanitized.length === 0) continue;
|
|
15547
16799
|
for (const account of sanitized) {
|
|
@@ -15699,6 +16951,47 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15699
16951
|
}
|
|
15700
16952
|
});
|
|
15701
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
|
+
}
|
|
15702
16995
|
/**
|
|
15703
16996
|
* Refresh a SPECIFIC managed account by id (background scheduler sweep and
|
|
15704
16997
|
* account-pool resolution). It uses only that account's stored refresh
|
|
@@ -15751,7 +17044,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15751
17044
|
}
|
|
15752
17045
|
const oauth = account.tokens;
|
|
15753
17046
|
if (!oauth.accessToken) return null;
|
|
15754
|
-
if (providerId === "codex" || providerId === "gemini") {
|
|
17047
|
+
if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
|
|
15755
17048
|
const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
|
|
15756
17049
|
const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
|
|
15757
17050
|
if (expiringSoon && oauth.refreshToken) {
|
|
@@ -15840,8 +17133,23 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15840
17133
|
}
|
|
15841
17134
|
/** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
|
|
15842
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
|
+
}
|
|
15843
17151
|
const flow = provider === "claude" ? claudeOAuth2 : provider === "codex" ? codexOAuth2 : geminiOAuth2;
|
|
15844
|
-
const r = await flow.refreshAccessToken(refreshToken,
|
|
17152
|
+
const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
|
|
15845
17153
|
return {
|
|
15846
17154
|
accessToken: r.accessToken,
|
|
15847
17155
|
refreshToken: r.refreshToken,
|
|
@@ -16004,42 +17312,86 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
16004
17312
|
/** Write the merged config to disk as pretty JSON (mkdir parent if needed).
|
|
16005
17313
|
* Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
|
|
16006
17314
|
* `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
|
|
16007
|
-
* 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). */
|
|
16008
17320
|
persist(config) {
|
|
16009
17321
|
mkdirSync6(dirname15(this.tokensPath), { recursive: true });
|
|
16010
17322
|
const encrypted = encryptTokens(config, this.box);
|
|
16011
|
-
|
|
17323
|
+
this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
|
|
16012
17324
|
}
|
|
16013
17325
|
/**
|
|
16014
|
-
* Read + parse `tokens.json`,
|
|
16015
|
-
*
|
|
16016
|
-
*
|
|
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.
|
|
16017
17333
|
*
|
|
16018
|
-
* The
|
|
16019
|
-
*
|
|
16020
|
-
*
|
|
16021
|
-
*
|
|
16022
|
-
*
|
|
16023
|
-
*
|
|
16024
|
-
* `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.
|
|
16025
17340
|
*/
|
|
16026
17341
|
readConfig() {
|
|
16027
|
-
if (!
|
|
17342
|
+
if (!existsSync24(this.tokensPath)) return { updatedAt: "" };
|
|
16028
17343
|
let parsed;
|
|
16029
17344
|
try {
|
|
16030
17345
|
const raw = JSON.parse(readFileSync20(this.tokensPath, "utf8"));
|
|
16031
|
-
|
|
17346
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
17347
|
+
return this.quarantineCorrupt("parsed JSON is not an object");
|
|
17348
|
+
}
|
|
17349
|
+
parsed = raw;
|
|
16032
17350
|
} catch {
|
|
16033
|
-
|
|
17351
|
+
return this.quarantineCorrupt("unparseable JSON");
|
|
16034
17352
|
}
|
|
16035
|
-
if (!parsed) return { updatedAt: "" };
|
|
16036
17353
|
const decrypted = decryptTokens(parsed, this.box);
|
|
16037
17354
|
return migrateLazily(decrypted);
|
|
16038
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
|
+
}
|
|
16039
17391
|
};
|
|
16040
17392
|
|
|
16041
17393
|
// src/AccountHealthProbeScheduler.ts
|
|
16042
|
-
import { fetchUpstream as
|
|
17394
|
+
import { fetchUpstream as fetchUpstream8 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
16043
17395
|
|
|
16044
17396
|
// src/probe/CodexGenerationProbe.ts
|
|
16045
17397
|
import {
|
|
@@ -16181,7 +17533,11 @@ var PROVIDER_PROBE_PLANS = {
|
|
|
16181
17533
|
// billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
|
|
16182
17534
|
codex: { kind: "local" },
|
|
16183
17535
|
gemini: { kind: "local" },
|
|
16184
|
-
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" }
|
|
16185
17541
|
};
|
|
16186
17542
|
function probePlanFor(providerId) {
|
|
16187
17543
|
return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
|
|
@@ -16203,7 +17559,7 @@ var AccountHealthProbeScheduler = class {
|
|
|
16203
17559
|
this.logger = logger;
|
|
16204
17560
|
this.config = config;
|
|
16205
17561
|
this.now = opts.now ?? Date.now;
|
|
16206
|
-
this.fetchImpl = opts.fetchImpl ??
|
|
17562
|
+
this.fetchImpl = opts.fetchImpl ?? fetchUpstream8;
|
|
16207
17563
|
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
16208
17564
|
this.planFor = opts.planFor ?? probePlanFor;
|
|
16209
17565
|
}
|
|
@@ -16547,7 +17903,7 @@ var AccountHealthSweeper = class {
|
|
|
16547
17903
|
};
|
|
16548
17904
|
|
|
16549
17905
|
// src/audit/AuditPruneSweeper.ts
|
|
16550
|
-
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";
|
|
16551
17907
|
import { join as join22 } from "path";
|
|
16552
17908
|
import { pipeline } from "stream/promises";
|
|
16553
17909
|
import { createGzip } from "zlib";
|
|
@@ -16555,11 +17911,11 @@ import { createGzip } from "zlib";
|
|
|
16555
17911
|
// src/audit/auditStats.ts
|
|
16556
17912
|
import {
|
|
16557
17913
|
createReadStream as createReadStream2,
|
|
16558
|
-
existsSync as
|
|
17914
|
+
existsSync as existsSync25,
|
|
16559
17915
|
readFileSync as readFileSync21,
|
|
16560
17916
|
readdirSync as readdirSync7,
|
|
16561
17917
|
statSync as statSync7,
|
|
16562
|
-
writeFileSync as
|
|
17918
|
+
writeFileSync as writeFileSync17
|
|
16563
17919
|
} from "fs";
|
|
16564
17920
|
import { basename as basename9, dirname as dirname16, join as join21 } from "path";
|
|
16565
17921
|
var SIDECAR_VERSION = 1;
|
|
@@ -16569,7 +17925,7 @@ function auditStatsFileName(auditFile) {
|
|
|
16569
17925
|
return auditFile.replace(/\.jsonl$/, ".stats.json");
|
|
16570
17926
|
}
|
|
16571
17927
|
function readPersisted(path2) {
|
|
16572
|
-
if (!
|
|
17928
|
+
if (!existsSync25(path2)) return null;
|
|
16573
17929
|
try {
|
|
16574
17930
|
const value = JSON.parse(readFileSync21(path2, "utf8"));
|
|
16575
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)) {
|
|
@@ -16601,7 +17957,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
|
|
|
16601
17957
|
minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
|
|
16602
17958
|
maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
|
|
16603
17959
|
};
|
|
16604
|
-
|
|
17960
|
+
writeFileSync17(statsPath, JSON.stringify(next), "utf8");
|
|
16605
17961
|
}
|
|
16606
17962
|
function queryCovers(stats, from, to) {
|
|
16607
17963
|
return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
|
|
@@ -16712,7 +18068,7 @@ function mergePersistedStats(previous, appended) {
|
|
|
16712
18068
|
};
|
|
16713
18069
|
}
|
|
16714
18070
|
async function readAuditStats(auditDir, query2 = {}) {
|
|
16715
|
-
if (!
|
|
18071
|
+
if (!existsSync25(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
|
|
16716
18072
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
16717
18073
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
16718
18074
|
let sources;
|
|
@@ -16725,7 +18081,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16725
18081
|
auditPath: join21(auditDir, name),
|
|
16726
18082
|
statsPath: join21(auditDir, auditStatsFileName(name))
|
|
16727
18083
|
}
|
|
16728
|
-
).filter((source) =>
|
|
18084
|
+
).filter((source) => existsSync25(source.auditPath));
|
|
16729
18085
|
} catch {
|
|
16730
18086
|
return { requestCount: 0, errorCount: 0, complete: false };
|
|
16731
18087
|
}
|
|
@@ -16751,7 +18107,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16751
18107
|
total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
|
|
16752
18108
|
total.complete = total.complete && scanned.filtered.complete;
|
|
16753
18109
|
const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
|
|
16754
|
-
if (current.complete)
|
|
18110
|
+
if (current.complete) writeFileSync17(statsPath, JSON.stringify(current), "utf8");
|
|
16755
18111
|
} catch {
|
|
16756
18112
|
total.complete = false;
|
|
16757
18113
|
}
|
|
@@ -16760,7 +18116,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16760
18116
|
}
|
|
16761
18117
|
|
|
16762
18118
|
// src/audit/AuditPruneSweeper.ts
|
|
16763
|
-
var
|
|
18119
|
+
var DAY_MS3 = 24 * 60 * 6e4;
|
|
16764
18120
|
var SWEEP_INTERVAL_MS2 = 60 * 6e4;
|
|
16765
18121
|
var ARCHIVE_BATCH = 64;
|
|
16766
18122
|
var AuditPruneSweeper = class {
|
|
@@ -16823,8 +18179,8 @@ var AuditPruneSweeper = class {
|
|
|
16823
18179
|
if (!this.config.enabled || this.sweeping) return 0;
|
|
16824
18180
|
this.sweeping = true;
|
|
16825
18181
|
try {
|
|
16826
|
-
if (!
|
|
16827
|
-
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;
|
|
16828
18184
|
let removed = 0;
|
|
16829
18185
|
for (const name of readdirSync8(this.auditDir)) {
|
|
16830
18186
|
const dateMs = auditFileDateMs(name);
|
|
@@ -16835,7 +18191,7 @@ var AuditPruneSweeper = class {
|
|
|
16835
18191
|
} else {
|
|
16836
18192
|
unlinkSync13(join22(this.auditDir, name));
|
|
16837
18193
|
const statsPath = join22(this.auditDir, auditStatsFileName(name));
|
|
16838
|
-
if (
|
|
18194
|
+
if (existsSync26(statsPath)) unlinkSync13(statsPath);
|
|
16839
18195
|
}
|
|
16840
18196
|
removed += 1;
|
|
16841
18197
|
} catch (error) {
|
|
@@ -16865,7 +18221,7 @@ var AuditPruneSweeper = class {
|
|
|
16865
18221
|
if (!this.config.enabled || this.archiving) return 0;
|
|
16866
18222
|
this.archiving = true;
|
|
16867
18223
|
try {
|
|
16868
|
-
if (!
|
|
18224
|
+
if (!existsSync26(this.auditDir)) return 0;
|
|
16869
18225
|
const today = this.todayMidnight();
|
|
16870
18226
|
let compressed = 0;
|
|
16871
18227
|
for (const name of readdirSync8(this.auditDir)) {
|
|
@@ -16919,7 +18275,7 @@ var AuditPruneSweeper = class {
|
|
|
16919
18275
|
const source = join22(bodiesPath, shard);
|
|
16920
18276
|
const target = `${source}.gz`;
|
|
16921
18277
|
try {
|
|
16922
|
-
if (
|
|
18278
|
+
if (existsSync26(target)) {
|
|
16923
18279
|
unlinkSync13(source);
|
|
16924
18280
|
continue;
|
|
16925
18281
|
}
|
|
@@ -16928,7 +18284,7 @@ var AuditPruneSweeper = class {
|
|
|
16928
18284
|
compressed += 1;
|
|
16929
18285
|
} catch (error) {
|
|
16930
18286
|
try {
|
|
16931
|
-
if (
|
|
18287
|
+
if (existsSync26(target)) unlinkSync13(target);
|
|
16932
18288
|
} catch {
|
|
16933
18289
|
}
|
|
16934
18290
|
this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
|
|
@@ -17081,7 +18437,7 @@ async function closeAll(writers) {
|
|
|
17081
18437
|
// src/usage/UsagePruneSweeper.ts
|
|
17082
18438
|
import { unlink as unlink3 } from "fs/promises";
|
|
17083
18439
|
import { join as join24 } from "path";
|
|
17084
|
-
var
|
|
18440
|
+
var DAY_MS4 = 24 * 60 * 6e4;
|
|
17085
18441
|
var SWEEP_INTERVAL_MS3 = 60 * 6e4;
|
|
17086
18442
|
var DEFAULT_USAGE_RETENTION_DAYS = 90;
|
|
17087
18443
|
var UsagePruneSweeper = class {
|
|
@@ -17138,7 +18494,7 @@ var UsagePruneSweeper = class {
|
|
|
17138
18494
|
this.sweeping = true;
|
|
17139
18495
|
try {
|
|
17140
18496
|
const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
|
|
17141
|
-
const cutoff = this.todayMidnight() - (retentionDays - 1) *
|
|
18497
|
+
const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
|
|
17142
18498
|
let removed = 0;
|
|
17143
18499
|
for (const entry of await listUsageDays(this.usageDir)) {
|
|
17144
18500
|
if (!entry.hasShard) continue;
|
|
@@ -17196,7 +18552,7 @@ var UsagePruneSweeper = class {
|
|
|
17196
18552
|
};
|
|
17197
18553
|
|
|
17198
18554
|
// src/audit/auditReader.ts
|
|
17199
|
-
import { existsSync as
|
|
18555
|
+
import { existsSync as existsSync27, readdirSync as readdirSync9 } from "fs";
|
|
17200
18556
|
import { join as join25 } from "path";
|
|
17201
18557
|
var DEFAULT_LIMIT = 200;
|
|
17202
18558
|
var MAX_LIMIT = 2e3;
|
|
@@ -17214,7 +18570,7 @@ function daySources(auditDir) {
|
|
|
17214
18570
|
if (dateMs === null) continue;
|
|
17215
18571
|
if (AUDIT_DAY_DIR_RE.test(name)) {
|
|
17216
18572
|
const path2 = join25(auditDir, name, AUDIT_META_FILE);
|
|
17217
|
-
if (
|
|
18573
|
+
if (existsSync27(path2)) sources.push({ path: path2, dateMs });
|
|
17218
18574
|
} else if (AUDIT_FILE_RE.test(name)) {
|
|
17219
18575
|
sources.push({ path: join25(auditDir, name), dateMs });
|
|
17220
18576
|
}
|
|
@@ -17232,7 +18588,7 @@ function toMetaRecord(record) {
|
|
|
17232
18588
|
return { ...meta, hasBody: true };
|
|
17233
18589
|
}
|
|
17234
18590
|
function readAuditRecords(auditDir, query2 = {}) {
|
|
17235
|
-
if (!
|
|
18591
|
+
if (!existsSync27(auditDir)) return [];
|
|
17236
18592
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
17237
18593
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
17238
18594
|
const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
|
|
@@ -17260,7 +18616,7 @@ function readAuditRecords(auditDir, query2 = {}) {
|
|
|
17260
18616
|
}
|
|
17261
18617
|
|
|
17262
18618
|
// src/audit/AuditWriter.ts
|
|
17263
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
18619
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync28, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
|
|
17264
18620
|
import { join as join26 } from "path";
|
|
17265
18621
|
var AuditWriter = class {
|
|
17266
18622
|
constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
|
|
@@ -17318,7 +18674,7 @@ var AuditWriter = class {
|
|
|
17318
18674
|
const { requestBody: _req, responseBody: _res, ...meta } = record;
|
|
17319
18675
|
const file = join26(dayPath, AUDIT_META_FILE);
|
|
17320
18676
|
const line = JSON.stringify(meta) + "\n";
|
|
17321
|
-
const bytesBefore =
|
|
18677
|
+
const bytesBefore = existsSync28(file) ? statSync8(file).size : 0;
|
|
17322
18678
|
appendFileSync2(file, line, "utf8");
|
|
17323
18679
|
try {
|
|
17324
18680
|
updateAuditStatsAfterAppend(
|
|
@@ -17366,7 +18722,7 @@ var AuditWriter = class {
|
|
|
17366
18722
|
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync8 } from "fs";
|
|
17367
18723
|
import { createHmac as createHmac5 } from "crypto";
|
|
17368
18724
|
import { join as join27 } from "path";
|
|
17369
|
-
import { fetchUpstream as
|
|
18725
|
+
import { fetchUpstream as fetchUpstream9 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
17370
18726
|
|
|
17371
18727
|
// src/billing/billingFiles.ts
|
|
17372
18728
|
var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
|
|
@@ -17389,7 +18745,7 @@ var BillingPublisher = class {
|
|
|
17389
18745
|
constructor(billingDir, logger, opts = {}) {
|
|
17390
18746
|
this.billingDir = billingDir;
|
|
17391
18747
|
this.logger = logger;
|
|
17392
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) =>
|
|
18748
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream9(url, init));
|
|
17393
18749
|
this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
|
|
17394
18750
|
this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
|
|
17395
18751
|
this.now = opts.now ?? Date.now;
|
|
@@ -17502,11 +18858,11 @@ var BillingPublisher = class {
|
|
|
17502
18858
|
};
|
|
17503
18859
|
|
|
17504
18860
|
// src/billing/billingReader.ts
|
|
17505
|
-
import { existsSync as
|
|
18861
|
+
import { existsSync as existsSync29, readdirSync as readdirSync10, readFileSync as readFileSync22 } from "fs";
|
|
17506
18862
|
import { join as join28 } from "path";
|
|
17507
18863
|
function readBillingLedger(billingDir) {
|
|
17508
18864
|
const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
|
|
17509
|
-
if (!
|
|
18865
|
+
if (!existsSync29(billingDir)) return view;
|
|
17510
18866
|
let files;
|
|
17511
18867
|
try {
|
|
17512
18868
|
files = readdirSync10(billingDir);
|
|
@@ -17639,7 +18995,7 @@ var BillingRetrySweeper = class {
|
|
|
17639
18995
|
// src/TokenRefreshScheduler.ts
|
|
17640
18996
|
var REFRESH_LEAD_MS2 = 5 * 6e4;
|
|
17641
18997
|
var SWEEP_INTERVAL_MS5 = 6e4;
|
|
17642
|
-
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
|
|
18998
|
+
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
|
|
17643
18999
|
var TokenRefreshScheduler = class {
|
|
17644
19000
|
constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
|
|
17645
19001
|
this.store = store;
|
|
@@ -17722,6 +19078,8 @@ var TokenRefreshScheduler = class {
|
|
|
17722
19078
|
return this.store.refreshCodexToken();
|
|
17723
19079
|
case "gemini":
|
|
17724
19080
|
return this.store.refreshGeminiToken();
|
|
19081
|
+
case "kimi":
|
|
19082
|
+
return this.store.refreshKimiToken();
|
|
17725
19083
|
}
|
|
17726
19084
|
}
|
|
17727
19085
|
};
|
|
@@ -17800,7 +19158,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
|
|
|
17800
19158
|
|
|
17801
19159
|
// src/webhook/WebhookDispatcher.ts
|
|
17802
19160
|
import { createHmac as createHmac6 } from "crypto";
|
|
17803
|
-
import { fetchUpstream as
|
|
19161
|
+
import { fetchUpstream as fetchUpstream10 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
17804
19162
|
var WEBHOOK_MAX_ATTEMPTS = 3;
|
|
17805
19163
|
var WEBHOOK_QUEUE_MAX = 1e3;
|
|
17806
19164
|
var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
|
|
@@ -17820,7 +19178,7 @@ var WebhookDispatcher = class {
|
|
|
17820
19178
|
sleep;
|
|
17821
19179
|
now;
|
|
17822
19180
|
constructor(opts = {}) {
|
|
17823
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) =>
|
|
19181
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream10(url, init));
|
|
17824
19182
|
this.logger = opts.logger;
|
|
17825
19183
|
this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
|
|
17826
19184
|
this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
|
|
@@ -17906,8 +19264,8 @@ var WebhookDispatcher = class {
|
|
|
17906
19264
|
signal: AbortSignal.timeout(this.timeoutMs)
|
|
17907
19265
|
});
|
|
17908
19266
|
return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
|
|
17909
|
-
} catch (
|
|
17910
|
-
return { ok: false, error:
|
|
19267
|
+
} catch (err6) {
|
|
19268
|
+
return { ok: false, error: err6 instanceof Error ? err6.message : String(err6) };
|
|
17911
19269
|
}
|
|
17912
19270
|
}
|
|
17913
19271
|
/**
|
|
@@ -18044,7 +19402,7 @@ function buildDaemon(config, paths) {
|
|
|
18044
19402
|
setSecretBox(secretBox3);
|
|
18045
19403
|
setSecretBox2(secretBox3);
|
|
18046
19404
|
const decryptedConfig = decryptConfigSecrets(config, secretBox3);
|
|
18047
|
-
const accountAllowanceStore = new
|
|
19405
|
+
const accountAllowanceStore = new AccountAllowanceStore6(
|
|
18048
19406
|
Date.now,
|
|
18049
19407
|
void 0,
|
|
18050
19408
|
new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
|
|
@@ -18089,6 +19447,7 @@ function buildDaemon(config, paths) {
|
|
|
18089
19447
|
);
|
|
18090
19448
|
setGeminiCodeAssistResolver(getGeminiCodeAssistProjectResolver());
|
|
18091
19449
|
const autoDisableStore = new AutoDisableStore();
|
|
19450
|
+
const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
|
|
18092
19451
|
const apiKeyPool = new ApiKeyPoolService(
|
|
18093
19452
|
createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
|
|
18094
19453
|
resolveEnvKey,
|
|
@@ -18105,7 +19464,7 @@ function buildDaemon(config, paths) {
|
|
|
18105
19464
|
const pricingEngine = new PricingEngine(pricingStore, logger, {
|
|
18106
19465
|
// Catalog egress follows the same global/env proxy policy as every other
|
|
18107
19466
|
// daemon upstream call; no provider/account override applies here.
|
|
18108
|
-
fetchImpl: ((input, init) =>
|
|
19467
|
+
fetchImpl: ((input, init) => fetchUpstream11(String(input), init ?? {}))
|
|
18109
19468
|
});
|
|
18110
19469
|
const pricingRefreshScheduler = new PricingRefreshScheduler(
|
|
18111
19470
|
pricingEngine,
|
|
@@ -18369,6 +19728,11 @@ function buildDaemon(config, paths) {
|
|
|
18369
19728
|
// values themselves NEVER leave (masked via `maskProviderApiKey`).
|
|
18370
19729
|
apiKeyPool,
|
|
18371
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,
|
|
18372
19736
|
// Interactive OAuth login over admin HTTP (app-parity child 4, design
|
|
18373
19737
|
// D1/D2-a). The in-memory pending-session store (NEVER serialized), the
|
|
18374
19738
|
// injected token-exchange fetch (global `fetch` here; mocked in tests), and a
|
|
@@ -18385,7 +19749,7 @@ function buildDaemon(config, paths) {
|
|
|
18385
19749
|
// — `server.proxy.byProvider[...]` was silently skipped — and the call was
|
|
18386
19750
|
// excluded from the upstream trace, so a failing login left no evidence.
|
|
18387
19751
|
// `redactBodies` keeps the code/verifier + minted token out of that trace.
|
|
18388
|
-
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) =>
|
|
19752
|
+
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream11(url, init, { providerId, redactBodies: true }),
|
|
18389
19753
|
subscriptionAccountAppender: credentialStore,
|
|
18390
19754
|
// Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
|
|
18391
19755
|
// + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
|
|
@@ -18393,6 +19757,10 @@ function buildDaemon(config, paths) {
|
|
|
18393
19757
|
// can inject a mock so no real port is bound.
|
|
18394
19758
|
codexSessions: new CodexOAuthSessionStore(),
|
|
18395
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(),
|
|
18396
19764
|
// Migration pack (app-parity child 6, design D2/D3) — the concrete credential
|
|
18397
19765
|
// store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
|
|
18398
19766
|
// the multi-account append (`appendProviderAccount`, import re-encrypts at-
|
|
@@ -18451,7 +19819,7 @@ function buildDaemon(config, paths) {
|
|
|
18451
19819
|
});
|
|
18452
19820
|
const webhookDispatcher = new WebhookDispatcher({
|
|
18453
19821
|
logger,
|
|
18454
|
-
fetchImpl: (url, init) =>
|
|
19822
|
+
fetchImpl: (url, init) => fetchUpstream11(url, init)
|
|
18455
19823
|
});
|
|
18456
19824
|
setWebhookRuntime(webhookDispatcher, getSharedAccountHealth4());
|
|
18457
19825
|
const auditWriter = new AuditWriter(auditDir, logger);
|
|
@@ -18528,7 +19896,7 @@ function buildDaemon(config, paths) {
|
|
|
18528
19896
|
}
|
|
18529
19897
|
function isTokensStoreReadable(tokensPath) {
|
|
18530
19898
|
try {
|
|
18531
|
-
if (!
|
|
19899
|
+
if (!existsSync30(tokensPath)) return true;
|
|
18532
19900
|
accessSync(tokensPath, fsConstants.R_OK);
|
|
18533
19901
|
return true;
|
|
18534
19902
|
} catch {
|
|
@@ -18765,11 +20133,11 @@ async function runLiveProbe(url, key, fetchImpl = fetch) {
|
|
|
18765
20133
|
status: res.status,
|
|
18766
20134
|
estimateHeader: res.headers.get("x-omnicross-count-estimate")
|
|
18767
20135
|
};
|
|
18768
|
-
} catch (
|
|
20136
|
+
} catch (err6) {
|
|
18769
20137
|
return {
|
|
18770
20138
|
status: null,
|
|
18771
20139
|
estimateHeader: null,
|
|
18772
|
-
error:
|
|
20140
|
+
error: err6 instanceof Error ? err6.message : String(err6)
|
|
18773
20141
|
};
|
|
18774
20142
|
}
|
|
18775
20143
|
}
|
|
@@ -19090,7 +20458,7 @@ async function keysRevoke(db, id) {
|
|
|
19090
20458
|
// src/commands/launch.ts
|
|
19091
20459
|
import { spawn as spawn2 } from "child_process";
|
|
19092
20460
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
19093
|
-
import { existsSync as
|
|
20461
|
+
import { existsSync as existsSync31 } from "fs";
|
|
19094
20462
|
import { delimiter as delimiter2, join as join29 } from "path";
|
|
19095
20463
|
import { parseArgs as parseArgs6 } from "util";
|
|
19096
20464
|
import {
|
|
@@ -19138,7 +20506,7 @@ function resolveInPathDefault(candidate) {
|
|
|
19138
20506
|
const segments = (process.env["PATH"] ?? "").split(delimiter2).filter(Boolean);
|
|
19139
20507
|
for (const seg of segments) {
|
|
19140
20508
|
const full = join29(seg, candidate);
|
|
19141
|
-
if (
|
|
20509
|
+
if (existsSync31(full)) return full;
|
|
19142
20510
|
}
|
|
19143
20511
|
return null;
|
|
19144
20512
|
}
|
|
@@ -19179,9 +20547,9 @@ async function runLaunch(argv, deps) {
|
|
|
19179
20547
|
await daemon.llmConfig.ready();
|
|
19180
20548
|
await daemon.migrateUsageStore();
|
|
19181
20549
|
await daemon.providerProxy.start();
|
|
19182
|
-
} catch (
|
|
20550
|
+
} catch (err6) {
|
|
19183
20551
|
await shutdownLaunchDaemon(daemon);
|
|
19184
|
-
throw
|
|
20552
|
+
throw err6;
|
|
19185
20553
|
}
|
|
19186
20554
|
let launch;
|
|
19187
20555
|
try {
|
|
@@ -19189,9 +20557,9 @@ async function runLaunch(argv, deps) {
|
|
|
19189
20557
|
providerId: values.provider,
|
|
19190
20558
|
model: values.model
|
|
19191
20559
|
});
|
|
19192
|
-
} catch (
|
|
20560
|
+
} catch (err6) {
|
|
19193
20561
|
await shutdownLaunchDaemon(daemon);
|
|
19194
|
-
throw
|
|
20562
|
+
throw err6;
|
|
19195
20563
|
}
|
|
19196
20564
|
try {
|
|
19197
20565
|
const plan = buildCliSpawnPlan({
|
|
@@ -19296,9 +20664,9 @@ function spawnCliInherit(plan) {
|
|
|
19296
20664
|
process.removeListener("SIGINT", onSignal);
|
|
19297
20665
|
process.removeListener("SIGTERM", onSignal);
|
|
19298
20666
|
};
|
|
19299
|
-
child.on("error", (
|
|
20667
|
+
child.on("error", (err6) => {
|
|
19300
20668
|
detach();
|
|
19301
|
-
if (
|
|
20669
|
+
if (err6.code === "ENOENT") {
|
|
19302
20670
|
reject(
|
|
19303
20671
|
new Error(
|
|
19304
20672
|
`launch: "${plan.command}" not found on PATH \u2014 install the CLI first.`
|
|
@@ -19306,7 +20674,7 @@ function spawnCliInherit(plan) {
|
|
|
19306
20674
|
);
|
|
19307
20675
|
return;
|
|
19308
20676
|
}
|
|
19309
|
-
reject(
|
|
20677
|
+
reject(err6);
|
|
19310
20678
|
});
|
|
19311
20679
|
child.on("exit", (code, signal) => {
|
|
19312
20680
|
detach();
|
|
@@ -19319,9 +20687,14 @@ function spawnCliInherit(plan) {
|
|
|
19319
20687
|
import { spawn as spawn3 } from "child_process";
|
|
19320
20688
|
import { createInterface as createInterface2 } from "readline";
|
|
19321
20689
|
import { parseArgs as parseArgs7 } from "util";
|
|
19322
|
-
import { fetchUpstream as
|
|
19323
|
-
import {
|
|
19324
|
-
|
|
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"];
|
|
19325
20698
|
async function runLogin(argv, deps) {
|
|
19326
20699
|
const { values, positionals } = parseArgs7({
|
|
19327
20700
|
args: argv,
|
|
@@ -19347,14 +20720,16 @@ async function runLogin(argv, deps) {
|
|
|
19347
20720
|
openBrowser: deps?.openBrowser ?? openBrowser,
|
|
19348
20721
|
promptPaste: deps?.promptPaste ?? promptPaste,
|
|
19349
20722
|
awaitLoopback: deps?.awaitLoopback ?? ((state) => awaitLoopbackCode(state)),
|
|
20723
|
+
awaitKimiDevice: deps?.awaitKimiDevice ?? ((fetchImpl) => runKimiDeviceFlow(fetchImpl, resolvedOpenBrowser)),
|
|
19350
20724
|
tokensFetch: deps?.tokensFetch
|
|
19351
20725
|
};
|
|
20726
|
+
const resolvedOpenBrowser = resolved.openBrowser;
|
|
19352
20727
|
const box = resolveSecretBox(values["master-key-file"]);
|
|
19353
20728
|
setSecretBox(box);
|
|
19354
20729
|
setUpstreamProxyResolver2(createUpstreamProxyResolver());
|
|
19355
20730
|
try {
|
|
19356
20731
|
const tokensPath = defaultTokensPath(values.config);
|
|
19357
|
-
const exchangeFetch = resolved.tokensFetch ?? ((url, init) =>
|
|
20732
|
+
const exchangeFetch = resolved.tokensFetch ?? ((url, init) => fetchUpstream12(url, init, { providerId: provider, redactBodies: true }));
|
|
19358
20733
|
const store = new JsonSubscriptionCredentialStore(tokensPath, box, exchangeFetch);
|
|
19359
20734
|
const expiresAt = await runProviderLogin(
|
|
19360
20735
|
provider,
|
|
@@ -19373,6 +20748,7 @@ async function runLogin(argv, deps) {
|
|
|
19373
20748
|
async function runProviderLogin(provider, store, deps, exchangeFetch, label) {
|
|
19374
20749
|
if (provider === "codex") return loginCodex(store, deps, exchangeFetch, label);
|
|
19375
20750
|
if (provider === "claude") return loginClaude(store, deps, exchangeFetch, label);
|
|
20751
|
+
if (provider === "kimi") return loginKimi(store, deps, exchangeFetch, label);
|
|
19376
20752
|
return loginGemini(store, deps, exchangeFetch, label);
|
|
19377
20753
|
}
|
|
19378
20754
|
async function loginCodex(store, deps, exchangeFetch, label) {
|
|
@@ -19443,6 +20819,45 @@ async function loginGemini(store, deps, exchangeFetch, label) {
|
|
|
19443
20819
|
logMasked("gemini", result.accessToken);
|
|
19444
20820
|
return expiresAt;
|
|
19445
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
|
+
}
|
|
19446
20861
|
function isLoginProvider(value) {
|
|
19447
20862
|
return PROVIDERS2.includes(value);
|
|
19448
20863
|
}
|
|
@@ -19640,7 +21055,7 @@ function providersRmKey(configPath, providerId, keyId) {
|
|
|
19640
21055
|
}
|
|
19641
21056
|
|
|
19642
21057
|
// src/commands/secrets.ts
|
|
19643
|
-
import { existsSync as
|
|
21058
|
+
import { existsSync as existsSync32, readFileSync as readFileSync24 } from "fs";
|
|
19644
21059
|
import { parseArgs as parseArgs9 } from "util";
|
|
19645
21060
|
async function runSecrets(argv) {
|
|
19646
21061
|
const { values, positionals } = parseArgs9({
|
|
@@ -19713,12 +21128,12 @@ function secretsStatus(args) {
|
|
|
19713
21128
|
reportField("admin.token", cfg.admin.token);
|
|
19714
21129
|
}
|
|
19715
21130
|
const tokensPath = defaultTokensPath(args.config);
|
|
19716
|
-
if (
|
|
21131
|
+
if (existsSync32(tokensPath)) {
|
|
19717
21132
|
console.info(`Secret status for ${tokensPath}:`);
|
|
19718
21133
|
reportTokenFields(tokensPath);
|
|
19719
21134
|
}
|
|
19720
21135
|
const integrationsPath = defaultIntegrationsPath(args.config);
|
|
19721
|
-
if (
|
|
21136
|
+
if (existsSync32(integrationsPath)) {
|
|
19722
21137
|
const state = readRawJson(integrationsPath);
|
|
19723
21138
|
const key = state.gatewayKey;
|
|
19724
21139
|
if (key && typeof key === "object" && !Array.isArray(key)) {
|
|
@@ -19772,8 +21187,8 @@ async function secretsRotate(args) {
|
|
|
19772
21187
|
const integrationsPath = defaultIntegrationsPath(args.config);
|
|
19773
21188
|
try {
|
|
19774
21189
|
cfg = loadConfig(args.config);
|
|
19775
|
-
if (
|
|
19776
|
-
if (
|
|
21190
|
+
if (existsSync32(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
|
|
21191
|
+
if (existsSync32(integrationsPath)) {
|
|
19777
21192
|
integrationsPlain = new IntegrationStateStore(integrationsPath, oldBox).load();
|
|
19778
21193
|
}
|
|
19779
21194
|
} finally {
|
|
@@ -19808,13 +21223,13 @@ function secretsDecrypt(args) {
|
|
|
19808
21223
|
let tokensPlain = null;
|
|
19809
21224
|
try {
|
|
19810
21225
|
cfg = loadConfig(args.config);
|
|
19811
|
-
if (
|
|
21226
|
+
if (existsSync32(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
|
|
19812
21227
|
} finally {
|
|
19813
21228
|
setSecretBox(null);
|
|
19814
21229
|
}
|
|
19815
21230
|
saveConfig(args.config, cfg);
|
|
19816
21231
|
if (tokensPlain) {
|
|
19817
|
-
|
|
21232
|
+
atomicReplaceUtf8(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n");
|
|
19818
21233
|
}
|
|
19819
21234
|
console.info(`Decrypted secrets to plaintext in ${args.config}` + tokensSuffix(args.config));
|
|
19820
21235
|
}
|
|
@@ -19839,13 +21254,13 @@ function readRawJson(path2) {
|
|
|
19839
21254
|
}
|
|
19840
21255
|
function encryptTokensFileInPlace(configPath, box) {
|
|
19841
21256
|
const tokensPath = defaultTokensPath(configPath);
|
|
19842
|
-
if (!
|
|
21257
|
+
if (!existsSync32(tokensPath)) return;
|
|
19843
21258
|
const plain = decryptTokensFile(tokensPath, box);
|
|
19844
21259
|
writeTokensEncrypted(tokensPath, plain, box);
|
|
19845
21260
|
}
|
|
19846
21261
|
function rewriteIntegrationState(configPath, readBox, writeBox) {
|
|
19847
21262
|
const path2 = defaultIntegrationsPath(configPath);
|
|
19848
|
-
if (!
|
|
21263
|
+
if (!existsSync32(path2)) return;
|
|
19849
21264
|
const state = new IntegrationStateStore(path2, readBox).load();
|
|
19850
21265
|
new IntegrationStateStore(path2, writeBox).save(state);
|
|
19851
21266
|
}
|
|
@@ -19858,7 +21273,7 @@ function writeTokensEncrypted(tokensPath, plain, box) {
|
|
|
19858
21273
|
{ updatedAt: "", ...plain },
|
|
19859
21274
|
box
|
|
19860
21275
|
);
|
|
19861
|
-
|
|
21276
|
+
atomicReplaceUtf8(tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
|
|
19862
21277
|
}
|
|
19863
21278
|
var TOKEN_FIELDS2 = {
|
|
19864
21279
|
claude: ["accessToken", "refreshToken"],
|
|
@@ -19881,7 +21296,7 @@ function walkTokens(raw, fn) {
|
|
|
19881
21296
|
return next;
|
|
19882
21297
|
}
|
|
19883
21298
|
function tokensSuffix(configPath) {
|
|
19884
|
-
return
|
|
21299
|
+
return existsSync32(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
|
|
19885
21300
|
}
|
|
19886
21301
|
|
|
19887
21302
|
// src/commands/start.ts
|
|
@@ -20148,7 +21563,7 @@ async function main() {
|
|
|
20148
21563
|
process.exitCode = 1;
|
|
20149
21564
|
}
|
|
20150
21565
|
}
|
|
20151
|
-
main().catch((
|
|
20152
|
-
console.error(
|
|
21566
|
+
main().catch((err6) => {
|
|
21567
|
+
console.error(err6 instanceof Error ? err6.message : String(err6));
|
|
20153
21568
|
process.exitCode = 1;
|
|
20154
21569
|
});
|