@omnicross/daemon 0.1.6 → 0.1.7
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 +676 -102
- package/dist/cli.js +671 -84
- package/dist/index.cjs +669 -96
- package/dist/index.d.cts +94 -24
- package/dist/index.d.ts +94 -24
- package/dist/index.js +664 -78
- package/package.json +63 -63
package/dist/cli.cjs
CHANGED
|
@@ -1508,10 +1508,20 @@ function assertLoopbackGatewayUrl(value) {
|
|
|
1508
1508
|
// src/ports/JsonOutboundKeyDb.ts
|
|
1509
1509
|
var import_node_fs6 = require("fs");
|
|
1510
1510
|
var JsonOutboundKeyDb = class {
|
|
1511
|
-
|
|
1511
|
+
/**
|
|
1512
|
+
* @param secretBox OPTIONAL reversible-secret codec. When present, a created
|
|
1513
|
+
* key's plaintext is persisted as a `keySecret` `enc:` envelope (enabling the
|
|
1514
|
+
* operator "view key" affordance via `outboundApiKeysReveal`). When absent the
|
|
1515
|
+
* store stays hash-only (byte-identical to the legacy behavior) and reveal
|
|
1516
|
+
* always returns `null`. Existing 1-arg call sites (tests, lightweight
|
|
1517
|
+
* embedders) keep working.
|
|
1518
|
+
*/
|
|
1519
|
+
constructor(keysPath, secretBox3) {
|
|
1512
1520
|
this.keysPath = keysPath;
|
|
1521
|
+
this.secretBox = secretBox3;
|
|
1513
1522
|
}
|
|
1514
1523
|
keysPath;
|
|
1524
|
+
secretBox;
|
|
1515
1525
|
async outboundApiKeysList() {
|
|
1516
1526
|
return this.readRows();
|
|
1517
1527
|
}
|
|
@@ -1537,10 +1547,27 @@ var JsonOutboundKeyDb = class {
|
|
|
1537
1547
|
allowedEndpoints: input.allowedEndpoints,
|
|
1538
1548
|
loopbackOnly: input.loopbackOnly
|
|
1539
1549
|
};
|
|
1550
|
+
if (input.plaintext && this.secretBox) {
|
|
1551
|
+
row.keySecret = this.secretBox.encrypt(input.plaintext);
|
|
1552
|
+
}
|
|
1540
1553
|
rows.push(row);
|
|
1541
1554
|
this.writeRows(rows);
|
|
1542
1555
|
return row;
|
|
1543
1556
|
}
|
|
1557
|
+
async outboundApiKeysReveal(id) {
|
|
1558
|
+
const rows = this.readRows();
|
|
1559
|
+
const row = rows.find((r) => r.id === id);
|
|
1560
|
+
if (!row || !row.keySecret || !this.secretBox) return null;
|
|
1561
|
+
return this.secretBox.decrypt(row.keySecret);
|
|
1562
|
+
}
|
|
1563
|
+
async outboundApiKeysDelete(id) {
|
|
1564
|
+
const rows = this.readRows();
|
|
1565
|
+
const idx = rows.findIndex((r) => r.id === id);
|
|
1566
|
+
if (idx < 0) return false;
|
|
1567
|
+
rows.splice(idx, 1);
|
|
1568
|
+
this.writeRows(rows);
|
|
1569
|
+
return true;
|
|
1570
|
+
}
|
|
1544
1571
|
async outboundApiKeysRevoke(id) {
|
|
1545
1572
|
return this.mutateRow(id, (row) => {
|
|
1546
1573
|
if (row.revokedAt !== null) return false;
|
|
@@ -1738,13 +1765,13 @@ async function keysRevoke(db, id) {
|
|
|
1738
1765
|
|
|
1739
1766
|
// src/commands/launch.ts
|
|
1740
1767
|
var import_node_child_process2 = require("child_process");
|
|
1741
|
-
var
|
|
1768
|
+
var import_node_fs25 = require("fs");
|
|
1742
1769
|
var import_node_path17 = require("path");
|
|
1743
1770
|
var import_node_util4 = require("util");
|
|
1744
1771
|
var import_cli_launcher2 = require("@omnicross/cli-launcher");
|
|
1745
1772
|
|
|
1746
1773
|
// src/bootstrap.ts
|
|
1747
|
-
var
|
|
1774
|
+
var import_node_fs24 = require("fs");
|
|
1748
1775
|
var import_audit_types = require("@omnicross/contracts/audit-types");
|
|
1749
1776
|
var import_billing_types = require("@omnicross/contracts/billing-types");
|
|
1750
1777
|
var import_GeminiCodeAssistProjectResolver = require("@omnicross/core/auth/GeminiCodeAssistProjectResolver");
|
|
@@ -1838,7 +1865,7 @@ async function runCodexLoopback(sessionId, codeVerifier, state, signal, deps) {
|
|
|
1838
1865
|
const code = await deps.codexAwaitLoopback(state, void 0, signal);
|
|
1839
1866
|
const result = await import_subscriptions.codexOAuth.exchangeCodeForTokens(
|
|
1840
1867
|
{ authorizationCode: code, codeVerifier, state },
|
|
1841
|
-
deps.oauthExchangeFetch
|
|
1868
|
+
deps.oauthExchangeFetch("codex")
|
|
1842
1869
|
);
|
|
1843
1870
|
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
1844
1871
|
const block = {
|
|
@@ -2340,6 +2367,17 @@ function handleAuditQuery(req, res, reader) {
|
|
|
2340
2367
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2341
2368
|
res.end(JSON.stringify({ records }));
|
|
2342
2369
|
}
|
|
2370
|
+
async function handleAuditStatsQuery(req, res, reader) {
|
|
2371
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
2372
|
+
const query2 = {};
|
|
2373
|
+
const from = intParam(url.searchParams.get("from"));
|
|
2374
|
+
if (from !== void 0) query2.from = from;
|
|
2375
|
+
const to = intParam(url.searchParams.get("to"));
|
|
2376
|
+
if (to !== void 0) query2.to = to;
|
|
2377
|
+
const stats = reader ? await reader(query2) : { requestCount: 0, errorCount: 0, complete: true };
|
|
2378
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2379
|
+
res.end(JSON.stringify(stats));
|
|
2380
|
+
}
|
|
2343
2381
|
|
|
2344
2382
|
// src/admin/billingStatusApi.ts
|
|
2345
2383
|
function handleBillingStatus(res, reader) {
|
|
@@ -2522,7 +2560,13 @@ function listMappablePresets() {
|
|
|
2522
2560
|
name: preset.name,
|
|
2523
2561
|
apiFormat: resolved.format,
|
|
2524
2562
|
baseUrl: preset.api_base_url,
|
|
2525
|
-
models: Array.isArray(preset.models) ? preset.models : []
|
|
2563
|
+
models: Array.isArray(preset.models) ? preset.models : [],
|
|
2564
|
+
nameKey: preset.nameKey,
|
|
2565
|
+
icon: preset.icon,
|
|
2566
|
+
description: preset.description,
|
|
2567
|
+
features: preset.features,
|
|
2568
|
+
website: preset.website,
|
|
2569
|
+
modelsEndpoint: preset.modelsEndpoint
|
|
2526
2570
|
});
|
|
2527
2571
|
}
|
|
2528
2572
|
return { mappable, excluded };
|
|
@@ -2911,7 +2955,7 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
2911
2955
|
const rawCode = typeof body["code"] === "string" ? body["code"] : "";
|
|
2912
2956
|
if (!sessionId) return err2(400, "oauth complete requires { sessionId }");
|
|
2913
2957
|
if (!rawCode) return err2(400, "oauth complete requires { code }");
|
|
2914
|
-
const session = deps.oauthSessions.
|
|
2958
|
+
const session = deps.oauthSessions.peek(sessionId);
|
|
2915
2959
|
if (!session) return err2(410, "oauth session is unknown, expired, or already used");
|
|
2916
2960
|
if (session.providerId !== providerId) {
|
|
2917
2961
|
return err2(400, `oauth session does not match provider '${providerId}'`);
|
|
@@ -2925,13 +2969,15 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
2925
2969
|
}
|
|
2926
2970
|
code = splitCode;
|
|
2927
2971
|
}
|
|
2972
|
+
const exchangeFetch = deps.oauthExchangeFetch(providerId);
|
|
2928
2973
|
let block;
|
|
2929
2974
|
try {
|
|
2930
|
-
block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state,
|
|
2975
|
+
block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
|
|
2931
2976
|
} catch (exchangeError) {
|
|
2932
2977
|
const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
|
|
2933
2978
|
return err2(502, `oauth token exchange failed for '${providerId}': ${reason}`);
|
|
2934
2979
|
}
|
|
2980
|
+
deps.oauthSessions.consume(sessionId);
|
|
2935
2981
|
const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
|
|
2936
2982
|
await deps.subscriptionAccountAppender.appendProviderAccount(providerId, block, label);
|
|
2937
2983
|
const status = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
@@ -3176,8 +3222,8 @@ function validateAuditSegment(patch) {
|
|
|
3176
3222
|
}
|
|
3177
3223
|
}
|
|
3178
3224
|
const maxBodyBytes = audit["maxBodyBytes"];
|
|
3179
|
-
if (maxBodyBytes !== void 0 && (typeof maxBodyBytes !== "number" || !Number.isFinite(maxBodyBytes) || maxBodyBytes <
|
|
3180
|
-
errors.push("audit.maxBodyBytes must be a non-negative number");
|
|
3225
|
+
if (maxBodyBytes !== void 0 && (typeof maxBodyBytes !== "number" || !Number.isFinite(maxBodyBytes) || maxBodyBytes < -1)) {
|
|
3226
|
+
errors.push("audit.maxBodyBytes must be -1 or a non-negative number");
|
|
3181
3227
|
}
|
|
3182
3228
|
const retentionDays = audit["retentionDays"];
|
|
3183
3229
|
if (retentionDays !== void 0 && (typeof retentionDays !== "number" || !Number.isFinite(retentionDays) || retentionDays < 0)) {
|
|
@@ -3612,18 +3658,16 @@ function preserveWebhookSecrets(incoming, current) {
|
|
|
3612
3658
|
}
|
|
3613
3659
|
|
|
3614
3660
|
// src/audit/auditRuntime.ts
|
|
3615
|
-
var import_node_path8 = require("path");
|
|
3616
3661
|
var import_auditSink = require("@omnicross/core/pipeline/auditSink");
|
|
3617
3662
|
var import_upstreamTrace = require("@omnicross/core/pipeline/upstreamTrace");
|
|
3618
3663
|
var writer = null;
|
|
3619
3664
|
var sweeper = null;
|
|
3620
|
-
|
|
3621
|
-
function setAuditRuntime(w, s, dir) {
|
|
3665
|
+
function setAuditRuntime(w, s) {
|
|
3622
3666
|
writer = w;
|
|
3623
3667
|
sweeper = s;
|
|
3624
|
-
auditDir = dir;
|
|
3625
3668
|
}
|
|
3626
3669
|
function applyAuditConfig(config) {
|
|
3670
|
+
(0, import_upstreamTrace.setUpstreamTracePath)(null);
|
|
3627
3671
|
const enabled = config?.enabled === true && writer !== null;
|
|
3628
3672
|
if (enabled && config) {
|
|
3629
3673
|
(0, import_auditSink.setAuditCaptureConfig)(config);
|
|
@@ -3633,11 +3677,9 @@ function applyAuditConfig(config) {
|
|
|
3633
3677
|
sweeper.configure(config);
|
|
3634
3678
|
sweeper.start();
|
|
3635
3679
|
}
|
|
3636
|
-
(0, import_upstreamTrace.setUpstreamTracePath)(config.captureBodies ? (0, import_node_path8.join)(auditDir, "upstream-trace.jsonl") : null);
|
|
3637
3680
|
} else {
|
|
3638
3681
|
(0, import_auditSink.setAuditCaptureConfig)(null);
|
|
3639
3682
|
(0, import_auditSink.setAuditSink)(null);
|
|
3640
|
-
(0, import_upstreamTrace.setUpstreamTracePath)(null);
|
|
3641
3683
|
if (sweeper) {
|
|
3642
3684
|
if (config) sweeper.configure(config);
|
|
3643
3685
|
sweeper.dispose();
|
|
@@ -4480,6 +4522,8 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
4480
4522
|
}
|
|
4481
4523
|
|
|
4482
4524
|
// src/admin/adminApi.ts
|
|
4525
|
+
var import_AccountRouteActivity = require("@omnicross/core/pipeline/AccountRouteActivity");
|
|
4526
|
+
var import_ServerOverloadCounter = require("@omnicross/core/pipeline/ServerOverloadCounter");
|
|
4483
4527
|
function readBody(req) {
|
|
4484
4528
|
return new Promise((resolve3, reject) => {
|
|
4485
4529
|
const chunks = [];
|
|
@@ -4516,6 +4560,9 @@ function toKeyInfo(row) {
|
|
|
4516
4560
|
id: row.id,
|
|
4517
4561
|
name: row.name,
|
|
4518
4562
|
keyPrefix: row.keyPrefix,
|
|
4563
|
+
// True only when a reversible `keySecret` envelope was persisted at creation
|
|
4564
|
+
// — gates the UI "view key" eye. Legacy hash-only rows read as absent.
|
|
4565
|
+
revealable: Boolean(row.keySecret),
|
|
4519
4566
|
enabled: row.enabled,
|
|
4520
4567
|
createdAt: row.createdAt,
|
|
4521
4568
|
lastUsedAt: row.lastUsedAt,
|
|
@@ -5171,7 +5218,13 @@ function handlePresets(res, method) {
|
|
|
5171
5218
|
name: p.name,
|
|
5172
5219
|
apiFormat: p.apiFormat,
|
|
5173
5220
|
baseUrl: p.baseUrl,
|
|
5174
|
-
models: p.models
|
|
5221
|
+
models: p.models,
|
|
5222
|
+
nameKey: p.nameKey,
|
|
5223
|
+
icon: p.icon,
|
|
5224
|
+
description: p.description,
|
|
5225
|
+
features: p.features,
|
|
5226
|
+
website: p.website,
|
|
5227
|
+
modelsEndpoint: p.modelsEndpoint
|
|
5175
5228
|
}));
|
|
5176
5229
|
return writeJson3(res, 200, { presets, excluded });
|
|
5177
5230
|
}
|
|
@@ -5205,12 +5258,27 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
5205
5258
|
plaintextOnce: created.plaintextOnce
|
|
5206
5259
|
});
|
|
5207
5260
|
}
|
|
5261
|
+
if (method === "GET" && rest.length === 2 && rest[1] === "reveal") {
|
|
5262
|
+
const revealed = await deps.keyDb.outboundApiKeysReveal(rest[0]);
|
|
5263
|
+
if (revealed !== null) return writeJson3(res, 200, { key: revealed });
|
|
5264
|
+
const exists = (await deps.keyDb.outboundApiKeysList()).some((r) => r.id === rest[0]);
|
|
5265
|
+
if (!exists) return writeJsonError(res, 404, `key '${rest[0]}' not found`);
|
|
5266
|
+
return writeJsonError(
|
|
5267
|
+
res,
|
|
5268
|
+
409,
|
|
5269
|
+
`key '${rest[0]}' is not revealable (created before revealable key storage)`
|
|
5270
|
+
);
|
|
5271
|
+
}
|
|
5208
5272
|
const id = rest[0];
|
|
5209
5273
|
const action = rest[1];
|
|
5210
5274
|
if (method === "POST" && id && action === "revoke") {
|
|
5211
5275
|
const ok = await deps.keyDb.outboundApiKeysRevoke(id);
|
|
5212
5276
|
return writeJson3(res, ok ? 200 : 404, { ok });
|
|
5213
5277
|
}
|
|
5278
|
+
if (method === "DELETE" && id && !action) {
|
|
5279
|
+
const ok = await deps.keyDb.outboundApiKeysDelete(id);
|
|
5280
|
+
return writeJson3(res, ok ? 200 : 404, { ok });
|
|
5281
|
+
}
|
|
5214
5282
|
if (method === "POST" && id && action === "enabled") {
|
|
5215
5283
|
const body = await readJsonBody3(req);
|
|
5216
5284
|
const enabled = body["enabled"] === true;
|
|
@@ -5387,6 +5455,40 @@ async function handleServer(req, res, method, deps) {
|
|
|
5387
5455
|
return writeJsonError(res, 405, `method ${method} not allowed on server`);
|
|
5388
5456
|
}
|
|
5389
5457
|
async function handleAccounts(req, res, method, rest, deps) {
|
|
5458
|
+
if (rest[0] === "route-activity" && rest.length === 1) {
|
|
5459
|
+
if (method !== "GET") {
|
|
5460
|
+
return writeJsonError(res, 405, `method ${method} not allowed on account route activity`);
|
|
5461
|
+
}
|
|
5462
|
+
const query2 = requestQuery(req);
|
|
5463
|
+
const parsedLimit = Number(query2.get("limit") ?? "100");
|
|
5464
|
+
const records = (0, import_AccountRouteActivity.getSharedAccountRouteActivity)().list({
|
|
5465
|
+
providerId: query2.get("providerId") ?? void 0,
|
|
5466
|
+
accountId: query2.get("accountId") ?? void 0,
|
|
5467
|
+
sessionKey: query2.get("sessionKey") ?? void 0,
|
|
5468
|
+
limit: Number.isFinite(parsedLimit) ? parsedLimit : 100
|
|
5469
|
+
});
|
|
5470
|
+
return writeJson3(res, 200, {
|
|
5471
|
+
available: true,
|
|
5472
|
+
records,
|
|
5473
|
+
capacity: import_AccountRouteActivity.ACCOUNT_ROUTE_ACTIVITY_LIMIT,
|
|
5474
|
+
collectedAt: Date.now()
|
|
5475
|
+
});
|
|
5476
|
+
}
|
|
5477
|
+
if (rest[0] === "overload-counters" && rest.length === 1) {
|
|
5478
|
+
if (method !== "GET") {
|
|
5479
|
+
return writeJsonError(res, 405, `method ${method} not allowed on overload counters`);
|
|
5480
|
+
}
|
|
5481
|
+
const query2 = requestQuery(req);
|
|
5482
|
+
const entries = (0, import_ServerOverloadCounter.getSharedOverloadCounter)().list({
|
|
5483
|
+
providerId: query2.get("providerId") ?? void 0,
|
|
5484
|
+
accountId: query2.get("accountId") ?? void 0
|
|
5485
|
+
});
|
|
5486
|
+
return writeJson3(res, 200, {
|
|
5487
|
+
available: true,
|
|
5488
|
+
entries,
|
|
5489
|
+
collectedAt: Date.now()
|
|
5490
|
+
});
|
|
5491
|
+
}
|
|
5390
5492
|
if (rest[0] === "allowances") {
|
|
5391
5493
|
return handleAccountAllowanceApi(
|
|
5392
5494
|
req,
|
|
@@ -5533,8 +5635,13 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
5533
5635
|
if (!(listed[providerId] ?? []).some((account) => account.id === accountId)) {
|
|
5534
5636
|
return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
5535
5637
|
}
|
|
5536
|
-
const result = await deps.accountProbeService.
|
|
5537
|
-
return writeJson3(res, 200, {
|
|
5638
|
+
const result = await deps.accountProbeService.testAccountConnection(providerId, accountId);
|
|
5639
|
+
return writeJson3(res, 200, {
|
|
5640
|
+
ok: result.ok,
|
|
5641
|
+
marked: result.marked,
|
|
5642
|
+
tier: result.tier,
|
|
5643
|
+
model: result.model
|
|
5644
|
+
});
|
|
5538
5645
|
}
|
|
5539
5646
|
if (method === "POST" && rest[2] === "label") {
|
|
5540
5647
|
const accountId = rest[1];
|
|
@@ -5813,7 +5920,7 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
|
5813
5920
|
var import_node_fs9 = require("fs");
|
|
5814
5921
|
var import_promises = require("fs/promises");
|
|
5815
5922
|
var import_node_module = require("module");
|
|
5816
|
-
var
|
|
5923
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
5817
5924
|
var import_meta = {};
|
|
5818
5925
|
var CONTENT_TYPES = {
|
|
5819
5926
|
".html": "text/html; charset=utf-8",
|
|
@@ -5834,13 +5941,13 @@ var CONTENT_TYPES = {
|
|
|
5834
5941
|
function resolveUiDist() {
|
|
5835
5942
|
const fromEnv = process.env["OMNICROSS_UI_DIST"];
|
|
5836
5943
|
if (fromEnv) {
|
|
5837
|
-
return (0, import_node_fs9.existsSync)(
|
|
5944
|
+
return (0, import_node_fs9.existsSync)(import_node_path8.default.join(fromEnv, "index.html")) ? import_node_path8.default.resolve(fromEnv) : null;
|
|
5838
5945
|
}
|
|
5839
5946
|
try {
|
|
5840
5947
|
const req = (0, import_node_module.createRequire)(typeof __filename !== "undefined" ? __filename : import_meta.url);
|
|
5841
5948
|
const pkgJson = req.resolve("@omnicross/ui/package.json");
|
|
5842
|
-
const dist =
|
|
5843
|
-
return (0, import_node_fs9.existsSync)(
|
|
5949
|
+
const dist = import_node_path8.default.join(import_node_path8.default.dirname(pkgJson), "dist");
|
|
5950
|
+
return (0, import_node_fs9.existsSync)(import_node_path8.default.join(dist, "index.html")) ? dist : null;
|
|
5844
5951
|
} catch {
|
|
5845
5952
|
return null;
|
|
5846
5953
|
}
|
|
@@ -5882,16 +5989,16 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
5882
5989
|
res.end(JSON.stringify({ error: { type: "bad_request", message: "invalid path" } }));
|
|
5883
5990
|
return true;
|
|
5884
5991
|
}
|
|
5885
|
-
const filePath =
|
|
5886
|
-
if (filePath !== uiDist && !filePath.startsWith(uiDist +
|
|
5992
|
+
const filePath = import_node_path8.default.resolve(uiDist, rel === "" ? "index.html" : rel);
|
|
5993
|
+
if (filePath !== uiDist && !filePath.startsWith(uiDist + import_node_path8.default.sep)) {
|
|
5887
5994
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
5888
5995
|
res.end(JSON.stringify({ error: { type: "forbidden", message: "path outside ui root" } }));
|
|
5889
5996
|
return true;
|
|
5890
5997
|
}
|
|
5891
5998
|
let target = filePath;
|
|
5892
5999
|
if (!(0, import_node_fs9.existsSync)(target) || (0, import_node_fs9.statSync)(target).isDirectory()) {
|
|
5893
|
-
if (
|
|
5894
|
-
target =
|
|
6000
|
+
if (import_node_path8.default.extname(rel) === "") {
|
|
6001
|
+
target = import_node_path8.default.join(uiDist, "index.html");
|
|
5895
6002
|
} else {
|
|
5896
6003
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
5897
6004
|
res.end(JSON.stringify({ error: { type: "not_found", message: "no such ui asset" } }));
|
|
@@ -5899,14 +6006,14 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
5899
6006
|
}
|
|
5900
6007
|
}
|
|
5901
6008
|
const body = await (0, import_promises.readFile)(target);
|
|
5902
|
-
const type = CONTENT_TYPES[
|
|
6009
|
+
const type = CONTENT_TYPES[import_node_path8.default.extname(target).toLowerCase()] ?? "application/octet-stream";
|
|
5903
6010
|
res.writeHead(200, { "Content-Type": type, "Content-Length": body.length });
|
|
5904
6011
|
res.end(req.method === "HEAD" ? void 0 : body);
|
|
5905
6012
|
return true;
|
|
5906
6013
|
}
|
|
5907
6014
|
|
|
5908
6015
|
// src/admin/version.ts
|
|
5909
|
-
var DAEMON_VERSION = true ? "0.1.
|
|
6016
|
+
var DAEMON_VERSION = true ? "0.1.7" : "0.0.0-dev";
|
|
5910
6017
|
|
|
5911
6018
|
// src/admin/AdminServer.ts
|
|
5912
6019
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -6014,6 +6121,10 @@ var AdminServer = class {
|
|
|
6014
6121
|
handleAuditQuery(req, res, this.deps.auditReader);
|
|
6015
6122
|
return;
|
|
6016
6123
|
}
|
|
6124
|
+
if (path2 === "/admin/api/audit/stats" && (req.method === "GET" || req.method === "HEAD")) {
|
|
6125
|
+
await handleAuditStatsQuery(req, res, this.deps.auditStatsReader);
|
|
6126
|
+
return;
|
|
6127
|
+
}
|
|
6017
6128
|
if (path2 === "/admin/api/billing-status" && (req.method === "GET" || req.method === "HEAD")) {
|
|
6018
6129
|
handleBillingStatus(res, this.deps.billingStatusReader);
|
|
6019
6130
|
return;
|
|
@@ -6130,20 +6241,36 @@ var OAuthSessionStore = class {
|
|
|
6130
6241
|
return sessionId;
|
|
6131
6242
|
}
|
|
6132
6243
|
/**
|
|
6133
|
-
*
|
|
6134
|
-
*
|
|
6135
|
-
* dropped). A `null` return means the completer must reject (no
|
|
6136
|
-
* write).
|
|
6244
|
+
* NON-DESTRUCTIVE lookup: return the session for `sessionId`, or `null` when
|
|
6245
|
+
* it is unknown, already consumed, or past its TTL (an expired entry is
|
|
6246
|
+
* dropped here). A `null` return means the completer must reject (no
|
|
6247
|
+
* exchange, no write).
|
|
6248
|
+
*
|
|
6249
|
+
* Deliberately NOT a consume: the completer peeks, runs the token exchange,
|
|
6250
|
+
* and only {@link consume}s once a token has actually been minted. Consuming
|
|
6251
|
+
* up-front burned the session on EVERY failed exchange (a mistyped/expired
|
|
6252
|
+
* pasted code, a proxy hiccup), so the user's natural retry hit
|
|
6253
|
+
* "session is unknown, expired, or already used" and the login became
|
|
6254
|
+
* unrecoverable without restarting the whole flow.
|
|
6137
6255
|
*/
|
|
6138
|
-
|
|
6256
|
+
peek(sessionId) {
|
|
6139
6257
|
this.sweep();
|
|
6140
6258
|
const session = this.sessions.get(sessionId);
|
|
6141
6259
|
if (!session) return null;
|
|
6142
|
-
|
|
6143
|
-
|
|
6260
|
+
if (Date.now() - session.createdAt > this.ttlMs) {
|
|
6261
|
+
this.sessions.delete(sessionId);
|
|
6262
|
+
return null;
|
|
6263
|
+
}
|
|
6144
6264
|
return session;
|
|
6145
6265
|
}
|
|
6146
|
-
/**
|
|
6266
|
+
/**
|
|
6267
|
+
* SINGLE-USE burn: drop the session so the same `sessionId` can never be
|
|
6268
|
+
* completed twice. Called ONLY after a successful token exchange.
|
|
6269
|
+
*/
|
|
6270
|
+
consume(sessionId) {
|
|
6271
|
+
this.sessions.delete(sessionId);
|
|
6272
|
+
}
|
|
6273
|
+
/** Drop every session past its TTL. Called on each put/peek. */
|
|
6147
6274
|
sweep() {
|
|
6148
6275
|
const now = Date.now();
|
|
6149
6276
|
for (const [id, session] of this.sessions) {
|
|
@@ -6158,6 +6285,10 @@ var LOOPBACK_HOST = "127.0.0.1";
|
|
|
6158
6285
|
var LOOPBACK_PORT = 1455;
|
|
6159
6286
|
var CALLBACK_PATH = "/auth/callback";
|
|
6160
6287
|
var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
6288
|
+
var HTML_HEADERS = {
|
|
6289
|
+
"Content-Type": "text/html",
|
|
6290
|
+
Connection: "close"
|
|
6291
|
+
};
|
|
6161
6292
|
function pageHtml(message) {
|
|
6162
6293
|
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>`;
|
|
6163
6294
|
}
|
|
@@ -6168,30 +6299,31 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
|
|
|
6168
6299
|
if (settled) return;
|
|
6169
6300
|
settled = true;
|
|
6170
6301
|
clearTimeout(timer);
|
|
6171
|
-
|
|
6302
|
+
fn();
|
|
6303
|
+
server2.close();
|
|
6172
6304
|
};
|
|
6173
6305
|
const server = (0, import_node_http3.createServer)((req, res) => {
|
|
6174
6306
|
const url = new URL(req.url ?? "", `http://${LOOPBACK_HOST}:${LOOPBACK_PORT}`);
|
|
6175
6307
|
if (url.pathname !== CALLBACK_PATH) {
|
|
6176
|
-
res.writeHead(404,
|
|
6308
|
+
res.writeHead(404, HTML_HEADERS);
|
|
6177
6309
|
res.end(pageHtml("Not found"));
|
|
6178
6310
|
return;
|
|
6179
6311
|
}
|
|
6180
6312
|
const code = url.searchParams.get("code");
|
|
6181
6313
|
const state = url.searchParams.get("state");
|
|
6182
6314
|
if (!code) {
|
|
6183
|
-
res.writeHead(400,
|
|
6315
|
+
res.writeHead(400, HTML_HEADERS);
|
|
6184
6316
|
res.end(pageHtml("Login failed: missing authorization code."));
|
|
6185
6317
|
finish(server, () => reject(new Error("login: callback did not include an authorization code")));
|
|
6186
6318
|
return;
|
|
6187
6319
|
}
|
|
6188
6320
|
if (state !== expectedState) {
|
|
6189
|
-
res.writeHead(400,
|
|
6321
|
+
res.writeHead(400, HTML_HEADERS);
|
|
6190
6322
|
res.end(pageHtml("Login failed: state mismatch."));
|
|
6191
6323
|
finish(server, () => reject(new Error("login: callback state did not match (possible CSRF) \u2014 aborting")));
|
|
6192
6324
|
return;
|
|
6193
6325
|
}
|
|
6194
|
-
res.writeHead(200,
|
|
6326
|
+
res.writeHead(200, HTML_HEADERS);
|
|
6195
6327
|
res.end(pageHtml("Login complete."));
|
|
6196
6328
|
finish(server, () => resolve3(code));
|
|
6197
6329
|
});
|
|
@@ -6677,8 +6809,12 @@ var JsonlUsageEventStore = class {
|
|
|
6677
6809
|
reasoningTokens: 0,
|
|
6678
6810
|
costUsd: 0,
|
|
6679
6811
|
costSavedByCacheUsd: 0,
|
|
6680
|
-
eventCount: 0
|
|
6812
|
+
eventCount: 0,
|
|
6813
|
+
cacheEligibleEventCount: 0,
|
|
6814
|
+
coldCacheEventCount: 0,
|
|
6815
|
+
medianCacheHitRate: null
|
|
6681
6816
|
};
|
|
6817
|
+
const perEventHitRates = [];
|
|
6682
6818
|
for (const row of this.readRows(range)) {
|
|
6683
6819
|
totals.inputTokens += row.inputTokens;
|
|
6684
6820
|
totals.outputTokens += row.outputTokens;
|
|
@@ -6688,7 +6824,14 @@ var JsonlUsageEventStore = class {
|
|
|
6688
6824
|
totals.costUsd += row.costUsd;
|
|
6689
6825
|
totals.costSavedByCacheUsd += row.costSavedByCacheUsd;
|
|
6690
6826
|
totals.eventCount += 1;
|
|
6827
|
+
const promptSideTokens = row.inputTokens + row.cacheReadTokens + row.cacheCreationTokens;
|
|
6828
|
+
if (promptSideTokens > 0) {
|
|
6829
|
+
totals.cacheEligibleEventCount += 1;
|
|
6830
|
+
if (row.cacheReadTokens === 0) totals.coldCacheEventCount += 1;
|
|
6831
|
+
perEventHitRates.push(row.cacheReadTokens / promptSideTokens);
|
|
6832
|
+
}
|
|
6691
6833
|
}
|
|
6834
|
+
totals.medianCacheHitRate = median(perEventHitRates);
|
|
6692
6835
|
return totals;
|
|
6693
6836
|
}
|
|
6694
6837
|
async getByModel(range) {
|
|
@@ -6922,6 +7065,15 @@ var NUMERIC_FIELDS = [
|
|
|
6922
7065
|
];
|
|
6923
7066
|
var NULLABLE_STRING_FIELDS = ["messageId", "parentMessageId", "sessionId", "apiKeyId"];
|
|
6924
7067
|
var isStringOrNull = (v) => v === null || typeof v === "string";
|
|
7068
|
+
var CACHE_KEY_SOURCES = /* @__PURE__ */ new Set([
|
|
7069
|
+
"client",
|
|
7070
|
+
"session-header",
|
|
7071
|
+
"thread-header",
|
|
7072
|
+
"body-session-id",
|
|
7073
|
+
"body-thread-id",
|
|
7074
|
+
"content-fingerprint",
|
|
7075
|
+
"none"
|
|
7076
|
+
]);
|
|
6925
7077
|
function isUsageEventRecord(parsed) {
|
|
6926
7078
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
6927
7079
|
const r = parsed;
|
|
@@ -6929,6 +7081,10 @@ function isUsageEventRecord(parsed) {
|
|
|
6929
7081
|
if (typeof r["providerId"] !== "string") return false;
|
|
6930
7082
|
if (typeof r["model"] !== "string") return false;
|
|
6931
7083
|
if (typeof r["engineOrigin"] !== "string") return false;
|
|
7084
|
+
if (r["cacheKeySource"] !== void 0 && (typeof r["cacheKeySource"] !== "string" || !CACHE_KEY_SOURCES.has(r["cacheKeySource"]))) return false;
|
|
7085
|
+
if (r["cacheKeyInjected"] !== void 0 && typeof r["cacheKeyInjected"] !== "boolean") {
|
|
7086
|
+
return false;
|
|
7087
|
+
}
|
|
6932
7088
|
for (const f of NULLABLE_STRING_FIELDS) {
|
|
6933
7089
|
if (!isStringOrNull(r[f])) return false;
|
|
6934
7090
|
}
|
|
@@ -6938,6 +7094,12 @@ function isUsageEventRecord(parsed) {
|
|
|
6938
7094
|
}
|
|
6939
7095
|
return true;
|
|
6940
7096
|
}
|
|
7097
|
+
function median(values) {
|
|
7098
|
+
if (values.length === 0) return null;
|
|
7099
|
+
values.sort((a, b) => a - b);
|
|
7100
|
+
const middle = Math.floor(values.length / 2);
|
|
7101
|
+
return values.length % 2 === 1 ? values[middle] : (values[middle - 1] + values[middle]) / 2;
|
|
7102
|
+
}
|
|
6941
7103
|
|
|
6942
7104
|
// src/ports/JsonPricingStore.ts
|
|
6943
7105
|
var import_node_fs13 = require("fs");
|
|
@@ -7298,7 +7460,7 @@ var JsonVoucherDb = class {
|
|
|
7298
7460
|
|
|
7299
7461
|
// src/ports/JsonSubscriptionCredentialStore.ts
|
|
7300
7462
|
var import_node_fs17 = require("fs");
|
|
7301
|
-
var
|
|
7463
|
+
var import_node_path10 = require("path");
|
|
7302
7464
|
var import_SubscriptionAccountHealth2 = require("@omnicross/core/pipeline/SubscriptionAccountHealth");
|
|
7303
7465
|
var import_AccountAllowanceScheduling3 = require("@omnicross/core/pipeline/AccountAllowanceScheduling");
|
|
7304
7466
|
var import_upstreamFetch4 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
@@ -7349,9 +7511,9 @@ function findDuplicateCredentialIds(accounts) {
|
|
|
7349
7511
|
// src/ports/external-cli-credentials.ts
|
|
7350
7512
|
var import_node_fs16 = require("fs");
|
|
7351
7513
|
var import_node_os3 = require("os");
|
|
7352
|
-
var
|
|
7514
|
+
var import_node_path9 = require("path");
|
|
7353
7515
|
function externalStorePath(provider, home = (0, import_node_os3.homedir)()) {
|
|
7354
|
-
return provider === "claude" ? (0,
|
|
7516
|
+
return provider === "claude" ? (0, import_node_path9.join)(home, ".claude", ".credentials.json") : (0, import_node_path9.join)(home, ".codex", "auth.json");
|
|
7355
7517
|
}
|
|
7356
7518
|
function decodeJwtExpiryMs(token) {
|
|
7357
7519
|
try {
|
|
@@ -7441,9 +7603,15 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
7441
7603
|
* TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
|
|
7442
7604
|
* through {@link fetchUpstream} with the account's `{ providerId, accountId }`
|
|
7443
7605
|
* ctx so the per-account/provider proxy applies. `@internal` also a test seam.
|
|
7606
|
+
*
|
|
7607
|
+
* `redactBodies` is REQUIRED here: this round-trip sends the refresh_token and
|
|
7608
|
+
* receives a fresh access/refresh token pair. Carrying a `providerId` opts the
|
|
7609
|
+
* call into the upstream trace (so a failing refresh is diagnosable), and the
|
|
7610
|
+
* trace captures bodies verbatim — without this flag every refresh would write
|
|
7611
|
+
* a plaintext token pair into `upstream-trace.jsonl`.
|
|
7444
7612
|
*/
|
|
7445
7613
|
buildRefreshFetch(providerId, accountId) {
|
|
7446
|
-
return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch4.fetchUpstream)(url, init, { providerId, accountId }));
|
|
7614
|
+
return this.fetchImpl ?? ((url, init) => (0, import_upstreamFetch4.fetchUpstream)(url, init, { providerId, accountId, redactBodies: true }));
|
|
7447
7615
|
}
|
|
7448
7616
|
/**
|
|
7449
7617
|
* In-flight refresh coalescing. OAuth refresh tokens are
|
|
@@ -7968,7 +8136,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
7968
8136
|
* `enc:v1:`; already-`enc:`/`$ENV` untouched) before serializing, so any
|
|
7969
8137
|
* write incl. child 4's future refresh writes lands encrypted. */
|
|
7970
8138
|
persist(config) {
|
|
7971
|
-
(0, import_node_fs17.mkdirSync)((0,
|
|
8139
|
+
(0, import_node_fs17.mkdirSync)((0, import_node_path10.dirname)(this.tokensPath), { recursive: true });
|
|
7972
8140
|
const encrypted = encryptTokens(config, this.box);
|
|
7973
8141
|
(0, import_node_fs17.writeFileSync)(this.tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
|
|
7974
8142
|
}
|
|
@@ -8003,6 +8171,124 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
8003
8171
|
// src/AccountHealthProbeScheduler.ts
|
|
8004
8172
|
var import_upstreamFetch5 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
8005
8173
|
|
|
8174
|
+
// src/probe/CodexGenerationProbe.ts
|
|
8175
|
+
var import_codexCliHeaders = require("@omnicross/core/provider-proxy/identity/codexCliHeaders");
|
|
8176
|
+
var CODEX_GENERATION_PROBE_MODEL = "gpt-5.6-luna";
|
|
8177
|
+
var CODEX_GENERATION_PROBE_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
8178
|
+
var MAX_STREAM_BYTES = 256 * 1024;
|
|
8179
|
+
var PROBE_INSTRUCTION = "Return exactly PONG and no other text.";
|
|
8180
|
+
function buildCodexGenerationProbeInit(token, signal) {
|
|
8181
|
+
return {
|
|
8182
|
+
method: "POST",
|
|
8183
|
+
signal,
|
|
8184
|
+
headers: {
|
|
8185
|
+
...import_codexCliHeaders.DEFAULT_CODEX_CLI_HEADERS,
|
|
8186
|
+
Authorization: `Bearer ${token}`,
|
|
8187
|
+
Accept: (0, import_codexCliHeaders.codexAcceptHeader)(true),
|
|
8188
|
+
"Content-Type": "application/json"
|
|
8189
|
+
},
|
|
8190
|
+
body: JSON.stringify({
|
|
8191
|
+
model: CODEX_GENERATION_PROBE_MODEL,
|
|
8192
|
+
input: [
|
|
8193
|
+
{
|
|
8194
|
+
role: "developer",
|
|
8195
|
+
content: [{ type: "input_text", text: PROBE_INSTRUCTION }]
|
|
8196
|
+
},
|
|
8197
|
+
{
|
|
8198
|
+
role: "user",
|
|
8199
|
+
content: [{ type: "input_text", text: "Connection probe." }]
|
|
8200
|
+
}
|
|
8201
|
+
],
|
|
8202
|
+
// GPT-5.6 otherwise defaults to medium reasoning. A connectivity probe
|
|
8203
|
+
// needs the lowest-cost path and no tool reasoning.
|
|
8204
|
+
reasoning: { effort: "none" },
|
|
8205
|
+
stream: true,
|
|
8206
|
+
store: false
|
|
8207
|
+
})
|
|
8208
|
+
};
|
|
8209
|
+
}
|
|
8210
|
+
async function readCodexGenerationProbeStream(response) {
|
|
8211
|
+
if (!response.body) return { completed: false, outputChars: 0 };
|
|
8212
|
+
const reader = response.body.getReader();
|
|
8213
|
+
const decoder = new TextDecoder();
|
|
8214
|
+
let buffer = "";
|
|
8215
|
+
let bytes = 0;
|
|
8216
|
+
let outputChars = 0;
|
|
8217
|
+
try {
|
|
8218
|
+
while (true) {
|
|
8219
|
+
const { done, value } = await reader.read();
|
|
8220
|
+
if (done) break;
|
|
8221
|
+
bytes += value.byteLength;
|
|
8222
|
+
if (bytes > MAX_STREAM_BYTES) {
|
|
8223
|
+
await reader.cancel();
|
|
8224
|
+
return { completed: false, outputChars };
|
|
8225
|
+
}
|
|
8226
|
+
buffer += decoder.decode(value, { stream: true });
|
|
8227
|
+
buffer = buffer.replace(/\r\n/g, "\n");
|
|
8228
|
+
let boundary = buffer.indexOf("\n\n");
|
|
8229
|
+
while (boundary >= 0) {
|
|
8230
|
+
const block = buffer.slice(0, boundary);
|
|
8231
|
+
buffer = buffer.slice(boundary + 2);
|
|
8232
|
+
const event = parseSseBlock(block);
|
|
8233
|
+
if (event) {
|
|
8234
|
+
const type = event["type"];
|
|
8235
|
+
if (type === "response.output_text.delta" && typeof event["delta"] === "string") {
|
|
8236
|
+
outputChars += event["delta"].length;
|
|
8237
|
+
} else if (type === "response.output_text.done" && typeof event["text"] === "string") {
|
|
8238
|
+
outputChars = Math.max(outputChars, event["text"].length);
|
|
8239
|
+
} else if (type === "response.failed" || type === "error") {
|
|
8240
|
+
await reader.cancel();
|
|
8241
|
+
return { completed: false, outputChars };
|
|
8242
|
+
} else if (type === "response.completed") {
|
|
8243
|
+
const completedResponse = asRecord(event["response"]);
|
|
8244
|
+
const status = completedResponse?.["status"];
|
|
8245
|
+
outputChars = Math.max(outputChars, countCompletedOutputChars(completedResponse));
|
|
8246
|
+
await reader.cancel();
|
|
8247
|
+
return {
|
|
8248
|
+
completed: (status === void 0 || status === "completed") && outputChars > 0,
|
|
8249
|
+
outputChars
|
|
8250
|
+
};
|
|
8251
|
+
}
|
|
8252
|
+
}
|
|
8253
|
+
boundary = buffer.indexOf("\n\n");
|
|
8254
|
+
}
|
|
8255
|
+
}
|
|
8256
|
+
} catch {
|
|
8257
|
+
return { completed: false, outputChars };
|
|
8258
|
+
} finally {
|
|
8259
|
+
reader.releaseLock();
|
|
8260
|
+
}
|
|
8261
|
+
return { completed: false, outputChars };
|
|
8262
|
+
}
|
|
8263
|
+
function parseSseBlock(block) {
|
|
8264
|
+
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
8265
|
+
if (!data || data === "[DONE]") return null;
|
|
8266
|
+
try {
|
|
8267
|
+
return JSON.parse(data);
|
|
8268
|
+
} catch {
|
|
8269
|
+
return null;
|
|
8270
|
+
}
|
|
8271
|
+
}
|
|
8272
|
+
function asRecord(value) {
|
|
8273
|
+
return value !== null && typeof value === "object" ? value : void 0;
|
|
8274
|
+
}
|
|
8275
|
+
function countCompletedOutputChars(response) {
|
|
8276
|
+
const output = response?.["output"];
|
|
8277
|
+
if (!Array.isArray(output)) return 0;
|
|
8278
|
+
let chars = 0;
|
|
8279
|
+
for (const item of output) {
|
|
8280
|
+
const content = asRecord(item)?.["content"];
|
|
8281
|
+
if (!Array.isArray(content)) continue;
|
|
8282
|
+
for (const part of content) {
|
|
8283
|
+
const record = asRecord(part);
|
|
8284
|
+
if (record?.["type"] === "output_text" && typeof record["text"] === "string") {
|
|
8285
|
+
chars += record["text"].length;
|
|
8286
|
+
}
|
|
8287
|
+
}
|
|
8288
|
+
}
|
|
8289
|
+
return chars;
|
|
8290
|
+
}
|
|
8291
|
+
|
|
8006
8292
|
// src/probe/ProbeStrategy.ts
|
|
8007
8293
|
var PROVIDER_PROBE_PLANS = {
|
|
8008
8294
|
claude: {
|
|
@@ -8130,17 +8416,17 @@ var AccountHealthProbeScheduler = class {
|
|
|
8130
8416
|
}
|
|
8131
8417
|
if (readThrew) {
|
|
8132
8418
|
this.record(providerId, accountId, { ts: now, ok: false, status: null, tier: "local" });
|
|
8133
|
-
return { ok: false, marked: false };
|
|
8419
|
+
return { ok: false, marked: false, tier: "local" };
|
|
8134
8420
|
}
|
|
8135
8421
|
if (!token) {
|
|
8136
8422
|
this.health.recordUpstreamOutcome(providerId, accountId, { status: 401, now });
|
|
8137
8423
|
this.record(providerId, accountId, { ts: now, ok: false, status: 401, tier: "local" });
|
|
8138
|
-
return { ok: false, marked: true };
|
|
8424
|
+
return { ok: false, marked: true, tier: "local" };
|
|
8139
8425
|
}
|
|
8140
8426
|
const plan = this.planFor(providerId);
|
|
8141
8427
|
if (plan.kind === "local") {
|
|
8142
8428
|
this.record(providerId, accountId, { ts: now, ok: true, tier: "local" });
|
|
8143
|
-
return { ok: true, marked: false };
|
|
8429
|
+
return { ok: true, marked: false, tier: "local" };
|
|
8144
8430
|
}
|
|
8145
8431
|
const start = this.now();
|
|
8146
8432
|
let status = null;
|
|
@@ -8165,7 +8451,60 @@ var AccountHealthProbeScheduler = class {
|
|
|
8165
8451
|
latencyMs,
|
|
8166
8452
|
tier: "upstream"
|
|
8167
8453
|
});
|
|
8168
|
-
return { ok: status !== null && status < 400, marked };
|
|
8454
|
+
return { ok: status !== null && status < 400, marked, tier: "upstream" };
|
|
8455
|
+
}
|
|
8456
|
+
/**
|
|
8457
|
+
* Manual connection test. Codex performs a real, quota-consuming generation;
|
|
8458
|
+
* every other provider keeps its existing cheap probe. Scheduled sweeps never
|
|
8459
|
+
* call this method, so they remain non-billable.
|
|
8460
|
+
*/
|
|
8461
|
+
async testAccountConnection(providerId, accountId) {
|
|
8462
|
+
if (providerId !== "codex") return this.probeAccount(providerId, accountId);
|
|
8463
|
+
const now = this.now();
|
|
8464
|
+
let token;
|
|
8465
|
+
try {
|
|
8466
|
+
token = await this.store.getAccessTokenForAccount(providerId, accountId);
|
|
8467
|
+
} catch {
|
|
8468
|
+
this.record(providerId, accountId, { ts: now, ok: false, status: null, tier: "local" });
|
|
8469
|
+
return { ok: false, marked: false, tier: "local", model: CODEX_GENERATION_PROBE_MODEL };
|
|
8470
|
+
}
|
|
8471
|
+
if (!token) {
|
|
8472
|
+
this.health.recordUpstreamOutcome(providerId, accountId, { status: 401, now });
|
|
8473
|
+
this.record(providerId, accountId, { ts: now, ok: false, status: 401, tier: "local" });
|
|
8474
|
+
return { ok: false, marked: true, tier: "local", model: CODEX_GENERATION_PROBE_MODEL };
|
|
8475
|
+
}
|
|
8476
|
+
const startedAt = this.now();
|
|
8477
|
+
let attempt = await this.runCodexGenerationAttempt(accountId, token);
|
|
8478
|
+
if (attempt.status === 401 && this.store.refreshAccountToken) {
|
|
8479
|
+
try {
|
|
8480
|
+
if (await this.store.refreshAccountToken(providerId, accountId)) {
|
|
8481
|
+
const refreshed = await this.store.getAccessTokenForAccount(providerId, accountId);
|
|
8482
|
+
if (refreshed) attempt = await this.runCodexGenerationAttempt(accountId, refreshed);
|
|
8483
|
+
}
|
|
8484
|
+
} catch {
|
|
8485
|
+
}
|
|
8486
|
+
}
|
|
8487
|
+
const latencyMs = this.now() - startedAt;
|
|
8488
|
+
const ok = attempt.status !== null && attempt.status >= 200 && attempt.status < 300 && attempt.completed;
|
|
8489
|
+
let marked = false;
|
|
8490
|
+
if (ok) {
|
|
8491
|
+
this.health.clearTransientMark(providerId, accountId);
|
|
8492
|
+
} else if (attempt.status === 401 || attempt.status === 403) {
|
|
8493
|
+
marked = this.applyOutcome(providerId, accountId, attempt.status, attempt.bodyText, now);
|
|
8494
|
+
}
|
|
8495
|
+
this.record(providerId, accountId, {
|
|
8496
|
+
ts: now,
|
|
8497
|
+
ok,
|
|
8498
|
+
status: attempt.status,
|
|
8499
|
+
latencyMs,
|
|
8500
|
+
tier: "generation"
|
|
8501
|
+
});
|
|
8502
|
+
return {
|
|
8503
|
+
ok,
|
|
8504
|
+
marked,
|
|
8505
|
+
tier: "generation",
|
|
8506
|
+
model: CODEX_GENERATION_PROBE_MODEL
|
|
8507
|
+
};
|
|
8169
8508
|
}
|
|
8170
8509
|
/** Per-account rolling history for the authed admin surface (design D5). */
|
|
8171
8510
|
getAllHistory() {
|
|
@@ -8222,6 +8561,24 @@ var AccountHealthProbeScheduler = class {
|
|
|
8222
8561
|
return "";
|
|
8223
8562
|
}
|
|
8224
8563
|
}
|
|
8564
|
+
async runCodexGenerationAttempt(accountId, token) {
|
|
8565
|
+
try {
|
|
8566
|
+
const timeoutMs = Math.max(this.config.timeoutMs, 15e3);
|
|
8567
|
+
const response = await this.fetchImpl(
|
|
8568
|
+
CODEX_GENERATION_PROBE_URL,
|
|
8569
|
+
buildCodexGenerationProbeInit(token, AbortSignal.timeout(timeoutMs)),
|
|
8570
|
+
{ providerId: "codex", accountId, redactBodies: true }
|
|
8571
|
+
);
|
|
8572
|
+
if (response.status < 200 || response.status >= 300) {
|
|
8573
|
+
const bodyText = response.status === 403 ? await this.readBounded(response) : void 0;
|
|
8574
|
+
return { status: response.status, completed: false, bodyText };
|
|
8575
|
+
}
|
|
8576
|
+
const stream = await readCodexGenerationProbeStream(response);
|
|
8577
|
+
return { status: response.status, completed: stream.completed };
|
|
8578
|
+
} catch {
|
|
8579
|
+
return { status: null, completed: false };
|
|
8580
|
+
}
|
|
8581
|
+
}
|
|
8225
8582
|
key(providerId, accountId) {
|
|
8226
8583
|
return `${providerId}${KEY_SEP}${accountId}`;
|
|
8227
8584
|
}
|
|
@@ -8317,7 +8674,7 @@ var AccountHealthSweeper = class {
|
|
|
8317
8674
|
};
|
|
8318
8675
|
|
|
8319
8676
|
// src/audit/AuditPruneSweeper.ts
|
|
8320
|
-
var
|
|
8677
|
+
var import_node_fs19 = require("fs");
|
|
8321
8678
|
var import_node_path12 = require("path");
|
|
8322
8679
|
|
|
8323
8680
|
// src/audit/auditFiles.ts
|
|
@@ -8340,12 +8697,206 @@ function auditFileDateMs(fileName) {
|
|
|
8340
8697
|
return d.getTime();
|
|
8341
8698
|
}
|
|
8342
8699
|
|
|
8700
|
+
// src/audit/auditStats.ts
|
|
8701
|
+
var import_node_fs18 = require("fs");
|
|
8702
|
+
var import_node_path11 = require("path");
|
|
8703
|
+
var SIDECAR_VERSION = 1;
|
|
8704
|
+
var META_PREFIX_BYTES = 64 * 1024;
|
|
8705
|
+
var READ_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
8706
|
+
function auditStatsFileName(auditFile) {
|
|
8707
|
+
return auditFile.replace(/\.jsonl$/, ".stats.json");
|
|
8708
|
+
}
|
|
8709
|
+
function readPersisted(path2) {
|
|
8710
|
+
if (!(0, import_node_fs18.existsSync)(path2)) return null;
|
|
8711
|
+
try {
|
|
8712
|
+
const value = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
|
|
8713
|
+
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)) {
|
|
8714
|
+
return null;
|
|
8715
|
+
}
|
|
8716
|
+
return value;
|
|
8717
|
+
} catch {
|
|
8718
|
+
return null;
|
|
8719
|
+
}
|
|
8720
|
+
}
|
|
8721
|
+
function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfter, record) {
|
|
8722
|
+
const statsPath = (0, import_node_path11.join)((0, import_node_path11.dirname)(auditPath), auditStatsFileName((0, import_node_path11.basename)(auditPath)));
|
|
8723
|
+
const previous = auditBytesBefore === 0 ? {
|
|
8724
|
+
version: SIDECAR_VERSION,
|
|
8725
|
+
auditBytes: 0,
|
|
8726
|
+
requestCount: 0,
|
|
8727
|
+
errorCount: 0,
|
|
8728
|
+
complete: true,
|
|
8729
|
+
minTs: null,
|
|
8730
|
+
maxTs: null
|
|
8731
|
+
} : readPersisted(statsPath);
|
|
8732
|
+
if (!previous || !previous.complete || previous.auditBytes !== auditBytesBefore) return;
|
|
8733
|
+
const next = {
|
|
8734
|
+
version: SIDECAR_VERSION,
|
|
8735
|
+
auditBytes: auditBytesAfter,
|
|
8736
|
+
requestCount: previous.requestCount + 1,
|
|
8737
|
+
errorCount: previous.errorCount + (record.status >= 400 || Boolean(record.error) ? 1 : 0),
|
|
8738
|
+
complete: true,
|
|
8739
|
+
minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
|
|
8740
|
+
maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
|
|
8741
|
+
};
|
|
8742
|
+
(0, import_node_fs18.writeFileSync)(statsPath, JSON.stringify(next), "utf8");
|
|
8743
|
+
}
|
|
8744
|
+
function queryCovers(stats, from, to) {
|
|
8745
|
+
return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
|
|
8746
|
+
}
|
|
8747
|
+
function fileOverlaps(file, from, to) {
|
|
8748
|
+
const start = auditFileDateMs(file);
|
|
8749
|
+
if (start === null) return false;
|
|
8750
|
+
const date = new Date(start);
|
|
8751
|
+
const end = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1).getTime();
|
|
8752
|
+
return end > from && start <= to;
|
|
8753
|
+
}
|
|
8754
|
+
function parseMetadataPrefix(prefix, prefixTruncated) {
|
|
8755
|
+
const text = prefix.toString("utf8");
|
|
8756
|
+
const tsMatch = /(?:^|,)"ts":(-?\d+)/.exec(text);
|
|
8757
|
+
const statusMatch = /(?:^|,)"status":(-?\d+)/.exec(text);
|
|
8758
|
+
const errorMatch = /(?:^|,)"error":"((?:\\.|[^"\\])*)"/.exec(text);
|
|
8759
|
+
const bodyStarted = /,(?:"requestBody"|"responseBody"):/.test(text);
|
|
8760
|
+
return {
|
|
8761
|
+
ts: tsMatch ? Number(tsMatch[1]) : void 0,
|
|
8762
|
+
status: statusMatch ? Number(statusMatch[1]) : void 0,
|
|
8763
|
+
hasError: Boolean(errorMatch?.[1]),
|
|
8764
|
+
complete: Boolean(tsMatch && statusMatch && (!prefixTruncated || bodyStarted))
|
|
8765
|
+
};
|
|
8766
|
+
}
|
|
8767
|
+
async function scanAuditFile(auditPath, startByte, auditBytes, from, to) {
|
|
8768
|
+
let requestCount = 0;
|
|
8769
|
+
let errorCount = 0;
|
|
8770
|
+
let filteredRequestCount = 0;
|
|
8771
|
+
let filteredErrorCount = 0;
|
|
8772
|
+
let minTs = null;
|
|
8773
|
+
let maxTs = null;
|
|
8774
|
+
let complete = true;
|
|
8775
|
+
let prefixParts = [];
|
|
8776
|
+
let prefixBytes = 0;
|
|
8777
|
+
let prefixTruncated = false;
|
|
8778
|
+
const consumeLine = () => {
|
|
8779
|
+
if (prefixBytes === 0 && !prefixTruncated) return;
|
|
8780
|
+
const prefix = Buffer.concat(prefixParts, prefixBytes);
|
|
8781
|
+
const metadata = parseMetadataPrefix(prefix, prefixTruncated);
|
|
8782
|
+
if (!metadata.complete || metadata.ts === void 0 || metadata.status === void 0) {
|
|
8783
|
+
complete = false;
|
|
8784
|
+
} else {
|
|
8785
|
+
requestCount += 1;
|
|
8786
|
+
const isError = metadata.status >= 400 || metadata.hasError;
|
|
8787
|
+
if (isError) errorCount += 1;
|
|
8788
|
+
minTs = minTs === null ? metadata.ts : Math.min(minTs, metadata.ts);
|
|
8789
|
+
maxTs = maxTs === null ? metadata.ts : Math.max(maxTs, metadata.ts);
|
|
8790
|
+
if (metadata.ts >= from && metadata.ts <= to) {
|
|
8791
|
+
filteredRequestCount += 1;
|
|
8792
|
+
if (isError) filteredErrorCount += 1;
|
|
8793
|
+
}
|
|
8794
|
+
}
|
|
8795
|
+
prefixParts = [];
|
|
8796
|
+
prefixBytes = 0;
|
|
8797
|
+
prefixTruncated = false;
|
|
8798
|
+
};
|
|
8799
|
+
if (auditBytes > startByte) {
|
|
8800
|
+
const stream = (0, import_node_fs18.createReadStream)(auditPath, {
|
|
8801
|
+
start: startByte,
|
|
8802
|
+
end: auditBytes - 1,
|
|
8803
|
+
highWaterMark: READ_CHUNK_BYTES
|
|
8804
|
+
});
|
|
8805
|
+
for await (const value of stream) {
|
|
8806
|
+
const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
8807
|
+
let offset = 0;
|
|
8808
|
+
while (offset < chunk.length) {
|
|
8809
|
+
const newline = chunk.indexOf(10, offset);
|
|
8810
|
+
const end = newline === -1 ? chunk.length : newline;
|
|
8811
|
+
if (prefixBytes < META_PREFIX_BYTES) {
|
|
8812
|
+
const retained = Math.min(META_PREFIX_BYTES - prefixBytes, end - offset);
|
|
8813
|
+
if (retained > 0) {
|
|
8814
|
+
prefixParts.push(Buffer.from(chunk.subarray(offset, offset + retained)));
|
|
8815
|
+
prefixBytes += retained;
|
|
8816
|
+
}
|
|
8817
|
+
if (retained < end - offset) prefixTruncated = true;
|
|
8818
|
+
} else if (end > offset) {
|
|
8819
|
+
prefixTruncated = true;
|
|
8820
|
+
}
|
|
8821
|
+
if (newline === -1) break;
|
|
8822
|
+
consumeLine();
|
|
8823
|
+
offset = newline + 1;
|
|
8824
|
+
}
|
|
8825
|
+
}
|
|
8826
|
+
}
|
|
8827
|
+
if (prefixBytes > 0 || prefixTruncated) complete = false;
|
|
8828
|
+
return {
|
|
8829
|
+
all: {
|
|
8830
|
+
version: SIDECAR_VERSION,
|
|
8831
|
+
auditBytes,
|
|
8832
|
+
requestCount,
|
|
8833
|
+
errorCount,
|
|
8834
|
+
complete,
|
|
8835
|
+
minTs,
|
|
8836
|
+
maxTs
|
|
8837
|
+
},
|
|
8838
|
+
filtered: { requestCount: filteredRequestCount, errorCount: filteredErrorCount, complete }
|
|
8839
|
+
};
|
|
8840
|
+
}
|
|
8841
|
+
function mergePersistedStats(previous, appended) {
|
|
8842
|
+
return {
|
|
8843
|
+
version: SIDECAR_VERSION,
|
|
8844
|
+
auditBytes: appended.auditBytes,
|
|
8845
|
+
requestCount: previous.requestCount + appended.requestCount,
|
|
8846
|
+
errorCount: previous.errorCount + appended.errorCount,
|
|
8847
|
+
complete: previous.complete && appended.complete,
|
|
8848
|
+
minTs: previous.minTs === null ? appended.minTs : appended.minTs === null ? previous.minTs : Math.min(previous.minTs, appended.minTs),
|
|
8849
|
+
maxTs: previous.maxTs === null ? appended.maxTs : appended.maxTs === null ? previous.maxTs : Math.max(previous.maxTs, appended.maxTs)
|
|
8850
|
+
};
|
|
8851
|
+
}
|
|
8852
|
+
async function readAuditStats(auditDir, query2 = {}) {
|
|
8853
|
+
if (!(0, import_node_fs18.existsSync)(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
|
|
8854
|
+
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
8855
|
+
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
8856
|
+
let files;
|
|
8857
|
+
try {
|
|
8858
|
+
files = (0, import_node_fs18.readdirSync)(auditDir).filter((file) => AUDIT_FILE_RE.test(file) && fileOverlaps(file, from, to)).sort();
|
|
8859
|
+
} catch {
|
|
8860
|
+
return { requestCount: 0, errorCount: 0, complete: false };
|
|
8861
|
+
}
|
|
8862
|
+
const total = { requestCount: 0, errorCount: 0, complete: true };
|
|
8863
|
+
for (const file of files) {
|
|
8864
|
+
const auditPath = (0, import_node_path11.join)(auditDir, file);
|
|
8865
|
+
try {
|
|
8866
|
+
const auditBytes = (0, import_node_fs18.statSync)(auditPath).size;
|
|
8867
|
+
const statsPath = (0, import_node_path11.join)(auditDir, auditStatsFileName(file));
|
|
8868
|
+
const persisted = readPersisted(statsPath);
|
|
8869
|
+
if (persisted && persisted.complete && persisted.auditBytes === auditBytes && queryCovers(persisted, from, to)) {
|
|
8870
|
+
total.requestCount += persisted.requestCount;
|
|
8871
|
+
total.errorCount += persisted.errorCount;
|
|
8872
|
+
continue;
|
|
8873
|
+
}
|
|
8874
|
+
const resumable = persisted && persisted.complete && persisted.auditBytes < auditBytes && queryCovers(persisted, from, to) ? persisted : null;
|
|
8875
|
+
const scanned = await scanAuditFile(
|
|
8876
|
+
auditPath,
|
|
8877
|
+
resumable?.auditBytes ?? 0,
|
|
8878
|
+
auditBytes,
|
|
8879
|
+
from,
|
|
8880
|
+
to
|
|
8881
|
+
);
|
|
8882
|
+
total.requestCount += scanned.filtered.requestCount + (resumable?.requestCount ?? 0);
|
|
8883
|
+
total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
|
|
8884
|
+
total.complete = total.complete && scanned.filtered.complete;
|
|
8885
|
+
const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
|
|
8886
|
+
if (current.complete) (0, import_node_fs18.writeFileSync)(statsPath, JSON.stringify(current), "utf8");
|
|
8887
|
+
} catch {
|
|
8888
|
+
total.complete = false;
|
|
8889
|
+
}
|
|
8890
|
+
}
|
|
8891
|
+
return total;
|
|
8892
|
+
}
|
|
8893
|
+
|
|
8343
8894
|
// src/audit/AuditPruneSweeper.ts
|
|
8344
8895
|
var DAY_MS = 24 * 60 * 6e4;
|
|
8345
8896
|
var SWEEP_INTERVAL_MS2 = 60 * 6e4;
|
|
8346
8897
|
var AuditPruneSweeper = class {
|
|
8347
|
-
constructor(
|
|
8348
|
-
this.auditDir =
|
|
8898
|
+
constructor(auditDir, logger, config, intervalMs = SWEEP_INTERVAL_MS2, now = Date.now) {
|
|
8899
|
+
this.auditDir = auditDir;
|
|
8349
8900
|
this.logger = logger;
|
|
8350
8901
|
this.config = config;
|
|
8351
8902
|
this.intervalMs = intervalMs;
|
|
@@ -8392,17 +8943,19 @@ var AuditPruneSweeper = class {
|
|
|
8392
8943
|
if (!this.config.enabled || this.sweeping) return 0;
|
|
8393
8944
|
this.sweeping = true;
|
|
8394
8945
|
try {
|
|
8395
|
-
if (!(0,
|
|
8946
|
+
if (!(0, import_node_fs19.existsSync)(this.auditDir)) return 0;
|
|
8396
8947
|
const today = new Date(this.now());
|
|
8397
8948
|
const todayMidnight = new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime();
|
|
8398
8949
|
const cutoff = todayMidnight - (this.config.retentionDays - 1) * DAY_MS;
|
|
8399
8950
|
let removed = 0;
|
|
8400
|
-
for (const file of (0,
|
|
8951
|
+
for (const file of (0, import_node_fs19.readdirSync)(this.auditDir)) {
|
|
8401
8952
|
const dateMs = auditFileDateMs(file);
|
|
8402
8953
|
if (dateMs === null || dateMs >= cutoff) continue;
|
|
8403
8954
|
try {
|
|
8404
|
-
(0,
|
|
8955
|
+
(0, import_node_fs19.unlinkSync)((0, import_node_path12.join)(this.auditDir, file));
|
|
8405
8956
|
removed += 1;
|
|
8957
|
+
const statsPath = (0, import_node_path12.join)(this.auditDir, auditStatsFileName(file));
|
|
8958
|
+
if ((0, import_node_fs19.existsSync)(statsPath)) (0, import_node_fs19.unlinkSync)(statsPath);
|
|
8406
8959
|
} catch (error) {
|
|
8407
8960
|
this.logger.warn("[AuditPruneSweeper] failed to unlink expired audit file", {
|
|
8408
8961
|
file,
|
|
@@ -8424,15 +8977,15 @@ var AuditPruneSweeper = class {
|
|
|
8424
8977
|
};
|
|
8425
8978
|
|
|
8426
8979
|
// src/audit/auditReader.ts
|
|
8427
|
-
var
|
|
8980
|
+
var import_node_fs20 = require("fs");
|
|
8428
8981
|
var import_node_path13 = require("path");
|
|
8429
8982
|
var DEFAULT_LIMIT = 200;
|
|
8430
8983
|
var MAX_LIMIT = 2e3;
|
|
8431
|
-
function readAuditRecords(
|
|
8432
|
-
if (!(0,
|
|
8984
|
+
function readAuditRecords(auditDir, query2 = {}) {
|
|
8985
|
+
if (!(0, import_node_fs20.existsSync)(auditDir)) return [];
|
|
8433
8986
|
let files;
|
|
8434
8987
|
try {
|
|
8435
|
-
files = (0,
|
|
8988
|
+
files = (0, import_node_fs20.readdirSync)(auditDir).filter((f) => AUDIT_FILE_RE.test(f));
|
|
8436
8989
|
} catch {
|
|
8437
8990
|
return [];
|
|
8438
8991
|
}
|
|
@@ -8443,7 +8996,7 @@ function readAuditRecords(auditDir2, query2 = {}) {
|
|
|
8443
8996
|
for (const file of files.sort().reverse()) {
|
|
8444
8997
|
let raw;
|
|
8445
8998
|
try {
|
|
8446
|
-
raw = (0,
|
|
8999
|
+
raw = (0, import_node_fs20.readFileSync)((0, import_node_path13.join)(auditDir, file), "utf8");
|
|
8447
9000
|
} catch {
|
|
8448
9001
|
continue;
|
|
8449
9002
|
}
|
|
@@ -8472,11 +9025,11 @@ function isAuditRecord(value) {
|
|
|
8472
9025
|
}
|
|
8473
9026
|
|
|
8474
9027
|
// src/audit/AuditWriter.ts
|
|
8475
|
-
var
|
|
9028
|
+
var import_node_fs21 = require("fs");
|
|
8476
9029
|
var import_node_path14 = require("path");
|
|
8477
9030
|
var AuditWriter = class {
|
|
8478
|
-
constructor(
|
|
8479
|
-
this.auditDir =
|
|
9031
|
+
constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
|
|
9032
|
+
this.auditDir = auditDir;
|
|
8480
9033
|
this.logger = logger;
|
|
8481
9034
|
this.defer = defer;
|
|
8482
9035
|
}
|
|
@@ -8506,16 +9059,30 @@ var AuditWriter = class {
|
|
|
8506
9059
|
*/
|
|
8507
9060
|
appendNow(record) {
|
|
8508
9061
|
if (!this.dirEnsured) {
|
|
8509
|
-
(0,
|
|
9062
|
+
(0, import_node_fs21.mkdirSync)(this.auditDir, { recursive: true });
|
|
8510
9063
|
this.dirEnsured = true;
|
|
8511
9064
|
}
|
|
8512
9065
|
const file = (0, import_node_path14.join)(this.auditDir, auditFileName(record.ts));
|
|
8513
|
-
|
|
9066
|
+
const line = JSON.stringify(record) + "\n";
|
|
9067
|
+
const auditBytesBefore = (0, import_node_fs21.existsSync)(file) ? (0, import_node_fs21.statSync)(file).size : 0;
|
|
9068
|
+
(0, import_node_fs21.appendFileSync)(file, line, "utf8");
|
|
9069
|
+
try {
|
|
9070
|
+
updateAuditStatsAfterAppend(
|
|
9071
|
+
file,
|
|
9072
|
+
auditBytesBefore,
|
|
9073
|
+
auditBytesBefore + Buffer.byteLength(line, "utf8"),
|
|
9074
|
+
record
|
|
9075
|
+
);
|
|
9076
|
+
} catch (error) {
|
|
9077
|
+
this.logger.warn("[AuditWriter] failed to update audit stats", {
|
|
9078
|
+
error: error instanceof Error ? error.message : String(error)
|
|
9079
|
+
});
|
|
9080
|
+
}
|
|
8514
9081
|
}
|
|
8515
9082
|
};
|
|
8516
9083
|
|
|
8517
9084
|
// src/billing/BillingPublisher.ts
|
|
8518
|
-
var
|
|
9085
|
+
var import_node_fs22 = require("fs");
|
|
8519
9086
|
var import_node_crypto13 = require("crypto");
|
|
8520
9087
|
var import_node_path15 = require("path");
|
|
8521
9088
|
var import_upstreamFetch6 = require("@omnicross/core/pipeline/upstreamFetch");
|
|
@@ -8589,7 +9156,7 @@ var BillingPublisher = class {
|
|
|
8589
9156
|
appendNow(event) {
|
|
8590
9157
|
this.ensureDir();
|
|
8591
9158
|
const file = (0, import_node_path15.join)(this.billingDir, billingFileName(event.ts));
|
|
8592
|
-
(0,
|
|
9159
|
+
(0, import_node_fs22.appendFileSync)(file, JSON.stringify(event) + "\n", "utf8");
|
|
8593
9160
|
}
|
|
8594
9161
|
/**
|
|
8595
9162
|
* One best-effort delivery attempt for an event ALREADY in the ledger. POSTs the
|
|
@@ -8639,7 +9206,7 @@ var BillingPublisher = class {
|
|
|
8639
9206
|
try {
|
|
8640
9207
|
this.ensureDir();
|
|
8641
9208
|
const file = (0, import_node_path15.join)(this.billingDir, deliveredFileName(event.ts));
|
|
8642
|
-
(0,
|
|
9209
|
+
(0, import_node_fs22.appendFileSync)(file, JSON.stringify({ id: event.id, deliveredAt: this.now() }) + "\n", "utf8");
|
|
8643
9210
|
} catch (error) {
|
|
8644
9211
|
this.logger.warn("[BillingPublisher] failed to append delivery marker", {
|
|
8645
9212
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -8648,20 +9215,20 @@ var BillingPublisher = class {
|
|
|
8648
9215
|
}
|
|
8649
9216
|
ensureDir() {
|
|
8650
9217
|
if (this.dirEnsured) return;
|
|
8651
|
-
(0,
|
|
9218
|
+
(0, import_node_fs22.mkdirSync)(this.billingDir, { recursive: true });
|
|
8652
9219
|
this.dirEnsured = true;
|
|
8653
9220
|
}
|
|
8654
9221
|
};
|
|
8655
9222
|
|
|
8656
9223
|
// src/billing/billingReader.ts
|
|
8657
|
-
var
|
|
9224
|
+
var import_node_fs23 = require("fs");
|
|
8658
9225
|
var import_node_path16 = require("path");
|
|
8659
9226
|
function readBillingLedger(billingDir) {
|
|
8660
9227
|
const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
|
|
8661
|
-
if (!(0,
|
|
9228
|
+
if (!(0, import_node_fs23.existsSync)(billingDir)) return view;
|
|
8662
9229
|
let files;
|
|
8663
9230
|
try {
|
|
8664
|
-
files = (0,
|
|
9231
|
+
files = (0, import_node_fs23.readdirSync)(billingDir);
|
|
8665
9232
|
} catch {
|
|
8666
9233
|
return view;
|
|
8667
9234
|
}
|
|
@@ -8692,7 +9259,7 @@ function readBillingStatus(billingDir) {
|
|
|
8692
9259
|
function parseLines(dir, file) {
|
|
8693
9260
|
let raw;
|
|
8694
9261
|
try {
|
|
8695
|
-
raw = (0,
|
|
9262
|
+
raw = (0, import_node_fs23.readFileSync)((0, import_node_path16.join)(dir, file), "utf8");
|
|
8696
9263
|
} catch {
|
|
8697
9264
|
return [];
|
|
8698
9265
|
}
|
|
@@ -9064,7 +9631,7 @@ function buildDaemon(config, paths) {
|
|
|
9064
9631
|
(0, import_outbound_api5.normalizeServerConfig)(decryptedConfig.server).allowanceScheduling
|
|
9065
9632
|
);
|
|
9066
9633
|
const llmConfig = new ConfigFileProviderConfigSource(decryptedConfig);
|
|
9067
|
-
const keyDb = new JsonOutboundKeyDb(paths.keysPath);
|
|
9634
|
+
const keyDb = new JsonOutboundKeyDb(paths.keysPath, secretBox3);
|
|
9068
9635
|
const voucherDb = new JsonVoucherDb(defaultVouchersPath(paths.configPath));
|
|
9069
9636
|
const settingsStore = new JsonApiServerSettingsStore(paths.configPath, secretBox3);
|
|
9070
9637
|
const integrationStateStore = new IntegrationStateStore(
|
|
@@ -9165,7 +9732,7 @@ function buildDaemon(config, paths) {
|
|
|
9165
9732
|
// lines through the injected logger (honors level/format/file sink).
|
|
9166
9733
|
logger
|
|
9167
9734
|
});
|
|
9168
|
-
const
|
|
9735
|
+
const auditDir = defaultAuditDir(paths.configPath);
|
|
9169
9736
|
const billingDir = defaultBillingDir(paths.configPath);
|
|
9170
9737
|
const adminServer = new AdminServer({
|
|
9171
9738
|
configPath: paths.configPath,
|
|
@@ -9199,10 +9766,16 @@ function buildDaemon(config, paths) {
|
|
|
9199
9766
|
// (NOT widening the least-authority writer — no token-returning read reachable).
|
|
9200
9767
|
oauthSessions: new OAuthSessionStore(),
|
|
9201
9768
|
// Real global fetch by default; a test seam (`paths.oauthExchangeFetch`) can
|
|
9202
|
-
// inject a mock so no real token endpoint is hit
|
|
9203
|
-
//
|
|
9204
|
-
//
|
|
9205
|
-
|
|
9769
|
+
// inject a mock so no real token endpoint is hit (one FetchLike for every
|
|
9770
|
+
// provider — the ctx below only matters on the real egress path).
|
|
9771
|
+
//
|
|
9772
|
+
// upstream-proxy: a PER-PROVIDER factory, so the exchange carries the same
|
|
9773
|
+
// `{ providerId }` ctx the CLI login and the token refresh already pass.
|
|
9774
|
+
// Without it the interactive login resolved only the global/env proxy layers
|
|
9775
|
+
// — `server.proxy.byProvider[...]` was silently skipped — and the call was
|
|
9776
|
+
// excluded from the upstream trace, so a failing login left no evidence.
|
|
9777
|
+
// `redactBodies` keeps the code/verifier + minted token out of that trace.
|
|
9778
|
+
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init, { providerId, redactBodies: true }),
|
|
9206
9779
|
subscriptionAccountAppender: credentialStore,
|
|
9207
9780
|
// Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
|
|
9208
9781
|
// + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
|
|
@@ -9254,7 +9827,8 @@ function buildDaemon(config, paths) {
|
|
|
9254
9827
|
// date-rotated audit store. Bound to the store dir here so the AdminServer
|
|
9255
9828
|
// carries no path/store coupling. Records hold IP/UA/bodies → admin-only,
|
|
9256
9829
|
// NEVER unauth, NEVER on `/health`. Routed in `AdminServer` (not `adminApi.ts`).
|
|
9257
|
-
auditReader: (query2) => readAuditRecords(
|
|
9830
|
+
auditReader: (query2) => readAuditRecords(auditDir, query2),
|
|
9831
|
+
auditStatsReader: (query2) => readAuditStats(auditDir, query2),
|
|
9258
9832
|
// billing-event-stream: the AUTHED `GET /admin/api/billing-status` returns the
|
|
9259
9833
|
// secret-free total/delivered/pending counts of the durable ledger.
|
|
9260
9834
|
billingStatusReader: () => readBillingStatus(billingDir)
|
|
@@ -9264,9 +9838,9 @@ function buildDaemon(config, paths) {
|
|
|
9264
9838
|
fetchImpl: (url, init) => (0, import_upstreamFetch8.fetchUpstream)(url, init)
|
|
9265
9839
|
});
|
|
9266
9840
|
setWebhookRuntime(webhookDispatcher, (0, import_SubscriptionAccountHealth3.getSharedAccountHealth)());
|
|
9267
|
-
const auditWriter = new AuditWriter(
|
|
9268
|
-
const auditPruneSweeper = new AuditPruneSweeper(
|
|
9269
|
-
setAuditRuntime(auditWriter, auditPruneSweeper
|
|
9841
|
+
const auditWriter = new AuditWriter(auditDir, logger);
|
|
9842
|
+
const auditPruneSweeper = new AuditPruneSweeper(auditDir, logger, import_audit_types.DEFAULT_AUDIT_CONFIG);
|
|
9843
|
+
setAuditRuntime(auditWriter, auditPruneSweeper);
|
|
9270
9844
|
const billingPublisher = new BillingPublisher(billingDir, logger);
|
|
9271
9845
|
const billingRetrySweeper = new BillingRetrySweeper(
|
|
9272
9846
|
billingDir,
|
|
@@ -9312,8 +9886,8 @@ function buildDaemon(config, paths) {
|
|
|
9312
9886
|
}
|
|
9313
9887
|
function isTokensStoreReadable(tokensPath) {
|
|
9314
9888
|
try {
|
|
9315
|
-
if (!(0,
|
|
9316
|
-
(0,
|
|
9889
|
+
if (!(0, import_node_fs24.existsSync)(tokensPath)) return true;
|
|
9890
|
+
(0, import_node_fs24.accessSync)(tokensPath, import_node_fs24.constants.R_OK);
|
|
9317
9891
|
return true;
|
|
9318
9892
|
} catch {
|
|
9319
9893
|
return false;
|
|
@@ -9361,7 +9935,7 @@ function resolveInPathDefault(candidate) {
|
|
|
9361
9935
|
const segments = (process.env["PATH"] ?? "").split(import_node_path17.delimiter).filter(Boolean);
|
|
9362
9936
|
for (const seg of segments) {
|
|
9363
9937
|
const full = (0, import_node_path17.join)(seg, candidate);
|
|
9364
|
-
if ((0,
|
|
9938
|
+
if ((0, import_node_fs25.existsSync)(full)) return full;
|
|
9365
9939
|
}
|
|
9366
9940
|
return null;
|
|
9367
9941
|
}
|
|
@@ -9563,7 +10137,7 @@ async function runLogin(argv, deps) {
|
|
|
9563
10137
|
(0, import_upstreamFetch9.setUpstreamProxyResolver)(createUpstreamProxyResolver());
|
|
9564
10138
|
try {
|
|
9565
10139
|
const tokensPath = defaultTokensPath(values.config);
|
|
9566
|
-
const exchangeFetch = resolved.tokensFetch ?? ((url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init, { providerId: provider }));
|
|
10140
|
+
const exchangeFetch = resolved.tokensFetch ?? ((url, init) => (0, import_upstreamFetch9.fetchUpstream)(url, init, { providerId: provider, redactBodies: true }));
|
|
9567
10141
|
const store = new JsonSubscriptionCredentialStore(tokensPath, box, exchangeFetch);
|
|
9568
10142
|
const expiresAt = await runProviderLogin(
|
|
9569
10143
|
provider,
|
|
@@ -9849,7 +10423,7 @@ function providersRmKey(configPath, providerId, keyId) {
|
|
|
9849
10423
|
}
|
|
9850
10424
|
|
|
9851
10425
|
// src/commands/secrets.ts
|
|
9852
|
-
var
|
|
10426
|
+
var import_node_fs26 = require("fs");
|
|
9853
10427
|
var import_node_util7 = require("util");
|
|
9854
10428
|
async function runSecrets(argv) {
|
|
9855
10429
|
const { values, positionals } = (0, import_node_util7.parseArgs)({
|
|
@@ -9922,12 +10496,12 @@ function secretsStatus(args) {
|
|
|
9922
10496
|
reportField("admin.token", cfg.admin.token);
|
|
9923
10497
|
}
|
|
9924
10498
|
const tokensPath = defaultTokensPath(args.config);
|
|
9925
|
-
if ((0,
|
|
10499
|
+
if ((0, import_node_fs26.existsSync)(tokensPath)) {
|
|
9926
10500
|
console.info(`Secret status for ${tokensPath}:`);
|
|
9927
10501
|
reportTokenFields(tokensPath);
|
|
9928
10502
|
}
|
|
9929
10503
|
const integrationsPath = defaultIntegrationsPath(args.config);
|
|
9930
|
-
if ((0,
|
|
10504
|
+
if ((0, import_node_fs26.existsSync)(integrationsPath)) {
|
|
9931
10505
|
const state = readRawJson(integrationsPath);
|
|
9932
10506
|
const key = state.gatewayKey;
|
|
9933
10507
|
if (key && typeof key === "object" && !Array.isArray(key)) {
|
|
@@ -9981,8 +10555,8 @@ async function secretsRotate(args) {
|
|
|
9981
10555
|
const integrationsPath = defaultIntegrationsPath(args.config);
|
|
9982
10556
|
try {
|
|
9983
10557
|
cfg = loadConfig(args.config);
|
|
9984
|
-
if ((0,
|
|
9985
|
-
if ((0,
|
|
10558
|
+
if ((0, import_node_fs26.existsSync)(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
|
|
10559
|
+
if ((0, import_node_fs26.existsSync)(integrationsPath)) {
|
|
9986
10560
|
integrationsPlain = new IntegrationStateStore(integrationsPath, oldBox).load();
|
|
9987
10561
|
}
|
|
9988
10562
|
} finally {
|
|
@@ -10017,20 +10591,20 @@ function secretsDecrypt(args) {
|
|
|
10017
10591
|
let tokensPlain = null;
|
|
10018
10592
|
try {
|
|
10019
10593
|
cfg = loadConfig(args.config);
|
|
10020
|
-
if ((0,
|
|
10594
|
+
if ((0, import_node_fs26.existsSync)(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
|
|
10021
10595
|
} finally {
|
|
10022
10596
|
setSecretBox(null);
|
|
10023
10597
|
}
|
|
10024
10598
|
saveConfig(args.config, cfg);
|
|
10025
10599
|
if (tokensPlain) {
|
|
10026
|
-
(0,
|
|
10600
|
+
(0, import_node_fs26.writeFileSync)(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n", "utf8");
|
|
10027
10601
|
}
|
|
10028
10602
|
console.info(`Decrypted secrets to plaintext in ${args.config}` + tokensSuffix(args.config));
|
|
10029
10603
|
}
|
|
10030
10604
|
function readRawConfig(path2) {
|
|
10031
10605
|
let parsed;
|
|
10032
10606
|
try {
|
|
10033
|
-
parsed = JSON.parse((0,
|
|
10607
|
+
parsed = JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
|
|
10034
10608
|
} catch {
|
|
10035
10609
|
throw new Error(`secrets: cannot read or parse '${path2}'`);
|
|
10036
10610
|
}
|
|
@@ -10038,7 +10612,7 @@ function readRawConfig(path2) {
|
|
|
10038
10612
|
}
|
|
10039
10613
|
function readRawJson(path2) {
|
|
10040
10614
|
try {
|
|
10041
|
-
const parsed = JSON.parse((0,
|
|
10615
|
+
const parsed = JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
|
|
10042
10616
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
10043
10617
|
return parsed;
|
|
10044
10618
|
}
|
|
@@ -10048,13 +10622,13 @@ function readRawJson(path2) {
|
|
|
10048
10622
|
}
|
|
10049
10623
|
function encryptTokensFileInPlace(configPath, box) {
|
|
10050
10624
|
const tokensPath = defaultTokensPath(configPath);
|
|
10051
|
-
if (!(0,
|
|
10625
|
+
if (!(0, import_node_fs26.existsSync)(tokensPath)) return;
|
|
10052
10626
|
const plain = decryptTokensFile(tokensPath, box);
|
|
10053
10627
|
writeTokensEncrypted(tokensPath, plain, box);
|
|
10054
10628
|
}
|
|
10055
10629
|
function rewriteIntegrationState(configPath, readBox, writeBox) {
|
|
10056
10630
|
const path2 = defaultIntegrationsPath(configPath);
|
|
10057
|
-
if (!(0,
|
|
10631
|
+
if (!(0, import_node_fs26.existsSync)(path2)) return;
|
|
10058
10632
|
const state = new IntegrationStateStore(path2, readBox).load();
|
|
10059
10633
|
new IntegrationStateStore(path2, writeBox).save(state);
|
|
10060
10634
|
}
|
|
@@ -10067,7 +10641,7 @@ function writeTokensEncrypted(tokensPath, plain, box) {
|
|
|
10067
10641
|
{ updatedAt: "", ...plain },
|
|
10068
10642
|
box
|
|
10069
10643
|
);
|
|
10070
|
-
(0,
|
|
10644
|
+
(0, import_node_fs26.writeFileSync)(tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
|
|
10071
10645
|
}
|
|
10072
10646
|
var TOKEN_FIELDS2 = {
|
|
10073
10647
|
claude: ["accessToken", "refreshToken"],
|
|
@@ -10090,7 +10664,7 @@ function walkTokens(raw, fn) {
|
|
|
10090
10664
|
return next;
|
|
10091
10665
|
}
|
|
10092
10666
|
function tokensSuffix(configPath) {
|
|
10093
|
-
return (0,
|
|
10667
|
+
return (0, import_node_fs26.existsSync)(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
|
|
10094
10668
|
}
|
|
10095
10669
|
|
|
10096
10670
|
// src/commands/start.ts
|