@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/index.cjs
CHANGED
|
@@ -58,7 +58,7 @@ __export(src_exports, {
|
|
|
58
58
|
module.exports = __toCommonJS(src_exports);
|
|
59
59
|
|
|
60
60
|
// src/bootstrap.ts
|
|
61
|
-
var
|
|
61
|
+
var import_node_fs35 = require("fs");
|
|
62
62
|
var import_node_path35 = require("path");
|
|
63
63
|
var import_audit_types = require("@omnicross/contracts/audit-types");
|
|
64
64
|
var import_billing_types = require("@omnicross/contracts/billing-types");
|
|
@@ -68,16 +68,16 @@ var import_ApiKeyPoolService = require("@omnicross/core/completion/ApiKeyPoolSer
|
|
|
68
68
|
var import_outbound_api10 = require("@omnicross/core/outbound-api");
|
|
69
69
|
var import_subscriptionRegistryPort = require("@omnicross/core/outbound-api/subscriptionRegistryPort");
|
|
70
70
|
var import_SubscriptionAccountHealth4 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
|
|
71
|
-
var
|
|
71
|
+
var import_AccountAllowanceStore7 = require("@omnicross/core/pipeline/AccountAllowanceStore");
|
|
72
72
|
var import_AccountAllowanceScheduling5 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
|
|
73
|
-
var
|
|
73
|
+
var import_upstreamFetch13 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
74
74
|
var import_SubscriptionIdentityStore3 = require("@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore");
|
|
75
75
|
var import_gemini_code_assist_resolver = require("@omnicross/core/ports/gemini-code-assist-resolver");
|
|
76
76
|
var import_provider_proxy4 = require("@omnicross/core/provider-proxy");
|
|
77
77
|
var import_cli_launcher2 = require("@omnicross/cli-launcher");
|
|
78
78
|
var import_outbound_api11 = require("@omnicross/core/outbound-api");
|
|
79
79
|
var import_usage2 = require("@omnicross/core/usage");
|
|
80
|
-
var
|
|
80
|
+
var import_subscriptions9 = require("@omnicross/subscriptions");
|
|
81
81
|
|
|
82
82
|
// src/admin/accountsCodexOAuth.ts
|
|
83
83
|
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
@@ -184,8 +184,83 @@ function handleCodexOAuthStatus(sessionId, deps) {
|
|
|
184
184
|
return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
// src/admin/accountsKimiOAuth.ts
|
|
188
|
+
var import_subscriptions2 = require("@omnicross/subscriptions");
|
|
189
|
+
function err2(status, message) {
|
|
190
|
+
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
191
|
+
}
|
|
192
|
+
var DEFAULT_KIMI_OAUTH_TTL_MS = 15 * 6e4;
|
|
193
|
+
async function handleKimiOAuthStart(deps) {
|
|
194
|
+
if (deps.kimiSessions.isBusy()) {
|
|
195
|
+
return err2(409, "a kimi sign-in is already in progress \u2014 finish it in the browser or cancel it");
|
|
196
|
+
}
|
|
197
|
+
const fetchImpl = deps.oauthExchangeFetch("kimi");
|
|
198
|
+
const deviceId = import_subscriptions2.kimiOAuth.generateKimiDeviceId();
|
|
199
|
+
const fingerprint = import_subscriptions2.kimiOAuth.kimiFingerprintHeaders(deviceId);
|
|
200
|
+
let authorization;
|
|
201
|
+
try {
|
|
202
|
+
authorization = await import_subscriptions2.kimiOAuth.requestDeviceAuthorization(fetchImpl, fingerprint);
|
|
203
|
+
} catch (e) {
|
|
204
|
+
const reason = e instanceof Error ? e.message : "device authorization failed";
|
|
205
|
+
return err2(502, `kimi device authorization failed: ${reason}`);
|
|
206
|
+
}
|
|
207
|
+
const { sessionId, signal } = deps.kimiSessions.begin();
|
|
208
|
+
void runKimiDevicePoll(sessionId, authorization.deviceCode, deviceId, fingerprint, signal, deps).catch(() => deps.kimiSessions.settle(sessionId, "error", "kimi sign-in failed"));
|
|
209
|
+
return {
|
|
210
|
+
status: 200,
|
|
211
|
+
body: {
|
|
212
|
+
authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
|
|
213
|
+
userCode: authorization.userCode,
|
|
214
|
+
sessionId
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
async function runKimiDevicePoll(sessionId, deviceCode, deviceId, fingerprint, signal, deps) {
|
|
219
|
+
const fetchImpl = deps.oauthExchangeFetch("kimi");
|
|
220
|
+
const result = await import_subscriptions2.kimiOAuth.awaitDeviceToken(
|
|
221
|
+
{ userCode: "", deviceCode, verificationUri: "" },
|
|
222
|
+
fetchImpl,
|
|
223
|
+
{
|
|
224
|
+
fingerprint,
|
|
225
|
+
deadlineMs: DEFAULT_KIMI_OAUTH_TTL_MS,
|
|
226
|
+
sleep: (ms) => new Promise((resolve10, reject) => {
|
|
227
|
+
const onAbort = () => {
|
|
228
|
+
clearTimeout(timer);
|
|
229
|
+
reject(new Error("login: cancelled"));
|
|
230
|
+
};
|
|
231
|
+
const timer = setTimeout(() => {
|
|
232
|
+
signal.removeEventListener("abort", onAbort);
|
|
233
|
+
resolve10();
|
|
234
|
+
}, ms);
|
|
235
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
236
|
+
})
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
const block = {
|
|
240
|
+
authMethod: "oauth",
|
|
241
|
+
status: "authorized",
|
|
242
|
+
accessToken: result.accessToken,
|
|
243
|
+
refreshToken: result.refreshToken,
|
|
244
|
+
expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
|
|
245
|
+
accountId: import_subscriptions2.kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
|
|
246
|
+
deviceId,
|
|
247
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
248
|
+
};
|
|
249
|
+
await deps.subscriptionAccountAppender.appendProviderAccount("kimi", block);
|
|
250
|
+
deps.kimiSessions.settle(sessionId, "done");
|
|
251
|
+
}
|
|
252
|
+
function handleKimiOAuthCancel(sessionId, deps) {
|
|
253
|
+
if (!deps.kimiSessions.cancel(sessionId)) return err2(404, "unknown or expired kimi sign-in session");
|
|
254
|
+
return { status: 200, body: { ok: true } };
|
|
255
|
+
}
|
|
256
|
+
function handleKimiOAuthStatus(sessionId, deps) {
|
|
257
|
+
const s = deps.kimiSessions.get(sessionId);
|
|
258
|
+
if (!s) return err2(404, "unknown or expired kimi sign-in session");
|
|
259
|
+
return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
|
|
260
|
+
}
|
|
261
|
+
|
|
187
262
|
// src/allowance/AccountAllowanceService.ts
|
|
188
|
-
var
|
|
263
|
+
var import_AccountAllowanceStore5 = require("@omnicross/core/pipeline/AccountAllowanceStore");
|
|
189
264
|
var import_AccountAllowanceScheduling = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
|
|
190
265
|
|
|
191
266
|
// src/allowance/ClaudeAllowanceCollector.ts
|
|
@@ -212,13 +287,11 @@ function secondsUntil(instant, now) {
|
|
|
212
287
|
function windowFromPayload(id, payload, now) {
|
|
213
288
|
const usedPercent = finitePercent(payload?.utilization);
|
|
214
289
|
const resetsAt = isoInstant(payload?.resets_at);
|
|
215
|
-
const isSonnet = id === "seven-day-sonnet";
|
|
216
290
|
const isFiveHour = id === "five-hour";
|
|
217
291
|
return {
|
|
218
292
|
id,
|
|
219
|
-
label: isFiveHour ? "5 hours" :
|
|
220
|
-
scope:
|
|
221
|
-
modelFamily: isSonnet ? "sonnet" : void 0,
|
|
293
|
+
label: isFiveHour ? "5 hours" : "7 days",
|
|
294
|
+
scope: "all",
|
|
222
295
|
usedPercent,
|
|
223
296
|
windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
|
|
224
297
|
resetsAt,
|
|
@@ -226,6 +299,44 @@ function windowFromPayload(id, payload, now) {
|
|
|
226
299
|
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
227
300
|
};
|
|
228
301
|
}
|
|
302
|
+
function limitEntryWindow(entries, kind) {
|
|
303
|
+
const entry = entries.find((candidate) => candidate.kind === kind);
|
|
304
|
+
if (!entry) return void 0;
|
|
305
|
+
return { utilization: entry.percent, resets_at: entry.resets_at };
|
|
306
|
+
}
|
|
307
|
+
function slugifyDisplayName(name) {
|
|
308
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
309
|
+
}
|
|
310
|
+
function scopedWeeklyWindows(entries, now) {
|
|
311
|
+
const seen = /* @__PURE__ */ new Set();
|
|
312
|
+
const windows = [];
|
|
313
|
+
for (const entry of entries) {
|
|
314
|
+
if (entry.kind !== "weekly_scoped") continue;
|
|
315
|
+
const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
|
|
316
|
+
if (!displayName) continue;
|
|
317
|
+
const slug = slugifyDisplayName(displayName);
|
|
318
|
+
if (!slug || seen.has(slug)) continue;
|
|
319
|
+
seen.add(slug);
|
|
320
|
+
const usedPercent = finitePercent(entry.percent);
|
|
321
|
+
const resetsAt = isoInstant(entry.resets_at);
|
|
322
|
+
windows.push({
|
|
323
|
+
id: `seven-day-${slug}`,
|
|
324
|
+
label: `7 days \xB7 ${displayName}`,
|
|
325
|
+
scope: "model-family",
|
|
326
|
+
modelFamily: slug,
|
|
327
|
+
usedPercent,
|
|
328
|
+
windowMinutes: 7 * 24 * 60,
|
|
329
|
+
resetsAt,
|
|
330
|
+
remainingSeconds: secondsUntil(resetsAt, now),
|
|
331
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
return windows;
|
|
335
|
+
}
|
|
336
|
+
function parseLimitEntries(raw) {
|
|
337
|
+
if (!Array.isArray(raw)) return [];
|
|
338
|
+
return raw.filter((entry) => !!entry && typeof entry === "object");
|
|
339
|
+
}
|
|
229
340
|
function emptyClaudeWindows(state) {
|
|
230
341
|
return [
|
|
231
342
|
{
|
|
@@ -243,15 +354,6 @@ function emptyClaudeWindows(state) {
|
|
|
243
354
|
usedPercent: null,
|
|
244
355
|
windowMinutes: 7 * 24 * 60,
|
|
245
356
|
state
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
id: "seven-day-sonnet",
|
|
249
|
-
label: "7 days \xB7 Sonnet",
|
|
250
|
-
scope: "model-family",
|
|
251
|
-
modelFamily: "sonnet",
|
|
252
|
-
usedPercent: null,
|
|
253
|
-
windowMinutes: 7 * 24 * 60,
|
|
254
|
-
state
|
|
255
357
|
}
|
|
256
358
|
];
|
|
257
359
|
}
|
|
@@ -332,6 +434,9 @@ var ClaudeAllowanceCollector = class {
|
|
|
332
434
|
}
|
|
333
435
|
const now = this.now();
|
|
334
436
|
const usage = payload;
|
|
437
|
+
const limitEntries = parseLimitEntries(usage.limits);
|
|
438
|
+
const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
|
|
439
|
+
const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
|
|
335
440
|
const snapshot = {
|
|
336
441
|
providerId: "claude",
|
|
337
442
|
accountId,
|
|
@@ -339,10 +444,10 @@ var ClaudeAllowanceCollector = class {
|
|
|
339
444
|
observedAt: new Date(now).toISOString(),
|
|
340
445
|
expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
|
|
341
446
|
windows: [
|
|
342
|
-
windowFromPayload("five-hour",
|
|
343
|
-
windowFromPayload("seven-day",
|
|
344
|
-
|
|
345
|
-
]
|
|
447
|
+
windowFromPayload("five-hour", fiveHour, now),
|
|
448
|
+
windowFromPayload("seven-day", sevenDay, now),
|
|
449
|
+
...scopedWeeklyWindows(limitEntries, now)
|
|
450
|
+
].slice(0, 8)
|
|
346
451
|
};
|
|
347
452
|
this.store.set(snapshot);
|
|
348
453
|
return snapshot;
|
|
@@ -399,6 +504,601 @@ var ClaudeAllowanceCollector = class {
|
|
|
399
504
|
}
|
|
400
505
|
};
|
|
401
506
|
|
|
507
|
+
// src/allowance/CodexAllowanceCollector.ts
|
|
508
|
+
var import_AccountAllowanceStore2 = require("@omnicross/core/pipeline/AccountAllowanceStore");
|
|
509
|
+
var import_upstreamFetch2 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
510
|
+
var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
511
|
+
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
512
|
+
var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
|
|
513
|
+
function finiteNumber(value) {
|
|
514
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
515
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
516
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
517
|
+
}
|
|
518
|
+
function finitePercent2(value) {
|
|
519
|
+
const parsed = finiteNumber(value);
|
|
520
|
+
return parsed !== null && parsed <= 100 ? parsed : null;
|
|
521
|
+
}
|
|
522
|
+
function epochMs(value) {
|
|
523
|
+
return value > 1e11 ? value : value * 1e3;
|
|
524
|
+
}
|
|
525
|
+
function secondsUntil2(instant, now) {
|
|
526
|
+
if (!instant) return void 0;
|
|
527
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
528
|
+
}
|
|
529
|
+
function decodeJwtClaims(token) {
|
|
530
|
+
const parts = token.split(".");
|
|
531
|
+
if (parts.length !== 3) return void 0;
|
|
532
|
+
try {
|
|
533
|
+
const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
534
|
+
const parsed = JSON.parse(json2);
|
|
535
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
536
|
+
} catch {
|
|
537
|
+
return void 0;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function chatgptAccountIdFromClaims(claims) {
|
|
541
|
+
const auth = claims?.["https://api.openai.com/auth"];
|
|
542
|
+
if (!auth || typeof auth !== "object") return void 0;
|
|
543
|
+
const accountId = auth.chatgpt_account_id;
|
|
544
|
+
return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
|
|
545
|
+
}
|
|
546
|
+
function resolveCodexChatGptAccountId(tokens) {
|
|
547
|
+
if (tokens.accountId?.trim()) return tokens.accountId.trim();
|
|
548
|
+
if (tokens.idToken) {
|
|
549
|
+
const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
|
|
550
|
+
if (fromIdToken) return fromIdToken;
|
|
551
|
+
}
|
|
552
|
+
if (tokens.accessToken) {
|
|
553
|
+
return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
|
|
554
|
+
}
|
|
555
|
+
return void 0;
|
|
556
|
+
}
|
|
557
|
+
function windowFromPayload2(id, payload, now) {
|
|
558
|
+
const usedPercent = finitePercent2(payload?.used_percent);
|
|
559
|
+
const resetAtSeconds = finiteNumber(payload?.reset_at);
|
|
560
|
+
const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
|
|
561
|
+
const windowSeconds = finiteNumber(payload?.limit_window_seconds);
|
|
562
|
+
const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
|
|
563
|
+
const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
|
|
564
|
+
return {
|
|
565
|
+
id,
|
|
566
|
+
label: id === "primary" ? "Primary" : "Secondary",
|
|
567
|
+
scope: "all",
|
|
568
|
+
usedPercent,
|
|
569
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
570
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
571
|
+
remainingSeconds: secondsUntil2(resetsAt, now),
|
|
572
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
var CodexAllowanceCollector = class {
|
|
576
|
+
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) {
|
|
577
|
+
this.credentials = credentials;
|
|
578
|
+
this.store = store;
|
|
579
|
+
this.fetchImpl = fetchImpl;
|
|
580
|
+
this.now = now;
|
|
581
|
+
}
|
|
582
|
+
credentials;
|
|
583
|
+
store;
|
|
584
|
+
fetchImpl;
|
|
585
|
+
now;
|
|
586
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
587
|
+
async collectMany(accounts, options = {}) {
|
|
588
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
589
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
590
|
+
}
|
|
591
|
+
collect(account, options = {}) {
|
|
592
|
+
const now = this.now();
|
|
593
|
+
const unsupported = account.tokens.authMethod !== "oauth";
|
|
594
|
+
if (unsupported) {
|
|
595
|
+
const existing = this.store.get("codex", account.id, now);
|
|
596
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
597
|
+
return Promise.resolve(existing);
|
|
598
|
+
}
|
|
599
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
600
|
+
this.store.set(snapshot);
|
|
601
|
+
return Promise.resolve(snapshot);
|
|
602
|
+
}
|
|
603
|
+
const cached = this.store.get("codex", account.id, now);
|
|
604
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
605
|
+
return Promise.resolve(cached);
|
|
606
|
+
}
|
|
607
|
+
const running = this.inFlight.get(account.id);
|
|
608
|
+
if (running) return running;
|
|
609
|
+
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));
|
|
610
|
+
this.inFlight.set(account.id, promise);
|
|
611
|
+
return promise;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* A response-header snapshot stays a valid cache hit only while fresh; an
|
|
615
|
+
* active oauth-usage snapshot is honored on the same 5-minute cadence as
|
|
616
|
+
* Claude's (the poll is cheap and quota is the scheduling input).
|
|
617
|
+
*/
|
|
618
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
619
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
620
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
621
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
622
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
623
|
+
}
|
|
624
|
+
async fetchAccount(accountId, tokens) {
|
|
625
|
+
let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
|
|
626
|
+
if (!accessToken) {
|
|
627
|
+
return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
|
|
628
|
+
}
|
|
629
|
+
let response = await this.request(accountId, accessToken, tokens);
|
|
630
|
+
if (response.status === 401) {
|
|
631
|
+
const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
|
|
632
|
+
if (!refreshed) {
|
|
633
|
+
return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
|
|
634
|
+
}
|
|
635
|
+
accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
|
|
636
|
+
if (!accessToken) {
|
|
637
|
+
return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
|
|
638
|
+
}
|
|
639
|
+
response = await this.request(accountId, accessToken, tokens);
|
|
640
|
+
}
|
|
641
|
+
if (response.status === 403) {
|
|
642
|
+
const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
|
|
643
|
+
this.store.set(snapshot2);
|
|
644
|
+
return snapshot2;
|
|
645
|
+
}
|
|
646
|
+
if (!response.ok) {
|
|
647
|
+
return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
|
|
648
|
+
}
|
|
649
|
+
let payload;
|
|
650
|
+
try {
|
|
651
|
+
payload = await response.json();
|
|
652
|
+
} catch {
|
|
653
|
+
return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
|
|
654
|
+
}
|
|
655
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
656
|
+
return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
|
|
657
|
+
}
|
|
658
|
+
const now = this.now();
|
|
659
|
+
const usage = payload.rate_limit;
|
|
660
|
+
const previous = this.store.get("codex", accountId, now);
|
|
661
|
+
const snapshot = {
|
|
662
|
+
providerId: "codex",
|
|
663
|
+
accountId,
|
|
664
|
+
source: "oauth-usage-api",
|
|
665
|
+
observedAt: new Date(now).toISOString(),
|
|
666
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
667
|
+
windows: [
|
|
668
|
+
windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
|
|
669
|
+
windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
|
|
670
|
+
],
|
|
671
|
+
// The wham payload has no ratio field; keep the passively-observed value.
|
|
672
|
+
...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
|
|
673
|
+
};
|
|
674
|
+
this.store.set(snapshot);
|
|
675
|
+
return snapshot;
|
|
676
|
+
}
|
|
677
|
+
request(accountId, accessToken, tokens) {
|
|
678
|
+
const headers = {
|
|
679
|
+
Authorization: `Bearer ${accessToken}`,
|
|
680
|
+
Accept: "application/json",
|
|
681
|
+
"User-Agent": CODEX_CLI_USER_AGENT
|
|
682
|
+
};
|
|
683
|
+
const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
|
|
684
|
+
if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
|
|
685
|
+
return this.fetchImpl(CODEX_USAGE_URL, {
|
|
686
|
+
method: "GET",
|
|
687
|
+
headers,
|
|
688
|
+
signal: AbortSignal.timeout(15e3)
|
|
689
|
+
}, accountId);
|
|
690
|
+
}
|
|
691
|
+
failureSnapshot(accountId, code, now) {
|
|
692
|
+
const existing = this.store.get("codex", accountId, now);
|
|
693
|
+
const snapshot = existing ? {
|
|
694
|
+
...existing,
|
|
695
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
696
|
+
windows: existing.windows.map((window) => ({
|
|
697
|
+
...window,
|
|
698
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
699
|
+
})),
|
|
700
|
+
lastErrorCode: code
|
|
701
|
+
} : {
|
|
702
|
+
providerId: "codex",
|
|
703
|
+
accountId,
|
|
704
|
+
source: "oauth-usage-api",
|
|
705
|
+
observedAt: new Date(now).toISOString(),
|
|
706
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
707
|
+
windows: [
|
|
708
|
+
{ id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
|
|
709
|
+
{ id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
|
|
710
|
+
],
|
|
711
|
+
lastErrorCode: code
|
|
712
|
+
};
|
|
713
|
+
this.store.set(snapshot);
|
|
714
|
+
return snapshot;
|
|
715
|
+
}
|
|
716
|
+
unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
|
|
717
|
+
return {
|
|
718
|
+
providerId: "codex",
|
|
719
|
+
accountId,
|
|
720
|
+
source: "oauth-usage-api",
|
|
721
|
+
observedAt: new Date(now).toISOString(),
|
|
722
|
+
windows: [
|
|
723
|
+
{ id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
|
|
724
|
+
{ id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
|
|
725
|
+
],
|
|
726
|
+
lastErrorCode: code
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
// src/allowance/KimiAllowanceCollector.ts
|
|
732
|
+
var import_AccountAllowanceStore3 = require("@omnicross/core/pipeline/AccountAllowanceStore");
|
|
733
|
+
var import_upstreamFetch3 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
734
|
+
var import_subscriptions3 = require("@omnicross/subscriptions");
|
|
735
|
+
var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
736
|
+
var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
|
|
737
|
+
function finiteNumber2(value) {
|
|
738
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
739
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
740
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
741
|
+
}
|
|
742
|
+
function isRecord(value) {
|
|
743
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
744
|
+
}
|
|
745
|
+
function parseResetMs(row, nowMs) {
|
|
746
|
+
for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
|
|
747
|
+
const value = row[key];
|
|
748
|
+
if (typeof value === "string" && value.trim()) {
|
|
749
|
+
const parsed = Date.parse(value);
|
|
750
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
751
|
+
}
|
|
752
|
+
const numeric = finiteNumber2(value);
|
|
753
|
+
if (numeric !== void 0 && numeric > 1e9) {
|
|
754
|
+
return numeric > 1e12 ? numeric : numeric * 1e3;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
|
|
758
|
+
const seconds = finiteNumber2(row[key]);
|
|
759
|
+
if (seconds !== void 0) return nowMs + seconds * 1e3;
|
|
760
|
+
}
|
|
761
|
+
return void 0;
|
|
762
|
+
}
|
|
763
|
+
var MINUTE_MS = 6e4;
|
|
764
|
+
var HOUR_MS = 36e5;
|
|
765
|
+
var DAY_MS = 864e5;
|
|
766
|
+
function canonicalWindow(durationMs) {
|
|
767
|
+
if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
|
|
768
|
+
if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
|
|
769
|
+
if (durationMs > 0 && durationMs % DAY_MS === 0) {
|
|
770
|
+
const days = durationMs / DAY_MS;
|
|
771
|
+
return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
|
|
772
|
+
}
|
|
773
|
+
if (durationMs > 0 && durationMs % HOUR_MS === 0) {
|
|
774
|
+
const hours = durationMs / HOUR_MS;
|
|
775
|
+
return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
|
|
776
|
+
}
|
|
777
|
+
return void 0;
|
|
778
|
+
}
|
|
779
|
+
function secondsUntil3(instant, now) {
|
|
780
|
+
if (!instant) return void 0;
|
|
781
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
782
|
+
}
|
|
783
|
+
function windowFromRow(row, fallback, now) {
|
|
784
|
+
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;
|
|
785
|
+
const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
|
|
786
|
+
return {
|
|
787
|
+
id: fallback.id,
|
|
788
|
+
label: fallback.label,
|
|
789
|
+
scope: "all",
|
|
790
|
+
usedPercent,
|
|
791
|
+
windowMinutes: fallback.minutes,
|
|
792
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
793
|
+
remainingSeconds: secondsUntil3(resetsAt, now),
|
|
794
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function parseKimiUsagePayload(payload, now) {
|
|
798
|
+
if (!isRecord(payload)) return [];
|
|
799
|
+
const byId = /* @__PURE__ */ new Map();
|
|
800
|
+
const rowFrom = (data) => {
|
|
801
|
+
const limit = finiteNumber2(data["limit"]);
|
|
802
|
+
let used = finiteNumber2(data["used"]);
|
|
803
|
+
const remaining = finiteNumber2(data["remaining"]);
|
|
804
|
+
if (used === void 0 && remaining !== void 0 && limit !== void 0) {
|
|
805
|
+
used = limit - remaining;
|
|
806
|
+
}
|
|
807
|
+
let windowDurationMs;
|
|
808
|
+
const windowData = isRecord(data["window"]) ? data["window"] : void 0;
|
|
809
|
+
const duration = finiteNumber2(windowData?.["duration"]);
|
|
810
|
+
const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
|
|
811
|
+
if (duration !== void 0) {
|
|
812
|
+
if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
|
|
813
|
+
else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
|
|
814
|
+
else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
|
|
815
|
+
else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
|
|
816
|
+
else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
|
|
817
|
+
}
|
|
818
|
+
const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
|
|
819
|
+
return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
|
|
820
|
+
};
|
|
821
|
+
if (isRecord(payload["usage"])) {
|
|
822
|
+
const row = rowFrom(payload["usage"]);
|
|
823
|
+
const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
|
|
824
|
+
byId.set("seven-day", window);
|
|
825
|
+
}
|
|
826
|
+
if (Array.isArray(payload["limits"])) {
|
|
827
|
+
for (const item of payload["limits"]) {
|
|
828
|
+
if (!isRecord(item)) continue;
|
|
829
|
+
const detail = isRecord(item["detail"]) ? item["detail"] : item;
|
|
830
|
+
const row = rowFrom(detail);
|
|
831
|
+
const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
|
|
832
|
+
if (!canonical) continue;
|
|
833
|
+
const window = windowFromRow(row, canonical, now);
|
|
834
|
+
const existing = byId.get(canonical.id);
|
|
835
|
+
if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
|
|
836
|
+
byId.set(canonical.id, window);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
|
|
841
|
+
}
|
|
842
|
+
var KimiAllowanceCollector = class {
|
|
843
|
+
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) {
|
|
844
|
+
this.credentials = credentials;
|
|
845
|
+
this.store = store;
|
|
846
|
+
this.fetchImpl = fetchImpl;
|
|
847
|
+
this.now = now;
|
|
848
|
+
}
|
|
849
|
+
credentials;
|
|
850
|
+
store;
|
|
851
|
+
fetchImpl;
|
|
852
|
+
now;
|
|
853
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
854
|
+
async collectMany(accounts, options = {}) {
|
|
855
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
856
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
857
|
+
}
|
|
858
|
+
collect(account, options = {}) {
|
|
859
|
+
const now = this.now();
|
|
860
|
+
if (account.tokens.authMethod !== "oauth") {
|
|
861
|
+
const existing = this.store.get("kimi", account.id, now);
|
|
862
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
863
|
+
return Promise.resolve(existing);
|
|
864
|
+
}
|
|
865
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
866
|
+
this.store.set(snapshot);
|
|
867
|
+
return Promise.resolve(snapshot);
|
|
868
|
+
}
|
|
869
|
+
const cached = this.store.get("kimi", account.id, now);
|
|
870
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
871
|
+
return Promise.resolve(cached);
|
|
872
|
+
}
|
|
873
|
+
const running = this.inFlight.get(account.id);
|
|
874
|
+
if (running) return running;
|
|
875
|
+
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));
|
|
876
|
+
this.inFlight.set(account.id, promise);
|
|
877
|
+
return promise;
|
|
878
|
+
}
|
|
879
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
880
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
881
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
882
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
883
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
884
|
+
}
|
|
885
|
+
async fetchAccount(accountId, tokens) {
|
|
886
|
+
let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
|
|
887
|
+
if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
|
|
888
|
+
let response = await this.request(accountId, accessToken, tokens);
|
|
889
|
+
if (response.status === 401) {
|
|
890
|
+
const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
|
|
891
|
+
if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
|
|
892
|
+
accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
|
|
893
|
+
if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
|
|
894
|
+
response = await this.request(accountId, accessToken, tokens);
|
|
895
|
+
}
|
|
896
|
+
if (response.status === 403) {
|
|
897
|
+
const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
|
|
898
|
+
this.store.set(snapshot2);
|
|
899
|
+
return snapshot2;
|
|
900
|
+
}
|
|
901
|
+
if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
|
|
902
|
+
let payload;
|
|
903
|
+
try {
|
|
904
|
+
payload = await response.json();
|
|
905
|
+
} catch {
|
|
906
|
+
return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
|
|
907
|
+
}
|
|
908
|
+
const now = this.now();
|
|
909
|
+
const windows = parseKimiUsagePayload(payload, now);
|
|
910
|
+
const snapshot = {
|
|
911
|
+
providerId: "kimi",
|
|
912
|
+
accountId,
|
|
913
|
+
source: "oauth-usage-api",
|
|
914
|
+
observedAt: new Date(now).toISOString(),
|
|
915
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
916
|
+
windows: windows.length > 0 ? windows : [
|
|
917
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
918
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
919
|
+
],
|
|
920
|
+
...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
|
|
921
|
+
};
|
|
922
|
+
this.store.set(snapshot);
|
|
923
|
+
return snapshot;
|
|
924
|
+
}
|
|
925
|
+
request(accountId, accessToken, tokens) {
|
|
926
|
+
return this.fetchImpl(KIMI_USAGE_URL, {
|
|
927
|
+
method: "GET",
|
|
928
|
+
headers: {
|
|
929
|
+
Authorization: `Bearer ${accessToken}`,
|
|
930
|
+
Accept: "application/json",
|
|
931
|
+
...(0, import_subscriptions3.kimiFingerprintHeaders)(tokens.deviceId)
|
|
932
|
+
},
|
|
933
|
+
signal: AbortSignal.timeout(15e3)
|
|
934
|
+
}, accountId);
|
|
935
|
+
}
|
|
936
|
+
failureSnapshot(accountId, code, now) {
|
|
937
|
+
const existing = this.store.get("kimi", accountId, now);
|
|
938
|
+
const snapshot = existing ? {
|
|
939
|
+
...existing,
|
|
940
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
941
|
+
windows: existing.windows.map((window) => ({
|
|
942
|
+
...window,
|
|
943
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
944
|
+
})),
|
|
945
|
+
lastErrorCode: code
|
|
946
|
+
} : {
|
|
947
|
+
providerId: "kimi",
|
|
948
|
+
accountId,
|
|
949
|
+
source: "oauth-usage-api",
|
|
950
|
+
observedAt: new Date(now).toISOString(),
|
|
951
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
952
|
+
windows: [
|
|
953
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
954
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
955
|
+
],
|
|
956
|
+
lastErrorCode: code
|
|
957
|
+
};
|
|
958
|
+
this.store.set(snapshot);
|
|
959
|
+
return snapshot;
|
|
960
|
+
}
|
|
961
|
+
unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
|
|
962
|
+
return {
|
|
963
|
+
providerId: "kimi",
|
|
964
|
+
accountId,
|
|
965
|
+
source: "oauth-usage-api",
|
|
966
|
+
observedAt: new Date(now).toISOString(),
|
|
967
|
+
windows: [
|
|
968
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
|
|
969
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
|
|
970
|
+
],
|
|
971
|
+
lastErrorCode: code
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
// src/allowance/OpenCodeGoAllowanceCollector.ts
|
|
977
|
+
var import_AccountAllowanceStore4 = require("@omnicross/core/pipeline/AccountAllowanceStore");
|
|
978
|
+
var import_upstreamFetch4 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
979
|
+
var import_subscriptions4 = require("@omnicross/subscriptions");
|
|
980
|
+
var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
981
|
+
var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
|
|
982
|
+
function finitePercent3(value) {
|
|
983
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
984
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
985
|
+
return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
|
|
986
|
+
}
|
|
987
|
+
function isoInstant2(value) {
|
|
988
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
989
|
+
const time = Date.parse(value);
|
|
990
|
+
return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
|
|
991
|
+
}
|
|
992
|
+
function secondsUntil4(instant, now) {
|
|
993
|
+
if (!instant) return void 0;
|
|
994
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
995
|
+
}
|
|
996
|
+
function windowFromPayload3(id, label, minutes, payload, now) {
|
|
997
|
+
const statusRateLimited = payload?.status === "rate-limited";
|
|
998
|
+
const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
|
|
999
|
+
const resetsAt = isoInstant2(payload?.resetsAt);
|
|
1000
|
+
return {
|
|
1001
|
+
id,
|
|
1002
|
+
label,
|
|
1003
|
+
scope: "all",
|
|
1004
|
+
usedPercent,
|
|
1005
|
+
windowMinutes: minutes,
|
|
1006
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
1007
|
+
remainingSeconds: secondsUntil4(resetsAt, now),
|
|
1008
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
var OpenCodeGoAllowanceCollector = class {
|
|
1012
|
+
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) {
|
|
1013
|
+
this.credentials = credentials;
|
|
1014
|
+
this.store = store;
|
|
1015
|
+
this.fetchImpl = fetchImpl;
|
|
1016
|
+
this.now = now;
|
|
1017
|
+
}
|
|
1018
|
+
credentials;
|
|
1019
|
+
store;
|
|
1020
|
+
fetchImpl;
|
|
1021
|
+
now;
|
|
1022
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1023
|
+
async collectMany(accounts, options = {}) {
|
|
1024
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
1025
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
1026
|
+
}
|
|
1027
|
+
collect(account, options = {}) {
|
|
1028
|
+
const now = this.now();
|
|
1029
|
+
const cached = this.store.get("opencodego", account.id, now);
|
|
1030
|
+
if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
|
|
1031
|
+
return Promise.resolve(cached);
|
|
1032
|
+
}
|
|
1033
|
+
const running = this.inFlight.get(account.id);
|
|
1034
|
+
if (running) return running;
|
|
1035
|
+
const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
|
|
1036
|
+
this.inFlight.set(account.id, promise);
|
|
1037
|
+
return promise;
|
|
1038
|
+
}
|
|
1039
|
+
async fetchAccount(account) {
|
|
1040
|
+
const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
|
|
1041
|
+
if (!apiKey) return this.failureSnapshot(account.id, this.now());
|
|
1042
|
+
const base = account.tokens.baseUrl ? (0, import_subscriptions4.normalizeOpenCodeGoBaseUrl)(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
|
|
1043
|
+
const response = await this.fetchImpl(`${base}/v1/usage`, {
|
|
1044
|
+
method: "GET",
|
|
1045
|
+
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
1046
|
+
signal: AbortSignal.timeout(15e3)
|
|
1047
|
+
}, account.id);
|
|
1048
|
+
if (response.status === 401 || response.status === 403) {
|
|
1049
|
+
return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
|
|
1050
|
+
}
|
|
1051
|
+
if (!response.ok) return this.failureSnapshot(account.id, this.now());
|
|
1052
|
+
let payload;
|
|
1053
|
+
try {
|
|
1054
|
+
payload = await response.json();
|
|
1055
|
+
} catch {
|
|
1056
|
+
return this.failureSnapshot(account.id, this.now());
|
|
1057
|
+
}
|
|
1058
|
+
const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
|
|
1059
|
+
const now = this.now();
|
|
1060
|
+
const snapshot = {
|
|
1061
|
+
providerId: "opencodego",
|
|
1062
|
+
accountId: account.id,
|
|
1063
|
+
source: "oauth-usage-api",
|
|
1064
|
+
observedAt: new Date(now).toISOString(),
|
|
1065
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1066
|
+
// Monthly deliberately omitted (module doc).
|
|
1067
|
+
windows: [
|
|
1068
|
+
windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
|
|
1069
|
+
windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
|
|
1070
|
+
]
|
|
1071
|
+
};
|
|
1072
|
+
this.store.set(snapshot);
|
|
1073
|
+
return snapshot;
|
|
1074
|
+
}
|
|
1075
|
+
failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
|
|
1076
|
+
const existing = this.store.get("opencodego", accountId, now);
|
|
1077
|
+
const snapshot = existing ? {
|
|
1078
|
+
...existing,
|
|
1079
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1080
|
+
windows: existing.windows.map((window) => ({
|
|
1081
|
+
...window,
|
|
1082
|
+
state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
|
|
1083
|
+
})),
|
|
1084
|
+
lastErrorCode: code
|
|
1085
|
+
} : {
|
|
1086
|
+
providerId: "opencodego",
|
|
1087
|
+
accountId,
|
|
1088
|
+
source: "oauth-usage-api",
|
|
1089
|
+
observedAt: new Date(now).toISOString(),
|
|
1090
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1091
|
+
windows: [
|
|
1092
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
1093
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
1094
|
+
],
|
|
1095
|
+
lastErrorCode: code
|
|
1096
|
+
};
|
|
1097
|
+
this.store.set(snapshot);
|
|
1098
|
+
return snapshot;
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
|
|
402
1102
|
// src/allowance/AccountAllowanceService.ts
|
|
403
1103
|
function codexUnavailable(accountId, now) {
|
|
404
1104
|
return {
|
|
@@ -414,26 +1114,30 @@ function codexUnavailable(accountId, now) {
|
|
|
414
1114
|
};
|
|
415
1115
|
}
|
|
416
1116
|
var AccountAllowanceService = class {
|
|
417
|
-
constructor(credentials, store = (0,
|
|
1117
|
+
constructor(credentials, store = (0, import_AccountAllowanceStore5.getSharedAccountAllowanceStore)(), collector, codexCollector, kimiCollector, opencodegoCollector, now = Date.now) {
|
|
418
1118
|
this.credentials = credentials;
|
|
419
1119
|
this.store = store;
|
|
420
1120
|
this.now = now;
|
|
421
1121
|
this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
|
|
1122
|
+
this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
|
|
1123
|
+
this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
|
|
1124
|
+
this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
|
|
422
1125
|
}
|
|
423
1126
|
credentials;
|
|
424
1127
|
store;
|
|
425
1128
|
now;
|
|
426
1129
|
claudeCollector;
|
|
1130
|
+
codexCollector;
|
|
1131
|
+
kimiCollector;
|
|
1132
|
+
opencodegoCollector;
|
|
427
1133
|
/**
|
|
428
|
-
* Read all/filtered snapshots. Claude's five-minute
|
|
429
|
-
*
|
|
1134
|
+
* Read all/filtered snapshots. Claude's and Codex's five-minute caches are
|
|
1135
|
+
* refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
|
|
1136
|
+
* passive `x-codex-*` header tap still feeds mid-flight updates).
|
|
430
1137
|
*/
|
|
431
1138
|
async list(filter = {}) {
|
|
432
1139
|
const config = await this.credentials.getFullConfig();
|
|
433
|
-
this.store.pruneToKnownAccounts(
|
|
434
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
435
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
436
|
-
]);
|
|
1140
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
437
1141
|
const wantsClaude = !filter.providerId || filter.providerId === "claude";
|
|
438
1142
|
const claudeAccounts = (config.claudeAccounts ?? []).filter(
|
|
439
1143
|
(account) => !filter.accountId || account.id === filter.accountId
|
|
@@ -444,39 +1148,90 @@ var AccountAllowanceService = class {
|
|
|
444
1148
|
(account) => !filter.accountId || account.id === filter.accountId
|
|
445
1149
|
);
|
|
446
1150
|
if (wantsCodex) {
|
|
1151
|
+
await this.codexCollector.collectMany(codexAccounts);
|
|
447
1152
|
for (const account of codexAccounts) {
|
|
448
1153
|
if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
|
|
449
1154
|
}
|
|
450
1155
|
}
|
|
1156
|
+
const wantsKimi = !filter.providerId || filter.providerId === "kimi";
|
|
1157
|
+
const kimiAccounts = (config.kimiAccounts ?? []).filter(
|
|
1158
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
1159
|
+
);
|
|
1160
|
+
if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
|
|
1161
|
+
const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
|
|
1162
|
+
const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
|
|
1163
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
1164
|
+
);
|
|
1165
|
+
if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
|
|
451
1166
|
const known = /* @__PURE__ */ new Set();
|
|
452
1167
|
if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
|
|
453
1168
|
if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
|
|
1169
|
+
if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
|
|
1170
|
+
if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
|
|
454
1171
|
return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
|
|
455
1172
|
}
|
|
1173
|
+
knownAccounts(config) {
|
|
1174
|
+
return [
|
|
1175
|
+
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
1176
|
+
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
|
|
1177
|
+
...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
|
|
1178
|
+
...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id }))
|
|
1179
|
+
];
|
|
1180
|
+
}
|
|
456
1181
|
/** Force-refresh Claude usage for one account or every stored Claude account. */
|
|
457
1182
|
async refreshClaude(accountId) {
|
|
458
1183
|
const config = await this.credentials.getFullConfig();
|
|
459
|
-
this.store.pruneToKnownAccounts(
|
|
460
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
461
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
462
|
-
]);
|
|
1184
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
463
1185
|
const accounts = (config.claudeAccounts ?? []).filter(
|
|
464
1186
|
(account) => !accountId || account.id === accountId
|
|
465
1187
|
);
|
|
466
1188
|
return this.claudeCollector.collectMany(accounts, { force: true });
|
|
467
1189
|
}
|
|
468
1190
|
/**
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
1191
|
+
* Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
|
|
1192
|
+
* every stored Codex account. Replaces the old probe-request workaround —
|
|
1193
|
+
* no quota is spent reading the usage endpoint.
|
|
1194
|
+
*/
|
|
1195
|
+
async refreshCodex(accountId) {
|
|
1196
|
+
const config = await this.credentials.getFullConfig();
|
|
1197
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1198
|
+
const accounts = (config.codexAccounts ?? []).filter(
|
|
1199
|
+
(account) => !accountId || account.id === accountId
|
|
1200
|
+
);
|
|
1201
|
+
return this.codexCollector.collectMany(accounts, { force: true });
|
|
1202
|
+
}
|
|
1203
|
+
/** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
|
|
1204
|
+
async refreshOpenCodeGo(accountId) {
|
|
1205
|
+
const config = await this.credentials.getFullConfig();
|
|
1206
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1207
|
+
const accounts = (config.opencodegoAccounts ?? []).filter(
|
|
1208
|
+
(account) => !accountId || account.id === accountId
|
|
1209
|
+
);
|
|
1210
|
+
return this.opencodegoCollector.collectMany(accounts, { force: true });
|
|
1211
|
+
}
|
|
1212
|
+
/** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
|
|
1213
|
+
async refreshKimi(accountId) {
|
|
1214
|
+
const config = await this.credentials.getFullConfig();
|
|
1215
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1216
|
+
const accounts = (config.kimiAccounts ?? []).filter(
|
|
1217
|
+
(account) => !accountId || account.id === accountId
|
|
1218
|
+
);
|
|
1219
|
+
return this.kimiCollector.collectMany(accounts, { force: true });
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
|
|
1223
|
+
* collectors preserve their cache + per-account in-flight coalescing; a tick
|
|
1224
|
+
* normally performs no network I/O. (Codex joined the warm path when it
|
|
1225
|
+
* gained an active `/wham/usage` collector — the passive `x-codex-*` header
|
|
1226
|
+
* tap alone could not keep the policy fed while idle.)
|
|
472
1227
|
*/
|
|
473
1228
|
async maintainClaudeCache(refreshAheadMs) {
|
|
474
1229
|
const config = await this.credentials.getFullConfig();
|
|
475
|
-
this.store.pruneToKnownAccounts(
|
|
476
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
477
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
478
|
-
]);
|
|
1230
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
479
1231
|
await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
|
|
1232
|
+
await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
|
|
1233
|
+
await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
|
|
1234
|
+
await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
|
|
480
1235
|
}
|
|
481
1236
|
/** Remove a cache row as soon as an account is deleted by the admin path. */
|
|
482
1237
|
removeAccountSnapshot(providerId, accountId) {
|
|
@@ -571,7 +1326,7 @@ var ClaudeAllowanceRefreshScheduler = class {
|
|
|
571
1326
|
var import_node_crypto2 = require("crypto");
|
|
572
1327
|
var import_node_fs = require("fs");
|
|
573
1328
|
var import_node_path = require("path");
|
|
574
|
-
var
|
|
1329
|
+
var import_AccountAllowanceStore6 = require("@omnicross/core/pipeline/AccountAllowanceStore");
|
|
575
1330
|
var ACCOUNT_ALLOWANCE_CACHE_VERSION = 1;
|
|
576
1331
|
var MAX_PERSISTED_ALLOWANCE_SNAPSHOTS = 256;
|
|
577
1332
|
var MAX_ALLOWANCE_CACHE_BYTES = 1e6;
|
|
@@ -600,7 +1355,7 @@ var JsonAccountAllowancePersistence = class {
|
|
|
600
1355
|
save(snapshots) {
|
|
601
1356
|
const rows = [];
|
|
602
1357
|
for (const snapshot of snapshots) {
|
|
603
|
-
const normalized2 = (0,
|
|
1358
|
+
const normalized2 = (0, import_AccountAllowanceStore6.normalizeAccountAllowanceSnapshot)(snapshot);
|
|
604
1359
|
if (!normalized2) continue;
|
|
605
1360
|
rows.push(normalized2);
|
|
606
1361
|
if (rows.length >= MAX_PERSISTED_ALLOWANCE_SNAPSHOTS) break;
|
|
@@ -883,7 +1638,7 @@ var import_outbound_api5 = require("@omnicross/core/outbound-api");
|
|
|
883
1638
|
var import_image_generation_types = require("@omnicross/contracts/image-generation-types");
|
|
884
1639
|
var import_AccountAllowanceScheduling2 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
|
|
885
1640
|
var import_SubscriptionAccountHealth = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
|
|
886
|
-
var
|
|
1641
|
+
var import_upstreamFetch7 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
887
1642
|
|
|
888
1643
|
// src/image-generation/imagesConfigValidation.ts
|
|
889
1644
|
var import_outbound_api = require("@omnicross/core/outbound-api");
|
|
@@ -2851,11 +3606,11 @@ function preserveOutboundProxySecrets(incoming, current) {
|
|
|
2851
3606
|
}
|
|
2852
3607
|
|
|
2853
3608
|
// src/proxy/upstreamProxyResolver.ts
|
|
2854
|
-
var
|
|
3609
|
+
var import_upstreamFetch5 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
2855
3610
|
var serverProxy;
|
|
2856
3611
|
function setServerProxyConfig(proxy) {
|
|
2857
3612
|
serverProxy = proxy;
|
|
2858
|
-
(0,
|
|
3613
|
+
(0, import_upstreamFetch5.bumpUpstreamProxyGeneration)();
|
|
2859
3614
|
}
|
|
2860
3615
|
function getServerProxyConfig() {
|
|
2861
3616
|
return serverProxy;
|
|
@@ -2923,14 +3678,15 @@ function createUpstreamProxyResolver(src = {}) {
|
|
|
2923
3678
|
}
|
|
2924
3679
|
|
|
2925
3680
|
// src/admin/accountsOAuth.ts
|
|
2926
|
-
var
|
|
3681
|
+
var import_subscriptions5 = require("@omnicross/subscriptions");
|
|
2927
3682
|
|
|
2928
3683
|
// src/admin/accountsWrite.ts
|
|
2929
3684
|
var VALID_PROVIDER_IDS = [
|
|
2930
3685
|
"claude",
|
|
2931
3686
|
"codex",
|
|
2932
3687
|
"gemini",
|
|
2933
|
-
"opencodego"
|
|
3688
|
+
"opencodego",
|
|
3689
|
+
"kimi"
|
|
2934
3690
|
];
|
|
2935
3691
|
function asSubscriptionProviderId(id) {
|
|
2936
3692
|
return VALID_PROVIDER_IDS.includes(id) ? id : null;
|
|
@@ -3054,7 +3810,19 @@ function validateCodex(body) {
|
|
|
3054
3810
|
]);
|
|
3055
3811
|
return out;
|
|
3056
3812
|
}
|
|
3057
|
-
function validateGemini(body) {
|
|
3813
|
+
function validateGemini(body) {
|
|
3814
|
+
const authMethod = str(body["authMethod"]);
|
|
3815
|
+
const status = str(body["status"]);
|
|
3816
|
+
if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
|
|
3817
|
+
if (!status || !TOKEN_STATUSES.has(status)) return null;
|
|
3818
|
+
const out = {
|
|
3819
|
+
authMethod,
|
|
3820
|
+
status
|
|
3821
|
+
};
|
|
3822
|
+
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
|
|
3823
|
+
return out;
|
|
3824
|
+
}
|
|
3825
|
+
function validateKimi(body) {
|
|
3058
3826
|
const authMethod = str(body["authMethod"]);
|
|
3059
3827
|
const status = str(body["status"]);
|
|
3060
3828
|
if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
|
|
@@ -3063,7 +3831,7 @@ function validateGemini(body) {
|
|
|
3063
3831
|
authMethod,
|
|
3064
3832
|
status
|
|
3065
3833
|
};
|
|
3066
|
-
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
|
|
3834
|
+
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
|
|
3067
3835
|
return out;
|
|
3068
3836
|
}
|
|
3069
3837
|
function validateOpenCodeGo(body) {
|
|
@@ -3101,6 +3869,8 @@ function validateTokenBody(providerId, body) {
|
|
|
3101
3869
|
return validateGemini(body);
|
|
3102
3870
|
case "opencodego":
|
|
3103
3871
|
return validateOpenCodeGo(body);
|
|
3872
|
+
case "kimi":
|
|
3873
|
+
return validateKimi(body);
|
|
3104
3874
|
default:
|
|
3105
3875
|
return null;
|
|
3106
3876
|
}
|
|
@@ -3130,37 +3900,37 @@ async function statusEntryFor(reader, providerId) {
|
|
|
3130
3900
|
|
|
3131
3901
|
// src/admin/accountsOAuth.ts
|
|
3132
3902
|
var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
|
|
3133
|
-
function
|
|
3903
|
+
function err3(status, message) {
|
|
3134
3904
|
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
3135
3905
|
}
|
|
3136
3906
|
function handleOAuthStart(providerId, deps) {
|
|
3137
3907
|
if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
|
|
3138
|
-
return
|
|
3908
|
+
return err3(400, `oauth not available for provider '${providerId}'`);
|
|
3139
3909
|
}
|
|
3140
|
-
const flow = providerId === "claude" ?
|
|
3910
|
+
const flow = providerId === "claude" ? import_subscriptions5.claudeOAuth : import_subscriptions5.geminiOAuth;
|
|
3141
3911
|
const { authUrl, codeVerifier, state } = flow.generateAuthParams();
|
|
3142
3912
|
const sessionId = deps.oauthSessions.put({ providerId, codeVerifier, state });
|
|
3143
3913
|
return { status: 200, body: { authUrl, sessionId } };
|
|
3144
3914
|
}
|
|
3145
3915
|
async function handleOAuthComplete(providerId, body, deps) {
|
|
3146
3916
|
if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
|
|
3147
|
-
return
|
|
3917
|
+
return err3(400, `oauth not available for provider '${providerId}'`);
|
|
3148
3918
|
}
|
|
3149
3919
|
const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
|
|
3150
3920
|
const rawCode = typeof body["code"] === "string" ? body["code"] : "";
|
|
3151
|
-
if (!sessionId) return
|
|
3152
|
-
if (!rawCode) return
|
|
3921
|
+
if (!sessionId) return err3(400, "oauth complete requires { sessionId }");
|
|
3922
|
+
if (!rawCode) return err3(400, "oauth complete requires { code }");
|
|
3153
3923
|
const session = deps.oauthSessions.peek(sessionId);
|
|
3154
|
-
if (!session) return
|
|
3924
|
+
if (!session) return err3(410, "oauth session is unknown, expired, or already used");
|
|
3155
3925
|
if (session.providerId !== providerId) {
|
|
3156
|
-
return
|
|
3926
|
+
return err3(400, `oauth session does not match provider '${providerId}'`);
|
|
3157
3927
|
}
|
|
3158
3928
|
let code = rawCode.trim();
|
|
3159
3929
|
if (providerId === "claude") {
|
|
3160
3930
|
const [splitCode, pastedState] = code.split("#");
|
|
3161
|
-
if (!splitCode) return
|
|
3931
|
+
if (!splitCode) return err3(400, "no authorization code was provided");
|
|
3162
3932
|
if (pastedState && pastedState !== session.state) {
|
|
3163
|
-
return
|
|
3933
|
+
return err3(400, "oauth state did not match (possible CSRF) \u2014 aborting");
|
|
3164
3934
|
}
|
|
3165
3935
|
code = splitCode;
|
|
3166
3936
|
}
|
|
@@ -3170,7 +3940,7 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
3170
3940
|
block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
|
|
3171
3941
|
} catch (exchangeError) {
|
|
3172
3942
|
const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
|
|
3173
|
-
return
|
|
3943
|
+
return err3(502, `oauth token exchange failed for '${providerId}': ${reason}`);
|
|
3174
3944
|
}
|
|
3175
3945
|
deps.oauthSessions.consume(sessionId);
|
|
3176
3946
|
const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
|
|
@@ -3179,7 +3949,7 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
3179
3949
|
return { status: 200, body: status ? { account: status } : { ok: true } };
|
|
3180
3950
|
}
|
|
3181
3951
|
async function exchangeClaude(code, codeVerifier, state, exchangeFetch) {
|
|
3182
|
-
const result = await
|
|
3952
|
+
const result = await import_subscriptions5.claudeOAuth.exchangeCodeForTokens(
|
|
3183
3953
|
{ authorizationCode: code, codeVerifier, state },
|
|
3184
3954
|
exchangeFetch
|
|
3185
3955
|
);
|
|
@@ -3195,7 +3965,7 @@ async function exchangeClaude(code, codeVerifier, state, exchangeFetch) {
|
|
|
3195
3965
|
};
|
|
3196
3966
|
}
|
|
3197
3967
|
async function exchangeGemini(code, codeVerifier, exchangeFetch) {
|
|
3198
|
-
const result = await
|
|
3968
|
+
const result = await import_subscriptions5.geminiOAuth.exchangeCodeForTokens(code, codeVerifier, exchangeFetch);
|
|
3199
3969
|
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
3200
3970
|
return {
|
|
3201
3971
|
authMethod: "oauth",
|
|
@@ -3500,8 +4270,8 @@ function errBody(message) {
|
|
|
3500
4270
|
return { error: { type: "admin_api_error", message } };
|
|
3501
4271
|
}
|
|
3502
4272
|
var defaultCommandRunner = (command) => new Promise((resolve10) => {
|
|
3503
|
-
(0, import_node_child_process.exec)(command, { timeout: 18e4 }, (
|
|
3504
|
-
if (
|
|
4273
|
+
(0, import_node_child_process.exec)(command, { timeout: 18e4 }, (err6, _stdout, stderr) => {
|
|
4274
|
+
if (err6) resolve10({ ok: false, error: stderr.trim() || err6.message });
|
|
3505
4275
|
else resolve10({ ok: true });
|
|
3506
4276
|
});
|
|
3507
4277
|
});
|
|
@@ -3547,8 +4317,8 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
3547
4317
|
providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
|
|
3548
4318
|
model: typeof body["model"] === "string" ? body["model"] : void 0
|
|
3549
4319
|
});
|
|
3550
|
-
} catch (
|
|
3551
|
-
return { status: 400, body: errBody(
|
|
4320
|
+
} catch (err6) {
|
|
4321
|
+
return { status: 400, body: errBody(err6 instanceof Error ? err6.message : "no launch target") };
|
|
3552
4322
|
}
|
|
3553
4323
|
const id = (0, import_node_crypto7.randomUUID)();
|
|
3554
4324
|
let leaseId2;
|
|
@@ -3576,9 +4346,9 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
3576
4346
|
} else {
|
|
3577
4347
|
launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
|
|
3578
4348
|
}
|
|
3579
|
-
} catch (
|
|
3580
|
-
const status =
|
|
3581
|
-
return { status, body: errBody(
|
|
4349
|
+
} catch (err6) {
|
|
4350
|
+
const status = err6 instanceof import_provider_proxy2.RouteLeaseError ? err6.status : 400;
|
|
4351
|
+
return { status, body: errBody(err6 instanceof Error ? err6.message : "failed to build launch env") };
|
|
3582
4352
|
}
|
|
3583
4353
|
const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
|
|
3584
4354
|
const opener = ctx.opener ?? defaultTerminalOpener;
|
|
@@ -3606,9 +4376,9 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
3606
4376
|
onFailure: onSessionEnd
|
|
3607
4377
|
});
|
|
3608
4378
|
if (cleanup) openerCleanup = cleanup;
|
|
3609
|
-
} catch (
|
|
4379
|
+
} catch (err6) {
|
|
3610
4380
|
onSessionEnd();
|
|
3611
|
-
return { status: 500, body: errBody(
|
|
4381
|
+
return { status: 500, body: errBody(err6 instanceof Error ? err6.message : "failed to open terminal") };
|
|
3612
4382
|
}
|
|
3613
4383
|
if (ended) {
|
|
3614
4384
|
openerCleanup?.();
|
|
@@ -3743,6 +4513,7 @@ async function handleDashboard(deps) {
|
|
|
3743
4513
|
var import_outbound_api2 = require("@omnicross/core/outbound-api");
|
|
3744
4514
|
var import_api3 = require("@omnicross/core/search/api");
|
|
3745
4515
|
var import_http3 = require("@omnicross/core/search/http");
|
|
4516
|
+
var import_search2 = require("@omnicross/core/search");
|
|
3746
4517
|
|
|
3747
4518
|
// src/search/searchDoctorProjection.ts
|
|
3748
4519
|
var import_search_types = require("@omnicross/contracts/search-types");
|
|
@@ -3803,7 +4574,7 @@ function buildSearchDoctorSnapshot(contributions = (0, import_http.builtinHttpSe
|
|
|
3803
4574
|
}
|
|
3804
4575
|
return rows;
|
|
3805
4576
|
}
|
|
3806
|
-
var SEARCH_DOCTOR_QUERY = "
|
|
4577
|
+
var SEARCH_DOCTOR_QUERY = "MDN HTTP headers documentation";
|
|
3807
4578
|
function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
|
|
3808
4579
|
if (outcome.kind === "results") {
|
|
3809
4580
|
if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
|
|
@@ -3848,6 +4619,7 @@ function classifySearchFailure(stage, code) {
|
|
|
3848
4619
|
}
|
|
3849
4620
|
|
|
3850
4621
|
// src/search/SearchAssembly.ts
|
|
4622
|
+
var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
3851
4623
|
var import_search = require("@omnicross/core/search");
|
|
3852
4624
|
var import_api2 = require("@omnicross/core/search/api");
|
|
3853
4625
|
var import_http2 = require("@omnicross/core/search/http");
|
|
@@ -3864,11 +4636,24 @@ function searchPolicyFrom(config) {
|
|
|
3864
4636
|
...maxAttempts !== void 0 ? { maxAttempts } : {}
|
|
3865
4637
|
};
|
|
3866
4638
|
}
|
|
4639
|
+
function resolveSearchUpstreamDispatcher(url) {
|
|
4640
|
+
return (0, import_upstreamFetch6.resolveUpstreamDispatcher)({ url });
|
|
4641
|
+
}
|
|
4642
|
+
var searchUpstreamProxyConfig = createUpstreamProxyResolver();
|
|
4643
|
+
function resolveSearchUpstreamProxyConfig(url) {
|
|
4644
|
+
return searchUpstreamProxyConfig({ url });
|
|
4645
|
+
}
|
|
3867
4646
|
function searchContributionsFrom(config) {
|
|
3868
4647
|
return [
|
|
3869
|
-
...(0, import_http2.builtinHttpSearchContributions)(
|
|
4648
|
+
...(0, import_http2.builtinHttpSearchContributions)(
|
|
4649
|
+
(0, import_http2.createSearchHttpTransport)({
|
|
4650
|
+
resolveProxyDispatcher: resolveSearchUpstreamDispatcher,
|
|
4651
|
+
resolveProxyConfig: resolveSearchUpstreamProxyConfig
|
|
4652
|
+
})
|
|
4653
|
+
),
|
|
3870
4654
|
...(0, import_api2.apiSearchContributions)(config.providers, {
|
|
3871
|
-
egressPolicy: searchEgressPolicyFrom(config)
|
|
4655
|
+
egressPolicy: searchEgressPolicyFrom(config),
|
|
4656
|
+
resolveProxyDispatcher: resolveSearchUpstreamDispatcher
|
|
3872
4657
|
})
|
|
3873
4658
|
];
|
|
3874
4659
|
}
|
|
@@ -4012,6 +4797,18 @@ async function handleSearchDiagnostics(res, deps) {
|
|
|
4012
4797
|
};
|
|
4013
4798
|
return writeJson(res, 200, { diagnostics: snapshot });
|
|
4014
4799
|
}
|
|
4800
|
+
function persistedSearchContributions(search, fetchImpl) {
|
|
4801
|
+
if (fetchImpl) {
|
|
4802
|
+
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4803
|
+
return [
|
|
4804
|
+
...(0, import_http3.builtinHttpSearchContributions)(
|
|
4805
|
+
(0, import_http3.createSearchHttpTransport)({ fetch: fetchImpl, egressPolicy })
|
|
4806
|
+
),
|
|
4807
|
+
...(0, import_api3.apiSearchContributions)(search.providers, { egressPolicy, fetchImpl })
|
|
4808
|
+
];
|
|
4809
|
+
}
|
|
4810
|
+
return searchContributionsFrom(search);
|
|
4811
|
+
}
|
|
4015
4812
|
async function handleSearchTest(req, res, deps) {
|
|
4016
4813
|
const status = deps.searchStatus;
|
|
4017
4814
|
const body = await readBodyOrReject(req, res);
|
|
@@ -4029,16 +4826,8 @@ async function handleSearchTest(req, res, deps) {
|
|
|
4029
4826
|
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4030
4827
|
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4031
4828
|
}
|
|
4032
|
-
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4033
4829
|
const fetchImpl = status.testFetch;
|
|
4034
|
-
const
|
|
4035
|
-
const contributions = [
|
|
4036
|
-
...(0, import_http3.builtinHttpSearchContributions)(transport),
|
|
4037
|
-
...(0, import_api3.apiSearchContributions)(search.providers, {
|
|
4038
|
-
egressPolicy,
|
|
4039
|
-
...fetchImpl ? { fetchImpl } : {}
|
|
4040
|
-
})
|
|
4041
|
-
];
|
|
4830
|
+
const contributions = persistedSearchContributions(search, fetchImpl);
|
|
4042
4831
|
const contribution = contributions.find((c) => c.id === providerId);
|
|
4043
4832
|
if (!contribution) {
|
|
4044
4833
|
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
@@ -4086,41 +4875,42 @@ async function handleSearchQuery(req, res, deps) {
|
|
|
4086
4875
|
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4087
4876
|
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4088
4877
|
}
|
|
4089
|
-
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4090
4878
|
const fetchImpl = status.testFetch;
|
|
4091
|
-
const
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
}
|
|
4879
|
+
const runtime = (0, import_search2.createSearchRuntime)({
|
|
4880
|
+
contributions: persistedSearchContributions(search, fetchImpl),
|
|
4881
|
+
policy: {
|
|
4882
|
+
...searchPolicyFrom(search),
|
|
4883
|
+
// The panel always walks: it answers "does a search WORK for this
|
|
4884
|
+
// operator", not "does this one provider behave" — that is `/test`'s
|
|
4885
|
+
// job. The persisted policy's allowlist still bounds the walk.
|
|
4886
|
+
fallbackEnabled: true,
|
|
4887
|
+
preferred: providerId
|
|
4888
|
+
}
|
|
4889
|
+
});
|
|
4103
4890
|
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4104
4891
|
try {
|
|
4105
|
-
const
|
|
4892
|
+
const orchestrated = await runtime.search({ query: query2, options: { maxResults: 5 } });
|
|
4893
|
+
const results = orchestrated.results;
|
|
4106
4894
|
const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
|
|
4107
4895
|
title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
|
|
4108
4896
|
url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
|
|
4109
4897
|
content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
|
|
4110
4898
|
}));
|
|
4111
|
-
const diagnostic = sanitized.length === 0 ? { providerId:
|
|
4112
|
-
|
|
4899
|
+
const diagnostic = sanitized.length === 0 ? { providerId: orchestrated.providerId, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
|
|
4900
|
+
orchestrated.providerId,
|
|
4113
4901
|
{ kind: "results", count: sanitized.length },
|
|
4114
4902
|
checkedAt
|
|
4115
4903
|
);
|
|
4116
4904
|
const response = {
|
|
4117
4905
|
diagnostic,
|
|
4906
|
+
providerUsed: orchestrated.providerId,
|
|
4907
|
+
fallbackCount: orchestrated.fallbackCount,
|
|
4118
4908
|
resultCount: sanitized.length,
|
|
4119
4909
|
results: sanitized
|
|
4120
4910
|
};
|
|
4121
4911
|
return writeJson(res, 200, { result: response });
|
|
4122
4912
|
} catch (error) {
|
|
4123
|
-
const diagnostic = classifyLiveSearchOutcome(
|
|
4913
|
+
const diagnostic = classifyLiveSearchOutcome(providerId, { kind: "failure", error }, checkedAt);
|
|
4124
4914
|
const response = { diagnostic };
|
|
4125
4915
|
return writeJson(res, 200, { result: response });
|
|
4126
4916
|
}
|
|
@@ -4129,7 +4919,7 @@ async function handleSearchQuery(req, res, deps) {
|
|
|
4129
4919
|
// src/admin/searchAdminView.ts
|
|
4130
4920
|
var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
|
|
4131
4921
|
var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
|
|
4132
|
-
function
|
|
4922
|
+
function isRecord2(value) {
|
|
4133
4923
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4134
4924
|
}
|
|
4135
4925
|
function redactSearchServerConfig(search) {
|
|
@@ -4179,13 +4969,13 @@ function resolveSecretField(entry, field, stored) {
|
|
|
4179
4969
|
else delete entry[field];
|
|
4180
4970
|
}
|
|
4181
4971
|
function preserveSearchSecrets(incoming, current) {
|
|
4182
|
-
if (!
|
|
4972
|
+
if (!isRecord2(incoming)) return incoming;
|
|
4183
4973
|
const section = { ...incoming };
|
|
4184
4974
|
const providersValue = section["providers"];
|
|
4185
|
-
if (!
|
|
4975
|
+
if (!isRecord2(providersValue)) return section;
|
|
4186
4976
|
const providers = {};
|
|
4187
4977
|
for (const [id, entryValue] of Object.entries(providersValue)) {
|
|
4188
|
-
if (!
|
|
4978
|
+
if (!isRecord2(entryValue)) {
|
|
4189
4979
|
providers[id] = entryValue;
|
|
4190
4980
|
continue;
|
|
4191
4981
|
}
|
|
@@ -4263,7 +5053,7 @@ function parseKeyPolicyBody(body) {
|
|
|
4263
5053
|
var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
|
|
4264
5054
|
var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
|
|
4265
5055
|
var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
|
|
4266
|
-
function
|
|
5056
|
+
function isRecord3(value) {
|
|
4267
5057
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4268
5058
|
}
|
|
4269
5059
|
function nonBlank(value) {
|
|
@@ -4283,7 +5073,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4283
5073
|
const ids = /* @__PURE__ */ new Set();
|
|
4284
5074
|
raw.forEach((entry, index) => {
|
|
4285
5075
|
const path2 = `bindings[${index}]`;
|
|
4286
|
-
if (!
|
|
5076
|
+
if (!isRecord3(entry)) {
|
|
4287
5077
|
errors.push(`${path2} must be an object`);
|
|
4288
5078
|
return;
|
|
4289
5079
|
}
|
|
@@ -4312,12 +5102,12 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4312
5102
|
} else if (entry.modelMappings.length > 100) {
|
|
4313
5103
|
errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
|
|
4314
5104
|
} else if (entry.modelMappings.some(
|
|
4315
|
-
(mapping) => !
|
|
5105
|
+
(mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
|
|
4316
5106
|
)) {
|
|
4317
5107
|
errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
|
|
4318
5108
|
}
|
|
4319
5109
|
}
|
|
4320
|
-
if (!
|
|
5110
|
+
if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
|
|
4321
5111
|
errors.push(`${path2}.target is invalid`);
|
|
4322
5112
|
} else {
|
|
4323
5113
|
if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
|
|
@@ -4332,7 +5122,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4332
5122
|
}
|
|
4333
5123
|
}
|
|
4334
5124
|
if (entry.modelMap !== void 0) {
|
|
4335
|
-
if (!
|
|
5125
|
+
if (!isRecord3(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
|
|
4336
5126
|
errors.push(`${path2}.modelMap must contain string values`);
|
|
4337
5127
|
}
|
|
4338
5128
|
}
|
|
@@ -4630,7 +5420,8 @@ var PROVIDER_KEYS = {
|
|
|
4630
5420
|
block: "opencodego",
|
|
4631
5421
|
accounts: "opencodegoAccounts",
|
|
4632
5422
|
active: "activeOpencodegoAccountId"
|
|
4633
|
-
}
|
|
5423
|
+
},
|
|
5424
|
+
kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
|
|
4634
5425
|
};
|
|
4635
5426
|
function clone(value) {
|
|
4636
5427
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -5152,7 +5943,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
|
|
|
5152
5943
|
}
|
|
5153
5944
|
|
|
5154
5945
|
// src/admin/adminMigration.ts
|
|
5155
|
-
function
|
|
5946
|
+
function err4(status, message) {
|
|
5156
5947
|
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
5157
5948
|
}
|
|
5158
5949
|
async function handleExport(body, deps) {
|
|
@@ -5162,30 +5953,30 @@ async function handleExport(body, deps) {
|
|
|
5162
5953
|
return { status: 200, body: { pack, version: BUNDLE_VERSION } };
|
|
5163
5954
|
} catch (error) {
|
|
5164
5955
|
if (error instanceof WeakPassphraseError) {
|
|
5165
|
-
return
|
|
5956
|
+
return err4(400, error.message);
|
|
5166
5957
|
}
|
|
5167
|
-
return
|
|
5958
|
+
return err4(500, "failed to build the migration pack");
|
|
5168
5959
|
}
|
|
5169
5960
|
}
|
|
5170
5961
|
async function handleImport(body, deps) {
|
|
5171
5962
|
const blob = typeof body["blob"] === "string" ? body["blob"] : "";
|
|
5172
5963
|
const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
|
|
5173
5964
|
const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
|
|
5174
|
-
if (!blob) return
|
|
5965
|
+
if (!blob) return err4(400, "import requires { blob }");
|
|
5175
5966
|
try {
|
|
5176
5967
|
const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
|
|
5177
5968
|
return { status: 200, body: counts };
|
|
5178
5969
|
} catch (error) {
|
|
5179
5970
|
if (error instanceof WeakPassphraseError) {
|
|
5180
|
-
return
|
|
5971
|
+
return err4(400, error.message);
|
|
5181
5972
|
}
|
|
5182
|
-
return
|
|
5973
|
+
return err4(400, error instanceof Error ? error.message : "import failed");
|
|
5183
5974
|
}
|
|
5184
5975
|
}
|
|
5185
5976
|
|
|
5186
5977
|
// src/admin/usagePricing.ts
|
|
5187
5978
|
var import_usage = require("@omnicross/core/usage");
|
|
5188
|
-
var
|
|
5979
|
+
var err5 = (status, message) => ({
|
|
5189
5980
|
status,
|
|
5190
5981
|
body: { error: { type: "admin_api_error", message } }
|
|
5191
5982
|
});
|
|
@@ -5198,7 +5989,7 @@ function parseRange(query2) {
|
|
|
5198
5989
|
const startTs = parseFiniteInt(query2.get("startTs"));
|
|
5199
5990
|
const endTs = parseFiniteInt(query2.get("endTs"));
|
|
5200
5991
|
if (startTs === null || endTs === null) {
|
|
5201
|
-
return
|
|
5992
|
+
return err5(400, "startTs and endTs are required finite-integer unix-millis query params");
|
|
5202
5993
|
}
|
|
5203
5994
|
return { startTs, endTs };
|
|
5204
5995
|
}
|
|
@@ -5223,14 +6014,14 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
5223
6014
|
case "timeseries": {
|
|
5224
6015
|
const bucket = query2.get("bucket");
|
|
5225
6016
|
if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
|
|
5226
|
-
return
|
|
6017
|
+
return err5(400, "bucket must be one of 'hour', 'day', 'month'");
|
|
5227
6018
|
}
|
|
5228
6019
|
const now = Date.now();
|
|
5229
6020
|
const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
|
|
5230
6021
|
if (clamped.startTs < clamped.endTs) {
|
|
5231
6022
|
const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
|
|
5232
6023
|
if (projected > MAX_TIMESERIES_BUCKETS) {
|
|
5233
|
-
return
|
|
6024
|
+
return err5(
|
|
5234
6025
|
400,
|
|
5235
6026
|
`requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
|
|
5236
6027
|
);
|
|
@@ -5253,7 +6044,7 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
5253
6044
|
};
|
|
5254
6045
|
}
|
|
5255
6046
|
default:
|
|
5256
|
-
return
|
|
6047
|
+
return err5(404, `unknown usage view '${view ?? ""}'`);
|
|
5257
6048
|
}
|
|
5258
6049
|
}
|
|
5259
6050
|
function poolKeyLabels(cfg) {
|
|
@@ -5302,7 +6093,7 @@ async function handlePricingList(deps) {
|
|
|
5302
6093
|
async function handlePricingUpsert(body, deps) {
|
|
5303
6094
|
const input = parsePricingEntryInput(body);
|
|
5304
6095
|
if (!input) {
|
|
5305
|
-
return
|
|
6096
|
+
return err5(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
|
|
5306
6097
|
}
|
|
5307
6098
|
const entry = await deps.pricingEngine.upsertManual(input);
|
|
5308
6099
|
return { status: 200, body: { entry } };
|
|
@@ -5311,7 +6102,7 @@ async function handlePricingDelete(query2, deps) {
|
|
|
5311
6102
|
const providerId = query2.get("providerId")?.trim() ?? "";
|
|
5312
6103
|
const modelId = query2.get("modelId")?.trim() ?? "";
|
|
5313
6104
|
if (!providerId || !modelId) {
|
|
5314
|
-
return
|
|
6105
|
+
return err5(400, "delete requires providerId and modelId query params");
|
|
5315
6106
|
}
|
|
5316
6107
|
const deleted = await deps.pricingStore.delete(providerId, modelId);
|
|
5317
6108
|
if (deleted) await deps.pricingEngine.invalidateCache();
|
|
@@ -5331,13 +6122,13 @@ async function handlePricingFetchLatest(deps) {
|
|
|
5331
6122
|
}
|
|
5332
6123
|
};
|
|
5333
6124
|
} catch (e) {
|
|
5334
|
-
return
|
|
6125
|
+
return err5(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
5335
6126
|
}
|
|
5336
6127
|
}
|
|
5337
6128
|
async function handlePricingResolveConflicts(body, deps) {
|
|
5338
6129
|
const raw = body["resolutions"];
|
|
5339
6130
|
if (!Array.isArray(raw)) {
|
|
5340
|
-
return
|
|
6131
|
+
return err5(400, "resolve-conflicts requires { resolutions: [...] }");
|
|
5341
6132
|
}
|
|
5342
6133
|
const currentRows = await deps.pricingStore.getAll();
|
|
5343
6134
|
const userEditedKeys = new Set(
|
|
@@ -5347,21 +6138,21 @@ async function handlePricingResolveConflicts(body, deps) {
|
|
|
5347
6138
|
const pendingIncoming = /* @__PURE__ */ new Map();
|
|
5348
6139
|
let staleCount = 0;
|
|
5349
6140
|
for (const item of raw) {
|
|
5350
|
-
if (!item || typeof item !== "object") return
|
|
6141
|
+
if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
|
|
5351
6142
|
const r = item;
|
|
5352
6143
|
const action = r["action"];
|
|
5353
6144
|
if (action !== "overwrite" && action !== "skip") {
|
|
5354
|
-
return
|
|
6145
|
+
return err5(400, "resolution action must be 'overwrite' or 'skip'");
|
|
5355
6146
|
}
|
|
5356
6147
|
const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
|
|
5357
6148
|
const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
|
|
5358
6149
|
if (!providerId || !modelId) {
|
|
5359
|
-
return
|
|
6150
|
+
return err5(400, "each resolution requires top-level providerId and modelId");
|
|
5360
6151
|
}
|
|
5361
6152
|
const incoming = parsePricingEntryInput(r["incoming"]);
|
|
5362
|
-
if (!incoming) return
|
|
6153
|
+
if (!incoming) return err5(400, "each resolution must echo a valid incoming pricing entry");
|
|
5363
6154
|
if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
|
|
5364
|
-
return
|
|
6155
|
+
return err5(400, "resolution providerId/modelId must match the echoed incoming entry");
|
|
5365
6156
|
}
|
|
5366
6157
|
const key = `${providerId}::${modelId}`;
|
|
5367
6158
|
if (action === "overwrite" && !userEditedKeys.has(key)) {
|
|
@@ -5406,7 +6197,7 @@ function query(req) {
|
|
|
5406
6197
|
}
|
|
5407
6198
|
function allowanceProvider(value) {
|
|
5408
6199
|
if (!value) return void 0;
|
|
5409
|
-
return value === "claude" || value === "codex" ? value : null;
|
|
6200
|
+
return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
|
|
5410
6201
|
}
|
|
5411
6202
|
async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
5412
6203
|
if (!service) return writeError2(res, 501, "account allowance service is not available");
|
|
@@ -5420,7 +6211,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5420
6211
|
const params = query(req);
|
|
5421
6212
|
const pathProvider = rest.length >= 2 ? rest[0] : null;
|
|
5422
6213
|
const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
|
|
5423
|
-
if (providerId === null)
|
|
6214
|
+
if (providerId === null) {
|
|
6215
|
+
return writeError2(res, 400, "providerId must be claude, codex, kimi, or opencodego");
|
|
6216
|
+
}
|
|
5424
6217
|
const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
|
|
5425
6218
|
const allowances = await service.list({ providerId, accountId });
|
|
5426
6219
|
return writeJson3(res, 200, { allowances });
|
|
@@ -5430,10 +6223,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5430
6223
|
const requestedProvider = allowanceProvider(
|
|
5431
6224
|
typeof body["providerId"] === "string" ? body["providerId"] : "claude"
|
|
5432
6225
|
);
|
|
5433
|
-
if (requestedProvider !== "claude") {
|
|
5434
|
-
return writeError2(res, 400, "only Claude allowances support explicit refresh");
|
|
5435
|
-
}
|
|
5436
6226
|
const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
|
|
6227
|
+
if (requestedProvider === "codex") {
|
|
6228
|
+
if (!service.refreshCodex) {
|
|
6229
|
+
return writeError2(res, 501, "codex allowance refresh is not available");
|
|
6230
|
+
}
|
|
6231
|
+
const allowances2 = await service.refreshCodex(accountId);
|
|
6232
|
+
if (accountId && allowances2.length === 0) {
|
|
6233
|
+
return writeError2(res, 404, `Codex account '${accountId}' not found`);
|
|
6234
|
+
}
|
|
6235
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
6236
|
+
}
|
|
6237
|
+
if (requestedProvider === "kimi") {
|
|
6238
|
+
if (!service.refreshKimi) {
|
|
6239
|
+
return writeError2(res, 501, "kimi allowance refresh is not available");
|
|
6240
|
+
}
|
|
6241
|
+
const allowances2 = await service.refreshKimi(accountId);
|
|
6242
|
+
if (accountId && allowances2.length === 0) {
|
|
6243
|
+
return writeError2(res, 404, `Kimi account '${accountId}' not found`);
|
|
6244
|
+
}
|
|
6245
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
6246
|
+
}
|
|
6247
|
+
if (requestedProvider === "opencodego") {
|
|
6248
|
+
if (!service.refreshOpenCodeGo) {
|
|
6249
|
+
return writeError2(res, 501, "opencodego allowance refresh is not available");
|
|
6250
|
+
}
|
|
6251
|
+
const allowances2 = await service.refreshOpenCodeGo(accountId);
|
|
6252
|
+
if (accountId && allowances2.length === 0) {
|
|
6253
|
+
return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
|
|
6254
|
+
}
|
|
6255
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
6256
|
+
}
|
|
5437
6257
|
const allowances = await service.refreshClaude(accountId);
|
|
5438
6258
|
if (accountId && allowances.length === 0) {
|
|
5439
6259
|
return writeError2(res, 404, `Claude account '${accountId}' not found`);
|
|
@@ -5601,8 +6421,8 @@ async function handleAdminApi(req, res, path2, deps) {
|
|
|
5601
6421
|
default:
|
|
5602
6422
|
return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
|
|
5603
6423
|
}
|
|
5604
|
-
} catch (
|
|
5605
|
-
writeJsonError(res, 500,
|
|
6424
|
+
} catch (err6) {
|
|
6425
|
+
writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
|
|
5606
6426
|
}
|
|
5607
6427
|
}
|
|
5608
6428
|
function requestQuery(req) {
|
|
@@ -5672,6 +6492,9 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
5672
6492
|
if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
|
|
5673
6493
|
return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
5674
6494
|
}
|
|
6495
|
+
if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
|
|
6496
|
+
return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
|
|
6497
|
+
}
|
|
5675
6498
|
if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
|
|
5676
6499
|
return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
5677
6500
|
}
|
|
@@ -5770,7 +6593,7 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
5770
6593
|
try {
|
|
5771
6594
|
const headers = { Accept: "application/json" };
|
|
5772
6595
|
if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
|
|
5773
|
-
const response = await (0,
|
|
6596
|
+
const response = await (0, import_upstreamFetch7.fetchUpstream)(url, { method: "GET", headers }, { providerId: "byo" });
|
|
5774
6597
|
if (!response.ok) {
|
|
5775
6598
|
const text = await response.text().catch(() => "");
|
|
5776
6599
|
let message = text.slice(0, 300);
|
|
@@ -5787,8 +6610,8 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
5787
6610
|
const data = await response.json();
|
|
5788
6611
|
const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
|
|
5789
6612
|
return writeJson4(res, 200, { models });
|
|
5790
|
-
} catch (
|
|
5791
|
-
const message =
|
|
6613
|
+
} catch (err6) {
|
|
6614
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
5792
6615
|
return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
|
|
5793
6616
|
}
|
|
5794
6617
|
}
|
|
@@ -5829,7 +6652,7 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
5829
6652
|
}
|
|
5830
6653
|
const startedAt = Date.now();
|
|
5831
6654
|
try {
|
|
5832
|
-
const response = await (0,
|
|
6655
|
+
const response = await (0, import_upstreamFetch7.fetchUpstream)(
|
|
5833
6656
|
url,
|
|
5834
6657
|
{ method: "POST", headers, body: JSON.stringify(payload) },
|
|
5835
6658
|
{ providerId: "byo" }
|
|
@@ -5851,8 +6674,8 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
5851
6674
|
latencyMs,
|
|
5852
6675
|
sample: extractSampleText(text, row.apiFormat)
|
|
5853
6676
|
});
|
|
5854
|
-
} catch (
|
|
5855
|
-
const message =
|
|
6677
|
+
} catch (err6) {
|
|
6678
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
5856
6679
|
return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
|
|
5857
6680
|
}
|
|
5858
6681
|
}
|
|
@@ -5894,7 +6717,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
|
|
|
5894
6717
|
const row = cfg.providers.find((p) => p.id === id);
|
|
5895
6718
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
5896
6719
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
5897
|
-
|
|
6720
|
+
const views = toPoolKeyView(row, cooldown, deps);
|
|
6721
|
+
if (deps.providerKeyQuota) {
|
|
6722
|
+
const quotas = await Promise.allSettled(
|
|
6723
|
+
views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
|
|
6724
|
+
);
|
|
6725
|
+
views.forEach((view, index) => {
|
|
6726
|
+
const settled = quotas[index];
|
|
6727
|
+
if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
|
|
6728
|
+
});
|
|
6729
|
+
}
|
|
6730
|
+
return writeJson4(res, 200, { keys: views });
|
|
6731
|
+
}
|
|
6732
|
+
async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
|
|
6733
|
+
if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
|
|
6734
|
+
if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
|
|
6735
|
+
const row = cfg.providers.find((p) => p.id === id);
|
|
6736
|
+
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6737
|
+
try {
|
|
6738
|
+
const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
|
|
6739
|
+
if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
|
|
6740
|
+
return writeJson4(res, 200, { quota });
|
|
6741
|
+
} catch {
|
|
6742
|
+
return writeJsonError(res, 502, "quota refresh failed");
|
|
6743
|
+
}
|
|
5898
6744
|
}
|
|
5899
6745
|
function parsePoolKeyInput(body, existing) {
|
|
5900
6746
|
const out = {};
|
|
@@ -6639,12 +7485,12 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6639
7485
|
}
|
|
6640
7486
|
return writeJson4(res, 200, { ok: true, affected: result.affected });
|
|
6641
7487
|
}
|
|
6642
|
-
if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
|
|
6643
|
-
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
7488
|
+
if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[3] === "status") {
|
|
7489
|
+
const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : handleKimiOAuthStatus(rest[2], deps);
|
|
6644
7490
|
return writeJson4(res, result.status, result.body);
|
|
6645
7491
|
}
|
|
6646
|
-
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
6647
|
-
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
7492
|
+
if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[2]) {
|
|
7493
|
+
const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : handleKimiOAuthCancel(rest[2], deps);
|
|
6648
7494
|
return writeJson4(res, result.status, result.body);
|
|
6649
7495
|
}
|
|
6650
7496
|
if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
|
|
@@ -6697,7 +7543,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6697
7543
|
return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
6698
7544
|
}
|
|
6699
7545
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
|
|
6700
|
-
|
|
7546
|
+
if (providerId === "codex") {
|
|
7547
|
+
const result2 = handleCodexOAuthStart(deps);
|
|
7548
|
+
return writeJson4(res, result2.status, result2.body);
|
|
7549
|
+
}
|
|
7550
|
+
if (providerId === "kimi") {
|
|
7551
|
+
const result2 = await handleKimiOAuthStart(deps);
|
|
7552
|
+
return writeJson4(res, result2.status, result2.body);
|
|
7553
|
+
}
|
|
7554
|
+
const result = handleOAuthStart(providerId, deps);
|
|
6701
7555
|
return writeJson4(res, result.status, result.body);
|
|
6702
7556
|
}
|
|
6703
7557
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
|
|
@@ -7191,12 +8045,12 @@ async function handlePlayground(req, res, method, deps) {
|
|
|
7191
8045
|
const payload = body["body"];
|
|
7192
8046
|
const status = deps.outboundApiServer.getStatus();
|
|
7193
8047
|
if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
|
|
7194
|
-
const path2 = resolvePlaygroundPath(endpoint,
|
|
8048
|
+
const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
|
|
7195
8049
|
if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
|
|
7196
8050
|
const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
7197
8051
|
await proxyToOutbound(res, status.port, path2, key, upstreamBody);
|
|
7198
8052
|
}
|
|
7199
|
-
function
|
|
8053
|
+
function isRecord4(v) {
|
|
7200
8054
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
7201
8055
|
}
|
|
7202
8056
|
function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
@@ -7225,8 +8079,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
|
7225
8079
|
});
|
|
7226
8080
|
}
|
|
7227
8081
|
);
|
|
7228
|
-
upstream.on("error", (
|
|
7229
|
-
if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${
|
|
8082
|
+
upstream.on("error", (err6) => {
|
|
8083
|
+
if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err6.message}`);
|
|
7230
8084
|
else res.end();
|
|
7231
8085
|
resolve10();
|
|
7232
8086
|
});
|
|
@@ -7332,7 +8186,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
7332
8186
|
}
|
|
7333
8187
|
|
|
7334
8188
|
// src/admin/version.ts
|
|
7335
|
-
var DAEMON_VERSION = true ? "0.
|
|
8189
|
+
var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
|
|
7336
8190
|
|
|
7337
8191
|
// src/admin/AdminServer.ts
|
|
7338
8192
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -7375,13 +8229,13 @@ var AdminServer = class {
|
|
|
7375
8229
|
const server = import_node_http2.default.createServer((req, res) => {
|
|
7376
8230
|
this.onRequest(req, res);
|
|
7377
8231
|
});
|
|
7378
|
-
const onError = (
|
|
7379
|
-
if (
|
|
8232
|
+
const onError = (err6) => {
|
|
8233
|
+
if (err6.code === "EADDRINUSE" && port !== 0) {
|
|
7380
8234
|
server.removeListener("error", onError);
|
|
7381
8235
|
this.listen(bindAddr, 0).then(resolve10, reject);
|
|
7382
8236
|
return;
|
|
7383
8237
|
}
|
|
7384
|
-
reject(
|
|
8238
|
+
reject(err6);
|
|
7385
8239
|
};
|
|
7386
8240
|
server.on("error", onError);
|
|
7387
8241
|
server.listen(port, bindAddr, () => {
|
|
@@ -7399,8 +8253,8 @@ var AdminServer = class {
|
|
|
7399
8253
|
}
|
|
7400
8254
|
/** Per-request handler: auth gate (when a token is set) → routing. */
|
|
7401
8255
|
onRequest(req, res) {
|
|
7402
|
-
void this.dispatch(req, res).catch((
|
|
7403
|
-
const message =
|
|
8256
|
+
void this.dispatch(req, res).catch((err6) => {
|
|
8257
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
7404
8258
|
this.deps.logger.error("[AdminServer] unhandled error:", message);
|
|
7405
8259
|
if (!res.headersSent) {
|
|
7406
8260
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -7664,18 +8518,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
|
|
|
7664
8518
|
return;
|
|
7665
8519
|
}
|
|
7666
8520
|
signal?.addEventListener("abort", abort, { once: true });
|
|
7667
|
-
server.on("error", (
|
|
8521
|
+
server.on("error", (err6) => {
|
|
7668
8522
|
if (settled) return;
|
|
7669
8523
|
settled = true;
|
|
7670
8524
|
clearTimeout(timer);
|
|
7671
|
-
if (
|
|
8525
|
+
if (err6.code === "EADDRINUSE") {
|
|
7672
8526
|
reject(
|
|
7673
8527
|
new Error(
|
|
7674
8528
|
`login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
|
|
7675
8529
|
)
|
|
7676
8530
|
);
|
|
7677
8531
|
} else {
|
|
7678
|
-
reject(
|
|
8532
|
+
reject(err6);
|
|
7679
8533
|
}
|
|
7680
8534
|
});
|
|
7681
8535
|
const timer = setTimeout(() => {
|
|
@@ -7750,6 +8604,411 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
|
|
|
7750
8604
|
};
|
|
7751
8605
|
}
|
|
7752
8606
|
|
|
8607
|
+
// src/allowance/ProviderKeyQuotaService.ts
|
|
8608
|
+
var import_upstreamFetch8 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
8609
|
+
|
|
8610
|
+
// src/allowance/ProviderKeyQuota.ts
|
|
8611
|
+
var MINUTE_MS2 = 6e4;
|
|
8612
|
+
var HOUR_MS2 = 60 * MINUTE_MS2;
|
|
8613
|
+
var DAY_MS2 = 24 * HOUR_MS2;
|
|
8614
|
+
var WEEK_MS = 7 * DAY_MS2;
|
|
8615
|
+
var MONTH_MS = 30 * DAY_MS2;
|
|
8616
|
+
function finiteNumber3(value) {
|
|
8617
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
8618
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
8619
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
8620
|
+
}
|
|
8621
|
+
function finitePercent4(value) {
|
|
8622
|
+
const parsed = finiteNumber3(value);
|
|
8623
|
+
return parsed !== void 0 && parsed <= 100 ? parsed : null;
|
|
8624
|
+
}
|
|
8625
|
+
function isoInstant3(value) {
|
|
8626
|
+
if (typeof value === "string" && value.trim()) {
|
|
8627
|
+
const time = Date.parse(value);
|
|
8628
|
+
if (Number.isFinite(time)) return new Date(time).toISOString();
|
|
8629
|
+
}
|
|
8630
|
+
const numeric = finiteNumber3(value);
|
|
8631
|
+
if (numeric !== void 0 && numeric > 1e9) {
|
|
8632
|
+
const ms = numeric > 1e12 ? numeric : numeric * 1e3;
|
|
8633
|
+
return new Date(ms).toISOString();
|
|
8634
|
+
}
|
|
8635
|
+
return void 0;
|
|
8636
|
+
}
|
|
8637
|
+
function secondsUntil5(instant, now) {
|
|
8638
|
+
if (!instant) return void 0;
|
|
8639
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
8640
|
+
}
|
|
8641
|
+
function isRecord5(value) {
|
|
8642
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8643
|
+
}
|
|
8644
|
+
function detectProviderKeyQuotaAdapter(baseUrl) {
|
|
8645
|
+
if (!baseUrl) return null;
|
|
8646
|
+
let url;
|
|
8647
|
+
try {
|
|
8648
|
+
url = new URL(baseUrl);
|
|
8649
|
+
} catch {
|
|
8650
|
+
return null;
|
|
8651
|
+
}
|
|
8652
|
+
const host = url.hostname.toLowerCase();
|
|
8653
|
+
const path2 = url.pathname.toLowerCase();
|
|
8654
|
+
if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
|
|
8655
|
+
return "zai";
|
|
8656
|
+
}
|
|
8657
|
+
if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
|
|
8658
|
+
// anthropic `/anthropic` rows are excluded (their usage impl is unverified).
|
|
8659
|
+
(path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
|
|
8660
|
+
return "minimax-token-plan";
|
|
8661
|
+
}
|
|
8662
|
+
if (host === "api.code.umans.ai") return "umans";
|
|
8663
|
+
if (host === "api.synthetic.new") return "synthetic";
|
|
8664
|
+
return null;
|
|
8665
|
+
}
|
|
8666
|
+
function providerKeyQuotaUrl(adapter, baseUrl) {
|
|
8667
|
+
const origin = new URL(baseUrl).origin;
|
|
8668
|
+
if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
|
|
8669
|
+
if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
|
|
8670
|
+
if (adapter === "umans") return `${origin}/v1/usage`;
|
|
8671
|
+
return `${origin}/v2/quotas`;
|
|
8672
|
+
}
|
|
8673
|
+
function providerKeyQuotaAuthHeader(adapter, key) {
|
|
8674
|
+
return adapter === "zai" ? key : `Bearer ${key}`;
|
|
8675
|
+
}
|
|
8676
|
+
function zaiWindowDurationMs(item) {
|
|
8677
|
+
const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
|
|
8678
|
+
switch (item.unit) {
|
|
8679
|
+
case 3:
|
|
8680
|
+
return count * HOUR_MS2;
|
|
8681
|
+
case 4:
|
|
8682
|
+
return count * DAY_MS2;
|
|
8683
|
+
case 5:
|
|
8684
|
+
return count * MONTH_MS;
|
|
8685
|
+
case 6:
|
|
8686
|
+
return WEEK_MS;
|
|
8687
|
+
default:
|
|
8688
|
+
return void 0;
|
|
8689
|
+
}
|
|
8690
|
+
}
|
|
8691
|
+
function zaiWindowIdLabel(durationMs) {
|
|
8692
|
+
if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
|
|
8693
|
+
if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
|
|
8694
|
+
if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
|
|
8695
|
+
if (durationMs !== void 0 && durationMs % DAY_MS2 === 0) {
|
|
8696
|
+
const days = durationMs / DAY_MS2;
|
|
8697
|
+
return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
|
|
8698
|
+
}
|
|
8699
|
+
if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
|
|
8700
|
+
const hours = durationMs / HOUR_MS2;
|
|
8701
|
+
return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
|
|
8702
|
+
}
|
|
8703
|
+
return { id: "quota", label: "Quota" };
|
|
8704
|
+
}
|
|
8705
|
+
function parseZaiQuotaPayload(payload, now) {
|
|
8706
|
+
if (!isRecord5(payload)) return null;
|
|
8707
|
+
const data = isRecord5(payload["data"]) ? payload["data"] : payload;
|
|
8708
|
+
if (payload["success"] === false) return null;
|
|
8709
|
+
const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
|
|
8710
|
+
const byWindow = /* @__PURE__ */ new Map();
|
|
8711
|
+
for (const raw of limits) {
|
|
8712
|
+
if (!isRecord5(raw)) continue;
|
|
8713
|
+
const item = raw;
|
|
8714
|
+
if (item.type === void 0) continue;
|
|
8715
|
+
const details = raw["usageDetails"];
|
|
8716
|
+
if (Array.isArray(details) && details.some((d) => isRecord5(d) && d["modelCode"] === "zread")) {
|
|
8717
|
+
continue;
|
|
8718
|
+
}
|
|
8719
|
+
const durationMs = zaiWindowDurationMs(item);
|
|
8720
|
+
const { id, label } = zaiWindowIdLabel(durationMs);
|
|
8721
|
+
const limit = finiteNumber3(item.usage);
|
|
8722
|
+
const used = finiteNumber3(item.currentValue);
|
|
8723
|
+
const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
|
|
8724
|
+
const fromPercentage = finitePercent4(item.percentage) ?? void 0;
|
|
8725
|
+
const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
|
|
8726
|
+
if (usedPercent === void 0) continue;
|
|
8727
|
+
const resetsAt = isoInstant3(item.nextResetTime);
|
|
8728
|
+
const candidate = {
|
|
8729
|
+
id,
|
|
8730
|
+
label,
|
|
8731
|
+
scope: "all",
|
|
8732
|
+
usedPercent,
|
|
8733
|
+
...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS2) } : {},
|
|
8734
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8735
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8736
|
+
state: "fresh"
|
|
8737
|
+
};
|
|
8738
|
+
const existing = byWindow.get(id);
|
|
8739
|
+
if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
|
|
8740
|
+
byWindow.set(id, candidate);
|
|
8741
|
+
}
|
|
8742
|
+
}
|
|
8743
|
+
const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
|
|
8744
|
+
return windows.length > 0 ? windows.slice(0, 4) : null;
|
|
8745
|
+
}
|
|
8746
|
+
var MINIMAX_STATUS_EXHAUSTED = 2;
|
|
8747
|
+
var MINIMAX_SHARED_BUCKET = "general";
|
|
8748
|
+
function parseMiniMaxBucket(value) {
|
|
8749
|
+
if (!isRecord5(value)) return null;
|
|
8750
|
+
const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
|
|
8751
|
+
if (!modelName) return null;
|
|
8752
|
+
const instant = (v) => {
|
|
8753
|
+
const n = finiteNumber3(v);
|
|
8754
|
+
return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
|
|
8755
|
+
};
|
|
8756
|
+
return {
|
|
8757
|
+
modelName,
|
|
8758
|
+
intervalEnd: instant(value["end_time"]),
|
|
8759
|
+
intervalRemainingPercent: finiteNumber3(value["current_interval_remaining_percent"]),
|
|
8760
|
+
intervalStatus: finiteNumber3(value["current_interval_status"]),
|
|
8761
|
+
weeklyEnd: instant(value["weekly_end_time"]),
|
|
8762
|
+
weeklyRemainingPercent: finiteNumber3(value["current_weekly_remaining_percent"]),
|
|
8763
|
+
weeklyStatus: finiteNumber3(value["current_weekly_status"])
|
|
8764
|
+
};
|
|
8765
|
+
}
|
|
8766
|
+
function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
|
|
8767
|
+
const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
|
|
8768
|
+
const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
|
|
8769
|
+
return {
|
|
8770
|
+
id,
|
|
8771
|
+
label,
|
|
8772
|
+
scope: "all",
|
|
8773
|
+
usedPercent,
|
|
8774
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
8775
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8776
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8777
|
+
state: usedPercent !== null ? "fresh" : "unavailable"
|
|
8778
|
+
};
|
|
8779
|
+
}
|
|
8780
|
+
function parseMiniMaxTokenPlanPayload(payload, now) {
|
|
8781
|
+
if (!isRecord5(payload)) return null;
|
|
8782
|
+
const baseResp = payload["base_resp"];
|
|
8783
|
+
if (!isRecord5(baseResp) || baseResp["status_code"] !== 0) return null;
|
|
8784
|
+
const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
|
|
8785
|
+
let general = null;
|
|
8786
|
+
for (const raw of buckets) {
|
|
8787
|
+
const bucket = parseMiniMaxBucket(raw);
|
|
8788
|
+
if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
|
|
8789
|
+
general = bucket;
|
|
8790
|
+
break;
|
|
8791
|
+
}
|
|
8792
|
+
}
|
|
8793
|
+
if (!general) return null;
|
|
8794
|
+
return [
|
|
8795
|
+
minimaxWindow(
|
|
8796
|
+
"five-hour",
|
|
8797
|
+
"5 hours",
|
|
8798
|
+
5 * 60,
|
|
8799
|
+
general.intervalEnd,
|
|
8800
|
+
general.intervalRemainingPercent,
|
|
8801
|
+
general.intervalStatus,
|
|
8802
|
+
now
|
|
8803
|
+
),
|
|
8804
|
+
minimaxWindow(
|
|
8805
|
+
"seven-day",
|
|
8806
|
+
"7 days",
|
|
8807
|
+
Math.round(WEEK_MS / MINUTE_MS2),
|
|
8808
|
+
general.weeklyEnd,
|
|
8809
|
+
general.weeklyRemainingPercent,
|
|
8810
|
+
general.weeklyStatus,
|
|
8811
|
+
now
|
|
8812
|
+
)
|
|
8813
|
+
];
|
|
8814
|
+
}
|
|
8815
|
+
function parseUmansUsagePayload(payload, now) {
|
|
8816
|
+
if (!isRecord5(payload)) return null;
|
|
8817
|
+
const limits = isRecord5(payload["limits"]) ? payload["limits"] : void 0;
|
|
8818
|
+
const requests = limits && isRecord5(limits["requests"]) ? limits["requests"] : void 0;
|
|
8819
|
+
const usage = isRecord5(payload["usage"]) ? payload["usage"] : void 0;
|
|
8820
|
+
const window = isRecord5(payload["window"]) ? payload["window"] : void 0;
|
|
8821
|
+
const hardCap = finiteNumber3(requests?.["hard_cap"]);
|
|
8822
|
+
const softLimit = finiteNumber3(requests?.["limit"]);
|
|
8823
|
+
const requestsInWindow = finiteNumber3(usage?.["requests_in_window"]);
|
|
8824
|
+
const weightedInWindow = finiteNumber3(usage?.["weighted_in_window"]);
|
|
8825
|
+
const resetsAt = isoInstant3(window?.["resets_at"]);
|
|
8826
|
+
let usedPercent = null;
|
|
8827
|
+
if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
|
|
8828
|
+
usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
|
|
8829
|
+
} else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
|
|
8830
|
+
usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
|
|
8831
|
+
}
|
|
8832
|
+
if (usedPercent === null && resetsAt === void 0) return null;
|
|
8833
|
+
return [
|
|
8834
|
+
{
|
|
8835
|
+
id: "five-hour",
|
|
8836
|
+
label: "5 hours",
|
|
8837
|
+
scope: "all",
|
|
8838
|
+
usedPercent,
|
|
8839
|
+
windowMinutes: 5 * 60,
|
|
8840
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8841
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8842
|
+
state: "fresh"
|
|
8843
|
+
}
|
|
8844
|
+
];
|
|
8845
|
+
}
|
|
8846
|
+
function parseSyntheticQuotasPayload(payload, now) {
|
|
8847
|
+
if (!isRecord5(payload)) return null;
|
|
8848
|
+
const fiveHour = isRecord5(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
|
|
8849
|
+
const weekly = isRecord5(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
|
|
8850
|
+
const windows = [];
|
|
8851
|
+
if (fiveHour) {
|
|
8852
|
+
const max = finiteNumber3(fiveHour["max"]);
|
|
8853
|
+
const remaining = finiteNumber3(fiveHour["remaining"]);
|
|
8854
|
+
const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
|
|
8855
|
+
const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
|
|
8856
|
+
windows.push({
|
|
8857
|
+
id: "five-hour",
|
|
8858
|
+
label: "5 hours",
|
|
8859
|
+
scope: "all",
|
|
8860
|
+
usedPercent,
|
|
8861
|
+
windowMinutes: 5 * 60,
|
|
8862
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8863
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8864
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
8865
|
+
});
|
|
8866
|
+
}
|
|
8867
|
+
if (weekly) {
|
|
8868
|
+
const percentRemaining = finiteNumber3(weekly["percentRemaining"]);
|
|
8869
|
+
const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
|
|
8870
|
+
const resetsAt = isoInstant3(weekly["nextRegenAt"]);
|
|
8871
|
+
windows.push({
|
|
8872
|
+
id: "seven-day",
|
|
8873
|
+
label: "7 days",
|
|
8874
|
+
scope: "all",
|
|
8875
|
+
usedPercent,
|
|
8876
|
+
windowMinutes: 7 * 24 * 60,
|
|
8877
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8878
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8879
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
8880
|
+
});
|
|
8881
|
+
}
|
|
8882
|
+
return windows.length > 0 ? windows : null;
|
|
8883
|
+
}
|
|
8884
|
+
|
|
8885
|
+
// src/allowance/ProviderKeyQuotaService.ts
|
|
8886
|
+
function parseQuotaPayload(adapter, payload, now) {
|
|
8887
|
+
switch (adapter) {
|
|
8888
|
+
case "zai":
|
|
8889
|
+
return parseZaiQuotaPayload(payload, now);
|
|
8890
|
+
case "minimax-token-plan":
|
|
8891
|
+
return parseMiniMaxTokenPlanPayload(payload, now);
|
|
8892
|
+
case "umans":
|
|
8893
|
+
return parseUmansUsagePayload(payload, now);
|
|
8894
|
+
case "synthetic":
|
|
8895
|
+
return parseSyntheticQuotasPayload(payload, now);
|
|
8896
|
+
}
|
|
8897
|
+
}
|
|
8898
|
+
var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
|
|
8899
|
+
function resolvedBaseUrl(row) {
|
|
8900
|
+
const modes = row.apiModes ?? [];
|
|
8901
|
+
const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
|
|
8902
|
+
const fallback = modes[0];
|
|
8903
|
+
const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
|
|
8904
|
+
return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
|
|
8905
|
+
}
|
|
8906
|
+
function rowKeyEntries(row) {
|
|
8907
|
+
const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
|
|
8908
|
+
if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
|
|
8909
|
+
if (row.apiKey.length > 0) {
|
|
8910
|
+
return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
|
|
8911
|
+
}
|
|
8912
|
+
return [];
|
|
8913
|
+
}
|
|
8914
|
+
var ProviderKeyQuotaService = class {
|
|
8915
|
+
constructor(box, fetchImpl = (url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init, { redactBodies: true }), now = Date.now) {
|
|
8916
|
+
this.box = box;
|
|
8917
|
+
this.fetchImpl = fetchImpl;
|
|
8918
|
+
this.now = now;
|
|
8919
|
+
}
|
|
8920
|
+
box;
|
|
8921
|
+
fetchImpl;
|
|
8922
|
+
now;
|
|
8923
|
+
cache = /* @__PURE__ */ new Map();
|
|
8924
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
8925
|
+
/**
|
|
8926
|
+
* Quota for one key of a provider row, or `null` when the row has no quota
|
|
8927
|
+
* adapter / no such key. Cache-first; concurrent reads share one flight.
|
|
8928
|
+
*/
|
|
8929
|
+
async quotaFor(row, keyId, options = {}) {
|
|
8930
|
+
const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
|
|
8931
|
+
if (!adapter) return null;
|
|
8932
|
+
const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
|
|
8933
|
+
if (!entry) return null;
|
|
8934
|
+
const cacheKey = `${row.id}\0${keyId}`;
|
|
8935
|
+
const now = this.now();
|
|
8936
|
+
const cached = this.cache.get(cacheKey);
|
|
8937
|
+
if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
|
|
8938
|
+
const running = this.inFlight.get(cacheKey);
|
|
8939
|
+
if (running) return running;
|
|
8940
|
+
const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
|
|
8941
|
+
void error;
|
|
8942
|
+
const previous = this.cache.get(cacheKey);
|
|
8943
|
+
if (previous) {
|
|
8944
|
+
const degraded = {
|
|
8945
|
+
...previous,
|
|
8946
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
8947
|
+
windows: previous.windows.map((window) => ({
|
|
8948
|
+
...window,
|
|
8949
|
+
state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
|
|
8950
|
+
})),
|
|
8951
|
+
errorCode: "quota_request_failed"
|
|
8952
|
+
};
|
|
8953
|
+
this.cache.set(cacheKey, degraded);
|
|
8954
|
+
return degraded;
|
|
8955
|
+
}
|
|
8956
|
+
return null;
|
|
8957
|
+
}).finally(() => this.inFlight.delete(cacheKey));
|
|
8958
|
+
this.inFlight.set(cacheKey, promise);
|
|
8959
|
+
return promise;
|
|
8960
|
+
}
|
|
8961
|
+
/** Drop cached rows for a provider (key added/removed/rotated). */
|
|
8962
|
+
invalidateProvider(providerRowId) {
|
|
8963
|
+
for (const key of this.cache.keys()) {
|
|
8964
|
+
if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
|
|
8965
|
+
}
|
|
8966
|
+
}
|
|
8967
|
+
async fetchQuota(adapter, row, rawKey, cacheKey) {
|
|
8968
|
+
const baseUrl = resolvedBaseUrl(row);
|
|
8969
|
+
const url = providerKeyQuotaUrl(adapter, baseUrl);
|
|
8970
|
+
const key = this.box.decryptMaybe(rawKey);
|
|
8971
|
+
const now = this.now();
|
|
8972
|
+
const response = await this.fetchImpl(url, {
|
|
8973
|
+
method: "GET",
|
|
8974
|
+
headers: {
|
|
8975
|
+
Authorization: providerKeyQuotaAuthHeader(adapter, key),
|
|
8976
|
+
Accept: "application/json",
|
|
8977
|
+
"Content-Type": "application/json"
|
|
8978
|
+
},
|
|
8979
|
+
signal: AbortSignal.timeout(15e3)
|
|
8980
|
+
});
|
|
8981
|
+
if (response.status === 401 || response.status === 403) {
|
|
8982
|
+
const snapshot2 = {
|
|
8983
|
+
adapter,
|
|
8984
|
+
observedAt: new Date(now).toISOString(),
|
|
8985
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
8986
|
+
windows: [],
|
|
8987
|
+
errorCode: "quota_unauthorized"
|
|
8988
|
+
};
|
|
8989
|
+
this.cache.set(cacheKey, snapshot2);
|
|
8990
|
+
return snapshot2;
|
|
8991
|
+
}
|
|
8992
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
8993
|
+
let payload;
|
|
8994
|
+
try {
|
|
8995
|
+
payload = await response.json();
|
|
8996
|
+
} catch {
|
|
8997
|
+
throw new Error("invalid JSON");
|
|
8998
|
+
}
|
|
8999
|
+
const windows = parseQuotaPayload(adapter, payload, now);
|
|
9000
|
+
const snapshot = {
|
|
9001
|
+
adapter,
|
|
9002
|
+
observedAt: new Date(now).toISOString(),
|
|
9003
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9004
|
+
windows: windows ?? [],
|
|
9005
|
+
...windows ? {} : { errorCode: "quota_unavailable" }
|
|
9006
|
+
};
|
|
9007
|
+
this.cache.set(cacheKey, snapshot);
|
|
9008
|
+
return snapshot;
|
|
9009
|
+
}
|
|
9010
|
+
};
|
|
9011
|
+
|
|
7753
9012
|
// src/commands/paths.ts
|
|
7754
9013
|
var import_node_path9 = require("path");
|
|
7755
9014
|
function defaultVouchersPath(configPath) {
|
|
@@ -7786,7 +9045,7 @@ function defaultBillingDir(configPath) {
|
|
|
7786
9045
|
// src/image-generation/ImageDoctorService.ts
|
|
7787
9046
|
var import_image_generation = require("@omnicross/core/image-generation");
|
|
7788
9047
|
var import_outbound_api7 = require("@omnicross/core/outbound-api");
|
|
7789
|
-
var
|
|
9048
|
+
var import_subscriptions6 = require("@omnicross/subscriptions");
|
|
7790
9049
|
|
|
7791
9050
|
// src/image-generation/FileCodexImageCapabilityEvidenceSource.ts
|
|
7792
9051
|
var import_node_crypto13 = require("crypto");
|
|
@@ -8224,7 +9483,7 @@ function createImageDoctorService(options) {
|
|
|
8224
9483
|
paths,
|
|
8225
9484
|
ttlMs: config.evidenceTtlMs
|
|
8226
9485
|
}));
|
|
8227
|
-
const createLiveVerifier = options.createLiveVerifier ?? ((strategy, config) => (0,
|
|
9486
|
+
const createLiveVerifier = options.createLiveVerifier ?? ((strategy, config) => (0, import_subscriptions6.createCodexImageLiveVerifier)({
|
|
8228
9487
|
authStrategy: strategy,
|
|
8229
9488
|
generationTimeoutMs: config.queue.generationTimeoutMs
|
|
8230
9489
|
}));
|
|
@@ -8586,7 +9845,7 @@ var ImageCleanupService = class {
|
|
|
8586
9845
|
var import_node_crypto16 = require("crypto");
|
|
8587
9846
|
var import_image_generation5 = require("@omnicross/core/image-generation");
|
|
8588
9847
|
var import_outbound_api8 = require("@omnicross/core/outbound-api");
|
|
8589
|
-
var
|
|
9848
|
+
var import_subscriptions7 = require("@omnicross/subscriptions");
|
|
8590
9849
|
|
|
8591
9850
|
// src/image-generation/ImageApiRuntimeResolver.ts
|
|
8592
9851
|
var import_node_crypto14 = require("crypto");
|
|
@@ -9117,7 +10376,7 @@ function createImageRuntimeGeneration(options) {
|
|
|
9117
10376
|
now: options.now ?? Date.now,
|
|
9118
10377
|
referenceStore: options.storage.referenceStore,
|
|
9119
10378
|
stateStore: options.storage.stateStore
|
|
9120
|
-
}) : (0,
|
|
10379
|
+
}) : (0, import_subscriptions7.createCodexSubscriptionImageProvider)({
|
|
9121
10380
|
authStrategy,
|
|
9122
10381
|
evidenceSource: generationEvidenceSource,
|
|
9123
10382
|
executionScheduler: scheduler,
|
|
@@ -13783,10 +15042,13 @@ function bucketLabel(bucketStartTs, bucket) {
|
|
|
13783
15042
|
}
|
|
13784
15043
|
|
|
13785
15044
|
// src/ports/JsonOutboundKeyDb.ts
|
|
15045
|
+
var import_node_fs19 = require("fs");
|
|
15046
|
+
var import_core3 = require("@omnicross/core");
|
|
15047
|
+
|
|
15048
|
+
// src/ports/atomicFile.ts
|
|
13786
15049
|
var import_node_crypto22 = require("crypto");
|
|
13787
15050
|
var import_node_fs18 = require("fs");
|
|
13788
15051
|
var import_node_path22 = require("path");
|
|
13789
|
-
var import_core3 = require("@omnicross/core");
|
|
13790
15052
|
function atomicReplaceUtf8(targetPath, contents) {
|
|
13791
15053
|
const tempPath = (0, import_node_path22.join)(
|
|
13792
15054
|
(0, import_node_path22.dirname)(targetPath),
|
|
@@ -13816,6 +15078,8 @@ function atomicReplaceUtf8(targetPath, contents) {
|
|
|
13816
15078
|
throw error;
|
|
13817
15079
|
}
|
|
13818
15080
|
}
|
|
15081
|
+
|
|
15082
|
+
// src/ports/JsonOutboundKeyDb.ts
|
|
13819
15083
|
var JsonOutboundKeyDb = class {
|
|
13820
15084
|
/**
|
|
13821
15085
|
* @param secretBox OPTIONAL reversible-secret codec. When present, a created
|
|
@@ -13958,9 +15222,9 @@ var JsonOutboundKeyDb = class {
|
|
|
13958
15222
|
}
|
|
13959
15223
|
/** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
|
|
13960
15224
|
readRows() {
|
|
13961
|
-
if (!(0,
|
|
15225
|
+
if (!(0, import_node_fs19.existsSync)(this.keysPath)) return [];
|
|
13962
15226
|
try {
|
|
13963
|
-
const parsed = JSON.parse((0,
|
|
15227
|
+
const parsed = JSON.parse((0, import_node_fs19.readFileSync)(this.keysPath, "utf8"));
|
|
13964
15228
|
return Array.isArray(parsed) ? parsed : [];
|
|
13965
15229
|
} catch {
|
|
13966
15230
|
return [];
|
|
@@ -13977,7 +15241,7 @@ function applyPolicyField(row, field, value) {
|
|
|
13977
15241
|
}
|
|
13978
15242
|
|
|
13979
15243
|
// src/ports/JsonPricingStore.ts
|
|
13980
|
-
var
|
|
15244
|
+
var import_node_fs20 = require("fs");
|
|
13981
15245
|
var import_node_crypto23 = require("crypto");
|
|
13982
15246
|
var JsonPricingStore = class {
|
|
13983
15247
|
constructor(pricingPath) {
|
|
@@ -13992,9 +15256,9 @@ var JsonPricingStore = class {
|
|
|
13992
15256
|
* otherwise unusable pricing table after a crash or manual file edit.
|
|
13993
15257
|
*/
|
|
13994
15258
|
hasUsableSnapshot() {
|
|
13995
|
-
if (!(0,
|
|
15259
|
+
if (!(0, import_node_fs20.existsSync)(this.pricingPath)) return false;
|
|
13996
15260
|
try {
|
|
13997
|
-
const parsed = JSON.parse((0,
|
|
15261
|
+
const parsed = JSON.parse((0, import_node_fs20.readFileSync)(this.pricingPath, "utf8"));
|
|
13998
15262
|
return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
|
|
13999
15263
|
} catch {
|
|
14000
15264
|
return false;
|
|
@@ -14107,9 +15371,9 @@ var JsonPricingStore = class {
|
|
|
14107
15371
|
}
|
|
14108
15372
|
/** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14109
15373
|
readRows() {
|
|
14110
|
-
if (!(0,
|
|
15374
|
+
if (!(0, import_node_fs20.existsSync)(this.pricingPath)) return [];
|
|
14111
15375
|
try {
|
|
14112
|
-
const parsed = JSON.parse((0,
|
|
15376
|
+
const parsed = JSON.parse((0, import_node_fs20.readFileSync)(this.pricingPath, "utf8"));
|
|
14113
15377
|
return Array.isArray(parsed) ? parsed : [];
|
|
14114
15378
|
} catch {
|
|
14115
15379
|
return [];
|
|
@@ -14118,18 +15382,18 @@ var JsonPricingStore = class {
|
|
|
14118
15382
|
writeRows(rows) {
|
|
14119
15383
|
const temporaryPath = `${this.pricingPath}.${process.pid}.${(0, import_node_crypto23.randomUUID)()}.tmp`;
|
|
14120
15384
|
try {
|
|
14121
|
-
(0,
|
|
15385
|
+
(0, import_node_fs20.writeFileSync)(temporaryPath, JSON.stringify(rows, null, 2) + "\n", {
|
|
14122
15386
|
encoding: "utf8",
|
|
14123
15387
|
flag: "wx"
|
|
14124
15388
|
});
|
|
14125
15389
|
this.replaceFile(temporaryPath);
|
|
14126
15390
|
} finally {
|
|
14127
|
-
(0,
|
|
15391
|
+
(0, import_node_fs20.rmSync)(temporaryPath, { force: true });
|
|
14128
15392
|
}
|
|
14129
15393
|
}
|
|
14130
15394
|
/** Isolated for deterministic failure testing; never removes the target. */
|
|
14131
15395
|
replaceFile(temporaryPath) {
|
|
14132
|
-
(0,
|
|
15396
|
+
(0, import_node_fs20.renameSync)(temporaryPath, this.pricingPath);
|
|
14133
15397
|
}
|
|
14134
15398
|
};
|
|
14135
15399
|
function isUsablePricingRow(value) {
|
|
@@ -14139,7 +15403,7 @@ function isUsablePricingRow(value) {
|
|
|
14139
15403
|
}
|
|
14140
15404
|
|
|
14141
15405
|
// src/pricing/PricingRefreshScheduler.ts
|
|
14142
|
-
var
|
|
15406
|
+
var import_node_fs21 = require("fs");
|
|
14143
15407
|
var EMPTY_STATE2 = {
|
|
14144
15408
|
lastAttemptAt: null,
|
|
14145
15409
|
lastSuccessAt: null,
|
|
@@ -14177,9 +15441,9 @@ var PricingRefreshScheduler = class {
|
|
|
14177
15441
|
this.timer = null;
|
|
14178
15442
|
}
|
|
14179
15443
|
getState() {
|
|
14180
|
-
if (!(0,
|
|
15444
|
+
if (!(0, import_node_fs21.existsSync)(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
|
|
14181
15445
|
try {
|
|
14182
|
-
const value = JSON.parse((0,
|
|
15446
|
+
const value = JSON.parse((0, import_node_fs21.readFileSync)(this.statePath, "utf8"));
|
|
14183
15447
|
return {
|
|
14184
15448
|
lastAttemptAt: finiteOrNull(value.lastAttemptAt),
|
|
14185
15449
|
lastSuccessAt: finiteOrNull(value.lastSuccessAt),
|
|
@@ -14232,9 +15496,9 @@ var PricingRefreshScheduler = class {
|
|
|
14232
15496
|
}
|
|
14233
15497
|
writeState(state) {
|
|
14234
15498
|
const temporaryPath = `${this.statePath}.tmp`;
|
|
14235
|
-
(0,
|
|
15499
|
+
(0, import_node_fs21.writeFileSync)(temporaryPath, `${JSON.stringify(state, null, 2)}
|
|
14236
15500
|
`, "utf8");
|
|
14237
|
-
(0,
|
|
15501
|
+
(0, import_node_fs21.renameSync)(temporaryPath, this.statePath);
|
|
14238
15502
|
}
|
|
14239
15503
|
};
|
|
14240
15504
|
function finiteOrNull(value) {
|
|
@@ -14242,7 +15506,7 @@ function finiteOrNull(value) {
|
|
|
14242
15506
|
}
|
|
14243
15507
|
|
|
14244
15508
|
// src/ports/JsonVoucherDb.ts
|
|
14245
|
-
var
|
|
15509
|
+
var import_node_fs22 = require("fs");
|
|
14246
15510
|
var JsonVoucherDb = class {
|
|
14247
15511
|
constructor(vouchersPath) {
|
|
14248
15512
|
this.vouchersPath = vouchersPath;
|
|
@@ -14320,27 +15584,27 @@ var JsonVoucherDb = class {
|
|
|
14320
15584
|
}
|
|
14321
15585
|
/** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14322
15586
|
readRows() {
|
|
14323
|
-
if (!(0,
|
|
15587
|
+
if (!(0, import_node_fs22.existsSync)(this.vouchersPath)) return [];
|
|
14324
15588
|
try {
|
|
14325
|
-
const parsed = JSON.parse((0,
|
|
15589
|
+
const parsed = JSON.parse((0, import_node_fs22.readFileSync)(this.vouchersPath, "utf8"));
|
|
14326
15590
|
return Array.isArray(parsed) ? parsed : [];
|
|
14327
15591
|
} catch {
|
|
14328
15592
|
return [];
|
|
14329
15593
|
}
|
|
14330
15594
|
}
|
|
14331
15595
|
writeRows(rows) {
|
|
14332
|
-
(0,
|
|
15596
|
+
(0, import_node_fs22.writeFileSync)(this.vouchersPath, JSON.stringify(rows, null, 2) + "\n", "utf8");
|
|
14333
15597
|
}
|
|
14334
15598
|
};
|
|
14335
15599
|
|
|
14336
15600
|
// src/ports/JsonSubscriptionCredentialStore.ts
|
|
14337
|
-
var
|
|
15601
|
+
var import_node_fs24 = require("fs");
|
|
14338
15602
|
var import_node_path24 = require("path");
|
|
14339
15603
|
var import_SubscriptionAccountHealth2 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
|
|
14340
15604
|
var import_AccountAllowanceScheduling3 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
|
|
14341
|
-
var
|
|
15605
|
+
var import_upstreamFetch9 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
14342
15606
|
var import_SubscriptionIdentityStore2 = require("@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore");
|
|
14343
|
-
var
|
|
15607
|
+
var import_subscriptions8 = require("@omnicross/subscriptions");
|
|
14344
15608
|
|
|
14345
15609
|
// src/ports/account-sync.ts
|
|
14346
15610
|
function viewOf(tokens) {
|
|
@@ -14384,7 +15648,7 @@ function findDuplicateCredentialIds(accounts) {
|
|
|
14384
15648
|
}
|
|
14385
15649
|
|
|
14386
15650
|
// src/ports/external-cli-credentials.ts
|
|
14387
|
-
var
|
|
15651
|
+
var import_node_fs23 = require("fs");
|
|
14388
15652
|
var import_node_os5 = require("os");
|
|
14389
15653
|
var import_node_path23 = require("path");
|
|
14390
15654
|
function externalStorePath(provider, home = (0, import_node_os5.homedir)()) {
|
|
@@ -14437,10 +15701,10 @@ function parseCodexTokensEnvelope(raw) {
|
|
|
14437
15701
|
}
|
|
14438
15702
|
function readExternalCliCredentials(provider, home = (0, import_node_os5.homedir)()) {
|
|
14439
15703
|
const path2 = externalStorePath(provider, home);
|
|
14440
|
-
if (!(0,
|
|
15704
|
+
if (!(0, import_node_fs23.existsSync)(path2)) return null;
|
|
14441
15705
|
let raw;
|
|
14442
15706
|
try {
|
|
14443
|
-
const parsed = JSON.parse((0,
|
|
15707
|
+
const parsed = JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8"));
|
|
14444
15708
|
raw = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
14445
15709
|
} catch {
|
|
14446
15710
|
return null;
|
|
@@ -14463,16 +15727,18 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14463
15727
|
* as on relay refresh egresses from the SAME proxy IP as the
|
|
14464
15728
|
* account's traffic. NOT used by any read/write path.
|
|
14465
15729
|
*/
|
|
14466
|
-
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
|
|
15730
|
+
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
|
|
14467
15731
|
this.tokensPath = tokensPath;
|
|
14468
15732
|
this.box = box;
|
|
14469
15733
|
this.fetchImpl = fetchImpl;
|
|
14470
15734
|
this.externalCliReader = externalCliReader;
|
|
15735
|
+
this.atomicReplace = atomicReplace;
|
|
14471
15736
|
}
|
|
14472
15737
|
tokensPath;
|
|
14473
15738
|
box;
|
|
14474
15739
|
fetchImpl;
|
|
14475
15740
|
externalCliReader;
|
|
15741
|
+
atomicReplace;
|
|
14476
15742
|
/**
|
|
14477
15743
|
* The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
|
|
14478
15744
|
* TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
|
|
@@ -14486,7 +15752,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14486
15752
|
* a plaintext token pair into `upstream-trace.jsonl`.
|
|
14487
15753
|
*/
|
|
14488
15754
|
buildRefreshFetch(providerId, accountId) {
|
|
14489
|
-
return this.fetchImpl ?? ((url, init) => (0,
|
|
15755
|
+
return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init, { providerId, accountId, redactBodies: true }));
|
|
14490
15756
|
}
|
|
14491
15757
|
/**
|
|
14492
15758
|
* In-flight refresh coalescing. OAuth refresh tokens are
|
|
@@ -14527,7 +15793,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14527
15793
|
* other hot reads. Never returns token material.
|
|
14528
15794
|
*/
|
|
14529
15795
|
getAccountProxy(providerId, accountId) {
|
|
14530
|
-
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
|
|
15796
|
+
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
|
|
14531
15797
|
return void 0;
|
|
14532
15798
|
}
|
|
14533
15799
|
return getAccountProxy(this.readConfig(), providerId, accountId);
|
|
@@ -14546,7 +15812,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14546
15812
|
const fingerprintOn = identityStore.isEnabled();
|
|
14547
15813
|
const now = Date.now();
|
|
14548
15814
|
const out = {};
|
|
14549
|
-
for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
|
|
15815
|
+
for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
|
|
14550
15816
|
const sanitized = sanitizeAccounts(config, provider);
|
|
14551
15817
|
if (sanitized.length === 0) continue;
|
|
14552
15818
|
for (const account of sanitized) {
|
|
@@ -14612,7 +15878,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14612
15878
|
this.materializeMigration(config);
|
|
14613
15879
|
const refreshFetch = this.buildRefreshFetch("claude", capturedId);
|
|
14614
15880
|
try {
|
|
14615
|
-
const result = await
|
|
15881
|
+
const result = await import_subscriptions8.claudeOAuth.refreshAccessToken(claude.refreshToken, refreshFetch);
|
|
14616
15882
|
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
14617
15883
|
const next = {
|
|
14618
15884
|
...claude,
|
|
@@ -14647,7 +15913,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14647
15913
|
this.materializeMigration(config);
|
|
14648
15914
|
const refreshFetch = this.buildRefreshFetch("codex", capturedId);
|
|
14649
15915
|
try {
|
|
14650
|
-
const result = await
|
|
15916
|
+
const result = await import_subscriptions8.codexOAuth.refreshAccessToken(codex.refreshToken, refreshFetch);
|
|
14651
15917
|
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
14652
15918
|
const next = {
|
|
14653
15919
|
...codex,
|
|
@@ -14685,7 +15951,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14685
15951
|
this.materializeMigration(config);
|
|
14686
15952
|
const refreshFetch = this.buildRefreshFetch("gemini", capturedId);
|
|
14687
15953
|
try {
|
|
14688
|
-
const result = await
|
|
15954
|
+
const result = await import_subscriptions8.geminiOAuth.refreshAccessToken(gemini.refreshToken, refreshFetch);
|
|
14689
15955
|
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
14690
15956
|
const next = {
|
|
14691
15957
|
...gemini,
|
|
@@ -14704,6 +15970,47 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14704
15970
|
}
|
|
14705
15971
|
});
|
|
14706
15972
|
}
|
|
15973
|
+
/**
|
|
15974
|
+
* Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
|
|
15975
|
+
* Kimi ROTATES the refresh token, so the response's pair is written back
|
|
15976
|
+
* whole; the account's stable `deviceId` (fingerprint header input) is
|
|
15977
|
+
* preserved. The refresh call carries the CLI fingerprint headers. HONEST
|
|
15978
|
+
* `false` when no refresh_token.
|
|
15979
|
+
*/
|
|
15980
|
+
async refreshKimiToken() {
|
|
15981
|
+
return this.coalesce("kimi:active", async () => {
|
|
15982
|
+
const config = this.readConfig();
|
|
15983
|
+
const active = getActiveAccount(config, "kimi");
|
|
15984
|
+
const kimi = active?.tokens;
|
|
15985
|
+
if (!active || !kimi?.refreshToken) return false;
|
|
15986
|
+
const capturedId = active.id;
|
|
15987
|
+
this.materializeMigration(config);
|
|
15988
|
+
const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
|
|
15989
|
+
try {
|
|
15990
|
+
const result = await import_subscriptions8.kimiOAuth.refreshAccessToken(
|
|
15991
|
+
kimi.refreshToken,
|
|
15992
|
+
refreshFetch,
|
|
15993
|
+
import_subscriptions8.kimiOAuth.kimiFingerprintHeaders(kimi.deviceId)
|
|
15994
|
+
);
|
|
15995
|
+
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
15996
|
+
const next = {
|
|
15997
|
+
...kimi,
|
|
15998
|
+
accessToken: result.accessToken,
|
|
15999
|
+
refreshToken: result.refreshToken,
|
|
16000
|
+
expiresAt,
|
|
16001
|
+
status: "authorized",
|
|
16002
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16003
|
+
errorMessage: void 0,
|
|
16004
|
+
syncWarning: void 0
|
|
16005
|
+
};
|
|
16006
|
+
this.writeBackById("kimi", capturedId, next);
|
|
16007
|
+
return true;
|
|
16008
|
+
} catch (error) {
|
|
16009
|
+
this.markExpiredById("kimi", capturedId, kimi, error);
|
|
16010
|
+
return false;
|
|
16011
|
+
}
|
|
16012
|
+
});
|
|
16013
|
+
}
|
|
14707
16014
|
/**
|
|
14708
16015
|
* Refresh a SPECIFIC managed account by id (background scheduler sweep and
|
|
14709
16016
|
* account-pool resolution). It uses only that account's stored refresh
|
|
@@ -14756,7 +16063,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14756
16063
|
}
|
|
14757
16064
|
const oauth = account.tokens;
|
|
14758
16065
|
if (!oauth.accessToken) return null;
|
|
14759
|
-
if (providerId === "codex" || providerId === "gemini") {
|
|
16066
|
+
if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
|
|
14760
16067
|
const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
|
|
14761
16068
|
const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
|
|
14762
16069
|
if (expiringSoon && oauth.refreshToken) {
|
|
@@ -14845,8 +16152,23 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14845
16152
|
}
|
|
14846
16153
|
/** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
|
|
14847
16154
|
async refreshUpstream(provider, refreshToken, accountId) {
|
|
14848
|
-
const
|
|
14849
|
-
|
|
16155
|
+
const refreshFetch = this.buildRefreshFetch(provider, accountId);
|
|
16156
|
+
if (provider === "kimi") {
|
|
16157
|
+
const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
|
|
16158
|
+
const deviceId = account?.tokens?.deviceId;
|
|
16159
|
+
const r2 = await import_subscriptions8.kimiOAuth.refreshAccessToken(
|
|
16160
|
+
refreshToken,
|
|
16161
|
+
refreshFetch,
|
|
16162
|
+
import_subscriptions8.kimiOAuth.kimiFingerprintHeaders(deviceId)
|
|
16163
|
+
);
|
|
16164
|
+
return {
|
|
16165
|
+
accessToken: r2.accessToken,
|
|
16166
|
+
refreshToken: r2.refreshToken,
|
|
16167
|
+
expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
|
|
16168
|
+
};
|
|
16169
|
+
}
|
|
16170
|
+
const flow = provider === "claude" ? import_subscriptions8.claudeOAuth : provider === "codex" ? import_subscriptions8.codexOAuth : import_subscriptions8.geminiOAuth;
|
|
16171
|
+
const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
|
|
14850
16172
|
return {
|
|
14851
16173
|
accessToken: r.accessToken,
|
|
14852
16174
|
refreshToken: r.refreshToken,
|
|
@@ -15009,42 +16331,86 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15009
16331
|
/** Write the merged config to disk as pretty JSON (mkdir parent if needed).
|
|
15010
16332
|
* Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
|
|
15011
16333
|
* `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
|
|
15012
|
-
* write incl. child 4's future refresh writes lands encrypted.
|
|
16334
|
+
* write incl. child 4's future refresh writes lands encrypted.
|
|
16335
|
+
* ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
|
|
16336
|
+
* interrupted write discards only the temp file; the prior `tokens.json`
|
|
16337
|
+
* survives byte-equal (bare `writeFileSync` truncate-writes lost every
|
|
16338
|
+
* account on a mid-write failure, 2026-09-06). */
|
|
15013
16339
|
persist(config) {
|
|
15014
|
-
(0,
|
|
16340
|
+
(0, import_node_fs24.mkdirSync)((0, import_node_path24.dirname)(this.tokensPath), { recursive: true });
|
|
15015
16341
|
const encrypted = encryptTokens(config, this.box);
|
|
15016
|
-
|
|
16342
|
+
this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
|
|
15017
16343
|
}
|
|
15018
16344
|
/**
|
|
15019
|
-
* Read + parse `tokens.json`,
|
|
15020
|
-
*
|
|
15021
|
-
*
|
|
16345
|
+
* Read + parse `tokens.json`, then DECRYPT the token-material fields so every
|
|
16346
|
+
* getter returns plaintext (the subscription bearer path is byte-identical).
|
|
16347
|
+
*
|
|
16348
|
+
* A MISSING file is a legitimate first-boot state → minimal `{ updatedAt: '' }`.
|
|
16349
|
+
* A file that EXISTS but cannot be parsed as a JSON object is CORRUPT →
|
|
16350
|
+
* `quarantineCorrupt` moves it aside (once) before the empty config is
|
|
16351
|
+
* returned, so the unreadable accounts survive for manual recovery.
|
|
15022
16352
|
*
|
|
15023
|
-
* The
|
|
15024
|
-
*
|
|
15025
|
-
*
|
|
15026
|
-
*
|
|
15027
|
-
*
|
|
15028
|
-
*
|
|
15029
|
-
* `config.ts loadConfig`, which decrypts outside its parse try.
|
|
16353
|
+
* The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
|
|
16354
|
+
* tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
|
|
16355
|
+
* error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
|
|
16356
|
+
* decrypt would report "no tokens" and silently send the WRONG bearer
|
|
16357
|
+
* upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
|
|
16358
|
+
* its parse try.
|
|
15030
16359
|
*/
|
|
15031
16360
|
readConfig() {
|
|
15032
|
-
if (!(0,
|
|
16361
|
+
if (!(0, import_node_fs24.existsSync)(this.tokensPath)) return { updatedAt: "" };
|
|
15033
16362
|
let parsed;
|
|
15034
16363
|
try {
|
|
15035
|
-
const raw = JSON.parse((0,
|
|
15036
|
-
|
|
16364
|
+
const raw = JSON.parse((0, import_node_fs24.readFileSync)(this.tokensPath, "utf8"));
|
|
16365
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
16366
|
+
return this.quarantineCorrupt("parsed JSON is not an object");
|
|
16367
|
+
}
|
|
16368
|
+
parsed = raw;
|
|
15037
16369
|
} catch {
|
|
15038
|
-
|
|
16370
|
+
return this.quarantineCorrupt("unparseable JSON");
|
|
15039
16371
|
}
|
|
15040
|
-
if (!parsed) return { updatedAt: "" };
|
|
15041
16372
|
const decrypted = decryptTokens(parsed, this.box);
|
|
15042
16373
|
return migrateLazily(decrypted);
|
|
15043
16374
|
}
|
|
16375
|
+
/** One-shot latch: a corrupt file is quarantined (or found unmovable) at
|
|
16376
|
+
* most once per process, so the hot read path never re-attempts or re-logs. */
|
|
16377
|
+
corruptQuarantined = false;
|
|
16378
|
+
/**
|
|
16379
|
+
* Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
|
|
16380
|
+
*
|
|
16381
|
+
* Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
|
|
16382
|
+
* logs loudly (the daemon's stderr log; secret-free — reason + paths only).
|
|
16383
|
+
* The daemon KEEPS SERVING (API-key routing is unaffected; subscription
|
|
16384
|
+
* routing reports no credential, same as an absent file) while the corrupt
|
|
16385
|
+
* bytes survive for manual recovery — and, critically, the NEXT persist
|
|
16386
|
+
* (e.g. the user re-logging in) can no longer overwrite the only copy of
|
|
16387
|
+
* the old accounts, which is exactly how the 2026-09-06 incident turned a
|
|
16388
|
+
* recoverable truncated file into permanent account loss.
|
|
16389
|
+
*
|
|
16390
|
+
* Best-effort: if the rename fails (file locked, permissions), the corrupt
|
|
16391
|
+
* file is left in place and every later read still tolerates it as empty;
|
|
16392
|
+
* the latch still trips so the attempt + log happen exactly once.
|
|
16393
|
+
*/
|
|
16394
|
+
quarantineCorrupt(reason) {
|
|
16395
|
+
if (!this.corruptQuarantined) {
|
|
16396
|
+
this.corruptQuarantined = true;
|
|
16397
|
+
const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
16398
|
+
let moved = false;
|
|
16399
|
+
try {
|
|
16400
|
+
(0, import_node_fs24.renameSync)(this.tokensPath, backup);
|
|
16401
|
+
moved = true;
|
|
16402
|
+
} catch {
|
|
16403
|
+
}
|
|
16404
|
+
console.error(
|
|
16405
|
+
`[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`)
|
|
16406
|
+
);
|
|
16407
|
+
}
|
|
16408
|
+
return { updatedAt: "" };
|
|
16409
|
+
}
|
|
15044
16410
|
};
|
|
15045
16411
|
|
|
15046
16412
|
// src/AccountHealthProbeScheduler.ts
|
|
15047
|
-
var
|
|
16413
|
+
var import_upstreamFetch10 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
15048
16414
|
|
|
15049
16415
|
// src/probe/CodexGenerationProbe.ts
|
|
15050
16416
|
var import_codexCliHeaders = require("@omnicross/core/provider-proxy/identity/codexCliHeaders");
|
|
@@ -15183,7 +16549,11 @@ var PROVIDER_PROBE_PLANS = {
|
|
|
15183
16549
|
// billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
|
|
15184
16550
|
codex: { kind: "local" },
|
|
15185
16551
|
gemini: { kind: "local" },
|
|
15186
|
-
opencodego: { kind: "local" }
|
|
16552
|
+
opencodego: { kind: "local" },
|
|
16553
|
+
// Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
|
|
16554
|
+
// collector uses it), but the probe path also needs the fingerprint headers —
|
|
16555
|
+
// keep the probe local until the collector covers the health surface.
|
|
16556
|
+
kimi: { kind: "local" }
|
|
15187
16557
|
};
|
|
15188
16558
|
function probePlanFor(providerId) {
|
|
15189
16559
|
return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
|
|
@@ -15205,7 +16575,7 @@ var AccountHealthProbeScheduler = class {
|
|
|
15205
16575
|
this.logger = logger;
|
|
15206
16576
|
this.config = config;
|
|
15207
16577
|
this.now = opts.now ?? Date.now;
|
|
15208
|
-
this.fetchImpl = opts.fetchImpl ??
|
|
16578
|
+
this.fetchImpl = opts.fetchImpl ?? import_upstreamFetch10.fetchUpstream;
|
|
15209
16579
|
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
15210
16580
|
this.planFor = opts.planFor ?? probePlanFor;
|
|
15211
16581
|
}
|
|
@@ -15549,13 +16919,13 @@ var AccountHealthSweeper = class {
|
|
|
15549
16919
|
};
|
|
15550
16920
|
|
|
15551
16921
|
// src/audit/AuditPruneSweeper.ts
|
|
15552
|
-
var
|
|
16922
|
+
var import_node_fs27 = require("fs");
|
|
15553
16923
|
var import_node_path27 = require("path");
|
|
15554
16924
|
var import_promises6 = require("stream/promises");
|
|
15555
16925
|
var import_node_zlib = require("zlib");
|
|
15556
16926
|
|
|
15557
16927
|
// src/audit/auditDictionary.ts
|
|
15558
|
-
var
|
|
16928
|
+
var import_node_fs25 = require("fs");
|
|
15559
16929
|
var import_node_path25 = require("path");
|
|
15560
16930
|
|
|
15561
16931
|
// src/audit/auditBodyStore.ts
|
|
@@ -15788,7 +17158,7 @@ function parseEntries(raw) {
|
|
|
15788
17158
|
}
|
|
15789
17159
|
function plainShards(bodiesPath) {
|
|
15790
17160
|
try {
|
|
15791
|
-
return (0,
|
|
17161
|
+
return (0, import_node_fs25.readdirSync)(bodiesPath).filter(
|
|
15792
17162
|
(file) => file.endsWith(".jsonl") && isSafeSessionKey(file.slice(0, -".jsonl".length))
|
|
15793
17163
|
);
|
|
15794
17164
|
} catch {
|
|
@@ -15813,9 +17183,9 @@ function chooseDictionary(anchors) {
|
|
|
15813
17183
|
var EMPTY = { shards: 0, anchors: 0, savedBytes: 0 };
|
|
15814
17184
|
function compactAuditDay(dayPath) {
|
|
15815
17185
|
const bodiesPath = (0, import_node_path25.join)(dayPath, AUDIT_BODIES_DIR);
|
|
15816
|
-
if (!(0,
|
|
17186
|
+
if (!(0, import_node_fs25.existsSync)(bodiesPath)) return EMPTY;
|
|
15817
17187
|
const dictPath = (0, import_node_path25.join)(bodiesPath, AUDIT_DICT_FILE);
|
|
15818
|
-
if ((0,
|
|
17188
|
+
if ((0, import_node_fs25.existsSync)(dictPath) || (0, import_node_fs25.existsSync)(`${dictPath}.gz`)) return EMPTY;
|
|
15819
17189
|
const shardFiles = plainShards(bodiesPath);
|
|
15820
17190
|
if (shardFiles.length < 2) return EMPTY;
|
|
15821
17191
|
const loaded = /* @__PURE__ */ new Map();
|
|
@@ -15823,7 +17193,7 @@ function compactAuditDay(dayPath) {
|
|
|
15823
17193
|
for (const file of shardFiles) {
|
|
15824
17194
|
let entries;
|
|
15825
17195
|
try {
|
|
15826
|
-
entries = parseEntries((0,
|
|
17196
|
+
entries = parseEntries((0, import_node_fs25.readFileSync)((0, import_node_path25.join)(bodiesPath, file), "utf8"));
|
|
15827
17197
|
} catch {
|
|
15828
17198
|
continue;
|
|
15829
17199
|
}
|
|
@@ -15840,7 +17210,7 @@ function compactAuditDay(dayPath) {
|
|
|
15840
17210
|
ts: 0,
|
|
15841
17211
|
req: { base: null, anchor: "dict", pre: 0, suf: 0, ins: dictionary }
|
|
15842
17212
|
};
|
|
15843
|
-
(0,
|
|
17213
|
+
(0, import_node_fs25.writeFileSync)(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
|
|
15844
17214
|
const result = { shards: 0, anchors: 0, savedBytes: 0 };
|
|
15845
17215
|
for (const [file, entries] of loaded) {
|
|
15846
17216
|
let changed = false;
|
|
@@ -15860,11 +17230,11 @@ function compactAuditDay(dayPath) {
|
|
|
15860
17230
|
const target = (0, import_node_path25.join)(bodiesPath, file);
|
|
15861
17231
|
const temp = `${target}.compacting`;
|
|
15862
17232
|
try {
|
|
15863
|
-
(0,
|
|
15864
|
-
(0,
|
|
17233
|
+
(0, import_node_fs25.writeFileSync)(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
|
|
17234
|
+
(0, import_node_fs25.renameSync)(temp, target);
|
|
15865
17235
|
} catch {
|
|
15866
17236
|
try {
|
|
15867
|
-
if ((0,
|
|
17237
|
+
if ((0, import_node_fs25.existsSync)(temp)) (0, import_node_fs25.unlinkSync)(temp);
|
|
15868
17238
|
} catch {
|
|
15869
17239
|
}
|
|
15870
17240
|
continue;
|
|
@@ -15875,7 +17245,7 @@ function compactAuditDay(dayPath) {
|
|
|
15875
17245
|
}
|
|
15876
17246
|
if (result.shards === 0) {
|
|
15877
17247
|
try {
|
|
15878
|
-
(0,
|
|
17248
|
+
(0, import_node_fs25.unlinkSync)(dictPath);
|
|
15879
17249
|
} catch {
|
|
15880
17250
|
}
|
|
15881
17251
|
}
|
|
@@ -15883,11 +17253,11 @@ function compactAuditDay(dayPath) {
|
|
|
15883
17253
|
}
|
|
15884
17254
|
function compactAllClosedAuditDays(auditDir, now = Date.now) {
|
|
15885
17255
|
const run = { days: 0, shards: 0, savedBytes: 0 };
|
|
15886
|
-
if (!(0,
|
|
17256
|
+
if (!(0, import_node_fs25.existsSync)(auditDir)) return run;
|
|
15887
17257
|
const today = auditDayDirName(now());
|
|
15888
17258
|
let names;
|
|
15889
17259
|
try {
|
|
15890
|
-
names = (0,
|
|
17260
|
+
names = (0, import_node_fs25.readdirSync)(auditDir).filter(isAuditDayDir).sort();
|
|
15891
17261
|
} catch {
|
|
15892
17262
|
return run;
|
|
15893
17263
|
}
|
|
@@ -15906,7 +17276,7 @@ function compactAllClosedAuditDays(auditDir, now = Date.now) {
|
|
|
15906
17276
|
}
|
|
15907
17277
|
|
|
15908
17278
|
// src/audit/auditStats.ts
|
|
15909
|
-
var
|
|
17279
|
+
var import_node_fs26 = require("fs");
|
|
15910
17280
|
var import_node_path26 = require("path");
|
|
15911
17281
|
var SIDECAR_VERSION = 1;
|
|
15912
17282
|
var META_PREFIX_BYTES = 64 * 1024;
|
|
@@ -15915,9 +17285,9 @@ function auditStatsFileName(auditFile) {
|
|
|
15915
17285
|
return auditFile.replace(/\.jsonl$/, ".stats.json");
|
|
15916
17286
|
}
|
|
15917
17287
|
function readPersisted(path2) {
|
|
15918
|
-
if (!(0,
|
|
17288
|
+
if (!(0, import_node_fs26.existsSync)(path2)) return null;
|
|
15919
17289
|
try {
|
|
15920
|
-
const value = JSON.parse((0,
|
|
17290
|
+
const value = JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
|
|
15921
17291
|
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)) {
|
|
15922
17292
|
return null;
|
|
15923
17293
|
}
|
|
@@ -15947,7 +17317,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
|
|
|
15947
17317
|
minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
|
|
15948
17318
|
maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
|
|
15949
17319
|
};
|
|
15950
|
-
(0,
|
|
17320
|
+
(0, import_node_fs26.writeFileSync)(statsPath, JSON.stringify(next), "utf8");
|
|
15951
17321
|
}
|
|
15952
17322
|
function queryCovers(stats, from, to) {
|
|
15953
17323
|
return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
|
|
@@ -16005,7 +17375,7 @@ async function scanAuditFile(auditPath, startByte, auditBytes, from, to) {
|
|
|
16005
17375
|
prefixTruncated = false;
|
|
16006
17376
|
};
|
|
16007
17377
|
if (auditBytes > startByte) {
|
|
16008
|
-
const stream = (0,
|
|
17378
|
+
const stream = (0, import_node_fs26.createReadStream)(auditPath, {
|
|
16009
17379
|
start: startByte,
|
|
16010
17380
|
end: auditBytes - 1,
|
|
16011
17381
|
highWaterMark: READ_CHUNK_BYTES2
|
|
@@ -16058,12 +17428,12 @@ function mergePersistedStats(previous, appended) {
|
|
|
16058
17428
|
};
|
|
16059
17429
|
}
|
|
16060
17430
|
async function readAuditStats(auditDir, query2 = {}) {
|
|
16061
|
-
if (!(0,
|
|
17431
|
+
if (!(0, import_node_fs26.existsSync)(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
|
|
16062
17432
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
16063
17433
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
16064
17434
|
let sources;
|
|
16065
17435
|
try {
|
|
16066
|
-
sources = (0,
|
|
17436
|
+
sources = (0, import_node_fs26.readdirSync)(auditDir).filter((name) => fileOverlaps(name, from, to)).sort().map(
|
|
16067
17437
|
(name) => AUDIT_DAY_DIR_RE.test(name) ? {
|
|
16068
17438
|
auditPath: (0, import_node_path26.join)(auditDir, name, AUDIT_META_FILE),
|
|
16069
17439
|
statsPath: (0, import_node_path26.join)(auditDir, name, auditStatsFileName(AUDIT_META_FILE))
|
|
@@ -16071,14 +17441,14 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16071
17441
|
auditPath: (0, import_node_path26.join)(auditDir, name),
|
|
16072
17442
|
statsPath: (0, import_node_path26.join)(auditDir, auditStatsFileName(name))
|
|
16073
17443
|
}
|
|
16074
|
-
).filter((source) => (0,
|
|
17444
|
+
).filter((source) => (0, import_node_fs26.existsSync)(source.auditPath));
|
|
16075
17445
|
} catch {
|
|
16076
17446
|
return { requestCount: 0, errorCount: 0, complete: false };
|
|
16077
17447
|
}
|
|
16078
17448
|
const total = { requestCount: 0, errorCount: 0, complete: true };
|
|
16079
17449
|
for (const { auditPath, statsPath } of sources) {
|
|
16080
17450
|
try {
|
|
16081
|
-
const auditBytes = (0,
|
|
17451
|
+
const auditBytes = (0, import_node_fs26.statSync)(auditPath).size;
|
|
16082
17452
|
const persisted = readPersisted(statsPath);
|
|
16083
17453
|
if (persisted && persisted.complete && persisted.auditBytes === auditBytes && queryCovers(persisted, from, to)) {
|
|
16084
17454
|
total.requestCount += persisted.requestCount;
|
|
@@ -16097,7 +17467,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16097
17467
|
total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
|
|
16098
17468
|
total.complete = total.complete && scanned.filtered.complete;
|
|
16099
17469
|
const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
|
|
16100
|
-
if (current.complete) (0,
|
|
17470
|
+
if (current.complete) (0, import_node_fs26.writeFileSync)(statsPath, JSON.stringify(current), "utf8");
|
|
16101
17471
|
} catch {
|
|
16102
17472
|
total.complete = false;
|
|
16103
17473
|
}
|
|
@@ -16106,7 +17476,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16106
17476
|
}
|
|
16107
17477
|
|
|
16108
17478
|
// src/audit/AuditPruneSweeper.ts
|
|
16109
|
-
var
|
|
17479
|
+
var DAY_MS3 = 24 * 60 * 6e4;
|
|
16110
17480
|
var SWEEP_INTERVAL_MS2 = 60 * 6e4;
|
|
16111
17481
|
var ARCHIVE_BATCH = 64;
|
|
16112
17482
|
var AuditPruneSweeper = class {
|
|
@@ -16169,19 +17539,19 @@ var AuditPruneSweeper = class {
|
|
|
16169
17539
|
if (!this.config.enabled || this.sweeping) return 0;
|
|
16170
17540
|
this.sweeping = true;
|
|
16171
17541
|
try {
|
|
16172
|
-
if (!(0,
|
|
16173
|
-
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) *
|
|
17542
|
+
if (!(0, import_node_fs27.existsSync)(this.auditDir)) return 0;
|
|
17543
|
+
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS3;
|
|
16174
17544
|
let removed = 0;
|
|
16175
|
-
for (const name of (0,
|
|
17545
|
+
for (const name of (0, import_node_fs27.readdirSync)(this.auditDir)) {
|
|
16176
17546
|
const dateMs = auditFileDateMs(name);
|
|
16177
17547
|
if (dateMs === null || dateMs >= cutoff) continue;
|
|
16178
17548
|
try {
|
|
16179
17549
|
if (isAuditDayDir(name)) {
|
|
16180
|
-
(0,
|
|
17550
|
+
(0, import_node_fs27.rmSync)((0, import_node_path27.join)(this.auditDir, name), { recursive: true, force: true });
|
|
16181
17551
|
} else {
|
|
16182
|
-
(0,
|
|
17552
|
+
(0, import_node_fs27.unlinkSync)((0, import_node_path27.join)(this.auditDir, name));
|
|
16183
17553
|
const statsPath = (0, import_node_path27.join)(this.auditDir, auditStatsFileName(name));
|
|
16184
|
-
if ((0,
|
|
17554
|
+
if ((0, import_node_fs27.existsSync)(statsPath)) (0, import_node_fs27.unlinkSync)(statsPath);
|
|
16185
17555
|
}
|
|
16186
17556
|
removed += 1;
|
|
16187
17557
|
} catch (error) {
|
|
@@ -16211,10 +17581,10 @@ var AuditPruneSweeper = class {
|
|
|
16211
17581
|
if (!this.config.enabled || this.archiving) return 0;
|
|
16212
17582
|
this.archiving = true;
|
|
16213
17583
|
try {
|
|
16214
|
-
if (!(0,
|
|
17584
|
+
if (!(0, import_node_fs27.existsSync)(this.auditDir)) return 0;
|
|
16215
17585
|
const today = this.todayMidnight();
|
|
16216
17586
|
let compressed = 0;
|
|
16217
|
-
for (const name of (0,
|
|
17587
|
+
for (const name of (0, import_node_fs27.readdirSync)(this.auditDir)) {
|
|
16218
17588
|
if (compressed >= ARCHIVE_BATCH) break;
|
|
16219
17589
|
const dateMs = auditFileDateMs(name);
|
|
16220
17590
|
if (dateMs === null || dateMs >= today || !isAuditDayDir(name)) continue;
|
|
@@ -16255,7 +17625,7 @@ var AuditPruneSweeper = class {
|
|
|
16255
17625
|
async archiveDay(bodiesPath, budget) {
|
|
16256
17626
|
let shards;
|
|
16257
17627
|
try {
|
|
16258
|
-
shards = (0,
|
|
17628
|
+
shards = (0, import_node_fs27.readdirSync)(bodiesPath).filter((file) => file.endsWith(".jsonl"));
|
|
16259
17629
|
} catch {
|
|
16260
17630
|
return 0;
|
|
16261
17631
|
}
|
|
@@ -16265,16 +17635,16 @@ var AuditPruneSweeper = class {
|
|
|
16265
17635
|
const source = (0, import_node_path27.join)(bodiesPath, shard);
|
|
16266
17636
|
const target = `${source}.gz`;
|
|
16267
17637
|
try {
|
|
16268
|
-
if ((0,
|
|
16269
|
-
(0,
|
|
17638
|
+
if ((0, import_node_fs27.existsSync)(target)) {
|
|
17639
|
+
(0, import_node_fs27.unlinkSync)(source);
|
|
16270
17640
|
continue;
|
|
16271
17641
|
}
|
|
16272
|
-
await (0, import_promises6.pipeline)((0,
|
|
16273
|
-
(0,
|
|
17642
|
+
await (0, import_promises6.pipeline)((0, import_node_fs27.createReadStream)(source), (0, import_node_zlib.createGzip)(), (0, import_node_fs27.createWriteStream)(target));
|
|
17643
|
+
(0, import_node_fs27.unlinkSync)(source);
|
|
16274
17644
|
compressed += 1;
|
|
16275
17645
|
} catch (error) {
|
|
16276
17646
|
try {
|
|
16277
|
-
if ((0,
|
|
17647
|
+
if ((0, import_node_fs27.existsSync)(target)) (0, import_node_fs27.unlinkSync)(target);
|
|
16278
17648
|
} catch {
|
|
16279
17649
|
}
|
|
16280
17650
|
this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
|
|
@@ -16288,7 +17658,7 @@ var AuditPruneSweeper = class {
|
|
|
16288
17658
|
};
|
|
16289
17659
|
|
|
16290
17660
|
// src/usage/usageMigrate.ts
|
|
16291
|
-
var
|
|
17661
|
+
var import_node_fs28 = require("fs");
|
|
16292
17662
|
var import_promises7 = require("fs/promises");
|
|
16293
17663
|
var import_node_path28 = require("path");
|
|
16294
17664
|
var import_node_readline = require("readline");
|
|
@@ -16335,7 +17705,7 @@ async function migrateLegacyUsageEvents(opts) {
|
|
|
16335
17705
|
let skipped = 0;
|
|
16336
17706
|
try {
|
|
16337
17707
|
const reader = (0, import_node_readline.createInterface)({
|
|
16338
|
-
input: (0,
|
|
17708
|
+
input: (0, import_node_fs28.createReadStream)(eventsPath, { encoding: "utf8" }),
|
|
16339
17709
|
crlfDelay: Number.POSITIVE_INFINITY
|
|
16340
17710
|
});
|
|
16341
17711
|
for await (const line of reader) {
|
|
@@ -16427,7 +17797,7 @@ async function closeAll(writers) {
|
|
|
16427
17797
|
// src/usage/UsagePruneSweeper.ts
|
|
16428
17798
|
var import_promises8 = require("fs/promises");
|
|
16429
17799
|
var import_node_path29 = require("path");
|
|
16430
|
-
var
|
|
17800
|
+
var DAY_MS4 = 24 * 60 * 6e4;
|
|
16431
17801
|
var SWEEP_INTERVAL_MS3 = 60 * 6e4;
|
|
16432
17802
|
var DEFAULT_USAGE_RETENTION_DAYS = 90;
|
|
16433
17803
|
var UsagePruneSweeper = class {
|
|
@@ -16484,7 +17854,7 @@ var UsagePruneSweeper = class {
|
|
|
16484
17854
|
this.sweeping = true;
|
|
16485
17855
|
try {
|
|
16486
17856
|
const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
|
|
16487
|
-
const cutoff = this.todayMidnight() - (retentionDays - 1) *
|
|
17857
|
+
const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
|
|
16488
17858
|
let removed = 0;
|
|
16489
17859
|
for (const entry of await listUsageDays(this.usageDir)) {
|
|
16490
17860
|
if (!entry.hasShard) continue;
|
|
@@ -16542,12 +17912,12 @@ var UsagePruneSweeper = class {
|
|
|
16542
17912
|
};
|
|
16543
17913
|
|
|
16544
17914
|
// src/audit/auditBodyReader.ts
|
|
16545
|
-
var
|
|
17915
|
+
var import_node_fs30 = require("fs");
|
|
16546
17916
|
var import_node_path30 = require("path");
|
|
16547
17917
|
var import_node_zlib2 = require("zlib");
|
|
16548
17918
|
|
|
16549
17919
|
// src/audit/auditJsonl.ts
|
|
16550
|
-
var
|
|
17920
|
+
var import_node_fs29 = require("fs");
|
|
16551
17921
|
var WINDOW_BYTES = 1 << 20;
|
|
16552
17922
|
var MAX_LINE_BYTES = 32 * 1024 * 1024;
|
|
16553
17923
|
var NEWLINE2 = 10;
|
|
@@ -16555,9 +17925,9 @@ function forEachLineFromTail(path2, onLine) {
|
|
|
16555
17925
|
let fd;
|
|
16556
17926
|
let end;
|
|
16557
17927
|
try {
|
|
16558
|
-
end = (0,
|
|
17928
|
+
end = (0, import_node_fs29.statSync)(path2).size;
|
|
16559
17929
|
if (end === 0) return;
|
|
16560
|
-
fd = (0,
|
|
17930
|
+
fd = (0, import_node_fs29.openSync)(path2, "r");
|
|
16561
17931
|
} catch {
|
|
16562
17932
|
return;
|
|
16563
17933
|
}
|
|
@@ -16568,7 +17938,7 @@ function forEachLineFromTail(path2, onLine) {
|
|
|
16568
17938
|
const window = Buffer.allocUnsafe(end - start);
|
|
16569
17939
|
let read;
|
|
16570
17940
|
try {
|
|
16571
|
-
read = (0,
|
|
17941
|
+
read = (0, import_node_fs29.readSync)(fd, window, 0, end - start, start);
|
|
16572
17942
|
} catch {
|
|
16573
17943
|
return;
|
|
16574
17944
|
}
|
|
@@ -16596,7 +17966,7 @@ function forEachLineFromTail(path2, onLine) {
|
|
|
16596
17966
|
}
|
|
16597
17967
|
} finally {
|
|
16598
17968
|
try {
|
|
16599
|
-
(0,
|
|
17969
|
+
(0, import_node_fs29.closeSync)(fd);
|
|
16600
17970
|
} catch {
|
|
16601
17971
|
}
|
|
16602
17972
|
}
|
|
@@ -16606,10 +17976,10 @@ function forEachLineFromTail(path2, onLine) {
|
|
|
16606
17976
|
function candidateDays(auditDir, ts) {
|
|
16607
17977
|
if (typeof ts === "number" && Number.isFinite(ts)) {
|
|
16608
17978
|
const named = auditDayDirName(ts);
|
|
16609
|
-
if ((0,
|
|
17979
|
+
if ((0, import_node_fs30.existsSync)((0, import_node_path30.join)(auditDir, named))) return [named];
|
|
16610
17980
|
}
|
|
16611
17981
|
try {
|
|
16612
|
-
return (0,
|
|
17982
|
+
return (0, import_node_fs30.readdirSync)(auditDir).filter(isAuditDayDir).sort().reverse();
|
|
16613
17983
|
} catch {
|
|
16614
17984
|
return [];
|
|
16615
17985
|
}
|
|
@@ -16617,9 +17987,9 @@ function candidateDays(auditDir, ts) {
|
|
|
16617
17987
|
function readShard(auditDir, day, sessionKey) {
|
|
16618
17988
|
const base = (0, import_node_path30.join)(auditDir, day, AUDIT_BODIES_DIR, auditBodyFileName(sessionKey));
|
|
16619
17989
|
try {
|
|
16620
|
-
if ((0,
|
|
17990
|
+
if ((0, import_node_fs30.existsSync)(base)) return (0, import_node_fs30.readFileSync)(base, "utf8");
|
|
16621
17991
|
const gz = `${base}.gz`;
|
|
16622
|
-
if ((0,
|
|
17992
|
+
if ((0, import_node_fs30.existsSync)(gz)) return (0, import_node_zlib2.gunzipSync)((0, import_node_fs30.readFileSync)(gz)).toString("utf8");
|
|
16623
17993
|
} catch {
|
|
16624
17994
|
return null;
|
|
16625
17995
|
}
|
|
@@ -16652,8 +18022,8 @@ function withDictionary(auditDir, day, entries) {
|
|
|
16652
18022
|
const base = (0, import_node_path30.join)(auditDir, day, AUDIT_BODIES_DIR, AUDIT_DICT_FILE);
|
|
16653
18023
|
let raw = null;
|
|
16654
18024
|
try {
|
|
16655
|
-
if ((0,
|
|
16656
|
-
else if ((0,
|
|
18025
|
+
if ((0, import_node_fs30.existsSync)(base)) raw = (0, import_node_fs30.readFileSync)(base, "utf8");
|
|
18026
|
+
else if ((0, import_node_fs30.existsSync)(`${base}.gz`)) raw = (0, import_node_zlib2.gunzipSync)((0, import_node_fs30.readFileSync)(`${base}.gz`)).toString("utf8");
|
|
16657
18027
|
} catch {
|
|
16658
18028
|
return entries;
|
|
16659
18029
|
}
|
|
@@ -16686,7 +18056,7 @@ function reconstructRequest(entries, entry) {
|
|
|
16686
18056
|
}
|
|
16687
18057
|
function readAuditBody(auditDir, query2) {
|
|
16688
18058
|
if (!isSafeSessionKey(query2.sessionKey) || !query2.id) return {};
|
|
16689
|
-
if (!(0,
|
|
18059
|
+
if (!(0, import_node_fs30.existsSync)(auditDir)) return {};
|
|
16690
18060
|
for (const day of candidateDays(auditDir, query2.ts)) {
|
|
16691
18061
|
const raw = readShard(auditDir, day, query2.sessionKey);
|
|
16692
18062
|
if (raw === null) continue;
|
|
@@ -16704,7 +18074,7 @@ function readAuditBody(auditDir, query2) {
|
|
|
16704
18074
|
function readLegacyInlineBody(auditDir, id) {
|
|
16705
18075
|
let names;
|
|
16706
18076
|
try {
|
|
16707
|
-
names = (0,
|
|
18077
|
+
names = (0, import_node_fs30.readdirSync)(auditDir).filter((name) => AUDIT_FILE_RE.test(name)).sort().reverse();
|
|
16708
18078
|
} catch {
|
|
16709
18079
|
return {};
|
|
16710
18080
|
}
|
|
@@ -16733,7 +18103,7 @@ function readLegacyInlineBody(auditDir, id) {
|
|
|
16733
18103
|
}
|
|
16734
18104
|
|
|
16735
18105
|
// src/audit/auditReader.ts
|
|
16736
|
-
var
|
|
18106
|
+
var import_node_fs31 = require("fs");
|
|
16737
18107
|
var import_node_path31 = require("path");
|
|
16738
18108
|
var DEFAULT_LIMIT = 200;
|
|
16739
18109
|
var MAX_LIMIT = 2e3;
|
|
@@ -16741,7 +18111,7 @@ var OVERSCAN = 256;
|
|
|
16741
18111
|
function daySources(auditDir) {
|
|
16742
18112
|
let names;
|
|
16743
18113
|
try {
|
|
16744
|
-
names = (0,
|
|
18114
|
+
names = (0, import_node_fs31.readdirSync)(auditDir);
|
|
16745
18115
|
} catch {
|
|
16746
18116
|
return [];
|
|
16747
18117
|
}
|
|
@@ -16751,7 +18121,7 @@ function daySources(auditDir) {
|
|
|
16751
18121
|
if (dateMs === null) continue;
|
|
16752
18122
|
if (AUDIT_DAY_DIR_RE.test(name)) {
|
|
16753
18123
|
const path2 = (0, import_node_path31.join)(auditDir, name, AUDIT_META_FILE);
|
|
16754
|
-
if ((0,
|
|
18124
|
+
if ((0, import_node_fs31.existsSync)(path2)) sources.push({ path: path2, dateMs });
|
|
16755
18125
|
} else if (AUDIT_FILE_RE.test(name)) {
|
|
16756
18126
|
sources.push({ path: (0, import_node_path31.join)(auditDir, name), dateMs });
|
|
16757
18127
|
}
|
|
@@ -16769,7 +18139,7 @@ function toMetaRecord(record) {
|
|
|
16769
18139
|
return { ...meta, hasBody: true };
|
|
16770
18140
|
}
|
|
16771
18141
|
function readAuditRecords(auditDir, query2 = {}) {
|
|
16772
|
-
if (!(0,
|
|
18142
|
+
if (!(0, import_node_fs31.existsSync)(auditDir)) return [];
|
|
16773
18143
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
16774
18144
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
16775
18145
|
const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
|
|
@@ -16797,7 +18167,7 @@ function readAuditRecords(auditDir, query2 = {}) {
|
|
|
16797
18167
|
}
|
|
16798
18168
|
|
|
16799
18169
|
// src/audit/AuditWriter.ts
|
|
16800
|
-
var
|
|
18170
|
+
var import_node_fs32 = require("fs");
|
|
16801
18171
|
var import_node_path32 = require("path");
|
|
16802
18172
|
var AuditWriter = class {
|
|
16803
18173
|
constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
|
|
@@ -16845,7 +18215,7 @@ var AuditWriter = class {
|
|
|
16845
18215
|
/** Create a directory once per process and remember it. */
|
|
16846
18216
|
ensureDir(path2) {
|
|
16847
18217
|
if (!this.ensuredDirs.has(path2)) {
|
|
16848
|
-
(0,
|
|
18218
|
+
(0, import_node_fs32.mkdirSync)(path2, { recursive: true });
|
|
16849
18219
|
this.ensuredDirs.add(path2);
|
|
16850
18220
|
}
|
|
16851
18221
|
return path2;
|
|
@@ -16855,8 +18225,8 @@ var AuditWriter = class {
|
|
|
16855
18225
|
const { requestBody: _req, responseBody: _res, ...meta } = record;
|
|
16856
18226
|
const file = (0, import_node_path32.join)(dayPath, AUDIT_META_FILE);
|
|
16857
18227
|
const line = JSON.stringify(meta) + "\n";
|
|
16858
|
-
const bytesBefore = (0,
|
|
16859
|
-
(0,
|
|
18228
|
+
const bytesBefore = (0, import_node_fs32.existsSync)(file) ? (0, import_node_fs32.statSync)(file).size : 0;
|
|
18229
|
+
(0, import_node_fs32.appendFileSync)(file, line, "utf8");
|
|
16860
18230
|
try {
|
|
16861
18231
|
updateAuditStatsAfterAppend(
|
|
16862
18232
|
file,
|
|
@@ -16888,7 +18258,7 @@ var AuditWriter = class {
|
|
|
16888
18258
|
const line = encodeBodyEntry(record, sessionKey, dayDir, this.bases);
|
|
16889
18259
|
if (line === null) return;
|
|
16890
18260
|
const bodiesPath = this.ensureDir((0, import_node_path32.join)(dayPath, AUDIT_BODIES_DIR));
|
|
16891
|
-
(0,
|
|
18261
|
+
(0, import_node_fs32.appendFileSync)((0, import_node_path32.join)(bodiesPath, auditBodyFileName(sessionKey)), line + "\n", "utf8");
|
|
16892
18262
|
} catch (error) {
|
|
16893
18263
|
this.bases.forget(sessionKey);
|
|
16894
18264
|
this.logger.warn("[AuditWriter] failed to append audit body shard", {
|
|
@@ -16900,10 +18270,10 @@ var AuditWriter = class {
|
|
|
16900
18270
|
};
|
|
16901
18271
|
|
|
16902
18272
|
// src/billing/BillingPublisher.ts
|
|
16903
|
-
var
|
|
18273
|
+
var import_node_fs33 = require("fs");
|
|
16904
18274
|
var import_node_crypto24 = require("crypto");
|
|
16905
18275
|
var import_node_path33 = require("path");
|
|
16906
|
-
var
|
|
18276
|
+
var import_upstreamFetch11 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
16907
18277
|
|
|
16908
18278
|
// src/billing/billingFiles.ts
|
|
16909
18279
|
var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
|
|
@@ -16926,7 +18296,7 @@ var BillingPublisher = class {
|
|
|
16926
18296
|
constructor(billingDir, logger, opts = {}) {
|
|
16927
18297
|
this.billingDir = billingDir;
|
|
16928
18298
|
this.logger = logger;
|
|
16929
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0,
|
|
18299
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch11.fetchUpstream)(url, init));
|
|
16930
18300
|
this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
|
|
16931
18301
|
this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
|
|
16932
18302
|
this.now = opts.now ?? Date.now;
|
|
@@ -16974,7 +18344,7 @@ var BillingPublisher = class {
|
|
|
16974
18344
|
appendNow(event) {
|
|
16975
18345
|
this.ensureDir();
|
|
16976
18346
|
const file = (0, import_node_path33.join)(this.billingDir, billingFileName(event.ts));
|
|
16977
|
-
(0,
|
|
18347
|
+
(0, import_node_fs33.appendFileSync)(file, JSON.stringify(event) + "\n", "utf8");
|
|
16978
18348
|
}
|
|
16979
18349
|
/**
|
|
16980
18350
|
* One best-effort delivery attempt for an event ALREADY in the ledger. POSTs the
|
|
@@ -17024,7 +18394,7 @@ var BillingPublisher = class {
|
|
|
17024
18394
|
try {
|
|
17025
18395
|
this.ensureDir();
|
|
17026
18396
|
const file = (0, import_node_path33.join)(this.billingDir, deliveredFileName(event.ts));
|
|
17027
|
-
(0,
|
|
18397
|
+
(0, import_node_fs33.appendFileSync)(file, JSON.stringify({ id: event.id, deliveredAt: this.now() }) + "\n", "utf8");
|
|
17028
18398
|
} catch (error) {
|
|
17029
18399
|
this.logger.warn("[BillingPublisher] failed to append delivery marker", {
|
|
17030
18400
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -17033,20 +18403,20 @@ var BillingPublisher = class {
|
|
|
17033
18403
|
}
|
|
17034
18404
|
ensureDir() {
|
|
17035
18405
|
if (this.dirEnsured) return;
|
|
17036
|
-
(0,
|
|
18406
|
+
(0, import_node_fs33.mkdirSync)(this.billingDir, { recursive: true });
|
|
17037
18407
|
this.dirEnsured = true;
|
|
17038
18408
|
}
|
|
17039
18409
|
};
|
|
17040
18410
|
|
|
17041
18411
|
// src/billing/billingReader.ts
|
|
17042
|
-
var
|
|
18412
|
+
var import_node_fs34 = require("fs");
|
|
17043
18413
|
var import_node_path34 = require("path");
|
|
17044
18414
|
function readBillingLedger(billingDir) {
|
|
17045
18415
|
const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
|
|
17046
|
-
if (!(0,
|
|
18416
|
+
if (!(0, import_node_fs34.existsSync)(billingDir)) return view;
|
|
17047
18417
|
let files;
|
|
17048
18418
|
try {
|
|
17049
|
-
files = (0,
|
|
18419
|
+
files = (0, import_node_fs34.readdirSync)(billingDir);
|
|
17050
18420
|
} catch {
|
|
17051
18421
|
return view;
|
|
17052
18422
|
}
|
|
@@ -17077,7 +18447,7 @@ function readBillingStatus(billingDir) {
|
|
|
17077
18447
|
function parseLines(dir, file) {
|
|
17078
18448
|
let raw;
|
|
17079
18449
|
try {
|
|
17080
|
-
raw = (0,
|
|
18450
|
+
raw = (0, import_node_fs34.readFileSync)((0, import_node_path34.join)(dir, file), "utf8");
|
|
17081
18451
|
} catch {
|
|
17082
18452
|
return [];
|
|
17083
18453
|
}
|
|
@@ -17176,7 +18546,7 @@ var BillingRetrySweeper = class {
|
|
|
17176
18546
|
// src/TokenRefreshScheduler.ts
|
|
17177
18547
|
var REFRESH_LEAD_MS2 = 5 * 6e4;
|
|
17178
18548
|
var SWEEP_INTERVAL_MS5 = 6e4;
|
|
17179
|
-
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
|
|
18549
|
+
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
|
|
17180
18550
|
var TokenRefreshScheduler = class {
|
|
17181
18551
|
constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
|
|
17182
18552
|
this.store = store;
|
|
@@ -17259,6 +18629,8 @@ var TokenRefreshScheduler = class {
|
|
|
17259
18629
|
return this.store.refreshCodexToken();
|
|
17260
18630
|
case "gemini":
|
|
17261
18631
|
return this.store.refreshGeminiToken();
|
|
18632
|
+
case "kimi":
|
|
18633
|
+
return this.store.refreshKimiToken();
|
|
17262
18634
|
}
|
|
17263
18635
|
}
|
|
17264
18636
|
};
|
|
@@ -17335,7 +18707,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
|
|
|
17335
18707
|
|
|
17336
18708
|
// src/webhook/WebhookDispatcher.ts
|
|
17337
18709
|
var import_node_crypto25 = require("crypto");
|
|
17338
|
-
var
|
|
18710
|
+
var import_upstreamFetch12 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
17339
18711
|
var WEBHOOK_MAX_ATTEMPTS = 3;
|
|
17340
18712
|
var WEBHOOK_QUEUE_MAX = 1e3;
|
|
17341
18713
|
var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
|
|
@@ -17355,7 +18727,7 @@ var WebhookDispatcher = class {
|
|
|
17355
18727
|
sleep;
|
|
17356
18728
|
now;
|
|
17357
18729
|
constructor(opts = {}) {
|
|
17358
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0,
|
|
18730
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => (0, import_upstreamFetch12.fetchUpstream)(url, init));
|
|
17359
18731
|
this.logger = opts.logger;
|
|
17360
18732
|
this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
|
|
17361
18733
|
this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
|
|
@@ -17441,8 +18813,8 @@ var WebhookDispatcher = class {
|
|
|
17441
18813
|
signal: AbortSignal.timeout(this.timeoutMs)
|
|
17442
18814
|
});
|
|
17443
18815
|
return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
|
|
17444
|
-
} catch (
|
|
17445
|
-
return { ok: false, error:
|
|
18816
|
+
} catch (err6) {
|
|
18817
|
+
return { ok: false, error: err6 instanceof Error ? err6.message : String(err6) };
|
|
17446
18818
|
}
|
|
17447
18819
|
}
|
|
17448
18820
|
/**
|
|
@@ -17566,7 +18938,7 @@ function resetImageRuntimeBootstrapSession() {
|
|
|
17566
18938
|
function resolveLoggingConfig(configured, configPath) {
|
|
17567
18939
|
const file = configured?.file ?? defaultDaemonLogPath(configPath);
|
|
17568
18940
|
try {
|
|
17569
|
-
(0,
|
|
18941
|
+
(0, import_node_fs35.mkdirSync)(configured?.file ? (0, import_node_path35.dirname)(configured.file) : defaultLogDir(configPath), {
|
|
17570
18942
|
recursive: true
|
|
17571
18943
|
});
|
|
17572
18944
|
} catch {
|
|
@@ -17584,12 +18956,12 @@ function buildDaemon(config, paths) {
|
|
|
17584
18956
|
setSecretBox(secretBox3);
|
|
17585
18957
|
setSecretBox2(secretBox3);
|
|
17586
18958
|
const decryptedConfig = decryptConfigSecrets(config, secretBox3);
|
|
17587
|
-
const accountAllowanceStore = new
|
|
18959
|
+
const accountAllowanceStore = new import_AccountAllowanceStore7.AccountAllowanceStore(
|
|
17588
18960
|
Date.now,
|
|
17589
18961
|
void 0,
|
|
17590
18962
|
new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
|
|
17591
18963
|
);
|
|
17592
|
-
(0,
|
|
18964
|
+
(0, import_AccountAllowanceStore7.setSharedAccountAllowanceStore)(accountAllowanceStore);
|
|
17593
18965
|
(0, import_AccountAllowanceScheduling5.getSharedAccountAllowanceScheduling)().configure(
|
|
17594
18966
|
(0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
|
|
17595
18967
|
);
|
|
@@ -17614,21 +18986,22 @@ function buildDaemon(config, paths) {
|
|
|
17614
18986
|
claudeAllowanceRefreshScheduler.configure(
|
|
17615
18987
|
(0, import_outbound_api10.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
|
|
17616
18988
|
);
|
|
17617
|
-
const subscriptionAccounts = new
|
|
17618
|
-
(0,
|
|
17619
|
-
const subscriptionRegistry = new
|
|
18989
|
+
const subscriptionAccounts = new import_subscriptions9.SubscriptionAccountService(credentialStore);
|
|
18990
|
+
(0, import_subscriptions9.setSubscriptionAccountService)(subscriptionAccounts);
|
|
18991
|
+
const subscriptionRegistry = new import_subscriptions9.SubscriptionProviderRegistry(
|
|
17620
18992
|
subscriptionAccounts,
|
|
17621
18993
|
credentialStore
|
|
17622
18994
|
);
|
|
17623
|
-
(0,
|
|
18995
|
+
(0, import_subscriptions9.setSubscriptionProviderRegistry)(subscriptionRegistry);
|
|
17624
18996
|
setServerProxyConfig(decryptedConfig.server?.proxy);
|
|
17625
|
-
(0,
|
|
18997
|
+
(0, import_upstreamFetch13.setUpstreamProxyResolver)(
|
|
17626
18998
|
createUpstreamProxyResolver({
|
|
17627
18999
|
getAccountProxy: (providerId, accountId) => credentialStore.getAccountProxy(providerId, accountId)
|
|
17628
19000
|
})
|
|
17629
19001
|
);
|
|
17630
19002
|
(0, import_gemini_code_assist_resolver.setGeminiCodeAssistResolver)((0, import_GeminiCodeAssistProjectResolver.getGeminiCodeAssistProjectResolver)());
|
|
17631
19003
|
const autoDisableStore = new AutoDisableStore();
|
|
19004
|
+
const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
|
|
17632
19005
|
const apiKeyPool = new import_ApiKeyPoolService.ApiKeyPoolService(
|
|
17633
19006
|
createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
|
|
17634
19007
|
resolveEnvKey,
|
|
@@ -17645,7 +19018,7 @@ function buildDaemon(config, paths) {
|
|
|
17645
19018
|
const pricingEngine = new import_usage2.PricingEngine(pricingStore, logger, {
|
|
17646
19019
|
// Catalog egress follows the same global/env proxy policy as every other
|
|
17647
19020
|
// daemon upstream call; no provider/account override applies here.
|
|
17648
|
-
fetchImpl: ((input, init) => (0,
|
|
19021
|
+
fetchImpl: ((input, init) => (0, import_upstreamFetch13.fetchUpstream)(String(input), init ?? {}))
|
|
17649
19022
|
});
|
|
17650
19023
|
const pricingRefreshScheduler = new PricingRefreshScheduler(
|
|
17651
19024
|
pricingEngine,
|
|
@@ -17909,6 +19282,11 @@ function buildDaemon(config, paths) {
|
|
|
17909
19282
|
// values themselves NEVER leave (masked via `maskProviderApiKey`).
|
|
17910
19283
|
apiKeyPool,
|
|
17911
19284
|
autoDisableStore,
|
|
19285
|
+
// BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
|
|
19286
|
+
// read-through cached same-key usage probe surfaced on the keys view. The
|
|
19287
|
+
// key plaintext is resolved + decrypted inside the service and never
|
|
19288
|
+
// crosses back out.
|
|
19289
|
+
providerKeyQuota: providerKeyQuotaService,
|
|
17912
19290
|
// Interactive OAuth login over admin HTTP (app-parity child 4, design
|
|
17913
19291
|
// D1/D2-a). The in-memory pending-session store (NEVER serialized), the
|
|
17914
19292
|
// injected token-exchange fetch (global `fetch` here; mocked in tests), and a
|
|
@@ -17925,7 +19303,7 @@ function buildDaemon(config, paths) {
|
|
|
17925
19303
|
// — `server.proxy.byProvider[...]` was silently skipped — and the call was
|
|
17926
19304
|
// excluded from the upstream trace, so a failing login left no evidence.
|
|
17927
19305
|
// `redactBodies` keeps the code/verifier + minted token out of that trace.
|
|
17928
|
-
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0,
|
|
19306
|
+
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0, import_upstreamFetch13.fetchUpstream)(url, init, { providerId, redactBodies: true }),
|
|
17929
19307
|
subscriptionAccountAppender: credentialStore,
|
|
17930
19308
|
// Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
|
|
17931
19309
|
// + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
|
|
@@ -17933,6 +19311,10 @@ function buildDaemon(config, paths) {
|
|
|
17933
19311
|
// can inject a mock so no real port is bound.
|
|
17934
19312
|
codexSessions: new CodexOAuthSessionStore(),
|
|
17935
19313
|
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
|
|
19314
|
+
// Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
|
|
19315
|
+
// paste; the app shows the verification URL + user code and polls the
|
|
19316
|
+
// token-free status). Token captured + persisted daemon-side.
|
|
19317
|
+
kimiSessions: new CodexOAuthSessionStore(),
|
|
17936
19318
|
// Migration pack (app-parity child 6, design D2/D3) — the concrete credential
|
|
17937
19319
|
// store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
|
|
17938
19320
|
// the multi-account append (`appendProviderAccount`, import re-encrypts at-
|
|
@@ -17991,7 +19373,7 @@ function buildDaemon(config, paths) {
|
|
|
17991
19373
|
});
|
|
17992
19374
|
const webhookDispatcher = new WebhookDispatcher({
|
|
17993
19375
|
logger,
|
|
17994
|
-
fetchImpl: (url, init) => (0,
|
|
19376
|
+
fetchImpl: (url, init) => (0, import_upstreamFetch13.fetchUpstream)(url, init)
|
|
17995
19377
|
});
|
|
17996
19378
|
setWebhookRuntime(webhookDispatcher, (0, import_SubscriptionAccountHealth4.getSharedAccountHealth)());
|
|
17997
19379
|
const auditWriter = new AuditWriter(auditDir, logger);
|
|
@@ -18071,9 +19453,9 @@ function resetDaemonSingletonsForTests() {
|
|
|
18071
19453
|
(0, import_provider_proxy4.__resetProviderProxyForTests)();
|
|
18072
19454
|
(0, import_outbound_api10.__resetOutboundApiServerForTests)();
|
|
18073
19455
|
(0, import_subscriptionRegistryPort.setSubscriptionRegistryForOutbound)(null);
|
|
18074
|
-
(0,
|
|
18075
|
-
(0,
|
|
18076
|
-
(0,
|
|
19456
|
+
(0, import_subscriptions9.setSubscriptionProviderRegistry)(null);
|
|
19457
|
+
(0, import_subscriptions9.setSubscriptionAccountService)(null);
|
|
19458
|
+
(0, import_upstreamFetch13.setUpstreamProxyResolver)(null);
|
|
18077
19459
|
setServerProxyConfig(void 0);
|
|
18078
19460
|
(0, import_gemini_code_assist_resolver.setGeminiCodeAssistResolver)(null);
|
|
18079
19461
|
setSecretBox(null);
|
|
@@ -18082,14 +19464,14 @@ function resetDaemonSingletonsForTests() {
|
|
|
18082
19464
|
resetAuditRuntimeForTests();
|
|
18083
19465
|
resetBillingRuntimeForTests();
|
|
18084
19466
|
(0, import_SubscriptionIdentityStore3.__resetSharedIdentityStoreForTests)();
|
|
18085
|
-
(0,
|
|
19467
|
+
(0, import_AccountAllowanceStore7.__resetSharedAccountAllowanceStoreForTests)();
|
|
18086
19468
|
(0, import_AccountAllowanceScheduling5.__resetSharedAccountAllowanceSchedulingForTests)();
|
|
18087
19469
|
(0, import_usage2.__resetSharedUsageThroughputTrackerForTests)();
|
|
18088
19470
|
}
|
|
18089
19471
|
function isTokensStoreReadable(tokensPath) {
|
|
18090
19472
|
try {
|
|
18091
|
-
if (!(0,
|
|
18092
|
-
(0,
|
|
19473
|
+
if (!(0, import_node_fs35.existsSync)(tokensPath)) return true;
|
|
19474
|
+
(0, import_node_fs35.accessSync)(tokensPath, import_node_fs35.constants.R_OK);
|
|
18093
19475
|
return true;
|
|
18094
19476
|
} catch {
|
|
18095
19477
|
return false;
|