@omnicross/daemon 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +19 -19
- package/dist/cli.cjs +1761 -337
- package/dist/cli.js +1698 -260
- package/dist/index.cjs +1702 -320
- package/dist/index.d.cts +334 -28
- package/dist/index.d.ts +334 -28
- package/dist/index.js +1637 -246
- package/package.json +6 -6
package/dist/cli.cjs
CHANGED
|
@@ -1163,10 +1163,10 @@ var import_node_util2 = require("util");
|
|
|
1163
1163
|
var import_outbound_api12 = require("@omnicross/core/outbound-api");
|
|
1164
1164
|
var import_api4 = require("@omnicross/core/search/api");
|
|
1165
1165
|
var import_http4 = require("@omnicross/core/search/http");
|
|
1166
|
-
var
|
|
1166
|
+
var import_search3 = require("@omnicross/core/search");
|
|
1167
1167
|
|
|
1168
1168
|
// src/bootstrap.ts
|
|
1169
|
-
var
|
|
1169
|
+
var import_node_fs36 = require("fs");
|
|
1170
1170
|
var import_node_path36 = require("path");
|
|
1171
1171
|
var import_audit_types = require("@omnicross/contracts/audit-types");
|
|
1172
1172
|
var import_billing_types = require("@omnicross/contracts/billing-types");
|
|
@@ -1176,16 +1176,16 @@ var import_ApiKeyPoolService = require("@omnicross/core/completion/ApiKeyPoolSer
|
|
|
1176
1176
|
var import_outbound_api10 = require("@omnicross/core/outbound-api");
|
|
1177
1177
|
var import_subscriptionRegistryPort = require("@omnicross/core/outbound-api/subscriptionRegistryPort");
|
|
1178
1178
|
var import_SubscriptionAccountHealth4 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
|
|
1179
|
-
var
|
|
1179
|
+
var import_AccountAllowanceStore7 = require("@omnicross/core/pipeline/AccountAllowanceStore");
|
|
1180
1180
|
var import_AccountAllowanceScheduling5 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
|
|
1181
|
-
var
|
|
1181
|
+
var import_upstreamFetch13 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
1182
1182
|
var import_SubscriptionIdentityStore3 = require("@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore");
|
|
1183
1183
|
var import_gemini_code_assist_resolver = require("@omnicross/core/ports/gemini-code-assist-resolver");
|
|
1184
1184
|
var import_provider_proxy4 = require("@omnicross/core/provider-proxy");
|
|
1185
1185
|
var import_cli_launcher2 = require("@omnicross/cli-launcher");
|
|
1186
1186
|
var import_outbound_api11 = require("@omnicross/core/outbound-api");
|
|
1187
1187
|
var import_usage2 = require("@omnicross/core/usage");
|
|
1188
|
-
var
|
|
1188
|
+
var import_subscriptions9 = require("@omnicross/subscriptions");
|
|
1189
1189
|
|
|
1190
1190
|
// src/admin/accountsCodexOAuth.ts
|
|
1191
1191
|
var import_node_crypto3 = __toESM(require("crypto"), 1);
|
|
@@ -1292,8 +1292,83 @@ function handleCodexOAuthStatus(sessionId, deps) {
|
|
|
1292
1292
|
return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
|
|
1293
1293
|
}
|
|
1294
1294
|
|
|
1295
|
+
// src/admin/accountsKimiOAuth.ts
|
|
1296
|
+
var import_subscriptions2 = require("@omnicross/subscriptions");
|
|
1297
|
+
function err2(status, message) {
|
|
1298
|
+
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
1299
|
+
}
|
|
1300
|
+
var DEFAULT_KIMI_OAUTH_TTL_MS = 15 * 6e4;
|
|
1301
|
+
async function handleKimiOAuthStart(deps) {
|
|
1302
|
+
if (deps.kimiSessions.isBusy()) {
|
|
1303
|
+
return err2(409, "a kimi sign-in is already in progress \u2014 finish it in the browser or cancel it");
|
|
1304
|
+
}
|
|
1305
|
+
const fetchImpl = deps.oauthExchangeFetch("kimi");
|
|
1306
|
+
const deviceId = import_subscriptions2.kimiOAuth.generateKimiDeviceId();
|
|
1307
|
+
const fingerprint = import_subscriptions2.kimiOAuth.kimiFingerprintHeaders(deviceId);
|
|
1308
|
+
let authorization;
|
|
1309
|
+
try {
|
|
1310
|
+
authorization = await import_subscriptions2.kimiOAuth.requestDeviceAuthorization(fetchImpl, fingerprint);
|
|
1311
|
+
} catch (e) {
|
|
1312
|
+
const reason = e instanceof Error ? e.message : "device authorization failed";
|
|
1313
|
+
return err2(502, `kimi device authorization failed: ${reason}`);
|
|
1314
|
+
}
|
|
1315
|
+
const { sessionId, signal } = deps.kimiSessions.begin();
|
|
1316
|
+
void runKimiDevicePoll(sessionId, authorization.deviceCode, deviceId, fingerprint, signal, deps).catch(() => deps.kimiSessions.settle(sessionId, "error", "kimi sign-in failed"));
|
|
1317
|
+
return {
|
|
1318
|
+
status: 200,
|
|
1319
|
+
body: {
|
|
1320
|
+
authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
|
|
1321
|
+
userCode: authorization.userCode,
|
|
1322
|
+
sessionId
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
async function runKimiDevicePoll(sessionId, deviceCode, deviceId, fingerprint, signal, deps) {
|
|
1327
|
+
const fetchImpl = deps.oauthExchangeFetch("kimi");
|
|
1328
|
+
const result = await import_subscriptions2.kimiOAuth.awaitDeviceToken(
|
|
1329
|
+
{ userCode: "", deviceCode, verificationUri: "" },
|
|
1330
|
+
fetchImpl,
|
|
1331
|
+
{
|
|
1332
|
+
fingerprint,
|
|
1333
|
+
deadlineMs: DEFAULT_KIMI_OAUTH_TTL_MS,
|
|
1334
|
+
sleep: (ms) => new Promise((resolve11, reject) => {
|
|
1335
|
+
const onAbort = () => {
|
|
1336
|
+
clearTimeout(timer);
|
|
1337
|
+
reject(new Error("login: cancelled"));
|
|
1338
|
+
};
|
|
1339
|
+
const timer = setTimeout(() => {
|
|
1340
|
+
signal.removeEventListener("abort", onAbort);
|
|
1341
|
+
resolve11();
|
|
1342
|
+
}, ms);
|
|
1343
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1344
|
+
})
|
|
1345
|
+
}
|
|
1346
|
+
);
|
|
1347
|
+
const block = {
|
|
1348
|
+
authMethod: "oauth",
|
|
1349
|
+
status: "authorized",
|
|
1350
|
+
accessToken: result.accessToken,
|
|
1351
|
+
refreshToken: result.refreshToken,
|
|
1352
|
+
expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
|
|
1353
|
+
accountId: import_subscriptions2.kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
|
|
1354
|
+
deviceId,
|
|
1355
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1356
|
+
};
|
|
1357
|
+
await deps.subscriptionAccountAppender.appendProviderAccount("kimi", block);
|
|
1358
|
+
deps.kimiSessions.settle(sessionId, "done");
|
|
1359
|
+
}
|
|
1360
|
+
function handleKimiOAuthCancel(sessionId, deps) {
|
|
1361
|
+
if (!deps.kimiSessions.cancel(sessionId)) return err2(404, "unknown or expired kimi sign-in session");
|
|
1362
|
+
return { status: 200, body: { ok: true } };
|
|
1363
|
+
}
|
|
1364
|
+
function handleKimiOAuthStatus(sessionId, deps) {
|
|
1365
|
+
const s = deps.kimiSessions.get(sessionId);
|
|
1366
|
+
if (!s) return err2(404, "unknown or expired kimi sign-in session");
|
|
1367
|
+
return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1295
1370
|
// src/allowance/AccountAllowanceService.ts
|
|
1296
|
-
var
|
|
1371
|
+
var import_AccountAllowanceStore5 = require("@omnicross/core/pipeline/AccountAllowanceStore");
|
|
1297
1372
|
var import_AccountAllowanceScheduling = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
|
|
1298
1373
|
|
|
1299
1374
|
// src/allowance/ClaudeAllowanceCollector.ts
|
|
@@ -1320,13 +1395,11 @@ function secondsUntil(instant, now) {
|
|
|
1320
1395
|
function windowFromPayload(id, payload, now) {
|
|
1321
1396
|
const usedPercent = finitePercent(payload?.utilization);
|
|
1322
1397
|
const resetsAt = isoInstant(payload?.resets_at);
|
|
1323
|
-
const isSonnet = id === "seven-day-sonnet";
|
|
1324
1398
|
const isFiveHour = id === "five-hour";
|
|
1325
1399
|
return {
|
|
1326
1400
|
id,
|
|
1327
|
-
label: isFiveHour ? "5 hours" :
|
|
1328
|
-
scope:
|
|
1329
|
-
modelFamily: isSonnet ? "sonnet" : void 0,
|
|
1401
|
+
label: isFiveHour ? "5 hours" : "7 days",
|
|
1402
|
+
scope: "all",
|
|
1330
1403
|
usedPercent,
|
|
1331
1404
|
windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
|
|
1332
1405
|
resetsAt,
|
|
@@ -1334,6 +1407,44 @@ function windowFromPayload(id, payload, now) {
|
|
|
1334
1407
|
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
1335
1408
|
};
|
|
1336
1409
|
}
|
|
1410
|
+
function limitEntryWindow(entries, kind) {
|
|
1411
|
+
const entry = entries.find((candidate) => candidate.kind === kind);
|
|
1412
|
+
if (!entry) return void 0;
|
|
1413
|
+
return { utilization: entry.percent, resets_at: entry.resets_at };
|
|
1414
|
+
}
|
|
1415
|
+
function slugifyDisplayName(name) {
|
|
1416
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1417
|
+
}
|
|
1418
|
+
function scopedWeeklyWindows(entries, now) {
|
|
1419
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1420
|
+
const windows = [];
|
|
1421
|
+
for (const entry of entries) {
|
|
1422
|
+
if (entry.kind !== "weekly_scoped") continue;
|
|
1423
|
+
const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
|
|
1424
|
+
if (!displayName) continue;
|
|
1425
|
+
const slug = slugifyDisplayName(displayName);
|
|
1426
|
+
if (!slug || seen.has(slug)) continue;
|
|
1427
|
+
seen.add(slug);
|
|
1428
|
+
const usedPercent = finitePercent(entry.percent);
|
|
1429
|
+
const resetsAt = isoInstant(entry.resets_at);
|
|
1430
|
+
windows.push({
|
|
1431
|
+
id: `seven-day-${slug}`,
|
|
1432
|
+
label: `7 days \xB7 ${displayName}`,
|
|
1433
|
+
scope: "model-family",
|
|
1434
|
+
modelFamily: slug,
|
|
1435
|
+
usedPercent,
|
|
1436
|
+
windowMinutes: 7 * 24 * 60,
|
|
1437
|
+
resetsAt,
|
|
1438
|
+
remainingSeconds: secondsUntil(resetsAt, now),
|
|
1439
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
return windows;
|
|
1443
|
+
}
|
|
1444
|
+
function parseLimitEntries(raw) {
|
|
1445
|
+
if (!Array.isArray(raw)) return [];
|
|
1446
|
+
return raw.filter((entry) => !!entry && typeof entry === "object");
|
|
1447
|
+
}
|
|
1337
1448
|
function emptyClaudeWindows(state) {
|
|
1338
1449
|
return [
|
|
1339
1450
|
{
|
|
@@ -1351,15 +1462,6 @@ function emptyClaudeWindows(state) {
|
|
|
1351
1462
|
usedPercent: null,
|
|
1352
1463
|
windowMinutes: 7 * 24 * 60,
|
|
1353
1464
|
state
|
|
1354
|
-
},
|
|
1355
|
-
{
|
|
1356
|
-
id: "seven-day-sonnet",
|
|
1357
|
-
label: "7 days \xB7 Sonnet",
|
|
1358
|
-
scope: "model-family",
|
|
1359
|
-
modelFamily: "sonnet",
|
|
1360
|
-
usedPercent: null,
|
|
1361
|
-
windowMinutes: 7 * 24 * 60,
|
|
1362
|
-
state
|
|
1363
1465
|
}
|
|
1364
1466
|
];
|
|
1365
1467
|
}
|
|
@@ -1440,6 +1542,9 @@ var ClaudeAllowanceCollector = class {
|
|
|
1440
1542
|
}
|
|
1441
1543
|
const now = this.now();
|
|
1442
1544
|
const usage = payload;
|
|
1545
|
+
const limitEntries = parseLimitEntries(usage.limits);
|
|
1546
|
+
const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
|
|
1547
|
+
const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
|
|
1443
1548
|
const snapshot = {
|
|
1444
1549
|
providerId: "claude",
|
|
1445
1550
|
accountId,
|
|
@@ -1447,10 +1552,10 @@ var ClaudeAllowanceCollector = class {
|
|
|
1447
1552
|
observedAt: new Date(now).toISOString(),
|
|
1448
1553
|
expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1449
1554
|
windows: [
|
|
1450
|
-
windowFromPayload("five-hour",
|
|
1451
|
-
windowFromPayload("seven-day",
|
|
1452
|
-
|
|
1453
|
-
]
|
|
1555
|
+
windowFromPayload("five-hour", fiveHour, now),
|
|
1556
|
+
windowFromPayload("seven-day", sevenDay, now),
|
|
1557
|
+
...scopedWeeklyWindows(limitEntries, now)
|
|
1558
|
+
].slice(0, 8)
|
|
1454
1559
|
};
|
|
1455
1560
|
this.store.set(snapshot);
|
|
1456
1561
|
return snapshot;
|
|
@@ -1507,6 +1612,601 @@ var ClaudeAllowanceCollector = class {
|
|
|
1507
1612
|
}
|
|
1508
1613
|
};
|
|
1509
1614
|
|
|
1615
|
+
// src/allowance/CodexAllowanceCollector.ts
|
|
1616
|
+
var import_AccountAllowanceStore2 = require("@omnicross/core/pipeline/AccountAllowanceStore");
|
|
1617
|
+
var import_upstreamFetch2 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
1618
|
+
var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
1619
|
+
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
1620
|
+
var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
|
|
1621
|
+
function finiteNumber(value) {
|
|
1622
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
1623
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1624
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
1625
|
+
}
|
|
1626
|
+
function finitePercent2(value) {
|
|
1627
|
+
const parsed = finiteNumber(value);
|
|
1628
|
+
return parsed !== null && parsed <= 100 ? parsed : null;
|
|
1629
|
+
}
|
|
1630
|
+
function epochMs(value) {
|
|
1631
|
+
return value > 1e11 ? value : value * 1e3;
|
|
1632
|
+
}
|
|
1633
|
+
function secondsUntil2(instant, now) {
|
|
1634
|
+
if (!instant) return void 0;
|
|
1635
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
1636
|
+
}
|
|
1637
|
+
function decodeJwtClaims(token) {
|
|
1638
|
+
const parts = token.split(".");
|
|
1639
|
+
if (parts.length !== 3) return void 0;
|
|
1640
|
+
try {
|
|
1641
|
+
const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
1642
|
+
const parsed = JSON.parse(json2);
|
|
1643
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
1644
|
+
} catch {
|
|
1645
|
+
return void 0;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
function chatgptAccountIdFromClaims(claims) {
|
|
1649
|
+
const auth = claims?.["https://api.openai.com/auth"];
|
|
1650
|
+
if (!auth || typeof auth !== "object") return void 0;
|
|
1651
|
+
const accountId = auth.chatgpt_account_id;
|
|
1652
|
+
return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
|
|
1653
|
+
}
|
|
1654
|
+
function resolveCodexChatGptAccountId(tokens) {
|
|
1655
|
+
if (tokens.accountId?.trim()) return tokens.accountId.trim();
|
|
1656
|
+
if (tokens.idToken) {
|
|
1657
|
+
const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
|
|
1658
|
+
if (fromIdToken) return fromIdToken;
|
|
1659
|
+
}
|
|
1660
|
+
if (tokens.accessToken) {
|
|
1661
|
+
return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
|
|
1662
|
+
}
|
|
1663
|
+
return void 0;
|
|
1664
|
+
}
|
|
1665
|
+
function windowFromPayload2(id, payload, now) {
|
|
1666
|
+
const usedPercent = finitePercent2(payload?.used_percent);
|
|
1667
|
+
const resetAtSeconds = finiteNumber(payload?.reset_at);
|
|
1668
|
+
const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
|
|
1669
|
+
const windowSeconds = finiteNumber(payload?.limit_window_seconds);
|
|
1670
|
+
const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
|
|
1671
|
+
const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
|
|
1672
|
+
return {
|
|
1673
|
+
id,
|
|
1674
|
+
label: id === "primary" ? "Primary" : "Secondary",
|
|
1675
|
+
scope: "all",
|
|
1676
|
+
usedPercent,
|
|
1677
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
1678
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
1679
|
+
remainingSeconds: secondsUntil2(resetsAt, now),
|
|
1680
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
var CodexAllowanceCollector = class {
|
|
1684
|
+
constructor(credentials, store = (0, import_AccountAllowanceStore2.getSharedAccountAllowanceStore)(), fetchImpl = (url, init, accountId) => (0, import_upstreamFetch2.fetchUpstream)(url, init, { providerId: "codex", accountId, redactBodies: true }), now = Date.now) {
|
|
1685
|
+
this.credentials = credentials;
|
|
1686
|
+
this.store = store;
|
|
1687
|
+
this.fetchImpl = fetchImpl;
|
|
1688
|
+
this.now = now;
|
|
1689
|
+
}
|
|
1690
|
+
credentials;
|
|
1691
|
+
store;
|
|
1692
|
+
fetchImpl;
|
|
1693
|
+
now;
|
|
1694
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1695
|
+
async collectMany(accounts, options = {}) {
|
|
1696
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
1697
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
1698
|
+
}
|
|
1699
|
+
collect(account, options = {}) {
|
|
1700
|
+
const now = this.now();
|
|
1701
|
+
const unsupported = account.tokens.authMethod !== "oauth";
|
|
1702
|
+
if (unsupported) {
|
|
1703
|
+
const existing = this.store.get("codex", account.id, now);
|
|
1704
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
1705
|
+
return Promise.resolve(existing);
|
|
1706
|
+
}
|
|
1707
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
1708
|
+
this.store.set(snapshot);
|
|
1709
|
+
return Promise.resolve(snapshot);
|
|
1710
|
+
}
|
|
1711
|
+
const cached = this.store.get("codex", account.id, now);
|
|
1712
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
1713
|
+
return Promise.resolve(cached);
|
|
1714
|
+
}
|
|
1715
|
+
const running = this.inFlight.get(account.id);
|
|
1716
|
+
if (running) return running;
|
|
1717
|
+
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));
|
|
1718
|
+
this.inFlight.set(account.id, promise);
|
|
1719
|
+
return promise;
|
|
1720
|
+
}
|
|
1721
|
+
/**
|
|
1722
|
+
* A response-header snapshot stays a valid cache hit only while fresh; an
|
|
1723
|
+
* active oauth-usage snapshot is honored on the same 5-minute cadence as
|
|
1724
|
+
* Claude's (the poll is cheap and quota is the scheduling input).
|
|
1725
|
+
*/
|
|
1726
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
1727
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
1728
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
1729
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
1730
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
1731
|
+
}
|
|
1732
|
+
async fetchAccount(accountId, tokens) {
|
|
1733
|
+
let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
|
|
1734
|
+
if (!accessToken) {
|
|
1735
|
+
return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
|
|
1736
|
+
}
|
|
1737
|
+
let response = await this.request(accountId, accessToken, tokens);
|
|
1738
|
+
if (response.status === 401) {
|
|
1739
|
+
const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
|
|
1740
|
+
if (!refreshed) {
|
|
1741
|
+
return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
|
|
1742
|
+
}
|
|
1743
|
+
accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
|
|
1744
|
+
if (!accessToken) {
|
|
1745
|
+
return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
|
|
1746
|
+
}
|
|
1747
|
+
response = await this.request(accountId, accessToken, tokens);
|
|
1748
|
+
}
|
|
1749
|
+
if (response.status === 403) {
|
|
1750
|
+
const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
|
|
1751
|
+
this.store.set(snapshot2);
|
|
1752
|
+
return snapshot2;
|
|
1753
|
+
}
|
|
1754
|
+
if (!response.ok) {
|
|
1755
|
+
return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
|
|
1756
|
+
}
|
|
1757
|
+
let payload;
|
|
1758
|
+
try {
|
|
1759
|
+
payload = await response.json();
|
|
1760
|
+
} catch {
|
|
1761
|
+
return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
|
|
1762
|
+
}
|
|
1763
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
1764
|
+
return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
|
|
1765
|
+
}
|
|
1766
|
+
const now = this.now();
|
|
1767
|
+
const usage = payload.rate_limit;
|
|
1768
|
+
const previous = this.store.get("codex", accountId, now);
|
|
1769
|
+
const snapshot = {
|
|
1770
|
+
providerId: "codex",
|
|
1771
|
+
accountId,
|
|
1772
|
+
source: "oauth-usage-api",
|
|
1773
|
+
observedAt: new Date(now).toISOString(),
|
|
1774
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1775
|
+
windows: [
|
|
1776
|
+
windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
|
|
1777
|
+
windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
|
|
1778
|
+
],
|
|
1779
|
+
// The wham payload has no ratio field; keep the passively-observed value.
|
|
1780
|
+
...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
|
|
1781
|
+
};
|
|
1782
|
+
this.store.set(snapshot);
|
|
1783
|
+
return snapshot;
|
|
1784
|
+
}
|
|
1785
|
+
request(accountId, accessToken, tokens) {
|
|
1786
|
+
const headers = {
|
|
1787
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1788
|
+
Accept: "application/json",
|
|
1789
|
+
"User-Agent": CODEX_CLI_USER_AGENT
|
|
1790
|
+
};
|
|
1791
|
+
const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
|
|
1792
|
+
if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
|
|
1793
|
+
return this.fetchImpl(CODEX_USAGE_URL, {
|
|
1794
|
+
method: "GET",
|
|
1795
|
+
headers,
|
|
1796
|
+
signal: AbortSignal.timeout(15e3)
|
|
1797
|
+
}, accountId);
|
|
1798
|
+
}
|
|
1799
|
+
failureSnapshot(accountId, code, now) {
|
|
1800
|
+
const existing = this.store.get("codex", accountId, now);
|
|
1801
|
+
const snapshot = existing ? {
|
|
1802
|
+
...existing,
|
|
1803
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1804
|
+
windows: existing.windows.map((window) => ({
|
|
1805
|
+
...window,
|
|
1806
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
1807
|
+
})),
|
|
1808
|
+
lastErrorCode: code
|
|
1809
|
+
} : {
|
|
1810
|
+
providerId: "codex",
|
|
1811
|
+
accountId,
|
|
1812
|
+
source: "oauth-usage-api",
|
|
1813
|
+
observedAt: new Date(now).toISOString(),
|
|
1814
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1815
|
+
windows: [
|
|
1816
|
+
{ id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
|
|
1817
|
+
{ id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
|
|
1818
|
+
],
|
|
1819
|
+
lastErrorCode: code
|
|
1820
|
+
};
|
|
1821
|
+
this.store.set(snapshot);
|
|
1822
|
+
return snapshot;
|
|
1823
|
+
}
|
|
1824
|
+
unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
|
|
1825
|
+
return {
|
|
1826
|
+
providerId: "codex",
|
|
1827
|
+
accountId,
|
|
1828
|
+
source: "oauth-usage-api",
|
|
1829
|
+
observedAt: new Date(now).toISOString(),
|
|
1830
|
+
windows: [
|
|
1831
|
+
{ id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
|
|
1832
|
+
{ id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
|
|
1833
|
+
],
|
|
1834
|
+
lastErrorCode: code
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
|
|
1839
|
+
// src/allowance/KimiAllowanceCollector.ts
|
|
1840
|
+
var import_AccountAllowanceStore3 = require("@omnicross/core/pipeline/AccountAllowanceStore");
|
|
1841
|
+
var import_upstreamFetch3 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
1842
|
+
var import_subscriptions3 = require("@omnicross/subscriptions");
|
|
1843
|
+
var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
1844
|
+
var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
|
|
1845
|
+
function finiteNumber2(value) {
|
|
1846
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
1847
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
1848
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
1849
|
+
}
|
|
1850
|
+
function isRecord(value) {
|
|
1851
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1852
|
+
}
|
|
1853
|
+
function parseResetMs(row, nowMs) {
|
|
1854
|
+
for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
|
|
1855
|
+
const value = row[key];
|
|
1856
|
+
if (typeof value === "string" && value.trim()) {
|
|
1857
|
+
const parsed = Date.parse(value);
|
|
1858
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
1859
|
+
}
|
|
1860
|
+
const numeric = finiteNumber2(value);
|
|
1861
|
+
if (numeric !== void 0 && numeric > 1e9) {
|
|
1862
|
+
return numeric > 1e12 ? numeric : numeric * 1e3;
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
|
|
1866
|
+
const seconds = finiteNumber2(row[key]);
|
|
1867
|
+
if (seconds !== void 0) return nowMs + seconds * 1e3;
|
|
1868
|
+
}
|
|
1869
|
+
return void 0;
|
|
1870
|
+
}
|
|
1871
|
+
var MINUTE_MS = 6e4;
|
|
1872
|
+
var HOUR_MS = 36e5;
|
|
1873
|
+
var DAY_MS = 864e5;
|
|
1874
|
+
function canonicalWindow(durationMs) {
|
|
1875
|
+
if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
|
|
1876
|
+
if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
|
|
1877
|
+
if (durationMs > 0 && durationMs % DAY_MS === 0) {
|
|
1878
|
+
const days = durationMs / DAY_MS;
|
|
1879
|
+
return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
|
|
1880
|
+
}
|
|
1881
|
+
if (durationMs > 0 && durationMs % HOUR_MS === 0) {
|
|
1882
|
+
const hours = durationMs / HOUR_MS;
|
|
1883
|
+
return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
|
|
1884
|
+
}
|
|
1885
|
+
return void 0;
|
|
1886
|
+
}
|
|
1887
|
+
function secondsUntil3(instant, now) {
|
|
1888
|
+
if (!instant) return void 0;
|
|
1889
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
1890
|
+
}
|
|
1891
|
+
function windowFromRow(row, fallback, now) {
|
|
1892
|
+
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;
|
|
1893
|
+
const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
|
|
1894
|
+
return {
|
|
1895
|
+
id: fallback.id,
|
|
1896
|
+
label: fallback.label,
|
|
1897
|
+
scope: "all",
|
|
1898
|
+
usedPercent,
|
|
1899
|
+
windowMinutes: fallback.minutes,
|
|
1900
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
1901
|
+
remainingSeconds: secondsUntil3(resetsAt, now),
|
|
1902
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
function parseKimiUsagePayload(payload, now) {
|
|
1906
|
+
if (!isRecord(payload)) return [];
|
|
1907
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1908
|
+
const rowFrom = (data) => {
|
|
1909
|
+
const limit = finiteNumber2(data["limit"]);
|
|
1910
|
+
let used = finiteNumber2(data["used"]);
|
|
1911
|
+
const remaining = finiteNumber2(data["remaining"]);
|
|
1912
|
+
if (used === void 0 && remaining !== void 0 && limit !== void 0) {
|
|
1913
|
+
used = limit - remaining;
|
|
1914
|
+
}
|
|
1915
|
+
let windowDurationMs;
|
|
1916
|
+
const windowData = isRecord(data["window"]) ? data["window"] : void 0;
|
|
1917
|
+
const duration = finiteNumber2(windowData?.["duration"]);
|
|
1918
|
+
const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
|
|
1919
|
+
if (duration !== void 0) {
|
|
1920
|
+
if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
|
|
1921
|
+
else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
|
|
1922
|
+
else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
|
|
1923
|
+
else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
|
|
1924
|
+
else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
|
|
1925
|
+
}
|
|
1926
|
+
const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
|
|
1927
|
+
return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
|
|
1928
|
+
};
|
|
1929
|
+
if (isRecord(payload["usage"])) {
|
|
1930
|
+
const row = rowFrom(payload["usage"]);
|
|
1931
|
+
const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
|
|
1932
|
+
byId.set("seven-day", window);
|
|
1933
|
+
}
|
|
1934
|
+
if (Array.isArray(payload["limits"])) {
|
|
1935
|
+
for (const item of payload["limits"]) {
|
|
1936
|
+
if (!isRecord(item)) continue;
|
|
1937
|
+
const detail = isRecord(item["detail"]) ? item["detail"] : item;
|
|
1938
|
+
const row = rowFrom(detail);
|
|
1939
|
+
const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
|
|
1940
|
+
if (!canonical) continue;
|
|
1941
|
+
const window = windowFromRow(row, canonical, now);
|
|
1942
|
+
const existing = byId.get(canonical.id);
|
|
1943
|
+
if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
|
|
1944
|
+
byId.set(canonical.id, window);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
|
|
1949
|
+
}
|
|
1950
|
+
var KimiAllowanceCollector = class {
|
|
1951
|
+
constructor(credentials, store = (0, import_AccountAllowanceStore3.getSharedAccountAllowanceStore)(), fetchImpl = (url, init, accountId) => (0, import_upstreamFetch3.fetchUpstream)(url, init, { providerId: "kimi", accountId, redactBodies: true }), now = Date.now) {
|
|
1952
|
+
this.credentials = credentials;
|
|
1953
|
+
this.store = store;
|
|
1954
|
+
this.fetchImpl = fetchImpl;
|
|
1955
|
+
this.now = now;
|
|
1956
|
+
}
|
|
1957
|
+
credentials;
|
|
1958
|
+
store;
|
|
1959
|
+
fetchImpl;
|
|
1960
|
+
now;
|
|
1961
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1962
|
+
async collectMany(accounts, options = {}) {
|
|
1963
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
1964
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
1965
|
+
}
|
|
1966
|
+
collect(account, options = {}) {
|
|
1967
|
+
const now = this.now();
|
|
1968
|
+
if (account.tokens.authMethod !== "oauth") {
|
|
1969
|
+
const existing = this.store.get("kimi", account.id, now);
|
|
1970
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
1971
|
+
return Promise.resolve(existing);
|
|
1972
|
+
}
|
|
1973
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
1974
|
+
this.store.set(snapshot);
|
|
1975
|
+
return Promise.resolve(snapshot);
|
|
1976
|
+
}
|
|
1977
|
+
const cached = this.store.get("kimi", account.id, now);
|
|
1978
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
1979
|
+
return Promise.resolve(cached);
|
|
1980
|
+
}
|
|
1981
|
+
const running = this.inFlight.get(account.id);
|
|
1982
|
+
if (running) return running;
|
|
1983
|
+
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));
|
|
1984
|
+
this.inFlight.set(account.id, promise);
|
|
1985
|
+
return promise;
|
|
1986
|
+
}
|
|
1987
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
1988
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
1989
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
1990
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
1991
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
1992
|
+
}
|
|
1993
|
+
async fetchAccount(accountId, tokens) {
|
|
1994
|
+
let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
|
|
1995
|
+
if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
|
|
1996
|
+
let response = await this.request(accountId, accessToken, tokens);
|
|
1997
|
+
if (response.status === 401) {
|
|
1998
|
+
const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
|
|
1999
|
+
if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
|
|
2000
|
+
accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
|
|
2001
|
+
if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
|
|
2002
|
+
response = await this.request(accountId, accessToken, tokens);
|
|
2003
|
+
}
|
|
2004
|
+
if (response.status === 403) {
|
|
2005
|
+
const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
|
|
2006
|
+
this.store.set(snapshot2);
|
|
2007
|
+
return snapshot2;
|
|
2008
|
+
}
|
|
2009
|
+
if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
|
|
2010
|
+
let payload;
|
|
2011
|
+
try {
|
|
2012
|
+
payload = await response.json();
|
|
2013
|
+
} catch {
|
|
2014
|
+
return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
|
|
2015
|
+
}
|
|
2016
|
+
const now = this.now();
|
|
2017
|
+
const windows = parseKimiUsagePayload(payload, now);
|
|
2018
|
+
const snapshot = {
|
|
2019
|
+
providerId: "kimi",
|
|
2020
|
+
accountId,
|
|
2021
|
+
source: "oauth-usage-api",
|
|
2022
|
+
observedAt: new Date(now).toISOString(),
|
|
2023
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
2024
|
+
windows: windows.length > 0 ? windows : [
|
|
2025
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
2026
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
2027
|
+
],
|
|
2028
|
+
...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
|
|
2029
|
+
};
|
|
2030
|
+
this.store.set(snapshot);
|
|
2031
|
+
return snapshot;
|
|
2032
|
+
}
|
|
2033
|
+
request(accountId, accessToken, tokens) {
|
|
2034
|
+
return this.fetchImpl(KIMI_USAGE_URL, {
|
|
2035
|
+
method: "GET",
|
|
2036
|
+
headers: {
|
|
2037
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2038
|
+
Accept: "application/json",
|
|
2039
|
+
...(0, import_subscriptions3.kimiFingerprintHeaders)(tokens.deviceId)
|
|
2040
|
+
},
|
|
2041
|
+
signal: AbortSignal.timeout(15e3)
|
|
2042
|
+
}, accountId);
|
|
2043
|
+
}
|
|
2044
|
+
failureSnapshot(accountId, code, now) {
|
|
2045
|
+
const existing = this.store.get("kimi", accountId, now);
|
|
2046
|
+
const snapshot = existing ? {
|
|
2047
|
+
...existing,
|
|
2048
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
2049
|
+
windows: existing.windows.map((window) => ({
|
|
2050
|
+
...window,
|
|
2051
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
2052
|
+
})),
|
|
2053
|
+
lastErrorCode: code
|
|
2054
|
+
} : {
|
|
2055
|
+
providerId: "kimi",
|
|
2056
|
+
accountId,
|
|
2057
|
+
source: "oauth-usage-api",
|
|
2058
|
+
observedAt: new Date(now).toISOString(),
|
|
2059
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
2060
|
+
windows: [
|
|
2061
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
2062
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
2063
|
+
],
|
|
2064
|
+
lastErrorCode: code
|
|
2065
|
+
};
|
|
2066
|
+
this.store.set(snapshot);
|
|
2067
|
+
return snapshot;
|
|
2068
|
+
}
|
|
2069
|
+
unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
|
|
2070
|
+
return {
|
|
2071
|
+
providerId: "kimi",
|
|
2072
|
+
accountId,
|
|
2073
|
+
source: "oauth-usage-api",
|
|
2074
|
+
observedAt: new Date(now).toISOString(),
|
|
2075
|
+
windows: [
|
|
2076
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
|
|
2077
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
|
|
2078
|
+
],
|
|
2079
|
+
lastErrorCode: code
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
};
|
|
2083
|
+
|
|
2084
|
+
// src/allowance/OpenCodeGoAllowanceCollector.ts
|
|
2085
|
+
var import_AccountAllowanceStore4 = require("@omnicross/core/pipeline/AccountAllowanceStore");
|
|
2086
|
+
var import_upstreamFetch4 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
2087
|
+
var import_subscriptions4 = require("@omnicross/subscriptions");
|
|
2088
|
+
var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
2089
|
+
var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
|
|
2090
|
+
function finitePercent3(value) {
|
|
2091
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
2092
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
2093
|
+
return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
|
|
2094
|
+
}
|
|
2095
|
+
function isoInstant2(value) {
|
|
2096
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
2097
|
+
const time = Date.parse(value);
|
|
2098
|
+
return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
|
|
2099
|
+
}
|
|
2100
|
+
function secondsUntil4(instant, now) {
|
|
2101
|
+
if (!instant) return void 0;
|
|
2102
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
2103
|
+
}
|
|
2104
|
+
function windowFromPayload3(id, label, minutes, payload, now) {
|
|
2105
|
+
const statusRateLimited = payload?.status === "rate-limited";
|
|
2106
|
+
const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
|
|
2107
|
+
const resetsAt = isoInstant2(payload?.resetsAt);
|
|
2108
|
+
return {
|
|
2109
|
+
id,
|
|
2110
|
+
label,
|
|
2111
|
+
scope: "all",
|
|
2112
|
+
usedPercent,
|
|
2113
|
+
windowMinutes: minutes,
|
|
2114
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
2115
|
+
remainingSeconds: secondsUntil4(resetsAt, now),
|
|
2116
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
var OpenCodeGoAllowanceCollector = class {
|
|
2120
|
+
constructor(credentials, store = (0, import_AccountAllowanceStore4.getSharedAccountAllowanceStore)(), fetchImpl = (url, init, accountId) => (0, import_upstreamFetch4.fetchUpstream)(url, init, { providerId: "opencodego", accountId, redactBodies: true }), now = Date.now) {
|
|
2121
|
+
this.credentials = credentials;
|
|
2122
|
+
this.store = store;
|
|
2123
|
+
this.fetchImpl = fetchImpl;
|
|
2124
|
+
this.now = now;
|
|
2125
|
+
}
|
|
2126
|
+
credentials;
|
|
2127
|
+
store;
|
|
2128
|
+
fetchImpl;
|
|
2129
|
+
now;
|
|
2130
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
2131
|
+
async collectMany(accounts, options = {}) {
|
|
2132
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
2133
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
2134
|
+
}
|
|
2135
|
+
collect(account, options = {}) {
|
|
2136
|
+
const now = this.now();
|
|
2137
|
+
const cached = this.store.get("opencodego", account.id, now);
|
|
2138
|
+
if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
|
|
2139
|
+
return Promise.resolve(cached);
|
|
2140
|
+
}
|
|
2141
|
+
const running = this.inFlight.get(account.id);
|
|
2142
|
+
if (running) return running;
|
|
2143
|
+
const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
|
|
2144
|
+
this.inFlight.set(account.id, promise);
|
|
2145
|
+
return promise;
|
|
2146
|
+
}
|
|
2147
|
+
async fetchAccount(account) {
|
|
2148
|
+
const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
|
|
2149
|
+
if (!apiKey) return this.failureSnapshot(account.id, this.now());
|
|
2150
|
+
const base = account.tokens.baseUrl ? (0, import_subscriptions4.normalizeOpenCodeGoBaseUrl)(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
|
|
2151
|
+
const response = await this.fetchImpl(`${base}/v1/usage`, {
|
|
2152
|
+
method: "GET",
|
|
2153
|
+
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
2154
|
+
signal: AbortSignal.timeout(15e3)
|
|
2155
|
+
}, account.id);
|
|
2156
|
+
if (response.status === 401 || response.status === 403) {
|
|
2157
|
+
return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
|
|
2158
|
+
}
|
|
2159
|
+
if (!response.ok) return this.failureSnapshot(account.id, this.now());
|
|
2160
|
+
let payload;
|
|
2161
|
+
try {
|
|
2162
|
+
payload = await response.json();
|
|
2163
|
+
} catch {
|
|
2164
|
+
return this.failureSnapshot(account.id, this.now());
|
|
2165
|
+
}
|
|
2166
|
+
const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
|
|
2167
|
+
const now = this.now();
|
|
2168
|
+
const snapshot = {
|
|
2169
|
+
providerId: "opencodego",
|
|
2170
|
+
accountId: account.id,
|
|
2171
|
+
source: "oauth-usage-api",
|
|
2172
|
+
observedAt: new Date(now).toISOString(),
|
|
2173
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
2174
|
+
// Monthly deliberately omitted (module doc).
|
|
2175
|
+
windows: [
|
|
2176
|
+
windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
|
|
2177
|
+
windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
|
|
2178
|
+
]
|
|
2179
|
+
};
|
|
2180
|
+
this.store.set(snapshot);
|
|
2181
|
+
return snapshot;
|
|
2182
|
+
}
|
|
2183
|
+
failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
|
|
2184
|
+
const existing = this.store.get("opencodego", accountId, now);
|
|
2185
|
+
const snapshot = existing ? {
|
|
2186
|
+
...existing,
|
|
2187
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
2188
|
+
windows: existing.windows.map((window) => ({
|
|
2189
|
+
...window,
|
|
2190
|
+
state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
|
|
2191
|
+
})),
|
|
2192
|
+
lastErrorCode: code
|
|
2193
|
+
} : {
|
|
2194
|
+
providerId: "opencodego",
|
|
2195
|
+
accountId,
|
|
2196
|
+
source: "oauth-usage-api",
|
|
2197
|
+
observedAt: new Date(now).toISOString(),
|
|
2198
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
2199
|
+
windows: [
|
|
2200
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
2201
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
2202
|
+
],
|
|
2203
|
+
lastErrorCode: code
|
|
2204
|
+
};
|
|
2205
|
+
this.store.set(snapshot);
|
|
2206
|
+
return snapshot;
|
|
2207
|
+
}
|
|
2208
|
+
};
|
|
2209
|
+
|
|
1510
2210
|
// src/allowance/AccountAllowanceService.ts
|
|
1511
2211
|
function codexUnavailable(accountId, now) {
|
|
1512
2212
|
return {
|
|
@@ -1522,26 +2222,30 @@ function codexUnavailable(accountId, now) {
|
|
|
1522
2222
|
};
|
|
1523
2223
|
}
|
|
1524
2224
|
var AccountAllowanceService = class {
|
|
1525
|
-
constructor(credentials, store = (0,
|
|
2225
|
+
constructor(credentials, store = (0, import_AccountAllowanceStore5.getSharedAccountAllowanceStore)(), collector, codexCollector, kimiCollector, opencodegoCollector, now = Date.now) {
|
|
1526
2226
|
this.credentials = credentials;
|
|
1527
2227
|
this.store = store;
|
|
1528
2228
|
this.now = now;
|
|
1529
2229
|
this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
|
|
2230
|
+
this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
|
|
2231
|
+
this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
|
|
2232
|
+
this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
|
|
1530
2233
|
}
|
|
1531
2234
|
credentials;
|
|
1532
2235
|
store;
|
|
1533
2236
|
now;
|
|
1534
2237
|
claudeCollector;
|
|
2238
|
+
codexCollector;
|
|
2239
|
+
kimiCollector;
|
|
2240
|
+
opencodegoCollector;
|
|
1535
2241
|
/**
|
|
1536
|
-
* Read all/filtered snapshots. Claude's five-minute
|
|
1537
|
-
*
|
|
2242
|
+
* Read all/filtered snapshots. Claude's and Codex's five-minute caches are
|
|
2243
|
+
* refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
|
|
2244
|
+
* passive `x-codex-*` header tap still feeds mid-flight updates).
|
|
1538
2245
|
*/
|
|
1539
2246
|
async list(filter = {}) {
|
|
1540
2247
|
const config = await this.credentials.getFullConfig();
|
|
1541
|
-
this.store.pruneToKnownAccounts(
|
|
1542
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
1543
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
1544
|
-
]);
|
|
2248
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1545
2249
|
const wantsClaude = !filter.providerId || filter.providerId === "claude";
|
|
1546
2250
|
const claudeAccounts = (config.claudeAccounts ?? []).filter(
|
|
1547
2251
|
(account) => !filter.accountId || account.id === filter.accountId
|
|
@@ -1552,39 +2256,90 @@ var AccountAllowanceService = class {
|
|
|
1552
2256
|
(account) => !filter.accountId || account.id === filter.accountId
|
|
1553
2257
|
);
|
|
1554
2258
|
if (wantsCodex) {
|
|
2259
|
+
await this.codexCollector.collectMany(codexAccounts);
|
|
1555
2260
|
for (const account of codexAccounts) {
|
|
1556
2261
|
if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
|
|
1557
2262
|
}
|
|
1558
2263
|
}
|
|
2264
|
+
const wantsKimi = !filter.providerId || filter.providerId === "kimi";
|
|
2265
|
+
const kimiAccounts = (config.kimiAccounts ?? []).filter(
|
|
2266
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
2267
|
+
);
|
|
2268
|
+
if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
|
|
2269
|
+
const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
|
|
2270
|
+
const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
|
|
2271
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
2272
|
+
);
|
|
2273
|
+
if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
|
|
1559
2274
|
const known = /* @__PURE__ */ new Set();
|
|
1560
2275
|
if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
|
|
1561
2276
|
if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
|
|
2277
|
+
if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
|
|
2278
|
+
if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
|
|
1562
2279
|
return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
|
|
1563
2280
|
}
|
|
2281
|
+
knownAccounts(config) {
|
|
2282
|
+
return [
|
|
2283
|
+
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
2284
|
+
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
|
|
2285
|
+
...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
|
|
2286
|
+
...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id }))
|
|
2287
|
+
];
|
|
2288
|
+
}
|
|
1564
2289
|
/** Force-refresh Claude usage for one account or every stored Claude account. */
|
|
1565
2290
|
async refreshClaude(accountId) {
|
|
1566
2291
|
const config = await this.credentials.getFullConfig();
|
|
1567
|
-
this.store.pruneToKnownAccounts(
|
|
1568
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
1569
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
1570
|
-
]);
|
|
2292
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1571
2293
|
const accounts = (config.claudeAccounts ?? []).filter(
|
|
1572
2294
|
(account) => !accountId || account.id === accountId
|
|
1573
2295
|
);
|
|
1574
2296
|
return this.claudeCollector.collectMany(accounts, { force: true });
|
|
1575
2297
|
}
|
|
1576
2298
|
/**
|
|
1577
|
-
*
|
|
1578
|
-
*
|
|
1579
|
-
*
|
|
2299
|
+
* Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
|
|
2300
|
+
* every stored Codex account. Replaces the old probe-request workaround —
|
|
2301
|
+
* no quota is spent reading the usage endpoint.
|
|
2302
|
+
*/
|
|
2303
|
+
async refreshCodex(accountId) {
|
|
2304
|
+
const config = await this.credentials.getFullConfig();
|
|
2305
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
2306
|
+
const accounts = (config.codexAccounts ?? []).filter(
|
|
2307
|
+
(account) => !accountId || account.id === accountId
|
|
2308
|
+
);
|
|
2309
|
+
return this.codexCollector.collectMany(accounts, { force: true });
|
|
2310
|
+
}
|
|
2311
|
+
/** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
|
|
2312
|
+
async refreshOpenCodeGo(accountId) {
|
|
2313
|
+
const config = await this.credentials.getFullConfig();
|
|
2314
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
2315
|
+
const accounts = (config.opencodegoAccounts ?? []).filter(
|
|
2316
|
+
(account) => !accountId || account.id === accountId
|
|
2317
|
+
);
|
|
2318
|
+
return this.opencodegoCollector.collectMany(accounts, { force: true });
|
|
2319
|
+
}
|
|
2320
|
+
/** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
|
|
2321
|
+
async refreshKimi(accountId) {
|
|
2322
|
+
const config = await this.credentials.getFullConfig();
|
|
2323
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
2324
|
+
const accounts = (config.kimiAccounts ?? []).filter(
|
|
2325
|
+
(account) => !accountId || account.id === accountId
|
|
2326
|
+
);
|
|
2327
|
+
return this.kimiCollector.collectMany(accounts, { force: true });
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
|
|
2331
|
+
* collectors preserve their cache + per-account in-flight coalescing; a tick
|
|
2332
|
+
* normally performs no network I/O. (Codex joined the warm path when it
|
|
2333
|
+
* gained an active `/wham/usage` collector — the passive `x-codex-*` header
|
|
2334
|
+
* tap alone could not keep the policy fed while idle.)
|
|
1580
2335
|
*/
|
|
1581
2336
|
async maintainClaudeCache(refreshAheadMs) {
|
|
1582
2337
|
const config = await this.credentials.getFullConfig();
|
|
1583
|
-
this.store.pruneToKnownAccounts(
|
|
1584
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
1585
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
1586
|
-
]);
|
|
2338
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1587
2339
|
await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
|
|
2340
|
+
await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
|
|
2341
|
+
await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
|
|
2342
|
+
await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
|
|
1588
2343
|
}
|
|
1589
2344
|
/** Remove a cache row as soon as an account is deleted by the admin path. */
|
|
1590
2345
|
removeAccountSnapshot(providerId, accountId) {
|
|
@@ -1679,7 +2434,7 @@ var ClaudeAllowanceRefreshScheduler = class {
|
|
|
1679
2434
|
var import_node_crypto4 = require("crypto");
|
|
1680
2435
|
var import_node_fs6 = require("fs");
|
|
1681
2436
|
var import_node_path6 = require("path");
|
|
1682
|
-
var
|
|
2437
|
+
var import_AccountAllowanceStore6 = require("@omnicross/core/pipeline/AccountAllowanceStore");
|
|
1683
2438
|
var ACCOUNT_ALLOWANCE_CACHE_VERSION = 1;
|
|
1684
2439
|
var MAX_PERSISTED_ALLOWANCE_SNAPSHOTS = 256;
|
|
1685
2440
|
var MAX_ALLOWANCE_CACHE_BYTES = 1e6;
|
|
@@ -1708,7 +2463,7 @@ var JsonAccountAllowancePersistence = class {
|
|
|
1708
2463
|
save(snapshots) {
|
|
1709
2464
|
const rows = [];
|
|
1710
2465
|
for (const snapshot of snapshots) {
|
|
1711
|
-
const normalized2 = (0,
|
|
2466
|
+
const normalized2 = (0, import_AccountAllowanceStore6.normalizeAccountAllowanceSnapshot)(snapshot);
|
|
1712
2467
|
if (!normalized2) continue;
|
|
1713
2468
|
rows.push(normalized2);
|
|
1714
2469
|
if (rows.length >= MAX_PERSISTED_ALLOWANCE_SNAPSHOTS) break;
|
|
@@ -1984,7 +2739,7 @@ var import_outbound_api5 = require("@omnicross/core/outbound-api");
|
|
|
1984
2739
|
var import_image_generation_types = require("@omnicross/contracts/image-generation-types");
|
|
1985
2740
|
var import_AccountAllowanceScheduling2 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
|
|
1986
2741
|
var import_SubscriptionAccountHealth = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
|
|
1987
|
-
var
|
|
2742
|
+
var import_upstreamFetch7 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
1988
2743
|
|
|
1989
2744
|
// src/image-generation/imagesConfigValidation.ts
|
|
1990
2745
|
var import_outbound_api = require("@omnicross/core/outbound-api");
|
|
@@ -3617,11 +4372,11 @@ function preserveOutboundProxySecrets(incoming, current) {
|
|
|
3617
4372
|
}
|
|
3618
4373
|
|
|
3619
4374
|
// src/proxy/upstreamProxyResolver.ts
|
|
3620
|
-
var
|
|
4375
|
+
var import_upstreamFetch5 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
3621
4376
|
var serverProxy;
|
|
3622
4377
|
function setServerProxyConfig(proxy) {
|
|
3623
4378
|
serverProxy = proxy;
|
|
3624
|
-
(0,
|
|
4379
|
+
(0, import_upstreamFetch5.bumpUpstreamProxyGeneration)();
|
|
3625
4380
|
}
|
|
3626
4381
|
function getServerProxyConfig() {
|
|
3627
4382
|
return serverProxy;
|
|
@@ -3689,14 +4444,15 @@ function createUpstreamProxyResolver(src = {}) {
|
|
|
3689
4444
|
}
|
|
3690
4445
|
|
|
3691
4446
|
// src/admin/accountsOAuth.ts
|
|
3692
|
-
var
|
|
4447
|
+
var import_subscriptions5 = require("@omnicross/subscriptions");
|
|
3693
4448
|
|
|
3694
4449
|
// src/admin/accountsWrite.ts
|
|
3695
4450
|
var VALID_PROVIDER_IDS = [
|
|
3696
4451
|
"claude",
|
|
3697
4452
|
"codex",
|
|
3698
4453
|
"gemini",
|
|
3699
|
-
"opencodego"
|
|
4454
|
+
"opencodego",
|
|
4455
|
+
"kimi"
|
|
3700
4456
|
];
|
|
3701
4457
|
function asSubscriptionProviderId(id) {
|
|
3702
4458
|
return VALID_PROVIDER_IDS.includes(id) ? id : null;
|
|
@@ -3832,6 +4588,18 @@ function validateGemini(body) {
|
|
|
3832
4588
|
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
|
|
3833
4589
|
return out;
|
|
3834
4590
|
}
|
|
4591
|
+
function validateKimi(body) {
|
|
4592
|
+
const authMethod = str(body["authMethod"]);
|
|
4593
|
+
const status = str(body["status"]);
|
|
4594
|
+
if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
|
|
4595
|
+
if (!status || !TOKEN_STATUSES.has(status)) return null;
|
|
4596
|
+
const out = {
|
|
4597
|
+
authMethod,
|
|
4598
|
+
status
|
|
4599
|
+
};
|
|
4600
|
+
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
|
|
4601
|
+
return out;
|
|
4602
|
+
}
|
|
3835
4603
|
function validateOpenCodeGo(body) {
|
|
3836
4604
|
const authMethod = str(body["authMethod"]);
|
|
3837
4605
|
const status = str(body["status"]);
|
|
@@ -3867,6 +4635,8 @@ function validateTokenBody(providerId, body) {
|
|
|
3867
4635
|
return validateGemini(body);
|
|
3868
4636
|
case "opencodego":
|
|
3869
4637
|
return validateOpenCodeGo(body);
|
|
4638
|
+
case "kimi":
|
|
4639
|
+
return validateKimi(body);
|
|
3870
4640
|
default:
|
|
3871
4641
|
return null;
|
|
3872
4642
|
}
|
|
@@ -3896,37 +4666,37 @@ async function statusEntryFor(reader, providerId) {
|
|
|
3896
4666
|
|
|
3897
4667
|
// src/admin/accountsOAuth.ts
|
|
3898
4668
|
var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
|
|
3899
|
-
function
|
|
4669
|
+
function err3(status, message) {
|
|
3900
4670
|
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
3901
4671
|
}
|
|
3902
4672
|
function handleOAuthStart(providerId, deps) {
|
|
3903
4673
|
if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
|
|
3904
|
-
return
|
|
4674
|
+
return err3(400, `oauth not available for provider '${providerId}'`);
|
|
3905
4675
|
}
|
|
3906
|
-
const flow = providerId === "claude" ?
|
|
4676
|
+
const flow = providerId === "claude" ? import_subscriptions5.claudeOAuth : import_subscriptions5.geminiOAuth;
|
|
3907
4677
|
const { authUrl, codeVerifier, state } = flow.generateAuthParams();
|
|
3908
4678
|
const sessionId = deps.oauthSessions.put({ providerId, codeVerifier, state });
|
|
3909
4679
|
return { status: 200, body: { authUrl, sessionId } };
|
|
3910
4680
|
}
|
|
3911
4681
|
async function handleOAuthComplete(providerId, body, deps) {
|
|
3912
4682
|
if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
|
|
3913
|
-
return
|
|
4683
|
+
return err3(400, `oauth not available for provider '${providerId}'`);
|
|
3914
4684
|
}
|
|
3915
4685
|
const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
|
|
3916
4686
|
const rawCode = typeof body["code"] === "string" ? body["code"] : "";
|
|
3917
|
-
if (!sessionId) return
|
|
3918
|
-
if (!rawCode) return
|
|
4687
|
+
if (!sessionId) return err3(400, "oauth complete requires { sessionId }");
|
|
4688
|
+
if (!rawCode) return err3(400, "oauth complete requires { code }");
|
|
3919
4689
|
const session = deps.oauthSessions.peek(sessionId);
|
|
3920
|
-
if (!session) return
|
|
4690
|
+
if (!session) return err3(410, "oauth session is unknown, expired, or already used");
|
|
3921
4691
|
if (session.providerId !== providerId) {
|
|
3922
|
-
return
|
|
4692
|
+
return err3(400, `oauth session does not match provider '${providerId}'`);
|
|
3923
4693
|
}
|
|
3924
4694
|
let code = rawCode.trim();
|
|
3925
4695
|
if (providerId === "claude") {
|
|
3926
4696
|
const [splitCode, pastedState] = code.split("#");
|
|
3927
|
-
if (!splitCode) return
|
|
4697
|
+
if (!splitCode) return err3(400, "no authorization code was provided");
|
|
3928
4698
|
if (pastedState && pastedState !== session.state) {
|
|
3929
|
-
return
|
|
4699
|
+
return err3(400, "oauth state did not match (possible CSRF) \u2014 aborting");
|
|
3930
4700
|
}
|
|
3931
4701
|
code = splitCode;
|
|
3932
4702
|
}
|
|
@@ -3936,7 +4706,7 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
3936
4706
|
block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
|
|
3937
4707
|
} catch (exchangeError) {
|
|
3938
4708
|
const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
|
|
3939
|
-
return
|
|
4709
|
+
return err3(502, `oauth token exchange failed for '${providerId}': ${reason}`);
|
|
3940
4710
|
}
|
|
3941
4711
|
deps.oauthSessions.consume(sessionId);
|
|
3942
4712
|
const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
|
|
@@ -3945,7 +4715,7 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
3945
4715
|
return { status: 200, body: status ? { account: status } : { ok: true } };
|
|
3946
4716
|
}
|
|
3947
4717
|
async function exchangeClaude(code, codeVerifier, state, exchangeFetch) {
|
|
3948
|
-
const result = await
|
|
4718
|
+
const result = await import_subscriptions5.claudeOAuth.exchangeCodeForTokens(
|
|
3949
4719
|
{ authorizationCode: code, codeVerifier, state },
|
|
3950
4720
|
exchangeFetch
|
|
3951
4721
|
);
|
|
@@ -3961,7 +4731,7 @@ async function exchangeClaude(code, codeVerifier, state, exchangeFetch) {
|
|
|
3961
4731
|
};
|
|
3962
4732
|
}
|
|
3963
4733
|
async function exchangeGemini(code, codeVerifier, exchangeFetch) {
|
|
3964
|
-
const result = await
|
|
4734
|
+
const result = await import_subscriptions5.geminiOAuth.exchangeCodeForTokens(code, codeVerifier, exchangeFetch);
|
|
3965
4735
|
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
3966
4736
|
return {
|
|
3967
4737
|
authMethod: "oauth",
|
|
@@ -4266,8 +5036,8 @@ function errBody(message) {
|
|
|
4266
5036
|
return { error: { type: "admin_api_error", message } };
|
|
4267
5037
|
}
|
|
4268
5038
|
var defaultCommandRunner = (command) => new Promise((resolve11) => {
|
|
4269
|
-
(0, import_node_child_process.exec)(command, { timeout: 18e4 }, (
|
|
4270
|
-
if (
|
|
5039
|
+
(0, import_node_child_process.exec)(command, { timeout: 18e4 }, (err6, _stdout, stderr) => {
|
|
5040
|
+
if (err6) resolve11({ ok: false, error: stderr.trim() || err6.message });
|
|
4271
5041
|
else resolve11({ ok: true });
|
|
4272
5042
|
});
|
|
4273
5043
|
});
|
|
@@ -4313,8 +5083,8 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
4313
5083
|
providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
|
|
4314
5084
|
model: typeof body["model"] === "string" ? body["model"] : void 0
|
|
4315
5085
|
});
|
|
4316
|
-
} catch (
|
|
4317
|
-
return { status: 400, body: errBody(
|
|
5086
|
+
} catch (err6) {
|
|
5087
|
+
return { status: 400, body: errBody(err6 instanceof Error ? err6.message : "no launch target") };
|
|
4318
5088
|
}
|
|
4319
5089
|
const id = (0, import_node_crypto7.randomUUID)();
|
|
4320
5090
|
let leaseId2;
|
|
@@ -4342,9 +5112,9 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
4342
5112
|
} else {
|
|
4343
5113
|
launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
|
|
4344
5114
|
}
|
|
4345
|
-
} catch (
|
|
4346
|
-
const status =
|
|
4347
|
-
return { status, body: errBody(
|
|
5115
|
+
} catch (err6) {
|
|
5116
|
+
const status = err6 instanceof import_provider_proxy2.RouteLeaseError ? err6.status : 400;
|
|
5117
|
+
return { status, body: errBody(err6 instanceof Error ? err6.message : "failed to build launch env") };
|
|
4348
5118
|
}
|
|
4349
5119
|
const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
|
|
4350
5120
|
const opener = ctx.opener ?? defaultTerminalOpener;
|
|
@@ -4372,9 +5142,9 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
4372
5142
|
onFailure: onSessionEnd
|
|
4373
5143
|
});
|
|
4374
5144
|
if (cleanup) openerCleanup = cleanup;
|
|
4375
|
-
} catch (
|
|
5145
|
+
} catch (err6) {
|
|
4376
5146
|
onSessionEnd();
|
|
4377
|
-
return { status: 500, body: errBody(
|
|
5147
|
+
return { status: 500, body: errBody(err6 instanceof Error ? err6.message : "failed to open terminal") };
|
|
4378
5148
|
}
|
|
4379
5149
|
if (ended) {
|
|
4380
5150
|
openerCleanup?.();
|
|
@@ -4509,6 +5279,7 @@ async function handleDashboard(deps) {
|
|
|
4509
5279
|
var import_outbound_api2 = require("@omnicross/core/outbound-api");
|
|
4510
5280
|
var import_api3 = require("@omnicross/core/search/api");
|
|
4511
5281
|
var import_http3 = require("@omnicross/core/search/http");
|
|
5282
|
+
var import_search2 = require("@omnicross/core/search");
|
|
4512
5283
|
|
|
4513
5284
|
// src/search/searchDoctorProjection.ts
|
|
4514
5285
|
var import_search_types = require("@omnicross/contracts/search-types");
|
|
@@ -4569,7 +5340,7 @@ function buildSearchDoctorSnapshot(contributions = (0, import_http.builtinHttpSe
|
|
|
4569
5340
|
}
|
|
4570
5341
|
return rows;
|
|
4571
5342
|
}
|
|
4572
|
-
var SEARCH_DOCTOR_QUERY = "
|
|
5343
|
+
var SEARCH_DOCTOR_QUERY = "MDN HTTP headers documentation";
|
|
4573
5344
|
function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
|
|
4574
5345
|
if (outcome.kind === "results") {
|
|
4575
5346
|
if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
|
|
@@ -4628,6 +5399,7 @@ async function runSearchLiveChecks(contributions, now = () => (/* @__PURE__ */ n
|
|
|
4628
5399
|
}
|
|
4629
5400
|
|
|
4630
5401
|
// src/search/SearchAssembly.ts
|
|
5402
|
+
var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
4631
5403
|
var import_search = require("@omnicross/core/search");
|
|
4632
5404
|
var import_api2 = require("@omnicross/core/search/api");
|
|
4633
5405
|
var import_http2 = require("@omnicross/core/search/http");
|
|
@@ -4644,11 +5416,24 @@ function searchPolicyFrom(config) {
|
|
|
4644
5416
|
...maxAttempts !== void 0 ? { maxAttempts } : {}
|
|
4645
5417
|
};
|
|
4646
5418
|
}
|
|
5419
|
+
function resolveSearchUpstreamDispatcher(url) {
|
|
5420
|
+
return (0, import_upstreamFetch6.resolveUpstreamDispatcher)({ url });
|
|
5421
|
+
}
|
|
5422
|
+
var searchUpstreamProxyConfig = createUpstreamProxyResolver();
|
|
5423
|
+
function resolveSearchUpstreamProxyConfig(url) {
|
|
5424
|
+
return searchUpstreamProxyConfig({ url });
|
|
5425
|
+
}
|
|
4647
5426
|
function searchContributionsFrom(config) {
|
|
4648
5427
|
return [
|
|
4649
|
-
...(0, import_http2.builtinHttpSearchContributions)(
|
|
5428
|
+
...(0, import_http2.builtinHttpSearchContributions)(
|
|
5429
|
+
(0, import_http2.createSearchHttpTransport)({
|
|
5430
|
+
resolveProxyDispatcher: resolveSearchUpstreamDispatcher,
|
|
5431
|
+
resolveProxyConfig: resolveSearchUpstreamProxyConfig
|
|
5432
|
+
})
|
|
5433
|
+
),
|
|
4650
5434
|
...(0, import_api2.apiSearchContributions)(config.providers, {
|
|
4651
|
-
egressPolicy: searchEgressPolicyFrom(config)
|
|
5435
|
+
egressPolicy: searchEgressPolicyFrom(config),
|
|
5436
|
+
resolveProxyDispatcher: resolveSearchUpstreamDispatcher
|
|
4652
5437
|
})
|
|
4653
5438
|
];
|
|
4654
5439
|
}
|
|
@@ -4792,6 +5577,18 @@ async function handleSearchDiagnostics(res, deps) {
|
|
|
4792
5577
|
};
|
|
4793
5578
|
return writeJson(res, 200, { diagnostics: snapshot });
|
|
4794
5579
|
}
|
|
5580
|
+
function persistedSearchContributions(search, fetchImpl) {
|
|
5581
|
+
if (fetchImpl) {
|
|
5582
|
+
const egressPolicy = searchEgressPolicyFrom(search);
|
|
5583
|
+
return [
|
|
5584
|
+
...(0, import_http3.builtinHttpSearchContributions)(
|
|
5585
|
+
(0, import_http3.createSearchHttpTransport)({ fetch: fetchImpl, egressPolicy })
|
|
5586
|
+
),
|
|
5587
|
+
...(0, import_api3.apiSearchContributions)(search.providers, { egressPolicy, fetchImpl })
|
|
5588
|
+
];
|
|
5589
|
+
}
|
|
5590
|
+
return searchContributionsFrom(search);
|
|
5591
|
+
}
|
|
4795
5592
|
async function handleSearchTest(req, res, deps) {
|
|
4796
5593
|
const status = deps.searchStatus;
|
|
4797
5594
|
const body = await readBodyOrReject(req, res);
|
|
@@ -4809,16 +5606,8 @@ async function handleSearchTest(req, res, deps) {
|
|
|
4809
5606
|
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4810
5607
|
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4811
5608
|
}
|
|
4812
|
-
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4813
5609
|
const fetchImpl = status.testFetch;
|
|
4814
|
-
const
|
|
4815
|
-
const contributions = [
|
|
4816
|
-
...(0, import_http3.builtinHttpSearchContributions)(transport),
|
|
4817
|
-
...(0, import_api3.apiSearchContributions)(search.providers, {
|
|
4818
|
-
egressPolicy,
|
|
4819
|
-
...fetchImpl ? { fetchImpl } : {}
|
|
4820
|
-
})
|
|
4821
|
-
];
|
|
5610
|
+
const contributions = persistedSearchContributions(search, fetchImpl);
|
|
4822
5611
|
const contribution = contributions.find((c) => c.id === providerId);
|
|
4823
5612
|
if (!contribution) {
|
|
4824
5613
|
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
@@ -4859,48 +5648,49 @@ async function handleSearchQuery(req, res, deps) {
|
|
|
4859
5648
|
}
|
|
4860
5649
|
if (QUERY_CONTROL_CHARS.test(query2)) {
|
|
4861
5650
|
return writeErr(res, 400, "query must not contain control characters");
|
|
4862
|
-
}
|
|
4863
|
-
const persisted = await (0, import_outbound_api2.loadServerConfig)(deps.settingsStore);
|
|
4864
|
-
const search = persisted.search ?? import_outbound_api2.DEFAULT_SEARCH_SERVER_CONFIG;
|
|
4865
|
-
const providers = search.providers;
|
|
4866
|
-
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4867
|
-
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4868
|
-
}
|
|
4869
|
-
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4870
|
-
const fetchImpl = status.testFetch;
|
|
4871
|
-
const transport = fetchImpl ? (0, import_http3.createSearchHttpTransport)({ fetch: fetchImpl, egressPolicy }) : void 0;
|
|
4872
|
-
const contributions = [
|
|
4873
|
-
...(0, import_http3.builtinHttpSearchContributions)(transport),
|
|
4874
|
-
...(0, import_api3.apiSearchContributions)(search.providers, {
|
|
4875
|
-
egressPolicy,
|
|
4876
|
-
...fetchImpl ? { fetchImpl } : {}
|
|
4877
|
-
})
|
|
4878
|
-
];
|
|
4879
|
-
const contribution = contributions.find((c) => c.id === providerId);
|
|
4880
|
-
if (!contribution) {
|
|
5651
|
+
}
|
|
5652
|
+
const persisted = await (0, import_outbound_api2.loadServerConfig)(deps.settingsStore);
|
|
5653
|
+
const search = persisted.search ?? import_outbound_api2.DEFAULT_SEARCH_SERVER_CONFIG;
|
|
5654
|
+
const providers = search.providers;
|
|
5655
|
+
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4881
5656
|
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4882
5657
|
}
|
|
5658
|
+
const fetchImpl = status.testFetch;
|
|
5659
|
+
const runtime = (0, import_search2.createSearchRuntime)({
|
|
5660
|
+
contributions: persistedSearchContributions(search, fetchImpl),
|
|
5661
|
+
policy: {
|
|
5662
|
+
...searchPolicyFrom(search),
|
|
5663
|
+
// The panel always walks: it answers "does a search WORK for this
|
|
5664
|
+
// operator", not "does this one provider behave" — that is `/test`'s
|
|
5665
|
+
// job. The persisted policy's allowlist still bounds the walk.
|
|
5666
|
+
fallbackEnabled: true,
|
|
5667
|
+
preferred: providerId
|
|
5668
|
+
}
|
|
5669
|
+
});
|
|
4883
5670
|
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4884
5671
|
try {
|
|
4885
|
-
const
|
|
5672
|
+
const orchestrated = await runtime.search({ query: query2, options: { maxResults: 5 } });
|
|
5673
|
+
const results = orchestrated.results;
|
|
4886
5674
|
const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
|
|
4887
5675
|
title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
|
|
4888
5676
|
url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
|
|
4889
5677
|
content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
|
|
4890
5678
|
}));
|
|
4891
|
-
const diagnostic = sanitized.length === 0 ? { providerId:
|
|
4892
|
-
|
|
5679
|
+
const diagnostic = sanitized.length === 0 ? { providerId: orchestrated.providerId, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
|
|
5680
|
+
orchestrated.providerId,
|
|
4893
5681
|
{ kind: "results", count: sanitized.length },
|
|
4894
5682
|
checkedAt
|
|
4895
5683
|
);
|
|
4896
5684
|
const response = {
|
|
4897
5685
|
diagnostic,
|
|
5686
|
+
providerUsed: orchestrated.providerId,
|
|
5687
|
+
fallbackCount: orchestrated.fallbackCount,
|
|
4898
5688
|
resultCount: sanitized.length,
|
|
4899
5689
|
results: sanitized
|
|
4900
5690
|
};
|
|
4901
5691
|
return writeJson(res, 200, { result: response });
|
|
4902
5692
|
} catch (error) {
|
|
4903
|
-
const diagnostic = classifyLiveSearchOutcome(
|
|
5693
|
+
const diagnostic = classifyLiveSearchOutcome(providerId, { kind: "failure", error }, checkedAt);
|
|
4904
5694
|
const response = { diagnostic };
|
|
4905
5695
|
return writeJson(res, 200, { result: response });
|
|
4906
5696
|
}
|
|
@@ -4909,7 +5699,7 @@ async function handleSearchQuery(req, res, deps) {
|
|
|
4909
5699
|
// src/admin/searchAdminView.ts
|
|
4910
5700
|
var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
|
|
4911
5701
|
var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
|
|
4912
|
-
function
|
|
5702
|
+
function isRecord2(value) {
|
|
4913
5703
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4914
5704
|
}
|
|
4915
5705
|
function redactSearchServerConfig(search) {
|
|
@@ -4959,13 +5749,13 @@ function resolveSecretField(entry, field, stored) {
|
|
|
4959
5749
|
else delete entry[field];
|
|
4960
5750
|
}
|
|
4961
5751
|
function preserveSearchSecrets(incoming, current) {
|
|
4962
|
-
if (!
|
|
5752
|
+
if (!isRecord2(incoming)) return incoming;
|
|
4963
5753
|
const section = { ...incoming };
|
|
4964
5754
|
const providersValue = section["providers"];
|
|
4965
|
-
if (!
|
|
5755
|
+
if (!isRecord2(providersValue)) return section;
|
|
4966
5756
|
const providers = {};
|
|
4967
5757
|
for (const [id, entryValue] of Object.entries(providersValue)) {
|
|
4968
|
-
if (!
|
|
5758
|
+
if (!isRecord2(entryValue)) {
|
|
4969
5759
|
providers[id] = entryValue;
|
|
4970
5760
|
continue;
|
|
4971
5761
|
}
|
|
@@ -5043,7 +5833,7 @@ function parseKeyPolicyBody(body) {
|
|
|
5043
5833
|
var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
|
|
5044
5834
|
var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
|
|
5045
5835
|
var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
|
|
5046
|
-
function
|
|
5836
|
+
function isRecord3(value) {
|
|
5047
5837
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5048
5838
|
}
|
|
5049
5839
|
function nonBlank(value) {
|
|
@@ -5063,7 +5853,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
5063
5853
|
const ids = /* @__PURE__ */ new Set();
|
|
5064
5854
|
raw.forEach((entry, index) => {
|
|
5065
5855
|
const path2 = `bindings[${index}]`;
|
|
5066
|
-
if (!
|
|
5856
|
+
if (!isRecord3(entry)) {
|
|
5067
5857
|
errors.push(`${path2} must be an object`);
|
|
5068
5858
|
return;
|
|
5069
5859
|
}
|
|
@@ -5092,12 +5882,12 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
5092
5882
|
} else if (entry.modelMappings.length > 100) {
|
|
5093
5883
|
errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
|
|
5094
5884
|
} else if (entry.modelMappings.some(
|
|
5095
|
-
(mapping) => !
|
|
5885
|
+
(mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
|
|
5096
5886
|
)) {
|
|
5097
5887
|
errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
|
|
5098
5888
|
}
|
|
5099
5889
|
}
|
|
5100
|
-
if (!
|
|
5890
|
+
if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
|
|
5101
5891
|
errors.push(`${path2}.target is invalid`);
|
|
5102
5892
|
} else {
|
|
5103
5893
|
if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
|
|
@@ -5112,7 +5902,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
5112
5902
|
}
|
|
5113
5903
|
}
|
|
5114
5904
|
if (entry.modelMap !== void 0) {
|
|
5115
|
-
if (!
|
|
5905
|
+
if (!isRecord3(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
|
|
5116
5906
|
errors.push(`${path2}.modelMap must contain string values`);
|
|
5117
5907
|
}
|
|
5118
5908
|
}
|
|
@@ -5394,7 +6184,8 @@ var PROVIDER_KEYS = {
|
|
|
5394
6184
|
block: "opencodego",
|
|
5395
6185
|
accounts: "opencodegoAccounts",
|
|
5396
6186
|
active: "activeOpencodegoAccountId"
|
|
5397
|
-
}
|
|
6187
|
+
},
|
|
6188
|
+
kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
|
|
5398
6189
|
};
|
|
5399
6190
|
function clone(value) {
|
|
5400
6191
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -5916,7 +6707,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
|
|
|
5916
6707
|
}
|
|
5917
6708
|
|
|
5918
6709
|
// src/admin/adminMigration.ts
|
|
5919
|
-
function
|
|
6710
|
+
function err4(status, message) {
|
|
5920
6711
|
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
5921
6712
|
}
|
|
5922
6713
|
async function handleExport(body, deps) {
|
|
@@ -5926,30 +6717,30 @@ async function handleExport(body, deps) {
|
|
|
5926
6717
|
return { status: 200, body: { pack, version: BUNDLE_VERSION } };
|
|
5927
6718
|
} catch (error) {
|
|
5928
6719
|
if (error instanceof WeakPassphraseError) {
|
|
5929
|
-
return
|
|
6720
|
+
return err4(400, error.message);
|
|
5930
6721
|
}
|
|
5931
|
-
return
|
|
6722
|
+
return err4(500, "failed to build the migration pack");
|
|
5932
6723
|
}
|
|
5933
6724
|
}
|
|
5934
6725
|
async function handleImport(body, deps) {
|
|
5935
6726
|
const blob = typeof body["blob"] === "string" ? body["blob"] : "";
|
|
5936
6727
|
const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
|
|
5937
6728
|
const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
|
|
5938
|
-
if (!blob) return
|
|
6729
|
+
if (!blob) return err4(400, "import requires { blob }");
|
|
5939
6730
|
try {
|
|
5940
6731
|
const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
|
|
5941
6732
|
return { status: 200, body: counts };
|
|
5942
6733
|
} catch (error) {
|
|
5943
6734
|
if (error instanceof WeakPassphraseError) {
|
|
5944
|
-
return
|
|
6735
|
+
return err4(400, error.message);
|
|
5945
6736
|
}
|
|
5946
|
-
return
|
|
6737
|
+
return err4(400, error instanceof Error ? error.message : "import failed");
|
|
5947
6738
|
}
|
|
5948
6739
|
}
|
|
5949
6740
|
|
|
5950
6741
|
// src/admin/usagePricing.ts
|
|
5951
6742
|
var import_usage = require("@omnicross/core/usage");
|
|
5952
|
-
var
|
|
6743
|
+
var err5 = (status, message) => ({
|
|
5953
6744
|
status,
|
|
5954
6745
|
body: { error: { type: "admin_api_error", message } }
|
|
5955
6746
|
});
|
|
@@ -5962,7 +6753,7 @@ function parseRange(query2) {
|
|
|
5962
6753
|
const startTs = parseFiniteInt(query2.get("startTs"));
|
|
5963
6754
|
const endTs = parseFiniteInt(query2.get("endTs"));
|
|
5964
6755
|
if (startTs === null || endTs === null) {
|
|
5965
|
-
return
|
|
6756
|
+
return err5(400, "startTs and endTs are required finite-integer unix-millis query params");
|
|
5966
6757
|
}
|
|
5967
6758
|
return { startTs, endTs };
|
|
5968
6759
|
}
|
|
@@ -5987,14 +6778,14 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
5987
6778
|
case "timeseries": {
|
|
5988
6779
|
const bucket = query2.get("bucket");
|
|
5989
6780
|
if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
|
|
5990
|
-
return
|
|
6781
|
+
return err5(400, "bucket must be one of 'hour', 'day', 'month'");
|
|
5991
6782
|
}
|
|
5992
6783
|
const now = Date.now();
|
|
5993
6784
|
const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
|
|
5994
6785
|
if (clamped.startTs < clamped.endTs) {
|
|
5995
6786
|
const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
|
|
5996
6787
|
if (projected > MAX_TIMESERIES_BUCKETS) {
|
|
5997
|
-
return
|
|
6788
|
+
return err5(
|
|
5998
6789
|
400,
|
|
5999
6790
|
`requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
|
|
6000
6791
|
);
|
|
@@ -6017,7 +6808,7 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
6017
6808
|
};
|
|
6018
6809
|
}
|
|
6019
6810
|
default:
|
|
6020
|
-
return
|
|
6811
|
+
return err5(404, `unknown usage view '${view ?? ""}'`);
|
|
6021
6812
|
}
|
|
6022
6813
|
}
|
|
6023
6814
|
function poolKeyLabels(cfg) {
|
|
@@ -6066,7 +6857,7 @@ async function handlePricingList(deps) {
|
|
|
6066
6857
|
async function handlePricingUpsert(body, deps) {
|
|
6067
6858
|
const input = parsePricingEntryInput(body);
|
|
6068
6859
|
if (!input) {
|
|
6069
|
-
return
|
|
6860
|
+
return err5(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
|
|
6070
6861
|
}
|
|
6071
6862
|
const entry = await deps.pricingEngine.upsertManual(input);
|
|
6072
6863
|
return { status: 200, body: { entry } };
|
|
@@ -6075,7 +6866,7 @@ async function handlePricingDelete(query2, deps) {
|
|
|
6075
6866
|
const providerId = query2.get("providerId")?.trim() ?? "";
|
|
6076
6867
|
const modelId = query2.get("modelId")?.trim() ?? "";
|
|
6077
6868
|
if (!providerId || !modelId) {
|
|
6078
|
-
return
|
|
6869
|
+
return err5(400, "delete requires providerId and modelId query params");
|
|
6079
6870
|
}
|
|
6080
6871
|
const deleted = await deps.pricingStore.delete(providerId, modelId);
|
|
6081
6872
|
if (deleted) await deps.pricingEngine.invalidateCache();
|
|
@@ -6095,13 +6886,13 @@ async function handlePricingFetchLatest(deps) {
|
|
|
6095
6886
|
}
|
|
6096
6887
|
};
|
|
6097
6888
|
} catch (e) {
|
|
6098
|
-
return
|
|
6889
|
+
return err5(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
6099
6890
|
}
|
|
6100
6891
|
}
|
|
6101
6892
|
async function handlePricingResolveConflicts(body, deps) {
|
|
6102
6893
|
const raw = body["resolutions"];
|
|
6103
6894
|
if (!Array.isArray(raw)) {
|
|
6104
|
-
return
|
|
6895
|
+
return err5(400, "resolve-conflicts requires { resolutions: [...] }");
|
|
6105
6896
|
}
|
|
6106
6897
|
const currentRows = await deps.pricingStore.getAll();
|
|
6107
6898
|
const userEditedKeys = new Set(
|
|
@@ -6111,21 +6902,21 @@ async function handlePricingResolveConflicts(body, deps) {
|
|
|
6111
6902
|
const pendingIncoming = /* @__PURE__ */ new Map();
|
|
6112
6903
|
let staleCount = 0;
|
|
6113
6904
|
for (const item of raw) {
|
|
6114
|
-
if (!item || typeof item !== "object") return
|
|
6905
|
+
if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
|
|
6115
6906
|
const r = item;
|
|
6116
6907
|
const action = r["action"];
|
|
6117
6908
|
if (action !== "overwrite" && action !== "skip") {
|
|
6118
|
-
return
|
|
6909
|
+
return err5(400, "resolution action must be 'overwrite' or 'skip'");
|
|
6119
6910
|
}
|
|
6120
6911
|
const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
|
|
6121
6912
|
const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
|
|
6122
6913
|
if (!providerId || !modelId) {
|
|
6123
|
-
return
|
|
6914
|
+
return err5(400, "each resolution requires top-level providerId and modelId");
|
|
6124
6915
|
}
|
|
6125
6916
|
const incoming = parsePricingEntryInput(r["incoming"]);
|
|
6126
|
-
if (!incoming) return
|
|
6917
|
+
if (!incoming) return err5(400, "each resolution must echo a valid incoming pricing entry");
|
|
6127
6918
|
if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
|
|
6128
|
-
return
|
|
6919
|
+
return err5(400, "resolution providerId/modelId must match the echoed incoming entry");
|
|
6129
6920
|
}
|
|
6130
6921
|
const key = `${providerId}::${modelId}`;
|
|
6131
6922
|
if (action === "overwrite" && !userEditedKeys.has(key)) {
|
|
@@ -6170,7 +6961,7 @@ function query(req) {
|
|
|
6170
6961
|
}
|
|
6171
6962
|
function allowanceProvider(value) {
|
|
6172
6963
|
if (!value) return void 0;
|
|
6173
|
-
return value === "claude" || value === "codex" ? value : null;
|
|
6964
|
+
return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
|
|
6174
6965
|
}
|
|
6175
6966
|
async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
6176
6967
|
if (!service) return writeError2(res, 501, "account allowance service is not available");
|
|
@@ -6184,7 +6975,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
6184
6975
|
const params = query(req);
|
|
6185
6976
|
const pathProvider = rest.length >= 2 ? rest[0] : null;
|
|
6186
6977
|
const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
|
|
6187
|
-
if (providerId === null)
|
|
6978
|
+
if (providerId === null) {
|
|
6979
|
+
return writeError2(res, 400, "providerId must be claude, codex, kimi, or opencodego");
|
|
6980
|
+
}
|
|
6188
6981
|
const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
|
|
6189
6982
|
const allowances = await service.list({ providerId, accountId });
|
|
6190
6983
|
return writeJson3(res, 200, { allowances });
|
|
@@ -6194,10 +6987,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
6194
6987
|
const requestedProvider = allowanceProvider(
|
|
6195
6988
|
typeof body["providerId"] === "string" ? body["providerId"] : "claude"
|
|
6196
6989
|
);
|
|
6197
|
-
if (requestedProvider !== "claude") {
|
|
6198
|
-
return writeError2(res, 400, "only Claude allowances support explicit refresh");
|
|
6199
|
-
}
|
|
6200
6990
|
const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
|
|
6991
|
+
if (requestedProvider === "codex") {
|
|
6992
|
+
if (!service.refreshCodex) {
|
|
6993
|
+
return writeError2(res, 501, "codex allowance refresh is not available");
|
|
6994
|
+
}
|
|
6995
|
+
const allowances2 = await service.refreshCodex(accountId);
|
|
6996
|
+
if (accountId && allowances2.length === 0) {
|
|
6997
|
+
return writeError2(res, 404, `Codex account '${accountId}' not found`);
|
|
6998
|
+
}
|
|
6999
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
7000
|
+
}
|
|
7001
|
+
if (requestedProvider === "kimi") {
|
|
7002
|
+
if (!service.refreshKimi) {
|
|
7003
|
+
return writeError2(res, 501, "kimi allowance refresh is not available");
|
|
7004
|
+
}
|
|
7005
|
+
const allowances2 = await service.refreshKimi(accountId);
|
|
7006
|
+
if (accountId && allowances2.length === 0) {
|
|
7007
|
+
return writeError2(res, 404, `Kimi account '${accountId}' not found`);
|
|
7008
|
+
}
|
|
7009
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
7010
|
+
}
|
|
7011
|
+
if (requestedProvider === "opencodego") {
|
|
7012
|
+
if (!service.refreshOpenCodeGo) {
|
|
7013
|
+
return writeError2(res, 501, "opencodego allowance refresh is not available");
|
|
7014
|
+
}
|
|
7015
|
+
const allowances2 = await service.refreshOpenCodeGo(accountId);
|
|
7016
|
+
if (accountId && allowances2.length === 0) {
|
|
7017
|
+
return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
|
|
7018
|
+
}
|
|
7019
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
7020
|
+
}
|
|
6201
7021
|
const allowances = await service.refreshClaude(accountId);
|
|
6202
7022
|
if (accountId && allowances.length === 0) {
|
|
6203
7023
|
return writeError2(res, 404, `Claude account '${accountId}' not found`);
|
|
@@ -6365,8 +7185,8 @@ async function handleAdminApi(req, res, path2, deps) {
|
|
|
6365
7185
|
default:
|
|
6366
7186
|
return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
|
|
6367
7187
|
}
|
|
6368
|
-
} catch (
|
|
6369
|
-
writeJsonError(res, 500,
|
|
7188
|
+
} catch (err6) {
|
|
7189
|
+
writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
|
|
6370
7190
|
}
|
|
6371
7191
|
}
|
|
6372
7192
|
function requestQuery(req) {
|
|
@@ -6436,6 +7256,9 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
6436
7256
|
if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
|
|
6437
7257
|
return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
6438
7258
|
}
|
|
7259
|
+
if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
|
|
7260
|
+
return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
|
|
7261
|
+
}
|
|
6439
7262
|
if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
|
|
6440
7263
|
return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
6441
7264
|
}
|
|
@@ -6534,7 +7357,7 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
6534
7357
|
try {
|
|
6535
7358
|
const headers = { Accept: "application/json" };
|
|
6536
7359
|
if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
|
|
6537
|
-
const response = await (0,
|
|
7360
|
+
const response = await (0, import_upstreamFetch7.fetchUpstream)(url, { method: "GET", headers }, { providerId: "byo" });
|
|
6538
7361
|
if (!response.ok) {
|
|
6539
7362
|
const text = await response.text().catch(() => "");
|
|
6540
7363
|
let message = text.slice(0, 300);
|
|
@@ -6551,8 +7374,8 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
6551
7374
|
const data = await response.json();
|
|
6552
7375
|
const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
|
|
6553
7376
|
return writeJson4(res, 200, { models });
|
|
6554
|
-
} catch (
|
|
6555
|
-
const message =
|
|
7377
|
+
} catch (err6) {
|
|
7378
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
6556
7379
|
return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
|
|
6557
7380
|
}
|
|
6558
7381
|
}
|
|
@@ -6593,7 +7416,7 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
6593
7416
|
}
|
|
6594
7417
|
const startedAt = Date.now();
|
|
6595
7418
|
try {
|
|
6596
|
-
const response = await (0,
|
|
7419
|
+
const response = await (0, import_upstreamFetch7.fetchUpstream)(
|
|
6597
7420
|
url,
|
|
6598
7421
|
{ method: "POST", headers, body: JSON.stringify(payload) },
|
|
6599
7422
|
{ providerId: "byo" }
|
|
@@ -6615,8 +7438,8 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
6615
7438
|
latencyMs,
|
|
6616
7439
|
sample: extractSampleText(text, row.apiFormat)
|
|
6617
7440
|
});
|
|
6618
|
-
} catch (
|
|
6619
|
-
const message =
|
|
7441
|
+
} catch (err6) {
|
|
7442
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
6620
7443
|
return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
|
|
6621
7444
|
}
|
|
6622
7445
|
}
|
|
@@ -6658,7 +7481,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
|
|
|
6658
7481
|
const row = cfg.providers.find((p) => p.id === id);
|
|
6659
7482
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6660
7483
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
6661
|
-
|
|
7484
|
+
const views = toPoolKeyView(row, cooldown, deps);
|
|
7485
|
+
if (deps.providerKeyQuota) {
|
|
7486
|
+
const quotas = await Promise.allSettled(
|
|
7487
|
+
views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
|
|
7488
|
+
);
|
|
7489
|
+
views.forEach((view, index) => {
|
|
7490
|
+
const settled = quotas[index];
|
|
7491
|
+
if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
|
|
7492
|
+
});
|
|
7493
|
+
}
|
|
7494
|
+
return writeJson4(res, 200, { keys: views });
|
|
7495
|
+
}
|
|
7496
|
+
async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
|
|
7497
|
+
if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
|
|
7498
|
+
if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
|
|
7499
|
+
const row = cfg.providers.find((p) => p.id === id);
|
|
7500
|
+
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
7501
|
+
try {
|
|
7502
|
+
const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
|
|
7503
|
+
if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
|
|
7504
|
+
return writeJson4(res, 200, { quota });
|
|
7505
|
+
} catch {
|
|
7506
|
+
return writeJsonError(res, 502, "quota refresh failed");
|
|
7507
|
+
}
|
|
6662
7508
|
}
|
|
6663
7509
|
function parsePoolKeyInput(body, existing) {
|
|
6664
7510
|
const out = {};
|
|
@@ -7403,12 +8249,12 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7403
8249
|
}
|
|
7404
8250
|
return writeJson4(res, 200, { ok: true, affected: result.affected });
|
|
7405
8251
|
}
|
|
7406
|
-
if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
|
|
7407
|
-
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
8252
|
+
if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[3] === "status") {
|
|
8253
|
+
const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : handleKimiOAuthStatus(rest[2], deps);
|
|
7408
8254
|
return writeJson4(res, result.status, result.body);
|
|
7409
8255
|
}
|
|
7410
|
-
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
7411
|
-
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
8256
|
+
if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[2]) {
|
|
8257
|
+
const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : handleKimiOAuthCancel(rest[2], deps);
|
|
7412
8258
|
return writeJson4(res, result.status, result.body);
|
|
7413
8259
|
}
|
|
7414
8260
|
if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
|
|
@@ -7461,7 +8307,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
7461
8307
|
return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
7462
8308
|
}
|
|
7463
8309
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
|
|
7464
|
-
|
|
8310
|
+
if (providerId === "codex") {
|
|
8311
|
+
const result2 = handleCodexOAuthStart(deps);
|
|
8312
|
+
return writeJson4(res, result2.status, result2.body);
|
|
8313
|
+
}
|
|
8314
|
+
if (providerId === "kimi") {
|
|
8315
|
+
const result2 = await handleKimiOAuthStart(deps);
|
|
8316
|
+
return writeJson4(res, result2.status, result2.body);
|
|
8317
|
+
}
|
|
8318
|
+
const result = handleOAuthStart(providerId, deps);
|
|
7465
8319
|
return writeJson4(res, result.status, result.body);
|
|
7466
8320
|
}
|
|
7467
8321
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
|
|
@@ -7955,12 +8809,12 @@ async function handlePlayground(req, res, method, deps) {
|
|
|
7955
8809
|
const payload = body["body"];
|
|
7956
8810
|
const status = deps.outboundApiServer.getStatus();
|
|
7957
8811
|
if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
|
|
7958
|
-
const path2 = resolvePlaygroundPath(endpoint,
|
|
8812
|
+
const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
|
|
7959
8813
|
if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
|
|
7960
8814
|
const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
7961
8815
|
await proxyToOutbound(res, status.port, path2, key, upstreamBody);
|
|
7962
8816
|
}
|
|
7963
|
-
function
|
|
8817
|
+
function isRecord4(v) {
|
|
7964
8818
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
7965
8819
|
}
|
|
7966
8820
|
function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
@@ -7989,8 +8843,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
|
7989
8843
|
});
|
|
7990
8844
|
}
|
|
7991
8845
|
);
|
|
7992
|
-
upstream.on("error", (
|
|
7993
|
-
if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${
|
|
8846
|
+
upstream.on("error", (err6) => {
|
|
8847
|
+
if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err6.message}`);
|
|
7994
8848
|
else res.end();
|
|
7995
8849
|
resolve11();
|
|
7996
8850
|
});
|
|
@@ -8096,7 +8950,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
8096
8950
|
}
|
|
8097
8951
|
|
|
8098
8952
|
// src/admin/version.ts
|
|
8099
|
-
var DAEMON_VERSION = true ? "0.
|
|
8953
|
+
var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
|
|
8100
8954
|
|
|
8101
8955
|
// src/admin/AdminServer.ts
|
|
8102
8956
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -8139,13 +8993,13 @@ var AdminServer = class {
|
|
|
8139
8993
|
const server = import_node_http2.default.createServer((req, res) => {
|
|
8140
8994
|
this.onRequest(req, res);
|
|
8141
8995
|
});
|
|
8142
|
-
const onError = (
|
|
8143
|
-
if (
|
|
8996
|
+
const onError = (err6) => {
|
|
8997
|
+
if (err6.code === "EADDRINUSE" && port !== 0) {
|
|
8144
8998
|
server.removeListener("error", onError);
|
|
8145
8999
|
this.listen(bindAddr, 0).then(resolve11, reject);
|
|
8146
9000
|
return;
|
|
8147
9001
|
}
|
|
8148
|
-
reject(
|
|
9002
|
+
reject(err6);
|
|
8149
9003
|
};
|
|
8150
9004
|
server.on("error", onError);
|
|
8151
9005
|
server.listen(port, bindAddr, () => {
|
|
@@ -8163,8 +9017,8 @@ var AdminServer = class {
|
|
|
8163
9017
|
}
|
|
8164
9018
|
/** Per-request handler: auth gate (when a token is set) → routing. */
|
|
8165
9019
|
onRequest(req, res) {
|
|
8166
|
-
void this.dispatch(req, res).catch((
|
|
8167
|
-
const message =
|
|
9020
|
+
void this.dispatch(req, res).catch((err6) => {
|
|
9021
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
8168
9022
|
this.deps.logger.error("[AdminServer] unhandled error:", message);
|
|
8169
9023
|
if (!res.headersSent) {
|
|
8170
9024
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -8428,18 +9282,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
|
|
|
8428
9282
|
return;
|
|
8429
9283
|
}
|
|
8430
9284
|
signal?.addEventListener("abort", abort, { once: true });
|
|
8431
|
-
server.on("error", (
|
|
9285
|
+
server.on("error", (err6) => {
|
|
8432
9286
|
if (settled) return;
|
|
8433
9287
|
settled = true;
|
|
8434
9288
|
clearTimeout(timer);
|
|
8435
|
-
if (
|
|
9289
|
+
if (err6.code === "EADDRINUSE") {
|
|
8436
9290
|
reject(
|
|
8437
9291
|
new Error(
|
|
8438
9292
|
`login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
|
|
8439
9293
|
)
|
|
8440
9294
|
);
|
|
8441
9295
|
} else {
|
|
8442
|
-
reject(
|
|
9296
|
+
reject(err6);
|
|
8443
9297
|
}
|
|
8444
9298
|
});
|
|
8445
9299
|
const timer = setTimeout(() => {
|
|
@@ -8514,10 +9368,415 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
|
|
|
8514
9368
|
};
|
|
8515
9369
|
}
|
|
8516
9370
|
|
|
9371
|
+
// src/allowance/ProviderKeyQuotaService.ts
|
|
9372
|
+
var import_upstreamFetch8 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
9373
|
+
|
|
9374
|
+
// src/allowance/ProviderKeyQuota.ts
|
|
9375
|
+
var MINUTE_MS2 = 6e4;
|
|
9376
|
+
var HOUR_MS2 = 60 * MINUTE_MS2;
|
|
9377
|
+
var DAY_MS2 = 24 * HOUR_MS2;
|
|
9378
|
+
var WEEK_MS = 7 * DAY_MS2;
|
|
9379
|
+
var MONTH_MS = 30 * DAY_MS2;
|
|
9380
|
+
function finiteNumber3(value) {
|
|
9381
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
9382
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
9383
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
9384
|
+
}
|
|
9385
|
+
function finitePercent4(value) {
|
|
9386
|
+
const parsed = finiteNumber3(value);
|
|
9387
|
+
return parsed !== void 0 && parsed <= 100 ? parsed : null;
|
|
9388
|
+
}
|
|
9389
|
+
function isoInstant3(value) {
|
|
9390
|
+
if (typeof value === "string" && value.trim()) {
|
|
9391
|
+
const time = Date.parse(value);
|
|
9392
|
+
if (Number.isFinite(time)) return new Date(time).toISOString();
|
|
9393
|
+
}
|
|
9394
|
+
const numeric = finiteNumber3(value);
|
|
9395
|
+
if (numeric !== void 0 && numeric > 1e9) {
|
|
9396
|
+
const ms = numeric > 1e12 ? numeric : numeric * 1e3;
|
|
9397
|
+
return new Date(ms).toISOString();
|
|
9398
|
+
}
|
|
9399
|
+
return void 0;
|
|
9400
|
+
}
|
|
9401
|
+
function secondsUntil5(instant, now) {
|
|
9402
|
+
if (!instant) return void 0;
|
|
9403
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
9404
|
+
}
|
|
9405
|
+
function isRecord5(value) {
|
|
9406
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
9407
|
+
}
|
|
9408
|
+
function detectProviderKeyQuotaAdapter(baseUrl) {
|
|
9409
|
+
if (!baseUrl) return null;
|
|
9410
|
+
let url;
|
|
9411
|
+
try {
|
|
9412
|
+
url = new URL(baseUrl);
|
|
9413
|
+
} catch {
|
|
9414
|
+
return null;
|
|
9415
|
+
}
|
|
9416
|
+
const host = url.hostname.toLowerCase();
|
|
9417
|
+
const path2 = url.pathname.toLowerCase();
|
|
9418
|
+
if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
|
|
9419
|
+
return "zai";
|
|
9420
|
+
}
|
|
9421
|
+
if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
|
|
9422
|
+
// anthropic `/anthropic` rows are excluded (their usage impl is unverified).
|
|
9423
|
+
(path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
|
|
9424
|
+
return "minimax-token-plan";
|
|
9425
|
+
}
|
|
9426
|
+
if (host === "api.code.umans.ai") return "umans";
|
|
9427
|
+
if (host === "api.synthetic.new") return "synthetic";
|
|
9428
|
+
return null;
|
|
9429
|
+
}
|
|
9430
|
+
function providerKeyQuotaUrl(adapter, baseUrl) {
|
|
9431
|
+
const origin = new URL(baseUrl).origin;
|
|
9432
|
+
if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
|
|
9433
|
+
if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
|
|
9434
|
+
if (adapter === "umans") return `${origin}/v1/usage`;
|
|
9435
|
+
return `${origin}/v2/quotas`;
|
|
9436
|
+
}
|
|
9437
|
+
function providerKeyQuotaAuthHeader(adapter, key) {
|
|
9438
|
+
return adapter === "zai" ? key : `Bearer ${key}`;
|
|
9439
|
+
}
|
|
9440
|
+
function zaiWindowDurationMs(item) {
|
|
9441
|
+
const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
|
|
9442
|
+
switch (item.unit) {
|
|
9443
|
+
case 3:
|
|
9444
|
+
return count * HOUR_MS2;
|
|
9445
|
+
case 4:
|
|
9446
|
+
return count * DAY_MS2;
|
|
9447
|
+
case 5:
|
|
9448
|
+
return count * MONTH_MS;
|
|
9449
|
+
case 6:
|
|
9450
|
+
return WEEK_MS;
|
|
9451
|
+
default:
|
|
9452
|
+
return void 0;
|
|
9453
|
+
}
|
|
9454
|
+
}
|
|
9455
|
+
function zaiWindowIdLabel(durationMs) {
|
|
9456
|
+
if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
|
|
9457
|
+
if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
|
|
9458
|
+
if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
|
|
9459
|
+
if (durationMs !== void 0 && durationMs % DAY_MS2 === 0) {
|
|
9460
|
+
const days = durationMs / DAY_MS2;
|
|
9461
|
+
return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
|
|
9462
|
+
}
|
|
9463
|
+
if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
|
|
9464
|
+
const hours = durationMs / HOUR_MS2;
|
|
9465
|
+
return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
|
|
9466
|
+
}
|
|
9467
|
+
return { id: "quota", label: "Quota" };
|
|
9468
|
+
}
|
|
9469
|
+
function parseZaiQuotaPayload(payload, now) {
|
|
9470
|
+
if (!isRecord5(payload)) return null;
|
|
9471
|
+
const data = isRecord5(payload["data"]) ? payload["data"] : payload;
|
|
9472
|
+
if (payload["success"] === false) return null;
|
|
9473
|
+
const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
|
|
9474
|
+
const byWindow = /* @__PURE__ */ new Map();
|
|
9475
|
+
for (const raw of limits) {
|
|
9476
|
+
if (!isRecord5(raw)) continue;
|
|
9477
|
+
const item = raw;
|
|
9478
|
+
if (item.type === void 0) continue;
|
|
9479
|
+
const details = raw["usageDetails"];
|
|
9480
|
+
if (Array.isArray(details) && details.some((d) => isRecord5(d) && d["modelCode"] === "zread")) {
|
|
9481
|
+
continue;
|
|
9482
|
+
}
|
|
9483
|
+
const durationMs = zaiWindowDurationMs(item);
|
|
9484
|
+
const { id, label } = zaiWindowIdLabel(durationMs);
|
|
9485
|
+
const limit = finiteNumber3(item.usage);
|
|
9486
|
+
const used = finiteNumber3(item.currentValue);
|
|
9487
|
+
const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
|
|
9488
|
+
const fromPercentage = finitePercent4(item.percentage) ?? void 0;
|
|
9489
|
+
const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
|
|
9490
|
+
if (usedPercent === void 0) continue;
|
|
9491
|
+
const resetsAt = isoInstant3(item.nextResetTime);
|
|
9492
|
+
const candidate = {
|
|
9493
|
+
id,
|
|
9494
|
+
label,
|
|
9495
|
+
scope: "all",
|
|
9496
|
+
usedPercent,
|
|
9497
|
+
...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS2) } : {},
|
|
9498
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9499
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
9500
|
+
state: "fresh"
|
|
9501
|
+
};
|
|
9502
|
+
const existing = byWindow.get(id);
|
|
9503
|
+
if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
|
|
9504
|
+
byWindow.set(id, candidate);
|
|
9505
|
+
}
|
|
9506
|
+
}
|
|
9507
|
+
const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
|
|
9508
|
+
return windows.length > 0 ? windows.slice(0, 4) : null;
|
|
9509
|
+
}
|
|
9510
|
+
var MINIMAX_STATUS_EXHAUSTED = 2;
|
|
9511
|
+
var MINIMAX_SHARED_BUCKET = "general";
|
|
9512
|
+
function parseMiniMaxBucket(value) {
|
|
9513
|
+
if (!isRecord5(value)) return null;
|
|
9514
|
+
const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
|
|
9515
|
+
if (!modelName) return null;
|
|
9516
|
+
const instant = (v) => {
|
|
9517
|
+
const n = finiteNumber3(v);
|
|
9518
|
+
return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
|
|
9519
|
+
};
|
|
9520
|
+
return {
|
|
9521
|
+
modelName,
|
|
9522
|
+
intervalEnd: instant(value["end_time"]),
|
|
9523
|
+
intervalRemainingPercent: finiteNumber3(value["current_interval_remaining_percent"]),
|
|
9524
|
+
intervalStatus: finiteNumber3(value["current_interval_status"]),
|
|
9525
|
+
weeklyEnd: instant(value["weekly_end_time"]),
|
|
9526
|
+
weeklyRemainingPercent: finiteNumber3(value["current_weekly_remaining_percent"]),
|
|
9527
|
+
weeklyStatus: finiteNumber3(value["current_weekly_status"])
|
|
9528
|
+
};
|
|
9529
|
+
}
|
|
9530
|
+
function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
|
|
9531
|
+
const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
|
|
9532
|
+
const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
|
|
9533
|
+
return {
|
|
9534
|
+
id,
|
|
9535
|
+
label,
|
|
9536
|
+
scope: "all",
|
|
9537
|
+
usedPercent,
|
|
9538
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
9539
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9540
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
9541
|
+
state: usedPercent !== null ? "fresh" : "unavailable"
|
|
9542
|
+
};
|
|
9543
|
+
}
|
|
9544
|
+
function parseMiniMaxTokenPlanPayload(payload, now) {
|
|
9545
|
+
if (!isRecord5(payload)) return null;
|
|
9546
|
+
const baseResp = payload["base_resp"];
|
|
9547
|
+
if (!isRecord5(baseResp) || baseResp["status_code"] !== 0) return null;
|
|
9548
|
+
const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
|
|
9549
|
+
let general = null;
|
|
9550
|
+
for (const raw of buckets) {
|
|
9551
|
+
const bucket = parseMiniMaxBucket(raw);
|
|
9552
|
+
if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
|
|
9553
|
+
general = bucket;
|
|
9554
|
+
break;
|
|
9555
|
+
}
|
|
9556
|
+
}
|
|
9557
|
+
if (!general) return null;
|
|
9558
|
+
return [
|
|
9559
|
+
minimaxWindow(
|
|
9560
|
+
"five-hour",
|
|
9561
|
+
"5 hours",
|
|
9562
|
+
5 * 60,
|
|
9563
|
+
general.intervalEnd,
|
|
9564
|
+
general.intervalRemainingPercent,
|
|
9565
|
+
general.intervalStatus,
|
|
9566
|
+
now
|
|
9567
|
+
),
|
|
9568
|
+
minimaxWindow(
|
|
9569
|
+
"seven-day",
|
|
9570
|
+
"7 days",
|
|
9571
|
+
Math.round(WEEK_MS / MINUTE_MS2),
|
|
9572
|
+
general.weeklyEnd,
|
|
9573
|
+
general.weeklyRemainingPercent,
|
|
9574
|
+
general.weeklyStatus,
|
|
9575
|
+
now
|
|
9576
|
+
)
|
|
9577
|
+
];
|
|
9578
|
+
}
|
|
9579
|
+
function parseUmansUsagePayload(payload, now) {
|
|
9580
|
+
if (!isRecord5(payload)) return null;
|
|
9581
|
+
const limits = isRecord5(payload["limits"]) ? payload["limits"] : void 0;
|
|
9582
|
+
const requests = limits && isRecord5(limits["requests"]) ? limits["requests"] : void 0;
|
|
9583
|
+
const usage = isRecord5(payload["usage"]) ? payload["usage"] : void 0;
|
|
9584
|
+
const window = isRecord5(payload["window"]) ? payload["window"] : void 0;
|
|
9585
|
+
const hardCap = finiteNumber3(requests?.["hard_cap"]);
|
|
9586
|
+
const softLimit = finiteNumber3(requests?.["limit"]);
|
|
9587
|
+
const requestsInWindow = finiteNumber3(usage?.["requests_in_window"]);
|
|
9588
|
+
const weightedInWindow = finiteNumber3(usage?.["weighted_in_window"]);
|
|
9589
|
+
const resetsAt = isoInstant3(window?.["resets_at"]);
|
|
9590
|
+
let usedPercent = null;
|
|
9591
|
+
if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
|
|
9592
|
+
usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
|
|
9593
|
+
} else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
|
|
9594
|
+
usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
|
|
9595
|
+
}
|
|
9596
|
+
if (usedPercent === null && resetsAt === void 0) return null;
|
|
9597
|
+
return [
|
|
9598
|
+
{
|
|
9599
|
+
id: "five-hour",
|
|
9600
|
+
label: "5 hours",
|
|
9601
|
+
scope: "all",
|
|
9602
|
+
usedPercent,
|
|
9603
|
+
windowMinutes: 5 * 60,
|
|
9604
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9605
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
9606
|
+
state: "fresh"
|
|
9607
|
+
}
|
|
9608
|
+
];
|
|
9609
|
+
}
|
|
9610
|
+
function parseSyntheticQuotasPayload(payload, now) {
|
|
9611
|
+
if (!isRecord5(payload)) return null;
|
|
9612
|
+
const fiveHour = isRecord5(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
|
|
9613
|
+
const weekly = isRecord5(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
|
|
9614
|
+
const windows = [];
|
|
9615
|
+
if (fiveHour) {
|
|
9616
|
+
const max = finiteNumber3(fiveHour["max"]);
|
|
9617
|
+
const remaining = finiteNumber3(fiveHour["remaining"]);
|
|
9618
|
+
const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
|
|
9619
|
+
const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
|
|
9620
|
+
windows.push({
|
|
9621
|
+
id: "five-hour",
|
|
9622
|
+
label: "5 hours",
|
|
9623
|
+
scope: "all",
|
|
9624
|
+
usedPercent,
|
|
9625
|
+
windowMinutes: 5 * 60,
|
|
9626
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9627
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
9628
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
9629
|
+
});
|
|
9630
|
+
}
|
|
9631
|
+
if (weekly) {
|
|
9632
|
+
const percentRemaining = finiteNumber3(weekly["percentRemaining"]);
|
|
9633
|
+
const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
|
|
9634
|
+
const resetsAt = isoInstant3(weekly["nextRegenAt"]);
|
|
9635
|
+
windows.push({
|
|
9636
|
+
id: "seven-day",
|
|
9637
|
+
label: "7 days",
|
|
9638
|
+
scope: "all",
|
|
9639
|
+
usedPercent,
|
|
9640
|
+
windowMinutes: 7 * 24 * 60,
|
|
9641
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
9642
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
9643
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
9644
|
+
});
|
|
9645
|
+
}
|
|
9646
|
+
return windows.length > 0 ? windows : null;
|
|
9647
|
+
}
|
|
9648
|
+
|
|
9649
|
+
// src/allowance/ProviderKeyQuotaService.ts
|
|
9650
|
+
function parseQuotaPayload(adapter, payload, now) {
|
|
9651
|
+
switch (adapter) {
|
|
9652
|
+
case "zai":
|
|
9653
|
+
return parseZaiQuotaPayload(payload, now);
|
|
9654
|
+
case "minimax-token-plan":
|
|
9655
|
+
return parseMiniMaxTokenPlanPayload(payload, now);
|
|
9656
|
+
case "umans":
|
|
9657
|
+
return parseUmansUsagePayload(payload, now);
|
|
9658
|
+
case "synthetic":
|
|
9659
|
+
return parseSyntheticQuotasPayload(payload, now);
|
|
9660
|
+
}
|
|
9661
|
+
}
|
|
9662
|
+
var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
|
|
9663
|
+
function resolvedBaseUrl(row) {
|
|
9664
|
+
const modes = row.apiModes ?? [];
|
|
9665
|
+
const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
|
|
9666
|
+
const fallback = modes[0];
|
|
9667
|
+
const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
|
|
9668
|
+
return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
|
|
9669
|
+
}
|
|
9670
|
+
function rowKeyEntries(row) {
|
|
9671
|
+
const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
|
|
9672
|
+
if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
|
|
9673
|
+
if (row.apiKey.length > 0) {
|
|
9674
|
+
return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
|
|
9675
|
+
}
|
|
9676
|
+
return [];
|
|
9677
|
+
}
|
|
9678
|
+
var ProviderKeyQuotaService = class {
|
|
9679
|
+
constructor(box, fetchImpl = (url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init, { redactBodies: true }), now = Date.now) {
|
|
9680
|
+
this.box = box;
|
|
9681
|
+
this.fetchImpl = fetchImpl;
|
|
9682
|
+
this.now = now;
|
|
9683
|
+
}
|
|
9684
|
+
box;
|
|
9685
|
+
fetchImpl;
|
|
9686
|
+
now;
|
|
9687
|
+
cache = /* @__PURE__ */ new Map();
|
|
9688
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
9689
|
+
/**
|
|
9690
|
+
* Quota for one key of a provider row, or `null` when the row has no quota
|
|
9691
|
+
* adapter / no such key. Cache-first; concurrent reads share one flight.
|
|
9692
|
+
*/
|
|
9693
|
+
async quotaFor(row, keyId, options = {}) {
|
|
9694
|
+
const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
|
|
9695
|
+
if (!adapter) return null;
|
|
9696
|
+
const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
|
|
9697
|
+
if (!entry) return null;
|
|
9698
|
+
const cacheKey = `${row.id}\0${keyId}`;
|
|
9699
|
+
const now = this.now();
|
|
9700
|
+
const cached = this.cache.get(cacheKey);
|
|
9701
|
+
if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
|
|
9702
|
+
const running = this.inFlight.get(cacheKey);
|
|
9703
|
+
if (running) return running;
|
|
9704
|
+
const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
|
|
9705
|
+
void error;
|
|
9706
|
+
const previous = this.cache.get(cacheKey);
|
|
9707
|
+
if (previous) {
|
|
9708
|
+
const degraded = {
|
|
9709
|
+
...previous,
|
|
9710
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9711
|
+
windows: previous.windows.map((window) => ({
|
|
9712
|
+
...window,
|
|
9713
|
+
state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
|
|
9714
|
+
})),
|
|
9715
|
+
errorCode: "quota_request_failed"
|
|
9716
|
+
};
|
|
9717
|
+
this.cache.set(cacheKey, degraded);
|
|
9718
|
+
return degraded;
|
|
9719
|
+
}
|
|
9720
|
+
return null;
|
|
9721
|
+
}).finally(() => this.inFlight.delete(cacheKey));
|
|
9722
|
+
this.inFlight.set(cacheKey, promise);
|
|
9723
|
+
return promise;
|
|
9724
|
+
}
|
|
9725
|
+
/** Drop cached rows for a provider (key added/removed/rotated). */
|
|
9726
|
+
invalidateProvider(providerRowId) {
|
|
9727
|
+
for (const key of this.cache.keys()) {
|
|
9728
|
+
if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
|
|
9729
|
+
}
|
|
9730
|
+
}
|
|
9731
|
+
async fetchQuota(adapter, row, rawKey, cacheKey) {
|
|
9732
|
+
const baseUrl = resolvedBaseUrl(row);
|
|
9733
|
+
const url = providerKeyQuotaUrl(adapter, baseUrl);
|
|
9734
|
+
const key = this.box.decryptMaybe(rawKey);
|
|
9735
|
+
const now = this.now();
|
|
9736
|
+
const response = await this.fetchImpl(url, {
|
|
9737
|
+
method: "GET",
|
|
9738
|
+
headers: {
|
|
9739
|
+
Authorization: providerKeyQuotaAuthHeader(adapter, key),
|
|
9740
|
+
Accept: "application/json",
|
|
9741
|
+
"Content-Type": "application/json"
|
|
9742
|
+
},
|
|
9743
|
+
signal: AbortSignal.timeout(15e3)
|
|
9744
|
+
});
|
|
9745
|
+
if (response.status === 401 || response.status === 403) {
|
|
9746
|
+
const snapshot2 = {
|
|
9747
|
+
adapter,
|
|
9748
|
+
observedAt: new Date(now).toISOString(),
|
|
9749
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9750
|
+
windows: [],
|
|
9751
|
+
errorCode: "quota_unauthorized"
|
|
9752
|
+
};
|
|
9753
|
+
this.cache.set(cacheKey, snapshot2);
|
|
9754
|
+
return snapshot2;
|
|
9755
|
+
}
|
|
9756
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
9757
|
+
let payload;
|
|
9758
|
+
try {
|
|
9759
|
+
payload = await response.json();
|
|
9760
|
+
} catch {
|
|
9761
|
+
throw new Error("invalid JSON");
|
|
9762
|
+
}
|
|
9763
|
+
const windows = parseQuotaPayload(adapter, payload, now);
|
|
9764
|
+
const snapshot = {
|
|
9765
|
+
adapter,
|
|
9766
|
+
observedAt: new Date(now).toISOString(),
|
|
9767
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9768
|
+
windows: windows ?? [],
|
|
9769
|
+
...windows ? {} : { errorCode: "quota_unavailable" }
|
|
9770
|
+
};
|
|
9771
|
+
this.cache.set(cacheKey, snapshot);
|
|
9772
|
+
return snapshot;
|
|
9773
|
+
}
|
|
9774
|
+
};
|
|
9775
|
+
|
|
8517
9776
|
// src/image-generation/ImageDoctorService.ts
|
|
8518
9777
|
var import_image_generation = require("@omnicross/core/image-generation");
|
|
8519
9778
|
var import_outbound_api7 = require("@omnicross/core/outbound-api");
|
|
8520
|
-
var
|
|
9779
|
+
var import_subscriptions6 = require("@omnicross/subscriptions");
|
|
8521
9780
|
|
|
8522
9781
|
// src/image-generation/FileCodexImageCapabilityEvidenceSource.ts
|
|
8523
9782
|
var import_node_crypto13 = require("crypto");
|
|
@@ -8955,7 +10214,7 @@ function createImageDoctorService(options) {
|
|
|
8955
10214
|
paths,
|
|
8956
10215
|
ttlMs: config.evidenceTtlMs
|
|
8957
10216
|
}));
|
|
8958
|
-
const createLiveVerifier = options.createLiveVerifier ?? ((strategy, config) => (0,
|
|
10217
|
+
const createLiveVerifier = options.createLiveVerifier ?? ((strategy, config) => (0, import_subscriptions6.createCodexImageLiveVerifier)({
|
|
8959
10218
|
authStrategy: strategy,
|
|
8960
10219
|
generationTimeoutMs: config.queue.generationTimeoutMs
|
|
8961
10220
|
}));
|
|
@@ -9317,7 +10576,7 @@ var ImageCleanupService = class {
|
|
|
9317
10576
|
var import_node_crypto16 = require("crypto");
|
|
9318
10577
|
var import_image_generation5 = require("@omnicross/core/image-generation");
|
|
9319
10578
|
var import_outbound_api8 = require("@omnicross/core/outbound-api");
|
|
9320
|
-
var
|
|
10579
|
+
var import_subscriptions7 = require("@omnicross/subscriptions");
|
|
9321
10580
|
|
|
9322
10581
|
// src/image-generation/ImageApiRuntimeResolver.ts
|
|
9323
10582
|
var import_node_crypto14 = require("crypto");
|
|
@@ -9848,7 +11107,7 @@ function createImageRuntimeGeneration(options) {
|
|
|
9848
11107
|
now: options.now ?? Date.now,
|
|
9849
11108
|
referenceStore: options.storage.referenceStore,
|
|
9850
11109
|
stateStore: options.storage.stateStore
|
|
9851
|
-
}) : (0,
|
|
11110
|
+
}) : (0, import_subscriptions7.createCodexSubscriptionImageProvider)({
|
|
9852
11111
|
authStrategy,
|
|
9853
11112
|
evidenceSource: generationEvidenceSource,
|
|
9854
11113
|
executionScheduler: scheduler,
|
|
@@ -14514,10 +15773,13 @@ function bucketLabel(bucketStartTs, bucket) {
|
|
|
14514
15773
|
}
|
|
14515
15774
|
|
|
14516
15775
|
// src/ports/JsonOutboundKeyDb.ts
|
|
15776
|
+
var import_node_fs23 = require("fs");
|
|
15777
|
+
var import_core3 = require("@omnicross/core");
|
|
15778
|
+
|
|
15779
|
+
// src/ports/atomicFile.ts
|
|
14517
15780
|
var import_node_crypto22 = require("crypto");
|
|
14518
15781
|
var import_node_fs22 = require("fs");
|
|
14519
15782
|
var import_node_path25 = require("path");
|
|
14520
|
-
var import_core3 = require("@omnicross/core");
|
|
14521
15783
|
function atomicReplaceUtf8(targetPath, contents) {
|
|
14522
15784
|
const tempPath = (0, import_node_path25.join)(
|
|
14523
15785
|
(0, import_node_path25.dirname)(targetPath),
|
|
@@ -14547,6 +15809,8 @@ function atomicReplaceUtf8(targetPath, contents) {
|
|
|
14547
15809
|
throw error;
|
|
14548
15810
|
}
|
|
14549
15811
|
}
|
|
15812
|
+
|
|
15813
|
+
// src/ports/JsonOutboundKeyDb.ts
|
|
14550
15814
|
var JsonOutboundKeyDb = class {
|
|
14551
15815
|
/**
|
|
14552
15816
|
* @param secretBox OPTIONAL reversible-secret codec. When present, a created
|
|
@@ -14689,9 +15953,9 @@ var JsonOutboundKeyDb = class {
|
|
|
14689
15953
|
}
|
|
14690
15954
|
/** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14691
15955
|
readRows() {
|
|
14692
|
-
if (!(0,
|
|
15956
|
+
if (!(0, import_node_fs23.existsSync)(this.keysPath)) return [];
|
|
14693
15957
|
try {
|
|
14694
|
-
const parsed = JSON.parse((0,
|
|
15958
|
+
const parsed = JSON.parse((0, import_node_fs23.readFileSync)(this.keysPath, "utf8"));
|
|
14695
15959
|
return Array.isArray(parsed) ? parsed : [];
|
|
14696
15960
|
} catch {
|
|
14697
15961
|
return [];
|
|
@@ -14708,7 +15972,7 @@ function applyPolicyField(row, field, value) {
|
|
|
14708
15972
|
}
|
|
14709
15973
|
|
|
14710
15974
|
// src/ports/JsonPricingStore.ts
|
|
14711
|
-
var
|
|
15975
|
+
var import_node_fs24 = require("fs");
|
|
14712
15976
|
var import_node_crypto23 = require("crypto");
|
|
14713
15977
|
var JsonPricingStore = class {
|
|
14714
15978
|
constructor(pricingPath) {
|
|
@@ -14723,9 +15987,9 @@ var JsonPricingStore = class {
|
|
|
14723
15987
|
* otherwise unusable pricing table after a crash or manual file edit.
|
|
14724
15988
|
*/
|
|
14725
15989
|
hasUsableSnapshot() {
|
|
14726
|
-
if (!(0,
|
|
15990
|
+
if (!(0, import_node_fs24.existsSync)(this.pricingPath)) return false;
|
|
14727
15991
|
try {
|
|
14728
|
-
const parsed = JSON.parse((0,
|
|
15992
|
+
const parsed = JSON.parse((0, import_node_fs24.readFileSync)(this.pricingPath, "utf8"));
|
|
14729
15993
|
return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
|
|
14730
15994
|
} catch {
|
|
14731
15995
|
return false;
|
|
@@ -14838,9 +16102,9 @@ var JsonPricingStore = class {
|
|
|
14838
16102
|
}
|
|
14839
16103
|
/** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14840
16104
|
readRows() {
|
|
14841
|
-
if (!(0,
|
|
16105
|
+
if (!(0, import_node_fs24.existsSync)(this.pricingPath)) return [];
|
|
14842
16106
|
try {
|
|
14843
|
-
const parsed = JSON.parse((0,
|
|
16107
|
+
const parsed = JSON.parse((0, import_node_fs24.readFileSync)(this.pricingPath, "utf8"));
|
|
14844
16108
|
return Array.isArray(parsed) ? parsed : [];
|
|
14845
16109
|
} catch {
|
|
14846
16110
|
return [];
|
|
@@ -14849,18 +16113,18 @@ var JsonPricingStore = class {
|
|
|
14849
16113
|
writeRows(rows) {
|
|
14850
16114
|
const temporaryPath = `${this.pricingPath}.${process.pid}.${(0, import_node_crypto23.randomUUID)()}.tmp`;
|
|
14851
16115
|
try {
|
|
14852
|
-
(0,
|
|
16116
|
+
(0, import_node_fs24.writeFileSync)(temporaryPath, JSON.stringify(rows, null, 2) + "\n", {
|
|
14853
16117
|
encoding: "utf8",
|
|
14854
16118
|
flag: "wx"
|
|
14855
16119
|
});
|
|
14856
16120
|
this.replaceFile(temporaryPath);
|
|
14857
16121
|
} finally {
|
|
14858
|
-
(0,
|
|
16122
|
+
(0, import_node_fs24.rmSync)(temporaryPath, { force: true });
|
|
14859
16123
|
}
|
|
14860
16124
|
}
|
|
14861
16125
|
/** Isolated for deterministic failure testing; never removes the target. */
|
|
14862
16126
|
replaceFile(temporaryPath) {
|
|
14863
|
-
(0,
|
|
16127
|
+
(0, import_node_fs24.renameSync)(temporaryPath, this.pricingPath);
|
|
14864
16128
|
}
|
|
14865
16129
|
};
|
|
14866
16130
|
function isUsablePricingRow(value) {
|
|
@@ -14870,7 +16134,7 @@ function isUsablePricingRow(value) {
|
|
|
14870
16134
|
}
|
|
14871
16135
|
|
|
14872
16136
|
// src/pricing/PricingRefreshScheduler.ts
|
|
14873
|
-
var
|
|
16137
|
+
var import_node_fs25 = require("fs");
|
|
14874
16138
|
var EMPTY_STATE2 = {
|
|
14875
16139
|
lastAttemptAt: null,
|
|
14876
16140
|
lastSuccessAt: null,
|
|
@@ -14908,9 +16172,9 @@ var PricingRefreshScheduler = class {
|
|
|
14908
16172
|
this.timer = null;
|
|
14909
16173
|
}
|
|
14910
16174
|
getState() {
|
|
14911
|
-
if (!(0,
|
|
16175
|
+
if (!(0, import_node_fs25.existsSync)(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
|
|
14912
16176
|
try {
|
|
14913
|
-
const value = JSON.parse((0,
|
|
16177
|
+
const value = JSON.parse((0, import_node_fs25.readFileSync)(this.statePath, "utf8"));
|
|
14914
16178
|
return {
|
|
14915
16179
|
lastAttemptAt: finiteOrNull(value.lastAttemptAt),
|
|
14916
16180
|
lastSuccessAt: finiteOrNull(value.lastSuccessAt),
|
|
@@ -14963,9 +16227,9 @@ var PricingRefreshScheduler = class {
|
|
|
14963
16227
|
}
|
|
14964
16228
|
writeState(state) {
|
|
14965
16229
|
const temporaryPath = `${this.statePath}.tmp`;
|
|
14966
|
-
(0,
|
|
16230
|
+
(0, import_node_fs25.writeFileSync)(temporaryPath, `${JSON.stringify(state, null, 2)}
|
|
14967
16231
|
`, "utf8");
|
|
14968
|
-
(0,
|
|
16232
|
+
(0, import_node_fs25.renameSync)(temporaryPath, this.statePath);
|
|
14969
16233
|
}
|
|
14970
16234
|
};
|
|
14971
16235
|
function finiteOrNull(value) {
|
|
@@ -14973,7 +16237,7 @@ function finiteOrNull(value) {
|
|
|
14973
16237
|
}
|
|
14974
16238
|
|
|
14975
16239
|
// src/ports/JsonVoucherDb.ts
|
|
14976
|
-
var
|
|
16240
|
+
var import_node_fs26 = require("fs");
|
|
14977
16241
|
var JsonVoucherDb = class {
|
|
14978
16242
|
constructor(vouchersPath) {
|
|
14979
16243
|
this.vouchersPath = vouchersPath;
|
|
@@ -15051,27 +16315,27 @@ var JsonVoucherDb = class {
|
|
|
15051
16315
|
}
|
|
15052
16316
|
/** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
|
|
15053
16317
|
readRows() {
|
|
15054
|
-
if (!(0,
|
|
16318
|
+
if (!(0, import_node_fs26.existsSync)(this.vouchersPath)) return [];
|
|
15055
16319
|
try {
|
|
15056
|
-
const parsed = JSON.parse((0,
|
|
16320
|
+
const parsed = JSON.parse((0, import_node_fs26.readFileSync)(this.vouchersPath, "utf8"));
|
|
15057
16321
|
return Array.isArray(parsed) ? parsed : [];
|
|
15058
16322
|
} catch {
|
|
15059
16323
|
return [];
|
|
15060
16324
|
}
|
|
15061
16325
|
}
|
|
15062
16326
|
writeRows(rows) {
|
|
15063
|
-
(0,
|
|
16327
|
+
(0, import_node_fs26.writeFileSync)(this.vouchersPath, JSON.stringify(rows, null, 2) + "\n", "utf8");
|
|
15064
16328
|
}
|
|
15065
16329
|
};
|
|
15066
16330
|
|
|
15067
16331
|
// src/ports/JsonSubscriptionCredentialStore.ts
|
|
15068
|
-
var
|
|
16332
|
+
var import_node_fs28 = require("fs");
|
|
15069
16333
|
var import_node_path27 = require("path");
|
|
15070
16334
|
var import_SubscriptionAccountHealth2 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
|
|
15071
16335
|
var import_AccountAllowanceScheduling3 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
|
|
15072
|
-
var
|
|
16336
|
+
var import_upstreamFetch9 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
15073
16337
|
var import_SubscriptionIdentityStore2 = require("@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore");
|
|
15074
|
-
var
|
|
16338
|
+
var import_subscriptions8 = require("@omnicross/subscriptions");
|
|
15075
16339
|
|
|
15076
16340
|
// src/ports/account-sync.ts
|
|
15077
16341
|
function viewOf(tokens) {
|
|
@@ -15115,7 +16379,7 @@ function findDuplicateCredentialIds(accounts) {
|
|
|
15115
16379
|
}
|
|
15116
16380
|
|
|
15117
16381
|
// src/ports/external-cli-credentials.ts
|
|
15118
|
-
var
|
|
16382
|
+
var import_node_fs27 = require("fs");
|
|
15119
16383
|
var import_node_os5 = require("os");
|
|
15120
16384
|
var import_node_path26 = require("path");
|
|
15121
16385
|
function externalStorePath(provider, home = (0, import_node_os5.homedir)()) {
|
|
@@ -15168,10 +16432,10 @@ function parseCodexTokensEnvelope(raw) {
|
|
|
15168
16432
|
}
|
|
15169
16433
|
function readExternalCliCredentials(provider, home = (0, import_node_os5.homedir)()) {
|
|
15170
16434
|
const path2 = externalStorePath(provider, home);
|
|
15171
|
-
if (!(0,
|
|
16435
|
+
if (!(0, import_node_fs27.existsSync)(path2)) return null;
|
|
15172
16436
|
let raw;
|
|
15173
16437
|
try {
|
|
15174
|
-
const parsed = JSON.parse((0,
|
|
16438
|
+
const parsed = JSON.parse((0, import_node_fs27.readFileSync)(path2, "utf8"));
|
|
15175
16439
|
raw = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
15176
16440
|
} catch {
|
|
15177
16441
|
return null;
|
|
@@ -15194,16 +16458,18 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15194
16458
|
* as on relay refresh egresses from the SAME proxy IP as the
|
|
15195
16459
|
* account's traffic. NOT used by any read/write path.
|
|
15196
16460
|
*/
|
|
15197
|
-
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
|
|
16461
|
+
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
|
|
15198
16462
|
this.tokensPath = tokensPath;
|
|
15199
16463
|
this.box = box;
|
|
15200
16464
|
this.fetchImpl = fetchImpl;
|
|
15201
16465
|
this.externalCliReader = externalCliReader;
|
|
16466
|
+
this.atomicReplace = atomicReplace;
|
|
15202
16467
|
}
|
|
15203
16468
|
tokensPath;
|
|
15204
16469
|
box;
|
|
15205
16470
|
fetchImpl;
|
|
15206
16471
|
externalCliReader;
|
|
16472
|
+
atomicReplace;
|
|
15207
16473
|
/**
|
|
15208
16474
|
* The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
|
|
15209
16475
|
* TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
|
|
@@ -15217,7 +16483,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15217
16483
|
* a plaintext token pair into `upstream-trace.jsonl`.
|
|
15218
16484
|
*/
|
|
15219
16485
|
buildRefreshFetch(providerId, accountId) {
|
|
15220
|
-
return this.fetchImpl ?? ((url, init) => (0,
|
|
16486
|
+
return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init, { providerId, accountId, redactBodies: true }));
|
|
15221
16487
|
}
|
|
15222
16488
|
/**
|
|
15223
16489
|
* In-flight refresh coalescing. OAuth refresh tokens are
|
|
@@ -15258,7 +16524,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15258
16524
|
* other hot reads. Never returns token material.
|
|
15259
16525
|
*/
|
|
15260
16526
|
getAccountProxy(providerId, accountId) {
|
|
15261
|
-
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
|
|
16527
|
+
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
|
|
15262
16528
|
return void 0;
|
|
15263
16529
|
}
|
|
15264
16530
|
return getAccountProxy(this.readConfig(), providerId, accountId);
|
|
@@ -15277,7 +16543,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15277
16543
|
const fingerprintOn = identityStore.isEnabled();
|
|
15278
16544
|
const now = Date.now();
|
|
15279
16545
|
const out = {};
|
|
15280
|
-
for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
|
|
16546
|
+
for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
|
|
15281
16547
|
const sanitized = sanitizeAccounts(config, provider);
|
|
15282
16548
|
if (sanitized.length === 0) continue;
|
|
15283
16549
|
for (const account of sanitized) {
|
|
@@ -15343,7 +16609,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15343
16609
|
this.materializeMigration(config);
|
|
15344
16610
|
const refreshFetch = this.buildRefreshFetch("claude", capturedId);
|
|
15345
16611
|
try {
|
|
15346
|
-
const result = await
|
|
16612
|
+
const result = await import_subscriptions8.claudeOAuth.refreshAccessToken(claude.refreshToken, refreshFetch);
|
|
15347
16613
|
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
15348
16614
|
const next = {
|
|
15349
16615
|
...claude,
|
|
@@ -15378,7 +16644,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15378
16644
|
this.materializeMigration(config);
|
|
15379
16645
|
const refreshFetch = this.buildRefreshFetch("codex", capturedId);
|
|
15380
16646
|
try {
|
|
15381
|
-
const result = await
|
|
16647
|
+
const result = await import_subscriptions8.codexOAuth.refreshAccessToken(codex.refreshToken, refreshFetch);
|
|
15382
16648
|
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
15383
16649
|
const next = {
|
|
15384
16650
|
...codex,
|
|
@@ -15416,7 +16682,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15416
16682
|
this.materializeMigration(config);
|
|
15417
16683
|
const refreshFetch = this.buildRefreshFetch("gemini", capturedId);
|
|
15418
16684
|
try {
|
|
15419
|
-
const result = await
|
|
16685
|
+
const result = await import_subscriptions8.geminiOAuth.refreshAccessToken(gemini.refreshToken, refreshFetch);
|
|
15420
16686
|
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
15421
16687
|
const next = {
|
|
15422
16688
|
...gemini,
|
|
@@ -15435,6 +16701,47 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15435
16701
|
}
|
|
15436
16702
|
});
|
|
15437
16703
|
}
|
|
16704
|
+
/**
|
|
16705
|
+
* Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
|
|
16706
|
+
* Kimi ROTATES the refresh token, so the response's pair is written back
|
|
16707
|
+
* whole; the account's stable `deviceId` (fingerprint header input) is
|
|
16708
|
+
* preserved. The refresh call carries the CLI fingerprint headers. HONEST
|
|
16709
|
+
* `false` when no refresh_token.
|
|
16710
|
+
*/
|
|
16711
|
+
async refreshKimiToken() {
|
|
16712
|
+
return this.coalesce("kimi:active", async () => {
|
|
16713
|
+
const config = this.readConfig();
|
|
16714
|
+
const active = getActiveAccount(config, "kimi");
|
|
16715
|
+
const kimi = active?.tokens;
|
|
16716
|
+
if (!active || !kimi?.refreshToken) return false;
|
|
16717
|
+
const capturedId = active.id;
|
|
16718
|
+
this.materializeMigration(config);
|
|
16719
|
+
const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
|
|
16720
|
+
try {
|
|
16721
|
+
const result = await import_subscriptions8.kimiOAuth.refreshAccessToken(
|
|
16722
|
+
kimi.refreshToken,
|
|
16723
|
+
refreshFetch,
|
|
16724
|
+
import_subscriptions8.kimiOAuth.kimiFingerprintHeaders(kimi.deviceId)
|
|
16725
|
+
);
|
|
16726
|
+
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
16727
|
+
const next = {
|
|
16728
|
+
...kimi,
|
|
16729
|
+
accessToken: result.accessToken,
|
|
16730
|
+
refreshToken: result.refreshToken,
|
|
16731
|
+
expiresAt,
|
|
16732
|
+
status: "authorized",
|
|
16733
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16734
|
+
errorMessage: void 0,
|
|
16735
|
+
syncWarning: void 0
|
|
16736
|
+
};
|
|
16737
|
+
this.writeBackById("kimi", capturedId, next);
|
|
16738
|
+
return true;
|
|
16739
|
+
} catch (error) {
|
|
16740
|
+
this.markExpiredById("kimi", capturedId, kimi, error);
|
|
16741
|
+
return false;
|
|
16742
|
+
}
|
|
16743
|
+
});
|
|
16744
|
+
}
|
|
15438
16745
|
/**
|
|
15439
16746
|
* Refresh a SPECIFIC managed account by id (background scheduler sweep and
|
|
15440
16747
|
* account-pool resolution). It uses only that account's stored refresh
|
|
@@ -15487,7 +16794,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15487
16794
|
}
|
|
15488
16795
|
const oauth = account.tokens;
|
|
15489
16796
|
if (!oauth.accessToken) return null;
|
|
15490
|
-
if (providerId === "codex" || providerId === "gemini") {
|
|
16797
|
+
if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
|
|
15491
16798
|
const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
|
|
15492
16799
|
const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
|
|
15493
16800
|
if (expiringSoon && oauth.refreshToken) {
|
|
@@ -15576,8 +16883,23 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15576
16883
|
}
|
|
15577
16884
|
/** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
|
|
15578
16885
|
async refreshUpstream(provider, refreshToken, accountId) {
|
|
15579
|
-
const
|
|
15580
|
-
|
|
16886
|
+
const refreshFetch = this.buildRefreshFetch(provider, accountId);
|
|
16887
|
+
if (provider === "kimi") {
|
|
16888
|
+
const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
|
|
16889
|
+
const deviceId = account?.tokens?.deviceId;
|
|
16890
|
+
const r2 = await import_subscriptions8.kimiOAuth.refreshAccessToken(
|
|
16891
|
+
refreshToken,
|
|
16892
|
+
refreshFetch,
|
|
16893
|
+
import_subscriptions8.kimiOAuth.kimiFingerprintHeaders(deviceId)
|
|
16894
|
+
);
|
|
16895
|
+
return {
|
|
16896
|
+
accessToken: r2.accessToken,
|
|
16897
|
+
refreshToken: r2.refreshToken,
|
|
16898
|
+
expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
|
|
16899
|
+
};
|
|
16900
|
+
}
|
|
16901
|
+
const flow = provider === "claude" ? import_subscriptions8.claudeOAuth : provider === "codex" ? import_subscriptions8.codexOAuth : import_subscriptions8.geminiOAuth;
|
|
16902
|
+
const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
|
|
15581
16903
|
return {
|
|
15582
16904
|
accessToken: r.accessToken,
|
|
15583
16905
|
refreshToken: r.refreshToken,
|
|
@@ -15740,42 +17062,86 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15740
17062
|
/** Write the merged config to disk as pretty JSON (mkdir parent if needed).
|
|
15741
17063
|
* Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
|
|
15742
17064
|
* `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
|
|
15743
|
-
* write incl. child 4's future refresh writes lands encrypted.
|
|
17065
|
+
* write incl. child 4's future refresh writes lands encrypted.
|
|
17066
|
+
* ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
|
|
17067
|
+
* interrupted write discards only the temp file; the prior `tokens.json`
|
|
17068
|
+
* survives byte-equal (bare `writeFileSync` truncate-writes lost every
|
|
17069
|
+
* account on a mid-write failure, 2026-09-06). */
|
|
15744
17070
|
persist(config) {
|
|
15745
|
-
(0,
|
|
17071
|
+
(0, import_node_fs28.mkdirSync)((0, import_node_path27.dirname)(this.tokensPath), { recursive: true });
|
|
15746
17072
|
const encrypted = encryptTokens(config, this.box);
|
|
15747
|
-
|
|
17073
|
+
this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
|
|
15748
17074
|
}
|
|
15749
17075
|
/**
|
|
15750
|
-
* Read + parse `tokens.json`,
|
|
15751
|
-
*
|
|
15752
|
-
* subscription bearer path is byte-identical).
|
|
17076
|
+
* Read + parse `tokens.json`, then DECRYPT the token-material fields so every
|
|
17077
|
+
* getter returns plaintext (the subscription bearer path is byte-identical).
|
|
15753
17078
|
*
|
|
15754
|
-
*
|
|
15755
|
-
* file
|
|
15756
|
-
*
|
|
15757
|
-
*
|
|
15758
|
-
*
|
|
15759
|
-
*
|
|
15760
|
-
* `
|
|
17079
|
+
* A MISSING file is a legitimate first-boot state → minimal `{ updatedAt: '' }`.
|
|
17080
|
+
* A file that EXISTS but cannot be parsed as a JSON object is CORRUPT →
|
|
17081
|
+
* `quarantineCorrupt` moves it aside (once) before the empty config is
|
|
17082
|
+
* returned, so the unreadable accounts survive for manual recovery.
|
|
17083
|
+
*
|
|
17084
|
+
* The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
|
|
17085
|
+
* tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
|
|
17086
|
+
* error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
|
|
17087
|
+
* decrypt would report "no tokens" and silently send the WRONG bearer
|
|
17088
|
+
* upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
|
|
17089
|
+
* its parse try.
|
|
15761
17090
|
*/
|
|
15762
17091
|
readConfig() {
|
|
15763
|
-
if (!(0,
|
|
17092
|
+
if (!(0, import_node_fs28.existsSync)(this.tokensPath)) return { updatedAt: "" };
|
|
15764
17093
|
let parsed;
|
|
15765
17094
|
try {
|
|
15766
|
-
const raw = JSON.parse((0,
|
|
15767
|
-
|
|
17095
|
+
const raw = JSON.parse((0, import_node_fs28.readFileSync)(this.tokensPath, "utf8"));
|
|
17096
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
17097
|
+
return this.quarantineCorrupt("parsed JSON is not an object");
|
|
17098
|
+
}
|
|
17099
|
+
parsed = raw;
|
|
15768
17100
|
} catch {
|
|
15769
|
-
|
|
17101
|
+
return this.quarantineCorrupt("unparseable JSON");
|
|
15770
17102
|
}
|
|
15771
|
-
if (!parsed) return { updatedAt: "" };
|
|
15772
17103
|
const decrypted = decryptTokens(parsed, this.box);
|
|
15773
17104
|
return migrateLazily(decrypted);
|
|
15774
17105
|
}
|
|
17106
|
+
/** One-shot latch: a corrupt file is quarantined (or found unmovable) at
|
|
17107
|
+
* most once per process, so the hot read path never re-attempts or re-logs. */
|
|
17108
|
+
corruptQuarantined = false;
|
|
17109
|
+
/**
|
|
17110
|
+
* Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
|
|
17111
|
+
*
|
|
17112
|
+
* Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
|
|
17113
|
+
* logs loudly (the daemon's stderr log; secret-free — reason + paths only).
|
|
17114
|
+
* The daemon KEEPS SERVING (API-key routing is unaffected; subscription
|
|
17115
|
+
* routing reports no credential, same as an absent file) while the corrupt
|
|
17116
|
+
* bytes survive for manual recovery — and, critically, the NEXT persist
|
|
17117
|
+
* (e.g. the user re-logging in) can no longer overwrite the only copy of
|
|
17118
|
+
* the old accounts, which is exactly how the 2026-09-06 incident turned a
|
|
17119
|
+
* recoverable truncated file into permanent account loss.
|
|
17120
|
+
*
|
|
17121
|
+
* Best-effort: if the rename fails (file locked, permissions), the corrupt
|
|
17122
|
+
* file is left in place and every later read still tolerates it as empty;
|
|
17123
|
+
* the latch still trips so the attempt + log happen exactly once.
|
|
17124
|
+
*/
|
|
17125
|
+
quarantineCorrupt(reason) {
|
|
17126
|
+
if (!this.corruptQuarantined) {
|
|
17127
|
+
this.corruptQuarantined = true;
|
|
17128
|
+
const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
17129
|
+
let moved = false;
|
|
17130
|
+
try {
|
|
17131
|
+
(0, import_node_fs28.renameSync)(this.tokensPath, backup);
|
|
17132
|
+
moved = true;
|
|
17133
|
+
} catch {
|
|
17134
|
+
}
|
|
17135
|
+
console.error(
|
|
17136
|
+
`[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`)
|
|
17137
|
+
);
|
|
17138
|
+
}
|
|
17139
|
+
return { updatedAt: "" };
|
|
17140
|
+
}
|
|
15775
17141
|
};
|
|
15776
17142
|
|
|
15777
17143
|
// src/AccountHealthProbeScheduler.ts
|
|
15778
|
-
var
|
|
17144
|
+
var import_upstreamFetch10 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
15779
17145
|
|
|
15780
17146
|
// src/probe/CodexGenerationProbe.ts
|
|
15781
17147
|
var import_codexCliHeaders = require("@omnicross/core/provider-proxy/identity/codexCliHeaders");
|
|
@@ -15914,7 +17280,11 @@ var PROVIDER_PROBE_PLANS = {
|
|
|
15914
17280
|
// billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
|
|
15915
17281
|
codex: { kind: "local" },
|
|
15916
17282
|
gemini: { kind: "local" },
|
|
15917
|
-
opencodego: { kind: "local" }
|
|
17283
|
+
opencodego: { kind: "local" },
|
|
17284
|
+
// Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
|
|
17285
|
+
// collector uses it), but the probe path also needs the fingerprint headers —
|
|
17286
|
+
// keep the probe local until the collector covers the health surface.
|
|
17287
|
+
kimi: { kind: "local" }
|
|
15918
17288
|
};
|
|
15919
17289
|
function probePlanFor(providerId) {
|
|
15920
17290
|
return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
|
|
@@ -15936,7 +17306,7 @@ var AccountHealthProbeScheduler = class {
|
|
|
15936
17306
|
this.logger = logger;
|
|
15937
17307
|
this.config = config;
|
|
15938
17308
|
this.now = opts.now ?? Date.now;
|
|
15939
|
-
this.fetchImpl = opts.fetchImpl ??
|
|
17309
|
+
this.fetchImpl = opts.fetchImpl ?? import_upstreamFetch10.fetchUpstream;
|
|
15940
17310
|
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
15941
17311
|
this.planFor = opts.planFor ?? probePlanFor;
|
|
15942
17312
|
}
|
|
@@ -16280,13 +17650,13 @@ var AccountHealthSweeper = class {
|
|
|
16280
17650
|
};
|
|
16281
17651
|
|
|
16282
17652
|
// src/audit/AuditPruneSweeper.ts
|
|
16283
|
-
var
|
|
17653
|
+
var import_node_fs30 = require("fs");
|
|
16284
17654
|
var import_node_path29 = require("path");
|
|
16285
17655
|
var import_promises6 = require("stream/promises");
|
|
16286
17656
|
var import_node_zlib2 = require("zlib");
|
|
16287
17657
|
|
|
16288
17658
|
// src/audit/auditStats.ts
|
|
16289
|
-
var
|
|
17659
|
+
var import_node_fs29 = require("fs");
|
|
16290
17660
|
var import_node_path28 = require("path");
|
|
16291
17661
|
var SIDECAR_VERSION = 1;
|
|
16292
17662
|
var META_PREFIX_BYTES = 64 * 1024;
|
|
@@ -16295,9 +17665,9 @@ function auditStatsFileName(auditFile) {
|
|
|
16295
17665
|
return auditFile.replace(/\.jsonl$/, ".stats.json");
|
|
16296
17666
|
}
|
|
16297
17667
|
function readPersisted(path2) {
|
|
16298
|
-
if (!(0,
|
|
17668
|
+
if (!(0, import_node_fs29.existsSync)(path2)) return null;
|
|
16299
17669
|
try {
|
|
16300
|
-
const value = JSON.parse((0,
|
|
17670
|
+
const value = JSON.parse((0, import_node_fs29.readFileSync)(path2, "utf8"));
|
|
16301
17671
|
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)) {
|
|
16302
17672
|
return null;
|
|
16303
17673
|
}
|
|
@@ -16327,7 +17697,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
|
|
|
16327
17697
|
minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
|
|
16328
17698
|
maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
|
|
16329
17699
|
};
|
|
16330
|
-
(0,
|
|
17700
|
+
(0, import_node_fs29.writeFileSync)(statsPath, JSON.stringify(next), "utf8");
|
|
16331
17701
|
}
|
|
16332
17702
|
function queryCovers(stats, from, to) {
|
|
16333
17703
|
return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
|
|
@@ -16385,7 +17755,7 @@ async function scanAuditFile(auditPath, startByte, auditBytes, from, to) {
|
|
|
16385
17755
|
prefixTruncated = false;
|
|
16386
17756
|
};
|
|
16387
17757
|
if (auditBytes > startByte) {
|
|
16388
|
-
const stream = (0,
|
|
17758
|
+
const stream = (0, import_node_fs29.createReadStream)(auditPath, {
|
|
16389
17759
|
start: startByte,
|
|
16390
17760
|
end: auditBytes - 1,
|
|
16391
17761
|
highWaterMark: READ_CHUNK_BYTES2
|
|
@@ -16438,12 +17808,12 @@ function mergePersistedStats(previous, appended) {
|
|
|
16438
17808
|
};
|
|
16439
17809
|
}
|
|
16440
17810
|
async function readAuditStats(auditDir, query2 = {}) {
|
|
16441
|
-
if (!(0,
|
|
17811
|
+
if (!(0, import_node_fs29.existsSync)(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
|
|
16442
17812
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
16443
17813
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
16444
17814
|
let sources;
|
|
16445
17815
|
try {
|
|
16446
|
-
sources = (0,
|
|
17816
|
+
sources = (0, import_node_fs29.readdirSync)(auditDir).filter((name) => fileOverlaps(name, from, to)).sort().map(
|
|
16447
17817
|
(name) => AUDIT_DAY_DIR_RE.test(name) ? {
|
|
16448
17818
|
auditPath: (0, import_node_path28.join)(auditDir, name, AUDIT_META_FILE),
|
|
16449
17819
|
statsPath: (0, import_node_path28.join)(auditDir, name, auditStatsFileName(AUDIT_META_FILE))
|
|
@@ -16451,14 +17821,14 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16451
17821
|
auditPath: (0, import_node_path28.join)(auditDir, name),
|
|
16452
17822
|
statsPath: (0, import_node_path28.join)(auditDir, auditStatsFileName(name))
|
|
16453
17823
|
}
|
|
16454
|
-
).filter((source) => (0,
|
|
17824
|
+
).filter((source) => (0, import_node_fs29.existsSync)(source.auditPath));
|
|
16455
17825
|
} catch {
|
|
16456
17826
|
return { requestCount: 0, errorCount: 0, complete: false };
|
|
16457
17827
|
}
|
|
16458
17828
|
const total = { requestCount: 0, errorCount: 0, complete: true };
|
|
16459
17829
|
for (const { auditPath, statsPath } of sources) {
|
|
16460
17830
|
try {
|
|
16461
|
-
const auditBytes = (0,
|
|
17831
|
+
const auditBytes = (0, import_node_fs29.statSync)(auditPath).size;
|
|
16462
17832
|
const persisted = readPersisted(statsPath);
|
|
16463
17833
|
if (persisted && persisted.complete && persisted.auditBytes === auditBytes && queryCovers(persisted, from, to)) {
|
|
16464
17834
|
total.requestCount += persisted.requestCount;
|
|
@@ -16477,7 +17847,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16477
17847
|
total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
|
|
16478
17848
|
total.complete = total.complete && scanned.filtered.complete;
|
|
16479
17849
|
const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
|
|
16480
|
-
if (current.complete) (0,
|
|
17850
|
+
if (current.complete) (0, import_node_fs29.writeFileSync)(statsPath, JSON.stringify(current), "utf8");
|
|
16481
17851
|
} catch {
|
|
16482
17852
|
total.complete = false;
|
|
16483
17853
|
}
|
|
@@ -16486,7 +17856,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16486
17856
|
}
|
|
16487
17857
|
|
|
16488
17858
|
// src/audit/AuditPruneSweeper.ts
|
|
16489
|
-
var
|
|
17859
|
+
var DAY_MS3 = 24 * 60 * 6e4;
|
|
16490
17860
|
var SWEEP_INTERVAL_MS2 = 60 * 6e4;
|
|
16491
17861
|
var ARCHIVE_BATCH = 64;
|
|
16492
17862
|
var AuditPruneSweeper = class {
|
|
@@ -16549,19 +17919,19 @@ var AuditPruneSweeper = class {
|
|
|
16549
17919
|
if (!this.config.enabled || this.sweeping) return 0;
|
|
16550
17920
|
this.sweeping = true;
|
|
16551
17921
|
try {
|
|
16552
|
-
if (!(0,
|
|
16553
|
-
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) *
|
|
17922
|
+
if (!(0, import_node_fs30.existsSync)(this.auditDir)) return 0;
|
|
17923
|
+
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS3;
|
|
16554
17924
|
let removed = 0;
|
|
16555
|
-
for (const name of (0,
|
|
17925
|
+
for (const name of (0, import_node_fs30.readdirSync)(this.auditDir)) {
|
|
16556
17926
|
const dateMs = auditFileDateMs(name);
|
|
16557
17927
|
if (dateMs === null || dateMs >= cutoff) continue;
|
|
16558
17928
|
try {
|
|
16559
17929
|
if (isAuditDayDir(name)) {
|
|
16560
|
-
(0,
|
|
17930
|
+
(0, import_node_fs30.rmSync)((0, import_node_path29.join)(this.auditDir, name), { recursive: true, force: true });
|
|
16561
17931
|
} else {
|
|
16562
|
-
(0,
|
|
17932
|
+
(0, import_node_fs30.unlinkSync)((0, import_node_path29.join)(this.auditDir, name));
|
|
16563
17933
|
const statsPath = (0, import_node_path29.join)(this.auditDir, auditStatsFileName(name));
|
|
16564
|
-
if ((0,
|
|
17934
|
+
if ((0, import_node_fs30.existsSync)(statsPath)) (0, import_node_fs30.unlinkSync)(statsPath);
|
|
16565
17935
|
}
|
|
16566
17936
|
removed += 1;
|
|
16567
17937
|
} catch (error) {
|
|
@@ -16591,10 +17961,10 @@ var AuditPruneSweeper = class {
|
|
|
16591
17961
|
if (!this.config.enabled || this.archiving) return 0;
|
|
16592
17962
|
this.archiving = true;
|
|
16593
17963
|
try {
|
|
16594
|
-
if (!(0,
|
|
17964
|
+
if (!(0, import_node_fs30.existsSync)(this.auditDir)) return 0;
|
|
16595
17965
|
const today = this.todayMidnight();
|
|
16596
17966
|
let compressed = 0;
|
|
16597
|
-
for (const name of (0,
|
|
17967
|
+
for (const name of (0, import_node_fs30.readdirSync)(this.auditDir)) {
|
|
16598
17968
|
if (compressed >= ARCHIVE_BATCH) break;
|
|
16599
17969
|
const dateMs = auditFileDateMs(name);
|
|
16600
17970
|
if (dateMs === null || dateMs >= today || !isAuditDayDir(name)) continue;
|
|
@@ -16635,7 +18005,7 @@ var AuditPruneSweeper = class {
|
|
|
16635
18005
|
async archiveDay(bodiesPath, budget) {
|
|
16636
18006
|
let shards;
|
|
16637
18007
|
try {
|
|
16638
|
-
shards = (0,
|
|
18008
|
+
shards = (0, import_node_fs30.readdirSync)(bodiesPath).filter((file) => file.endsWith(".jsonl"));
|
|
16639
18009
|
} catch {
|
|
16640
18010
|
return 0;
|
|
16641
18011
|
}
|
|
@@ -16645,16 +18015,16 @@ var AuditPruneSweeper = class {
|
|
|
16645
18015
|
const source = (0, import_node_path29.join)(bodiesPath, shard);
|
|
16646
18016
|
const target = `${source}.gz`;
|
|
16647
18017
|
try {
|
|
16648
|
-
if ((0,
|
|
16649
|
-
(0,
|
|
18018
|
+
if ((0, import_node_fs30.existsSync)(target)) {
|
|
18019
|
+
(0, import_node_fs30.unlinkSync)(source);
|
|
16650
18020
|
continue;
|
|
16651
18021
|
}
|
|
16652
|
-
await (0, import_promises6.pipeline)((0,
|
|
16653
|
-
(0,
|
|
18022
|
+
await (0, import_promises6.pipeline)((0, import_node_fs30.createReadStream)(source), (0, import_node_zlib2.createGzip)(), (0, import_node_fs30.createWriteStream)(target));
|
|
18023
|
+
(0, import_node_fs30.unlinkSync)(source);
|
|
16654
18024
|
compressed += 1;
|
|
16655
18025
|
} catch (error) {
|
|
16656
18026
|
try {
|
|
16657
|
-
if ((0,
|
|
18027
|
+
if ((0, import_node_fs30.existsSync)(target)) (0, import_node_fs30.unlinkSync)(target);
|
|
16658
18028
|
} catch {
|
|
16659
18029
|
}
|
|
16660
18030
|
this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
|
|
@@ -16668,7 +18038,7 @@ var AuditPruneSweeper = class {
|
|
|
16668
18038
|
};
|
|
16669
18039
|
|
|
16670
18040
|
// src/usage/usageMigrate.ts
|
|
16671
|
-
var
|
|
18041
|
+
var import_node_fs31 = require("fs");
|
|
16672
18042
|
var import_promises7 = require("fs/promises");
|
|
16673
18043
|
var import_node_path30 = require("path");
|
|
16674
18044
|
var import_node_readline = require("readline");
|
|
@@ -16715,7 +18085,7 @@ async function migrateLegacyUsageEvents(opts) {
|
|
|
16715
18085
|
let skipped = 0;
|
|
16716
18086
|
try {
|
|
16717
18087
|
const reader = (0, import_node_readline.createInterface)({
|
|
16718
|
-
input: (0,
|
|
18088
|
+
input: (0, import_node_fs31.createReadStream)(eventsPath, { encoding: "utf8" }),
|
|
16719
18089
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
16720
18090
|
});
|
|
16721
18091
|
for await (const line of reader) {
|
|
@@ -16807,7 +18177,7 @@ async function closeAll(writers) {
|
|
|
16807
18177
|
// src/usage/UsagePruneSweeper.ts
|
|
16808
18178
|
var import_promises8 = require("fs/promises");
|
|
16809
18179
|
var import_node_path31 = require("path");
|
|
16810
|
-
var
|
|
18180
|
+
var DAY_MS4 = 24 * 60 * 6e4;
|
|
16811
18181
|
var SWEEP_INTERVAL_MS3 = 60 * 6e4;
|
|
16812
18182
|
var DEFAULT_USAGE_RETENTION_DAYS = 90;
|
|
16813
18183
|
var UsagePruneSweeper = class {
|
|
@@ -16864,7 +18234,7 @@ var UsagePruneSweeper = class {
|
|
|
16864
18234
|
this.sweeping = true;
|
|
16865
18235
|
try {
|
|
16866
18236
|
const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
|
|
16867
|
-
const cutoff = this.todayMidnight() - (retentionDays - 1) *
|
|
18237
|
+
const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
|
|
16868
18238
|
let removed = 0;
|
|
16869
18239
|
for (const entry of await listUsageDays(this.usageDir)) {
|
|
16870
18240
|
if (!entry.hasShard) continue;
|
|
@@ -16922,7 +18292,7 @@ var UsagePruneSweeper = class {
|
|
|
16922
18292
|
};
|
|
16923
18293
|
|
|
16924
18294
|
// src/audit/auditReader.ts
|
|
16925
|
-
var
|
|
18295
|
+
var import_node_fs32 = require("fs");
|
|
16926
18296
|
var import_node_path32 = require("path");
|
|
16927
18297
|
var DEFAULT_LIMIT = 200;
|
|
16928
18298
|
var MAX_LIMIT = 2e3;
|
|
@@ -16930,7 +18300,7 @@ var OVERSCAN = 256;
|
|
|
16930
18300
|
function daySources(auditDir) {
|
|
16931
18301
|
let names;
|
|
16932
18302
|
try {
|
|
16933
|
-
names = (0,
|
|
18303
|
+
names = (0, import_node_fs32.readdirSync)(auditDir);
|
|
16934
18304
|
} catch {
|
|
16935
18305
|
return [];
|
|
16936
18306
|
}
|
|
@@ -16940,7 +18310,7 @@ function daySources(auditDir) {
|
|
|
16940
18310
|
if (dateMs === null) continue;
|
|
16941
18311
|
if (AUDIT_DAY_DIR_RE.test(name)) {
|
|
16942
18312
|
const path2 = (0, import_node_path32.join)(auditDir, name, AUDIT_META_FILE);
|
|
16943
|
-
if ((0,
|
|
18313
|
+
if ((0, import_node_fs32.existsSync)(path2)) sources.push({ path: path2, dateMs });
|
|
16944
18314
|
} else if (AUDIT_FILE_RE.test(name)) {
|
|
16945
18315
|
sources.push({ path: (0, import_node_path32.join)(auditDir, name), dateMs });
|
|
16946
18316
|
}
|
|
@@ -16958,7 +18328,7 @@ function toMetaRecord(record) {
|
|
|
16958
18328
|
return { ...meta, hasBody: true };
|
|
16959
18329
|
}
|
|
16960
18330
|
function readAuditRecords(auditDir, query2 = {}) {
|
|
16961
|
-
if (!(0,
|
|
18331
|
+
if (!(0, import_node_fs32.existsSync)(auditDir)) return [];
|
|
16962
18332
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
16963
18333
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
16964
18334
|
const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
|
|
@@ -16986,7 +18356,7 @@ function readAuditRecords(auditDir, query2 = {}) {
|
|
|
16986
18356
|
}
|
|
16987
18357
|
|
|
16988
18358
|
// src/audit/AuditWriter.ts
|
|
16989
|
-
var
|
|
18359
|
+
var import_node_fs33 = require("fs");
|
|
16990
18360
|
var import_node_path33 = require("path");
|
|
16991
18361
|
var AuditWriter = class {
|
|
16992
18362
|
constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
|
|
@@ -17034,7 +18404,7 @@ var AuditWriter = class {
|
|
|
17034
18404
|
/** Create a directory once per process and remember it. */
|
|
17035
18405
|
ensureDir(path2) {
|
|
17036
18406
|
if (!this.ensuredDirs.has(path2)) {
|
|
17037
|
-
(0,
|
|
18407
|
+
(0, import_node_fs33.mkdirSync)(path2, { recursive: true });
|
|
17038
18408
|
this.ensuredDirs.add(path2);
|
|
17039
18409
|
}
|
|
17040
18410
|
return path2;
|
|
@@ -17044,8 +18414,8 @@ var AuditWriter = class {
|
|
|
17044
18414
|
const { requestBody: _req, responseBody: _res, ...meta } = record;
|
|
17045
18415
|
const file = (0, import_node_path33.join)(dayPath, AUDIT_META_FILE);
|
|
17046
18416
|
const line = JSON.stringify(meta) + "\n";
|
|
17047
|
-
const bytesBefore = (0,
|
|
17048
|
-
(0,
|
|
18417
|
+
const bytesBefore = (0, import_node_fs33.existsSync)(file) ? (0, import_node_fs33.statSync)(file).size : 0;
|
|
18418
|
+
(0, import_node_fs33.appendFileSync)(file, line, "utf8");
|
|
17049
18419
|
try {
|
|
17050
18420
|
updateAuditStatsAfterAppend(
|
|
17051
18421
|
file,
|
|
@@ -17077,7 +18447,7 @@ var AuditWriter = class {
|
|
|
17077
18447
|
const line = encodeBodyEntry(record, sessionKey, dayDir, this.bases);
|
|
17078
18448
|
if (line === null) return;
|
|
17079
18449
|
const bodiesPath = this.ensureDir((0, import_node_path33.join)(dayPath, AUDIT_BODIES_DIR));
|
|
17080
|
-
(0,
|
|
18450
|
+
(0, import_node_fs33.appendFileSync)((0, import_node_path33.join)(bodiesPath, auditBodyFileName(sessionKey)), line + "\n", "utf8");
|
|
17081
18451
|
} catch (error) {
|
|
17082
18452
|
this.bases.forget(sessionKey);
|
|
17083
18453
|
this.logger.warn("[AuditWriter] failed to append audit body shard", {
|
|
@@ -17089,10 +18459,10 @@ var AuditWriter = class {
|
|
|
17089
18459
|
};
|
|
17090
18460
|
|
|
17091
18461
|
// src/billing/BillingPublisher.ts
|
|
17092
|
-
var
|
|
18462
|
+
var import_node_fs34 = require("fs");
|
|
17093
18463
|
var import_node_crypto24 = require("crypto");
|
|
17094
18464
|
var import_node_path34 = require("path");
|
|
17095
|
-
var
|
|
18465
|
+
var import_upstreamFetch11 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
17096
18466
|
|
|
17097
18467
|
// src/billing/billingFiles.ts
|
|
17098
18468
|
var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
|
|
@@ -17115,7 +18485,7 @@ var BillingPublisher = class {
|
|
|
17115
18485
|
constructor(billingDir, logger, opts = {}) {
|
|
17116
18486
|
this.billingDir = billingDir;
|
|
17117
18487
|
this.logger = logger;
|
|
17118
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0,
|
|
18488
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch11.fetchUpstream)(url, init));
|
|
17119
18489
|
this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
|
|
17120
18490
|
this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
|
|
17121
18491
|
this.now = opts.now ?? Date.now;
|
|
@@ -17163,7 +18533,7 @@ var BillingPublisher = class {
|
|
|
17163
18533
|
appendNow(event) {
|
|
17164
18534
|
this.ensureDir();
|
|
17165
18535
|
const file = (0, import_node_path34.join)(this.billingDir, billingFileName(event.ts));
|
|
17166
|
-
(0,
|
|
18536
|
+
(0, import_node_fs34.appendFileSync)(file, JSON.stringify(event) + "\n", "utf8");
|
|
17167
18537
|
}
|
|
17168
18538
|
/**
|
|
17169
18539
|
* One best-effort delivery attempt for an event ALREADY in the ledger. POSTs the
|
|
@@ -17213,7 +18583,7 @@ var BillingPublisher = class {
|
|
|
17213
18583
|
try {
|
|
17214
18584
|
this.ensureDir();
|
|
17215
18585
|
const file = (0, import_node_path34.join)(this.billingDir, deliveredFileName(event.ts));
|
|
17216
|
-
(0,
|
|
18586
|
+
(0, import_node_fs34.appendFileSync)(file, JSON.stringify({ id: event.id, deliveredAt: this.now() }) + "\n", "utf8");
|
|
17217
18587
|
} catch (error) {
|
|
17218
18588
|
this.logger.warn("[BillingPublisher] failed to append delivery marker", {
|
|
17219
18589
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -17222,20 +18592,20 @@ var BillingPublisher = class {
|
|
|
17222
18592
|
}
|
|
17223
18593
|
ensureDir() {
|
|
17224
18594
|
if (this.dirEnsured) return;
|
|
17225
|
-
(0,
|
|
18595
|
+
(0, import_node_fs34.mkdirSync)(this.billingDir, { recursive: true });
|
|
17226
18596
|
this.dirEnsured = true;
|
|
17227
18597
|
}
|
|
17228
18598
|
};
|
|
17229
18599
|
|
|
17230
18600
|
// src/billing/billingReader.ts
|
|
17231
|
-
var
|
|
18601
|
+
var import_node_fs35 = require("fs");
|
|
17232
18602
|
var import_node_path35 = require("path");
|
|
17233
18603
|
function readBillingLedger(billingDir) {
|
|
17234
18604
|
const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
|
|
17235
|
-
if (!(0,
|
|
18605
|
+
if (!(0, import_node_fs35.existsSync)(billingDir)) return view;
|
|
17236
18606
|
let files;
|
|
17237
18607
|
try {
|
|
17238
|
-
files = (0,
|
|
18608
|
+
files = (0, import_node_fs35.readdirSync)(billingDir);
|
|
17239
18609
|
} catch {
|
|
17240
18610
|
return view;
|
|
17241
18611
|
}
|
|
@@ -17266,7 +18636,7 @@ function readBillingStatus(billingDir) {
|
|
|
17266
18636
|
function parseLines(dir, file) {
|
|
17267
18637
|
let raw;
|
|
17268
18638
|
try {
|
|
17269
|
-
raw = (0,
|
|
18639
|
+
raw = (0, import_node_fs35.readFileSync)((0, import_node_path35.join)(dir, file), "utf8");
|
|
17270
18640
|
} catch {
|
|
17271
18641
|
return [];
|
|
17272
18642
|
}
|
|
@@ -17365,7 +18735,7 @@ var BillingRetrySweeper = class {
|
|
|
17365
18735
|
// src/TokenRefreshScheduler.ts
|
|
17366
18736
|
var REFRESH_LEAD_MS2 = 5 * 6e4;
|
|
17367
18737
|
var SWEEP_INTERVAL_MS5 = 6e4;
|
|
17368
|
-
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
|
|
18738
|
+
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
|
|
17369
18739
|
var TokenRefreshScheduler = class {
|
|
17370
18740
|
constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
|
|
17371
18741
|
this.store = store;
|
|
@@ -17448,6 +18818,8 @@ var TokenRefreshScheduler = class {
|
|
|
17448
18818
|
return this.store.refreshCodexToken();
|
|
17449
18819
|
case "gemini":
|
|
17450
18820
|
return this.store.refreshGeminiToken();
|
|
18821
|
+
case "kimi":
|
|
18822
|
+
return this.store.refreshKimiToken();
|
|
17451
18823
|
}
|
|
17452
18824
|
}
|
|
17453
18825
|
};
|
|
@@ -17524,7 +18896,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
|
|
|
17524
18896
|
|
|
17525
18897
|
// src/webhook/WebhookDispatcher.ts
|
|
17526
18898
|
var import_node_crypto25 = require("crypto");
|
|
17527
|
-
var
|
|
18899
|
+
var import_upstreamFetch12 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
17528
18900
|
var WEBHOOK_MAX_ATTEMPTS = 3;
|
|
17529
18901
|
var WEBHOOK_QUEUE_MAX = 1e3;
|
|
17530
18902
|
var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
|
|
@@ -17544,7 +18916,7 @@ var WebhookDispatcher = class {
|
|
|
17544
18916
|
sleep;
|
|
17545
18917
|
now;
|
|
17546
18918
|
constructor(opts = {}) {
|
|
17547
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0,
|
|
18919
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch12.fetchUpstream)(url, init));
|
|
17548
18920
|
this.logger = opts.logger;
|
|
17549
18921
|
this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
|
|
17550
18922
|
this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
|
|
@@ -17630,8 +19002,8 @@ var WebhookDispatcher = class {
|
|
|
17630
19002
|
signal: AbortSignal.timeout(this.timeoutMs)
|
|
17631
19003
|
});
|
|
17632
19004
|
return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
|
|
17633
|
-
} catch (
|
|
17634
|
-
return { ok: false, error:
|
|
19005
|
+
} catch (err6) {
|
|
19006
|
+
return { ok: false, error: err6 instanceof Error ? err6.message : String(err6) };
|
|
17635
19007
|
}
|
|
17636
19008
|
}
|
|
17637
19009
|
/**
|
|
@@ -17750,7 +19122,7 @@ function installImageRuntimeBootstrapSession(initialGeneration) {
|
|
|
17750
19122
|
function resolveLoggingConfig(configured, configPath) {
|
|
17751
19123
|
const file = configured?.file ?? defaultDaemonLogPath(configPath);
|
|
17752
19124
|
try {
|
|
17753
|
-
(0,
|
|
19125
|
+
(0, import_node_fs36.mkdirSync)(configured?.file ? (0, import_node_path36.dirname)(configured.file) : defaultLogDir(configPath), {
|
|
17754
19126
|
recursive: true
|
|
17755
19127
|
});
|
|
17756
19128
|
} catch {
|
|
@@ -17768,12 +19140,12 @@ function buildDaemon(config, paths) {
|
|
|
17768
19140
|
setSecretBox(secretBox3);
|
|
17769
19141
|
setSecretBox2(secretBox3);
|
|
17770
19142
|
const decryptedConfig = decryptConfigSecrets(config, secretBox3);
|
|
17771
|
-
const accountAllowanceStore = new
|
|
19143
|
+
const accountAllowanceStore = new import_AccountAllowanceStore7.AccountAllowanceStore(
|
|
17772
19144
|
Date.now,
|
|
17773
19145
|
void 0,
|
|
17774
19146
|
new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
|
|
17775
19147
|
);
|
|
17776
|
-
(0,
|
|
19148
|
+
(0, import_AccountAllowanceStore7.setSharedAccountAllowanceStore)(accountAllowanceStore);
|
|
17777
19149
|
(0, import_AccountAllowanceScheduling5.getSharedAccountAllowanceScheduling)().configure(
|
|
17778
19150
|
(0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
|
|
17779
19151
|
);
|
|
@@ -17798,21 +19170,22 @@ function buildDaemon(config, paths) {
|
|
|
17798
19170
|
claudeAllowanceRefreshScheduler.configure(
|
|
17799
19171
|
(0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
|
|
17800
19172
|
);
|
|
17801
|
-
const subscriptionAccounts = new
|
|
17802
|
-
(0,
|
|
17803
|
-
const subscriptionRegistry = new
|
|
19173
|
+
const subscriptionAccounts = new import_subscriptions9.SubscriptionAccountService(credentialStore);
|
|
19174
|
+
(0, import_subscriptions9.setSubscriptionAccountService)(subscriptionAccounts);
|
|
19175
|
+
const subscriptionRegistry = new import_subscriptions9.SubscriptionProviderRegistry(
|
|
17804
19176
|
subscriptionAccounts,
|
|
17805
19177
|
credentialStore
|
|
17806
19178
|
);
|
|
17807
|
-
(0,
|
|
19179
|
+
(0, import_subscriptions9.setSubscriptionProviderRegistry)(subscriptionRegistry);
|
|
17808
19180
|
setServerProxyConfig(decryptedConfig.server?.proxy);
|
|
17809
|
-
(0,
|
|
19181
|
+
(0, import_upstreamFetch13.setUpstreamProxyResolver)(
|
|
17810
19182
|
createUpstreamProxyResolver({
|
|
17811
19183
|
getAccountProxy: (providerId, accountId) => credentialStore.getAccountProxy(providerId, accountId)
|
|
17812
19184
|
})
|
|
17813
19185
|
);
|
|
17814
19186
|
(0, import_gemini_code_assist_resolver.setGeminiCodeAssistResolver)((0, import_GeminiCodeAssistProjectResolver.getGeminiCodeAssistProjectResolver)());
|
|
17815
19187
|
const autoDisableStore = new AutoDisableStore();
|
|
19188
|
+
const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
|
|
17816
19189
|
const apiKeyPool = new import_ApiKeyPoolService.ApiKeyPoolService(
|
|
17817
19190
|
createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
|
|
17818
19191
|
resolveEnvKey,
|
|
@@ -17829,7 +19202,7 @@ function buildDaemon(config, paths) {
|
|
|
17829
19202
|
const pricingEngine = new import_usage2.PricingEngine(pricingStore, logger, {
|
|
17830
19203
|
// Catalog egress follows the same global/env proxy policy as every other
|
|
17831
19204
|
// daemon upstream call; no provider/account override applies here.
|
|
17832
|
-
fetchImpl: ((input, init) => (0,
|
|
19205
|
+
fetchImpl: ((input, init) => (0, import_upstreamFetch13.fetchUpstream)(String(input), init ?? {}))
|
|
17833
19206
|
});
|
|
17834
19207
|
const pricingRefreshScheduler = new PricingRefreshScheduler(
|
|
17835
19208
|
pricingEngine,
|
|
@@ -18093,6 +19466,11 @@ function buildDaemon(config, paths) {
|
|
|
18093
19466
|
// values themselves NEVER leave (masked via `maskProviderApiKey`).
|
|
18094
19467
|
apiKeyPool,
|
|
18095
19468
|
autoDisableStore,
|
|
19469
|
+
// BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
|
|
19470
|
+
// read-through cached same-key usage probe surfaced on the keys view. The
|
|
19471
|
+
// key plaintext is resolved + decrypted inside the service and never
|
|
19472
|
+
// crosses back out.
|
|
19473
|
+
providerKeyQuota: providerKeyQuotaService,
|
|
18096
19474
|
// Interactive OAuth login over admin HTTP (app-parity child 4, design
|
|
18097
19475
|
// D1/D2-a). The in-memory pending-session store (NEVER serialized), the
|
|
18098
19476
|
// injected token-exchange fetch (global `fetch` here; mocked in tests), and a
|
|
@@ -18109,7 +19487,7 @@ function buildDaemon(config, paths) {
|
|
|
18109
19487
|
// — `server.proxy.byProvider[...]` was silently skipped — and the call was
|
|
18110
19488
|
// excluded from the upstream trace, so a failing login left no evidence.
|
|
18111
19489
|
// `redactBodies` keeps the code/verifier + minted token out of that trace.
|
|
18112
|
-
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0,
|
|
19490
|
+
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0, import_upstreamFetch13.fetchUpstream)(url, init, { providerId, redactBodies: true }),
|
|
18113
19491
|
subscriptionAccountAppender: credentialStore,
|
|
18114
19492
|
// Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
|
|
18115
19493
|
// + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
|
|
@@ -18117,6 +19495,10 @@ function buildDaemon(config, paths) {
|
|
|
18117
19495
|
// can inject a mock so no real port is bound.
|
|
18118
19496
|
codexSessions: new CodexOAuthSessionStore(),
|
|
18119
19497
|
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
|
|
19498
|
+
// Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
|
|
19499
|
+
// paste; the app shows the verification URL + user code and polls the
|
|
19500
|
+
// token-free status). Token captured + persisted daemon-side.
|
|
19501
|
+
kimiSessions: new CodexOAuthSessionStore(),
|
|
18120
19502
|
// Migration pack (app-parity child 6, design D2/D3) — the concrete credential
|
|
18121
19503
|
// store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
|
|
18122
19504
|
// the multi-account append (`appendProviderAccount`, import re-encrypts at-
|
|
@@ -18175,7 +19557,7 @@ function buildDaemon(config, paths) {
|
|
|
18175
19557
|
});
|
|
18176
19558
|
const webhookDispatcher = new WebhookDispatcher({
|
|
18177
19559
|
logger,
|
|
18178
|
-
fetchImpl: (url, init) => (0,
|
|
19560
|
+
fetchImpl: (url, init) => (0, import_upstreamFetch13.fetchUpstream)(url, init)
|
|
18179
19561
|
});
|
|
18180
19562
|
setWebhookRuntime(webhookDispatcher, (0, import_SubscriptionAccountHealth4.getSharedAccountHealth)());
|
|
18181
19563
|
const auditWriter = new AuditWriter(auditDir, logger);
|
|
@@ -18252,8 +19634,8 @@ function buildDaemon(config, paths) {
|
|
|
18252
19634
|
}
|
|
18253
19635
|
function isTokensStoreReadable(tokensPath) {
|
|
18254
19636
|
try {
|
|
18255
|
-
if (!(0,
|
|
18256
|
-
(0,
|
|
19637
|
+
if (!(0, import_node_fs36.existsSync)(tokensPath)) return true;
|
|
19638
|
+
(0, import_node_fs36.accessSync)(tokensPath, import_node_fs36.constants.R_OK);
|
|
18257
19639
|
return true;
|
|
18258
19640
|
} catch {
|
|
18259
19641
|
return false;
|
|
@@ -18448,9 +19830,9 @@ async function runSearchDoctor(live, env = process.env, source = {}) {
|
|
|
18448
19830
|
];
|
|
18449
19831
|
const declarations = source.runtime?.listProviders() ?? contributions;
|
|
18450
19832
|
console.info("omnicross doctor search \u2014 builtin search contributions (offline, no network)");
|
|
18451
|
-
const modes = source.config?.modes ??
|
|
19833
|
+
const modes = source.config?.modes ?? import_search3.DEFAULT_SEARCH_FRONTEND_MODES;
|
|
18452
19834
|
console.info(
|
|
18453
|
-
` [i] frontend modes: ${
|
|
19835
|
+
` [i] frontend modes: ${import_search3.SEARCH_FRONTEND_NAMES.map((name) => `${name}=${modes[name]}`).join(", ")}`
|
|
18454
19836
|
);
|
|
18455
19837
|
console.info(
|
|
18456
19838
|
" [i] codex mode applies immediately; responses/anthropic modes, provider config, egress allowlist and policy apply on daemon restart"
|
|
@@ -18489,11 +19871,11 @@ async function runLiveProbe(url, key, fetchImpl = fetch) {
|
|
|
18489
19871
|
status: res.status,
|
|
18490
19872
|
estimateHeader: res.headers.get("x-omnicross-count-estimate")
|
|
18491
19873
|
};
|
|
18492
|
-
} catch (
|
|
19874
|
+
} catch (err6) {
|
|
18493
19875
|
return {
|
|
18494
19876
|
status: null,
|
|
18495
19877
|
estimateHeader: null,
|
|
18496
|
-
error:
|
|
19878
|
+
error: err6 instanceof Error ? err6.message : String(err6)
|
|
18497
19879
|
};
|
|
18498
19880
|
}
|
|
18499
19881
|
}
|
|
@@ -18581,7 +19963,7 @@ async function runDoctor(argv, fetchImpl = fetch) {
|
|
|
18581
19963
|
}
|
|
18582
19964
|
|
|
18583
19965
|
// src/commands/import-ccr.ts
|
|
18584
|
-
var
|
|
19966
|
+
var import_node_fs37 = require("fs");
|
|
18585
19967
|
var import_node_util3 = require("util");
|
|
18586
19968
|
|
|
18587
19969
|
// src/ccr-import.ts
|
|
@@ -18676,7 +20058,7 @@ async function runImportCcr(argv) {
|
|
|
18676
20058
|
const outPath = values.out ?? "omnicross.config.json";
|
|
18677
20059
|
let raw;
|
|
18678
20060
|
try {
|
|
18679
|
-
raw = JSON.parse((0,
|
|
20061
|
+
raw = JSON.parse((0, import_node_fs37.readFileSync)(ccrPath, "utf8"));
|
|
18680
20062
|
} catch {
|
|
18681
20063
|
throw new Error(`import-ccr: cannot read or parse '${ccrPath}'`);
|
|
18682
20064
|
}
|
|
@@ -18814,7 +20196,7 @@ async function keysRevoke(db, id) {
|
|
|
18814
20196
|
// src/commands/launch.ts
|
|
18815
20197
|
var import_node_child_process2 = require("child_process");
|
|
18816
20198
|
var import_node_crypto26 = require("crypto");
|
|
18817
|
-
var
|
|
20199
|
+
var import_node_fs38 = require("fs");
|
|
18818
20200
|
var import_node_path38 = require("path");
|
|
18819
20201
|
var import_node_util6 = require("util");
|
|
18820
20202
|
var import_cli_launcher3 = require("@omnicross/cli-launcher");
|
|
@@ -18859,7 +20241,7 @@ function resolveInPathDefault(candidate) {
|
|
|
18859
20241
|
const segments = (process.env["PATH"] ?? "").split(import_node_path38.delimiter).filter(Boolean);
|
|
18860
20242
|
for (const seg of segments) {
|
|
18861
20243
|
const full = (0, import_node_path38.join)(seg, candidate);
|
|
18862
|
-
if ((0,
|
|
20244
|
+
if ((0, import_node_fs38.existsSync)(full)) return full;
|
|
18863
20245
|
}
|
|
18864
20246
|
return null;
|
|
18865
20247
|
}
|
|
@@ -18900,9 +20282,9 @@ async function runLaunch(argv, deps) {
|
|
|
18900
20282
|
await daemon.llmConfig.ready();
|
|
18901
20283
|
await daemon.migrateUsageStore();
|
|
18902
20284
|
await daemon.providerProxy.start();
|
|
18903
|
-
} catch (
|
|
20285
|
+
} catch (err6) {
|
|
18904
20286
|
await shutdownLaunchDaemon(daemon);
|
|
18905
|
-
throw
|
|
20287
|
+
throw err6;
|
|
18906
20288
|
}
|
|
18907
20289
|
let launch;
|
|
18908
20290
|
try {
|
|
@@ -18910,9 +20292,9 @@ async function runLaunch(argv, deps) {
|
|
|
18910
20292
|
providerId: values.provider,
|
|
18911
20293
|
model: values.model
|
|
18912
20294
|
});
|
|
18913
|
-
} catch (
|
|
20295
|
+
} catch (err6) {
|
|
18914
20296
|
await shutdownLaunchDaemon(daemon);
|
|
18915
|
-
throw
|
|
20297
|
+
throw err6;
|
|
18916
20298
|
}
|
|
18917
20299
|
try {
|
|
18918
20300
|
const plan = buildCliSpawnPlan({
|
|
@@ -19017,9 +20399,9 @@ function spawnCliInherit(plan) {
|
|
|
19017
20399
|
process.removeListener("SIGINT", onSignal);
|
|
19018
20400
|
process.removeListener("SIGTERM", onSignal);
|
|
19019
20401
|
};
|
|
19020
|
-
child.on("error", (
|
|
20402
|
+
child.on("error", (err6) => {
|
|
19021
20403
|
detach();
|
|
19022
|
-
if (
|
|
20404
|
+
if (err6.code === "ENOENT") {
|
|
19023
20405
|
reject(
|
|
19024
20406
|
new Error(
|
|
19025
20407
|
`launch: "${plan.command}" not found on PATH \u2014 install the CLI first.`
|
|
@@ -19027,7 +20409,7 @@ function spawnCliInherit(plan) {
|
|
|
19027
20409
|
);
|
|
19028
20410
|
return;
|
|
19029
20411
|
}
|
|
19030
|
-
reject(
|
|
20412
|
+
reject(err6);
|
|
19031
20413
|
});
|
|
19032
20414
|
child.on("exit", (code, signal) => {
|
|
19033
20415
|
detach();
|
|
@@ -19040,9 +20422,9 @@ function spawnCliInherit(plan) {
|
|
|
19040
20422
|
var import_node_child_process3 = require("child_process");
|
|
19041
20423
|
var import_node_readline2 = require("readline");
|
|
19042
20424
|
var import_node_util7 = require("util");
|
|
19043
|
-
var
|
|
19044
|
-
var
|
|
19045
|
-
var PROVIDERS2 = ["claude", "codex", "gemini"];
|
|
20425
|
+
var import_upstreamFetch14 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
20426
|
+
var import_subscriptions10 = require("@omnicross/subscriptions");
|
|
20427
|
+
var PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
|
|
19046
20428
|
async function runLogin(argv, deps) {
|
|
19047
20429
|
const { values, positionals } = (0, import_node_util7.parseArgs)({
|
|
19048
20430
|
args: argv,
|
|
@@ -19068,14 +20450,16 @@ async function runLogin(argv, deps) {
|
|
|
19068
20450
|
openBrowser: deps?.openBrowser ?? openBrowser,
|
|
19069
20451
|
promptPaste: deps?.promptPaste ?? promptPaste,
|
|
19070
20452
|
awaitLoopback: deps?.awaitLoopback ?? ((state) => awaitLoopbackCode(state)),
|
|
20453
|
+
awaitKimiDevice: deps?.awaitKimiDevice ?? ((fetchImpl) => runKimiDeviceFlow(fetchImpl, resolvedOpenBrowser)),
|
|
19071
20454
|
tokensFetch: deps?.tokensFetch
|
|
19072
20455
|
};
|
|
20456
|
+
const resolvedOpenBrowser = resolved.openBrowser;
|
|
19073
20457
|
const box = resolveSecretBox(values["master-key-file"]);
|
|
19074
20458
|
setSecretBox(box);
|
|
19075
|
-
(0,
|
|
20459
|
+
(0, import_upstreamFetch14.setUpstreamProxyResolver)(createUpstreamProxyResolver());
|
|
19076
20460
|
try {
|
|
19077
20461
|
const tokensPath = defaultTokensPath(values.config);
|
|
19078
|
-
const exchangeFetch = resolved.tokensFetch ?? ((url, init) => (0,
|
|
20462
|
+
const exchangeFetch = resolved.tokensFetch ?? ((url, init) => (0, import_upstreamFetch14.fetchUpstream)(url, init, { providerId: provider, redactBodies: true }));
|
|
19079
20463
|
const store = new JsonSubscriptionCredentialStore(tokensPath, box, exchangeFetch);
|
|
19080
20464
|
const expiresAt = await runProviderLogin(
|
|
19081
20465
|
provider,
|
|
@@ -19088,19 +20472,20 @@ async function runLogin(argv, deps) {
|
|
|
19088
20472
|
console.info(` token: [stored, encrypted] expiresAt: ${expiresAt ?? "n/a"}`);
|
|
19089
20473
|
} finally {
|
|
19090
20474
|
setSecretBox(null);
|
|
19091
|
-
(0,
|
|
20475
|
+
(0, import_upstreamFetch14.setUpstreamProxyResolver)(null);
|
|
19092
20476
|
}
|
|
19093
20477
|
}
|
|
19094
20478
|
async function runProviderLogin(provider, store, deps, exchangeFetch, label) {
|
|
19095
20479
|
if (provider === "codex") return loginCodex(store, deps, exchangeFetch, label);
|
|
19096
20480
|
if (provider === "claude") return loginClaude(store, deps, exchangeFetch, label);
|
|
20481
|
+
if (provider === "kimi") return loginKimi(store, deps, exchangeFetch, label);
|
|
19097
20482
|
return loginGemini(store, deps, exchangeFetch, label);
|
|
19098
20483
|
}
|
|
19099
20484
|
async function loginCodex(store, deps, exchangeFetch, label) {
|
|
19100
|
-
const { authUrl, codeVerifier, state } =
|
|
20485
|
+
const { authUrl, codeVerifier, state } = import_subscriptions10.codexOAuth.generateAuthParams();
|
|
19101
20486
|
await presentUrl(authUrl, deps);
|
|
19102
20487
|
const code = await deps.awaitLoopback(state);
|
|
19103
|
-
const result = await
|
|
20488
|
+
const result = await import_subscriptions10.codexOAuth.exchangeCodeForTokens(
|
|
19104
20489
|
{ authorizationCode: code, codeVerifier, state },
|
|
19105
20490
|
exchangeFetch
|
|
19106
20491
|
);
|
|
@@ -19119,7 +20504,7 @@ async function loginCodex(store, deps, exchangeFetch, label) {
|
|
|
19119
20504
|
return expiresAt;
|
|
19120
20505
|
}
|
|
19121
20506
|
async function loginClaude(store, deps, exchangeFetch, label) {
|
|
19122
|
-
const { authUrl, codeVerifier, state } =
|
|
20507
|
+
const { authUrl, codeVerifier, state } = import_subscriptions10.claudeOAuth.generateAuthParams();
|
|
19123
20508
|
await presentUrl(authUrl, deps);
|
|
19124
20509
|
const pasted = (await deps.promptPaste("Paste the authorization code (code#state): ")).trim();
|
|
19125
20510
|
const [code, pastedState] = pasted.split("#");
|
|
@@ -19127,7 +20512,7 @@ async function loginClaude(store, deps, exchangeFetch, label) {
|
|
|
19127
20512
|
if (pastedState && pastedState !== state) {
|
|
19128
20513
|
throw new Error("login: pasted state did not match (possible CSRF) \u2014 aborting");
|
|
19129
20514
|
}
|
|
19130
|
-
const result = await
|
|
20515
|
+
const result = await import_subscriptions10.claudeOAuth.exchangeCodeForTokens(
|
|
19131
20516
|
{ authorizationCode: code, codeVerifier, state },
|
|
19132
20517
|
exchangeFetch
|
|
19133
20518
|
);
|
|
@@ -19146,11 +20531,11 @@ async function loginClaude(store, deps, exchangeFetch, label) {
|
|
|
19146
20531
|
return expiresAt;
|
|
19147
20532
|
}
|
|
19148
20533
|
async function loginGemini(store, deps, exchangeFetch, label) {
|
|
19149
|
-
const { authUrl, codeVerifier } =
|
|
20534
|
+
const { authUrl, codeVerifier } = import_subscriptions10.geminiOAuth.generateAuthParams();
|
|
19150
20535
|
await presentUrl(authUrl, deps);
|
|
19151
20536
|
const code = (await deps.promptPaste("Paste the authorization code: ")).trim();
|
|
19152
20537
|
if (!code) throw new Error("login: no authorization code was pasted");
|
|
19153
|
-
const result = await
|
|
20538
|
+
const result = await import_subscriptions10.geminiOAuth.exchangeCodeForTokens(code, codeVerifier, exchangeFetch);
|
|
19154
20539
|
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
19155
20540
|
const block = {
|
|
19156
20541
|
authMethod: "oauth",
|
|
@@ -19164,6 +20549,45 @@ async function loginGemini(store, deps, exchangeFetch, label) {
|
|
|
19164
20549
|
logMasked("gemini", result.accessToken);
|
|
19165
20550
|
return expiresAt;
|
|
19166
20551
|
}
|
|
20552
|
+
async function runKimiDeviceFlow(exchangeFetch, openBrowserFn) {
|
|
20553
|
+
const deviceId = import_subscriptions10.kimiOAuth.generateKimiDeviceId();
|
|
20554
|
+
const fingerprint = import_subscriptions10.kimiOAuth.kimiFingerprintHeaders(deviceId);
|
|
20555
|
+
const authorization = await import_subscriptions10.kimiOAuth.requestDeviceAuthorization(exchangeFetch, fingerprint);
|
|
20556
|
+
const url = authorization.verificationUriComplete ?? authorization.verificationUri;
|
|
20557
|
+
console.info("Open this URL in your browser and approve the request:");
|
|
20558
|
+
console.info(` ${url}`);
|
|
20559
|
+
if (!authorization.verificationUriComplete) {
|
|
20560
|
+
console.info(` Then enter this code: ${authorization.userCode}`);
|
|
20561
|
+
}
|
|
20562
|
+
await openBrowserFn(url).catch(() => false);
|
|
20563
|
+
const result = await import_subscriptions10.kimiOAuth.awaitDeviceToken(authorization, exchangeFetch, {
|
|
20564
|
+
fingerprint,
|
|
20565
|
+
onPending: () => process.stdout.write(".")
|
|
20566
|
+
});
|
|
20567
|
+
console.info("");
|
|
20568
|
+
return {
|
|
20569
|
+
...result,
|
|
20570
|
+
accountId: import_subscriptions10.kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
|
|
20571
|
+
deviceId
|
|
20572
|
+
};
|
|
20573
|
+
}
|
|
20574
|
+
async function loginKimi(store, deps, exchangeFetch, label) {
|
|
20575
|
+
const result = await deps.awaitKimiDevice(exchangeFetch);
|
|
20576
|
+
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
20577
|
+
const block = {
|
|
20578
|
+
authMethod: "oauth",
|
|
20579
|
+
status: "authorized",
|
|
20580
|
+
accessToken: result.accessToken,
|
|
20581
|
+
refreshToken: result.refreshToken,
|
|
20582
|
+
expiresAt,
|
|
20583
|
+
...result.accountId ? { accountId: result.accountId } : {},
|
|
20584
|
+
deviceId: result.deviceId,
|
|
20585
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
20586
|
+
};
|
|
20587
|
+
await store.appendProviderAccount("kimi", block, label);
|
|
20588
|
+
logMasked("kimi", result.accessToken);
|
|
20589
|
+
return expiresAt;
|
|
20590
|
+
}
|
|
19167
20591
|
function isLoginProvider(value) {
|
|
19168
20592
|
return PROVIDERS2.includes(value);
|
|
19169
20593
|
}
|
|
@@ -19361,7 +20785,7 @@ function providersRmKey(configPath, providerId, keyId) {
|
|
|
19361
20785
|
}
|
|
19362
20786
|
|
|
19363
20787
|
// src/commands/secrets.ts
|
|
19364
|
-
var
|
|
20788
|
+
var import_node_fs39 = require("fs");
|
|
19365
20789
|
var import_node_util9 = require("util");
|
|
19366
20790
|
async function runSecrets(argv) {
|
|
19367
20791
|
const { values, positionals } = (0, import_node_util9.parseArgs)({
|
|
@@ -19434,12 +20858,12 @@ function secretsStatus(args) {
|
|
|
19434
20858
|
reportField("admin.token", cfg.admin.token);
|
|
19435
20859
|
}
|
|
19436
20860
|
const tokensPath = defaultTokensPath(args.config);
|
|
19437
|
-
if ((0,
|
|
20861
|
+
if ((0, import_node_fs39.existsSync)(tokensPath)) {
|
|
19438
20862
|
console.info(`Secret status for ${tokensPath}:`);
|
|
19439
20863
|
reportTokenFields(tokensPath);
|
|
19440
20864
|
}
|
|
19441
20865
|
const integrationsPath = defaultIntegrationsPath(args.config);
|
|
19442
|
-
if ((0,
|
|
20866
|
+
if ((0, import_node_fs39.existsSync)(integrationsPath)) {
|
|
19443
20867
|
const state = readRawJson(integrationsPath);
|
|
19444
20868
|
const key = state.gatewayKey;
|
|
19445
20869
|
if (key && typeof key === "object" && !Array.isArray(key)) {
|
|
@@ -19493,8 +20917,8 @@ async function secretsRotate(args) {
|
|
|
19493
20917
|
const integrationsPath = defaultIntegrationsPath(args.config);
|
|
19494
20918
|
try {
|
|
19495
20919
|
cfg = loadConfig(args.config);
|
|
19496
|
-
if ((0,
|
|
19497
|
-
if ((0,
|
|
20920
|
+
if ((0, import_node_fs39.existsSync)(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
|
|
20921
|
+
if ((0, import_node_fs39.existsSync)(integrationsPath)) {
|
|
19498
20922
|
integrationsPlain = new IntegrationStateStore(integrationsPath, oldBox).load();
|
|
19499
20923
|
}
|
|
19500
20924
|
} finally {
|
|
@@ -19529,20 +20953,20 @@ function secretsDecrypt(args) {
|
|
|
19529
20953
|
let tokensPlain = null;
|
|
19530
20954
|
try {
|
|
19531
20955
|
cfg = loadConfig(args.config);
|
|
19532
|
-
if ((0,
|
|
20956
|
+
if ((0, import_node_fs39.existsSync)(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
|
|
19533
20957
|
} finally {
|
|
19534
20958
|
setSecretBox(null);
|
|
19535
20959
|
}
|
|
19536
20960
|
saveConfig(args.config, cfg);
|
|
19537
20961
|
if (tokensPlain) {
|
|
19538
|
-
(
|
|
20962
|
+
atomicReplaceUtf8(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n");
|
|
19539
20963
|
}
|
|
19540
20964
|
console.info(`Decrypted secrets to plaintext in ${args.config}` + tokensSuffix(args.config));
|
|
19541
20965
|
}
|
|
19542
20966
|
function readRawConfig(path2) {
|
|
19543
20967
|
let parsed;
|
|
19544
20968
|
try {
|
|
19545
|
-
parsed = JSON.parse((0,
|
|
20969
|
+
parsed = JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
|
|
19546
20970
|
} catch {
|
|
19547
20971
|
throw new Error(`secrets: cannot read or parse '${path2}'`);
|
|
19548
20972
|
}
|
|
@@ -19550,7 +20974,7 @@ function readRawConfig(path2) {
|
|
|
19550
20974
|
}
|
|
19551
20975
|
function readRawJson(path2) {
|
|
19552
20976
|
try {
|
|
19553
|
-
const parsed = JSON.parse((0,
|
|
20977
|
+
const parsed = JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
|
|
19554
20978
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
19555
20979
|
return parsed;
|
|
19556
20980
|
}
|
|
@@ -19560,13 +20984,13 @@ function readRawJson(path2) {
|
|
|
19560
20984
|
}
|
|
19561
20985
|
function encryptTokensFileInPlace(configPath, box) {
|
|
19562
20986
|
const tokensPath = defaultTokensPath(configPath);
|
|
19563
|
-
if (!(0,
|
|
20987
|
+
if (!(0, import_node_fs39.existsSync)(tokensPath)) return;
|
|
19564
20988
|
const plain = decryptTokensFile(tokensPath, box);
|
|
19565
20989
|
writeTokensEncrypted(tokensPath, plain, box);
|
|
19566
20990
|
}
|
|
19567
20991
|
function rewriteIntegrationState(configPath, readBox, writeBox) {
|
|
19568
20992
|
const path2 = defaultIntegrationsPath(configPath);
|
|
19569
|
-
if (!(0,
|
|
20993
|
+
if (!(0, import_node_fs39.existsSync)(path2)) return;
|
|
19570
20994
|
const state = new IntegrationStateStore(path2, readBox).load();
|
|
19571
20995
|
new IntegrationStateStore(path2, writeBox).save(state);
|
|
19572
20996
|
}
|
|
@@ -19579,7 +21003,7 @@ function writeTokensEncrypted(tokensPath, plain, box) {
|
|
|
19579
21003
|
{ updatedAt: "", ...plain },
|
|
19580
21004
|
box
|
|
19581
21005
|
);
|
|
19582
|
-
(
|
|
21006
|
+
atomicReplaceUtf8(tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
|
|
19583
21007
|
}
|
|
19584
21008
|
var TOKEN_FIELDS2 = {
|
|
19585
21009
|
claude: ["accessToken", "refreshToken"],
|
|
@@ -19602,7 +21026,7 @@ function walkTokens(raw, fn) {
|
|
|
19602
21026
|
return next;
|
|
19603
21027
|
}
|
|
19604
21028
|
function tokensSuffix(configPath) {
|
|
19605
|
-
return (0,
|
|
21029
|
+
return (0, import_node_fs39.existsSync)(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
|
|
19606
21030
|
}
|
|
19607
21031
|
|
|
19608
21032
|
// src/commands/start.ts
|
|
@@ -19869,7 +21293,7 @@ async function main() {
|
|
|
19869
21293
|
process.exitCode = 1;
|
|
19870
21294
|
}
|
|
19871
21295
|
}
|
|
19872
|
-
main().catch((
|
|
19873
|
-
console.error(
|
|
21296
|
+
main().catch((err6) => {
|
|
21297
|
+
console.error(err6 instanceof Error ? err6.message : String(err6));
|
|
19874
21298
|
process.exitCode = 1;
|
|
19875
21299
|
});
|