@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.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/bootstrap.ts
|
|
2
|
-
import { accessSync, constants as fsConstants, existsSync as
|
|
2
|
+
import { accessSync, constants as fsConstants, existsSync as existsSync30, mkdirSync as mkdirSync9 } from "fs";
|
|
3
3
|
import { dirname as dirname17 } from "path";
|
|
4
4
|
import { DEFAULT_AUDIT_CONFIG } from "@omnicross/contracts/audit-types";
|
|
5
5
|
import { DEFAULT_BILLING_CONFIG } from "@omnicross/contracts/billing-types";
|
|
@@ -18,14 +18,14 @@ import { setSubscriptionRegistryForOutbound } from "@omnicross/core/outbound-api
|
|
|
18
18
|
import { getSharedAccountHealth as getSharedAccountHealth4 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
19
19
|
import {
|
|
20
20
|
__resetSharedAccountAllowanceStoreForTests,
|
|
21
|
-
AccountAllowanceStore as
|
|
21
|
+
AccountAllowanceStore as AccountAllowanceStore6,
|
|
22
22
|
setSharedAccountAllowanceStore
|
|
23
23
|
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
24
24
|
import {
|
|
25
25
|
__resetSharedAccountAllowanceSchedulingForTests,
|
|
26
26
|
getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling5
|
|
27
27
|
} from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
28
|
-
import { fetchUpstream as
|
|
28
|
+
import { fetchUpstream as fetchUpstream11, setUpstreamProxyResolver } from "@omnicross/core/pipeline/upstreamFetch";
|
|
29
29
|
import { __resetSharedIdentityStoreForTests } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
|
|
30
30
|
import { setGeminiCodeAssistResolver } from "@omnicross/core/ports/gemini-code-assist-resolver";
|
|
31
31
|
import {
|
|
@@ -155,9 +155,84 @@ function handleCodexOAuthStatus(sessionId, deps) {
|
|
|
155
155
|
return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
// src/admin/accountsKimiOAuth.ts
|
|
159
|
+
import { kimiOAuth } from "@omnicross/subscriptions";
|
|
160
|
+
function err2(status, message) {
|
|
161
|
+
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
162
|
+
}
|
|
163
|
+
var DEFAULT_KIMI_OAUTH_TTL_MS = 15 * 6e4;
|
|
164
|
+
async function handleKimiOAuthStart(deps) {
|
|
165
|
+
if (deps.kimiSessions.isBusy()) {
|
|
166
|
+
return err2(409, "a kimi sign-in is already in progress \u2014 finish it in the browser or cancel it");
|
|
167
|
+
}
|
|
168
|
+
const fetchImpl = deps.oauthExchangeFetch("kimi");
|
|
169
|
+
const deviceId = kimiOAuth.generateKimiDeviceId();
|
|
170
|
+
const fingerprint = kimiOAuth.kimiFingerprintHeaders(deviceId);
|
|
171
|
+
let authorization;
|
|
172
|
+
try {
|
|
173
|
+
authorization = await kimiOAuth.requestDeviceAuthorization(fetchImpl, fingerprint);
|
|
174
|
+
} catch (e) {
|
|
175
|
+
const reason = e instanceof Error ? e.message : "device authorization failed";
|
|
176
|
+
return err2(502, `kimi device authorization failed: ${reason}`);
|
|
177
|
+
}
|
|
178
|
+
const { sessionId, signal } = deps.kimiSessions.begin();
|
|
179
|
+
void runKimiDevicePoll(sessionId, authorization.deviceCode, deviceId, fingerprint, signal, deps).catch(() => deps.kimiSessions.settle(sessionId, "error", "kimi sign-in failed"));
|
|
180
|
+
return {
|
|
181
|
+
status: 200,
|
|
182
|
+
body: {
|
|
183
|
+
authUrl: authorization.verificationUriComplete ?? authorization.verificationUri,
|
|
184
|
+
userCode: authorization.userCode,
|
|
185
|
+
sessionId
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
async function runKimiDevicePoll(sessionId, deviceCode, deviceId, fingerprint, signal, deps) {
|
|
190
|
+
const fetchImpl = deps.oauthExchangeFetch("kimi");
|
|
191
|
+
const result = await kimiOAuth.awaitDeviceToken(
|
|
192
|
+
{ userCode: "", deviceCode, verificationUri: "" },
|
|
193
|
+
fetchImpl,
|
|
194
|
+
{
|
|
195
|
+
fingerprint,
|
|
196
|
+
deadlineMs: DEFAULT_KIMI_OAUTH_TTL_MS,
|
|
197
|
+
sleep: (ms) => new Promise((resolve10, reject) => {
|
|
198
|
+
const onAbort = () => {
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
reject(new Error("login: cancelled"));
|
|
201
|
+
};
|
|
202
|
+
const timer = setTimeout(() => {
|
|
203
|
+
signal.removeEventListener("abort", onAbort);
|
|
204
|
+
resolve10();
|
|
205
|
+
}, ms);
|
|
206
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
const block = {
|
|
211
|
+
authMethod: "oauth",
|
|
212
|
+
status: "authorized",
|
|
213
|
+
accessToken: result.accessToken,
|
|
214
|
+
refreshToken: result.refreshToken,
|
|
215
|
+
expiresAt: new Date(Date.now() + result.expiresIn * 1e3).toISOString(),
|
|
216
|
+
accountId: kimiOAuth.kimiAccountIdFromAccessToken(result.accessToken),
|
|
217
|
+
deviceId,
|
|
218
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
219
|
+
};
|
|
220
|
+
await deps.subscriptionAccountAppender.appendProviderAccount("kimi", block);
|
|
221
|
+
deps.kimiSessions.settle(sessionId, "done");
|
|
222
|
+
}
|
|
223
|
+
function handleKimiOAuthCancel(sessionId, deps) {
|
|
224
|
+
if (!deps.kimiSessions.cancel(sessionId)) return err2(404, "unknown or expired kimi sign-in session");
|
|
225
|
+
return { status: 200, body: { ok: true } };
|
|
226
|
+
}
|
|
227
|
+
function handleKimiOAuthStatus(sessionId, deps) {
|
|
228
|
+
const s = deps.kimiSessions.get(sessionId);
|
|
229
|
+
if (!s) return err2(404, "unknown or expired kimi sign-in session");
|
|
230
|
+
return { status: 200, body: { state: s.status, ...s.error ? { message: s.error } : {} } };
|
|
231
|
+
}
|
|
232
|
+
|
|
158
233
|
// src/allowance/AccountAllowanceService.ts
|
|
159
234
|
import {
|
|
160
|
-
getSharedAccountAllowanceStore as
|
|
235
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore5
|
|
161
236
|
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
162
237
|
import {
|
|
163
238
|
getSharedAccountAllowanceScheduling
|
|
@@ -191,13 +266,11 @@ function secondsUntil(instant, now) {
|
|
|
191
266
|
function windowFromPayload(id, payload, now) {
|
|
192
267
|
const usedPercent = finitePercent(payload?.utilization);
|
|
193
268
|
const resetsAt = isoInstant(payload?.resets_at);
|
|
194
|
-
const isSonnet = id === "seven-day-sonnet";
|
|
195
269
|
const isFiveHour = id === "five-hour";
|
|
196
270
|
return {
|
|
197
271
|
id,
|
|
198
|
-
label: isFiveHour ? "5 hours" :
|
|
199
|
-
scope:
|
|
200
|
-
modelFamily: isSonnet ? "sonnet" : void 0,
|
|
272
|
+
label: isFiveHour ? "5 hours" : "7 days",
|
|
273
|
+
scope: "all",
|
|
201
274
|
usedPercent,
|
|
202
275
|
windowMinutes: isFiveHour ? 5 * 60 : 7 * 24 * 60,
|
|
203
276
|
resetsAt,
|
|
@@ -205,6 +278,44 @@ function windowFromPayload(id, payload, now) {
|
|
|
205
278
|
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
206
279
|
};
|
|
207
280
|
}
|
|
281
|
+
function limitEntryWindow(entries, kind) {
|
|
282
|
+
const entry = entries.find((candidate) => candidate.kind === kind);
|
|
283
|
+
if (!entry) return void 0;
|
|
284
|
+
return { utilization: entry.percent, resets_at: entry.resets_at };
|
|
285
|
+
}
|
|
286
|
+
function slugifyDisplayName(name) {
|
|
287
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
288
|
+
}
|
|
289
|
+
function scopedWeeklyWindows(entries, now) {
|
|
290
|
+
const seen = /* @__PURE__ */ new Set();
|
|
291
|
+
const windows = [];
|
|
292
|
+
for (const entry of entries) {
|
|
293
|
+
if (entry.kind !== "weekly_scoped") continue;
|
|
294
|
+
const displayName = typeof entry.scope?.model?.display_name === "string" && entry.scope.model.display_name.trim() ? entry.scope.model.display_name.trim() : void 0;
|
|
295
|
+
if (!displayName) continue;
|
|
296
|
+
const slug = slugifyDisplayName(displayName);
|
|
297
|
+
if (!slug || seen.has(slug)) continue;
|
|
298
|
+
seen.add(slug);
|
|
299
|
+
const usedPercent = finitePercent(entry.percent);
|
|
300
|
+
const resetsAt = isoInstant(entry.resets_at);
|
|
301
|
+
windows.push({
|
|
302
|
+
id: `seven-day-${slug}`,
|
|
303
|
+
label: `7 days \xB7 ${displayName}`,
|
|
304
|
+
scope: "model-family",
|
|
305
|
+
modelFamily: slug,
|
|
306
|
+
usedPercent,
|
|
307
|
+
windowMinutes: 7 * 24 * 60,
|
|
308
|
+
resetsAt,
|
|
309
|
+
remainingSeconds: secondsUntil(resetsAt, now),
|
|
310
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
return windows;
|
|
314
|
+
}
|
|
315
|
+
function parseLimitEntries(raw) {
|
|
316
|
+
if (!Array.isArray(raw)) return [];
|
|
317
|
+
return raw.filter((entry) => !!entry && typeof entry === "object");
|
|
318
|
+
}
|
|
208
319
|
function emptyClaudeWindows(state) {
|
|
209
320
|
return [
|
|
210
321
|
{
|
|
@@ -222,15 +333,6 @@ function emptyClaudeWindows(state) {
|
|
|
222
333
|
usedPercent: null,
|
|
223
334
|
windowMinutes: 7 * 24 * 60,
|
|
224
335
|
state
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
id: "seven-day-sonnet",
|
|
228
|
-
label: "7 days \xB7 Sonnet",
|
|
229
|
-
scope: "model-family",
|
|
230
|
-
modelFamily: "sonnet",
|
|
231
|
-
usedPercent: null,
|
|
232
|
-
windowMinutes: 7 * 24 * 60,
|
|
233
|
-
state
|
|
234
336
|
}
|
|
235
337
|
];
|
|
236
338
|
}
|
|
@@ -311,6 +413,9 @@ var ClaudeAllowanceCollector = class {
|
|
|
311
413
|
}
|
|
312
414
|
const now = this.now();
|
|
313
415
|
const usage = payload;
|
|
416
|
+
const limitEntries = parseLimitEntries(usage.limits);
|
|
417
|
+
const fiveHour = usage.five_hour ?? limitEntryWindow(limitEntries, "session");
|
|
418
|
+
const sevenDay = usage.seven_day ?? limitEntryWindow(limitEntries, "weekly_all");
|
|
314
419
|
const snapshot = {
|
|
315
420
|
providerId: "claude",
|
|
316
421
|
accountId,
|
|
@@ -318,10 +423,10 @@ var ClaudeAllowanceCollector = class {
|
|
|
318
423
|
observedAt: new Date(now).toISOString(),
|
|
319
424
|
expiresAt: new Date(now + CLAUDE_ALLOWANCE_CACHE_MS).toISOString(),
|
|
320
425
|
windows: [
|
|
321
|
-
windowFromPayload("five-hour",
|
|
322
|
-
windowFromPayload("seven-day",
|
|
323
|
-
|
|
324
|
-
]
|
|
426
|
+
windowFromPayload("five-hour", fiveHour, now),
|
|
427
|
+
windowFromPayload("seven-day", sevenDay, now),
|
|
428
|
+
...scopedWeeklyWindows(limitEntries, now)
|
|
429
|
+
].slice(0, 8)
|
|
325
430
|
};
|
|
326
431
|
this.store.set(snapshot);
|
|
327
432
|
return snapshot;
|
|
@@ -378,6 +483,607 @@ var ClaudeAllowanceCollector = class {
|
|
|
378
483
|
}
|
|
379
484
|
};
|
|
380
485
|
|
|
486
|
+
// src/allowance/CodexAllowanceCollector.ts
|
|
487
|
+
import {
|
|
488
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore2
|
|
489
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
490
|
+
import { fetchUpstream as fetchUpstream2 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
491
|
+
var CODEX_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
492
|
+
var CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
493
|
+
var CODEX_CLI_USER_AGENT = "codex_cli_rs/0.144.5";
|
|
494
|
+
function finiteNumber(value) {
|
|
495
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
496
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
497
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
498
|
+
}
|
|
499
|
+
function finitePercent2(value) {
|
|
500
|
+
const parsed = finiteNumber(value);
|
|
501
|
+
return parsed !== null && parsed <= 100 ? parsed : null;
|
|
502
|
+
}
|
|
503
|
+
function epochMs(value) {
|
|
504
|
+
return value > 1e11 ? value : value * 1e3;
|
|
505
|
+
}
|
|
506
|
+
function secondsUntil2(instant, now) {
|
|
507
|
+
if (!instant) return void 0;
|
|
508
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
509
|
+
}
|
|
510
|
+
function decodeJwtClaims(token) {
|
|
511
|
+
const parts = token.split(".");
|
|
512
|
+
if (parts.length !== 3) return void 0;
|
|
513
|
+
try {
|
|
514
|
+
const json2 = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
515
|
+
const parsed = JSON.parse(json2);
|
|
516
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
517
|
+
} catch {
|
|
518
|
+
return void 0;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function chatgptAccountIdFromClaims(claims) {
|
|
522
|
+
const auth = claims?.["https://api.openai.com/auth"];
|
|
523
|
+
if (!auth || typeof auth !== "object") return void 0;
|
|
524
|
+
const accountId = auth.chatgpt_account_id;
|
|
525
|
+
return typeof accountId === "string" && accountId.trim() ? accountId.trim() : void 0;
|
|
526
|
+
}
|
|
527
|
+
function resolveCodexChatGptAccountId(tokens) {
|
|
528
|
+
if (tokens.accountId?.trim()) return tokens.accountId.trim();
|
|
529
|
+
if (tokens.idToken) {
|
|
530
|
+
const fromIdToken = chatgptAccountIdFromClaims(decodeJwtClaims(tokens.idToken));
|
|
531
|
+
if (fromIdToken) return fromIdToken;
|
|
532
|
+
}
|
|
533
|
+
if (tokens.accessToken) {
|
|
534
|
+
return chatgptAccountIdFromClaims(decodeJwtClaims(tokens.accessToken));
|
|
535
|
+
}
|
|
536
|
+
return void 0;
|
|
537
|
+
}
|
|
538
|
+
function windowFromPayload2(id, payload, now) {
|
|
539
|
+
const usedPercent = finitePercent2(payload?.used_percent);
|
|
540
|
+
const resetAtSeconds = finiteNumber(payload?.reset_at);
|
|
541
|
+
const resetAfterSeconds = finiteNumber(payload?.reset_after_seconds);
|
|
542
|
+
const windowSeconds = finiteNumber(payload?.limit_window_seconds);
|
|
543
|
+
const resetsAt = resetAtSeconds !== null && resetAtSeconds > 0 ? new Date(epochMs(resetAtSeconds)).toISOString() : resetAfterSeconds !== null && resetAfterSeconds > 0 ? new Date(now + resetAfterSeconds * 1e3).toISOString() : void 0;
|
|
544
|
+
const windowMinutes = windowSeconds !== null && windowSeconds > 0 ? Math.round(windowSeconds / 60) : void 0;
|
|
545
|
+
return {
|
|
546
|
+
id,
|
|
547
|
+
label: id === "primary" ? "Primary" : "Secondary",
|
|
548
|
+
scope: "all",
|
|
549
|
+
usedPercent,
|
|
550
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
551
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
552
|
+
remainingSeconds: secondsUntil2(resetsAt, now),
|
|
553
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
var CodexAllowanceCollector = class {
|
|
557
|
+
constructor(credentials, store = getSharedAccountAllowanceStore2(), fetchImpl = (url, init, accountId) => fetchUpstream2(url, init, { providerId: "codex", accountId, redactBodies: true }), now = Date.now) {
|
|
558
|
+
this.credentials = credentials;
|
|
559
|
+
this.store = store;
|
|
560
|
+
this.fetchImpl = fetchImpl;
|
|
561
|
+
this.now = now;
|
|
562
|
+
}
|
|
563
|
+
credentials;
|
|
564
|
+
store;
|
|
565
|
+
fetchImpl;
|
|
566
|
+
now;
|
|
567
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
568
|
+
async collectMany(accounts, options = {}) {
|
|
569
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
570
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
571
|
+
}
|
|
572
|
+
collect(account, options = {}) {
|
|
573
|
+
const now = this.now();
|
|
574
|
+
const unsupported = account.tokens.authMethod !== "oauth";
|
|
575
|
+
if (unsupported) {
|
|
576
|
+
const existing = this.store.get("codex", account.id, now);
|
|
577
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
578
|
+
return Promise.resolve(existing);
|
|
579
|
+
}
|
|
580
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
581
|
+
this.store.set(snapshot);
|
|
582
|
+
return Promise.resolve(snapshot);
|
|
583
|
+
}
|
|
584
|
+
const cached = this.store.get("codex", account.id, now);
|
|
585
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
586
|
+
return Promise.resolve(cached);
|
|
587
|
+
}
|
|
588
|
+
const running = this.inFlight.get(account.id);
|
|
589
|
+
if (running) return running;
|
|
590
|
+
const promise = this.fetchAccount(account.id, account.tokens).catch(() => this.failureSnapshot(account.id, "codex_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
|
|
591
|
+
this.inFlight.set(account.id, promise);
|
|
592
|
+
return promise;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* A response-header snapshot stays a valid cache hit only while fresh; an
|
|
596
|
+
* active oauth-usage snapshot is honored on the same 5-minute cadence as
|
|
597
|
+
* Claude's (the poll is cheap and quota is the scheduling input).
|
|
598
|
+
*/
|
|
599
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
600
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
601
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
602
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
603
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
604
|
+
}
|
|
605
|
+
async fetchAccount(accountId, tokens) {
|
|
606
|
+
let accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
|
|
607
|
+
if (!accessToken) {
|
|
608
|
+
return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
|
|
609
|
+
}
|
|
610
|
+
let response = await this.request(accountId, accessToken, tokens);
|
|
611
|
+
if (response.status === 401) {
|
|
612
|
+
const refreshed = await this.credentials.refreshAccountToken("codex", accountId);
|
|
613
|
+
if (!refreshed) {
|
|
614
|
+
return this.failureSnapshot(accountId, "codex_usage_unauthorized", this.now());
|
|
615
|
+
}
|
|
616
|
+
accessToken = await this.credentials.getAccessTokenForAccount("codex", accountId);
|
|
617
|
+
if (!accessToken) {
|
|
618
|
+
return this.failureSnapshot(accountId, "codex_usage_token_unavailable", this.now());
|
|
619
|
+
}
|
|
620
|
+
response = await this.request(accountId, accessToken, tokens);
|
|
621
|
+
}
|
|
622
|
+
if (response.status === 403) {
|
|
623
|
+
const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "codex_usage_unsupported");
|
|
624
|
+
this.store.set(snapshot2);
|
|
625
|
+
return snapshot2;
|
|
626
|
+
}
|
|
627
|
+
if (!response.ok) {
|
|
628
|
+
return this.failureSnapshot(accountId, "codex_usage_http_error", this.now());
|
|
629
|
+
}
|
|
630
|
+
let payload;
|
|
631
|
+
try {
|
|
632
|
+
payload = await response.json();
|
|
633
|
+
} catch {
|
|
634
|
+
return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
|
|
635
|
+
}
|
|
636
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
637
|
+
return this.failureSnapshot(accountId, "codex_usage_invalid_response", this.now());
|
|
638
|
+
}
|
|
639
|
+
const now = this.now();
|
|
640
|
+
const usage = payload.rate_limit;
|
|
641
|
+
const previous = this.store.get("codex", accountId, now);
|
|
642
|
+
const snapshot = {
|
|
643
|
+
providerId: "codex",
|
|
644
|
+
accountId,
|
|
645
|
+
source: "oauth-usage-api",
|
|
646
|
+
observedAt: new Date(now).toISOString(),
|
|
647
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
648
|
+
windows: [
|
|
649
|
+
windowFromPayload2("primary", usage?.primary_window ?? void 0, now),
|
|
650
|
+
windowFromPayload2("secondary", usage?.secondary_window ?? void 0, now)
|
|
651
|
+
],
|
|
652
|
+
// The wham payload has no ratio field; keep the passively-observed value.
|
|
653
|
+
...previous?.primaryOverSecondaryLimitPercent !== void 0 ? { primaryOverSecondaryLimitPercent: previous.primaryOverSecondaryLimitPercent } : {}
|
|
654
|
+
};
|
|
655
|
+
this.store.set(snapshot);
|
|
656
|
+
return snapshot;
|
|
657
|
+
}
|
|
658
|
+
request(accountId, accessToken, tokens) {
|
|
659
|
+
const headers = {
|
|
660
|
+
Authorization: `Bearer ${accessToken}`,
|
|
661
|
+
Accept: "application/json",
|
|
662
|
+
"User-Agent": CODEX_CLI_USER_AGENT
|
|
663
|
+
};
|
|
664
|
+
const chatgptAccountId = resolveCodexChatGptAccountId(tokens);
|
|
665
|
+
if (chatgptAccountId) headers["ChatGPT-Account-Id"] = chatgptAccountId;
|
|
666
|
+
return this.fetchImpl(CODEX_USAGE_URL, {
|
|
667
|
+
method: "GET",
|
|
668
|
+
headers,
|
|
669
|
+
signal: AbortSignal.timeout(15e3)
|
|
670
|
+
}, accountId);
|
|
671
|
+
}
|
|
672
|
+
failureSnapshot(accountId, code, now) {
|
|
673
|
+
const existing = this.store.get("codex", accountId, now);
|
|
674
|
+
const snapshot = existing ? {
|
|
675
|
+
...existing,
|
|
676
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
677
|
+
windows: existing.windows.map((window) => ({
|
|
678
|
+
...window,
|
|
679
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
680
|
+
})),
|
|
681
|
+
lastErrorCode: code
|
|
682
|
+
} : {
|
|
683
|
+
providerId: "codex",
|
|
684
|
+
accountId,
|
|
685
|
+
source: "oauth-usage-api",
|
|
686
|
+
observedAt: new Date(now).toISOString(),
|
|
687
|
+
expiresAt: new Date(now + CODEX_ALLOWANCE_CACHE_MS).toISOString(),
|
|
688
|
+
windows: [
|
|
689
|
+
{ id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unavailable" },
|
|
690
|
+
{ id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unavailable" }
|
|
691
|
+
],
|
|
692
|
+
lastErrorCode: code
|
|
693
|
+
};
|
|
694
|
+
this.store.set(snapshot);
|
|
695
|
+
return snapshot;
|
|
696
|
+
}
|
|
697
|
+
unsupportedSnapshot(accountId, now, code = "codex_usage_unsupported_auth") {
|
|
698
|
+
return {
|
|
699
|
+
providerId: "codex",
|
|
700
|
+
accountId,
|
|
701
|
+
source: "oauth-usage-api",
|
|
702
|
+
observedAt: new Date(now).toISOString(),
|
|
703
|
+
windows: [
|
|
704
|
+
{ id: "primary", label: "Primary", scope: "all", usedPercent: null, state: "unsupported" },
|
|
705
|
+
{ id: "secondary", label: "Secondary", scope: "all", usedPercent: null, state: "unsupported" }
|
|
706
|
+
],
|
|
707
|
+
lastErrorCode: code
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// src/allowance/KimiAllowanceCollector.ts
|
|
713
|
+
import {
|
|
714
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore3
|
|
715
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
716
|
+
import { fetchUpstream as fetchUpstream3 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
717
|
+
import { kimiFingerprintHeaders } from "@omnicross/subscriptions";
|
|
718
|
+
var KIMI_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
719
|
+
var KIMI_USAGE_URL = "https://api.kimi.com/coding/v1/usages";
|
|
720
|
+
function finiteNumber2(value) {
|
|
721
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
722
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
723
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
724
|
+
}
|
|
725
|
+
function isRecord(value) {
|
|
726
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
727
|
+
}
|
|
728
|
+
function parseResetMs(row, nowMs) {
|
|
729
|
+
for (const key of ["reset_at", "resetAt", "reset_time", "resetTime"]) {
|
|
730
|
+
const value = row[key];
|
|
731
|
+
if (typeof value === "string" && value.trim()) {
|
|
732
|
+
const parsed = Date.parse(value);
|
|
733
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
734
|
+
}
|
|
735
|
+
const numeric = finiteNumber2(value);
|
|
736
|
+
if (numeric !== void 0 && numeric > 1e9) {
|
|
737
|
+
return numeric > 1e12 ? numeric : numeric * 1e3;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
for (const key of ["reset_in", "resetIn", "ttl", "window"]) {
|
|
741
|
+
const seconds = finiteNumber2(row[key]);
|
|
742
|
+
if (seconds !== void 0) return nowMs + seconds * 1e3;
|
|
743
|
+
}
|
|
744
|
+
return void 0;
|
|
745
|
+
}
|
|
746
|
+
var MINUTE_MS = 6e4;
|
|
747
|
+
var HOUR_MS = 36e5;
|
|
748
|
+
var DAY_MS = 864e5;
|
|
749
|
+
function canonicalWindow(durationMs) {
|
|
750
|
+
if (durationMs === 5 * HOUR_MS) return { id: "five-hour", label: "5 hours", minutes: 300 };
|
|
751
|
+
if (durationMs === 7 * DAY_MS) return { id: "seven-day", label: "7 days", minutes: 10080 };
|
|
752
|
+
if (durationMs > 0 && durationMs % DAY_MS === 0) {
|
|
753
|
+
const days = durationMs / DAY_MS;
|
|
754
|
+
return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
|
|
755
|
+
}
|
|
756
|
+
if (durationMs > 0 && durationMs % HOUR_MS === 0) {
|
|
757
|
+
const hours = durationMs / HOUR_MS;
|
|
758
|
+
return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}`, minutes: Math.round(durationMs / MINUTE_MS) };
|
|
759
|
+
}
|
|
760
|
+
return void 0;
|
|
761
|
+
}
|
|
762
|
+
function secondsUntil3(instant, now) {
|
|
763
|
+
if (!instant) return void 0;
|
|
764
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
765
|
+
}
|
|
766
|
+
function windowFromRow(row, fallback, now) {
|
|
767
|
+
const usedPercent = row?.limit !== void 0 && row.limit > 0 && row.used !== void 0 ? Math.round(Math.min(100, row.used / row.limit * 100) * 10) / 10 : null;
|
|
768
|
+
const resetsAt = row?.resetsAtMs !== void 0 ? new Date(row.resetsAtMs).toISOString() : void 0;
|
|
769
|
+
return {
|
|
770
|
+
id: fallback.id,
|
|
771
|
+
label: fallback.label,
|
|
772
|
+
scope: "all",
|
|
773
|
+
usedPercent,
|
|
774
|
+
windowMinutes: fallback.minutes,
|
|
775
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
776
|
+
remainingSeconds: secondsUntil3(resetsAt, now),
|
|
777
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
function parseKimiUsagePayload(payload, now) {
|
|
781
|
+
if (!isRecord(payload)) return [];
|
|
782
|
+
const byId = /* @__PURE__ */ new Map();
|
|
783
|
+
const rowFrom = (data) => {
|
|
784
|
+
const limit = finiteNumber2(data["limit"]);
|
|
785
|
+
let used = finiteNumber2(data["used"]);
|
|
786
|
+
const remaining = finiteNumber2(data["remaining"]);
|
|
787
|
+
if (used === void 0 && remaining !== void 0 && limit !== void 0) {
|
|
788
|
+
used = limit - remaining;
|
|
789
|
+
}
|
|
790
|
+
let windowDurationMs;
|
|
791
|
+
const windowData = isRecord(data["window"]) ? data["window"] : void 0;
|
|
792
|
+
const duration = finiteNumber2(windowData?.["duration"]);
|
|
793
|
+
const timeUnit = typeof windowData?.["timeUnit"] === "string" ? windowData["timeUnit"].toUpperCase() : "";
|
|
794
|
+
if (duration !== void 0) {
|
|
795
|
+
if (timeUnit.includes("MINUTE")) windowDurationMs = duration * MINUTE_MS;
|
|
796
|
+
else if (timeUnit.includes("HOUR")) windowDurationMs = duration * HOUR_MS;
|
|
797
|
+
else if (timeUnit.includes("DAY")) windowDurationMs = duration * DAY_MS;
|
|
798
|
+
else if (timeUnit.includes("WEEK")) windowDurationMs = duration * 7 * DAY_MS;
|
|
799
|
+
else if (timeUnit.includes("SECOND")) windowDurationMs = duration * 1e3;
|
|
800
|
+
}
|
|
801
|
+
const resetsAtMs = parseResetMs(windowData && parseResetMs(windowData, now) !== void 0 ? windowData : data, now);
|
|
802
|
+
return { used, limit, remaining, ...resetsAtMs !== void 0 ? { resetsAtMs } : {}, ...windowDurationMs !== void 0 ? { windowDurationMs } : {} };
|
|
803
|
+
};
|
|
804
|
+
if (isRecord(payload["usage"])) {
|
|
805
|
+
const row = rowFrom(payload["usage"]);
|
|
806
|
+
const window = windowFromRow({ ...row, resetsAtMs: row.resetsAtMs }, { id: "seven-day", label: "7 days", minutes: 10080 }, now);
|
|
807
|
+
byId.set("seven-day", window);
|
|
808
|
+
}
|
|
809
|
+
if (Array.isArray(payload["limits"])) {
|
|
810
|
+
for (const item of payload["limits"]) {
|
|
811
|
+
if (!isRecord(item)) continue;
|
|
812
|
+
const detail = isRecord(item["detail"]) ? item["detail"] : item;
|
|
813
|
+
const row = rowFrom(detail);
|
|
814
|
+
const canonical = row.windowDurationMs !== void 0 ? canonicalWindow(row.windowDurationMs) : void 0;
|
|
815
|
+
if (!canonical) continue;
|
|
816
|
+
const window = windowFromRow(row, canonical, now);
|
|
817
|
+
const existing = byId.get(canonical.id);
|
|
818
|
+
if (!existing || (window.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
|
|
819
|
+
byId.set(canonical.id, window);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return [...byId.values()].sort((a, b) => (a.windowMinutes ?? Infinity) - (b.windowMinutes ?? Infinity)).slice(0, 4);
|
|
824
|
+
}
|
|
825
|
+
var KimiAllowanceCollector = class {
|
|
826
|
+
constructor(credentials, store = getSharedAccountAllowanceStore3(), fetchImpl = (url, init, accountId) => fetchUpstream3(url, init, { providerId: "kimi", accountId, redactBodies: true }), now = Date.now) {
|
|
827
|
+
this.credentials = credentials;
|
|
828
|
+
this.store = store;
|
|
829
|
+
this.fetchImpl = fetchImpl;
|
|
830
|
+
this.now = now;
|
|
831
|
+
}
|
|
832
|
+
credentials;
|
|
833
|
+
store;
|
|
834
|
+
fetchImpl;
|
|
835
|
+
now;
|
|
836
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
837
|
+
async collectMany(accounts, options = {}) {
|
|
838
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
839
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
840
|
+
}
|
|
841
|
+
collect(account, options = {}) {
|
|
842
|
+
const now = this.now();
|
|
843
|
+
if (account.tokens.authMethod !== "oauth") {
|
|
844
|
+
const existing = this.store.get("kimi", account.id, now);
|
|
845
|
+
if (existing?.windows.every((window) => window.state === "unsupported")) {
|
|
846
|
+
return Promise.resolve(existing);
|
|
847
|
+
}
|
|
848
|
+
const snapshot = this.unsupportedSnapshot(account.id, now);
|
|
849
|
+
this.store.set(snapshot);
|
|
850
|
+
return Promise.resolve(snapshot);
|
|
851
|
+
}
|
|
852
|
+
const cached = this.store.get("kimi", account.id, now);
|
|
853
|
+
if (!options.force && cached && this.isCacheValid(cached, now, options.refreshAheadMs)) {
|
|
854
|
+
return Promise.resolve(cached);
|
|
855
|
+
}
|
|
856
|
+
const running = this.inFlight.get(account.id);
|
|
857
|
+
if (running) return running;
|
|
858
|
+
const promise = this.fetchAccount(account.id, account.tokens).catch(() => this.failureSnapshot(account.id, "kimi_usage_request_failed", this.now())).finally(() => this.inFlight.delete(account.id));
|
|
859
|
+
this.inFlight.set(account.id, promise);
|
|
860
|
+
return promise;
|
|
861
|
+
}
|
|
862
|
+
isCacheValid(snapshot, now, refreshAheadMs) {
|
|
863
|
+
if (snapshot.windows.every((window) => window.state === "unsupported")) return true;
|
|
864
|
+
const expiresAt = snapshot.expiresAt ? Date.parse(snapshot.expiresAt) : 0;
|
|
865
|
+
const ahead = typeof refreshAheadMs === "number" && Number.isFinite(refreshAheadMs) ? Math.max(0, refreshAheadMs) : 0;
|
|
866
|
+
return Number.isFinite(expiresAt) && expiresAt > now + ahead;
|
|
867
|
+
}
|
|
868
|
+
async fetchAccount(accountId, tokens) {
|
|
869
|
+
let accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
|
|
870
|
+
if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
|
|
871
|
+
let response = await this.request(accountId, accessToken, tokens);
|
|
872
|
+
if (response.status === 401) {
|
|
873
|
+
const refreshed = await this.credentials.refreshAccountToken("kimi", accountId);
|
|
874
|
+
if (!refreshed) return this.failureSnapshot(accountId, "kimi_usage_unauthorized", this.now());
|
|
875
|
+
accessToken = await this.credentials.getAccessTokenForAccount("kimi", accountId);
|
|
876
|
+
if (!accessToken) return this.failureSnapshot(accountId, "kimi_usage_token_unavailable", this.now());
|
|
877
|
+
response = await this.request(accountId, accessToken, tokens);
|
|
878
|
+
}
|
|
879
|
+
if (response.status === 403) {
|
|
880
|
+
const snapshot2 = this.unsupportedSnapshot(accountId, this.now(), "kimi_usage_unsupported");
|
|
881
|
+
this.store.set(snapshot2);
|
|
882
|
+
return snapshot2;
|
|
883
|
+
}
|
|
884
|
+
if (!response.ok) return this.failureSnapshot(accountId, "kimi_usage_http_error", this.now());
|
|
885
|
+
let payload;
|
|
886
|
+
try {
|
|
887
|
+
payload = await response.json();
|
|
888
|
+
} catch {
|
|
889
|
+
return this.failureSnapshot(accountId, "kimi_usage_invalid_response", this.now());
|
|
890
|
+
}
|
|
891
|
+
const now = this.now();
|
|
892
|
+
const windows = parseKimiUsagePayload(payload, now);
|
|
893
|
+
const snapshot = {
|
|
894
|
+
providerId: "kimi",
|
|
895
|
+
accountId,
|
|
896
|
+
source: "oauth-usage-api",
|
|
897
|
+
observedAt: new Date(now).toISOString(),
|
|
898
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
899
|
+
windows: windows.length > 0 ? windows : [
|
|
900
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
901
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
902
|
+
],
|
|
903
|
+
...windows.length > 0 ? {} : { lastErrorCode: "kimi_usage_invalid_response" }
|
|
904
|
+
};
|
|
905
|
+
this.store.set(snapshot);
|
|
906
|
+
return snapshot;
|
|
907
|
+
}
|
|
908
|
+
request(accountId, accessToken, tokens) {
|
|
909
|
+
return this.fetchImpl(KIMI_USAGE_URL, {
|
|
910
|
+
method: "GET",
|
|
911
|
+
headers: {
|
|
912
|
+
Authorization: `Bearer ${accessToken}`,
|
|
913
|
+
Accept: "application/json",
|
|
914
|
+
...kimiFingerprintHeaders(tokens.deviceId)
|
|
915
|
+
},
|
|
916
|
+
signal: AbortSignal.timeout(15e3)
|
|
917
|
+
}, accountId);
|
|
918
|
+
}
|
|
919
|
+
failureSnapshot(accountId, code, now) {
|
|
920
|
+
const existing = this.store.get("kimi", accountId, now);
|
|
921
|
+
const snapshot = existing ? {
|
|
922
|
+
...existing,
|
|
923
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
924
|
+
windows: existing.windows.map((window) => ({
|
|
925
|
+
...window,
|
|
926
|
+
state: window.state === "unsupported" ? "unsupported" : window.usedPercent !== null || window.resetsAt ? "stale" : "unavailable"
|
|
927
|
+
})),
|
|
928
|
+
lastErrorCode: code
|
|
929
|
+
} : {
|
|
930
|
+
providerId: "kimi",
|
|
931
|
+
accountId,
|
|
932
|
+
source: "oauth-usage-api",
|
|
933
|
+
observedAt: new Date(now).toISOString(),
|
|
934
|
+
expiresAt: new Date(now + KIMI_ALLOWANCE_CACHE_MS).toISOString(),
|
|
935
|
+
windows: [
|
|
936
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
937
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
938
|
+
],
|
|
939
|
+
lastErrorCode: code
|
|
940
|
+
};
|
|
941
|
+
this.store.set(snapshot);
|
|
942
|
+
return snapshot;
|
|
943
|
+
}
|
|
944
|
+
unsupportedSnapshot(accountId, now, code = "kimi_usage_unsupported_auth") {
|
|
945
|
+
return {
|
|
946
|
+
providerId: "kimi",
|
|
947
|
+
accountId,
|
|
948
|
+
source: "oauth-usage-api",
|
|
949
|
+
observedAt: new Date(now).toISOString(),
|
|
950
|
+
windows: [
|
|
951
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unsupported" },
|
|
952
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unsupported" }
|
|
953
|
+
],
|
|
954
|
+
lastErrorCode: code
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
// src/allowance/OpenCodeGoAllowanceCollector.ts
|
|
960
|
+
import {
|
|
961
|
+
getSharedAccountAllowanceStore as getSharedAccountAllowanceStore4
|
|
962
|
+
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
963
|
+
import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
964
|
+
import { normalizeOpenCodeGoBaseUrl } from "@omnicross/subscriptions";
|
|
965
|
+
var OPENCODEGO_ALLOWANCE_CACHE_MS = 5 * 6e4;
|
|
966
|
+
var OPENCODEGO_DEFAULT_GO_BASE = "https://opencode.ai/zen/go";
|
|
967
|
+
function finitePercent3(value) {
|
|
968
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
969
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
970
|
+
return Number.isFinite(parsed) && parsed >= 0 && parsed <= 100 ? parsed : null;
|
|
971
|
+
}
|
|
972
|
+
function isoInstant2(value) {
|
|
973
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
974
|
+
const time = Date.parse(value);
|
|
975
|
+
return Number.isFinite(time) ? new Date(time).toISOString() : void 0;
|
|
976
|
+
}
|
|
977
|
+
function secondsUntil4(instant, now) {
|
|
978
|
+
if (!instant) return void 0;
|
|
979
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
980
|
+
}
|
|
981
|
+
function windowFromPayload3(id, label, minutes, payload, now) {
|
|
982
|
+
const statusRateLimited = payload?.status === "rate-limited";
|
|
983
|
+
const usedPercent = statusRateLimited ? 100 : finitePercent3(payload?.percent);
|
|
984
|
+
const resetsAt = isoInstant2(payload?.resetsAt);
|
|
985
|
+
return {
|
|
986
|
+
id,
|
|
987
|
+
label,
|
|
988
|
+
scope: "all",
|
|
989
|
+
usedPercent,
|
|
990
|
+
windowMinutes: minutes,
|
|
991
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
992
|
+
remainingSeconds: secondsUntil4(resetsAt, now),
|
|
993
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
var OpenCodeGoAllowanceCollector = class {
|
|
997
|
+
constructor(credentials, store = getSharedAccountAllowanceStore4(), fetchImpl = (url, init, accountId) => fetchUpstream4(url, init, { providerId: "opencodego", accountId, redactBodies: true }), now = Date.now) {
|
|
998
|
+
this.credentials = credentials;
|
|
999
|
+
this.store = store;
|
|
1000
|
+
this.fetchImpl = fetchImpl;
|
|
1001
|
+
this.now = now;
|
|
1002
|
+
}
|
|
1003
|
+
credentials;
|
|
1004
|
+
store;
|
|
1005
|
+
fetchImpl;
|
|
1006
|
+
now;
|
|
1007
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
1008
|
+
async collectMany(accounts, options = {}) {
|
|
1009
|
+
const settled = await Promise.allSettled(accounts.map((account) => this.collect(account, options)));
|
|
1010
|
+
return settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
|
|
1011
|
+
}
|
|
1012
|
+
collect(account, options = {}) {
|
|
1013
|
+
const now = this.now();
|
|
1014
|
+
const cached = this.store.get("opencodego", account.id, now);
|
|
1015
|
+
if (!options.force && cached && (cached.windows.every((window) => window.state === "unsupported") || cached.expiresAt && Date.parse(cached.expiresAt) > now + (options.refreshAheadMs ?? 0))) {
|
|
1016
|
+
return Promise.resolve(cached);
|
|
1017
|
+
}
|
|
1018
|
+
const running = this.inFlight.get(account.id);
|
|
1019
|
+
if (running) return running;
|
|
1020
|
+
const promise = this.fetchAccount(account).catch(() => this.failureSnapshot(account.id, this.now())).finally(() => this.inFlight.delete(account.id));
|
|
1021
|
+
this.inFlight.set(account.id, promise);
|
|
1022
|
+
return promise;
|
|
1023
|
+
}
|
|
1024
|
+
async fetchAccount(account) {
|
|
1025
|
+
const apiKey = await this.credentials.getAccessTokenForAccount("opencodego", account.id);
|
|
1026
|
+
if (!apiKey) return this.failureSnapshot(account.id, this.now());
|
|
1027
|
+
const base = account.tokens.baseUrl ? normalizeOpenCodeGoBaseUrl(account.tokens.baseUrl) : OPENCODEGO_DEFAULT_GO_BASE;
|
|
1028
|
+
const response = await this.fetchImpl(`${base}/v1/usage`, {
|
|
1029
|
+
method: "GET",
|
|
1030
|
+
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
1031
|
+
signal: AbortSignal.timeout(15e3)
|
|
1032
|
+
}, account.id);
|
|
1033
|
+
if (response.status === 401 || response.status === 403) {
|
|
1034
|
+
return this.failureSnapshot(account.id, this.now(), "opencodego_usage_unauthorized");
|
|
1035
|
+
}
|
|
1036
|
+
if (!response.ok) return this.failureSnapshot(account.id, this.now());
|
|
1037
|
+
let payload;
|
|
1038
|
+
try {
|
|
1039
|
+
payload = await response.json();
|
|
1040
|
+
} catch {
|
|
1041
|
+
return this.failureSnapshot(account.id, this.now());
|
|
1042
|
+
}
|
|
1043
|
+
const usage = payload && typeof payload === "object" && !Array.isArray(payload) ? payload.usage : void 0;
|
|
1044
|
+
const now = this.now();
|
|
1045
|
+
const snapshot = {
|
|
1046
|
+
providerId: "opencodego",
|
|
1047
|
+
accountId: account.id,
|
|
1048
|
+
source: "oauth-usage-api",
|
|
1049
|
+
observedAt: new Date(now).toISOString(),
|
|
1050
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1051
|
+
// Monthly deliberately omitted (module doc).
|
|
1052
|
+
windows: [
|
|
1053
|
+
windowFromPayload3("five-hour", "5 hours", 5 * 60, usage?.rolling ?? void 0, now),
|
|
1054
|
+
windowFromPayload3("seven-day", "7 days", 7 * 24 * 60, usage?.weekly ?? void 0, now)
|
|
1055
|
+
]
|
|
1056
|
+
};
|
|
1057
|
+
this.store.set(snapshot);
|
|
1058
|
+
return snapshot;
|
|
1059
|
+
}
|
|
1060
|
+
failureSnapshot(accountId, now, code = "opencodego_usage_request_failed") {
|
|
1061
|
+
const existing = this.store.get("opencodego", accountId, now);
|
|
1062
|
+
const snapshot = existing ? {
|
|
1063
|
+
...existing,
|
|
1064
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1065
|
+
windows: existing.windows.map((window) => ({
|
|
1066
|
+
...window,
|
|
1067
|
+
state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
|
|
1068
|
+
})),
|
|
1069
|
+
lastErrorCode: code
|
|
1070
|
+
} : {
|
|
1071
|
+
providerId: "opencodego",
|
|
1072
|
+
accountId,
|
|
1073
|
+
source: "oauth-usage-api",
|
|
1074
|
+
observedAt: new Date(now).toISOString(),
|
|
1075
|
+
expiresAt: new Date(now + OPENCODEGO_ALLOWANCE_CACHE_MS).toISOString(),
|
|
1076
|
+
windows: [
|
|
1077
|
+
{ id: "five-hour", label: "5 hours", scope: "all", usedPercent: null, state: "unavailable" },
|
|
1078
|
+
{ id: "seven-day", label: "7 days", scope: "all", usedPercent: null, state: "unavailable" }
|
|
1079
|
+
],
|
|
1080
|
+
lastErrorCode: code
|
|
1081
|
+
};
|
|
1082
|
+
this.store.set(snapshot);
|
|
1083
|
+
return snapshot;
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
|
|
381
1087
|
// src/allowance/AccountAllowanceService.ts
|
|
382
1088
|
function codexUnavailable(accountId, now) {
|
|
383
1089
|
return {
|
|
@@ -393,26 +1099,30 @@ function codexUnavailable(accountId, now) {
|
|
|
393
1099
|
};
|
|
394
1100
|
}
|
|
395
1101
|
var AccountAllowanceService = class {
|
|
396
|
-
constructor(credentials, store =
|
|
1102
|
+
constructor(credentials, store = getSharedAccountAllowanceStore5(), collector, codexCollector, kimiCollector, opencodegoCollector, now = Date.now) {
|
|
397
1103
|
this.credentials = credentials;
|
|
398
1104
|
this.store = store;
|
|
399
1105
|
this.now = now;
|
|
400
1106
|
this.claudeCollector = collector ?? new ClaudeAllowanceCollector(credentials, store);
|
|
1107
|
+
this.codexCollector = codexCollector ?? new CodexAllowanceCollector(credentials, store);
|
|
1108
|
+
this.kimiCollector = kimiCollector ?? new KimiAllowanceCollector(credentials, store);
|
|
1109
|
+
this.opencodegoCollector = opencodegoCollector ?? new OpenCodeGoAllowanceCollector(credentials, store);
|
|
401
1110
|
}
|
|
402
1111
|
credentials;
|
|
403
1112
|
store;
|
|
404
1113
|
now;
|
|
405
1114
|
claudeCollector;
|
|
1115
|
+
codexCollector;
|
|
1116
|
+
kimiCollector;
|
|
1117
|
+
opencodegoCollector;
|
|
406
1118
|
/**
|
|
407
|
-
* Read all/filtered snapshots. Claude's five-minute
|
|
408
|
-
*
|
|
1119
|
+
* Read all/filtered snapshots. Claude's and Codex's five-minute caches are
|
|
1120
|
+
* refreshed lazily on read (Codex polls `/backend-api/wham/usage`; the
|
|
1121
|
+
* passive `x-codex-*` header tap still feeds mid-flight updates).
|
|
409
1122
|
*/
|
|
410
1123
|
async list(filter = {}) {
|
|
411
1124
|
const config = await this.credentials.getFullConfig();
|
|
412
|
-
this.store.pruneToKnownAccounts(
|
|
413
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
414
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
415
|
-
]);
|
|
1125
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
416
1126
|
const wantsClaude = !filter.providerId || filter.providerId === "claude";
|
|
417
1127
|
const claudeAccounts = (config.claudeAccounts ?? []).filter(
|
|
418
1128
|
(account) => !filter.accountId || account.id === filter.accountId
|
|
@@ -423,39 +1133,90 @@ var AccountAllowanceService = class {
|
|
|
423
1133
|
(account) => !filter.accountId || account.id === filter.accountId
|
|
424
1134
|
);
|
|
425
1135
|
if (wantsCodex) {
|
|
1136
|
+
await this.codexCollector.collectMany(codexAccounts);
|
|
426
1137
|
for (const account of codexAccounts) {
|
|
427
1138
|
if (!this.store.get("codex", account.id)) this.store.set(codexUnavailable(account.id, this.now()));
|
|
428
1139
|
}
|
|
429
1140
|
}
|
|
1141
|
+
const wantsKimi = !filter.providerId || filter.providerId === "kimi";
|
|
1142
|
+
const kimiAccounts = (config.kimiAccounts ?? []).filter(
|
|
1143
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
1144
|
+
);
|
|
1145
|
+
if (wantsKimi) await this.kimiCollector.collectMany(kimiAccounts);
|
|
1146
|
+
const wantsOpenCodeGo = !filter.providerId || filter.providerId === "opencodego";
|
|
1147
|
+
const opencodegoAccounts = (config.opencodegoAccounts ?? []).filter(
|
|
1148
|
+
(account) => !filter.accountId || account.id === filter.accountId
|
|
1149
|
+
);
|
|
1150
|
+
if (wantsOpenCodeGo) await this.opencodegoCollector.collectMany(opencodegoAccounts);
|
|
430
1151
|
const known = /* @__PURE__ */ new Set();
|
|
431
1152
|
if (wantsClaude) for (const account of claudeAccounts) known.add(`claude\0${account.id}`);
|
|
432
1153
|
if (wantsCodex) for (const account of codexAccounts) known.add(`codex\0${account.id}`);
|
|
1154
|
+
if (wantsKimi) for (const account of kimiAccounts) known.add(`kimi\0${account.id}`);
|
|
1155
|
+
if (wantsOpenCodeGo) for (const account of opencodegoAccounts) known.add(`opencodego\0${account.id}`);
|
|
433
1156
|
return this.store.list(filter).filter((snapshot) => known.has(`${snapshot.providerId}\0${snapshot.accountId}`));
|
|
434
1157
|
}
|
|
1158
|
+
knownAccounts(config) {
|
|
1159
|
+
return [
|
|
1160
|
+
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
1161
|
+
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id })),
|
|
1162
|
+
...(config.kimiAccounts ?? []).map((account) => ({ providerId: "kimi", accountId: account.id })),
|
|
1163
|
+
...(config.opencodegoAccounts ?? []).map((account) => ({ providerId: "opencodego", accountId: account.id }))
|
|
1164
|
+
];
|
|
1165
|
+
}
|
|
435
1166
|
/** Force-refresh Claude usage for one account or every stored Claude account. */
|
|
436
1167
|
async refreshClaude(accountId) {
|
|
437
1168
|
const config = await this.credentials.getFullConfig();
|
|
438
|
-
this.store.pruneToKnownAccounts(
|
|
439
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
440
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
441
|
-
]);
|
|
1169
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
442
1170
|
const accounts = (config.claudeAccounts ?? []).filter(
|
|
443
1171
|
(account) => !accountId || account.id === accountId
|
|
444
1172
|
);
|
|
445
1173
|
return this.claudeCollector.collectMany(accounts, { force: true });
|
|
446
1174
|
}
|
|
447
1175
|
/**
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
1176
|
+
* Force-refresh Codex usage (`/backend-api/wham/usage`) for one account or
|
|
1177
|
+
* every stored Codex account. Replaces the old probe-request workaround —
|
|
1178
|
+
* no quota is spent reading the usage endpoint.
|
|
1179
|
+
*/
|
|
1180
|
+
async refreshCodex(accountId) {
|
|
1181
|
+
const config = await this.credentials.getFullConfig();
|
|
1182
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1183
|
+
const accounts = (config.codexAccounts ?? []).filter(
|
|
1184
|
+
(account) => !accountId || account.id === accountId
|
|
1185
|
+
);
|
|
1186
|
+
return this.codexCollector.collectMany(accounts, { force: true });
|
|
1187
|
+
}
|
|
1188
|
+
/** Force-refresh OpenCodeGo usage (`{go}/v1/usage`) for one/all accounts. */
|
|
1189
|
+
async refreshOpenCodeGo(accountId) {
|
|
1190
|
+
const config = await this.credentials.getFullConfig();
|
|
1191
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1192
|
+
const accounts = (config.opencodegoAccounts ?? []).filter(
|
|
1193
|
+
(account) => !accountId || account.id === accountId
|
|
1194
|
+
);
|
|
1195
|
+
return this.opencodegoCollector.collectMany(accounts, { force: true });
|
|
1196
|
+
}
|
|
1197
|
+
/** Force-refresh Kimi usage (`/coding/v1/usages`) for one/all accounts. */
|
|
1198
|
+
async refreshKimi(accountId) {
|
|
1199
|
+
const config = await this.credentials.getFullConfig();
|
|
1200
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
1201
|
+
const accounts = (config.kimiAccounts ?? []).filter(
|
|
1202
|
+
(account) => !accountId || account.id === accountId
|
|
1203
|
+
);
|
|
1204
|
+
return this.kimiCollector.collectMany(accounts, { force: true });
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Keep Claude + Codex + Kimi snapshots warm for allowance-aware routing. All
|
|
1208
|
+
* collectors preserve their cache + per-account in-flight coalescing; a tick
|
|
1209
|
+
* normally performs no network I/O. (Codex joined the warm path when it
|
|
1210
|
+
* gained an active `/wham/usage` collector — the passive `x-codex-*` header
|
|
1211
|
+
* tap alone could not keep the policy fed while idle.)
|
|
451
1212
|
*/
|
|
452
1213
|
async maintainClaudeCache(refreshAheadMs) {
|
|
453
1214
|
const config = await this.credentials.getFullConfig();
|
|
454
|
-
this.store.pruneToKnownAccounts(
|
|
455
|
-
...(config.claudeAccounts ?? []).map((account) => ({ providerId: "claude", accountId: account.id })),
|
|
456
|
-
...(config.codexAccounts ?? []).map((account) => ({ providerId: "codex", accountId: account.id }))
|
|
457
|
-
]);
|
|
1215
|
+
this.store.pruneToKnownAccounts(this.knownAccounts(config));
|
|
458
1216
|
await this.claudeCollector.collectMany(config.claudeAccounts ?? [], { refreshAheadMs });
|
|
1217
|
+
await this.codexCollector.collectMany(config.codexAccounts ?? [], { refreshAheadMs });
|
|
1218
|
+
await this.kimiCollector.collectMany(config.kimiAccounts ?? [], { refreshAheadMs });
|
|
1219
|
+
await this.opencodegoCollector.collectMany(config.opencodegoAccounts ?? [], { refreshAheadMs });
|
|
459
1220
|
}
|
|
460
1221
|
/** Remove a cache row as soon as an account is deleted by the admin path. */
|
|
461
1222
|
removeAccountSnapshot(providerId, accountId) {
|
|
@@ -892,7 +1653,7 @@ import {
|
|
|
892
1653
|
} from "@omnicross/contracts/image-generation-types";
|
|
893
1654
|
import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling2 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
894
1655
|
import { getSharedAccountHealth } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
895
|
-
import { fetchUpstream as
|
|
1656
|
+
import { fetchUpstream as fetchUpstream5 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
896
1657
|
|
|
897
1658
|
// src/image-generation/imagesConfigValidation.ts
|
|
898
1659
|
import { validateImagesServerConfig } from "@omnicross/core/outbound-api";
|
|
@@ -2969,7 +3730,8 @@ var VALID_PROVIDER_IDS = [
|
|
|
2969
3730
|
"claude",
|
|
2970
3731
|
"codex",
|
|
2971
3732
|
"gemini",
|
|
2972
|
-
"opencodego"
|
|
3733
|
+
"opencodego",
|
|
3734
|
+
"kimi"
|
|
2973
3735
|
];
|
|
2974
3736
|
function asSubscriptionProviderId(id) {
|
|
2975
3737
|
return VALID_PROVIDER_IDS.includes(id) ? id : null;
|
|
@@ -3105,6 +3867,18 @@ function validateGemini(body) {
|
|
|
3105
3867
|
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "lastRefreshedAt", "errorMessage"]);
|
|
3106
3868
|
return out;
|
|
3107
3869
|
}
|
|
3870
|
+
function validateKimi(body) {
|
|
3871
|
+
const authMethod = str(body["authMethod"]);
|
|
3872
|
+
const status = str(body["status"]);
|
|
3873
|
+
if (!authMethod || !OAUTH_AUTH_METHODS.has(authMethod)) return null;
|
|
3874
|
+
if (!status || !TOKEN_STATUSES.has(status)) return null;
|
|
3875
|
+
const out = {
|
|
3876
|
+
authMethod,
|
|
3877
|
+
status
|
|
3878
|
+
};
|
|
3879
|
+
copyOptional(out, body, ["accessToken", "refreshToken", "expiresAt", "accountId", "deviceId", "lastRefreshedAt", "errorMessage"]);
|
|
3880
|
+
return out;
|
|
3881
|
+
}
|
|
3108
3882
|
function validateOpenCodeGo(body) {
|
|
3109
3883
|
const authMethod = str(body["authMethod"]);
|
|
3110
3884
|
const status = str(body["status"]);
|
|
@@ -3140,6 +3914,8 @@ function validateTokenBody(providerId, body) {
|
|
|
3140
3914
|
return validateGemini(body);
|
|
3141
3915
|
case "opencodego":
|
|
3142
3916
|
return validateOpenCodeGo(body);
|
|
3917
|
+
case "kimi":
|
|
3918
|
+
return validateKimi(body);
|
|
3143
3919
|
default:
|
|
3144
3920
|
return null;
|
|
3145
3921
|
}
|
|
@@ -3169,12 +3945,12 @@ async function statusEntryFor(reader, providerId) {
|
|
|
3169
3945
|
|
|
3170
3946
|
// src/admin/accountsOAuth.ts
|
|
3171
3947
|
var OAUTH_HTTP_PROVIDERS = /* @__PURE__ */ new Set(["claude", "gemini"]);
|
|
3172
|
-
function
|
|
3948
|
+
function err3(status, message) {
|
|
3173
3949
|
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
3174
3950
|
}
|
|
3175
3951
|
function handleOAuthStart(providerId, deps) {
|
|
3176
3952
|
if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
|
|
3177
|
-
return
|
|
3953
|
+
return err3(400, `oauth not available for provider '${providerId}'`);
|
|
3178
3954
|
}
|
|
3179
3955
|
const flow = providerId === "claude" ? claudeOAuth : geminiOAuth;
|
|
3180
3956
|
const { authUrl, codeVerifier, state } = flow.generateAuthParams();
|
|
@@ -3183,23 +3959,23 @@ function handleOAuthStart(providerId, deps) {
|
|
|
3183
3959
|
}
|
|
3184
3960
|
async function handleOAuthComplete(providerId, body, deps) {
|
|
3185
3961
|
if (!OAUTH_HTTP_PROVIDERS.has(providerId)) {
|
|
3186
|
-
return
|
|
3962
|
+
return err3(400, `oauth not available for provider '${providerId}'`);
|
|
3187
3963
|
}
|
|
3188
3964
|
const sessionId = typeof body["sessionId"] === "string" ? body["sessionId"] : "";
|
|
3189
3965
|
const rawCode = typeof body["code"] === "string" ? body["code"] : "";
|
|
3190
|
-
if (!sessionId) return
|
|
3191
|
-
if (!rawCode) return
|
|
3966
|
+
if (!sessionId) return err3(400, "oauth complete requires { sessionId }");
|
|
3967
|
+
if (!rawCode) return err3(400, "oauth complete requires { code }");
|
|
3192
3968
|
const session = deps.oauthSessions.peek(sessionId);
|
|
3193
|
-
if (!session) return
|
|
3969
|
+
if (!session) return err3(410, "oauth session is unknown, expired, or already used");
|
|
3194
3970
|
if (session.providerId !== providerId) {
|
|
3195
|
-
return
|
|
3971
|
+
return err3(400, `oauth session does not match provider '${providerId}'`);
|
|
3196
3972
|
}
|
|
3197
3973
|
let code = rawCode.trim();
|
|
3198
3974
|
if (providerId === "claude") {
|
|
3199
3975
|
const [splitCode, pastedState] = code.split("#");
|
|
3200
|
-
if (!splitCode) return
|
|
3976
|
+
if (!splitCode) return err3(400, "no authorization code was provided");
|
|
3201
3977
|
if (pastedState && pastedState !== session.state) {
|
|
3202
|
-
return
|
|
3978
|
+
return err3(400, "oauth state did not match (possible CSRF) \u2014 aborting");
|
|
3203
3979
|
}
|
|
3204
3980
|
code = splitCode;
|
|
3205
3981
|
}
|
|
@@ -3209,7 +3985,7 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
3209
3985
|
block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
|
|
3210
3986
|
} catch (exchangeError) {
|
|
3211
3987
|
const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
|
|
3212
|
-
return
|
|
3988
|
+
return err3(502, `oauth token exchange failed for '${providerId}': ${reason}`);
|
|
3213
3989
|
}
|
|
3214
3990
|
deps.oauthSessions.consume(sessionId);
|
|
3215
3991
|
const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
|
|
@@ -3547,8 +4323,8 @@ function errBody(message) {
|
|
|
3547
4323
|
return { error: { type: "admin_api_error", message } };
|
|
3548
4324
|
}
|
|
3549
4325
|
var defaultCommandRunner = (command) => new Promise((resolve10) => {
|
|
3550
|
-
exec(command, { timeout: 18e4 }, (
|
|
3551
|
-
if (
|
|
4326
|
+
exec(command, { timeout: 18e4 }, (err6, _stdout, stderr) => {
|
|
4327
|
+
if (err6) resolve10({ ok: false, error: stderr.trim() || err6.message });
|
|
3552
4328
|
else resolve10({ ok: true });
|
|
3553
4329
|
});
|
|
3554
4330
|
});
|
|
@@ -3594,8 +4370,8 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
3594
4370
|
providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
|
|
3595
4371
|
model: typeof body["model"] === "string" ? body["model"] : void 0
|
|
3596
4372
|
});
|
|
3597
|
-
} catch (
|
|
3598
|
-
return { status: 400, body: errBody(
|
|
4373
|
+
} catch (err6) {
|
|
4374
|
+
return { status: 400, body: errBody(err6 instanceof Error ? err6.message : "no launch target") };
|
|
3599
4375
|
}
|
|
3600
4376
|
const id = randomUUID2();
|
|
3601
4377
|
let leaseId2;
|
|
@@ -3623,9 +4399,9 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
3623
4399
|
} else {
|
|
3624
4400
|
launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
|
|
3625
4401
|
}
|
|
3626
|
-
} catch (
|
|
3627
|
-
const status =
|
|
3628
|
-
return { status, body: errBody(
|
|
4402
|
+
} catch (err6) {
|
|
4403
|
+
const status = err6 instanceof RouteLeaseError2 ? err6.status : 400;
|
|
4404
|
+
return { status, body: errBody(err6 instanceof Error ? err6.message : "failed to build launch env") };
|
|
3629
4405
|
}
|
|
3630
4406
|
const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
|
|
3631
4407
|
const opener = ctx.opener ?? defaultTerminalOpener;
|
|
@@ -3653,9 +4429,9 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
3653
4429
|
onFailure: onSessionEnd
|
|
3654
4430
|
});
|
|
3655
4431
|
if (cleanup) openerCleanup = cleanup;
|
|
3656
|
-
} catch (
|
|
4432
|
+
} catch (err6) {
|
|
3657
4433
|
onSessionEnd();
|
|
3658
|
-
return { status: 500, body: errBody(
|
|
4434
|
+
return { status: 500, body: errBody(err6 instanceof Error ? err6.message : "failed to open terminal") };
|
|
3659
4435
|
}
|
|
3660
4436
|
if (ended) {
|
|
3661
4437
|
openerCleanup?.();
|
|
@@ -3789,7 +4565,8 @@ async function handleDashboard(deps) {
|
|
|
3789
4565
|
// src/admin/searchAdminApi.ts
|
|
3790
4566
|
import { DEFAULT_SEARCH_SERVER_CONFIG, loadServerConfig } from "@omnicross/core/outbound-api";
|
|
3791
4567
|
import { apiSearchContributions as apiSearchContributions2 } from "@omnicross/core/search/api";
|
|
3792
|
-
import { builtinHttpSearchContributions as builtinHttpSearchContributions3, createSearchHttpTransport } from "@omnicross/core/search/http";
|
|
4568
|
+
import { builtinHttpSearchContributions as builtinHttpSearchContributions3, createSearchHttpTransport as createSearchHttpTransport2 } from "@omnicross/core/search/http";
|
|
4569
|
+
import { createSearchRuntime as createSearchRuntime2 } from "@omnicross/core/search";
|
|
3793
4570
|
|
|
3794
4571
|
// src/search/searchDoctorProjection.ts
|
|
3795
4572
|
import { toSearchErrorShape } from "@omnicross/contracts/search-types";
|
|
@@ -3855,7 +4632,7 @@ function buildSearchDoctorSnapshot(contributions = builtinHttpSearchContribution
|
|
|
3855
4632
|
}
|
|
3856
4633
|
return rows;
|
|
3857
4634
|
}
|
|
3858
|
-
var SEARCH_DOCTOR_QUERY = "
|
|
4635
|
+
var SEARCH_DOCTOR_QUERY = "MDN HTTP headers documentation";
|
|
3859
4636
|
function classifyLiveSearchOutcome(providerId, outcome, checkedAt) {
|
|
3860
4637
|
if (outcome.kind === "results") {
|
|
3861
4638
|
if (outcome.count > 0) return { providerId, status: "healthy", checkedAt };
|
|
@@ -3900,9 +4677,13 @@ function classifySearchFailure(stage, code) {
|
|
|
3900
4677
|
}
|
|
3901
4678
|
|
|
3902
4679
|
// src/search/SearchAssembly.ts
|
|
4680
|
+
import { resolveUpstreamDispatcher } from "@omnicross/core/pipeline/upstreamFetch";
|
|
3903
4681
|
import { createSearchRuntime } from "@omnicross/core/search";
|
|
3904
4682
|
import { apiSearchContributions } from "@omnicross/core/search/api";
|
|
3905
|
-
import {
|
|
4683
|
+
import {
|
|
4684
|
+
builtinHttpSearchContributions as builtinHttpSearchContributions2,
|
|
4685
|
+
createSearchHttpTransport
|
|
4686
|
+
} from "@omnicross/core/search/http";
|
|
3906
4687
|
function searchEgressPolicyFrom(config) {
|
|
3907
4688
|
const hosts = config.egress.allowedPrivateHosts;
|
|
3908
4689
|
return hosts.length > 0 ? { allowedPrivateHosts: [...hosts] } : {};
|
|
@@ -3916,11 +4697,24 @@ function searchPolicyFrom(config) {
|
|
|
3916
4697
|
...maxAttempts !== void 0 ? { maxAttempts } : {}
|
|
3917
4698
|
};
|
|
3918
4699
|
}
|
|
4700
|
+
function resolveSearchUpstreamDispatcher(url) {
|
|
4701
|
+
return resolveUpstreamDispatcher({ url });
|
|
4702
|
+
}
|
|
4703
|
+
var searchUpstreamProxyConfig = createUpstreamProxyResolver();
|
|
4704
|
+
function resolveSearchUpstreamProxyConfig(url) {
|
|
4705
|
+
return searchUpstreamProxyConfig({ url });
|
|
4706
|
+
}
|
|
3919
4707
|
function searchContributionsFrom(config) {
|
|
3920
4708
|
return [
|
|
3921
|
-
...builtinHttpSearchContributions2(
|
|
4709
|
+
...builtinHttpSearchContributions2(
|
|
4710
|
+
createSearchHttpTransport({
|
|
4711
|
+
resolveProxyDispatcher: resolveSearchUpstreamDispatcher,
|
|
4712
|
+
resolveProxyConfig: resolveSearchUpstreamProxyConfig
|
|
4713
|
+
})
|
|
4714
|
+
),
|
|
3922
4715
|
...apiSearchContributions(config.providers, {
|
|
3923
|
-
egressPolicy: searchEgressPolicyFrom(config)
|
|
4716
|
+
egressPolicy: searchEgressPolicyFrom(config),
|
|
4717
|
+
resolveProxyDispatcher: resolveSearchUpstreamDispatcher
|
|
3924
4718
|
})
|
|
3925
4719
|
];
|
|
3926
4720
|
}
|
|
@@ -4064,6 +4858,18 @@ async function handleSearchDiagnostics(res, deps) {
|
|
|
4064
4858
|
};
|
|
4065
4859
|
return writeJson(res, 200, { diagnostics: snapshot });
|
|
4066
4860
|
}
|
|
4861
|
+
function persistedSearchContributions(search, fetchImpl) {
|
|
4862
|
+
if (fetchImpl) {
|
|
4863
|
+
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4864
|
+
return [
|
|
4865
|
+
...builtinHttpSearchContributions3(
|
|
4866
|
+
createSearchHttpTransport2({ fetch: fetchImpl, egressPolicy })
|
|
4867
|
+
),
|
|
4868
|
+
...apiSearchContributions2(search.providers, { egressPolicy, fetchImpl })
|
|
4869
|
+
];
|
|
4870
|
+
}
|
|
4871
|
+
return searchContributionsFrom(search);
|
|
4872
|
+
}
|
|
4067
4873
|
async function handleSearchTest(req, res, deps) {
|
|
4068
4874
|
const status = deps.searchStatus;
|
|
4069
4875
|
const body = await readBodyOrReject(req, res);
|
|
@@ -4081,16 +4887,8 @@ async function handleSearchTest(req, res, deps) {
|
|
|
4081
4887
|
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4082
4888
|
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4083
4889
|
}
|
|
4084
|
-
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4085
4890
|
const fetchImpl = status.testFetch;
|
|
4086
|
-
const
|
|
4087
|
-
const contributions = [
|
|
4088
|
-
...builtinHttpSearchContributions3(transport),
|
|
4089
|
-
...apiSearchContributions2(search.providers, {
|
|
4090
|
-
egressPolicy,
|
|
4091
|
-
...fetchImpl ? { fetchImpl } : {}
|
|
4092
|
-
})
|
|
4093
|
-
];
|
|
4891
|
+
const contributions = persistedSearchContributions(search, fetchImpl);
|
|
4094
4892
|
const contribution = contributions.find((c) => c.id === providerId);
|
|
4095
4893
|
if (!contribution) {
|
|
4096
4894
|
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
@@ -4138,41 +4936,42 @@ async function handleSearchQuery(req, res, deps) {
|
|
|
4138
4936
|
if (!KEYLESS_HTTP_PROVIDER_IDS.has(providerId) && providers[providerId] === void 0) {
|
|
4139
4937
|
return writeErr(res, 400, `search provider '${providerId}' is not configured`);
|
|
4140
4938
|
}
|
|
4141
|
-
const egressPolicy = searchEgressPolicyFrom(search);
|
|
4142
4939
|
const fetchImpl = status.testFetch;
|
|
4143
|
-
const
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
}
|
|
4940
|
+
const runtime = createSearchRuntime2({
|
|
4941
|
+
contributions: persistedSearchContributions(search, fetchImpl),
|
|
4942
|
+
policy: {
|
|
4943
|
+
...searchPolicyFrom(search),
|
|
4944
|
+
// The panel always walks: it answers "does a search WORK for this
|
|
4945
|
+
// operator", not "does this one provider behave" — that is `/test`'s
|
|
4946
|
+
// job. The persisted policy's allowlist still bounds the walk.
|
|
4947
|
+
fallbackEnabled: true,
|
|
4948
|
+
preferred: providerId
|
|
4949
|
+
}
|
|
4950
|
+
});
|
|
4155
4951
|
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4156
4952
|
try {
|
|
4157
|
-
const
|
|
4953
|
+
const orchestrated = await runtime.search({ query: query2, options: { maxResults: 5 } });
|
|
4954
|
+
const results = orchestrated.results;
|
|
4158
4955
|
const sanitized = results.slice(0, SEARCH_QUERY_MAX_RESULTS).map((result) => ({
|
|
4159
4956
|
title: sanitizeResultField(result.title, SEARCH_RESULT_FIELD_CAPS.title),
|
|
4160
4957
|
url: sanitizeResultField(result.url, SEARCH_RESULT_FIELD_CAPS.url),
|
|
4161
4958
|
content: sanitizeResultField(result.content, SEARCH_RESULT_FIELD_CAPS.content)
|
|
4162
4959
|
}));
|
|
4163
|
-
const diagnostic = sanitized.length === 0 ? { providerId:
|
|
4164
|
-
|
|
4960
|
+
const diagnostic = sanitized.length === 0 ? { providerId: orchestrated.providerId, status: "healthy", checkedAt } : classifyLiveSearchOutcome(
|
|
4961
|
+
orchestrated.providerId,
|
|
4165
4962
|
{ kind: "results", count: sanitized.length },
|
|
4166
4963
|
checkedAt
|
|
4167
4964
|
);
|
|
4168
4965
|
const response = {
|
|
4169
4966
|
diagnostic,
|
|
4967
|
+
providerUsed: orchestrated.providerId,
|
|
4968
|
+
fallbackCount: orchestrated.fallbackCount,
|
|
4170
4969
|
resultCount: sanitized.length,
|
|
4171
4970
|
results: sanitized
|
|
4172
4971
|
};
|
|
4173
4972
|
return writeJson(res, 200, { result: response });
|
|
4174
4973
|
} catch (error) {
|
|
4175
|
-
const diagnostic = classifyLiveSearchOutcome(
|
|
4974
|
+
const diagnostic = classifyLiveSearchOutcome(providerId, { kind: "failure", error }, checkedAt);
|
|
4176
4975
|
const response = { diagnostic };
|
|
4177
4976
|
return writeJson(res, 200, { result: response });
|
|
4178
4977
|
}
|
|
@@ -4181,7 +4980,7 @@ async function handleSearchQuery(req, res, deps) {
|
|
|
4181
4980
|
// src/admin/searchAdminView.ts
|
|
4182
4981
|
var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
|
|
4183
4982
|
var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
|
|
4184
|
-
function
|
|
4983
|
+
function isRecord2(value) {
|
|
4185
4984
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4186
4985
|
}
|
|
4187
4986
|
function redactSearchServerConfig(search) {
|
|
@@ -4231,13 +5030,13 @@ function resolveSecretField(entry, field, stored) {
|
|
|
4231
5030
|
else delete entry[field];
|
|
4232
5031
|
}
|
|
4233
5032
|
function preserveSearchSecrets(incoming, current) {
|
|
4234
|
-
if (!
|
|
5033
|
+
if (!isRecord2(incoming)) return incoming;
|
|
4235
5034
|
const section = { ...incoming };
|
|
4236
5035
|
const providersValue = section["providers"];
|
|
4237
|
-
if (!
|
|
5036
|
+
if (!isRecord2(providersValue)) return section;
|
|
4238
5037
|
const providers = {};
|
|
4239
5038
|
for (const [id, entryValue] of Object.entries(providersValue)) {
|
|
4240
|
-
if (!
|
|
5039
|
+
if (!isRecord2(entryValue)) {
|
|
4241
5040
|
providers[id] = entryValue;
|
|
4242
5041
|
continue;
|
|
4243
5042
|
}
|
|
@@ -4315,7 +5114,7 @@ function parseKeyPolicyBody(body) {
|
|
|
4315
5114
|
var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
|
|
4316
5115
|
var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
|
|
4317
5116
|
var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
|
|
4318
|
-
function
|
|
5117
|
+
function isRecord3(value) {
|
|
4319
5118
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4320
5119
|
}
|
|
4321
5120
|
function nonBlank(value) {
|
|
@@ -4335,7 +5134,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4335
5134
|
const ids = /* @__PURE__ */ new Set();
|
|
4336
5135
|
raw.forEach((entry, index) => {
|
|
4337
5136
|
const path2 = `bindings[${index}]`;
|
|
4338
|
-
if (!
|
|
5137
|
+
if (!isRecord3(entry)) {
|
|
4339
5138
|
errors.push(`${path2} must be an object`);
|
|
4340
5139
|
return;
|
|
4341
5140
|
}
|
|
@@ -4364,12 +5163,12 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4364
5163
|
} else if (entry.modelMappings.length > 100) {
|
|
4365
5164
|
errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
|
|
4366
5165
|
} else if (entry.modelMappings.some(
|
|
4367
|
-
(mapping) => !
|
|
5166
|
+
(mapping) => !isRecord3(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
|
|
4368
5167
|
)) {
|
|
4369
5168
|
errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
|
|
4370
5169
|
}
|
|
4371
5170
|
}
|
|
4372
|
-
if (!
|
|
5171
|
+
if (!isRecord3(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
|
|
4373
5172
|
errors.push(`${path2}.target is invalid`);
|
|
4374
5173
|
} else {
|
|
4375
5174
|
if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
|
|
@@ -4384,7 +5183,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
4384
5183
|
}
|
|
4385
5184
|
}
|
|
4386
5185
|
if (entry.modelMap !== void 0) {
|
|
4387
|
-
if (!
|
|
5186
|
+
if (!isRecord3(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
|
|
4388
5187
|
errors.push(`${path2}.modelMap must contain string values`);
|
|
4389
5188
|
}
|
|
4390
5189
|
}
|
|
@@ -4694,7 +5493,8 @@ var PROVIDER_KEYS = {
|
|
|
4694
5493
|
block: "opencodego",
|
|
4695
5494
|
accounts: "opencodegoAccounts",
|
|
4696
5495
|
active: "activeOpencodegoAccountId"
|
|
4697
|
-
}
|
|
5496
|
+
},
|
|
5497
|
+
kimi: { block: "kimi", accounts: "kimiAccounts", active: "activeKimiAccountId" }
|
|
4698
5498
|
};
|
|
4699
5499
|
function clone(value) {
|
|
4700
5500
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -5216,7 +6016,7 @@ async function applyImport(packString, passphrase, mode, deps, parseProviderInpu
|
|
|
5216
6016
|
}
|
|
5217
6017
|
|
|
5218
6018
|
// src/admin/adminMigration.ts
|
|
5219
|
-
function
|
|
6019
|
+
function err4(status, message) {
|
|
5220
6020
|
return { status, body: { error: { type: "admin_api_error", message } } };
|
|
5221
6021
|
}
|
|
5222
6022
|
async function handleExport(body, deps) {
|
|
@@ -5226,30 +6026,30 @@ async function handleExport(body, deps) {
|
|
|
5226
6026
|
return { status: 200, body: { pack, version: BUNDLE_VERSION } };
|
|
5227
6027
|
} catch (error) {
|
|
5228
6028
|
if (error instanceof WeakPassphraseError) {
|
|
5229
|
-
return
|
|
6029
|
+
return err4(400, error.message);
|
|
5230
6030
|
}
|
|
5231
|
-
return
|
|
6031
|
+
return err4(500, "failed to build the migration pack");
|
|
5232
6032
|
}
|
|
5233
6033
|
}
|
|
5234
6034
|
async function handleImport(body, deps) {
|
|
5235
6035
|
const blob = typeof body["blob"] === "string" ? body["blob"] : "";
|
|
5236
6036
|
const passphrase = typeof body["passphrase"] === "string" ? body["passphrase"] : "";
|
|
5237
6037
|
const mode = body["mode"] === "overwrite" ? "overwrite" : "merge";
|
|
5238
|
-
if (!blob) return
|
|
6038
|
+
if (!blob) return err4(400, "import requires { blob }");
|
|
5239
6039
|
try {
|
|
5240
6040
|
const counts = await applyImport(blob, passphrase, mode, deps, deps.parseProviderInput);
|
|
5241
6041
|
return { status: 200, body: counts };
|
|
5242
6042
|
} catch (error) {
|
|
5243
6043
|
if (error instanceof WeakPassphraseError) {
|
|
5244
|
-
return
|
|
6044
|
+
return err4(400, error.message);
|
|
5245
6045
|
}
|
|
5246
|
-
return
|
|
6046
|
+
return err4(400, error instanceof Error ? error.message : "import failed");
|
|
5247
6047
|
}
|
|
5248
6048
|
}
|
|
5249
6049
|
|
|
5250
6050
|
// src/admin/usagePricing.ts
|
|
5251
6051
|
import { getSharedUsageThroughputTracker } from "@omnicross/core/usage";
|
|
5252
|
-
var
|
|
6052
|
+
var err5 = (status, message) => ({
|
|
5253
6053
|
status,
|
|
5254
6054
|
body: { error: { type: "admin_api_error", message } }
|
|
5255
6055
|
});
|
|
@@ -5262,7 +6062,7 @@ function parseRange(query2) {
|
|
|
5262
6062
|
const startTs = parseFiniteInt(query2.get("startTs"));
|
|
5263
6063
|
const endTs = parseFiniteInt(query2.get("endTs"));
|
|
5264
6064
|
if (startTs === null || endTs === null) {
|
|
5265
|
-
return
|
|
6065
|
+
return err5(400, "startTs and endTs are required finite-integer unix-millis query params");
|
|
5266
6066
|
}
|
|
5267
6067
|
return { startTs, endTs };
|
|
5268
6068
|
}
|
|
@@ -5287,14 +6087,14 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
5287
6087
|
case "timeseries": {
|
|
5288
6088
|
const bucket = query2.get("bucket");
|
|
5289
6089
|
if (bucket !== "hour" && bucket !== "day" && bucket !== "month") {
|
|
5290
|
-
return
|
|
6090
|
+
return err5(400, "bucket must be one of 'hour', 'day', 'month'");
|
|
5291
6091
|
}
|
|
5292
6092
|
const now = Date.now();
|
|
5293
6093
|
const clamped = { startTs: range.startTs, endTs: Math.min(range.endTs, now) };
|
|
5294
6094
|
if (clamped.startTs < clamped.endTs) {
|
|
5295
6095
|
const projected = Math.ceil((clamped.endTs - clamped.startTs) / BUCKET_SPAN_MS[bucket]) + 1;
|
|
5296
6096
|
if (projected > MAX_TIMESERIES_BUCKETS) {
|
|
5297
|
-
return
|
|
6097
|
+
return err5(
|
|
5298
6098
|
400,
|
|
5299
6099
|
`requested range projects ~${projected} '${bucket}' buckets (max ${MAX_TIMESERIES_BUCKETS}); narrow the range or use a coarser bucket`
|
|
5300
6100
|
);
|
|
@@ -5317,7 +6117,7 @@ async function handleUsageGet(view, query2, deps) {
|
|
|
5317
6117
|
};
|
|
5318
6118
|
}
|
|
5319
6119
|
default:
|
|
5320
|
-
return
|
|
6120
|
+
return err5(404, `unknown usage view '${view ?? ""}'`);
|
|
5321
6121
|
}
|
|
5322
6122
|
}
|
|
5323
6123
|
function poolKeyLabels(cfg) {
|
|
@@ -5366,7 +6166,7 @@ async function handlePricingList(deps) {
|
|
|
5366
6166
|
async function handlePricingUpsert(body, deps) {
|
|
5367
6167
|
const input = parsePricingEntryInput(body);
|
|
5368
6168
|
if (!input) {
|
|
5369
|
-
return
|
|
6169
|
+
return err5(400, "invalid pricing entry (providerId, modelId, finite numeric inputPricePer1m/outputPricePer1m required)");
|
|
5370
6170
|
}
|
|
5371
6171
|
const entry = await deps.pricingEngine.upsertManual(input);
|
|
5372
6172
|
return { status: 200, body: { entry } };
|
|
@@ -5375,7 +6175,7 @@ async function handlePricingDelete(query2, deps) {
|
|
|
5375
6175
|
const providerId = query2.get("providerId")?.trim() ?? "";
|
|
5376
6176
|
const modelId = query2.get("modelId")?.trim() ?? "";
|
|
5377
6177
|
if (!providerId || !modelId) {
|
|
5378
|
-
return
|
|
6178
|
+
return err5(400, "delete requires providerId and modelId query params");
|
|
5379
6179
|
}
|
|
5380
6180
|
const deleted = await deps.pricingStore.delete(providerId, modelId);
|
|
5381
6181
|
if (deleted) await deps.pricingEngine.invalidateCache();
|
|
@@ -5395,13 +6195,13 @@ async function handlePricingFetchLatest(deps) {
|
|
|
5395
6195
|
}
|
|
5396
6196
|
};
|
|
5397
6197
|
} catch (e) {
|
|
5398
|
-
return
|
|
6198
|
+
return err5(502, `pricing-source fetch failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
5399
6199
|
}
|
|
5400
6200
|
}
|
|
5401
6201
|
async function handlePricingResolveConflicts(body, deps) {
|
|
5402
6202
|
const raw = body["resolutions"];
|
|
5403
6203
|
if (!Array.isArray(raw)) {
|
|
5404
|
-
return
|
|
6204
|
+
return err5(400, "resolve-conflicts requires { resolutions: [...] }");
|
|
5405
6205
|
}
|
|
5406
6206
|
const currentRows = await deps.pricingStore.getAll();
|
|
5407
6207
|
const userEditedKeys = new Set(
|
|
@@ -5411,21 +6211,21 @@ async function handlePricingResolveConflicts(body, deps) {
|
|
|
5411
6211
|
const pendingIncoming = /* @__PURE__ */ new Map();
|
|
5412
6212
|
let staleCount = 0;
|
|
5413
6213
|
for (const item of raw) {
|
|
5414
|
-
if (!item || typeof item !== "object") return
|
|
6214
|
+
if (!item || typeof item !== "object") return err5(400, "invalid resolution entry");
|
|
5415
6215
|
const r = item;
|
|
5416
6216
|
const action = r["action"];
|
|
5417
6217
|
if (action !== "overwrite" && action !== "skip") {
|
|
5418
|
-
return
|
|
6218
|
+
return err5(400, "resolution action must be 'overwrite' or 'skip'");
|
|
5419
6219
|
}
|
|
5420
6220
|
const providerId = typeof r["providerId"] === "string" && r["providerId"].trim() ? r["providerId"].trim() : "";
|
|
5421
6221
|
const modelId = typeof r["modelId"] === "string" && r["modelId"].trim() ? r["modelId"].trim() : "";
|
|
5422
6222
|
if (!providerId || !modelId) {
|
|
5423
|
-
return
|
|
6223
|
+
return err5(400, "each resolution requires top-level providerId and modelId");
|
|
5424
6224
|
}
|
|
5425
6225
|
const incoming = parsePricingEntryInput(r["incoming"]);
|
|
5426
|
-
if (!incoming) return
|
|
6226
|
+
if (!incoming) return err5(400, "each resolution must echo a valid incoming pricing entry");
|
|
5427
6227
|
if (incoming.providerId !== providerId || incoming.modelId !== modelId) {
|
|
5428
|
-
return
|
|
6228
|
+
return err5(400, "resolution providerId/modelId must match the echoed incoming entry");
|
|
5429
6229
|
}
|
|
5430
6230
|
const key = `${providerId}::${modelId}`;
|
|
5431
6231
|
if (action === "overwrite" && !userEditedKeys.has(key)) {
|
|
@@ -5470,7 +6270,7 @@ function query(req) {
|
|
|
5470
6270
|
}
|
|
5471
6271
|
function allowanceProvider(value) {
|
|
5472
6272
|
if (!value) return void 0;
|
|
5473
|
-
return value === "claude" || value === "codex" ? value : null;
|
|
6273
|
+
return value === "claude" || value === "codex" || value === "kimi" || value === "opencodego" ? value : null;
|
|
5474
6274
|
}
|
|
5475
6275
|
async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
5476
6276
|
if (!service) return writeError2(res, 501, "account allowance service is not available");
|
|
@@ -5484,7 +6284,9 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5484
6284
|
const params = query(req);
|
|
5485
6285
|
const pathProvider = rest.length >= 2 ? rest[0] : null;
|
|
5486
6286
|
const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
|
|
5487
|
-
if (providerId === null)
|
|
6287
|
+
if (providerId === null) {
|
|
6288
|
+
return writeError2(res, 400, "providerId must be claude, codex, kimi, or opencodego");
|
|
6289
|
+
}
|
|
5488
6290
|
const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
|
|
5489
6291
|
const allowances = await service.list({ providerId, accountId });
|
|
5490
6292
|
return writeJson3(res, 200, { allowances });
|
|
@@ -5494,10 +6296,37 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
5494
6296
|
const requestedProvider = allowanceProvider(
|
|
5495
6297
|
typeof body["providerId"] === "string" ? body["providerId"] : "claude"
|
|
5496
6298
|
);
|
|
5497
|
-
if (requestedProvider !== "claude") {
|
|
5498
|
-
return writeError2(res, 400, "only Claude allowances support explicit refresh");
|
|
5499
|
-
}
|
|
5500
6299
|
const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
|
|
6300
|
+
if (requestedProvider === "codex") {
|
|
6301
|
+
if (!service.refreshCodex) {
|
|
6302
|
+
return writeError2(res, 501, "codex allowance refresh is not available");
|
|
6303
|
+
}
|
|
6304
|
+
const allowances2 = await service.refreshCodex(accountId);
|
|
6305
|
+
if (accountId && allowances2.length === 0) {
|
|
6306
|
+
return writeError2(res, 404, `Codex account '${accountId}' not found`);
|
|
6307
|
+
}
|
|
6308
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
6309
|
+
}
|
|
6310
|
+
if (requestedProvider === "kimi") {
|
|
6311
|
+
if (!service.refreshKimi) {
|
|
6312
|
+
return writeError2(res, 501, "kimi allowance refresh is not available");
|
|
6313
|
+
}
|
|
6314
|
+
const allowances2 = await service.refreshKimi(accountId);
|
|
6315
|
+
if (accountId && allowances2.length === 0) {
|
|
6316
|
+
return writeError2(res, 404, `Kimi account '${accountId}' not found`);
|
|
6317
|
+
}
|
|
6318
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
6319
|
+
}
|
|
6320
|
+
if (requestedProvider === "opencodego") {
|
|
6321
|
+
if (!service.refreshOpenCodeGo) {
|
|
6322
|
+
return writeError2(res, 501, "opencodego allowance refresh is not available");
|
|
6323
|
+
}
|
|
6324
|
+
const allowances2 = await service.refreshOpenCodeGo(accountId);
|
|
6325
|
+
if (accountId && allowances2.length === 0) {
|
|
6326
|
+
return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
|
|
6327
|
+
}
|
|
6328
|
+
return writeJson3(res, 200, { allowances: allowances2 });
|
|
6329
|
+
}
|
|
5501
6330
|
const allowances = await service.refreshClaude(accountId);
|
|
5502
6331
|
if (accountId && allowances.length === 0) {
|
|
5503
6332
|
return writeError2(res, 404, `Claude account '${accountId}' not found`);
|
|
@@ -5668,8 +6497,8 @@ async function handleAdminApi(req, res, path2, deps) {
|
|
|
5668
6497
|
default:
|
|
5669
6498
|
return writeJsonError(res, 404, `unknown admin resource '${resource}'`);
|
|
5670
6499
|
}
|
|
5671
|
-
} catch (
|
|
5672
|
-
writeJsonError(res, 500,
|
|
6500
|
+
} catch (err6) {
|
|
6501
|
+
writeJsonError(res, 500, err6 instanceof Error ? err6.message : String(err6));
|
|
5673
6502
|
}
|
|
5674
6503
|
}
|
|
5675
6504
|
function requestQuery(req) {
|
|
@@ -5739,6 +6568,9 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
5739
6568
|
if (method === "POST" && rest.length === 4 && rest[1] === "keys" && rest[3] === "enabled") {
|
|
5740
6569
|
return await handleToggleProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
5741
6570
|
}
|
|
6571
|
+
if (method === "POST" && rest.length === 5 && rest[1] === "keys" && rest[3] === "quota" && rest[4] === "refresh") {
|
|
6572
|
+
return await handleProviderKeyQuotaRefresh(res, rest[0], rest[2], cfg, deps);
|
|
6573
|
+
}
|
|
5742
6574
|
if (method === "PUT" && rest.length === 3 && rest[1] === "keys") {
|
|
5743
6575
|
return await handleUpdateProviderKey(req, res, rest[0], rest[2], cfg, deps);
|
|
5744
6576
|
}
|
|
@@ -5837,7 +6669,7 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
5837
6669
|
try {
|
|
5838
6670
|
const headers = { Accept: "application/json" };
|
|
5839
6671
|
if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
|
|
5840
|
-
const response = await
|
|
6672
|
+
const response = await fetchUpstream5(url, { method: "GET", headers }, { providerId: "byo" });
|
|
5841
6673
|
if (!response.ok) {
|
|
5842
6674
|
const text = await response.text().catch(() => "");
|
|
5843
6675
|
let message = text.slice(0, 300);
|
|
@@ -5854,8 +6686,8 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
5854
6686
|
const data = await response.json();
|
|
5855
6687
|
const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
|
|
5856
6688
|
return writeJson4(res, 200, { models });
|
|
5857
|
-
} catch (
|
|
5858
|
-
const message =
|
|
6689
|
+
} catch (err6) {
|
|
6690
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
5859
6691
|
return writeJson4(res, 200, { models: [], error: `discovery failed: ${message}` });
|
|
5860
6692
|
}
|
|
5861
6693
|
}
|
|
@@ -5896,7 +6728,7 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
5896
6728
|
}
|
|
5897
6729
|
const startedAt = Date.now();
|
|
5898
6730
|
try {
|
|
5899
|
-
const response = await
|
|
6731
|
+
const response = await fetchUpstream5(
|
|
5900
6732
|
url,
|
|
5901
6733
|
{ method: "POST", headers, body: JSON.stringify(payload) },
|
|
5902
6734
|
{ providerId: "byo" }
|
|
@@ -5918,8 +6750,8 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
5918
6750
|
latencyMs,
|
|
5919
6751
|
sample: extractSampleText(text, row.apiFormat)
|
|
5920
6752
|
});
|
|
5921
|
-
} catch (
|
|
5922
|
-
const message =
|
|
6753
|
+
} catch (err6) {
|
|
6754
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
5923
6755
|
return writeJson4(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
|
|
5924
6756
|
}
|
|
5925
6757
|
}
|
|
@@ -5961,7 +6793,30 @@ async function handleProviderKeys(res, id, cfg, deps) {
|
|
|
5961
6793
|
const row = cfg.providers.find((p) => p.id === id);
|
|
5962
6794
|
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
5963
6795
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
5964
|
-
|
|
6796
|
+
const views = toPoolKeyView(row, cooldown, deps);
|
|
6797
|
+
if (deps.providerKeyQuota) {
|
|
6798
|
+
const quotas = await Promise.allSettled(
|
|
6799
|
+
views.map((view) => deps.providerKeyQuota.quotaFor(row, view.id))
|
|
6800
|
+
);
|
|
6801
|
+
views.forEach((view, index) => {
|
|
6802
|
+
const settled = quotas[index];
|
|
6803
|
+
if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
|
|
6804
|
+
});
|
|
6805
|
+
}
|
|
6806
|
+
return writeJson4(res, 200, { keys: views });
|
|
6807
|
+
}
|
|
6808
|
+
async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
|
|
6809
|
+
if (!deps.providerKeyQuota) return writeJsonError(res, 501, "provider key quota is not available");
|
|
6810
|
+
if (!id || !keyId) return writeJsonError(res, 400, "provider id and key id required in path");
|
|
6811
|
+
const row = cfg.providers.find((p) => p.id === id);
|
|
6812
|
+
if (!row) return writeJsonError(res, 404, `provider '${id}' not found`);
|
|
6813
|
+
try {
|
|
6814
|
+
const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
|
|
6815
|
+
if (!quota) return writeJsonError(res, 404, `no quota endpoint for key '${keyId}'`);
|
|
6816
|
+
return writeJson4(res, 200, { quota });
|
|
6817
|
+
} catch {
|
|
6818
|
+
return writeJsonError(res, 502, "quota refresh failed");
|
|
6819
|
+
}
|
|
5965
6820
|
}
|
|
5966
6821
|
function parsePoolKeyInput(body, existing) {
|
|
5967
6822
|
const out = {};
|
|
@@ -6706,12 +7561,12 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6706
7561
|
}
|
|
6707
7562
|
return writeJson4(res, 200, { ok: true, affected: result.affected });
|
|
6708
7563
|
}
|
|
6709
|
-
if (method === "GET" && rest[0] === "codex" && rest[1] === "oauth" && rest[3] === "status") {
|
|
6710
|
-
const result = handleCodexOAuthStatus(rest[2], deps);
|
|
7564
|
+
if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[3] === "status") {
|
|
7565
|
+
const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : handleKimiOAuthStatus(rest[2], deps);
|
|
6711
7566
|
return writeJson4(res, result.status, result.body);
|
|
6712
7567
|
}
|
|
6713
|
-
if (method === "DELETE" && rest[0] === "codex" && rest[1] === "oauth" && rest[2]) {
|
|
6714
|
-
const result = handleCodexOAuthCancel(rest[2], deps);
|
|
7568
|
+
if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi") && rest[1] === "oauth" && rest[2]) {
|
|
7569
|
+
const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : handleKimiOAuthCancel(rest[2], deps);
|
|
6715
7570
|
return writeJson4(res, result.status, result.body);
|
|
6716
7571
|
}
|
|
6717
7572
|
if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
|
|
@@ -6764,7 +7619,15 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
6764
7619
|
return writeJsonError(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
6765
7620
|
}
|
|
6766
7621
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
|
|
6767
|
-
|
|
7622
|
+
if (providerId === "codex") {
|
|
7623
|
+
const result2 = handleCodexOAuthStart(deps);
|
|
7624
|
+
return writeJson4(res, result2.status, result2.body);
|
|
7625
|
+
}
|
|
7626
|
+
if (providerId === "kimi") {
|
|
7627
|
+
const result2 = await handleKimiOAuthStart(deps);
|
|
7628
|
+
return writeJson4(res, result2.status, result2.body);
|
|
7629
|
+
}
|
|
7630
|
+
const result = handleOAuthStart(providerId, deps);
|
|
6768
7631
|
return writeJson4(res, result.status, result.body);
|
|
6769
7632
|
}
|
|
6770
7633
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
|
|
@@ -7258,12 +8121,12 @@ async function handlePlayground(req, res, method, deps) {
|
|
|
7258
8121
|
const payload = body["body"];
|
|
7259
8122
|
const status = deps.outboundApiServer.getStatus();
|
|
7260
8123
|
if (!status.running || !status.port) return writeJsonError(res, 503, "outbound server not running");
|
|
7261
|
-
const path2 = resolvePlaygroundPath(endpoint,
|
|
8124
|
+
const path2 = resolvePlaygroundPath(endpoint, isRecord4(payload) ? payload : {});
|
|
7262
8125
|
if (!path2) return writeJsonError(res, 400, `unknown endpoint '${endpoint}'`);
|
|
7263
8126
|
const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
7264
8127
|
await proxyToOutbound(res, status.port, path2, key, upstreamBody);
|
|
7265
8128
|
}
|
|
7266
|
-
function
|
|
8129
|
+
function isRecord4(v) {
|
|
7267
8130
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
7268
8131
|
}
|
|
7269
8132
|
function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
@@ -7292,8 +8155,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
|
7292
8155
|
});
|
|
7293
8156
|
}
|
|
7294
8157
|
);
|
|
7295
|
-
upstream.on("error", (
|
|
7296
|
-
if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${
|
|
8158
|
+
upstream.on("error", (err6) => {
|
|
8159
|
+
if (!res.headersSent) writeJsonError(res, 502, `playground proxy failed: ${err6.message}`);
|
|
7297
8160
|
else res.end();
|
|
7298
8161
|
resolve10();
|
|
7299
8162
|
});
|
|
@@ -7398,7 +8261,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
7398
8261
|
}
|
|
7399
8262
|
|
|
7400
8263
|
// src/admin/version.ts
|
|
7401
|
-
var DAEMON_VERSION = true ? "0.
|
|
8264
|
+
var DAEMON_VERSION = true ? "0.3.1" : "0.0.0-dev";
|
|
7402
8265
|
|
|
7403
8266
|
// src/admin/AdminServer.ts
|
|
7404
8267
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -7441,13 +8304,13 @@ var AdminServer = class {
|
|
|
7441
8304
|
const server = http2.createServer((req, res) => {
|
|
7442
8305
|
this.onRequest(req, res);
|
|
7443
8306
|
});
|
|
7444
|
-
const onError = (
|
|
7445
|
-
if (
|
|
8307
|
+
const onError = (err6) => {
|
|
8308
|
+
if (err6.code === "EADDRINUSE" && port !== 0) {
|
|
7446
8309
|
server.removeListener("error", onError);
|
|
7447
8310
|
this.listen(bindAddr, 0).then(resolve10, reject);
|
|
7448
8311
|
return;
|
|
7449
8312
|
}
|
|
7450
|
-
reject(
|
|
8313
|
+
reject(err6);
|
|
7451
8314
|
};
|
|
7452
8315
|
server.on("error", onError);
|
|
7453
8316
|
server.listen(port, bindAddr, () => {
|
|
@@ -7465,8 +8328,8 @@ var AdminServer = class {
|
|
|
7465
8328
|
}
|
|
7466
8329
|
/** Per-request handler: auth gate (when a token is set) → routing. */
|
|
7467
8330
|
onRequest(req, res) {
|
|
7468
|
-
void this.dispatch(req, res).catch((
|
|
7469
|
-
const message =
|
|
8331
|
+
void this.dispatch(req, res).catch((err6) => {
|
|
8332
|
+
const message = err6 instanceof Error ? err6.message : String(err6);
|
|
7470
8333
|
this.deps.logger.error("[AdminServer] unhandled error:", message);
|
|
7471
8334
|
if (!res.headersSent) {
|
|
7472
8335
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
@@ -7730,18 +8593,18 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
|
|
|
7730
8593
|
return;
|
|
7731
8594
|
}
|
|
7732
8595
|
signal?.addEventListener("abort", abort, { once: true });
|
|
7733
|
-
server.on("error", (
|
|
8596
|
+
server.on("error", (err6) => {
|
|
7734
8597
|
if (settled) return;
|
|
7735
8598
|
settled = true;
|
|
7736
8599
|
clearTimeout(timer);
|
|
7737
|
-
if (
|
|
8600
|
+
if (err6.code === "EADDRINUSE") {
|
|
7738
8601
|
reject(
|
|
7739
8602
|
new Error(
|
|
7740
8603
|
`login: cannot bind ${LOOPBACK_HOST}:${LOOPBACK_PORT} (address in use) \u2014 another codex login or process is holding the port`
|
|
7741
8604
|
)
|
|
7742
8605
|
);
|
|
7743
8606
|
} else {
|
|
7744
|
-
reject(
|
|
8607
|
+
reject(err6);
|
|
7745
8608
|
}
|
|
7746
8609
|
});
|
|
7747
8610
|
const timer = setTimeout(() => {
|
|
@@ -7816,6 +8679,411 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
|
|
|
7816
8679
|
};
|
|
7817
8680
|
}
|
|
7818
8681
|
|
|
8682
|
+
// src/allowance/ProviderKeyQuotaService.ts
|
|
8683
|
+
import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
8684
|
+
|
|
8685
|
+
// src/allowance/ProviderKeyQuota.ts
|
|
8686
|
+
var MINUTE_MS2 = 6e4;
|
|
8687
|
+
var HOUR_MS2 = 60 * MINUTE_MS2;
|
|
8688
|
+
var DAY_MS2 = 24 * HOUR_MS2;
|
|
8689
|
+
var WEEK_MS = 7 * DAY_MS2;
|
|
8690
|
+
var MONTH_MS = 30 * DAY_MS2;
|
|
8691
|
+
function finiteNumber3(value) {
|
|
8692
|
+
if (value === null || value === void 0 || value === "") return void 0;
|
|
8693
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
8694
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
8695
|
+
}
|
|
8696
|
+
function finitePercent4(value) {
|
|
8697
|
+
const parsed = finiteNumber3(value);
|
|
8698
|
+
return parsed !== void 0 && parsed <= 100 ? parsed : null;
|
|
8699
|
+
}
|
|
8700
|
+
function isoInstant3(value) {
|
|
8701
|
+
if (typeof value === "string" && value.trim()) {
|
|
8702
|
+
const time = Date.parse(value);
|
|
8703
|
+
if (Number.isFinite(time)) return new Date(time).toISOString();
|
|
8704
|
+
}
|
|
8705
|
+
const numeric = finiteNumber3(value);
|
|
8706
|
+
if (numeric !== void 0 && numeric > 1e9) {
|
|
8707
|
+
const ms = numeric > 1e12 ? numeric : numeric * 1e3;
|
|
8708
|
+
return new Date(ms).toISOString();
|
|
8709
|
+
}
|
|
8710
|
+
return void 0;
|
|
8711
|
+
}
|
|
8712
|
+
function secondsUntil5(instant, now) {
|
|
8713
|
+
if (!instant) return void 0;
|
|
8714
|
+
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
8715
|
+
}
|
|
8716
|
+
function isRecord5(value) {
|
|
8717
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
8718
|
+
}
|
|
8719
|
+
function detectProviderKeyQuotaAdapter(baseUrl) {
|
|
8720
|
+
if (!baseUrl) return null;
|
|
8721
|
+
let url;
|
|
8722
|
+
try {
|
|
8723
|
+
url = new URL(baseUrl);
|
|
8724
|
+
} catch {
|
|
8725
|
+
return null;
|
|
8726
|
+
}
|
|
8727
|
+
const host = url.hostname.toLowerCase();
|
|
8728
|
+
const path2 = url.pathname.toLowerCase();
|
|
8729
|
+
if ((host === "api.z.ai" || host === "open.bigmodel.cn") && path2.includes("/coding")) {
|
|
8730
|
+
return "zai";
|
|
8731
|
+
}
|
|
8732
|
+
if ((host === "api.minimax.io" || host === "api.minimaxi.com") && // Token Plan rides the plain openai `/v1` (chat completions) surface; the
|
|
8733
|
+
// anthropic `/anthropic` rows are excluded (their usage impl is unverified).
|
|
8734
|
+
(path2 === "/v1" || path2 === "/v1/" || path2 === "" || path2 === "/")) {
|
|
8735
|
+
return "minimax-token-plan";
|
|
8736
|
+
}
|
|
8737
|
+
if (host === "api.code.umans.ai") return "umans";
|
|
8738
|
+
if (host === "api.synthetic.new") return "synthetic";
|
|
8739
|
+
return null;
|
|
8740
|
+
}
|
|
8741
|
+
function providerKeyQuotaUrl(adapter, baseUrl) {
|
|
8742
|
+
const origin = new URL(baseUrl).origin;
|
|
8743
|
+
if (adapter === "zai") return `${origin}/api/monitor/usage/quota/limit`;
|
|
8744
|
+
if (adapter === "minimax-token-plan") return `${origin}/v1/token_plan/remains`;
|
|
8745
|
+
if (adapter === "umans") return `${origin}/v1/usage`;
|
|
8746
|
+
return `${origin}/v2/quotas`;
|
|
8747
|
+
}
|
|
8748
|
+
function providerKeyQuotaAuthHeader(adapter, key) {
|
|
8749
|
+
return adapter === "zai" ? key : `Bearer ${key}`;
|
|
8750
|
+
}
|
|
8751
|
+
function zaiWindowDurationMs(item) {
|
|
8752
|
+
const count = item.number !== void 0 && item.number > 0 ? item.number : 1;
|
|
8753
|
+
switch (item.unit) {
|
|
8754
|
+
case 3:
|
|
8755
|
+
return count * HOUR_MS2;
|
|
8756
|
+
case 4:
|
|
8757
|
+
return count * DAY_MS2;
|
|
8758
|
+
case 5:
|
|
8759
|
+
return count * MONTH_MS;
|
|
8760
|
+
case 6:
|
|
8761
|
+
return WEEK_MS;
|
|
8762
|
+
default:
|
|
8763
|
+
return void 0;
|
|
8764
|
+
}
|
|
8765
|
+
}
|
|
8766
|
+
function zaiWindowIdLabel(durationMs) {
|
|
8767
|
+
if (durationMs === WEEK_MS) return { id: "seven-day", label: "7 days" };
|
|
8768
|
+
if (durationMs === 5 * HOUR_MS2) return { id: "five-hour", label: "5 hours" };
|
|
8769
|
+
if (durationMs === MONTH_MS) return { id: "thirty-day", label: "30 days" };
|
|
8770
|
+
if (durationMs !== void 0 && durationMs % DAY_MS2 === 0) {
|
|
8771
|
+
const days = durationMs / DAY_MS2;
|
|
8772
|
+
return { id: `${days}d`, label: `${days} day${days === 1 ? "" : "s"}` };
|
|
8773
|
+
}
|
|
8774
|
+
if (durationMs !== void 0 && durationMs % HOUR_MS2 === 0) {
|
|
8775
|
+
const hours = durationMs / HOUR_MS2;
|
|
8776
|
+
return { id: `${hours}h`, label: `${hours} hour${hours === 1 ? "" : "s"}` };
|
|
8777
|
+
}
|
|
8778
|
+
return { id: "quota", label: "Quota" };
|
|
8779
|
+
}
|
|
8780
|
+
function parseZaiQuotaPayload(payload, now) {
|
|
8781
|
+
if (!isRecord5(payload)) return null;
|
|
8782
|
+
const data = isRecord5(payload["data"]) ? payload["data"] : payload;
|
|
8783
|
+
if (payload["success"] === false) return null;
|
|
8784
|
+
const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
|
|
8785
|
+
const byWindow = /* @__PURE__ */ new Map();
|
|
8786
|
+
for (const raw of limits) {
|
|
8787
|
+
if (!isRecord5(raw)) continue;
|
|
8788
|
+
const item = raw;
|
|
8789
|
+
if (item.type === void 0) continue;
|
|
8790
|
+
const details = raw["usageDetails"];
|
|
8791
|
+
if (Array.isArray(details) && details.some((d) => isRecord5(d) && d["modelCode"] === "zread")) {
|
|
8792
|
+
continue;
|
|
8793
|
+
}
|
|
8794
|
+
const durationMs = zaiWindowDurationMs(item);
|
|
8795
|
+
const { id, label } = zaiWindowIdLabel(durationMs);
|
|
8796
|
+
const limit = finiteNumber3(item.usage);
|
|
8797
|
+
const used = finiteNumber3(item.currentValue);
|
|
8798
|
+
const fromAbsolute = limit !== void 0 && used !== void 0 && limit > 0 ? Math.min(100, used / limit * 100) : void 0;
|
|
8799
|
+
const fromPercentage = finitePercent4(item.percentage) ?? void 0;
|
|
8800
|
+
const usedPercent = fromAbsolute !== void 0 ? Math.round(fromAbsolute * 10) / 10 : fromPercentage;
|
|
8801
|
+
if (usedPercent === void 0) continue;
|
|
8802
|
+
const resetsAt = isoInstant3(item.nextResetTime);
|
|
8803
|
+
const candidate = {
|
|
8804
|
+
id,
|
|
8805
|
+
label,
|
|
8806
|
+
scope: "all",
|
|
8807
|
+
usedPercent,
|
|
8808
|
+
...durationMs !== void 0 ? { windowMinutes: Math.round(durationMs / MINUTE_MS2) } : {},
|
|
8809
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8810
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8811
|
+
state: "fresh"
|
|
8812
|
+
};
|
|
8813
|
+
const existing = byWindow.get(id);
|
|
8814
|
+
if (!existing || (candidate.usedPercent ?? 0) > (existing.usedPercent ?? 0)) {
|
|
8815
|
+
byWindow.set(id, candidate);
|
|
8816
|
+
}
|
|
8817
|
+
}
|
|
8818
|
+
const windows = [...byWindow.values()].sort((a, b) => (a.windowMinutes ?? Number.POSITIVE_INFINITY) - (b.windowMinutes ?? Number.POSITIVE_INFINITY));
|
|
8819
|
+
return windows.length > 0 ? windows.slice(0, 4) : null;
|
|
8820
|
+
}
|
|
8821
|
+
var MINIMAX_STATUS_EXHAUSTED = 2;
|
|
8822
|
+
var MINIMAX_SHARED_BUCKET = "general";
|
|
8823
|
+
function parseMiniMaxBucket(value) {
|
|
8824
|
+
if (!isRecord5(value)) return null;
|
|
8825
|
+
const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
|
|
8826
|
+
if (!modelName) return null;
|
|
8827
|
+
const instant = (v) => {
|
|
8828
|
+
const n = finiteNumber3(v);
|
|
8829
|
+
return n !== void 0 && n > 1e9 ? n > 1e12 ? n : n * 1e3 : void 0;
|
|
8830
|
+
};
|
|
8831
|
+
return {
|
|
8832
|
+
modelName,
|
|
8833
|
+
intervalEnd: instant(value["end_time"]),
|
|
8834
|
+
intervalRemainingPercent: finiteNumber3(value["current_interval_remaining_percent"]),
|
|
8835
|
+
intervalStatus: finiteNumber3(value["current_interval_status"]),
|
|
8836
|
+
weeklyEnd: instant(value["weekly_end_time"]),
|
|
8837
|
+
weeklyRemainingPercent: finiteNumber3(value["current_weekly_remaining_percent"]),
|
|
8838
|
+
weeklyStatus: finiteNumber3(value["current_weekly_status"])
|
|
8839
|
+
};
|
|
8840
|
+
}
|
|
8841
|
+
function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, status, now) {
|
|
8842
|
+
const usedPercent = status === MINIMAX_STATUS_EXHAUSTED ? 100 : remainingPercent !== void 0 ? Math.round((100 - remainingPercent) * 10) / 10 : null;
|
|
8843
|
+
const resetsAt = resetsAtMs !== void 0 ? new Date(resetsAtMs).toISOString() : void 0;
|
|
8844
|
+
return {
|
|
8845
|
+
id,
|
|
8846
|
+
label,
|
|
8847
|
+
scope: "all",
|
|
8848
|
+
usedPercent,
|
|
8849
|
+
...windowMinutes !== void 0 ? { windowMinutes } : {},
|
|
8850
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8851
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8852
|
+
state: usedPercent !== null ? "fresh" : "unavailable"
|
|
8853
|
+
};
|
|
8854
|
+
}
|
|
8855
|
+
function parseMiniMaxTokenPlanPayload(payload, now) {
|
|
8856
|
+
if (!isRecord5(payload)) return null;
|
|
8857
|
+
const baseResp = payload["base_resp"];
|
|
8858
|
+
if (!isRecord5(baseResp) || baseResp["status_code"] !== 0) return null;
|
|
8859
|
+
const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
|
|
8860
|
+
let general = null;
|
|
8861
|
+
for (const raw of buckets) {
|
|
8862
|
+
const bucket = parseMiniMaxBucket(raw);
|
|
8863
|
+
if (bucket?.modelName === MINIMAX_SHARED_BUCKET) {
|
|
8864
|
+
general = bucket;
|
|
8865
|
+
break;
|
|
8866
|
+
}
|
|
8867
|
+
}
|
|
8868
|
+
if (!general) return null;
|
|
8869
|
+
return [
|
|
8870
|
+
minimaxWindow(
|
|
8871
|
+
"five-hour",
|
|
8872
|
+
"5 hours",
|
|
8873
|
+
5 * 60,
|
|
8874
|
+
general.intervalEnd,
|
|
8875
|
+
general.intervalRemainingPercent,
|
|
8876
|
+
general.intervalStatus,
|
|
8877
|
+
now
|
|
8878
|
+
),
|
|
8879
|
+
minimaxWindow(
|
|
8880
|
+
"seven-day",
|
|
8881
|
+
"7 days",
|
|
8882
|
+
Math.round(WEEK_MS / MINUTE_MS2),
|
|
8883
|
+
general.weeklyEnd,
|
|
8884
|
+
general.weeklyRemainingPercent,
|
|
8885
|
+
general.weeklyStatus,
|
|
8886
|
+
now
|
|
8887
|
+
)
|
|
8888
|
+
];
|
|
8889
|
+
}
|
|
8890
|
+
function parseUmansUsagePayload(payload, now) {
|
|
8891
|
+
if (!isRecord5(payload)) return null;
|
|
8892
|
+
const limits = isRecord5(payload["limits"]) ? payload["limits"] : void 0;
|
|
8893
|
+
const requests = limits && isRecord5(limits["requests"]) ? limits["requests"] : void 0;
|
|
8894
|
+
const usage = isRecord5(payload["usage"]) ? payload["usage"] : void 0;
|
|
8895
|
+
const window = isRecord5(payload["window"]) ? payload["window"] : void 0;
|
|
8896
|
+
const hardCap = finiteNumber3(requests?.["hard_cap"]);
|
|
8897
|
+
const softLimit = finiteNumber3(requests?.["limit"]);
|
|
8898
|
+
const requestsInWindow = finiteNumber3(usage?.["requests_in_window"]);
|
|
8899
|
+
const weightedInWindow = finiteNumber3(usage?.["weighted_in_window"]);
|
|
8900
|
+
const resetsAt = isoInstant3(window?.["resets_at"]);
|
|
8901
|
+
let usedPercent = null;
|
|
8902
|
+
if (hardCap !== void 0 && hardCap > 0 && requestsInWindow !== void 0) {
|
|
8903
|
+
usedPercent = Math.round(Math.min(100, requestsInWindow / hardCap * 100) * 10) / 10;
|
|
8904
|
+
} else if (softLimit !== void 0 && softLimit > 0 && weightedInWindow !== void 0) {
|
|
8905
|
+
usedPercent = Math.round(Math.min(100, weightedInWindow / softLimit * 100) * 10) / 10;
|
|
8906
|
+
}
|
|
8907
|
+
if (usedPercent === null && resetsAt === void 0) return null;
|
|
8908
|
+
return [
|
|
8909
|
+
{
|
|
8910
|
+
id: "five-hour",
|
|
8911
|
+
label: "5 hours",
|
|
8912
|
+
scope: "all",
|
|
8913
|
+
usedPercent,
|
|
8914
|
+
windowMinutes: 5 * 60,
|
|
8915
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8916
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8917
|
+
state: "fresh"
|
|
8918
|
+
}
|
|
8919
|
+
];
|
|
8920
|
+
}
|
|
8921
|
+
function parseSyntheticQuotasPayload(payload, now) {
|
|
8922
|
+
if (!isRecord5(payload)) return null;
|
|
8923
|
+
const fiveHour = isRecord5(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
|
|
8924
|
+
const weekly = isRecord5(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
|
|
8925
|
+
const windows = [];
|
|
8926
|
+
if (fiveHour) {
|
|
8927
|
+
const max = finiteNumber3(fiveHour["max"]);
|
|
8928
|
+
const remaining = finiteNumber3(fiveHour["remaining"]);
|
|
8929
|
+
const usedPercent = max !== void 0 && max > 0 && remaining !== void 0 ? Math.round(Math.min(100, (max - remaining) / max * 100) * 10) / 10 : null;
|
|
8930
|
+
const resetsAt = isoInstant3(fiveHour["nextTickAt"]);
|
|
8931
|
+
windows.push({
|
|
8932
|
+
id: "five-hour",
|
|
8933
|
+
label: "5 hours",
|
|
8934
|
+
scope: "all",
|
|
8935
|
+
usedPercent,
|
|
8936
|
+
windowMinutes: 5 * 60,
|
|
8937
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8938
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8939
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
8940
|
+
});
|
|
8941
|
+
}
|
|
8942
|
+
if (weekly) {
|
|
8943
|
+
const percentRemaining = finiteNumber3(weekly["percentRemaining"]);
|
|
8944
|
+
const usedPercent = percentRemaining !== void 0 ? Math.round(Math.min(100, Math.max(0, 100 - percentRemaining)) * 10) / 10 : null;
|
|
8945
|
+
const resetsAt = isoInstant3(weekly["nextRegenAt"]);
|
|
8946
|
+
windows.push({
|
|
8947
|
+
id: "seven-day",
|
|
8948
|
+
label: "7 days",
|
|
8949
|
+
scope: "all",
|
|
8950
|
+
usedPercent,
|
|
8951
|
+
windowMinutes: 7 * 24 * 60,
|
|
8952
|
+
...resetsAt !== void 0 ? { resetsAt } : {},
|
|
8953
|
+
remainingSeconds: secondsUntil5(resetsAt, now),
|
|
8954
|
+
state: usedPercent !== null || resetsAt ? "fresh" : "unavailable"
|
|
8955
|
+
});
|
|
8956
|
+
}
|
|
8957
|
+
return windows.length > 0 ? windows : null;
|
|
8958
|
+
}
|
|
8959
|
+
|
|
8960
|
+
// src/allowance/ProviderKeyQuotaService.ts
|
|
8961
|
+
function parseQuotaPayload(adapter, payload, now) {
|
|
8962
|
+
switch (adapter) {
|
|
8963
|
+
case "zai":
|
|
8964
|
+
return parseZaiQuotaPayload(payload, now);
|
|
8965
|
+
case "minimax-token-plan":
|
|
8966
|
+
return parseMiniMaxTokenPlanPayload(payload, now);
|
|
8967
|
+
case "umans":
|
|
8968
|
+
return parseUmansUsagePayload(payload, now);
|
|
8969
|
+
case "synthetic":
|
|
8970
|
+
return parseSyntheticQuotasPayload(payload, now);
|
|
8971
|
+
}
|
|
8972
|
+
}
|
|
8973
|
+
var PROVIDER_KEY_QUOTA_CACHE_MS = 5 * 6e4;
|
|
8974
|
+
function resolvedBaseUrl(row) {
|
|
8975
|
+
const modes = row.apiModes ?? [];
|
|
8976
|
+
const selected = row.selectedApiModeId ? modes.find((mode) => mode.id === row.selectedApiModeId) : void 0;
|
|
8977
|
+
const fallback = modes[0];
|
|
8978
|
+
const modeBase = selected?.baseUrl ?? fallback?.baseUrl;
|
|
8979
|
+
return modeBase ?? row.codingPlan?.baseUrl ?? row.baseUrl;
|
|
8980
|
+
}
|
|
8981
|
+
function rowKeyEntries(row) {
|
|
8982
|
+
const pool = (row.apiKeys ?? []).filter((entry) => entry.apiKey.length > 0);
|
|
8983
|
+
if (pool.length > 0) return pool.map((entry) => ({ id: entry.id, apiKey: entry.apiKey }));
|
|
8984
|
+
if (row.apiKey.length > 0) {
|
|
8985
|
+
return [{ id: `${row.id}:default`, apiKey: row.apiKey }];
|
|
8986
|
+
}
|
|
8987
|
+
return [];
|
|
8988
|
+
}
|
|
8989
|
+
var ProviderKeyQuotaService = class {
|
|
8990
|
+
constructor(box, fetchImpl = (url, init) => fetchUpstream6(url, init, { redactBodies: true }), now = Date.now) {
|
|
8991
|
+
this.box = box;
|
|
8992
|
+
this.fetchImpl = fetchImpl;
|
|
8993
|
+
this.now = now;
|
|
8994
|
+
}
|
|
8995
|
+
box;
|
|
8996
|
+
fetchImpl;
|
|
8997
|
+
now;
|
|
8998
|
+
cache = /* @__PURE__ */ new Map();
|
|
8999
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
9000
|
+
/**
|
|
9001
|
+
* Quota for one key of a provider row, or `null` when the row has no quota
|
|
9002
|
+
* adapter / no such key. Cache-first; concurrent reads share one flight.
|
|
9003
|
+
*/
|
|
9004
|
+
async quotaFor(row, keyId, options = {}) {
|
|
9005
|
+
const adapter = detectProviderKeyQuotaAdapter(resolvedBaseUrl(row));
|
|
9006
|
+
if (!adapter) return null;
|
|
9007
|
+
const entry = rowKeyEntries(row).find((candidate) => candidate.id === keyId);
|
|
9008
|
+
if (!entry) return null;
|
|
9009
|
+
const cacheKey = `${row.id}\0${keyId}`;
|
|
9010
|
+
const now = this.now();
|
|
9011
|
+
const cached = this.cache.get(cacheKey);
|
|
9012
|
+
if (!options.force && cached && Date.parse(cached.expiresAt) > now) return cached;
|
|
9013
|
+
const running = this.inFlight.get(cacheKey);
|
|
9014
|
+
if (running) return running;
|
|
9015
|
+
const promise = this.fetchQuota(adapter, row, entry.apiKey, cacheKey).catch((error) => {
|
|
9016
|
+
void error;
|
|
9017
|
+
const previous = this.cache.get(cacheKey);
|
|
9018
|
+
if (previous) {
|
|
9019
|
+
const degraded = {
|
|
9020
|
+
...previous,
|
|
9021
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9022
|
+
windows: previous.windows.map((window) => ({
|
|
9023
|
+
...window,
|
|
9024
|
+
state: window.usedPercent !== null || window.resetsAt ? "stale" : window.state
|
|
9025
|
+
})),
|
|
9026
|
+
errorCode: "quota_request_failed"
|
|
9027
|
+
};
|
|
9028
|
+
this.cache.set(cacheKey, degraded);
|
|
9029
|
+
return degraded;
|
|
9030
|
+
}
|
|
9031
|
+
return null;
|
|
9032
|
+
}).finally(() => this.inFlight.delete(cacheKey));
|
|
9033
|
+
this.inFlight.set(cacheKey, promise);
|
|
9034
|
+
return promise;
|
|
9035
|
+
}
|
|
9036
|
+
/** Drop cached rows for a provider (key added/removed/rotated). */
|
|
9037
|
+
invalidateProvider(providerRowId) {
|
|
9038
|
+
for (const key of this.cache.keys()) {
|
|
9039
|
+
if (key.startsWith(`${providerRowId}\0`)) this.cache.delete(key);
|
|
9040
|
+
}
|
|
9041
|
+
}
|
|
9042
|
+
async fetchQuota(adapter, row, rawKey, cacheKey) {
|
|
9043
|
+
const baseUrl = resolvedBaseUrl(row);
|
|
9044
|
+
const url = providerKeyQuotaUrl(adapter, baseUrl);
|
|
9045
|
+
const key = this.box.decryptMaybe(rawKey);
|
|
9046
|
+
const now = this.now();
|
|
9047
|
+
const response = await this.fetchImpl(url, {
|
|
9048
|
+
method: "GET",
|
|
9049
|
+
headers: {
|
|
9050
|
+
Authorization: providerKeyQuotaAuthHeader(adapter, key),
|
|
9051
|
+
Accept: "application/json",
|
|
9052
|
+
"Content-Type": "application/json"
|
|
9053
|
+
},
|
|
9054
|
+
signal: AbortSignal.timeout(15e3)
|
|
9055
|
+
});
|
|
9056
|
+
if (response.status === 401 || response.status === 403) {
|
|
9057
|
+
const snapshot2 = {
|
|
9058
|
+
adapter,
|
|
9059
|
+
observedAt: new Date(now).toISOString(),
|
|
9060
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9061
|
+
windows: [],
|
|
9062
|
+
errorCode: "quota_unauthorized"
|
|
9063
|
+
};
|
|
9064
|
+
this.cache.set(cacheKey, snapshot2);
|
|
9065
|
+
return snapshot2;
|
|
9066
|
+
}
|
|
9067
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
9068
|
+
let payload;
|
|
9069
|
+
try {
|
|
9070
|
+
payload = await response.json();
|
|
9071
|
+
} catch {
|
|
9072
|
+
throw new Error("invalid JSON");
|
|
9073
|
+
}
|
|
9074
|
+
const windows = parseQuotaPayload(adapter, payload, now);
|
|
9075
|
+
const snapshot = {
|
|
9076
|
+
adapter,
|
|
9077
|
+
observedAt: new Date(now).toISOString(),
|
|
9078
|
+
expiresAt: new Date(now + PROVIDER_KEY_QUOTA_CACHE_MS).toISOString(),
|
|
9079
|
+
windows: windows ?? [],
|
|
9080
|
+
...windows ? {} : { errorCode: "quota_unavailable" }
|
|
9081
|
+
};
|
|
9082
|
+
this.cache.set(cacheKey, snapshot);
|
|
9083
|
+
return snapshot;
|
|
9084
|
+
}
|
|
9085
|
+
};
|
|
9086
|
+
|
|
7819
9087
|
// src/commands/paths.ts
|
|
7820
9088
|
import { dirname as dirname5, join as join5 } from "path";
|
|
7821
9089
|
function defaultVouchersPath(configPath) {
|
|
@@ -13965,21 +15233,23 @@ function bucketLabel(bucketStartTs, bucket) {
|
|
|
13965
15233
|
}
|
|
13966
15234
|
|
|
13967
15235
|
// src/ports/JsonOutboundKeyDb.ts
|
|
15236
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
15237
|
+
import {
|
|
15238
|
+
validateOutboundPermissions as validateOutboundPermissions3
|
|
15239
|
+
} from "@omnicross/core";
|
|
15240
|
+
|
|
15241
|
+
// src/ports/atomicFile.ts
|
|
13968
15242
|
import { randomBytes as randomBytes11 } from "crypto";
|
|
13969
15243
|
import {
|
|
13970
15244
|
closeSync as closeSync7,
|
|
13971
15245
|
existsSync as existsSync16,
|
|
13972
15246
|
fsyncSync as fsyncSync7,
|
|
13973
15247
|
openSync as openSync7,
|
|
13974
|
-
readFileSync as readFileSync13,
|
|
13975
15248
|
renameSync as renameSync9,
|
|
13976
15249
|
unlinkSync as unlinkSync11,
|
|
13977
15250
|
writeFileSync as writeFileSync12
|
|
13978
15251
|
} from "fs";
|
|
13979
15252
|
import { basename as basename8, dirname as dirname14, join as join16 } from "path";
|
|
13980
|
-
import {
|
|
13981
|
-
validateOutboundPermissions as validateOutboundPermissions3
|
|
13982
|
-
} from "@omnicross/core";
|
|
13983
15253
|
function atomicReplaceUtf8(targetPath, contents) {
|
|
13984
15254
|
const tempPath = join16(
|
|
13985
15255
|
dirname14(targetPath),
|
|
@@ -14009,6 +15279,8 @@ function atomicReplaceUtf8(targetPath, contents) {
|
|
|
14009
15279
|
throw error;
|
|
14010
15280
|
}
|
|
14011
15281
|
}
|
|
15282
|
+
|
|
15283
|
+
// src/ports/JsonOutboundKeyDb.ts
|
|
14012
15284
|
var JsonOutboundKeyDb = class {
|
|
14013
15285
|
/**
|
|
14014
15286
|
* @param secretBox OPTIONAL reversible-secret codec. When present, a created
|
|
@@ -14151,7 +15423,7 @@ var JsonOutboundKeyDb = class {
|
|
|
14151
15423
|
}
|
|
14152
15424
|
/** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14153
15425
|
readRows() {
|
|
14154
|
-
if (!
|
|
15426
|
+
if (!existsSync17(this.keysPath)) return [];
|
|
14155
15427
|
try {
|
|
14156
15428
|
const parsed = JSON.parse(readFileSync13(this.keysPath, "utf8"));
|
|
14157
15429
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -14170,7 +15442,7 @@ function applyPolicyField(row, field, value) {
|
|
|
14170
15442
|
}
|
|
14171
15443
|
|
|
14172
15444
|
// src/ports/JsonPricingStore.ts
|
|
14173
|
-
import { existsSync as
|
|
15445
|
+
import { existsSync as existsSync18, readFileSync as readFileSync14, renameSync as renameSync10, rmSync as rmSync3, writeFileSync as writeFileSync13 } from "fs";
|
|
14174
15446
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
14175
15447
|
var JsonPricingStore = class {
|
|
14176
15448
|
constructor(pricingPath) {
|
|
@@ -14185,7 +15457,7 @@ var JsonPricingStore = class {
|
|
|
14185
15457
|
* otherwise unusable pricing table after a crash or manual file edit.
|
|
14186
15458
|
*/
|
|
14187
15459
|
hasUsableSnapshot() {
|
|
14188
|
-
if (!
|
|
15460
|
+
if (!existsSync18(this.pricingPath)) return false;
|
|
14189
15461
|
try {
|
|
14190
15462
|
const parsed = JSON.parse(readFileSync14(this.pricingPath, "utf8"));
|
|
14191
15463
|
return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
|
|
@@ -14300,7 +15572,7 @@ var JsonPricingStore = class {
|
|
|
14300
15572
|
}
|
|
14301
15573
|
/** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14302
15574
|
readRows() {
|
|
14303
|
-
if (!
|
|
15575
|
+
if (!existsSync18(this.pricingPath)) return [];
|
|
14304
15576
|
try {
|
|
14305
15577
|
const parsed = JSON.parse(readFileSync14(this.pricingPath, "utf8"));
|
|
14306
15578
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -14332,7 +15604,7 @@ function isUsablePricingRow(value) {
|
|
|
14332
15604
|
}
|
|
14333
15605
|
|
|
14334
15606
|
// src/pricing/PricingRefreshScheduler.ts
|
|
14335
|
-
import { existsSync as
|
|
15607
|
+
import { existsSync as existsSync19, readFileSync as readFileSync15, renameSync as renameSync11, writeFileSync as writeFileSync14 } from "fs";
|
|
14336
15608
|
var EMPTY_STATE2 = {
|
|
14337
15609
|
lastAttemptAt: null,
|
|
14338
15610
|
lastSuccessAt: null,
|
|
@@ -14370,7 +15642,7 @@ var PricingRefreshScheduler = class {
|
|
|
14370
15642
|
this.timer = null;
|
|
14371
15643
|
}
|
|
14372
15644
|
getState() {
|
|
14373
|
-
if (!
|
|
15645
|
+
if (!existsSync19(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
|
|
14374
15646
|
try {
|
|
14375
15647
|
const value = JSON.parse(readFileSync15(this.statePath, "utf8"));
|
|
14376
15648
|
return {
|
|
@@ -14435,7 +15707,7 @@ function finiteOrNull(value) {
|
|
|
14435
15707
|
}
|
|
14436
15708
|
|
|
14437
15709
|
// src/ports/JsonVoucherDb.ts
|
|
14438
|
-
import { existsSync as
|
|
15710
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16, writeFileSync as writeFileSync15 } from "fs";
|
|
14439
15711
|
var JsonVoucherDb = class {
|
|
14440
15712
|
constructor(vouchersPath) {
|
|
14441
15713
|
this.vouchersPath = vouchersPath;
|
|
@@ -14513,7 +15785,7 @@ var JsonVoucherDb = class {
|
|
|
14513
15785
|
}
|
|
14514
15786
|
/** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
|
|
14515
15787
|
readRows() {
|
|
14516
|
-
if (!
|
|
15788
|
+
if (!existsSync20(this.vouchersPath)) return [];
|
|
14517
15789
|
try {
|
|
14518
15790
|
const parsed = JSON.parse(readFileSync16(this.vouchersPath, "utf8"));
|
|
14519
15791
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -14527,16 +15799,17 @@ var JsonVoucherDb = class {
|
|
|
14527
15799
|
};
|
|
14528
15800
|
|
|
14529
15801
|
// src/ports/JsonSubscriptionCredentialStore.ts
|
|
14530
|
-
import { existsSync as
|
|
15802
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync6, readFileSync as readFileSync18, renameSync as renameSync12 } from "fs";
|
|
14531
15803
|
import { dirname as dirname15 } from "path";
|
|
14532
15804
|
import { getSharedAccountHealth as getSharedAccountHealth2 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
14533
15805
|
import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling3 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
14534
|
-
import { fetchUpstream as
|
|
15806
|
+
import { fetchUpstream as fetchUpstream7 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
14535
15807
|
import { getSharedIdentityStore as getSharedIdentityStore2 } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
|
|
14536
15808
|
import {
|
|
14537
15809
|
claudeOAuth as claudeOAuth2,
|
|
14538
15810
|
codexOAuth as codexOAuth2,
|
|
14539
|
-
geminiOAuth as geminiOAuth2
|
|
15811
|
+
geminiOAuth as geminiOAuth2,
|
|
15812
|
+
kimiOAuth as kimiOAuth2
|
|
14540
15813
|
} from "@omnicross/subscriptions";
|
|
14541
15814
|
|
|
14542
15815
|
// src/ports/account-sync.ts
|
|
@@ -14581,7 +15854,7 @@ function findDuplicateCredentialIds(accounts) {
|
|
|
14581
15854
|
}
|
|
14582
15855
|
|
|
14583
15856
|
// src/ports/external-cli-credentials.ts
|
|
14584
|
-
import { existsSync as
|
|
15857
|
+
import { existsSync as existsSync21, readFileSync as readFileSync17 } from "fs";
|
|
14585
15858
|
import { homedir as homedir4 } from "os";
|
|
14586
15859
|
import { join as join17 } from "path";
|
|
14587
15860
|
function externalStorePath(provider, home = homedir4()) {
|
|
@@ -14634,7 +15907,7 @@ function parseCodexTokensEnvelope(raw) {
|
|
|
14634
15907
|
}
|
|
14635
15908
|
function readExternalCliCredentials(provider, home = homedir4()) {
|
|
14636
15909
|
const path2 = externalStorePath(provider, home);
|
|
14637
|
-
if (!
|
|
15910
|
+
if (!existsSync21(path2)) return null;
|
|
14638
15911
|
let raw;
|
|
14639
15912
|
try {
|
|
14640
15913
|
const parsed = JSON.parse(readFileSync17(path2, "utf8"));
|
|
@@ -14660,16 +15933,18 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14660
15933
|
* as on relay refresh egresses from the SAME proxy IP as the
|
|
14661
15934
|
* account's traffic. NOT used by any read/write path.
|
|
14662
15935
|
*/
|
|
14663
|
-
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials) {
|
|
15936
|
+
constructor(tokensPath, box, fetchImpl = void 0, externalCliReader = readExternalCliCredentials, atomicReplace = atomicReplaceUtf8) {
|
|
14664
15937
|
this.tokensPath = tokensPath;
|
|
14665
15938
|
this.box = box;
|
|
14666
15939
|
this.fetchImpl = fetchImpl;
|
|
14667
15940
|
this.externalCliReader = externalCliReader;
|
|
15941
|
+
this.atomicReplace = atomicReplace;
|
|
14668
15942
|
}
|
|
14669
15943
|
tokensPath;
|
|
14670
15944
|
box;
|
|
14671
15945
|
fetchImpl;
|
|
14672
15946
|
externalCliReader;
|
|
15947
|
+
atomicReplace;
|
|
14673
15948
|
/**
|
|
14674
15949
|
* The proxy-aware `FetchLike` for one refresh round-trip (upstream-proxy M1). A
|
|
14675
15950
|
* TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
|
|
@@ -14683,7 +15958,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14683
15958
|
* a plaintext token pair into `upstream-trace.jsonl`.
|
|
14684
15959
|
*/
|
|
14685
15960
|
buildRefreshFetch(providerId, accountId) {
|
|
14686
|
-
return this.fetchImpl ?? ((url, init) =>
|
|
15961
|
+
return this.fetchImpl ?? ((url, init) => fetchUpstream7(url, init, { providerId, accountId, redactBodies: true }));
|
|
14687
15962
|
}
|
|
14688
15963
|
/**
|
|
14689
15964
|
* In-flight refresh coalescing. OAuth refresh tokens are
|
|
@@ -14724,7 +15999,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14724
15999
|
* other hot reads. Never returns token material.
|
|
14725
16000
|
*/
|
|
14726
16001
|
getAccountProxy(providerId, accountId) {
|
|
14727
|
-
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego") {
|
|
16002
|
+
if (providerId !== "claude" && providerId !== "codex" && providerId !== "gemini" && providerId !== "opencodego" && providerId !== "kimi") {
|
|
14728
16003
|
return void 0;
|
|
14729
16004
|
}
|
|
14730
16005
|
return getAccountProxy(this.readConfig(), providerId, accountId);
|
|
@@ -14743,7 +16018,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14743
16018
|
const fingerprintOn = identityStore.isEnabled();
|
|
14744
16019
|
const now = Date.now();
|
|
14745
16020
|
const out = {};
|
|
14746
|
-
for (const provider of ["claude", "codex", "gemini", "opencodego"]) {
|
|
16021
|
+
for (const provider of ["claude", "codex", "gemini", "opencodego", "kimi"]) {
|
|
14747
16022
|
const sanitized = sanitizeAccounts(config, provider);
|
|
14748
16023
|
if (sanitized.length === 0) continue;
|
|
14749
16024
|
for (const account of sanitized) {
|
|
@@ -14901,6 +16176,47 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14901
16176
|
}
|
|
14902
16177
|
});
|
|
14903
16178
|
}
|
|
16179
|
+
/**
|
|
16180
|
+
* Refresh the Kimi Code (Moonshot) OAuth access token (device-flow grant).
|
|
16181
|
+
* Kimi ROTATES the refresh token, so the response's pair is written back
|
|
16182
|
+
* whole; the account's stable `deviceId` (fingerprint header input) is
|
|
16183
|
+
* preserved. The refresh call carries the CLI fingerprint headers. HONEST
|
|
16184
|
+
* `false` when no refresh_token.
|
|
16185
|
+
*/
|
|
16186
|
+
async refreshKimiToken() {
|
|
16187
|
+
return this.coalesce("kimi:active", async () => {
|
|
16188
|
+
const config = this.readConfig();
|
|
16189
|
+
const active = getActiveAccount(config, "kimi");
|
|
16190
|
+
const kimi = active?.tokens;
|
|
16191
|
+
if (!active || !kimi?.refreshToken) return false;
|
|
16192
|
+
const capturedId = active.id;
|
|
16193
|
+
this.materializeMigration(config);
|
|
16194
|
+
const refreshFetch = this.buildRefreshFetch("kimi", capturedId);
|
|
16195
|
+
try {
|
|
16196
|
+
const result = await kimiOAuth2.refreshAccessToken(
|
|
16197
|
+
kimi.refreshToken,
|
|
16198
|
+
refreshFetch,
|
|
16199
|
+
kimiOAuth2.kimiFingerprintHeaders(kimi.deviceId)
|
|
16200
|
+
);
|
|
16201
|
+
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
16202
|
+
const next = {
|
|
16203
|
+
...kimi,
|
|
16204
|
+
accessToken: result.accessToken,
|
|
16205
|
+
refreshToken: result.refreshToken,
|
|
16206
|
+
expiresAt,
|
|
16207
|
+
status: "authorized",
|
|
16208
|
+
lastRefreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16209
|
+
errorMessage: void 0,
|
|
16210
|
+
syncWarning: void 0
|
|
16211
|
+
};
|
|
16212
|
+
this.writeBackById("kimi", capturedId, next);
|
|
16213
|
+
return true;
|
|
16214
|
+
} catch (error) {
|
|
16215
|
+
this.markExpiredById("kimi", capturedId, kimi, error);
|
|
16216
|
+
return false;
|
|
16217
|
+
}
|
|
16218
|
+
});
|
|
16219
|
+
}
|
|
14904
16220
|
/**
|
|
14905
16221
|
* Refresh a SPECIFIC managed account by id (background scheduler sweep and
|
|
14906
16222
|
* account-pool resolution). It uses only that account's stored refresh
|
|
@@ -14953,7 +16269,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
14953
16269
|
}
|
|
14954
16270
|
const oauth = account.tokens;
|
|
14955
16271
|
if (!oauth.accessToken) return null;
|
|
14956
|
-
if (providerId === "codex" || providerId === "gemini") {
|
|
16272
|
+
if (providerId === "codex" || providerId === "gemini" || providerId === "kimi") {
|
|
14957
16273
|
const expiresAtMs = oauth.expiresAt ? Date.parse(oauth.expiresAt) : 0;
|
|
14958
16274
|
const expiringSoon = expiresAtMs > 0 && Date.now() >= expiresAtMs - ACCOUNT_REFRESH_LEAD_MS;
|
|
14959
16275
|
if (expiringSoon && oauth.refreshToken) {
|
|
@@ -15042,8 +16358,23 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15042
16358
|
}
|
|
15043
16359
|
/** Dispatch one OAuth refresh round-trip to the provider's shared flow. */
|
|
15044
16360
|
async refreshUpstream(provider, refreshToken, accountId) {
|
|
16361
|
+
const refreshFetch = this.buildRefreshFetch(provider, accountId);
|
|
16362
|
+
if (provider === "kimi") {
|
|
16363
|
+
const account = accountId ? getAccountById(this.readConfig(), "kimi", accountId) : void 0;
|
|
16364
|
+
const deviceId = account?.tokens?.deviceId;
|
|
16365
|
+
const r2 = await kimiOAuth2.refreshAccessToken(
|
|
16366
|
+
refreshToken,
|
|
16367
|
+
refreshFetch,
|
|
16368
|
+
kimiOAuth2.kimiFingerprintHeaders(deviceId)
|
|
16369
|
+
);
|
|
16370
|
+
return {
|
|
16371
|
+
accessToken: r2.accessToken,
|
|
16372
|
+
refreshToken: r2.refreshToken,
|
|
16373
|
+
expiresAt: new Date(Date.now() + r2.expiresIn * 1e3).toISOString()
|
|
16374
|
+
};
|
|
16375
|
+
}
|
|
15045
16376
|
const flow = provider === "claude" ? claudeOAuth2 : provider === "codex" ? codexOAuth2 : geminiOAuth2;
|
|
15046
|
-
const r = await flow.refreshAccessToken(refreshToken,
|
|
16377
|
+
const r = await flow.refreshAccessToken(refreshToken, refreshFetch);
|
|
15047
16378
|
return {
|
|
15048
16379
|
accessToken: r.accessToken,
|
|
15049
16380
|
refreshToken: r.refreshToken,
|
|
@@ -15206,42 +16537,86 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
15206
16537
|
/** Write the merged config to disk as pretty JSON (mkdir parent if needed).
|
|
15207
16538
|
* Encrypt-on-write: the token-material fields are encrypted (legacy plaintext
|
|
15208
16539
|
* `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
|
|
15209
|
-
* write incl. child 4's future refresh writes lands encrypted.
|
|
16540
|
+
* write incl. child 4's future refresh writes lands encrypted.
|
|
16541
|
+
* ATOMIC: temp + fsync + rename (`atomicReplaceUtf8`) — a failed or
|
|
16542
|
+
* interrupted write discards only the temp file; the prior `tokens.json`
|
|
16543
|
+
* survives byte-equal (bare `writeFileSync` truncate-writes lost every
|
|
16544
|
+
* account on a mid-write failure, 2026-09-06). */
|
|
15210
16545
|
persist(config) {
|
|
15211
16546
|
mkdirSync6(dirname15(this.tokensPath), { recursive: true });
|
|
15212
16547
|
const encrypted = encryptTokens(config, this.box);
|
|
15213
|
-
|
|
16548
|
+
this.atomicReplace(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n");
|
|
15214
16549
|
}
|
|
15215
16550
|
/**
|
|
15216
|
-
* Read + parse `tokens.json`,
|
|
15217
|
-
*
|
|
15218
|
-
*
|
|
16551
|
+
* Read + parse `tokens.json`, then DECRYPT the token-material fields so every
|
|
16552
|
+
* getter returns plaintext (the subscription bearer path is byte-identical).
|
|
16553
|
+
*
|
|
16554
|
+
* A MISSING file is a legitimate first-boot state → minimal `{ updatedAt: '' }`.
|
|
16555
|
+
* A file that EXISTS but cannot be parsed as a JSON object is CORRUPT →
|
|
16556
|
+
* `quarantineCorrupt` moves it aside (once) before the empty config is
|
|
16557
|
+
* returned, so the unreadable accounts survive for manual recovery.
|
|
15219
16558
|
*
|
|
15220
|
-
* The
|
|
15221
|
-
*
|
|
15222
|
-
*
|
|
15223
|
-
*
|
|
15224
|
-
*
|
|
15225
|
-
*
|
|
15226
|
-
* `config.ts loadConfig`, which decrypts outside its parse try.
|
|
16559
|
+
* The DECRYPT runs OUTSIDE any try, so a wrong/missing master key or a
|
|
16560
|
+
* tampered `enc:` envelope FAILS FAST with the box's clear, secret-free
|
|
16561
|
+
* error (secrets spec "/ UX": SHALL fail-fast, SHALL NOT a swallowed
|
|
16562
|
+
* decrypt would report "no tokens" and silently send the WRONG bearer
|
|
16563
|
+
* upstream 401). Mirrors `config.ts loadConfig`, which decrypts outside
|
|
16564
|
+
* its parse try.
|
|
15227
16565
|
*/
|
|
15228
16566
|
readConfig() {
|
|
15229
|
-
if (!
|
|
16567
|
+
if (!existsSync22(this.tokensPath)) return { updatedAt: "" };
|
|
15230
16568
|
let parsed;
|
|
15231
16569
|
try {
|
|
15232
16570
|
const raw = JSON.parse(readFileSync18(this.tokensPath, "utf8"));
|
|
15233
|
-
|
|
16571
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
16572
|
+
return this.quarantineCorrupt("parsed JSON is not an object");
|
|
16573
|
+
}
|
|
16574
|
+
parsed = raw;
|
|
15234
16575
|
} catch {
|
|
15235
|
-
|
|
16576
|
+
return this.quarantineCorrupt("unparseable JSON");
|
|
15236
16577
|
}
|
|
15237
|
-
if (!parsed) return { updatedAt: "" };
|
|
15238
16578
|
const decrypted = decryptTokens(parsed, this.box);
|
|
15239
16579
|
return migrateLazily(decrypted);
|
|
15240
16580
|
}
|
|
16581
|
+
/** One-shot latch: a corrupt file is quarantined (or found unmovable) at
|
|
16582
|
+
* most once per process, so the hot read path never re-attempts or re-logs. */
|
|
16583
|
+
corruptQuarantined = false;
|
|
16584
|
+
/**
|
|
16585
|
+
* Quarantine a present-but-corrupt `tokens.json`, then treat it as empty.
|
|
16586
|
+
*
|
|
16587
|
+
* Renames the file to a sibling `tokens.json.corrupt-<stamp>` backup and
|
|
16588
|
+
* logs loudly (the daemon's stderr log; secret-free — reason + paths only).
|
|
16589
|
+
* The daemon KEEPS SERVING (API-key routing is unaffected; subscription
|
|
16590
|
+
* routing reports no credential, same as an absent file) while the corrupt
|
|
16591
|
+
* bytes survive for manual recovery — and, critically, the NEXT persist
|
|
16592
|
+
* (e.g. the user re-logging in) can no longer overwrite the only copy of
|
|
16593
|
+
* the old accounts, which is exactly how the 2026-09-06 incident turned a
|
|
16594
|
+
* recoverable truncated file into permanent account loss.
|
|
16595
|
+
*
|
|
16596
|
+
* Best-effort: if the rename fails (file locked, permissions), the corrupt
|
|
16597
|
+
* file is left in place and every later read still tolerates it as empty;
|
|
16598
|
+
* the latch still trips so the attempt + log happen exactly once.
|
|
16599
|
+
*/
|
|
16600
|
+
quarantineCorrupt(reason) {
|
|
16601
|
+
if (!this.corruptQuarantined) {
|
|
16602
|
+
this.corruptQuarantined = true;
|
|
16603
|
+
const backup = `${this.tokensPath}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
16604
|
+
let moved = false;
|
|
16605
|
+
try {
|
|
16606
|
+
renameSync12(this.tokensPath, backup);
|
|
16607
|
+
moved = true;
|
|
16608
|
+
} catch {
|
|
16609
|
+
}
|
|
16610
|
+
console.error(
|
|
16611
|
+
`[JsonSubscriptionCredentialStore] tokens.json is corrupt (${reason}); ` + (moved ? `moved to '${backup}' and treated as empty \u2014 recover accounts from that backup before re-adding them` : `could not move '${this.tokensPath}' \u2014 treated as empty`)
|
|
16612
|
+
);
|
|
16613
|
+
}
|
|
16614
|
+
return { updatedAt: "" };
|
|
16615
|
+
}
|
|
15241
16616
|
};
|
|
15242
16617
|
|
|
15243
16618
|
// src/AccountHealthProbeScheduler.ts
|
|
15244
|
-
import { fetchUpstream as
|
|
16619
|
+
import { fetchUpstream as fetchUpstream8 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
15245
16620
|
|
|
15246
16621
|
// src/probe/CodexGenerationProbe.ts
|
|
15247
16622
|
import {
|
|
@@ -15383,7 +16758,11 @@ var PROVIDER_PROBE_PLANS = {
|
|
|
15383
16758
|
// billable/wrong endpoint). Upgrade to `{ kind:'upstream' }` once verified.
|
|
15384
16759
|
codex: { kind: "local" },
|
|
15385
16760
|
gemini: { kind: "local" },
|
|
15386
|
-
opencodego: { kind: "local" }
|
|
16761
|
+
opencodego: { kind: "local" },
|
|
16762
|
+
// Kimi's `GET /coding/v1/usages` is a verified FREE authed GET (the allowance
|
|
16763
|
+
// collector uses it), but the probe path also needs the fingerprint headers —
|
|
16764
|
+
// keep the probe local until the collector covers the health surface.
|
|
16765
|
+
kimi: { kind: "local" }
|
|
15387
16766
|
};
|
|
15388
16767
|
function probePlanFor(providerId) {
|
|
15389
16768
|
return PROVIDER_PROBE_PLANS[providerId] ?? { kind: "local" };
|
|
@@ -15405,7 +16784,7 @@ var AccountHealthProbeScheduler = class {
|
|
|
15405
16784
|
this.logger = logger;
|
|
15406
16785
|
this.config = config;
|
|
15407
16786
|
this.now = opts.now ?? Date.now;
|
|
15408
|
-
this.fetchImpl = opts.fetchImpl ??
|
|
16787
|
+
this.fetchImpl = opts.fetchImpl ?? fetchUpstream8;
|
|
15409
16788
|
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
15410
16789
|
this.planFor = opts.planFor ?? probePlanFor;
|
|
15411
16790
|
}
|
|
@@ -15749,13 +17128,13 @@ var AccountHealthSweeper = class {
|
|
|
15749
17128
|
};
|
|
15750
17129
|
|
|
15751
17130
|
// src/audit/AuditPruneSweeper.ts
|
|
15752
|
-
import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as
|
|
17131
|
+
import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync25, readdirSync as readdirSync6, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
|
|
15753
17132
|
import { join as join20 } from "path";
|
|
15754
17133
|
import { pipeline } from "stream/promises";
|
|
15755
17134
|
import { createGzip } from "zlib";
|
|
15756
17135
|
|
|
15757
17136
|
// src/audit/auditDictionary.ts
|
|
15758
|
-
import { existsSync as
|
|
17137
|
+
import { existsSync as existsSync23, readdirSync as readdirSync4, readFileSync as readFileSync19, renameSync as renameSync13, unlinkSync as unlinkSync12, writeFileSync as writeFileSync16 } from "fs";
|
|
15759
17138
|
import { join as join18 } from "path";
|
|
15760
17139
|
|
|
15761
17140
|
// src/audit/auditBodyStore.ts
|
|
@@ -16013,9 +17392,9 @@ function chooseDictionary(anchors) {
|
|
|
16013
17392
|
var EMPTY = { shards: 0, anchors: 0, savedBytes: 0 };
|
|
16014
17393
|
function compactAuditDay(dayPath) {
|
|
16015
17394
|
const bodiesPath = join18(dayPath, AUDIT_BODIES_DIR);
|
|
16016
|
-
if (!
|
|
17395
|
+
if (!existsSync23(bodiesPath)) return EMPTY;
|
|
16017
17396
|
const dictPath = join18(bodiesPath, AUDIT_DICT_FILE);
|
|
16018
|
-
if (
|
|
17397
|
+
if (existsSync23(dictPath) || existsSync23(`${dictPath}.gz`)) return EMPTY;
|
|
16019
17398
|
const shardFiles = plainShards(bodiesPath);
|
|
16020
17399
|
if (shardFiles.length < 2) return EMPTY;
|
|
16021
17400
|
const loaded = /* @__PURE__ */ new Map();
|
|
@@ -16040,7 +17419,7 @@ function compactAuditDay(dayPath) {
|
|
|
16040
17419
|
ts: 0,
|
|
16041
17420
|
req: { base: null, anchor: "dict", pre: 0, suf: 0, ins: dictionary }
|
|
16042
17421
|
};
|
|
16043
|
-
|
|
17422
|
+
writeFileSync16(dictPath, JSON.stringify(dictEntry) + "\n", "utf8");
|
|
16044
17423
|
const result = { shards: 0, anchors: 0, savedBytes: 0 };
|
|
16045
17424
|
for (const [file, entries] of loaded) {
|
|
16046
17425
|
let changed = false;
|
|
@@ -16060,11 +17439,11 @@ function compactAuditDay(dayPath) {
|
|
|
16060
17439
|
const target = join18(bodiesPath, file);
|
|
16061
17440
|
const temp = `${target}.compacting`;
|
|
16062
17441
|
try {
|
|
16063
|
-
|
|
16064
|
-
|
|
17442
|
+
writeFileSync16(temp, rewritten.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
|
|
17443
|
+
renameSync13(temp, target);
|
|
16065
17444
|
} catch {
|
|
16066
17445
|
try {
|
|
16067
|
-
if (
|
|
17446
|
+
if (existsSync23(temp)) unlinkSync12(temp);
|
|
16068
17447
|
} catch {
|
|
16069
17448
|
}
|
|
16070
17449
|
continue;
|
|
@@ -16083,7 +17462,7 @@ function compactAuditDay(dayPath) {
|
|
|
16083
17462
|
}
|
|
16084
17463
|
function compactAllClosedAuditDays(auditDir, now = Date.now) {
|
|
16085
17464
|
const run = { days: 0, shards: 0, savedBytes: 0 };
|
|
16086
|
-
if (!
|
|
17465
|
+
if (!existsSync23(auditDir)) return run;
|
|
16087
17466
|
const today = auditDayDirName(now());
|
|
16088
17467
|
let names;
|
|
16089
17468
|
try {
|
|
@@ -16108,11 +17487,11 @@ function compactAllClosedAuditDays(auditDir, now = Date.now) {
|
|
|
16108
17487
|
// src/audit/auditStats.ts
|
|
16109
17488
|
import {
|
|
16110
17489
|
createReadStream as createReadStream2,
|
|
16111
|
-
existsSync as
|
|
17490
|
+
existsSync as existsSync24,
|
|
16112
17491
|
readFileSync as readFileSync20,
|
|
16113
17492
|
readdirSync as readdirSync5,
|
|
16114
17493
|
statSync as statSync5,
|
|
16115
|
-
writeFileSync as
|
|
17494
|
+
writeFileSync as writeFileSync17
|
|
16116
17495
|
} from "fs";
|
|
16117
17496
|
import { basename as basename9, dirname as dirname16, join as join19 } from "path";
|
|
16118
17497
|
var SIDECAR_VERSION = 1;
|
|
@@ -16122,7 +17501,7 @@ function auditStatsFileName(auditFile) {
|
|
|
16122
17501
|
return auditFile.replace(/\.jsonl$/, ".stats.json");
|
|
16123
17502
|
}
|
|
16124
17503
|
function readPersisted(path2) {
|
|
16125
|
-
if (!
|
|
17504
|
+
if (!existsSync24(path2)) return null;
|
|
16126
17505
|
try {
|
|
16127
17506
|
const value = JSON.parse(readFileSync20(path2, "utf8"));
|
|
16128
17507
|
if (value.version !== SIDECAR_VERSION || !Number.isSafeInteger(value.auditBytes) || (value.auditBytes ?? -1) < 0 || !Number.isSafeInteger(value.requestCount) || (value.requestCount ?? -1) < 0 || !Number.isSafeInteger(value.errorCount) || (value.errorCount ?? -1) < 0 || (value.errorCount ?? 0) > (value.requestCount ?? -1) || typeof value.complete !== "boolean" || value.minTs !== null && !Number.isFinite(value.minTs) || value.maxTs !== null && !Number.isFinite(value.maxTs)) {
|
|
@@ -16154,7 +17533,7 @@ function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfte
|
|
|
16154
17533
|
minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
|
|
16155
17534
|
maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
|
|
16156
17535
|
};
|
|
16157
|
-
|
|
17536
|
+
writeFileSync17(statsPath, JSON.stringify(next), "utf8");
|
|
16158
17537
|
}
|
|
16159
17538
|
function queryCovers(stats, from, to) {
|
|
16160
17539
|
return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
|
|
@@ -16265,7 +17644,7 @@ function mergePersistedStats(previous, appended) {
|
|
|
16265
17644
|
};
|
|
16266
17645
|
}
|
|
16267
17646
|
async function readAuditStats(auditDir, query2 = {}) {
|
|
16268
|
-
if (!
|
|
17647
|
+
if (!existsSync24(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
|
|
16269
17648
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
16270
17649
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
16271
17650
|
let sources;
|
|
@@ -16278,7 +17657,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16278
17657
|
auditPath: join19(auditDir, name),
|
|
16279
17658
|
statsPath: join19(auditDir, auditStatsFileName(name))
|
|
16280
17659
|
}
|
|
16281
|
-
).filter((source) =>
|
|
17660
|
+
).filter((source) => existsSync24(source.auditPath));
|
|
16282
17661
|
} catch {
|
|
16283
17662
|
return { requestCount: 0, errorCount: 0, complete: false };
|
|
16284
17663
|
}
|
|
@@ -16304,7 +17683,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16304
17683
|
total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
|
|
16305
17684
|
total.complete = total.complete && scanned.filtered.complete;
|
|
16306
17685
|
const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
|
|
16307
|
-
if (current.complete)
|
|
17686
|
+
if (current.complete) writeFileSync17(statsPath, JSON.stringify(current), "utf8");
|
|
16308
17687
|
} catch {
|
|
16309
17688
|
total.complete = false;
|
|
16310
17689
|
}
|
|
@@ -16313,7 +17692,7 @@ async function readAuditStats(auditDir, query2 = {}) {
|
|
|
16313
17692
|
}
|
|
16314
17693
|
|
|
16315
17694
|
// src/audit/AuditPruneSweeper.ts
|
|
16316
|
-
var
|
|
17695
|
+
var DAY_MS3 = 24 * 60 * 6e4;
|
|
16317
17696
|
var SWEEP_INTERVAL_MS2 = 60 * 6e4;
|
|
16318
17697
|
var ARCHIVE_BATCH = 64;
|
|
16319
17698
|
var AuditPruneSweeper = class {
|
|
@@ -16376,8 +17755,8 @@ var AuditPruneSweeper = class {
|
|
|
16376
17755
|
if (!this.config.enabled || this.sweeping) return 0;
|
|
16377
17756
|
this.sweeping = true;
|
|
16378
17757
|
try {
|
|
16379
|
-
if (!
|
|
16380
|
-
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) *
|
|
17758
|
+
if (!existsSync25(this.auditDir)) return 0;
|
|
17759
|
+
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS3;
|
|
16381
17760
|
let removed = 0;
|
|
16382
17761
|
for (const name of readdirSync6(this.auditDir)) {
|
|
16383
17762
|
const dateMs = auditFileDateMs(name);
|
|
@@ -16388,7 +17767,7 @@ var AuditPruneSweeper = class {
|
|
|
16388
17767
|
} else {
|
|
16389
17768
|
unlinkSync13(join20(this.auditDir, name));
|
|
16390
17769
|
const statsPath = join20(this.auditDir, auditStatsFileName(name));
|
|
16391
|
-
if (
|
|
17770
|
+
if (existsSync25(statsPath)) unlinkSync13(statsPath);
|
|
16392
17771
|
}
|
|
16393
17772
|
removed += 1;
|
|
16394
17773
|
} catch (error) {
|
|
@@ -16418,7 +17797,7 @@ var AuditPruneSweeper = class {
|
|
|
16418
17797
|
if (!this.config.enabled || this.archiving) return 0;
|
|
16419
17798
|
this.archiving = true;
|
|
16420
17799
|
try {
|
|
16421
|
-
if (!
|
|
17800
|
+
if (!existsSync25(this.auditDir)) return 0;
|
|
16422
17801
|
const today = this.todayMidnight();
|
|
16423
17802
|
let compressed = 0;
|
|
16424
17803
|
for (const name of readdirSync6(this.auditDir)) {
|
|
@@ -16472,7 +17851,7 @@ var AuditPruneSweeper = class {
|
|
|
16472
17851
|
const source = join20(bodiesPath, shard);
|
|
16473
17852
|
const target = `${source}.gz`;
|
|
16474
17853
|
try {
|
|
16475
|
-
if (
|
|
17854
|
+
if (existsSync25(target)) {
|
|
16476
17855
|
unlinkSync13(source);
|
|
16477
17856
|
continue;
|
|
16478
17857
|
}
|
|
@@ -16481,7 +17860,7 @@ var AuditPruneSweeper = class {
|
|
|
16481
17860
|
compressed += 1;
|
|
16482
17861
|
} catch (error) {
|
|
16483
17862
|
try {
|
|
16484
|
-
if (
|
|
17863
|
+
if (existsSync25(target)) unlinkSync13(target);
|
|
16485
17864
|
} catch {
|
|
16486
17865
|
}
|
|
16487
17866
|
this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
|
|
@@ -16634,7 +18013,7 @@ async function closeAll(writers) {
|
|
|
16634
18013
|
// src/usage/UsagePruneSweeper.ts
|
|
16635
18014
|
import { unlink as unlink3 } from "fs/promises";
|
|
16636
18015
|
import { join as join22 } from "path";
|
|
16637
|
-
var
|
|
18016
|
+
var DAY_MS4 = 24 * 60 * 6e4;
|
|
16638
18017
|
var SWEEP_INTERVAL_MS3 = 60 * 6e4;
|
|
16639
18018
|
var DEFAULT_USAGE_RETENTION_DAYS = 90;
|
|
16640
18019
|
var UsagePruneSweeper = class {
|
|
@@ -16691,7 +18070,7 @@ var UsagePruneSweeper = class {
|
|
|
16691
18070
|
this.sweeping = true;
|
|
16692
18071
|
try {
|
|
16693
18072
|
const retentionDays = this.config.retentionDays ?? DEFAULT_USAGE_RETENTION_DAYS;
|
|
16694
|
-
const cutoff = this.todayMidnight() - (retentionDays - 1) *
|
|
18073
|
+
const cutoff = this.todayMidnight() - (retentionDays - 1) * DAY_MS4;
|
|
16695
18074
|
let removed = 0;
|
|
16696
18075
|
for (const entry of await listUsageDays(this.usageDir)) {
|
|
16697
18076
|
if (!entry.hasShard) continue;
|
|
@@ -16749,7 +18128,7 @@ var UsagePruneSweeper = class {
|
|
|
16749
18128
|
};
|
|
16750
18129
|
|
|
16751
18130
|
// src/audit/auditBodyReader.ts
|
|
16752
|
-
import { existsSync as
|
|
18131
|
+
import { existsSync as existsSync26, readdirSync as readdirSync7, readFileSync as readFileSync21, statSync as statSync7 } from "fs";
|
|
16753
18132
|
import { join as join23 } from "path";
|
|
16754
18133
|
import { gunzipSync } from "zlib";
|
|
16755
18134
|
|
|
@@ -16813,7 +18192,7 @@ function forEachLineFromTail(path2, onLine) {
|
|
|
16813
18192
|
function candidateDays(auditDir, ts) {
|
|
16814
18193
|
if (typeof ts === "number" && Number.isFinite(ts)) {
|
|
16815
18194
|
const named = auditDayDirName(ts);
|
|
16816
|
-
if (
|
|
18195
|
+
if (existsSync26(join23(auditDir, named))) return [named];
|
|
16817
18196
|
}
|
|
16818
18197
|
try {
|
|
16819
18198
|
return readdirSync7(auditDir).filter(isAuditDayDir).sort().reverse();
|
|
@@ -16824,9 +18203,9 @@ function candidateDays(auditDir, ts) {
|
|
|
16824
18203
|
function readShard(auditDir, day, sessionKey) {
|
|
16825
18204
|
const base = join23(auditDir, day, AUDIT_BODIES_DIR, auditBodyFileName(sessionKey));
|
|
16826
18205
|
try {
|
|
16827
|
-
if (
|
|
18206
|
+
if (existsSync26(base)) return readFileSync21(base, "utf8");
|
|
16828
18207
|
const gz = `${base}.gz`;
|
|
16829
|
-
if (
|
|
18208
|
+
if (existsSync26(gz)) return gunzipSync(readFileSync21(gz)).toString("utf8");
|
|
16830
18209
|
} catch {
|
|
16831
18210
|
return null;
|
|
16832
18211
|
}
|
|
@@ -16859,8 +18238,8 @@ function withDictionary(auditDir, day, entries) {
|
|
|
16859
18238
|
const base = join23(auditDir, day, AUDIT_BODIES_DIR, AUDIT_DICT_FILE);
|
|
16860
18239
|
let raw = null;
|
|
16861
18240
|
try {
|
|
16862
|
-
if (
|
|
16863
|
-
else if (
|
|
18241
|
+
if (existsSync26(base)) raw = readFileSync21(base, "utf8");
|
|
18242
|
+
else if (existsSync26(`${base}.gz`)) raw = gunzipSync(readFileSync21(`${base}.gz`)).toString("utf8");
|
|
16864
18243
|
} catch {
|
|
16865
18244
|
return entries;
|
|
16866
18245
|
}
|
|
@@ -16893,7 +18272,7 @@ function reconstructRequest(entries, entry) {
|
|
|
16893
18272
|
}
|
|
16894
18273
|
function readAuditBody(auditDir, query2) {
|
|
16895
18274
|
if (!isSafeSessionKey(query2.sessionKey) || !query2.id) return {};
|
|
16896
|
-
if (!
|
|
18275
|
+
if (!existsSync26(auditDir)) return {};
|
|
16897
18276
|
for (const day of candidateDays(auditDir, query2.ts)) {
|
|
16898
18277
|
const raw = readShard(auditDir, day, query2.sessionKey);
|
|
16899
18278
|
if (raw === null) continue;
|
|
@@ -16940,7 +18319,7 @@ function readLegacyInlineBody(auditDir, id) {
|
|
|
16940
18319
|
}
|
|
16941
18320
|
|
|
16942
18321
|
// src/audit/auditReader.ts
|
|
16943
|
-
import { existsSync as
|
|
18322
|
+
import { existsSync as existsSync27, readdirSync as readdirSync8 } from "fs";
|
|
16944
18323
|
import { join as join24 } from "path";
|
|
16945
18324
|
var DEFAULT_LIMIT = 200;
|
|
16946
18325
|
var MAX_LIMIT = 2e3;
|
|
@@ -16958,7 +18337,7 @@ function daySources(auditDir) {
|
|
|
16958
18337
|
if (dateMs === null) continue;
|
|
16959
18338
|
if (AUDIT_DAY_DIR_RE.test(name)) {
|
|
16960
18339
|
const path2 = join24(auditDir, name, AUDIT_META_FILE);
|
|
16961
|
-
if (
|
|
18340
|
+
if (existsSync27(path2)) sources.push({ path: path2, dateMs });
|
|
16962
18341
|
} else if (AUDIT_FILE_RE.test(name)) {
|
|
16963
18342
|
sources.push({ path: join24(auditDir, name), dateMs });
|
|
16964
18343
|
}
|
|
@@ -16976,7 +18355,7 @@ function toMetaRecord(record) {
|
|
|
16976
18355
|
return { ...meta, hasBody: true };
|
|
16977
18356
|
}
|
|
16978
18357
|
function readAuditRecords(auditDir, query2 = {}) {
|
|
16979
|
-
if (!
|
|
18358
|
+
if (!existsSync27(auditDir)) return [];
|
|
16980
18359
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
16981
18360
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
16982
18361
|
const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
|
|
@@ -17004,7 +18383,7 @@ function readAuditRecords(auditDir, query2 = {}) {
|
|
|
17004
18383
|
}
|
|
17005
18384
|
|
|
17006
18385
|
// src/audit/AuditWriter.ts
|
|
17007
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
18386
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync28, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
|
|
17008
18387
|
import { join as join25 } from "path";
|
|
17009
18388
|
var AuditWriter = class {
|
|
17010
18389
|
constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
|
|
@@ -17062,7 +18441,7 @@ var AuditWriter = class {
|
|
|
17062
18441
|
const { requestBody: _req, responseBody: _res, ...meta } = record;
|
|
17063
18442
|
const file = join25(dayPath, AUDIT_META_FILE);
|
|
17064
18443
|
const line = JSON.stringify(meta) + "\n";
|
|
17065
|
-
const bytesBefore =
|
|
18444
|
+
const bytesBefore = existsSync28(file) ? statSync8(file).size : 0;
|
|
17066
18445
|
appendFileSync2(file, line, "utf8");
|
|
17067
18446
|
try {
|
|
17068
18447
|
updateAuditStatsAfterAppend(
|
|
@@ -17110,7 +18489,7 @@ var AuditWriter = class {
|
|
|
17110
18489
|
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync8 } from "fs";
|
|
17111
18490
|
import { createHmac as createHmac5 } from "crypto";
|
|
17112
18491
|
import { join as join26 } from "path";
|
|
17113
|
-
import { fetchUpstream as
|
|
18492
|
+
import { fetchUpstream as fetchUpstream9 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
17114
18493
|
|
|
17115
18494
|
// src/billing/billingFiles.ts
|
|
17116
18495
|
var BILLING_FILE_RE = /^billing-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
|
|
@@ -17133,7 +18512,7 @@ var BillingPublisher = class {
|
|
|
17133
18512
|
constructor(billingDir, logger, opts = {}) {
|
|
17134
18513
|
this.billingDir = billingDir;
|
|
17135
18514
|
this.logger = logger;
|
|
17136
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) =>
|
|
18515
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream9(url, init));
|
|
17137
18516
|
this.defer = opts.defer ?? ((fn) => setTimeout(fn, 0));
|
|
17138
18517
|
this.timeoutMs = opts.timeoutMs ?? BILLING_POST_TIMEOUT_MS;
|
|
17139
18518
|
this.now = opts.now ?? Date.now;
|
|
@@ -17246,11 +18625,11 @@ var BillingPublisher = class {
|
|
|
17246
18625
|
};
|
|
17247
18626
|
|
|
17248
18627
|
// src/billing/billingReader.ts
|
|
17249
|
-
import { existsSync as
|
|
18628
|
+
import { existsSync as existsSync29, readdirSync as readdirSync9, readFileSync as readFileSync22 } from "fs";
|
|
17250
18629
|
import { join as join27 } from "path";
|
|
17251
18630
|
function readBillingLedger(billingDir) {
|
|
17252
18631
|
const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
|
|
17253
|
-
if (!
|
|
18632
|
+
if (!existsSync29(billingDir)) return view;
|
|
17254
18633
|
let files;
|
|
17255
18634
|
try {
|
|
17256
18635
|
files = readdirSync9(billingDir);
|
|
@@ -17383,7 +18762,7 @@ var BillingRetrySweeper = class {
|
|
|
17383
18762
|
// src/TokenRefreshScheduler.ts
|
|
17384
18763
|
var REFRESH_LEAD_MS2 = 5 * 6e4;
|
|
17385
18764
|
var SWEEP_INTERVAL_MS5 = 6e4;
|
|
17386
|
-
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini"];
|
|
18765
|
+
var OAUTH_PROVIDERS2 = ["claude", "codex", "gemini", "kimi"];
|
|
17387
18766
|
var TokenRefreshScheduler = class {
|
|
17388
18767
|
constructor(store, logger, intervalMs = SWEEP_INTERVAL_MS5, leadMs = REFRESH_LEAD_MS2) {
|
|
17389
18768
|
this.store = store;
|
|
@@ -17466,6 +18845,8 @@ var TokenRefreshScheduler = class {
|
|
|
17466
18845
|
return this.store.refreshCodexToken();
|
|
17467
18846
|
case "gemini":
|
|
17468
18847
|
return this.store.refreshGeminiToken();
|
|
18848
|
+
case "kimi":
|
|
18849
|
+
return this.store.refreshKimiToken();
|
|
17469
18850
|
}
|
|
17470
18851
|
}
|
|
17471
18852
|
};
|
|
@@ -17544,7 +18925,7 @@ function createRouteLeaseSubscriptionPreflight(credentials) {
|
|
|
17544
18925
|
|
|
17545
18926
|
// src/webhook/WebhookDispatcher.ts
|
|
17546
18927
|
import { createHmac as createHmac6 } from "crypto";
|
|
17547
|
-
import { fetchUpstream as
|
|
18928
|
+
import { fetchUpstream as fetchUpstream10 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
17548
18929
|
var WEBHOOK_MAX_ATTEMPTS = 3;
|
|
17549
18930
|
var WEBHOOK_QUEUE_MAX = 1e3;
|
|
17550
18931
|
var WEBHOOK_SEND_TIMEOUT_MS = 1e4;
|
|
@@ -17564,7 +18945,7 @@ var WebhookDispatcher = class {
|
|
|
17564
18945
|
sleep;
|
|
17565
18946
|
now;
|
|
17566
18947
|
constructor(opts = {}) {
|
|
17567
|
-
this.fetchImpl = opts.fetchImpl ?? ((url, init) =>
|
|
18948
|
+
this.fetchImpl = opts.fetchImpl ?? ((url, init) => fetchUpstream10(url, init));
|
|
17568
18949
|
this.logger = opts.logger;
|
|
17569
18950
|
this.maxAttempts = opts.maxAttempts ?? WEBHOOK_MAX_ATTEMPTS;
|
|
17570
18951
|
this.queueMax = opts.queueMax ?? WEBHOOK_QUEUE_MAX;
|
|
@@ -17650,8 +19031,8 @@ var WebhookDispatcher = class {
|
|
|
17650
19031
|
signal: AbortSignal.timeout(this.timeoutMs)
|
|
17651
19032
|
});
|
|
17652
19033
|
return res.ok ? { ok: true, status: res.status } : { ok: false, status: res.status };
|
|
17653
|
-
} catch (
|
|
17654
|
-
return { ok: false, error:
|
|
19034
|
+
} catch (err6) {
|
|
19035
|
+
return { ok: false, error: err6 instanceof Error ? err6.message : String(err6) };
|
|
17655
19036
|
}
|
|
17656
19037
|
}
|
|
17657
19038
|
/**
|
|
@@ -17793,7 +19174,7 @@ function buildDaemon(config, paths) {
|
|
|
17793
19174
|
setSecretBox(secretBox3);
|
|
17794
19175
|
setSecretBox2(secretBox3);
|
|
17795
19176
|
const decryptedConfig = decryptConfigSecrets(config, secretBox3);
|
|
17796
|
-
const accountAllowanceStore = new
|
|
19177
|
+
const accountAllowanceStore = new AccountAllowanceStore6(
|
|
17797
19178
|
Date.now,
|
|
17798
19179
|
void 0,
|
|
17799
19180
|
new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
|
|
@@ -17838,6 +19219,7 @@ function buildDaemon(config, paths) {
|
|
|
17838
19219
|
);
|
|
17839
19220
|
setGeminiCodeAssistResolver(getGeminiCodeAssistProjectResolver());
|
|
17840
19221
|
const autoDisableStore = new AutoDisableStore();
|
|
19222
|
+
const providerKeyQuotaService = new ProviderKeyQuotaService(secretBox3);
|
|
17841
19223
|
const apiKeyPool = new ApiKeyPoolService(
|
|
17842
19224
|
createPoolKeysLoader((id) => llmConfig.getProviderRow(id), autoDisableStore),
|
|
17843
19225
|
resolveEnvKey,
|
|
@@ -17854,7 +19236,7 @@ function buildDaemon(config, paths) {
|
|
|
17854
19236
|
const pricingEngine = new PricingEngine(pricingStore, logger, {
|
|
17855
19237
|
// Catalog egress follows the same global/env proxy policy as every other
|
|
17856
19238
|
// daemon upstream call; no provider/account override applies here.
|
|
17857
|
-
fetchImpl: ((input, init) =>
|
|
19239
|
+
fetchImpl: ((input, init) => fetchUpstream11(String(input), init ?? {}))
|
|
17858
19240
|
});
|
|
17859
19241
|
const pricingRefreshScheduler = new PricingRefreshScheduler(
|
|
17860
19242
|
pricingEngine,
|
|
@@ -18118,6 +19500,11 @@ function buildDaemon(config, paths) {
|
|
|
18118
19500
|
// values themselves NEVER leave (masked via `maskProviderApiKey`).
|
|
18119
19501
|
apiKeyPool,
|
|
18120
19502
|
autoDisableStore,
|
|
19503
|
+
// BYO provider-key quota (Z.AI coding plan, MiniMax Token Plan, …): a
|
|
19504
|
+
// read-through cached same-key usage probe surfaced on the keys view. The
|
|
19505
|
+
// key plaintext is resolved + decrypted inside the service and never
|
|
19506
|
+
// crosses back out.
|
|
19507
|
+
providerKeyQuota: providerKeyQuotaService,
|
|
18121
19508
|
// Interactive OAuth login over admin HTTP (app-parity child 4, design
|
|
18122
19509
|
// D1/D2-a). The in-memory pending-session store (NEVER serialized), the
|
|
18123
19510
|
// injected token-exchange fetch (global `fetch` here; mocked in tests), and a
|
|
@@ -18134,7 +19521,7 @@ function buildDaemon(config, paths) {
|
|
|
18134
19521
|
// — `server.proxy.byProvider[...]` was silently skipped — and the call was
|
|
18135
19522
|
// excluded from the upstream trace, so a failing login left no evidence.
|
|
18136
19523
|
// `redactBodies` keeps the code/verifier + minted token out of that trace.
|
|
18137
|
-
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) =>
|
|
19524
|
+
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream11(url, init, { providerId, redactBodies: true }),
|
|
18138
19525
|
subscriptionAccountAppender: credentialStore,
|
|
18139
19526
|
// Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
|
|
18140
19527
|
// + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
|
|
@@ -18142,6 +19529,10 @@ function buildDaemon(config, paths) {
|
|
|
18142
19529
|
// can inject a mock so no real port is bound.
|
|
18143
19530
|
codexSessions: new CodexOAuthSessionStore(),
|
|
18144
19531
|
codexAwaitLoopback: paths.codexAwaitLoopback ?? ((state, timeoutMs, signal) => awaitLoopbackCode(state, timeoutMs, signal)),
|
|
19532
|
+
// Kimi interactive OAuth — the async DEVICE-CODE flow store (no port, no
|
|
19533
|
+
// paste; the app shows the verification URL + user code and polls the
|
|
19534
|
+
// token-free status). Token captured + persisted daemon-side.
|
|
19535
|
+
kimiSessions: new CodexOAuthSessionStore(),
|
|
18145
19536
|
// Migration pack (app-parity child 6, design D2/D3) — the concrete credential
|
|
18146
19537
|
// store provides BOTH the full DECRYPTED read (`getFullConfig`, export) and
|
|
18147
19538
|
// the multi-account append (`appendProviderAccount`, import re-encrypts at-
|
|
@@ -18200,7 +19591,7 @@ function buildDaemon(config, paths) {
|
|
|
18200
19591
|
});
|
|
18201
19592
|
const webhookDispatcher = new WebhookDispatcher({
|
|
18202
19593
|
logger,
|
|
18203
|
-
fetchImpl: (url, init) =>
|
|
19594
|
+
fetchImpl: (url, init) => fetchUpstream11(url, init)
|
|
18204
19595
|
});
|
|
18205
19596
|
setWebhookRuntime(webhookDispatcher, getSharedAccountHealth4());
|
|
18206
19597
|
const auditWriter = new AuditWriter(auditDir, logger);
|
|
@@ -18297,7 +19688,7 @@ function resetDaemonSingletonsForTests() {
|
|
|
18297
19688
|
}
|
|
18298
19689
|
function isTokensStoreReadable(tokensPath) {
|
|
18299
19690
|
try {
|
|
18300
|
-
if (!
|
|
19691
|
+
if (!existsSync30(tokensPath)) return true;
|
|
18301
19692
|
accessSync(tokensPath, fsConstants.R_OK);
|
|
18302
19693
|
return true;
|
|
18303
19694
|
} catch {
|