@omnicross/daemon 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +1186 -200
- package/dist/cli.js +1200 -191
- package/dist/index.cjs +1118 -147
- package/dist/index.d.cts +101 -26
- package/dist/index.d.ts +101 -26
- package/dist/index.js +1135 -139
- package/package.json +63 -63
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 existsSync19 } from "fs";
|
|
3
3
|
import { DEFAULT_AUDIT_CONFIG } from "@omnicross/contracts/audit-types";
|
|
4
4
|
import { DEFAULT_BILLING_CONFIG } from "@omnicross/contracts/billing-types";
|
|
5
5
|
import { getGeminiCodeAssistProjectResolver } from "@omnicross/core/auth/GeminiCodeAssistProjectResolver";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
normalizeServerConfig
|
|
13
13
|
} from "@omnicross/core/outbound-api";
|
|
14
14
|
import { setSubscriptionRegistryForOutbound } from "@omnicross/core/outbound-api/subscriptionRegistryPort";
|
|
15
|
-
import { getSharedAccountHealth as
|
|
15
|
+
import { getSharedAccountHealth as getSharedAccountHealth4 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
16
16
|
import {
|
|
17
17
|
__resetSharedAccountAllowanceStoreForTests,
|
|
18
18
|
AccountAllowanceStore as AccountAllowanceStore3,
|
|
@@ -20,15 +20,18 @@ import {
|
|
|
20
20
|
} from "@omnicross/core/pipeline/AccountAllowanceStore";
|
|
21
21
|
import {
|
|
22
22
|
__resetSharedAccountAllowanceSchedulingForTests,
|
|
23
|
-
getSharedAccountAllowanceScheduling as
|
|
23
|
+
getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling5
|
|
24
24
|
} from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
25
25
|
import { fetchUpstream as fetchUpstream7, setUpstreamProxyResolver } from "@omnicross/core/pipeline/upstreamFetch";
|
|
26
26
|
import { __resetSharedIdentityStoreForTests } from "@omnicross/core/provider-proxy/identity/SubscriptionIdentityStore";
|
|
27
27
|
import { setGeminiCodeAssistResolver } from "@omnicross/core/ports/gemini-code-assist-resolver";
|
|
28
28
|
import {
|
|
29
29
|
__resetProviderProxyForTests,
|
|
30
|
-
getProviderProxy
|
|
30
|
+
getProviderProxy,
|
|
31
|
+
RouteLeaseManager,
|
|
32
|
+
RouteLeaseTargetResolver
|
|
31
33
|
} from "@omnicross/core/provider-proxy";
|
|
34
|
+
import { routeLeaseDescriptorPort } from "@omnicross/cli-launcher";
|
|
32
35
|
import { KeySpendTracker } from "@omnicross/core/outbound-api";
|
|
33
36
|
import { PricingEngine, UsageRecorder } from "@omnicross/core/usage";
|
|
34
37
|
import {
|
|
@@ -114,7 +117,7 @@ async function runCodexLoopback(sessionId, codeVerifier, state, signal, deps) {
|
|
|
114
117
|
const code = await deps.codexAwaitLoopback(state, void 0, signal);
|
|
115
118
|
const result = await codexOAuth.exchangeCodeForTokens(
|
|
116
119
|
{ authorizationCode: code, codeVerifier, state },
|
|
117
|
-
deps.oauthExchangeFetch
|
|
120
|
+
deps.oauthExchangeFetch("codex")
|
|
118
121
|
);
|
|
119
122
|
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
120
123
|
const block = {
|
|
@@ -634,6 +637,17 @@ function handleAuditQuery(req, res, reader) {
|
|
|
634
637
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
635
638
|
res.end(JSON.stringify({ records }));
|
|
636
639
|
}
|
|
640
|
+
async function handleAuditStatsQuery(req, res, reader) {
|
|
641
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
642
|
+
const query2 = {};
|
|
643
|
+
const from = intParam(url.searchParams.get("from"));
|
|
644
|
+
if (from !== void 0) query2.from = from;
|
|
645
|
+
const to = intParam(url.searchParams.get("to"));
|
|
646
|
+
if (to !== void 0) query2.to = to;
|
|
647
|
+
const stats = reader ? await reader(query2) : { requestCount: 0, errorCount: 0, complete: true };
|
|
648
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
649
|
+
res.end(JSON.stringify(stats));
|
|
650
|
+
}
|
|
637
651
|
|
|
638
652
|
// src/admin/billingStatusApi.ts
|
|
639
653
|
function handleBillingStatus(res, reader) {
|
|
@@ -730,6 +744,93 @@ async function handleWebhookTest(req, res) {
|
|
|
730
744
|
res.end(JSON.stringify({ result }));
|
|
731
745
|
}
|
|
732
746
|
|
|
747
|
+
// src/admin/routeLeaseApi.ts
|
|
748
|
+
import {
|
|
749
|
+
isLoopbackAddress,
|
|
750
|
+
normalizeRouteLeaseTtl,
|
|
751
|
+
ROUTE_LEASE_CAPABILITIES,
|
|
752
|
+
RouteLeaseError
|
|
753
|
+
} from "@omnicross/core/provider-proxy";
|
|
754
|
+
var MAX_BODY_BYTES = 64 * 1024;
|
|
755
|
+
var SAFE_LEASE_ID = /^[A-Za-z0-9-]{1,128}$/u;
|
|
756
|
+
async function readJson(req) {
|
|
757
|
+
const chunks = [];
|
|
758
|
+
let bytes = 0;
|
|
759
|
+
for await (const chunk of req) {
|
|
760
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
761
|
+
bytes += buffer.length;
|
|
762
|
+
if (bytes > MAX_BODY_BYTES) throw new RouteLeaseError("invalid_request", "request body is too large");
|
|
763
|
+
chunks.push(buffer);
|
|
764
|
+
}
|
|
765
|
+
if (chunks.length === 0) return {};
|
|
766
|
+
try {
|
|
767
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
768
|
+
} catch {
|
|
769
|
+
throw new RouteLeaseError("invalid_request", "request body is not valid JSON");
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function json(res, status, body, noStore = false) {
|
|
773
|
+
res.statusCode = status;
|
|
774
|
+
res.setHeader("Content-Type", "application/json");
|
|
775
|
+
if (noStore) res.setHeader("Cache-Control", "no-store");
|
|
776
|
+
res.end(JSON.stringify(body));
|
|
777
|
+
}
|
|
778
|
+
function leaseId(value) {
|
|
779
|
+
if (!value || !SAFE_LEASE_ID.test(value)) throw new RouteLeaseError("invalid_request", "lease id is invalid");
|
|
780
|
+
return value;
|
|
781
|
+
}
|
|
782
|
+
function header(req, name) {
|
|
783
|
+
const value = req.headers[name.toLowerCase()];
|
|
784
|
+
return Array.isArray(value) ? value[0] : value;
|
|
785
|
+
}
|
|
786
|
+
function writeError(res, error, noStore) {
|
|
787
|
+
const safe = error instanceof RouteLeaseError ? error : new RouteLeaseError("upstream_unavailable", "route lease operation failed safely");
|
|
788
|
+
if (safe.retryAfterSeconds !== void 0) res.setHeader("Retry-After", String(safe.retryAfterSeconds));
|
|
789
|
+
json(res, safe.status, safe.toResponse(), noStore);
|
|
790
|
+
}
|
|
791
|
+
async function handleRouteLeaseApi(req, res, path2, deps) {
|
|
792
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
793
|
+
const noStore = method === "POST" && (path2 === "/admin/api/route-leases" || path2.endsWith("/renew"));
|
|
794
|
+
try {
|
|
795
|
+
if (!isLoopbackAddress(req.socket.remoteAddress)) {
|
|
796
|
+
throw new RouteLeaseError("control_unauthorized", "route lease control plane is loopback only");
|
|
797
|
+
}
|
|
798
|
+
const manager = deps.routeLeaseManager;
|
|
799
|
+
if (!manager) throw new RouteLeaseError("daemon_not_ready", "route lease manager is unavailable");
|
|
800
|
+
const base = "/admin/api/route-leases";
|
|
801
|
+
const suffix = path2.slice(base.length).replace(/^\/+|\/+$/gu, "");
|
|
802
|
+
const segments = suffix ? suffix.split("/") : [];
|
|
803
|
+
if (segments.length === 1 && segments[0] === "capabilities") {
|
|
804
|
+
if (method !== "GET" && method !== "HEAD") throw new RouteLeaseError("invalid_request", "method is not allowed");
|
|
805
|
+
return json(res, 200, ROUTE_LEASE_CAPABILITIES);
|
|
806
|
+
}
|
|
807
|
+
if (segments.length === 0) {
|
|
808
|
+
if (method === "GET") return json(res, 200, { leases: manager.list() });
|
|
809
|
+
if (method === "POST") {
|
|
810
|
+
const outcome = await manager.createFromRequest(await readJson(req), header(req, "idempotency-key"));
|
|
811
|
+
return json(res, outcome.created ? 201 : 200, outcome.result, true);
|
|
812
|
+
}
|
|
813
|
+
throw new RouteLeaseError("invalid_request", "method is not allowed");
|
|
814
|
+
}
|
|
815
|
+
const id = leaseId(segments[0]);
|
|
816
|
+
if (segments.length === 1) {
|
|
817
|
+
if (method === "GET") return json(res, 200, manager.get(id));
|
|
818
|
+
if (method === "DELETE") return json(res, 200, manager.release(id));
|
|
819
|
+
throw new RouteLeaseError("invalid_request", "method is not allowed");
|
|
820
|
+
}
|
|
821
|
+
if (segments.length === 2 && segments[1] === "renew" && method === "POST") {
|
|
822
|
+
const body = await readJson(req);
|
|
823
|
+
const ttl = normalizeRouteLeaseTtl(
|
|
824
|
+
body && typeof body === "object" && !Array.isArray(body) ? body.ttlSeconds : void 0
|
|
825
|
+
);
|
|
826
|
+
return json(res, 200, manager.renew(id, ttl), true);
|
|
827
|
+
}
|
|
828
|
+
throw new RouteLeaseError("lease_not_found", "route lease endpoint was not found");
|
|
829
|
+
} catch (error) {
|
|
830
|
+
writeError(res, error, noStore);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
733
834
|
// src/admin/adminApi.ts
|
|
734
835
|
import http from "http";
|
|
735
836
|
import {
|
|
@@ -2145,7 +2246,13 @@ function listMappablePresets() {
|
|
|
2145
2246
|
name: preset.name,
|
|
2146
2247
|
apiFormat: resolved.format,
|
|
2147
2248
|
baseUrl: preset.api_base_url,
|
|
2148
|
-
models: Array.isArray(preset.models) ? preset.models : []
|
|
2249
|
+
models: Array.isArray(preset.models) ? preset.models : [],
|
|
2250
|
+
nameKey: preset.nameKey,
|
|
2251
|
+
icon: preset.icon,
|
|
2252
|
+
description: preset.description,
|
|
2253
|
+
features: preset.features,
|
|
2254
|
+
website: preset.website,
|
|
2255
|
+
modelsEndpoint: preset.modelsEndpoint
|
|
2149
2256
|
});
|
|
2150
2257
|
}
|
|
2151
2258
|
return { mappable, excluded };
|
|
@@ -2536,7 +2643,7 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
2536
2643
|
const rawCode = typeof body["code"] === "string" ? body["code"] : "";
|
|
2537
2644
|
if (!sessionId) return err2(400, "oauth complete requires { sessionId }");
|
|
2538
2645
|
if (!rawCode) return err2(400, "oauth complete requires { code }");
|
|
2539
|
-
const session = deps.oauthSessions.
|
|
2646
|
+
const session = deps.oauthSessions.peek(sessionId);
|
|
2540
2647
|
if (!session) return err2(410, "oauth session is unknown, expired, or already used");
|
|
2541
2648
|
if (session.providerId !== providerId) {
|
|
2542
2649
|
return err2(400, `oauth session does not match provider '${providerId}'`);
|
|
@@ -2550,13 +2657,15 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
2550
2657
|
}
|
|
2551
2658
|
code = splitCode;
|
|
2552
2659
|
}
|
|
2660
|
+
const exchangeFetch = deps.oauthExchangeFetch(providerId);
|
|
2553
2661
|
let block;
|
|
2554
2662
|
try {
|
|
2555
|
-
block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state,
|
|
2663
|
+
block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
|
|
2556
2664
|
} catch (exchangeError) {
|
|
2557
2665
|
const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
|
|
2558
2666
|
return err2(502, `oauth token exchange failed for '${providerId}': ${reason}`);
|
|
2559
2667
|
}
|
|
2668
|
+
deps.oauthSessions.consume(sessionId);
|
|
2560
2669
|
const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
|
|
2561
2670
|
await deps.subscriptionAccountAppender.appendProviderAccount(providerId, block, label);
|
|
2562
2671
|
const status = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
@@ -2594,7 +2703,9 @@ async function exchangeGemini(code, codeVerifier, exchangeFetch) {
|
|
|
2594
2703
|
// src/admin/cliLaunch.ts
|
|
2595
2704
|
import { exec, spawn } from "child_process";
|
|
2596
2705
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2597
|
-
import { existsSync as existsSync5 } from "fs";
|
|
2706
|
+
import { chmodSync as chmodSync3, existsSync as existsSync5, mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
2707
|
+
import { createServer } from "net";
|
|
2708
|
+
import { tmpdir } from "os";
|
|
2598
2709
|
import { delimiter, join as join3 } from "path";
|
|
2599
2710
|
import {
|
|
2600
2711
|
buildChatCliLaunchConfig,
|
|
@@ -2602,6 +2713,33 @@ import {
|
|
|
2602
2713
|
buildCodexLaunchConfig,
|
|
2603
2714
|
buildGeminiCliLaunchConfig
|
|
2604
2715
|
} from "@omnicross/cli-launcher";
|
|
2716
|
+
import {
|
|
2717
|
+
ROUTE_LEASE_REQUEST_SCHEMA,
|
|
2718
|
+
RouteLeaseError as RouteLeaseError2
|
|
2719
|
+
} from "@omnicross/core/provider-proxy";
|
|
2720
|
+
|
|
2721
|
+
// src/routeLeaseRenewal.ts
|
|
2722
|
+
var TERMINAL_LEASE_TTL_SECONDS = 600;
|
|
2723
|
+
var TERMINAL_LEASE_RENEW_INTERVAL_MS = 5 * 60 * 1e3;
|
|
2724
|
+
var TERMINAL_LEASE_MAX_LIFETIME_MS = 24 * 60 * 60 * 1e3;
|
|
2725
|
+
function startTerminalLeaseRenewal(manager, leaseId2) {
|
|
2726
|
+
const stopAt = Date.now() + TERMINAL_LEASE_MAX_LIFETIME_MS;
|
|
2727
|
+
const timer = setInterval(() => {
|
|
2728
|
+
if (Date.now() >= stopAt) {
|
|
2729
|
+
clearInterval(timer);
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
try {
|
|
2733
|
+
manager.renew(leaseId2, TERMINAL_LEASE_TTL_SECONDS);
|
|
2734
|
+
} catch {
|
|
2735
|
+
clearInterval(timer);
|
|
2736
|
+
}
|
|
2737
|
+
}, TERMINAL_LEASE_RENEW_INTERVAL_MS);
|
|
2738
|
+
timer.unref?.();
|
|
2739
|
+
return () => clearInterval(timer);
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
// src/admin/cliLaunch.ts
|
|
2605
2743
|
var LAUNCHABLE_CLIS = [
|
|
2606
2744
|
{ id: "claude", displayName: "Claude Code", command: "claude" },
|
|
2607
2745
|
{ id: "codex", displayName: "Codex CLI", command: "codex" },
|
|
@@ -2682,34 +2820,183 @@ async function buildLaunchEnv(cli, llmConfig, target) {
|
|
|
2682
2820
|
function shq(s) {
|
|
2683
2821
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
2684
2822
|
}
|
|
2685
|
-
var
|
|
2823
|
+
var MAC_TERMINAL_BOOTSTRAP_SOURCE = `
|
|
2824
|
+
'use strict';
|
|
2825
|
+
const fs = require('node:fs');
|
|
2826
|
+
const net = require('node:net');
|
|
2827
|
+
const { spawn } = require('node:child_process');
|
|
2828
|
+
const [socketPath, launchDir, cwd, command, ...args] = process.argv.slice(2);
|
|
2829
|
+
let payload = '';
|
|
2830
|
+
const socket = net.createConnection(socketPath);
|
|
2831
|
+
socket.setEncoding('utf8');
|
|
2832
|
+
socket.on('data', (chunk) => { payload += chunk; });
|
|
2833
|
+
socket.on('end', () => {
|
|
2834
|
+
const descriptor = JSON.parse(payload);
|
|
2835
|
+
if (!descriptor || Array.isArray(descriptor) || Object.values(descriptor).some((value) => typeof value !== 'string')) {
|
|
2836
|
+
throw new Error('invalid terminal launch descriptor');
|
|
2837
|
+
}
|
|
2838
|
+
try { fs.rmSync(launchDir, { recursive: true, force: true }); } catch {}
|
|
2839
|
+
const child = spawn(command, args, {
|
|
2840
|
+
cwd: cwd || undefined,
|
|
2841
|
+
env: { ...process.env, ...descriptor },
|
|
2842
|
+
stdio: 'inherit',
|
|
2843
|
+
});
|
|
2844
|
+
child.on('error', (error) => { console.error(error.message); process.exitCode = 1; });
|
|
2845
|
+
child.on('exit', (code, signal) => {
|
|
2846
|
+
if (signal) process.kill(process.pid, signal);
|
|
2847
|
+
else process.exitCode = code == null ? 1 : code;
|
|
2848
|
+
});
|
|
2849
|
+
});
|
|
2850
|
+
socket.on('error', (error) => { console.error(error.message); process.exitCode = 1; });
|
|
2851
|
+
`;
|
|
2852
|
+
var MAC_TERMINAL_IPC_TIMEOUT_MS = 12e4;
|
|
2853
|
+
function openTerminal({ cli, command, extraArgs, env, cwd, platform, onFailure }, spawnProcess = spawn, macIpc = {}) {
|
|
2686
2854
|
const childEnv = { ...process.env, ...env };
|
|
2687
2855
|
if (platform === "win32") {
|
|
2688
2856
|
const args = ["/c", "start", `"omnicross ${cli}"`];
|
|
2689
2857
|
if (cwd) args.push("/D", `"${cwd}"`);
|
|
2690
2858
|
args.push("cmd", "/k", command, ...extraArgs);
|
|
2691
|
-
|
|
2859
|
+
spawnProcess(process.env["ComSpec"] || "cmd.exe", args, {
|
|
2692
2860
|
env: childEnv,
|
|
2693
2861
|
windowsVerbatimArguments: true,
|
|
2694
2862
|
detached: true,
|
|
2695
2863
|
stdio: "ignore"
|
|
2696
2864
|
}).unref();
|
|
2697
|
-
return
|
|
2865
|
+
return () => {
|
|
2866
|
+
};
|
|
2698
2867
|
}
|
|
2699
|
-
const exportLine = Object.entries(env).map(([k, v]) => `export ${k}=${shq(v)}`).join("; ");
|
|
2700
2868
|
const runLine = [command, ...extraArgs].map(shq).join(" ");
|
|
2701
|
-
const script = `${
|
|
2869
|
+
const script = `${cwd ? `cd ${shq(cwd)}; ` : ""}${runLine}`;
|
|
2702
2870
|
if (platform === "darwin") {
|
|
2703
|
-
const
|
|
2704
|
-
|
|
2705
|
-
|
|
2871
|
+
const launchDir = mkdtempSync(join3(tmpdir(), "omnicross-terminal-"));
|
|
2872
|
+
const commandFile = join3(launchDir, "launch.command");
|
|
2873
|
+
const bootstrapFile = join3(launchDir, "bootstrap.cjs");
|
|
2874
|
+
const socketPath = macIpc.socketPath ?? join3(launchDir, "descriptor.sock");
|
|
2875
|
+
const openerEnv = { ...process.env };
|
|
2876
|
+
for (const key of Object.keys(env)) delete openerEnv[key];
|
|
2877
|
+
let claimed = false;
|
|
2878
|
+
let cleaned = false;
|
|
2879
|
+
let failureNotified = false;
|
|
2880
|
+
let timer;
|
|
2881
|
+
const notifyFailure = () => {
|
|
2882
|
+
cleanup();
|
|
2883
|
+
if (failureNotified) return;
|
|
2884
|
+
failureNotified = true;
|
|
2885
|
+
try {
|
|
2886
|
+
onFailure?.();
|
|
2887
|
+
} catch {
|
|
2888
|
+
}
|
|
2889
|
+
};
|
|
2890
|
+
const handleLaunchFailure = () => {
|
|
2891
|
+
if (claimed) cleanup();
|
|
2892
|
+
else notifyFailure();
|
|
2893
|
+
};
|
|
2894
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
2895
|
+
const server = createServer((socket) => {
|
|
2896
|
+
socket.unref();
|
|
2897
|
+
sockets.add(socket);
|
|
2898
|
+
socket.once("close", () => sockets.delete(socket));
|
|
2899
|
+
try {
|
|
2900
|
+
macIpc.onAccepted?.(socket);
|
|
2901
|
+
} catch {
|
|
2902
|
+
cleanup();
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
if (claimed || cleaned) {
|
|
2906
|
+
socket.destroy();
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
claimed = true;
|
|
2910
|
+
try {
|
|
2911
|
+
macIpc.onClaimed?.();
|
|
2912
|
+
if (cleaned) return;
|
|
2913
|
+
socket.end(JSON.stringify(env), cleanup);
|
|
2914
|
+
} catch {
|
|
2915
|
+
cleanup();
|
|
2916
|
+
}
|
|
2917
|
+
});
|
|
2918
|
+
const cleanup = () => {
|
|
2919
|
+
if (!cleaned) {
|
|
2920
|
+
cleaned = true;
|
|
2921
|
+
if (timer) clearTimeout(timer);
|
|
2922
|
+
for (const socket of sockets) socket.destroy();
|
|
2923
|
+
sockets.clear();
|
|
2924
|
+
try {
|
|
2925
|
+
server.close();
|
|
2926
|
+
} catch {
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
try {
|
|
2930
|
+
if (macIpc.removeArtifacts) {
|
|
2931
|
+
macIpc.removeArtifacts(launchDir);
|
|
2932
|
+
} else {
|
|
2933
|
+
rmSync2(launchDir, {
|
|
2934
|
+
recursive: true,
|
|
2935
|
+
force: true,
|
|
2936
|
+
maxRetries: 3,
|
|
2937
|
+
retryDelay: 20
|
|
2938
|
+
});
|
|
2939
|
+
}
|
|
2940
|
+
} catch {
|
|
2941
|
+
}
|
|
2942
|
+
};
|
|
2943
|
+
try {
|
|
2944
|
+
writeFileSync5(bootstrapFile, MAC_TERMINAL_BOOTSTRAP_SOURCE, { encoding: "utf8", mode: 448 });
|
|
2945
|
+
writeFileSync5(commandFile, `#!/bin/bash
|
|
2946
|
+
rm -f -- "$0"
|
|
2947
|
+
exec ${shq(process.execPath)} ${shq(bootstrapFile)} ${shq(socketPath)} ${shq(launchDir)} ${shq(cwd ?? "")} ${runLine}
|
|
2948
|
+
`, {
|
|
2949
|
+
encoding: "utf8",
|
|
2950
|
+
mode: 448
|
|
2951
|
+
});
|
|
2952
|
+
chmodSync3(commandFile, 448);
|
|
2953
|
+
chmodSync3(bootstrapFile, 448);
|
|
2954
|
+
server.once("error", handleLaunchFailure);
|
|
2955
|
+
server.listen(socketPath, () => {
|
|
2956
|
+
if (cleaned) return;
|
|
2957
|
+
try {
|
|
2958
|
+
macIpc.onListening?.();
|
|
2959
|
+
if (cleaned) return;
|
|
2960
|
+
if (process.platform !== "win32") chmodSync3(socketPath, 384);
|
|
2961
|
+
const opener = spawnProcess("open", ["-n", "-a", "Terminal", commandFile], {
|
|
2962
|
+
env: openerEnv,
|
|
2963
|
+
detached: true,
|
|
2964
|
+
stdio: "ignore"
|
|
2965
|
+
});
|
|
2966
|
+
opener.once("error", handleLaunchFailure);
|
|
2967
|
+
opener.unref();
|
|
2968
|
+
server.unref();
|
|
2969
|
+
} catch {
|
|
2970
|
+
handleLaunchFailure();
|
|
2971
|
+
}
|
|
2972
|
+
});
|
|
2973
|
+
timer = setTimeout(handleLaunchFailure, macIpc.timeoutMs ?? MAC_TERMINAL_IPC_TIMEOUT_MS);
|
|
2974
|
+
timer.unref?.();
|
|
2975
|
+
return cleanup;
|
|
2976
|
+
} catch (error) {
|
|
2977
|
+
cleanup();
|
|
2978
|
+
throw error;
|
|
2979
|
+
}
|
|
2706
2980
|
}
|
|
2707
|
-
|
|
2981
|
+
spawnProcess("x-terminal-emulator", ["-e", "bash", "-lc", `${script}; exec bash`], {
|
|
2982
|
+
env: childEnv,
|
|
2708
2983
|
detached: true,
|
|
2709
2984
|
stdio: "ignore"
|
|
2710
2985
|
}).unref();
|
|
2711
|
-
|
|
2986
|
+
return () => {
|
|
2987
|
+
};
|
|
2988
|
+
}
|
|
2989
|
+
var defaultTerminalOpener = (input) => openTerminal(input);
|
|
2712
2990
|
var sessions = /* @__PURE__ */ new Map();
|
|
2991
|
+
function resetCliSessions() {
|
|
2992
|
+
for (const s of sessions.values()) {
|
|
2993
|
+
try {
|
|
2994
|
+
s.onSessionEnd();
|
|
2995
|
+
} catch {
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
sessions.clear();
|
|
2999
|
+
}
|
|
2713
3000
|
function errBody(message) {
|
|
2714
3001
|
return { error: { type: "admin_api_error", message } };
|
|
2715
3002
|
}
|
|
@@ -2764,29 +3051,81 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
2764
3051
|
} catch (err5) {
|
|
2765
3052
|
return { status: 400, body: errBody(err5 instanceof Error ? err5.message : "no launch target") };
|
|
2766
3053
|
}
|
|
3054
|
+
const id = randomUUID2();
|
|
3055
|
+
let leaseId2;
|
|
2767
3056
|
let launch;
|
|
2768
3057
|
try {
|
|
2769
|
-
|
|
3058
|
+
if ((cli === "claude" || cli === "codex") && ctx.routeLeaseManager) {
|
|
3059
|
+
const outcome = await ctx.routeLeaseManager.createFromRequest({
|
|
3060
|
+
schemaVersion: ROUTE_LEASE_REQUEST_SCHEMA,
|
|
3061
|
+
consumer: "omnicross-terminal",
|
|
3062
|
+
runtime: cli,
|
|
3063
|
+
upstream: { kind: "provider", providerId: target.providerId },
|
|
3064
|
+
model: target.model,
|
|
3065
|
+
execution: { sessionId: id }
|
|
3066
|
+
}, `omnicross-terminal:${id}`);
|
|
3067
|
+
leaseId2 = outcome.result.leaseId;
|
|
3068
|
+
const stopRenewal = startTerminalLeaseRenewal(ctx.routeLeaseManager, leaseId2);
|
|
3069
|
+
launch = {
|
|
3070
|
+
env: outcome.result.launch.env,
|
|
3071
|
+
extraArgs: outcome.result.launch.extraArgs,
|
|
3072
|
+
onSessionEnd: () => {
|
|
3073
|
+
stopRenewal();
|
|
3074
|
+
ctx.routeLeaseManager?.release(outcome.result.leaseId);
|
|
3075
|
+
}
|
|
3076
|
+
};
|
|
3077
|
+
} else {
|
|
3078
|
+
launch = await buildLaunchEnv(cli, ctx.llmConfig, target);
|
|
3079
|
+
}
|
|
2770
3080
|
} catch (err5) {
|
|
2771
|
-
|
|
3081
|
+
const status = err5 instanceof RouteLeaseError2 ? err5.status : 400;
|
|
3082
|
+
return { status, body: errBody(err5 instanceof Error ? err5.message : "failed to build launch env") };
|
|
2772
3083
|
}
|
|
2773
3084
|
const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
|
|
2774
3085
|
const opener = ctx.opener ?? defaultTerminalOpener;
|
|
3086
|
+
let openerCleanup;
|
|
3087
|
+
let ended = false;
|
|
3088
|
+
let published = false;
|
|
3089
|
+
const onSessionEnd = () => {
|
|
3090
|
+
if (ended) return;
|
|
3091
|
+
ended = true;
|
|
3092
|
+
if (published) sessions.delete(id);
|
|
3093
|
+
try {
|
|
3094
|
+
openerCleanup?.();
|
|
3095
|
+
} finally {
|
|
3096
|
+
launch.onSessionEnd();
|
|
3097
|
+
}
|
|
3098
|
+
};
|
|
2775
3099
|
try {
|
|
2776
|
-
opener({
|
|
3100
|
+
const cleanup = opener({
|
|
3101
|
+
cli,
|
|
3102
|
+
command: meta.command,
|
|
3103
|
+
extraArgs: launch.extraArgs ?? [],
|
|
3104
|
+
env: launch.env,
|
|
3105
|
+
cwd,
|
|
3106
|
+
platform,
|
|
3107
|
+
onFailure: onSessionEnd
|
|
3108
|
+
});
|
|
3109
|
+
if (cleanup) openerCleanup = cleanup;
|
|
2777
3110
|
} catch (err5) {
|
|
2778
|
-
|
|
3111
|
+
onSessionEnd();
|
|
2779
3112
|
return { status: 500, body: errBody(err5 instanceof Error ? err5.message : "failed to open terminal") };
|
|
2780
3113
|
}
|
|
2781
|
-
|
|
3114
|
+
if (ended) {
|
|
3115
|
+
openerCleanup?.();
|
|
3116
|
+
return { status: 500, body: errBody("failed to open terminal") };
|
|
3117
|
+
}
|
|
2782
3118
|
sessions.set(id, {
|
|
2783
3119
|
id,
|
|
2784
3120
|
cli,
|
|
2785
3121
|
providerId: target.providerId,
|
|
2786
3122
|
model: target.model,
|
|
3123
|
+
...leaseId2 ? { leaseId: leaseId2 } : {},
|
|
2787
3124
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2788
|
-
onSessionEnd
|
|
3125
|
+
onSessionEnd
|
|
2789
3126
|
});
|
|
3127
|
+
published = true;
|
|
3128
|
+
if (ended) sessions.delete(id);
|
|
2790
3129
|
return { status: 200, body: { sessionId: id, providerId: target.providerId, model: target.model } };
|
|
2791
3130
|
}
|
|
2792
3131
|
|
|
@@ -2806,8 +3145,8 @@ function validateAuditSegment(patch) {
|
|
|
2806
3145
|
}
|
|
2807
3146
|
}
|
|
2808
3147
|
const maxBodyBytes = audit["maxBodyBytes"];
|
|
2809
|
-
if (maxBodyBytes !== void 0 && (typeof maxBodyBytes !== "number" || !Number.isFinite(maxBodyBytes) || maxBodyBytes <
|
|
2810
|
-
errors.push("audit.maxBodyBytes must be a non-negative number");
|
|
3148
|
+
if (maxBodyBytes !== void 0 && (typeof maxBodyBytes !== "number" || !Number.isFinite(maxBodyBytes) || maxBodyBytes < -1)) {
|
|
3149
|
+
errors.push("audit.maxBodyBytes must be -1 or a non-negative number");
|
|
2811
3150
|
}
|
|
2812
3151
|
const retentionDays = audit["retentionDays"];
|
|
2813
3152
|
if (retentionDays !== void 0 && (typeof retentionDays !== "number" || !Number.isFinite(retentionDays) || retentionDays < 0)) {
|
|
@@ -3252,18 +3591,16 @@ function preserveWebhookSecrets(incoming, current) {
|
|
|
3252
3591
|
}
|
|
3253
3592
|
|
|
3254
3593
|
// src/audit/auditRuntime.ts
|
|
3255
|
-
import { join as join4 } from "path";
|
|
3256
3594
|
import { setAuditCaptureConfig, setAuditSink } from "@omnicross/core/pipeline/auditSink";
|
|
3257
3595
|
import { setUpstreamTracePath } from "@omnicross/core/pipeline/upstreamTrace";
|
|
3258
3596
|
var writer = null;
|
|
3259
3597
|
var sweeper = null;
|
|
3260
|
-
|
|
3261
|
-
function setAuditRuntime(w, s, dir) {
|
|
3598
|
+
function setAuditRuntime(w, s) {
|
|
3262
3599
|
writer = w;
|
|
3263
3600
|
sweeper = s;
|
|
3264
|
-
auditDir = dir;
|
|
3265
3601
|
}
|
|
3266
3602
|
function applyAuditConfig(config) {
|
|
3603
|
+
setUpstreamTracePath(null);
|
|
3267
3604
|
const enabled = config?.enabled === true && writer !== null;
|
|
3268
3605
|
if (enabled && config) {
|
|
3269
3606
|
setAuditCaptureConfig(config);
|
|
@@ -3273,11 +3610,9 @@ function applyAuditConfig(config) {
|
|
|
3273
3610
|
sweeper.configure(config);
|
|
3274
3611
|
sweeper.start();
|
|
3275
3612
|
}
|
|
3276
|
-
setUpstreamTracePath(config.captureBodies ? join4(auditDir, "upstream-trace.jsonl") : null);
|
|
3277
3613
|
} else {
|
|
3278
3614
|
setAuditCaptureConfig(null);
|
|
3279
3615
|
setAuditSink(null);
|
|
3280
|
-
setUpstreamTracePath(null);
|
|
3281
3616
|
if (sweeper) {
|
|
3282
3617
|
if (config) sweeper.configure(config);
|
|
3283
3618
|
sweeper.dispose();
|
|
@@ -3291,7 +3626,6 @@ function resetAuditRuntimeForTests() {
|
|
|
3291
3626
|
if (sweeper) sweeper.dispose();
|
|
3292
3627
|
writer = null;
|
|
3293
3628
|
sweeper = null;
|
|
3294
|
-
auditDir = "";
|
|
3295
3629
|
}
|
|
3296
3630
|
|
|
3297
3631
|
// src/billing/billingRuntime.ts
|
|
@@ -3693,7 +4027,7 @@ function sealPack(bundleJson, passphrase) {
|
|
|
3693
4027
|
cipher.setAAD(aadFor(PACK_MAGIC, PACK_VERSION, KDF_ALGORITHM));
|
|
3694
4028
|
const ciphertext = Buffer.concat([cipher.update(bundleJson, "utf8"), cipher.final()]);
|
|
3695
4029
|
const tag = cipher.getAuthTag();
|
|
3696
|
-
const
|
|
4030
|
+
const header2 = {
|
|
3697
4031
|
magic: PACK_MAGIC,
|
|
3698
4032
|
v: PACK_VERSION,
|
|
3699
4033
|
kdf: KDF_ALGORITHM,
|
|
@@ -3704,7 +4038,7 @@ function sealPack(bundleJson, passphrase) {
|
|
|
3704
4038
|
iv: iv.toString("base64"),
|
|
3705
4039
|
tag: tag.toString("base64")
|
|
3706
4040
|
};
|
|
3707
|
-
return `${PACK_PREFIX}${toB64Url(JSON.stringify(
|
|
4041
|
+
return `${PACK_PREFIX}${toB64Url(JSON.stringify(header2))}.${ciphertext.toString("base64")}`;
|
|
3708
4042
|
}
|
|
3709
4043
|
function parsePack(packString) {
|
|
3710
4044
|
if (typeof packString !== "string" || !packString.startsWith(PACK_PREFIX)) {
|
|
@@ -3715,28 +4049,28 @@ function parsePack(packString) {
|
|
|
3715
4049
|
if (dot < 0) throw new PackAuthError("migration pack is malformed (missing body)");
|
|
3716
4050
|
const headerB64Url = rest.slice(0, dot);
|
|
3717
4051
|
const ctB64 = rest.slice(dot + 1);
|
|
3718
|
-
let
|
|
4052
|
+
let header2;
|
|
3719
4053
|
try {
|
|
3720
|
-
|
|
4054
|
+
header2 = JSON.parse(fromB64Url(headerB64Url));
|
|
3721
4055
|
} catch {
|
|
3722
4056
|
throw new PackAuthError("migration pack is malformed (unreadable header)");
|
|
3723
4057
|
}
|
|
3724
|
-
if (!
|
|
4058
|
+
if (!header2 || header2.magic !== PACK_MAGIC || header2.v !== PACK_VERSION || header2.kdf !== KDF_ALGORITHM || typeof header2.salt !== "string" || typeof header2.iv !== "string" || typeof header2.tag !== "string" || typeof header2.N !== "number" || typeof header2.r !== "number" || typeof header2.p !== "number") {
|
|
3725
4059
|
throw new PackAuthError("migration pack is malformed (unsupported header)");
|
|
3726
4060
|
}
|
|
3727
4061
|
const ciphertext = Buffer.from(ctB64, "base64");
|
|
3728
|
-
return { header, ciphertext };
|
|
4062
|
+
return { header: header2, ciphertext };
|
|
3729
4063
|
}
|
|
3730
4064
|
function openPack(packString, passphrase) {
|
|
3731
4065
|
assertPassphraseStrength(passphrase);
|
|
3732
|
-
const { header, ciphertext } = parsePack(packString);
|
|
3733
|
-
const salt = Buffer.from(
|
|
3734
|
-
const iv = Buffer.from(
|
|
3735
|
-
const tag = Buffer.from(
|
|
4066
|
+
const { header: header2, ciphertext } = parsePack(packString);
|
|
4067
|
+
const salt = Buffer.from(header2.salt, "base64");
|
|
4068
|
+
const iv = Buffer.from(header2.iv, "base64");
|
|
4069
|
+
const tag = Buffer.from(header2.tag, "base64");
|
|
3736
4070
|
if (iv.length !== IV_BYTES2 || tag.length !== TAG_BYTES2) {
|
|
3737
4071
|
throw new PackAuthError("migration pack is malformed (invalid iv/tag length)");
|
|
3738
4072
|
}
|
|
3739
|
-
const key = deriveKey(passphrase, salt,
|
|
4073
|
+
const key = deriveKey(passphrase, salt, header2.N, header2.r, header2.p);
|
|
3740
4074
|
const decipher = createDecipheriv2("aes-256-gcm", key, iv);
|
|
3741
4075
|
decipher.setAAD(aadFor(PACK_MAGIC, PACK_VERSION, KDF_ALGORITHM));
|
|
3742
4076
|
decipher.setAuthTag(tag);
|
|
@@ -4072,10 +4406,10 @@ function writeJson2(res, status, body) {
|
|
|
4072
4406
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
4073
4407
|
res.end(JSON.stringify(body));
|
|
4074
4408
|
}
|
|
4075
|
-
function
|
|
4409
|
+
function writeError2(res, status, message) {
|
|
4076
4410
|
writeJson2(res, status, { error: { type: "account_allowance_error", message } });
|
|
4077
4411
|
}
|
|
4078
|
-
function
|
|
4412
|
+
function readJson2(req) {
|
|
4079
4413
|
return new Promise((resolve2, reject) => {
|
|
4080
4414
|
const chunks = [];
|
|
4081
4415
|
req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
@@ -4101,10 +4435,10 @@ function allowanceProvider(value) {
|
|
|
4101
4435
|
return value === "claude" || value === "codex" ? value : null;
|
|
4102
4436
|
}
|
|
4103
4437
|
async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
4104
|
-
if (!service) return
|
|
4438
|
+
if (!service) return writeError2(res, 501, "account allowance service is not available");
|
|
4105
4439
|
if (method === "GET" && rest.length === 1 && rest[0] === "scheduling") {
|
|
4106
4440
|
if (!service.getSchedulingStatus) {
|
|
4107
|
-
return
|
|
4441
|
+
return writeError2(res, 501, "allowance scheduling diagnostics are not available");
|
|
4108
4442
|
}
|
|
4109
4443
|
return writeJson2(res, 200, { scheduling: service.getSchedulingStatus() });
|
|
4110
4444
|
}
|
|
@@ -4112,30 +4446,35 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
4112
4446
|
const params = query(req);
|
|
4113
4447
|
const pathProvider = rest.length >= 2 ? rest[0] : null;
|
|
4114
4448
|
const providerId = allowanceProvider(pathProvider ?? params.get("providerId") ?? params.get("provider"));
|
|
4115
|
-
if (providerId === null) return
|
|
4449
|
+
if (providerId === null) return writeError2(res, 400, "providerId must be claude or codex");
|
|
4116
4450
|
const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
|
|
4117
4451
|
const allowances = await service.list({ providerId, accountId });
|
|
4118
4452
|
return writeJson2(res, 200, { allowances });
|
|
4119
4453
|
}
|
|
4120
4454
|
if (method === "POST" && rest[0] === "refresh") {
|
|
4121
|
-
const body = await
|
|
4455
|
+
const body = await readJson2(req);
|
|
4122
4456
|
const requestedProvider = allowanceProvider(
|
|
4123
4457
|
typeof body["providerId"] === "string" ? body["providerId"] : "claude"
|
|
4124
4458
|
);
|
|
4125
4459
|
if (requestedProvider !== "claude") {
|
|
4126
|
-
return
|
|
4460
|
+
return writeError2(res, 400, "only Claude allowances support explicit refresh");
|
|
4127
4461
|
}
|
|
4128
4462
|
const accountId = typeof body["accountId"] === "string" && body["accountId"].trim() ? body["accountId"].trim() : void 0;
|
|
4129
4463
|
const allowances = await service.refreshClaude(accountId);
|
|
4130
4464
|
if (accountId && allowances.length === 0) {
|
|
4131
|
-
return
|
|
4465
|
+
return writeError2(res, 404, `Claude account '${accountId}' not found`);
|
|
4132
4466
|
}
|
|
4133
4467
|
return writeJson2(res, 200, { allowances });
|
|
4134
4468
|
}
|
|
4135
|
-
return
|
|
4469
|
+
return writeError2(res, 405, `method ${method} not allowed on account allowances`);
|
|
4136
4470
|
}
|
|
4137
4471
|
|
|
4138
4472
|
// src/admin/adminApi.ts
|
|
4473
|
+
import {
|
|
4474
|
+
ACCOUNT_ROUTE_ACTIVITY_LIMIT,
|
|
4475
|
+
getSharedAccountRouteActivity
|
|
4476
|
+
} from "@omnicross/core/pipeline/AccountRouteActivity";
|
|
4477
|
+
import { getSharedOverloadCounter } from "@omnicross/core/pipeline/ServerOverloadCounter";
|
|
4139
4478
|
function readBody(req) {
|
|
4140
4479
|
return new Promise((resolve2, reject) => {
|
|
4141
4480
|
const chunks = [];
|
|
@@ -4172,6 +4511,9 @@ function toKeyInfo(row) {
|
|
|
4172
4511
|
id: row.id,
|
|
4173
4512
|
name: row.name,
|
|
4174
4513
|
keyPrefix: row.keyPrefix,
|
|
4514
|
+
// True only when a reversible `keySecret` envelope was persisted at creation
|
|
4515
|
+
// — gates the UI "view key" eye. Legacy hash-only rows read as absent.
|
|
4516
|
+
revealable: Boolean(row.keySecret),
|
|
4175
4517
|
enabled: row.enabled,
|
|
4176
4518
|
createdAt: row.createdAt,
|
|
4177
4519
|
lastUsedAt: row.lastUsedAt,
|
|
@@ -4827,7 +5169,13 @@ function handlePresets(res, method) {
|
|
|
4827
5169
|
name: p.name,
|
|
4828
5170
|
apiFormat: p.apiFormat,
|
|
4829
5171
|
baseUrl: p.baseUrl,
|
|
4830
|
-
models: p.models
|
|
5172
|
+
models: p.models,
|
|
5173
|
+
nameKey: p.nameKey,
|
|
5174
|
+
icon: p.icon,
|
|
5175
|
+
description: p.description,
|
|
5176
|
+
features: p.features,
|
|
5177
|
+
website: p.website,
|
|
5178
|
+
modelsEndpoint: p.modelsEndpoint
|
|
4831
5179
|
}));
|
|
4832
5180
|
return writeJson3(res, 200, { presets, excluded });
|
|
4833
5181
|
}
|
|
@@ -4861,12 +5209,27 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
4861
5209
|
plaintextOnce: created.plaintextOnce
|
|
4862
5210
|
});
|
|
4863
5211
|
}
|
|
5212
|
+
if (method === "GET" && rest.length === 2 && rest[1] === "reveal") {
|
|
5213
|
+
const revealed = await deps.keyDb.outboundApiKeysReveal(rest[0]);
|
|
5214
|
+
if (revealed !== null) return writeJson3(res, 200, { key: revealed });
|
|
5215
|
+
const exists = (await deps.keyDb.outboundApiKeysList()).some((r) => r.id === rest[0]);
|
|
5216
|
+
if (!exists) return writeJsonError(res, 404, `key '${rest[0]}' not found`);
|
|
5217
|
+
return writeJsonError(
|
|
5218
|
+
res,
|
|
5219
|
+
409,
|
|
5220
|
+
`key '${rest[0]}' is not revealable (created before revealable key storage)`
|
|
5221
|
+
);
|
|
5222
|
+
}
|
|
4864
5223
|
const id = rest[0];
|
|
4865
5224
|
const action = rest[1];
|
|
4866
5225
|
if (method === "POST" && id && action === "revoke") {
|
|
4867
5226
|
const ok = await deps.keyDb.outboundApiKeysRevoke(id);
|
|
4868
5227
|
return writeJson3(res, ok ? 200 : 404, { ok });
|
|
4869
5228
|
}
|
|
5229
|
+
if (method === "DELETE" && id && !action) {
|
|
5230
|
+
const ok = await deps.keyDb.outboundApiKeysDelete(id);
|
|
5231
|
+
return writeJson3(res, ok ? 200 : 404, { ok });
|
|
5232
|
+
}
|
|
4870
5233
|
if (method === "POST" && id && action === "enabled") {
|
|
4871
5234
|
const body = await readJsonBody3(req);
|
|
4872
5235
|
const enabled = body["enabled"] === true;
|
|
@@ -5043,6 +5406,40 @@ async function handleServer(req, res, method, deps) {
|
|
|
5043
5406
|
return writeJsonError(res, 405, `method ${method} not allowed on server`);
|
|
5044
5407
|
}
|
|
5045
5408
|
async function handleAccounts(req, res, method, rest, deps) {
|
|
5409
|
+
if (rest[0] === "route-activity" && rest.length === 1) {
|
|
5410
|
+
if (method !== "GET") {
|
|
5411
|
+
return writeJsonError(res, 405, `method ${method} not allowed on account route activity`);
|
|
5412
|
+
}
|
|
5413
|
+
const query2 = requestQuery(req);
|
|
5414
|
+
const parsedLimit = Number(query2.get("limit") ?? "100");
|
|
5415
|
+
const records = getSharedAccountRouteActivity().list({
|
|
5416
|
+
providerId: query2.get("providerId") ?? void 0,
|
|
5417
|
+
accountId: query2.get("accountId") ?? void 0,
|
|
5418
|
+
sessionKey: query2.get("sessionKey") ?? void 0,
|
|
5419
|
+
limit: Number.isFinite(parsedLimit) ? parsedLimit : 100
|
|
5420
|
+
});
|
|
5421
|
+
return writeJson3(res, 200, {
|
|
5422
|
+
available: true,
|
|
5423
|
+
records,
|
|
5424
|
+
capacity: ACCOUNT_ROUTE_ACTIVITY_LIMIT,
|
|
5425
|
+
collectedAt: Date.now()
|
|
5426
|
+
});
|
|
5427
|
+
}
|
|
5428
|
+
if (rest[0] === "overload-counters" && rest.length === 1) {
|
|
5429
|
+
if (method !== "GET") {
|
|
5430
|
+
return writeJsonError(res, 405, `method ${method} not allowed on overload counters`);
|
|
5431
|
+
}
|
|
5432
|
+
const query2 = requestQuery(req);
|
|
5433
|
+
const entries = getSharedOverloadCounter().list({
|
|
5434
|
+
providerId: query2.get("providerId") ?? void 0,
|
|
5435
|
+
accountId: query2.get("accountId") ?? void 0
|
|
5436
|
+
});
|
|
5437
|
+
return writeJson3(res, 200, {
|
|
5438
|
+
available: true,
|
|
5439
|
+
entries,
|
|
5440
|
+
collectedAt: Date.now()
|
|
5441
|
+
});
|
|
5442
|
+
}
|
|
5046
5443
|
if (rest[0] === "allowances") {
|
|
5047
5444
|
return handleAccountAllowanceApi(
|
|
5048
5445
|
req,
|
|
@@ -5189,8 +5586,13 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
5189
5586
|
if (!(listed[providerId] ?? []).some((account) => account.id === accountId)) {
|
|
5190
5587
|
return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
5191
5588
|
}
|
|
5192
|
-
const result = await deps.accountProbeService.
|
|
5193
|
-
return writeJson3(res, 200, {
|
|
5589
|
+
const result = await deps.accountProbeService.testAccountConnection(providerId, accountId);
|
|
5590
|
+
return writeJson3(res, 200, {
|
|
5591
|
+
ok: result.ok,
|
|
5592
|
+
marked: result.marked,
|
|
5593
|
+
tier: result.tier,
|
|
5594
|
+
model: result.model
|
|
5595
|
+
});
|
|
5194
5596
|
}
|
|
5195
5597
|
if (method === "POST" && rest[2] === "label") {
|
|
5196
5598
|
const accountId = rest[1];
|
|
@@ -5299,6 +5701,7 @@ async function handleCli(req, res, method, rest, deps) {
|
|
|
5299
5701
|
const result = await handleCliLaunch(cli, body, {
|
|
5300
5702
|
llmConfig: deps.llmConfig,
|
|
5301
5703
|
providers,
|
|
5704
|
+
routeLeaseManager: deps.routeLeaseManager,
|
|
5302
5705
|
opener: deps.cliTerminalOpener,
|
|
5303
5706
|
probe: deps.cliPathProbe
|
|
5304
5707
|
});
|
|
@@ -5561,7 +5964,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
5561
5964
|
}
|
|
5562
5965
|
|
|
5563
5966
|
// src/admin/version.ts
|
|
5564
|
-
var DAEMON_VERSION = true ? "0.1.
|
|
5967
|
+
var DAEMON_VERSION = true ? "0.1.8" : "0.0.0-dev";
|
|
5565
5968
|
|
|
5566
5969
|
// src/admin/AdminServer.ts
|
|
5567
5970
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -5669,6 +6072,10 @@ var AdminServer = class {
|
|
|
5669
6072
|
handleAuditQuery(req, res, this.deps.auditReader);
|
|
5670
6073
|
return;
|
|
5671
6074
|
}
|
|
6075
|
+
if (path2 === "/admin/api/audit/stats" && (req.method === "GET" || req.method === "HEAD")) {
|
|
6076
|
+
await handleAuditStatsQuery(req, res, this.deps.auditStatsReader);
|
|
6077
|
+
return;
|
|
6078
|
+
}
|
|
5672
6079
|
if (path2 === "/admin/api/billing-status" && (req.method === "GET" || req.method === "HEAD")) {
|
|
5673
6080
|
handleBillingStatus(res, this.deps.billingStatusReader);
|
|
5674
6081
|
return;
|
|
@@ -5677,6 +6084,10 @@ var AdminServer = class {
|
|
|
5677
6084
|
await handleWebhookTest(req, res);
|
|
5678
6085
|
return;
|
|
5679
6086
|
}
|
|
6087
|
+
if (path2 === "/admin/api/route-leases" || path2.startsWith("/admin/api/route-leases/")) {
|
|
6088
|
+
await handleRouteLeaseApi(req, res, path2, this.deps);
|
|
6089
|
+
return;
|
|
6090
|
+
}
|
|
5680
6091
|
if (path2.startsWith("/admin/api/")) {
|
|
5681
6092
|
await handleAdminApi(req, res, path2, this.deps);
|
|
5682
6093
|
return;
|
|
@@ -5688,8 +6099,8 @@ var AdminServer = class {
|
|
|
5688
6099
|
}
|
|
5689
6100
|
/** Constant-time bearer/header check against the configured token. */
|
|
5690
6101
|
isAuthorized(req, token) {
|
|
5691
|
-
const
|
|
5692
|
-
const bearer = typeof
|
|
6102
|
+
const header2 = req.headers["authorization"];
|
|
6103
|
+
const bearer = typeof header2 === "string" && header2.startsWith("Bearer ") ? header2.slice("Bearer ".length).trim() : void 0;
|
|
5693
6104
|
const xToken = req.headers["x-admin-token"];
|
|
5694
6105
|
const presented = bearer ?? (typeof xToken === "string" ? xToken.trim() : void 0);
|
|
5695
6106
|
return constantTimeEquals(presented, token);
|
|
@@ -5785,20 +6196,36 @@ var OAuthSessionStore = class {
|
|
|
5785
6196
|
return sessionId;
|
|
5786
6197
|
}
|
|
5787
6198
|
/**
|
|
5788
|
-
*
|
|
5789
|
-
*
|
|
5790
|
-
* dropped). A `null` return means the completer must reject (no
|
|
5791
|
-
* write).
|
|
6199
|
+
* NON-DESTRUCTIVE lookup: return the session for `sessionId`, or `null` when
|
|
6200
|
+
* it is unknown, already consumed, or past its TTL (an expired entry is
|
|
6201
|
+
* dropped here). A `null` return means the completer must reject (no
|
|
6202
|
+
* exchange, no write).
|
|
6203
|
+
*
|
|
6204
|
+
* Deliberately NOT a consume: the completer peeks, runs the token exchange,
|
|
6205
|
+
* and only {@link consume}s once a token has actually been minted. Consuming
|
|
6206
|
+
* up-front burned the session on EVERY failed exchange (a mistyped/expired
|
|
6207
|
+
* pasted code, a proxy hiccup), so the user's natural retry hit
|
|
6208
|
+
* "session is unknown, expired, or already used" and the login became
|
|
6209
|
+
* unrecoverable without restarting the whole flow.
|
|
5792
6210
|
*/
|
|
5793
|
-
|
|
6211
|
+
peek(sessionId) {
|
|
5794
6212
|
this.sweep();
|
|
5795
6213
|
const session = this.sessions.get(sessionId);
|
|
5796
6214
|
if (!session) return null;
|
|
5797
|
-
|
|
5798
|
-
|
|
6215
|
+
if (Date.now() - session.createdAt > this.ttlMs) {
|
|
6216
|
+
this.sessions.delete(sessionId);
|
|
6217
|
+
return null;
|
|
6218
|
+
}
|
|
5799
6219
|
return session;
|
|
5800
6220
|
}
|
|
5801
|
-
/**
|
|
6221
|
+
/**
|
|
6222
|
+
* SINGLE-USE burn: drop the session so the same `sessionId` can never be
|
|
6223
|
+
* completed twice. Called ONLY after a successful token exchange.
|
|
6224
|
+
*/
|
|
6225
|
+
consume(sessionId) {
|
|
6226
|
+
this.sessions.delete(sessionId);
|
|
6227
|
+
}
|
|
6228
|
+
/** Drop every session past its TTL. Called on each put/peek. */
|
|
5802
6229
|
sweep() {
|
|
5803
6230
|
const now = Date.now();
|
|
5804
6231
|
for (const [id, session] of this.sessions) {
|
|
@@ -5808,11 +6235,15 @@ var OAuthSessionStore = class {
|
|
|
5808
6235
|
};
|
|
5809
6236
|
|
|
5810
6237
|
// src/commands/loopbackCallback.ts
|
|
5811
|
-
import { createServer } from "http";
|
|
6238
|
+
import { createServer as createServer2 } from "http";
|
|
5812
6239
|
var LOOPBACK_HOST = "127.0.0.1";
|
|
5813
6240
|
var LOOPBACK_PORT = 1455;
|
|
5814
6241
|
var CALLBACK_PATH = "/auth/callback";
|
|
5815
6242
|
var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
6243
|
+
var HTML_HEADERS = {
|
|
6244
|
+
"Content-Type": "text/html",
|
|
6245
|
+
Connection: "close"
|
|
6246
|
+
};
|
|
5816
6247
|
function pageHtml(message) {
|
|
5817
6248
|
return `<!doctype html><meta charset="utf-8"><title>omnicross login</title><body style="font-family:sans-serif;padding:2rem"><h2>${message}</h2><p>You can close this window and return to the terminal.</p></body>`;
|
|
5818
6249
|
}
|
|
@@ -5823,30 +6254,31 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
|
|
|
5823
6254
|
if (settled) return;
|
|
5824
6255
|
settled = true;
|
|
5825
6256
|
clearTimeout(timer);
|
|
5826
|
-
|
|
6257
|
+
fn();
|
|
6258
|
+
server2.close();
|
|
5827
6259
|
};
|
|
5828
|
-
const server =
|
|
6260
|
+
const server = createServer2((req, res) => {
|
|
5829
6261
|
const url = new URL(req.url ?? "", `http://${LOOPBACK_HOST}:${LOOPBACK_PORT}`);
|
|
5830
6262
|
if (url.pathname !== CALLBACK_PATH) {
|
|
5831
|
-
res.writeHead(404,
|
|
6263
|
+
res.writeHead(404, HTML_HEADERS);
|
|
5832
6264
|
res.end(pageHtml("Not found"));
|
|
5833
6265
|
return;
|
|
5834
6266
|
}
|
|
5835
6267
|
const code = url.searchParams.get("code");
|
|
5836
6268
|
const state = url.searchParams.get("state");
|
|
5837
6269
|
if (!code) {
|
|
5838
|
-
res.writeHead(400,
|
|
6270
|
+
res.writeHead(400, HTML_HEADERS);
|
|
5839
6271
|
res.end(pageHtml("Login failed: missing authorization code."));
|
|
5840
6272
|
finish(server, () => reject(new Error("login: callback did not include an authorization code")));
|
|
5841
6273
|
return;
|
|
5842
6274
|
}
|
|
5843
6275
|
if (state !== expectedState) {
|
|
5844
|
-
res.writeHead(400,
|
|
6276
|
+
res.writeHead(400, HTML_HEADERS);
|
|
5845
6277
|
res.end(pageHtml("Login failed: state mismatch."));
|
|
5846
6278
|
finish(server, () => reject(new Error("login: callback state did not match (possible CSRF) \u2014 aborting")));
|
|
5847
6279
|
return;
|
|
5848
6280
|
}
|
|
5849
|
-
res.writeHead(200,
|
|
6281
|
+
res.writeHead(200, HTML_HEADERS);
|
|
5850
6282
|
res.end(pageHtml("Login complete."));
|
|
5851
6283
|
finish(server, () => resolve2(code));
|
|
5852
6284
|
});
|
|
@@ -5943,30 +6375,30 @@ function createPoolKeysLoader(getProviderRow, autoDisabled) {
|
|
|
5943
6375
|
}
|
|
5944
6376
|
|
|
5945
6377
|
// src/commands/paths.ts
|
|
5946
|
-
import { dirname as dirname5, join as
|
|
6378
|
+
import { dirname as dirname5, join as join4 } from "path";
|
|
5947
6379
|
function defaultVouchersPath(configPath) {
|
|
5948
|
-
return
|
|
6380
|
+
return join4(dirname5(configPath), "vouchers.json");
|
|
5949
6381
|
}
|
|
5950
6382
|
function defaultIntegrationsPath(configPath) {
|
|
5951
|
-
return
|
|
6383
|
+
return join4(dirname5(configPath), "integrations.json");
|
|
5952
6384
|
}
|
|
5953
6385
|
function defaultPricingPath(configPath) {
|
|
5954
|
-
return
|
|
6386
|
+
return join4(dirname5(configPath), "pricing.json");
|
|
5955
6387
|
}
|
|
5956
6388
|
function defaultPricingRefreshStatePath(configPath) {
|
|
5957
|
-
return
|
|
6389
|
+
return join4(dirname5(configPath), "pricing-refresh.json");
|
|
5958
6390
|
}
|
|
5959
6391
|
function defaultAccountAllowancePath(configPath) {
|
|
5960
|
-
return
|
|
6392
|
+
return join4(dirname5(configPath), "allowance-cache.json");
|
|
5961
6393
|
}
|
|
5962
6394
|
function defaultUsageEventsPath(configPath) {
|
|
5963
|
-
return
|
|
6395
|
+
return join4(dirname5(configPath), "usage-events.jsonl");
|
|
5964
6396
|
}
|
|
5965
6397
|
function defaultAuditDir(configPath) {
|
|
5966
|
-
return
|
|
6398
|
+
return join4(dirname5(configPath), "audit");
|
|
5967
6399
|
}
|
|
5968
6400
|
function defaultBillingDir(configPath) {
|
|
5969
|
-
return
|
|
6401
|
+
return join4(dirname5(configPath), "billing");
|
|
5970
6402
|
}
|
|
5971
6403
|
|
|
5972
6404
|
// src/ports/ConfigFileProviderConfigSource.ts
|
|
@@ -6274,7 +6706,7 @@ function safeStringify(value) {
|
|
|
6274
6706
|
}
|
|
6275
6707
|
|
|
6276
6708
|
// src/ports/JsonApiServerSettingsStore.ts
|
|
6277
|
-
import { readFileSync as readFileSync6, writeFileSync as
|
|
6709
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
6278
6710
|
import { OUTBOUND_API_SERVER_CONFIG_KEY } from "@omnicross/core/outbound-api";
|
|
6279
6711
|
var JsonApiServerSettingsStore = class {
|
|
6280
6712
|
/**
|
|
@@ -6301,7 +6733,7 @@ var JsonApiServerSettingsStore = class {
|
|
|
6301
6733
|
if (key !== OUTBOUND_API_SERVER_CONFIG_KEY) return;
|
|
6302
6734
|
const file = this.readFile();
|
|
6303
6735
|
file.server = this.encryptSecrets(value);
|
|
6304
|
-
|
|
6736
|
+
writeFileSync6(this.configPath, JSON.stringify(file, null, 2) + "\n", "utf8");
|
|
6305
6737
|
}
|
|
6306
6738
|
/** Encrypt the proxy passwords + webhook + billing secrets before persisting (no-op without a box). */
|
|
6307
6739
|
encryptSecrets(config) {
|
|
@@ -6362,8 +6794,12 @@ var JsonlUsageEventStore = class {
|
|
|
6362
6794
|
reasoningTokens: 0,
|
|
6363
6795
|
costUsd: 0,
|
|
6364
6796
|
costSavedByCacheUsd: 0,
|
|
6365
|
-
eventCount: 0
|
|
6797
|
+
eventCount: 0,
|
|
6798
|
+
cacheEligibleEventCount: 0,
|
|
6799
|
+
coldCacheEventCount: 0,
|
|
6800
|
+
medianCacheHitRate: null
|
|
6366
6801
|
};
|
|
6802
|
+
const perEventHitRates = [];
|
|
6367
6803
|
for (const row of this.readRows(range)) {
|
|
6368
6804
|
totals.inputTokens += row.inputTokens;
|
|
6369
6805
|
totals.outputTokens += row.outputTokens;
|
|
@@ -6373,7 +6809,14 @@ var JsonlUsageEventStore = class {
|
|
|
6373
6809
|
totals.costUsd += row.costUsd;
|
|
6374
6810
|
totals.costSavedByCacheUsd += row.costSavedByCacheUsd;
|
|
6375
6811
|
totals.eventCount += 1;
|
|
6812
|
+
const promptSideTokens = row.inputTokens + row.cacheReadTokens + row.cacheCreationTokens;
|
|
6813
|
+
if (promptSideTokens > 0) {
|
|
6814
|
+
totals.cacheEligibleEventCount += 1;
|
|
6815
|
+
if (row.cacheReadTokens === 0) totals.coldCacheEventCount += 1;
|
|
6816
|
+
perEventHitRates.push(row.cacheReadTokens / promptSideTokens);
|
|
6817
|
+
}
|
|
6376
6818
|
}
|
|
6819
|
+
totals.medianCacheHitRate = median(perEventHitRates);
|
|
6377
6820
|
return totals;
|
|
6378
6821
|
}
|
|
6379
6822
|
async getByModel(range) {
|
|
@@ -6607,6 +7050,15 @@ var NUMERIC_FIELDS = [
|
|
|
6607
7050
|
];
|
|
6608
7051
|
var NULLABLE_STRING_FIELDS = ["messageId", "parentMessageId", "sessionId", "apiKeyId"];
|
|
6609
7052
|
var isStringOrNull = (v) => v === null || typeof v === "string";
|
|
7053
|
+
var CACHE_KEY_SOURCES = /* @__PURE__ */ new Set([
|
|
7054
|
+
"client",
|
|
7055
|
+
"session-header",
|
|
7056
|
+
"thread-header",
|
|
7057
|
+
"body-session-id",
|
|
7058
|
+
"body-thread-id",
|
|
7059
|
+
"content-fingerprint",
|
|
7060
|
+
"none"
|
|
7061
|
+
]);
|
|
6610
7062
|
function isUsageEventRecord(parsed) {
|
|
6611
7063
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
6612
7064
|
const r = parsed;
|
|
@@ -6614,6 +7066,10 @@ function isUsageEventRecord(parsed) {
|
|
|
6614
7066
|
if (typeof r["providerId"] !== "string") return false;
|
|
6615
7067
|
if (typeof r["model"] !== "string") return false;
|
|
6616
7068
|
if (typeof r["engineOrigin"] !== "string") return false;
|
|
7069
|
+
if (r["cacheKeySource"] !== void 0 && (typeof r["cacheKeySource"] !== "string" || !CACHE_KEY_SOURCES.has(r["cacheKeySource"]))) return false;
|
|
7070
|
+
if (r["cacheKeyInjected"] !== void 0 && typeof r["cacheKeyInjected"] !== "boolean") {
|
|
7071
|
+
return false;
|
|
7072
|
+
}
|
|
6617
7073
|
for (const f of NULLABLE_STRING_FIELDS) {
|
|
6618
7074
|
if (!isStringOrNull(r[f])) return false;
|
|
6619
7075
|
}
|
|
@@ -6623,14 +7079,30 @@ function isUsageEventRecord(parsed) {
|
|
|
6623
7079
|
}
|
|
6624
7080
|
return true;
|
|
6625
7081
|
}
|
|
7082
|
+
function median(values) {
|
|
7083
|
+
if (values.length === 0) return null;
|
|
7084
|
+
values.sort((a, b) => a - b);
|
|
7085
|
+
const middle = Math.floor(values.length / 2);
|
|
7086
|
+
return values.length % 2 === 1 ? values[middle] : (values[middle - 1] + values[middle]) / 2;
|
|
7087
|
+
}
|
|
6626
7088
|
|
|
6627
7089
|
// src/ports/JsonOutboundKeyDb.ts
|
|
6628
|
-
import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as
|
|
7090
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
6629
7091
|
var JsonOutboundKeyDb = class {
|
|
6630
|
-
|
|
7092
|
+
/**
|
|
7093
|
+
* @param secretBox OPTIONAL reversible-secret codec. When present, a created
|
|
7094
|
+
* key's plaintext is persisted as a `keySecret` `enc:` envelope (enabling the
|
|
7095
|
+
* operator "view key" affordance via `outboundApiKeysReveal`). When absent the
|
|
7096
|
+
* store stays hash-only (byte-identical to the legacy behavior) and reveal
|
|
7097
|
+
* always returns `null`. Existing 1-arg call sites (tests, lightweight
|
|
7098
|
+
* embedders) keep working.
|
|
7099
|
+
*/
|
|
7100
|
+
constructor(keysPath, secretBox3) {
|
|
6631
7101
|
this.keysPath = keysPath;
|
|
7102
|
+
this.secretBox = secretBox3;
|
|
6632
7103
|
}
|
|
6633
7104
|
keysPath;
|
|
7105
|
+
secretBox;
|
|
6634
7106
|
async outboundApiKeysList() {
|
|
6635
7107
|
return this.readRows();
|
|
6636
7108
|
}
|
|
@@ -6656,10 +7128,27 @@ var JsonOutboundKeyDb = class {
|
|
|
6656
7128
|
allowedEndpoints: input.allowedEndpoints,
|
|
6657
7129
|
loopbackOnly: input.loopbackOnly
|
|
6658
7130
|
};
|
|
7131
|
+
if (input.plaintext && this.secretBox) {
|
|
7132
|
+
row.keySecret = this.secretBox.encrypt(input.plaintext);
|
|
7133
|
+
}
|
|
6659
7134
|
rows.push(row);
|
|
6660
7135
|
this.writeRows(rows);
|
|
6661
7136
|
return row;
|
|
6662
7137
|
}
|
|
7138
|
+
async outboundApiKeysReveal(id) {
|
|
7139
|
+
const rows = this.readRows();
|
|
7140
|
+
const row = rows.find((r) => r.id === id);
|
|
7141
|
+
if (!row || !row.keySecret || !this.secretBox) return null;
|
|
7142
|
+
return this.secretBox.decrypt(row.keySecret);
|
|
7143
|
+
}
|
|
7144
|
+
async outboundApiKeysDelete(id) {
|
|
7145
|
+
const rows = this.readRows();
|
|
7146
|
+
const idx = rows.findIndex((r) => r.id === id);
|
|
7147
|
+
if (idx < 0) return false;
|
|
7148
|
+
rows.splice(idx, 1);
|
|
7149
|
+
this.writeRows(rows);
|
|
7150
|
+
return true;
|
|
7151
|
+
}
|
|
6663
7152
|
async outboundApiKeysRevoke(id) {
|
|
6664
7153
|
return this.mutateRow(id, (row) => {
|
|
6665
7154
|
if (row.revokedAt !== null) return false;
|
|
@@ -6740,7 +7229,7 @@ var JsonOutboundKeyDb = class {
|
|
|
6740
7229
|
}
|
|
6741
7230
|
}
|
|
6742
7231
|
writeRows(rows) {
|
|
6743
|
-
|
|
7232
|
+
writeFileSync7(this.keysPath, JSON.stringify(rows, null, 2) + "\n", "utf8");
|
|
6744
7233
|
}
|
|
6745
7234
|
};
|
|
6746
7235
|
function applyPolicyField(row, field, value) {
|
|
@@ -6750,7 +7239,7 @@ function applyPolicyField(row, field, value) {
|
|
|
6750
7239
|
}
|
|
6751
7240
|
|
|
6752
7241
|
// src/ports/JsonPricingStore.ts
|
|
6753
|
-
import { existsSync as existsSync9, readFileSync as readFileSync9, renameSync as renameSync3, rmSync as
|
|
7242
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
|
|
6754
7243
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
6755
7244
|
var JsonPricingStore = class {
|
|
6756
7245
|
constructor(pricingPath) {
|
|
@@ -6891,13 +7380,13 @@ var JsonPricingStore = class {
|
|
|
6891
7380
|
writeRows(rows) {
|
|
6892
7381
|
const temporaryPath = `${this.pricingPath}.${process.pid}.${randomUUID5()}.tmp`;
|
|
6893
7382
|
try {
|
|
6894
|
-
|
|
7383
|
+
writeFileSync8(temporaryPath, JSON.stringify(rows, null, 2) + "\n", {
|
|
6895
7384
|
encoding: "utf8",
|
|
6896
7385
|
flag: "wx"
|
|
6897
7386
|
});
|
|
6898
7387
|
this.replaceFile(temporaryPath);
|
|
6899
7388
|
} finally {
|
|
6900
|
-
|
|
7389
|
+
rmSync3(temporaryPath, { force: true });
|
|
6901
7390
|
}
|
|
6902
7391
|
}
|
|
6903
7392
|
/** Isolated for deterministic failure testing; never removes the target. */
|
|
@@ -6912,7 +7401,7 @@ function isUsablePricingRow(value) {
|
|
|
6912
7401
|
}
|
|
6913
7402
|
|
|
6914
7403
|
// src/pricing/PricingRefreshScheduler.ts
|
|
6915
|
-
import { existsSync as existsSync10, readFileSync as readFileSync10, renameSync as renameSync4, writeFileSync as
|
|
7404
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10, renameSync as renameSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
6916
7405
|
var EMPTY_STATE2 = {
|
|
6917
7406
|
lastAttemptAt: null,
|
|
6918
7407
|
lastSuccessAt: null,
|
|
@@ -7005,7 +7494,7 @@ var PricingRefreshScheduler = class {
|
|
|
7005
7494
|
}
|
|
7006
7495
|
writeState(state) {
|
|
7007
7496
|
const temporaryPath = `${this.statePath}.tmp`;
|
|
7008
|
-
|
|
7497
|
+
writeFileSync9(temporaryPath, `${JSON.stringify(state, null, 2)}
|
|
7009
7498
|
`, "utf8");
|
|
7010
7499
|
renameSync4(temporaryPath, this.statePath);
|
|
7011
7500
|
}
|
|
@@ -7015,7 +7504,7 @@ function finiteOrNull(value) {
|
|
|
7015
7504
|
}
|
|
7016
7505
|
|
|
7017
7506
|
// src/ports/JsonVoucherDb.ts
|
|
7018
|
-
import { existsSync as existsSync11, readFileSync as readFileSync11, writeFileSync as
|
|
7507
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync10 } from "fs";
|
|
7019
7508
|
var JsonVoucherDb = class {
|
|
7020
7509
|
constructor(vouchersPath) {
|
|
7021
7510
|
this.vouchersPath = vouchersPath;
|
|
@@ -7102,12 +7591,12 @@ var JsonVoucherDb = class {
|
|
|
7102
7591
|
}
|
|
7103
7592
|
}
|
|
7104
7593
|
writeRows(rows) {
|
|
7105
|
-
|
|
7594
|
+
writeFileSync10(this.vouchersPath, JSON.stringify(rows, null, 2) + "\n", "utf8");
|
|
7106
7595
|
}
|
|
7107
7596
|
};
|
|
7108
7597
|
|
|
7109
7598
|
// src/ports/JsonSubscriptionCredentialStore.ts
|
|
7110
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync13, writeFileSync as
|
|
7599
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
7111
7600
|
import { dirname as dirname6 } from "path";
|
|
7112
7601
|
import { getSharedAccountHealth as getSharedAccountHealth2 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
7113
7602
|
import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling3 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
@@ -7163,9 +7652,9 @@ function findDuplicateCredentialIds(accounts) {
|
|
|
7163
7652
|
// src/ports/external-cli-credentials.ts
|
|
7164
7653
|
import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
|
|
7165
7654
|
import { homedir as homedir3 } from "os";
|
|
7166
|
-
import { join as
|
|
7655
|
+
import { join as join5 } from "path";
|
|
7167
7656
|
function externalStorePath(provider, home = homedir3()) {
|
|
7168
|
-
return provider === "claude" ?
|
|
7657
|
+
return provider === "claude" ? join5(home, ".claude", ".credentials.json") : join5(home, ".codex", "auth.json");
|
|
7169
7658
|
}
|
|
7170
7659
|
function decodeJwtExpiryMs(token) {
|
|
7171
7660
|
try {
|
|
@@ -7255,9 +7744,15 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
7255
7744
|
* TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
|
|
7256
7745
|
* through {@link fetchUpstream} with the account's `{ providerId, accountId }`
|
|
7257
7746
|
* ctx so the per-account/provider proxy applies. `@internal` also a test seam.
|
|
7747
|
+
*
|
|
7748
|
+
* `redactBodies` is REQUIRED here: this round-trip sends the refresh_token and
|
|
7749
|
+
* receives a fresh access/refresh token pair. Carrying a `providerId` opts the
|
|
7750
|
+
* call into the upstream trace (so a failing refresh is diagnosable), and the
|
|
7751
|
+
* trace captures bodies verbatim — without this flag every refresh would write
|
|
7752
|
+
* a plaintext token pair into `upstream-trace.jsonl`.
|
|
7258
7753
|
*/
|
|
7259
7754
|
buildRefreshFetch(providerId, accountId) {
|
|
7260
|
-
return this.fetchImpl ?? ((url, init) => fetchUpstream3(url, init, { providerId, accountId }));
|
|
7755
|
+
return this.fetchImpl ?? ((url, init) => fetchUpstream3(url, init, { providerId, accountId, redactBodies: true }));
|
|
7261
7756
|
}
|
|
7262
7757
|
/**
|
|
7263
7758
|
* In-flight refresh coalescing. OAuth refresh tokens are
|
|
@@ -7784,7 +8279,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
7784
8279
|
persist(config) {
|
|
7785
8280
|
mkdirSync4(dirname6(this.tokensPath), { recursive: true });
|
|
7786
8281
|
const encrypted = encryptTokens(config, this.box);
|
|
7787
|
-
|
|
8282
|
+
writeFileSync11(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
|
|
7788
8283
|
}
|
|
7789
8284
|
/**
|
|
7790
8285
|
* Read + parse `tokens.json`, tolerating a missing/corrupt file, then DECRYPT
|
|
@@ -7817,6 +8312,127 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
7817
8312
|
// src/AccountHealthProbeScheduler.ts
|
|
7818
8313
|
import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
7819
8314
|
|
|
8315
|
+
// src/probe/CodexGenerationProbe.ts
|
|
8316
|
+
import {
|
|
8317
|
+
DEFAULT_CODEX_CLI_HEADERS,
|
|
8318
|
+
codexAcceptHeader
|
|
8319
|
+
} from "@omnicross/core/provider-proxy/identity/codexCliHeaders";
|
|
8320
|
+
var CODEX_GENERATION_PROBE_MODEL = "gpt-5.6-luna";
|
|
8321
|
+
var CODEX_GENERATION_PROBE_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
8322
|
+
var MAX_STREAM_BYTES = 256 * 1024;
|
|
8323
|
+
var PROBE_INSTRUCTION = "Return exactly PONG and no other text.";
|
|
8324
|
+
function buildCodexGenerationProbeInit(token, signal) {
|
|
8325
|
+
return {
|
|
8326
|
+
method: "POST",
|
|
8327
|
+
signal,
|
|
8328
|
+
headers: {
|
|
8329
|
+
...DEFAULT_CODEX_CLI_HEADERS,
|
|
8330
|
+
Authorization: `Bearer ${token}`,
|
|
8331
|
+
Accept: codexAcceptHeader(true),
|
|
8332
|
+
"Content-Type": "application/json"
|
|
8333
|
+
},
|
|
8334
|
+
body: JSON.stringify({
|
|
8335
|
+
model: CODEX_GENERATION_PROBE_MODEL,
|
|
8336
|
+
input: [
|
|
8337
|
+
{
|
|
8338
|
+
role: "developer",
|
|
8339
|
+
content: [{ type: "input_text", text: PROBE_INSTRUCTION }]
|
|
8340
|
+
},
|
|
8341
|
+
{
|
|
8342
|
+
role: "user",
|
|
8343
|
+
content: [{ type: "input_text", text: "Connection probe." }]
|
|
8344
|
+
}
|
|
8345
|
+
],
|
|
8346
|
+
// GPT-5.6 otherwise defaults to medium reasoning. A connectivity probe
|
|
8347
|
+
// needs the lowest-cost path and no tool reasoning.
|
|
8348
|
+
reasoning: { effort: "none" },
|
|
8349
|
+
stream: true,
|
|
8350
|
+
store: false
|
|
8351
|
+
})
|
|
8352
|
+
};
|
|
8353
|
+
}
|
|
8354
|
+
async function readCodexGenerationProbeStream(response) {
|
|
8355
|
+
if (!response.body) return { completed: false, outputChars: 0 };
|
|
8356
|
+
const reader = response.body.getReader();
|
|
8357
|
+
const decoder = new TextDecoder();
|
|
8358
|
+
let buffer = "";
|
|
8359
|
+
let bytes = 0;
|
|
8360
|
+
let outputChars = 0;
|
|
8361
|
+
try {
|
|
8362
|
+
while (true) {
|
|
8363
|
+
const { done, value } = await reader.read();
|
|
8364
|
+
if (done) break;
|
|
8365
|
+
bytes += value.byteLength;
|
|
8366
|
+
if (bytes > MAX_STREAM_BYTES) {
|
|
8367
|
+
await reader.cancel();
|
|
8368
|
+
return { completed: false, outputChars };
|
|
8369
|
+
}
|
|
8370
|
+
buffer += decoder.decode(value, { stream: true });
|
|
8371
|
+
buffer = buffer.replace(/\r\n/g, "\n");
|
|
8372
|
+
let boundary = buffer.indexOf("\n\n");
|
|
8373
|
+
while (boundary >= 0) {
|
|
8374
|
+
const block = buffer.slice(0, boundary);
|
|
8375
|
+
buffer = buffer.slice(boundary + 2);
|
|
8376
|
+
const event = parseSseBlock(block);
|
|
8377
|
+
if (event) {
|
|
8378
|
+
const type = event["type"];
|
|
8379
|
+
if (type === "response.output_text.delta" && typeof event["delta"] === "string") {
|
|
8380
|
+
outputChars += event["delta"].length;
|
|
8381
|
+
} else if (type === "response.output_text.done" && typeof event["text"] === "string") {
|
|
8382
|
+
outputChars = Math.max(outputChars, event["text"].length);
|
|
8383
|
+
} else if (type === "response.failed" || type === "error") {
|
|
8384
|
+
await reader.cancel();
|
|
8385
|
+
return { completed: false, outputChars };
|
|
8386
|
+
} else if (type === "response.completed") {
|
|
8387
|
+
const completedResponse = asRecord(event["response"]);
|
|
8388
|
+
const status = completedResponse?.["status"];
|
|
8389
|
+
outputChars = Math.max(outputChars, countCompletedOutputChars(completedResponse));
|
|
8390
|
+
await reader.cancel();
|
|
8391
|
+
return {
|
|
8392
|
+
completed: (status === void 0 || status === "completed") && outputChars > 0,
|
|
8393
|
+
outputChars
|
|
8394
|
+
};
|
|
8395
|
+
}
|
|
8396
|
+
}
|
|
8397
|
+
boundary = buffer.indexOf("\n\n");
|
|
8398
|
+
}
|
|
8399
|
+
}
|
|
8400
|
+
} catch {
|
|
8401
|
+
return { completed: false, outputChars };
|
|
8402
|
+
} finally {
|
|
8403
|
+
reader.releaseLock();
|
|
8404
|
+
}
|
|
8405
|
+
return { completed: false, outputChars };
|
|
8406
|
+
}
|
|
8407
|
+
function parseSseBlock(block) {
|
|
8408
|
+
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
8409
|
+
if (!data || data === "[DONE]") return null;
|
|
8410
|
+
try {
|
|
8411
|
+
return JSON.parse(data);
|
|
8412
|
+
} catch {
|
|
8413
|
+
return null;
|
|
8414
|
+
}
|
|
8415
|
+
}
|
|
8416
|
+
function asRecord(value) {
|
|
8417
|
+
return value !== null && typeof value === "object" ? value : void 0;
|
|
8418
|
+
}
|
|
8419
|
+
function countCompletedOutputChars(response) {
|
|
8420
|
+
const output = response?.["output"];
|
|
8421
|
+
if (!Array.isArray(output)) return 0;
|
|
8422
|
+
let chars = 0;
|
|
8423
|
+
for (const item of output) {
|
|
8424
|
+
const content = asRecord(item)?.["content"];
|
|
8425
|
+
if (!Array.isArray(content)) continue;
|
|
8426
|
+
for (const part of content) {
|
|
8427
|
+
const record = asRecord(part);
|
|
8428
|
+
if (record?.["type"] === "output_text" && typeof record["text"] === "string") {
|
|
8429
|
+
chars += record["text"].length;
|
|
8430
|
+
}
|
|
8431
|
+
}
|
|
8432
|
+
}
|
|
8433
|
+
return chars;
|
|
8434
|
+
}
|
|
8435
|
+
|
|
7820
8436
|
// src/probe/ProbeStrategy.ts
|
|
7821
8437
|
var PROVIDER_PROBE_PLANS = {
|
|
7822
8438
|
claude: {
|
|
@@ -7944,17 +8560,17 @@ var AccountHealthProbeScheduler = class {
|
|
|
7944
8560
|
}
|
|
7945
8561
|
if (readThrew) {
|
|
7946
8562
|
this.record(providerId, accountId, { ts: now, ok: false, status: null, tier: "local" });
|
|
7947
|
-
return { ok: false, marked: false };
|
|
8563
|
+
return { ok: false, marked: false, tier: "local" };
|
|
7948
8564
|
}
|
|
7949
8565
|
if (!token) {
|
|
7950
8566
|
this.health.recordUpstreamOutcome(providerId, accountId, { status: 401, now });
|
|
7951
8567
|
this.record(providerId, accountId, { ts: now, ok: false, status: 401, tier: "local" });
|
|
7952
|
-
return { ok: false, marked: true };
|
|
8568
|
+
return { ok: false, marked: true, tier: "local" };
|
|
7953
8569
|
}
|
|
7954
8570
|
const plan = this.planFor(providerId);
|
|
7955
8571
|
if (plan.kind === "local") {
|
|
7956
8572
|
this.record(providerId, accountId, { ts: now, ok: true, tier: "local" });
|
|
7957
|
-
return { ok: true, marked: false };
|
|
8573
|
+
return { ok: true, marked: false, tier: "local" };
|
|
7958
8574
|
}
|
|
7959
8575
|
const start = this.now();
|
|
7960
8576
|
let status = null;
|
|
@@ -7979,7 +8595,60 @@ var AccountHealthProbeScheduler = class {
|
|
|
7979
8595
|
latencyMs,
|
|
7980
8596
|
tier: "upstream"
|
|
7981
8597
|
});
|
|
7982
|
-
return { ok: status !== null && status < 400, marked };
|
|
8598
|
+
return { ok: status !== null && status < 400, marked, tier: "upstream" };
|
|
8599
|
+
}
|
|
8600
|
+
/**
|
|
8601
|
+
* Manual connection test. Codex performs a real, quota-consuming generation;
|
|
8602
|
+
* every other provider keeps its existing cheap probe. Scheduled sweeps never
|
|
8603
|
+
* call this method, so they remain non-billable.
|
|
8604
|
+
*/
|
|
8605
|
+
async testAccountConnection(providerId, accountId) {
|
|
8606
|
+
if (providerId !== "codex") return this.probeAccount(providerId, accountId);
|
|
8607
|
+
const now = this.now();
|
|
8608
|
+
let token;
|
|
8609
|
+
try {
|
|
8610
|
+
token = await this.store.getAccessTokenForAccount(providerId, accountId);
|
|
8611
|
+
} catch {
|
|
8612
|
+
this.record(providerId, accountId, { ts: now, ok: false, status: null, tier: "local" });
|
|
8613
|
+
return { ok: false, marked: false, tier: "local", model: CODEX_GENERATION_PROBE_MODEL };
|
|
8614
|
+
}
|
|
8615
|
+
if (!token) {
|
|
8616
|
+
this.health.recordUpstreamOutcome(providerId, accountId, { status: 401, now });
|
|
8617
|
+
this.record(providerId, accountId, { ts: now, ok: false, status: 401, tier: "local" });
|
|
8618
|
+
return { ok: false, marked: true, tier: "local", model: CODEX_GENERATION_PROBE_MODEL };
|
|
8619
|
+
}
|
|
8620
|
+
const startedAt = this.now();
|
|
8621
|
+
let attempt = await this.runCodexGenerationAttempt(accountId, token);
|
|
8622
|
+
if (attempt.status === 401 && this.store.refreshAccountToken) {
|
|
8623
|
+
try {
|
|
8624
|
+
if (await this.store.refreshAccountToken(providerId, accountId)) {
|
|
8625
|
+
const refreshed = await this.store.getAccessTokenForAccount(providerId, accountId);
|
|
8626
|
+
if (refreshed) attempt = await this.runCodexGenerationAttempt(accountId, refreshed);
|
|
8627
|
+
}
|
|
8628
|
+
} catch {
|
|
8629
|
+
}
|
|
8630
|
+
}
|
|
8631
|
+
const latencyMs = this.now() - startedAt;
|
|
8632
|
+
const ok = attempt.status !== null && attempt.status >= 200 && attempt.status < 300 && attempt.completed;
|
|
8633
|
+
let marked = false;
|
|
8634
|
+
if (ok) {
|
|
8635
|
+
this.health.clearTransientMark(providerId, accountId);
|
|
8636
|
+
} else if (attempt.status === 401 || attempt.status === 403) {
|
|
8637
|
+
marked = this.applyOutcome(providerId, accountId, attempt.status, attempt.bodyText, now);
|
|
8638
|
+
}
|
|
8639
|
+
this.record(providerId, accountId, {
|
|
8640
|
+
ts: now,
|
|
8641
|
+
ok,
|
|
8642
|
+
status: attempt.status,
|
|
8643
|
+
latencyMs,
|
|
8644
|
+
tier: "generation"
|
|
8645
|
+
});
|
|
8646
|
+
return {
|
|
8647
|
+
ok,
|
|
8648
|
+
marked,
|
|
8649
|
+
tier: "generation",
|
|
8650
|
+
model: CODEX_GENERATION_PROBE_MODEL
|
|
8651
|
+
};
|
|
7983
8652
|
}
|
|
7984
8653
|
/** Per-account rolling history for the authed admin surface (design D5). */
|
|
7985
8654
|
getAllHistory() {
|
|
@@ -8036,6 +8705,24 @@ var AccountHealthProbeScheduler = class {
|
|
|
8036
8705
|
return "";
|
|
8037
8706
|
}
|
|
8038
8707
|
}
|
|
8708
|
+
async runCodexGenerationAttempt(accountId, token) {
|
|
8709
|
+
try {
|
|
8710
|
+
const timeoutMs = Math.max(this.config.timeoutMs, 15e3);
|
|
8711
|
+
const response = await this.fetchImpl(
|
|
8712
|
+
CODEX_GENERATION_PROBE_URL,
|
|
8713
|
+
buildCodexGenerationProbeInit(token, AbortSignal.timeout(timeoutMs)),
|
|
8714
|
+
{ providerId: "codex", accountId, redactBodies: true }
|
|
8715
|
+
);
|
|
8716
|
+
if (response.status < 200 || response.status >= 300) {
|
|
8717
|
+
const bodyText = response.status === 403 ? await this.readBounded(response) : void 0;
|
|
8718
|
+
return { status: response.status, completed: false, bodyText };
|
|
8719
|
+
}
|
|
8720
|
+
const stream = await readCodexGenerationProbeStream(response);
|
|
8721
|
+
return { status: response.status, completed: stream.completed };
|
|
8722
|
+
} catch {
|
|
8723
|
+
return { status: null, completed: false };
|
|
8724
|
+
}
|
|
8725
|
+
}
|
|
8039
8726
|
key(providerId, accountId) {
|
|
8040
8727
|
return `${providerId}${KEY_SEP}${accountId}`;
|
|
8041
8728
|
}
|
|
@@ -8131,7 +8818,7 @@ var AccountHealthSweeper = class {
|
|
|
8131
8818
|
};
|
|
8132
8819
|
|
|
8133
8820
|
// src/audit/AuditPruneSweeper.ts
|
|
8134
|
-
import { existsSync as
|
|
8821
|
+
import { existsSync as existsSync15, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
8135
8822
|
import { join as join7 } from "path";
|
|
8136
8823
|
|
|
8137
8824
|
// src/audit/auditFiles.ts
|
|
@@ -8154,12 +8841,213 @@ function auditFileDateMs(fileName) {
|
|
|
8154
8841
|
return d.getTime();
|
|
8155
8842
|
}
|
|
8156
8843
|
|
|
8844
|
+
// src/audit/auditStats.ts
|
|
8845
|
+
import {
|
|
8846
|
+
createReadStream,
|
|
8847
|
+
existsSync as existsSync14,
|
|
8848
|
+
readFileSync as readFileSync14,
|
|
8849
|
+
readdirSync,
|
|
8850
|
+
statSync as statSync3,
|
|
8851
|
+
writeFileSync as writeFileSync12
|
|
8852
|
+
} from "fs";
|
|
8853
|
+
import { basename, dirname as dirname7, join as join6 } from "path";
|
|
8854
|
+
var SIDECAR_VERSION = 1;
|
|
8855
|
+
var META_PREFIX_BYTES = 64 * 1024;
|
|
8856
|
+
var READ_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
8857
|
+
function auditStatsFileName(auditFile) {
|
|
8858
|
+
return auditFile.replace(/\.jsonl$/, ".stats.json");
|
|
8859
|
+
}
|
|
8860
|
+
function readPersisted(path2) {
|
|
8861
|
+
if (!existsSync14(path2)) return null;
|
|
8862
|
+
try {
|
|
8863
|
+
const value = JSON.parse(readFileSync14(path2, "utf8"));
|
|
8864
|
+
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)) {
|
|
8865
|
+
return null;
|
|
8866
|
+
}
|
|
8867
|
+
return value;
|
|
8868
|
+
} catch {
|
|
8869
|
+
return null;
|
|
8870
|
+
}
|
|
8871
|
+
}
|
|
8872
|
+
function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfter, record) {
|
|
8873
|
+
const statsPath = join6(dirname7(auditPath), auditStatsFileName(basename(auditPath)));
|
|
8874
|
+
const previous = auditBytesBefore === 0 ? {
|
|
8875
|
+
version: SIDECAR_VERSION,
|
|
8876
|
+
auditBytes: 0,
|
|
8877
|
+
requestCount: 0,
|
|
8878
|
+
errorCount: 0,
|
|
8879
|
+
complete: true,
|
|
8880
|
+
minTs: null,
|
|
8881
|
+
maxTs: null
|
|
8882
|
+
} : readPersisted(statsPath);
|
|
8883
|
+
if (!previous || !previous.complete || previous.auditBytes !== auditBytesBefore) return;
|
|
8884
|
+
const next = {
|
|
8885
|
+
version: SIDECAR_VERSION,
|
|
8886
|
+
auditBytes: auditBytesAfter,
|
|
8887
|
+
requestCount: previous.requestCount + 1,
|
|
8888
|
+
errorCount: previous.errorCount + (record.status >= 400 || Boolean(record.error) ? 1 : 0),
|
|
8889
|
+
complete: true,
|
|
8890
|
+
minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
|
|
8891
|
+
maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
|
|
8892
|
+
};
|
|
8893
|
+
writeFileSync12(statsPath, JSON.stringify(next), "utf8");
|
|
8894
|
+
}
|
|
8895
|
+
function queryCovers(stats, from, to) {
|
|
8896
|
+
return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
|
|
8897
|
+
}
|
|
8898
|
+
function fileOverlaps(file, from, to) {
|
|
8899
|
+
const start = auditFileDateMs(file);
|
|
8900
|
+
if (start === null) return false;
|
|
8901
|
+
const date = new Date(start);
|
|
8902
|
+
const end = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1).getTime();
|
|
8903
|
+
return end > from && start <= to;
|
|
8904
|
+
}
|
|
8905
|
+
function parseMetadataPrefix(prefix, prefixTruncated) {
|
|
8906
|
+
const text = prefix.toString("utf8");
|
|
8907
|
+
const tsMatch = /(?:^|,)"ts":(-?\d+)/.exec(text);
|
|
8908
|
+
const statusMatch = /(?:^|,)"status":(-?\d+)/.exec(text);
|
|
8909
|
+
const errorMatch = /(?:^|,)"error":"((?:\\.|[^"\\])*)"/.exec(text);
|
|
8910
|
+
const bodyStarted = /,(?:"requestBody"|"responseBody"):/.test(text);
|
|
8911
|
+
return {
|
|
8912
|
+
ts: tsMatch ? Number(tsMatch[1]) : void 0,
|
|
8913
|
+
status: statusMatch ? Number(statusMatch[1]) : void 0,
|
|
8914
|
+
hasError: Boolean(errorMatch?.[1]),
|
|
8915
|
+
complete: Boolean(tsMatch && statusMatch && (!prefixTruncated || bodyStarted))
|
|
8916
|
+
};
|
|
8917
|
+
}
|
|
8918
|
+
async function scanAuditFile(auditPath, startByte, auditBytes, from, to) {
|
|
8919
|
+
let requestCount = 0;
|
|
8920
|
+
let errorCount = 0;
|
|
8921
|
+
let filteredRequestCount = 0;
|
|
8922
|
+
let filteredErrorCount = 0;
|
|
8923
|
+
let minTs = null;
|
|
8924
|
+
let maxTs = null;
|
|
8925
|
+
let complete = true;
|
|
8926
|
+
let prefixParts = [];
|
|
8927
|
+
let prefixBytes = 0;
|
|
8928
|
+
let prefixTruncated = false;
|
|
8929
|
+
const consumeLine = () => {
|
|
8930
|
+
if (prefixBytes === 0 && !prefixTruncated) return;
|
|
8931
|
+
const prefix = Buffer.concat(prefixParts, prefixBytes);
|
|
8932
|
+
const metadata = parseMetadataPrefix(prefix, prefixTruncated);
|
|
8933
|
+
if (!metadata.complete || metadata.ts === void 0 || metadata.status === void 0) {
|
|
8934
|
+
complete = false;
|
|
8935
|
+
} else {
|
|
8936
|
+
requestCount += 1;
|
|
8937
|
+
const isError = metadata.status >= 400 || metadata.hasError;
|
|
8938
|
+
if (isError) errorCount += 1;
|
|
8939
|
+
minTs = minTs === null ? metadata.ts : Math.min(minTs, metadata.ts);
|
|
8940
|
+
maxTs = maxTs === null ? metadata.ts : Math.max(maxTs, metadata.ts);
|
|
8941
|
+
if (metadata.ts >= from && metadata.ts <= to) {
|
|
8942
|
+
filteredRequestCount += 1;
|
|
8943
|
+
if (isError) filteredErrorCount += 1;
|
|
8944
|
+
}
|
|
8945
|
+
}
|
|
8946
|
+
prefixParts = [];
|
|
8947
|
+
prefixBytes = 0;
|
|
8948
|
+
prefixTruncated = false;
|
|
8949
|
+
};
|
|
8950
|
+
if (auditBytes > startByte) {
|
|
8951
|
+
const stream = createReadStream(auditPath, {
|
|
8952
|
+
start: startByte,
|
|
8953
|
+
end: auditBytes - 1,
|
|
8954
|
+
highWaterMark: READ_CHUNK_BYTES
|
|
8955
|
+
});
|
|
8956
|
+
for await (const value of stream) {
|
|
8957
|
+
const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
8958
|
+
let offset = 0;
|
|
8959
|
+
while (offset < chunk.length) {
|
|
8960
|
+
const newline = chunk.indexOf(10, offset);
|
|
8961
|
+
const end = newline === -1 ? chunk.length : newline;
|
|
8962
|
+
if (prefixBytes < META_PREFIX_BYTES) {
|
|
8963
|
+
const retained = Math.min(META_PREFIX_BYTES - prefixBytes, end - offset);
|
|
8964
|
+
if (retained > 0) {
|
|
8965
|
+
prefixParts.push(Buffer.from(chunk.subarray(offset, offset + retained)));
|
|
8966
|
+
prefixBytes += retained;
|
|
8967
|
+
}
|
|
8968
|
+
if (retained < end - offset) prefixTruncated = true;
|
|
8969
|
+
} else if (end > offset) {
|
|
8970
|
+
prefixTruncated = true;
|
|
8971
|
+
}
|
|
8972
|
+
if (newline === -1) break;
|
|
8973
|
+
consumeLine();
|
|
8974
|
+
offset = newline + 1;
|
|
8975
|
+
}
|
|
8976
|
+
}
|
|
8977
|
+
}
|
|
8978
|
+
if (prefixBytes > 0 || prefixTruncated) complete = false;
|
|
8979
|
+
return {
|
|
8980
|
+
all: {
|
|
8981
|
+
version: SIDECAR_VERSION,
|
|
8982
|
+
auditBytes,
|
|
8983
|
+
requestCount,
|
|
8984
|
+
errorCount,
|
|
8985
|
+
complete,
|
|
8986
|
+
minTs,
|
|
8987
|
+
maxTs
|
|
8988
|
+
},
|
|
8989
|
+
filtered: { requestCount: filteredRequestCount, errorCount: filteredErrorCount, complete }
|
|
8990
|
+
};
|
|
8991
|
+
}
|
|
8992
|
+
function mergePersistedStats(previous, appended) {
|
|
8993
|
+
return {
|
|
8994
|
+
version: SIDECAR_VERSION,
|
|
8995
|
+
auditBytes: appended.auditBytes,
|
|
8996
|
+
requestCount: previous.requestCount + appended.requestCount,
|
|
8997
|
+
errorCount: previous.errorCount + appended.errorCount,
|
|
8998
|
+
complete: previous.complete && appended.complete,
|
|
8999
|
+
minTs: previous.minTs === null ? appended.minTs : appended.minTs === null ? previous.minTs : Math.min(previous.minTs, appended.minTs),
|
|
9000
|
+
maxTs: previous.maxTs === null ? appended.maxTs : appended.maxTs === null ? previous.maxTs : Math.max(previous.maxTs, appended.maxTs)
|
|
9001
|
+
};
|
|
9002
|
+
}
|
|
9003
|
+
async function readAuditStats(auditDir, query2 = {}) {
|
|
9004
|
+
if (!existsSync14(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
|
|
9005
|
+
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
9006
|
+
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
9007
|
+
let files;
|
|
9008
|
+
try {
|
|
9009
|
+
files = readdirSync(auditDir).filter((file) => AUDIT_FILE_RE.test(file) && fileOverlaps(file, from, to)).sort();
|
|
9010
|
+
} catch {
|
|
9011
|
+
return { requestCount: 0, errorCount: 0, complete: false };
|
|
9012
|
+
}
|
|
9013
|
+
const total = { requestCount: 0, errorCount: 0, complete: true };
|
|
9014
|
+
for (const file of files) {
|
|
9015
|
+
const auditPath = join6(auditDir, file);
|
|
9016
|
+
try {
|
|
9017
|
+
const auditBytes = statSync3(auditPath).size;
|
|
9018
|
+
const statsPath = join6(auditDir, auditStatsFileName(file));
|
|
9019
|
+
const persisted = readPersisted(statsPath);
|
|
9020
|
+
if (persisted && persisted.complete && persisted.auditBytes === auditBytes && queryCovers(persisted, from, to)) {
|
|
9021
|
+
total.requestCount += persisted.requestCount;
|
|
9022
|
+
total.errorCount += persisted.errorCount;
|
|
9023
|
+
continue;
|
|
9024
|
+
}
|
|
9025
|
+
const resumable = persisted && persisted.complete && persisted.auditBytes < auditBytes && queryCovers(persisted, from, to) ? persisted : null;
|
|
9026
|
+
const scanned = await scanAuditFile(
|
|
9027
|
+
auditPath,
|
|
9028
|
+
resumable?.auditBytes ?? 0,
|
|
9029
|
+
auditBytes,
|
|
9030
|
+
from,
|
|
9031
|
+
to
|
|
9032
|
+
);
|
|
9033
|
+
total.requestCount += scanned.filtered.requestCount + (resumable?.requestCount ?? 0);
|
|
9034
|
+
total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
|
|
9035
|
+
total.complete = total.complete && scanned.filtered.complete;
|
|
9036
|
+
const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
|
|
9037
|
+
if (current.complete) writeFileSync12(statsPath, JSON.stringify(current), "utf8");
|
|
9038
|
+
} catch {
|
|
9039
|
+
total.complete = false;
|
|
9040
|
+
}
|
|
9041
|
+
}
|
|
9042
|
+
return total;
|
|
9043
|
+
}
|
|
9044
|
+
|
|
8157
9045
|
// src/audit/AuditPruneSweeper.ts
|
|
8158
9046
|
var DAY_MS = 24 * 60 * 6e4;
|
|
8159
9047
|
var SWEEP_INTERVAL_MS2 = 60 * 6e4;
|
|
8160
9048
|
var AuditPruneSweeper = class {
|
|
8161
|
-
constructor(
|
|
8162
|
-
this.auditDir =
|
|
9049
|
+
constructor(auditDir, logger, config, intervalMs = SWEEP_INTERVAL_MS2, now = Date.now) {
|
|
9050
|
+
this.auditDir = auditDir;
|
|
8163
9051
|
this.logger = logger;
|
|
8164
9052
|
this.config = config;
|
|
8165
9053
|
this.intervalMs = intervalMs;
|
|
@@ -8206,17 +9094,19 @@ var AuditPruneSweeper = class {
|
|
|
8206
9094
|
if (!this.config.enabled || this.sweeping) return 0;
|
|
8207
9095
|
this.sweeping = true;
|
|
8208
9096
|
try {
|
|
8209
|
-
if (!
|
|
9097
|
+
if (!existsSync15(this.auditDir)) return 0;
|
|
8210
9098
|
const today = new Date(this.now());
|
|
8211
9099
|
const todayMidnight = new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime();
|
|
8212
9100
|
const cutoff = todayMidnight - (this.config.retentionDays - 1) * DAY_MS;
|
|
8213
9101
|
let removed = 0;
|
|
8214
|
-
for (const file of
|
|
9102
|
+
for (const file of readdirSync2(this.auditDir)) {
|
|
8215
9103
|
const dateMs = auditFileDateMs(file);
|
|
8216
9104
|
if (dateMs === null || dateMs >= cutoff) continue;
|
|
8217
9105
|
try {
|
|
8218
9106
|
unlinkSync3(join7(this.auditDir, file));
|
|
8219
9107
|
removed += 1;
|
|
9108
|
+
const statsPath = join7(this.auditDir, auditStatsFileName(file));
|
|
9109
|
+
if (existsSync15(statsPath)) unlinkSync3(statsPath);
|
|
8220
9110
|
} catch (error) {
|
|
8221
9111
|
this.logger.warn("[AuditPruneSweeper] failed to unlink expired audit file", {
|
|
8222
9112
|
file,
|
|
@@ -8238,15 +9128,15 @@ var AuditPruneSweeper = class {
|
|
|
8238
9128
|
};
|
|
8239
9129
|
|
|
8240
9130
|
// src/audit/auditReader.ts
|
|
8241
|
-
import { existsSync as
|
|
9131
|
+
import { existsSync as existsSync16, readdirSync as readdirSync3, readFileSync as readFileSync15 } from "fs";
|
|
8242
9132
|
import { join as join8 } from "path";
|
|
8243
9133
|
var DEFAULT_LIMIT = 200;
|
|
8244
9134
|
var MAX_LIMIT = 2e3;
|
|
8245
|
-
function readAuditRecords(
|
|
8246
|
-
if (!
|
|
9135
|
+
function readAuditRecords(auditDir, query2 = {}) {
|
|
9136
|
+
if (!existsSync16(auditDir)) return [];
|
|
8247
9137
|
let files;
|
|
8248
9138
|
try {
|
|
8249
|
-
files =
|
|
9139
|
+
files = readdirSync3(auditDir).filter((f) => AUDIT_FILE_RE.test(f));
|
|
8250
9140
|
} catch {
|
|
8251
9141
|
return [];
|
|
8252
9142
|
}
|
|
@@ -8257,7 +9147,7 @@ function readAuditRecords(auditDir2, query2 = {}) {
|
|
|
8257
9147
|
for (const file of files.sort().reverse()) {
|
|
8258
9148
|
let raw;
|
|
8259
9149
|
try {
|
|
8260
|
-
raw =
|
|
9150
|
+
raw = readFileSync15(join8(auditDir, file), "utf8");
|
|
8261
9151
|
} catch {
|
|
8262
9152
|
continue;
|
|
8263
9153
|
}
|
|
@@ -8286,11 +9176,11 @@ function isAuditRecord(value) {
|
|
|
8286
9176
|
}
|
|
8287
9177
|
|
|
8288
9178
|
// src/audit/AuditWriter.ts
|
|
8289
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5 } from "fs";
|
|
9179
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync5, statSync as statSync4 } from "fs";
|
|
8290
9180
|
import { join as join9 } from "path";
|
|
8291
9181
|
var AuditWriter = class {
|
|
8292
|
-
constructor(
|
|
8293
|
-
this.auditDir =
|
|
9182
|
+
constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
|
|
9183
|
+
this.auditDir = auditDir;
|
|
8294
9184
|
this.logger = logger;
|
|
8295
9185
|
this.defer = defer;
|
|
8296
9186
|
}
|
|
@@ -8324,7 +9214,21 @@ var AuditWriter = class {
|
|
|
8324
9214
|
this.dirEnsured = true;
|
|
8325
9215
|
}
|
|
8326
9216
|
const file = join9(this.auditDir, auditFileName(record.ts));
|
|
8327
|
-
|
|
9217
|
+
const line = JSON.stringify(record) + "\n";
|
|
9218
|
+
const auditBytesBefore = existsSync17(file) ? statSync4(file).size : 0;
|
|
9219
|
+
appendFileSync2(file, line, "utf8");
|
|
9220
|
+
try {
|
|
9221
|
+
updateAuditStatsAfterAppend(
|
|
9222
|
+
file,
|
|
9223
|
+
auditBytesBefore,
|
|
9224
|
+
auditBytesBefore + Buffer.byteLength(line, "utf8"),
|
|
9225
|
+
record
|
|
9226
|
+
);
|
|
9227
|
+
} catch (error) {
|
|
9228
|
+
this.logger.warn("[AuditWriter] failed to update audit stats", {
|
|
9229
|
+
error: error instanceof Error ? error.message : String(error)
|
|
9230
|
+
});
|
|
9231
|
+
}
|
|
8328
9232
|
}
|
|
8329
9233
|
};
|
|
8330
9234
|
|
|
@@ -8468,14 +9372,14 @@ var BillingPublisher = class {
|
|
|
8468
9372
|
};
|
|
8469
9373
|
|
|
8470
9374
|
// src/billing/billingReader.ts
|
|
8471
|
-
import { existsSync as
|
|
9375
|
+
import { existsSync as existsSync18, readdirSync as readdirSync4, readFileSync as readFileSync16 } from "fs";
|
|
8472
9376
|
import { join as join11 } from "path";
|
|
8473
9377
|
function readBillingLedger(billingDir) {
|
|
8474
9378
|
const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
|
|
8475
|
-
if (!
|
|
9379
|
+
if (!existsSync18(billingDir)) return view;
|
|
8476
9380
|
let files;
|
|
8477
9381
|
try {
|
|
8478
|
-
files =
|
|
9382
|
+
files = readdirSync4(billingDir);
|
|
8479
9383
|
} catch {
|
|
8480
9384
|
return view;
|
|
8481
9385
|
}
|
|
@@ -8506,7 +9410,7 @@ function readBillingStatus(billingDir) {
|
|
|
8506
9410
|
function parseLines(dir, file) {
|
|
8507
9411
|
let raw;
|
|
8508
9412
|
try {
|
|
8509
|
-
raw =
|
|
9413
|
+
raw = readFileSync16(join11(dir, file), "utf8");
|
|
8510
9414
|
} catch {
|
|
8511
9415
|
return [];
|
|
8512
9416
|
}
|
|
@@ -8692,6 +9596,78 @@ var TokenRefreshScheduler = class {
|
|
|
8692
9596
|
}
|
|
8693
9597
|
};
|
|
8694
9598
|
|
|
9599
|
+
// src/routeLeaseSubscriptionPreflight.ts
|
|
9600
|
+
import {
|
|
9601
|
+
RouteLeaseError as RouteLeaseError3
|
|
9602
|
+
} from "@omnicross/core/provider-proxy";
|
|
9603
|
+
import { getSharedAccountAllowanceScheduling as getSharedAccountAllowanceScheduling4 } from "@omnicross/core/pipeline/AccountAllowanceScheduling";
|
|
9604
|
+
import { getSharedAccountHealth as getSharedAccountHealth3 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
9605
|
+
import { accountSupportsModel } from "@omnicross/subscriptions/scheduler/accountModelMap";
|
|
9606
|
+
var PROVIDERS = /* @__PURE__ */ new Set(["claude", "codex", "gemini", "opencodego"]);
|
|
9607
|
+
function accountArray(config, providerId) {
|
|
9608
|
+
const record = config;
|
|
9609
|
+
const key = `${providerId}Accounts`;
|
|
9610
|
+
const accounts = record[key];
|
|
9611
|
+
if (Array.isArray(accounts)) return accounts;
|
|
9612
|
+
const legacy = record[providerId];
|
|
9613
|
+
if (!legacy || typeof legacy !== "object") return [];
|
|
9614
|
+
const activeKey = `active${providerId[0].toUpperCase()}${providerId.slice(1)}AccountId`;
|
|
9615
|
+
return [{ id: String(record[activeKey] ?? "active"), enabled: true, tokens: legacy }];
|
|
9616
|
+
}
|
|
9617
|
+
function hasCredential(providerId, account) {
|
|
9618
|
+
const tokens = account.tokens;
|
|
9619
|
+
if (providerId === "opencodego") return typeof tokens.apiKey === "string" && tokens.apiKey.length > 0;
|
|
9620
|
+
return typeof tokens.accessToken === "string" && tokens.accessToken.length > 0;
|
|
9621
|
+
}
|
|
9622
|
+
function safeProviderId(value) {
|
|
9623
|
+
if (!PROVIDERS.has(value)) {
|
|
9624
|
+
throw new RouteLeaseError3("upstream_not_found", "subscription provider was not found");
|
|
9625
|
+
}
|
|
9626
|
+
return value;
|
|
9627
|
+
}
|
|
9628
|
+
function createRouteLeaseSubscriptionPreflight(credentials) {
|
|
9629
|
+
return {
|
|
9630
|
+
async assertAvailable(upstream, model) {
|
|
9631
|
+
const providerId = safeProviderId(upstream.providerId);
|
|
9632
|
+
const config = await credentials.getFullConfig();
|
|
9633
|
+
const all = accountArray(config, providerId);
|
|
9634
|
+
if (all.length === 0) {
|
|
9635
|
+
throw new RouteLeaseError3("upstream_unavailable", "subscription provider has no configured account");
|
|
9636
|
+
}
|
|
9637
|
+
let bounded = all;
|
|
9638
|
+
if (upstream.kind === "account") {
|
|
9639
|
+
bounded = all.filter((account) => account.id === upstream.accountId);
|
|
9640
|
+
} else if (upstream.kind === "account-group") {
|
|
9641
|
+
bounded = all.filter((account) => account.group?.trim() === upstream.group);
|
|
9642
|
+
}
|
|
9643
|
+
if (bounded.length === 0) {
|
|
9644
|
+
throw new RouteLeaseError3("upstream_not_found", "the selected subscription resource was not found");
|
|
9645
|
+
}
|
|
9646
|
+
const modelEligible = bounded.filter(
|
|
9647
|
+
(account) => accountSupportsModel(account.supportedModels, model)
|
|
9648
|
+
);
|
|
9649
|
+
if (modelEligible.length === 0) {
|
|
9650
|
+
throw new RouteLeaseError3("model_not_configured", "model is not supported by the selected subscription resource");
|
|
9651
|
+
}
|
|
9652
|
+
const credentialEligible = modelEligible.filter(
|
|
9653
|
+
(account) => account.enabled !== false && hasCredential(providerId, account)
|
|
9654
|
+
);
|
|
9655
|
+
const health2 = getSharedAccountHealth3();
|
|
9656
|
+
const allowance = getSharedAccountAllowanceScheduling4();
|
|
9657
|
+
const candidates = credentialEligible.filter(
|
|
9658
|
+
(account) => health2.isSchedulable(providerId, account.id) && allowance.preview(providerId, account.id, account.priority ?? 50).schedulable
|
|
9659
|
+
);
|
|
9660
|
+
if (candidates.length > 0) return;
|
|
9661
|
+
if (upstream.kind === "account") {
|
|
9662
|
+
throw new RouteLeaseError3("upstream_unavailable", "the selected subscription account is unavailable");
|
|
9663
|
+
}
|
|
9664
|
+
throw new RouteLeaseError3("upstream_exhausted", "the selected subscription pool has no eligible account", {
|
|
9665
|
+
retryAfterSeconds: 30
|
|
9666
|
+
});
|
|
9667
|
+
}
|
|
9668
|
+
};
|
|
9669
|
+
}
|
|
9670
|
+
|
|
8695
9671
|
// src/webhook/WebhookDispatcher.ts
|
|
8696
9672
|
import { createHmac as createHmac2 } from "crypto";
|
|
8697
9673
|
import { fetchUpstream as fetchUpstream6 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
@@ -8874,11 +9850,11 @@ function buildDaemon(config, paths) {
|
|
|
8874
9850
|
new JsonAccountAllowancePersistence(defaultAccountAllowancePath(paths.configPath))
|
|
8875
9851
|
);
|
|
8876
9852
|
setSharedAccountAllowanceStore(accountAllowanceStore);
|
|
8877
|
-
|
|
9853
|
+
getSharedAccountAllowanceScheduling5().configure(
|
|
8878
9854
|
normalizeServerConfig(decryptedConfig.server).allowanceScheduling
|
|
8879
9855
|
);
|
|
8880
9856
|
const llmConfig = new ConfigFileProviderConfigSource(decryptedConfig);
|
|
8881
|
-
const keyDb = new JsonOutboundKeyDb(paths.keysPath);
|
|
9857
|
+
const keyDb = new JsonOutboundKeyDb(paths.keysPath, secretBox3);
|
|
8882
9858
|
const voucherDb = new JsonVoucherDb(defaultVouchersPath(paths.configPath));
|
|
8883
9859
|
const settingsStore = new JsonApiServerSettingsStore(paths.configPath, secretBox3);
|
|
8884
9860
|
const integrationStateStore = new IntegrationStateStore(
|
|
@@ -8942,10 +9918,21 @@ function buildDaemon(config, paths) {
|
|
|
8942
9918
|
onRecord: (apiKeyId, costUsd, at) => keySpendTracker.add(apiKeyId, costUsd, at)
|
|
8943
9919
|
});
|
|
8944
9920
|
const providerProxy = getProviderProxy({ llmConfig, apiKeyPool, usageRecorder });
|
|
9921
|
+
const routeLeaseManager = new RouteLeaseManager(
|
|
9922
|
+
providerProxy,
|
|
9923
|
+
new RouteLeaseTargetResolver(llmConfig, {
|
|
9924
|
+
providerKeys: apiKeyPool,
|
|
9925
|
+
subscriptions: createRouteLeaseSubscriptionPreflight(credentialStore)
|
|
9926
|
+
}),
|
|
9927
|
+
routeLeaseDescriptorPort,
|
|
9928
|
+
{ logger }
|
|
9929
|
+
);
|
|
9930
|
+
providerProxy.registerBeforeStop(() => routeLeaseManager.shutdown());
|
|
9931
|
+
providerProxy.registerBeforeStop(() => resetCliSessions());
|
|
8945
9932
|
llmConfig.setReloadHook(() => apiKeyPool.invalidateCache());
|
|
8946
9933
|
const accountHealthProbeScheduler = new AccountHealthProbeScheduler(
|
|
8947
9934
|
credentialStore,
|
|
8948
|
-
|
|
9935
|
+
getSharedAccountHealth4(),
|
|
8949
9936
|
logger,
|
|
8950
9937
|
DEFAULT_ACCOUNT_PROBE
|
|
8951
9938
|
);
|
|
@@ -8979,7 +9966,7 @@ function buildDaemon(config, paths) {
|
|
|
8979
9966
|
// lines through the injected logger (honors level/format/file sink).
|
|
8980
9967
|
logger
|
|
8981
9968
|
});
|
|
8982
|
-
const
|
|
9969
|
+
const auditDir = defaultAuditDir(paths.configPath);
|
|
8983
9970
|
const billingDir = defaultBillingDir(paths.configPath);
|
|
8984
9971
|
const adminServer = new AdminServer({
|
|
8985
9972
|
configPath: paths.configPath,
|
|
@@ -8992,6 +9979,7 @@ function buildDaemon(config, paths) {
|
|
|
8992
9979
|
keySpendReader: keySpendTracker,
|
|
8993
9980
|
settingsStore,
|
|
8994
9981
|
outboundApiServer,
|
|
9982
|
+
routeLeaseManager,
|
|
8995
9983
|
subscriptionAccounts,
|
|
8996
9984
|
accountAllowanceService,
|
|
8997
9985
|
allowanceRefreshScheduler: claudeAllowanceRefreshScheduler,
|
|
@@ -9013,10 +10001,16 @@ function buildDaemon(config, paths) {
|
|
|
9013
10001
|
// (NOT widening the least-authority writer — no token-returning read reachable).
|
|
9014
10002
|
oauthSessions: new OAuthSessionStore(),
|
|
9015
10003
|
// Real global fetch by default; a test seam (`paths.oauthExchangeFetch`) can
|
|
9016
|
-
// inject a mock so no real token endpoint is hit
|
|
9017
|
-
//
|
|
9018
|
-
//
|
|
9019
|
-
|
|
10004
|
+
// inject a mock so no real token endpoint is hit (one FetchLike for every
|
|
10005
|
+
// provider — the ctx below only matters on the real egress path).
|
|
10006
|
+
//
|
|
10007
|
+
// upstream-proxy: a PER-PROVIDER factory, so the exchange carries the same
|
|
10008
|
+
// `{ providerId }` ctx the CLI login and the token refresh already pass.
|
|
10009
|
+
// Without it the interactive login resolved only the global/env proxy layers
|
|
10010
|
+
// — `server.proxy.byProvider[...]` was silently skipped — and the call was
|
|
10011
|
+
// excluded from the upstream trace, so a failing login left no evidence.
|
|
10012
|
+
// `redactBodies` keeps the code/verifier + minted token out of that trace.
|
|
10013
|
+
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream7(url, init, { providerId, redactBodies: true }),
|
|
9020
10014
|
subscriptionAccountAppender: credentialStore,
|
|
9021
10015
|
// Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
|
|
9022
10016
|
// + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
|
|
@@ -9068,7 +10062,8 @@ function buildDaemon(config, paths) {
|
|
|
9068
10062
|
// date-rotated audit store. Bound to the store dir here so the AdminServer
|
|
9069
10063
|
// carries no path/store coupling. Records hold IP/UA/bodies → admin-only,
|
|
9070
10064
|
// NEVER unauth, NEVER on `/health`. Routed in `AdminServer` (not `adminApi.ts`).
|
|
9071
|
-
auditReader: (query2) => readAuditRecords(
|
|
10065
|
+
auditReader: (query2) => readAuditRecords(auditDir, query2),
|
|
10066
|
+
auditStatsReader: (query2) => readAuditStats(auditDir, query2),
|
|
9072
10067
|
// billing-event-stream: the AUTHED `GET /admin/api/billing-status` returns the
|
|
9073
10068
|
// secret-free total/delivered/pending counts of the durable ledger.
|
|
9074
10069
|
billingStatusReader: () => readBillingStatus(billingDir)
|
|
@@ -9077,10 +10072,10 @@ function buildDaemon(config, paths) {
|
|
|
9077
10072
|
logger,
|
|
9078
10073
|
fetchImpl: (url, init) => fetchUpstream7(url, init)
|
|
9079
10074
|
});
|
|
9080
|
-
setWebhookRuntime(webhookDispatcher,
|
|
9081
|
-
const auditWriter = new AuditWriter(
|
|
9082
|
-
const auditPruneSweeper = new AuditPruneSweeper(
|
|
9083
|
-
setAuditRuntime(auditWriter, auditPruneSweeper
|
|
10075
|
+
setWebhookRuntime(webhookDispatcher, getSharedAccountHealth4());
|
|
10076
|
+
const auditWriter = new AuditWriter(auditDir, logger);
|
|
10077
|
+
const auditPruneSweeper = new AuditPruneSweeper(auditDir, logger, DEFAULT_AUDIT_CONFIG);
|
|
10078
|
+
setAuditRuntime(auditWriter, auditPruneSweeper);
|
|
9084
10079
|
const billingPublisher = new BillingPublisher(billingDir, logger);
|
|
9085
10080
|
const billingRetrySweeper = new BillingRetrySweeper(
|
|
9086
10081
|
billingDir,
|
|
@@ -9092,7 +10087,7 @@ function buildDaemon(config, paths) {
|
|
|
9092
10087
|
const tokenRefreshScheduler = new TokenRefreshScheduler(credentialStore, logger);
|
|
9093
10088
|
const accountHealthSweeper = new AccountHealthSweeper(
|
|
9094
10089
|
credentialStore,
|
|
9095
|
-
|
|
10090
|
+
getSharedAccountHealth4(),
|
|
9096
10091
|
logger
|
|
9097
10092
|
);
|
|
9098
10093
|
return {
|
|
@@ -9101,6 +10096,7 @@ function buildDaemon(config, paths) {
|
|
|
9101
10096
|
keyDb,
|
|
9102
10097
|
settingsStore,
|
|
9103
10098
|
providerProxy,
|
|
10099
|
+
routeLeaseManager,
|
|
9104
10100
|
outboundApiServer,
|
|
9105
10101
|
apiKeyPool,
|
|
9106
10102
|
autoDisableStore,
|
|
@@ -9144,7 +10140,7 @@ function resetDaemonSingletonsForTests() {
|
|
|
9144
10140
|
}
|
|
9145
10141
|
function isTokensStoreReadable(tokensPath) {
|
|
9146
10142
|
try {
|
|
9147
|
-
if (!
|
|
10143
|
+
if (!existsSync19(tokensPath)) return true;
|
|
9148
10144
|
accessSync(tokensPath, fsConstants.R_OK);
|
|
9149
10145
|
return true;
|
|
9150
10146
|
} catch {
|