@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.js
CHANGED
|
@@ -1493,10 +1493,20 @@ function assertLoopbackGatewayUrl(value) {
|
|
|
1493
1493
|
// src/ports/JsonOutboundKeyDb.ts
|
|
1494
1494
|
import { existsSync as existsSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
1495
1495
|
var JsonOutboundKeyDb = class {
|
|
1496
|
-
|
|
1496
|
+
/**
|
|
1497
|
+
* @param secretBox OPTIONAL reversible-secret codec. When present, a created
|
|
1498
|
+
* key's plaintext is persisted as a `keySecret` `enc:` envelope (enabling the
|
|
1499
|
+
* operator "view key" affordance via `outboundApiKeysReveal`). When absent the
|
|
1500
|
+
* store stays hash-only (byte-identical to the legacy behavior) and reveal
|
|
1501
|
+
* always returns `null`. Existing 1-arg call sites (tests, lightweight
|
|
1502
|
+
* embedders) keep working.
|
|
1503
|
+
*/
|
|
1504
|
+
constructor(keysPath, secretBox3) {
|
|
1497
1505
|
this.keysPath = keysPath;
|
|
1506
|
+
this.secretBox = secretBox3;
|
|
1498
1507
|
}
|
|
1499
1508
|
keysPath;
|
|
1509
|
+
secretBox;
|
|
1500
1510
|
async outboundApiKeysList() {
|
|
1501
1511
|
return this.readRows();
|
|
1502
1512
|
}
|
|
@@ -1522,10 +1532,27 @@ var JsonOutboundKeyDb = class {
|
|
|
1522
1532
|
allowedEndpoints: input.allowedEndpoints,
|
|
1523
1533
|
loopbackOnly: input.loopbackOnly
|
|
1524
1534
|
};
|
|
1535
|
+
if (input.plaintext && this.secretBox) {
|
|
1536
|
+
row.keySecret = this.secretBox.encrypt(input.plaintext);
|
|
1537
|
+
}
|
|
1525
1538
|
rows.push(row);
|
|
1526
1539
|
this.writeRows(rows);
|
|
1527
1540
|
return row;
|
|
1528
1541
|
}
|
|
1542
|
+
async outboundApiKeysReveal(id) {
|
|
1543
|
+
const rows = this.readRows();
|
|
1544
|
+
const row = rows.find((r) => r.id === id);
|
|
1545
|
+
if (!row || !row.keySecret || !this.secretBox) return null;
|
|
1546
|
+
return this.secretBox.decrypt(row.keySecret);
|
|
1547
|
+
}
|
|
1548
|
+
async outboundApiKeysDelete(id) {
|
|
1549
|
+
const rows = this.readRows();
|
|
1550
|
+
const idx = rows.findIndex((r) => r.id === id);
|
|
1551
|
+
if (idx < 0) return false;
|
|
1552
|
+
rows.splice(idx, 1);
|
|
1553
|
+
this.writeRows(rows);
|
|
1554
|
+
return true;
|
|
1555
|
+
}
|
|
1529
1556
|
async outboundApiKeysRevoke(id) {
|
|
1530
1557
|
return this.mutateRow(id, (row) => {
|
|
1531
1558
|
if (row.revokedAt !== null) return false;
|
|
@@ -1723,7 +1750,7 @@ async function keysRevoke(db, id) {
|
|
|
1723
1750
|
|
|
1724
1751
|
// src/commands/launch.ts
|
|
1725
1752
|
import { spawn as spawn2 } from "child_process";
|
|
1726
|
-
import { existsSync as
|
|
1753
|
+
import { existsSync as existsSync20 } from "fs";
|
|
1727
1754
|
import { delimiter as delimiter2, join as join12 } from "path";
|
|
1728
1755
|
import { parseArgs as parseArgs4 } from "util";
|
|
1729
1756
|
import {
|
|
@@ -1734,7 +1761,7 @@ import {
|
|
|
1734
1761
|
} from "@omnicross/cli-launcher";
|
|
1735
1762
|
|
|
1736
1763
|
// src/bootstrap.ts
|
|
1737
|
-
import { accessSync, constants as fsConstants, existsSync as
|
|
1764
|
+
import { accessSync, constants as fsConstants, existsSync as existsSync19 } from "fs";
|
|
1738
1765
|
import { DEFAULT_AUDIT_CONFIG } from "@omnicross/contracts/audit-types";
|
|
1739
1766
|
import { DEFAULT_BILLING_CONFIG } from "@omnicross/contracts/billing-types";
|
|
1740
1767
|
import { getGeminiCodeAssistProjectResolver } from "@omnicross/core/auth/GeminiCodeAssistProjectResolver";
|
|
@@ -1849,7 +1876,7 @@ async function runCodexLoopback(sessionId, codeVerifier, state, signal, deps) {
|
|
|
1849
1876
|
const code = await deps.codexAwaitLoopback(state, void 0, signal);
|
|
1850
1877
|
const result = await codexOAuth.exchangeCodeForTokens(
|
|
1851
1878
|
{ authorizationCode: code, codeVerifier, state },
|
|
1852
|
-
deps.oauthExchangeFetch
|
|
1879
|
+
deps.oauthExchangeFetch("codex")
|
|
1853
1880
|
);
|
|
1854
1881
|
const expiresAt = new Date(Date.now() + result.expiresIn * 1e3).toISOString();
|
|
1855
1882
|
const block = {
|
|
@@ -2369,6 +2396,17 @@ function handleAuditQuery(req, res, reader) {
|
|
|
2369
2396
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2370
2397
|
res.end(JSON.stringify({ records }));
|
|
2371
2398
|
}
|
|
2399
|
+
async function handleAuditStatsQuery(req, res, reader) {
|
|
2400
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
2401
|
+
const query2 = {};
|
|
2402
|
+
const from = intParam(url.searchParams.get("from"));
|
|
2403
|
+
if (from !== void 0) query2.from = from;
|
|
2404
|
+
const to = intParam(url.searchParams.get("to"));
|
|
2405
|
+
if (to !== void 0) query2.to = to;
|
|
2406
|
+
const stats = reader ? await reader(query2) : { requestCount: 0, errorCount: 0, complete: true };
|
|
2407
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2408
|
+
res.end(JSON.stringify(stats));
|
|
2409
|
+
}
|
|
2372
2410
|
|
|
2373
2411
|
// src/admin/billingStatusApi.ts
|
|
2374
2412
|
function handleBillingStatus(res, reader) {
|
|
@@ -2559,7 +2597,13 @@ function listMappablePresets() {
|
|
|
2559
2597
|
name: preset.name,
|
|
2560
2598
|
apiFormat: resolved.format,
|
|
2561
2599
|
baseUrl: preset.api_base_url,
|
|
2562
|
-
models: Array.isArray(preset.models) ? preset.models : []
|
|
2600
|
+
models: Array.isArray(preset.models) ? preset.models : [],
|
|
2601
|
+
nameKey: preset.nameKey,
|
|
2602
|
+
icon: preset.icon,
|
|
2603
|
+
description: preset.description,
|
|
2604
|
+
features: preset.features,
|
|
2605
|
+
website: preset.website,
|
|
2606
|
+
modelsEndpoint: preset.modelsEndpoint
|
|
2563
2607
|
});
|
|
2564
2608
|
}
|
|
2565
2609
|
return { mappable, excluded };
|
|
@@ -2950,7 +2994,7 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
2950
2994
|
const rawCode = typeof body["code"] === "string" ? body["code"] : "";
|
|
2951
2995
|
if (!sessionId) return err2(400, "oauth complete requires { sessionId }");
|
|
2952
2996
|
if (!rawCode) return err2(400, "oauth complete requires { code }");
|
|
2953
|
-
const session = deps.oauthSessions.
|
|
2997
|
+
const session = deps.oauthSessions.peek(sessionId);
|
|
2954
2998
|
if (!session) return err2(410, "oauth session is unknown, expired, or already used");
|
|
2955
2999
|
if (session.providerId !== providerId) {
|
|
2956
3000
|
return err2(400, `oauth session does not match provider '${providerId}'`);
|
|
@@ -2964,13 +3008,15 @@ async function handleOAuthComplete(providerId, body, deps) {
|
|
|
2964
3008
|
}
|
|
2965
3009
|
code = splitCode;
|
|
2966
3010
|
}
|
|
3011
|
+
const exchangeFetch = deps.oauthExchangeFetch(providerId);
|
|
2967
3012
|
let block;
|
|
2968
3013
|
try {
|
|
2969
|
-
block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state,
|
|
3014
|
+
block = providerId === "claude" ? await exchangeClaude(code, session.codeVerifier, session.state, exchangeFetch) : await exchangeGemini(code, session.codeVerifier, exchangeFetch);
|
|
2970
3015
|
} catch (exchangeError) {
|
|
2971
3016
|
const reason = exchangeError instanceof Error ? exchangeError.message : "token exchange failed";
|
|
2972
3017
|
return err2(502, `oauth token exchange failed for '${providerId}': ${reason}`);
|
|
2973
3018
|
}
|
|
3019
|
+
deps.oauthSessions.consume(sessionId);
|
|
2974
3020
|
const label = typeof body["label"] === "string" && body["label"].trim() ? body["label"].trim() : void 0;
|
|
2975
3021
|
await deps.subscriptionAccountAppender.appendProviderAccount(providerId, block, label);
|
|
2976
3022
|
const status = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
@@ -3220,8 +3266,8 @@ function validateAuditSegment(patch) {
|
|
|
3220
3266
|
}
|
|
3221
3267
|
}
|
|
3222
3268
|
const maxBodyBytes = audit["maxBodyBytes"];
|
|
3223
|
-
if (maxBodyBytes !== void 0 && (typeof maxBodyBytes !== "number" || !Number.isFinite(maxBodyBytes) || maxBodyBytes <
|
|
3224
|
-
errors.push("audit.maxBodyBytes must be a non-negative number");
|
|
3269
|
+
if (maxBodyBytes !== void 0 && (typeof maxBodyBytes !== "number" || !Number.isFinite(maxBodyBytes) || maxBodyBytes < -1)) {
|
|
3270
|
+
errors.push("audit.maxBodyBytes must be -1 or a non-negative number");
|
|
3225
3271
|
}
|
|
3226
3272
|
const retentionDays = audit["retentionDays"];
|
|
3227
3273
|
if (retentionDays !== void 0 && (typeof retentionDays !== "number" || !Number.isFinite(retentionDays) || retentionDays < 0)) {
|
|
@@ -3666,18 +3712,16 @@ function preserveWebhookSecrets(incoming, current) {
|
|
|
3666
3712
|
}
|
|
3667
3713
|
|
|
3668
3714
|
// src/audit/auditRuntime.ts
|
|
3669
|
-
import { join as join5 } from "path";
|
|
3670
3715
|
import { setAuditCaptureConfig, setAuditSink } from "@omnicross/core/pipeline/auditSink";
|
|
3671
3716
|
import { setUpstreamTracePath } from "@omnicross/core/pipeline/upstreamTrace";
|
|
3672
3717
|
var writer = null;
|
|
3673
3718
|
var sweeper = null;
|
|
3674
|
-
|
|
3675
|
-
function setAuditRuntime(w, s, dir) {
|
|
3719
|
+
function setAuditRuntime(w, s) {
|
|
3676
3720
|
writer = w;
|
|
3677
3721
|
sweeper = s;
|
|
3678
|
-
auditDir = dir;
|
|
3679
3722
|
}
|
|
3680
3723
|
function applyAuditConfig(config) {
|
|
3724
|
+
setUpstreamTracePath(null);
|
|
3681
3725
|
const enabled = config?.enabled === true && writer !== null;
|
|
3682
3726
|
if (enabled && config) {
|
|
3683
3727
|
setAuditCaptureConfig(config);
|
|
@@ -3687,11 +3731,9 @@ function applyAuditConfig(config) {
|
|
|
3687
3731
|
sweeper.configure(config);
|
|
3688
3732
|
sweeper.start();
|
|
3689
3733
|
}
|
|
3690
|
-
setUpstreamTracePath(config.captureBodies ? join5(auditDir, "upstream-trace.jsonl") : null);
|
|
3691
3734
|
} else {
|
|
3692
3735
|
setAuditCaptureConfig(null);
|
|
3693
3736
|
setAuditSink(null);
|
|
3694
|
-
setUpstreamTracePath(null);
|
|
3695
3737
|
if (sweeper) {
|
|
3696
3738
|
if (config) sweeper.configure(config);
|
|
3697
3739
|
sweeper.dispose();
|
|
@@ -4534,6 +4576,11 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
4534
4576
|
}
|
|
4535
4577
|
|
|
4536
4578
|
// src/admin/adminApi.ts
|
|
4579
|
+
import {
|
|
4580
|
+
ACCOUNT_ROUTE_ACTIVITY_LIMIT,
|
|
4581
|
+
getSharedAccountRouteActivity
|
|
4582
|
+
} from "@omnicross/core/pipeline/AccountRouteActivity";
|
|
4583
|
+
import { getSharedOverloadCounter } from "@omnicross/core/pipeline/ServerOverloadCounter";
|
|
4537
4584
|
function readBody(req) {
|
|
4538
4585
|
return new Promise((resolve3, reject) => {
|
|
4539
4586
|
const chunks = [];
|
|
@@ -4570,6 +4617,9 @@ function toKeyInfo(row) {
|
|
|
4570
4617
|
id: row.id,
|
|
4571
4618
|
name: row.name,
|
|
4572
4619
|
keyPrefix: row.keyPrefix,
|
|
4620
|
+
// True only when a reversible `keySecret` envelope was persisted at creation
|
|
4621
|
+
// — gates the UI "view key" eye. Legacy hash-only rows read as absent.
|
|
4622
|
+
revealable: Boolean(row.keySecret),
|
|
4573
4623
|
enabled: row.enabled,
|
|
4574
4624
|
createdAt: row.createdAt,
|
|
4575
4625
|
lastUsedAt: row.lastUsedAt,
|
|
@@ -5225,7 +5275,13 @@ function handlePresets(res, method) {
|
|
|
5225
5275
|
name: p.name,
|
|
5226
5276
|
apiFormat: p.apiFormat,
|
|
5227
5277
|
baseUrl: p.baseUrl,
|
|
5228
|
-
models: p.models
|
|
5278
|
+
models: p.models,
|
|
5279
|
+
nameKey: p.nameKey,
|
|
5280
|
+
icon: p.icon,
|
|
5281
|
+
description: p.description,
|
|
5282
|
+
features: p.features,
|
|
5283
|
+
website: p.website,
|
|
5284
|
+
modelsEndpoint: p.modelsEndpoint
|
|
5229
5285
|
}));
|
|
5230
5286
|
return writeJson3(res, 200, { presets, excluded });
|
|
5231
5287
|
}
|
|
@@ -5259,12 +5315,27 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
5259
5315
|
plaintextOnce: created.plaintextOnce
|
|
5260
5316
|
});
|
|
5261
5317
|
}
|
|
5318
|
+
if (method === "GET" && rest.length === 2 && rest[1] === "reveal") {
|
|
5319
|
+
const revealed = await deps.keyDb.outboundApiKeysReveal(rest[0]);
|
|
5320
|
+
if (revealed !== null) return writeJson3(res, 200, { key: revealed });
|
|
5321
|
+
const exists = (await deps.keyDb.outboundApiKeysList()).some((r) => r.id === rest[0]);
|
|
5322
|
+
if (!exists) return writeJsonError(res, 404, `key '${rest[0]}' not found`);
|
|
5323
|
+
return writeJsonError(
|
|
5324
|
+
res,
|
|
5325
|
+
409,
|
|
5326
|
+
`key '${rest[0]}' is not revealable (created before revealable key storage)`
|
|
5327
|
+
);
|
|
5328
|
+
}
|
|
5262
5329
|
const id = rest[0];
|
|
5263
5330
|
const action = rest[1];
|
|
5264
5331
|
if (method === "POST" && id && action === "revoke") {
|
|
5265
5332
|
const ok = await deps.keyDb.outboundApiKeysRevoke(id);
|
|
5266
5333
|
return writeJson3(res, ok ? 200 : 404, { ok });
|
|
5267
5334
|
}
|
|
5335
|
+
if (method === "DELETE" && id && !action) {
|
|
5336
|
+
const ok = await deps.keyDb.outboundApiKeysDelete(id);
|
|
5337
|
+
return writeJson3(res, ok ? 200 : 404, { ok });
|
|
5338
|
+
}
|
|
5268
5339
|
if (method === "POST" && id && action === "enabled") {
|
|
5269
5340
|
const body = await readJsonBody3(req);
|
|
5270
5341
|
const enabled = body["enabled"] === true;
|
|
@@ -5441,6 +5512,40 @@ async function handleServer(req, res, method, deps) {
|
|
|
5441
5512
|
return writeJsonError(res, 405, `method ${method} not allowed on server`);
|
|
5442
5513
|
}
|
|
5443
5514
|
async function handleAccounts(req, res, method, rest, deps) {
|
|
5515
|
+
if (rest[0] === "route-activity" && rest.length === 1) {
|
|
5516
|
+
if (method !== "GET") {
|
|
5517
|
+
return writeJsonError(res, 405, `method ${method} not allowed on account route activity`);
|
|
5518
|
+
}
|
|
5519
|
+
const query2 = requestQuery(req);
|
|
5520
|
+
const parsedLimit = Number(query2.get("limit") ?? "100");
|
|
5521
|
+
const records = getSharedAccountRouteActivity().list({
|
|
5522
|
+
providerId: query2.get("providerId") ?? void 0,
|
|
5523
|
+
accountId: query2.get("accountId") ?? void 0,
|
|
5524
|
+
sessionKey: query2.get("sessionKey") ?? void 0,
|
|
5525
|
+
limit: Number.isFinite(parsedLimit) ? parsedLimit : 100
|
|
5526
|
+
});
|
|
5527
|
+
return writeJson3(res, 200, {
|
|
5528
|
+
available: true,
|
|
5529
|
+
records,
|
|
5530
|
+
capacity: ACCOUNT_ROUTE_ACTIVITY_LIMIT,
|
|
5531
|
+
collectedAt: Date.now()
|
|
5532
|
+
});
|
|
5533
|
+
}
|
|
5534
|
+
if (rest[0] === "overload-counters" && rest.length === 1) {
|
|
5535
|
+
if (method !== "GET") {
|
|
5536
|
+
return writeJsonError(res, 405, `method ${method} not allowed on overload counters`);
|
|
5537
|
+
}
|
|
5538
|
+
const query2 = requestQuery(req);
|
|
5539
|
+
const entries = getSharedOverloadCounter().list({
|
|
5540
|
+
providerId: query2.get("providerId") ?? void 0,
|
|
5541
|
+
accountId: query2.get("accountId") ?? void 0
|
|
5542
|
+
});
|
|
5543
|
+
return writeJson3(res, 200, {
|
|
5544
|
+
available: true,
|
|
5545
|
+
entries,
|
|
5546
|
+
collectedAt: Date.now()
|
|
5547
|
+
});
|
|
5548
|
+
}
|
|
5444
5549
|
if (rest[0] === "allowances") {
|
|
5445
5550
|
return handleAccountAllowanceApi(
|
|
5446
5551
|
req,
|
|
@@ -5587,8 +5692,13 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
5587
5692
|
if (!(listed[providerId] ?? []).some((account) => account.id === accountId)) {
|
|
5588
5693
|
return writeJsonError(res, 404, `account '${accountId}' not found`);
|
|
5589
5694
|
}
|
|
5590
|
-
const result = await deps.accountProbeService.
|
|
5591
|
-
return writeJson3(res, 200, {
|
|
5695
|
+
const result = await deps.accountProbeService.testAccountConnection(providerId, accountId);
|
|
5696
|
+
return writeJson3(res, 200, {
|
|
5697
|
+
ok: result.ok,
|
|
5698
|
+
marked: result.marked,
|
|
5699
|
+
tier: result.tier,
|
|
5700
|
+
model: result.model
|
|
5701
|
+
});
|
|
5592
5702
|
}
|
|
5593
5703
|
if (method === "POST" && rest[2] === "label") {
|
|
5594
5704
|
const accountId = rest[1];
|
|
@@ -5959,7 +6069,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
5959
6069
|
}
|
|
5960
6070
|
|
|
5961
6071
|
// src/admin/version.ts
|
|
5962
|
-
var DAEMON_VERSION = true ? "0.1.
|
|
6072
|
+
var DAEMON_VERSION = true ? "0.1.7" : "0.0.0-dev";
|
|
5963
6073
|
|
|
5964
6074
|
// src/admin/AdminServer.ts
|
|
5965
6075
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -6067,6 +6177,10 @@ var AdminServer = class {
|
|
|
6067
6177
|
handleAuditQuery(req, res, this.deps.auditReader);
|
|
6068
6178
|
return;
|
|
6069
6179
|
}
|
|
6180
|
+
if (path2 === "/admin/api/audit/stats" && (req.method === "GET" || req.method === "HEAD")) {
|
|
6181
|
+
await handleAuditStatsQuery(req, res, this.deps.auditStatsReader);
|
|
6182
|
+
return;
|
|
6183
|
+
}
|
|
6070
6184
|
if (path2 === "/admin/api/billing-status" && (req.method === "GET" || req.method === "HEAD")) {
|
|
6071
6185
|
handleBillingStatus(res, this.deps.billingStatusReader);
|
|
6072
6186
|
return;
|
|
@@ -6183,20 +6297,36 @@ var OAuthSessionStore = class {
|
|
|
6183
6297
|
return sessionId;
|
|
6184
6298
|
}
|
|
6185
6299
|
/**
|
|
6186
|
-
*
|
|
6187
|
-
*
|
|
6188
|
-
* dropped). A `null` return means the completer must reject (no
|
|
6189
|
-
* write).
|
|
6300
|
+
* NON-DESTRUCTIVE lookup: return the session for `sessionId`, or `null` when
|
|
6301
|
+
* it is unknown, already consumed, or past its TTL (an expired entry is
|
|
6302
|
+
* dropped here). A `null` return means the completer must reject (no
|
|
6303
|
+
* exchange, no write).
|
|
6304
|
+
*
|
|
6305
|
+
* Deliberately NOT a consume: the completer peeks, runs the token exchange,
|
|
6306
|
+
* and only {@link consume}s once a token has actually been minted. Consuming
|
|
6307
|
+
* up-front burned the session on EVERY failed exchange (a mistyped/expired
|
|
6308
|
+
* pasted code, a proxy hiccup), so the user's natural retry hit
|
|
6309
|
+
* "session is unknown, expired, or already used" and the login became
|
|
6310
|
+
* unrecoverable without restarting the whole flow.
|
|
6190
6311
|
*/
|
|
6191
|
-
|
|
6312
|
+
peek(sessionId) {
|
|
6192
6313
|
this.sweep();
|
|
6193
6314
|
const session = this.sessions.get(sessionId);
|
|
6194
6315
|
if (!session) return null;
|
|
6195
|
-
|
|
6196
|
-
|
|
6316
|
+
if (Date.now() - session.createdAt > this.ttlMs) {
|
|
6317
|
+
this.sessions.delete(sessionId);
|
|
6318
|
+
return null;
|
|
6319
|
+
}
|
|
6197
6320
|
return session;
|
|
6198
6321
|
}
|
|
6199
|
-
/**
|
|
6322
|
+
/**
|
|
6323
|
+
* SINGLE-USE burn: drop the session so the same `sessionId` can never be
|
|
6324
|
+
* completed twice. Called ONLY after a successful token exchange.
|
|
6325
|
+
*/
|
|
6326
|
+
consume(sessionId) {
|
|
6327
|
+
this.sessions.delete(sessionId);
|
|
6328
|
+
}
|
|
6329
|
+
/** Drop every session past its TTL. Called on each put/peek. */
|
|
6200
6330
|
sweep() {
|
|
6201
6331
|
const now = Date.now();
|
|
6202
6332
|
for (const [id, session] of this.sessions) {
|
|
@@ -6211,6 +6341,10 @@ var LOOPBACK_HOST = "127.0.0.1";
|
|
|
6211
6341
|
var LOOPBACK_PORT = 1455;
|
|
6212
6342
|
var CALLBACK_PATH = "/auth/callback";
|
|
6213
6343
|
var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
6344
|
+
var HTML_HEADERS = {
|
|
6345
|
+
"Content-Type": "text/html",
|
|
6346
|
+
Connection: "close"
|
|
6347
|
+
};
|
|
6214
6348
|
function pageHtml(message) {
|
|
6215
6349
|
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>`;
|
|
6216
6350
|
}
|
|
@@ -6221,30 +6355,31 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
|
|
|
6221
6355
|
if (settled) return;
|
|
6222
6356
|
settled = true;
|
|
6223
6357
|
clearTimeout(timer);
|
|
6224
|
-
|
|
6358
|
+
fn();
|
|
6359
|
+
server2.close();
|
|
6225
6360
|
};
|
|
6226
6361
|
const server = createServer((req, res) => {
|
|
6227
6362
|
const url = new URL(req.url ?? "", `http://${LOOPBACK_HOST}:${LOOPBACK_PORT}`);
|
|
6228
6363
|
if (url.pathname !== CALLBACK_PATH) {
|
|
6229
|
-
res.writeHead(404,
|
|
6364
|
+
res.writeHead(404, HTML_HEADERS);
|
|
6230
6365
|
res.end(pageHtml("Not found"));
|
|
6231
6366
|
return;
|
|
6232
6367
|
}
|
|
6233
6368
|
const code = url.searchParams.get("code");
|
|
6234
6369
|
const state = url.searchParams.get("state");
|
|
6235
6370
|
if (!code) {
|
|
6236
|
-
res.writeHead(400,
|
|
6371
|
+
res.writeHead(400, HTML_HEADERS);
|
|
6237
6372
|
res.end(pageHtml("Login failed: missing authorization code."));
|
|
6238
6373
|
finish(server, () => reject(new Error("login: callback did not include an authorization code")));
|
|
6239
6374
|
return;
|
|
6240
6375
|
}
|
|
6241
6376
|
if (state !== expectedState) {
|
|
6242
|
-
res.writeHead(400,
|
|
6377
|
+
res.writeHead(400, HTML_HEADERS);
|
|
6243
6378
|
res.end(pageHtml("Login failed: state mismatch."));
|
|
6244
6379
|
finish(server, () => reject(new Error("login: callback state did not match (possible CSRF) \u2014 aborting")));
|
|
6245
6380
|
return;
|
|
6246
6381
|
}
|
|
6247
|
-
res.writeHead(200,
|
|
6382
|
+
res.writeHead(200, HTML_HEADERS);
|
|
6248
6383
|
res.end(pageHtml("Login complete."));
|
|
6249
6384
|
finish(server, () => resolve3(code));
|
|
6250
6385
|
});
|
|
@@ -6733,8 +6868,12 @@ var JsonlUsageEventStore = class {
|
|
|
6733
6868
|
reasoningTokens: 0,
|
|
6734
6869
|
costUsd: 0,
|
|
6735
6870
|
costSavedByCacheUsd: 0,
|
|
6736
|
-
eventCount: 0
|
|
6871
|
+
eventCount: 0,
|
|
6872
|
+
cacheEligibleEventCount: 0,
|
|
6873
|
+
coldCacheEventCount: 0,
|
|
6874
|
+
medianCacheHitRate: null
|
|
6737
6875
|
};
|
|
6876
|
+
const perEventHitRates = [];
|
|
6738
6877
|
for (const row of this.readRows(range)) {
|
|
6739
6878
|
totals.inputTokens += row.inputTokens;
|
|
6740
6879
|
totals.outputTokens += row.outputTokens;
|
|
@@ -6744,7 +6883,14 @@ var JsonlUsageEventStore = class {
|
|
|
6744
6883
|
totals.costUsd += row.costUsd;
|
|
6745
6884
|
totals.costSavedByCacheUsd += row.costSavedByCacheUsd;
|
|
6746
6885
|
totals.eventCount += 1;
|
|
6886
|
+
const promptSideTokens = row.inputTokens + row.cacheReadTokens + row.cacheCreationTokens;
|
|
6887
|
+
if (promptSideTokens > 0) {
|
|
6888
|
+
totals.cacheEligibleEventCount += 1;
|
|
6889
|
+
if (row.cacheReadTokens === 0) totals.coldCacheEventCount += 1;
|
|
6890
|
+
perEventHitRates.push(row.cacheReadTokens / promptSideTokens);
|
|
6891
|
+
}
|
|
6747
6892
|
}
|
|
6893
|
+
totals.medianCacheHitRate = median(perEventHitRates);
|
|
6748
6894
|
return totals;
|
|
6749
6895
|
}
|
|
6750
6896
|
async getByModel(range) {
|
|
@@ -6978,6 +7124,15 @@ var NUMERIC_FIELDS = [
|
|
|
6978
7124
|
];
|
|
6979
7125
|
var NULLABLE_STRING_FIELDS = ["messageId", "parentMessageId", "sessionId", "apiKeyId"];
|
|
6980
7126
|
var isStringOrNull = (v) => v === null || typeof v === "string";
|
|
7127
|
+
var CACHE_KEY_SOURCES = /* @__PURE__ */ new Set([
|
|
7128
|
+
"client",
|
|
7129
|
+
"session-header",
|
|
7130
|
+
"thread-header",
|
|
7131
|
+
"body-session-id",
|
|
7132
|
+
"body-thread-id",
|
|
7133
|
+
"content-fingerprint",
|
|
7134
|
+
"none"
|
|
7135
|
+
]);
|
|
6981
7136
|
function isUsageEventRecord(parsed) {
|
|
6982
7137
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
6983
7138
|
const r = parsed;
|
|
@@ -6985,6 +7140,10 @@ function isUsageEventRecord(parsed) {
|
|
|
6985
7140
|
if (typeof r["providerId"] !== "string") return false;
|
|
6986
7141
|
if (typeof r["model"] !== "string") return false;
|
|
6987
7142
|
if (typeof r["engineOrigin"] !== "string") return false;
|
|
7143
|
+
if (r["cacheKeySource"] !== void 0 && (typeof r["cacheKeySource"] !== "string" || !CACHE_KEY_SOURCES.has(r["cacheKeySource"]))) return false;
|
|
7144
|
+
if (r["cacheKeyInjected"] !== void 0 && typeof r["cacheKeyInjected"] !== "boolean") {
|
|
7145
|
+
return false;
|
|
7146
|
+
}
|
|
6988
7147
|
for (const f of NULLABLE_STRING_FIELDS) {
|
|
6989
7148
|
if (!isStringOrNull(r[f])) return false;
|
|
6990
7149
|
}
|
|
@@ -6994,6 +7153,12 @@ function isUsageEventRecord(parsed) {
|
|
|
6994
7153
|
}
|
|
6995
7154
|
return true;
|
|
6996
7155
|
}
|
|
7156
|
+
function median(values) {
|
|
7157
|
+
if (values.length === 0) return null;
|
|
7158
|
+
values.sort((a, b) => a - b);
|
|
7159
|
+
const middle = Math.floor(values.length / 2);
|
|
7160
|
+
return values.length % 2 === 1 ? values[middle] : (values[middle - 1] + values[middle]) / 2;
|
|
7161
|
+
}
|
|
6997
7162
|
|
|
6998
7163
|
// src/ports/JsonPricingStore.ts
|
|
6999
7164
|
import { existsSync as existsSync9, readFileSync as readFileSync10, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
@@ -7409,9 +7574,9 @@ function findDuplicateCredentialIds(accounts) {
|
|
|
7409
7574
|
// src/ports/external-cli-credentials.ts
|
|
7410
7575
|
import { existsSync as existsSync12, readFileSync as readFileSync13 } from "fs";
|
|
7411
7576
|
import { homedir as homedir3 } from "os";
|
|
7412
|
-
import { join as
|
|
7577
|
+
import { join as join5 } from "path";
|
|
7413
7578
|
function externalStorePath(provider, home = homedir3()) {
|
|
7414
|
-
return provider === "claude" ?
|
|
7579
|
+
return provider === "claude" ? join5(home, ".claude", ".credentials.json") : join5(home, ".codex", "auth.json");
|
|
7415
7580
|
}
|
|
7416
7581
|
function decodeJwtExpiryMs(token) {
|
|
7417
7582
|
try {
|
|
@@ -7501,9 +7666,15 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
7501
7666
|
* TEST-injected `fetchImpl` is returned verbatim; otherwise the refresh routes
|
|
7502
7667
|
* through {@link fetchUpstream} with the account's `{ providerId, accountId }`
|
|
7503
7668
|
* ctx so the per-account/provider proxy applies. `@internal` also a test seam.
|
|
7669
|
+
*
|
|
7670
|
+
* `redactBodies` is REQUIRED here: this round-trip sends the refresh_token and
|
|
7671
|
+
* receives a fresh access/refresh token pair. Carrying a `providerId` opts the
|
|
7672
|
+
* call into the upstream trace (so a failing refresh is diagnosable), and the
|
|
7673
|
+
* trace captures bodies verbatim — without this flag every refresh would write
|
|
7674
|
+
* a plaintext token pair into `upstream-trace.jsonl`.
|
|
7504
7675
|
*/
|
|
7505
7676
|
buildRefreshFetch(providerId, accountId) {
|
|
7506
|
-
return this.fetchImpl ?? ((url, init) => fetchUpstream3(url, init, { providerId, accountId }));
|
|
7677
|
+
return this.fetchImpl ?? ((url, init) => fetchUpstream3(url, init, { providerId, accountId, redactBodies: true }));
|
|
7507
7678
|
}
|
|
7508
7679
|
/**
|
|
7509
7680
|
* In-flight refresh coalescing. OAuth refresh tokens are
|
|
@@ -8063,6 +8234,127 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
8063
8234
|
// src/AccountHealthProbeScheduler.ts
|
|
8064
8235
|
import { fetchUpstream as fetchUpstream4 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
8065
8236
|
|
|
8237
|
+
// src/probe/CodexGenerationProbe.ts
|
|
8238
|
+
import {
|
|
8239
|
+
DEFAULT_CODEX_CLI_HEADERS,
|
|
8240
|
+
codexAcceptHeader
|
|
8241
|
+
} from "@omnicross/core/provider-proxy/identity/codexCliHeaders";
|
|
8242
|
+
var CODEX_GENERATION_PROBE_MODEL = "gpt-5.6-luna";
|
|
8243
|
+
var CODEX_GENERATION_PROBE_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
8244
|
+
var MAX_STREAM_BYTES = 256 * 1024;
|
|
8245
|
+
var PROBE_INSTRUCTION = "Return exactly PONG and no other text.";
|
|
8246
|
+
function buildCodexGenerationProbeInit(token, signal) {
|
|
8247
|
+
return {
|
|
8248
|
+
method: "POST",
|
|
8249
|
+
signal,
|
|
8250
|
+
headers: {
|
|
8251
|
+
...DEFAULT_CODEX_CLI_HEADERS,
|
|
8252
|
+
Authorization: `Bearer ${token}`,
|
|
8253
|
+
Accept: codexAcceptHeader(true),
|
|
8254
|
+
"Content-Type": "application/json"
|
|
8255
|
+
},
|
|
8256
|
+
body: JSON.stringify({
|
|
8257
|
+
model: CODEX_GENERATION_PROBE_MODEL,
|
|
8258
|
+
input: [
|
|
8259
|
+
{
|
|
8260
|
+
role: "developer",
|
|
8261
|
+
content: [{ type: "input_text", text: PROBE_INSTRUCTION }]
|
|
8262
|
+
},
|
|
8263
|
+
{
|
|
8264
|
+
role: "user",
|
|
8265
|
+
content: [{ type: "input_text", text: "Connection probe." }]
|
|
8266
|
+
}
|
|
8267
|
+
],
|
|
8268
|
+
// GPT-5.6 otherwise defaults to medium reasoning. A connectivity probe
|
|
8269
|
+
// needs the lowest-cost path and no tool reasoning.
|
|
8270
|
+
reasoning: { effort: "none" },
|
|
8271
|
+
stream: true,
|
|
8272
|
+
store: false
|
|
8273
|
+
})
|
|
8274
|
+
};
|
|
8275
|
+
}
|
|
8276
|
+
async function readCodexGenerationProbeStream(response) {
|
|
8277
|
+
if (!response.body) return { completed: false, outputChars: 0 };
|
|
8278
|
+
const reader = response.body.getReader();
|
|
8279
|
+
const decoder = new TextDecoder();
|
|
8280
|
+
let buffer = "";
|
|
8281
|
+
let bytes = 0;
|
|
8282
|
+
let outputChars = 0;
|
|
8283
|
+
try {
|
|
8284
|
+
while (true) {
|
|
8285
|
+
const { done, value } = await reader.read();
|
|
8286
|
+
if (done) break;
|
|
8287
|
+
bytes += value.byteLength;
|
|
8288
|
+
if (bytes > MAX_STREAM_BYTES) {
|
|
8289
|
+
await reader.cancel();
|
|
8290
|
+
return { completed: false, outputChars };
|
|
8291
|
+
}
|
|
8292
|
+
buffer += decoder.decode(value, { stream: true });
|
|
8293
|
+
buffer = buffer.replace(/\r\n/g, "\n");
|
|
8294
|
+
let boundary = buffer.indexOf("\n\n");
|
|
8295
|
+
while (boundary >= 0) {
|
|
8296
|
+
const block = buffer.slice(0, boundary);
|
|
8297
|
+
buffer = buffer.slice(boundary + 2);
|
|
8298
|
+
const event = parseSseBlock(block);
|
|
8299
|
+
if (event) {
|
|
8300
|
+
const type = event["type"];
|
|
8301
|
+
if (type === "response.output_text.delta" && typeof event["delta"] === "string") {
|
|
8302
|
+
outputChars += event["delta"].length;
|
|
8303
|
+
} else if (type === "response.output_text.done" && typeof event["text"] === "string") {
|
|
8304
|
+
outputChars = Math.max(outputChars, event["text"].length);
|
|
8305
|
+
} else if (type === "response.failed" || type === "error") {
|
|
8306
|
+
await reader.cancel();
|
|
8307
|
+
return { completed: false, outputChars };
|
|
8308
|
+
} else if (type === "response.completed") {
|
|
8309
|
+
const completedResponse = asRecord(event["response"]);
|
|
8310
|
+
const status = completedResponse?.["status"];
|
|
8311
|
+
outputChars = Math.max(outputChars, countCompletedOutputChars(completedResponse));
|
|
8312
|
+
await reader.cancel();
|
|
8313
|
+
return {
|
|
8314
|
+
completed: (status === void 0 || status === "completed") && outputChars > 0,
|
|
8315
|
+
outputChars
|
|
8316
|
+
};
|
|
8317
|
+
}
|
|
8318
|
+
}
|
|
8319
|
+
boundary = buffer.indexOf("\n\n");
|
|
8320
|
+
}
|
|
8321
|
+
}
|
|
8322
|
+
} catch {
|
|
8323
|
+
return { completed: false, outputChars };
|
|
8324
|
+
} finally {
|
|
8325
|
+
reader.releaseLock();
|
|
8326
|
+
}
|
|
8327
|
+
return { completed: false, outputChars };
|
|
8328
|
+
}
|
|
8329
|
+
function parseSseBlock(block) {
|
|
8330
|
+
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
8331
|
+
if (!data || data === "[DONE]") return null;
|
|
8332
|
+
try {
|
|
8333
|
+
return JSON.parse(data);
|
|
8334
|
+
} catch {
|
|
8335
|
+
return null;
|
|
8336
|
+
}
|
|
8337
|
+
}
|
|
8338
|
+
function asRecord(value) {
|
|
8339
|
+
return value !== null && typeof value === "object" ? value : void 0;
|
|
8340
|
+
}
|
|
8341
|
+
function countCompletedOutputChars(response) {
|
|
8342
|
+
const output = response?.["output"];
|
|
8343
|
+
if (!Array.isArray(output)) return 0;
|
|
8344
|
+
let chars = 0;
|
|
8345
|
+
for (const item of output) {
|
|
8346
|
+
const content = asRecord(item)?.["content"];
|
|
8347
|
+
if (!Array.isArray(content)) continue;
|
|
8348
|
+
for (const part of content) {
|
|
8349
|
+
const record = asRecord(part);
|
|
8350
|
+
if (record?.["type"] === "output_text" && typeof record["text"] === "string") {
|
|
8351
|
+
chars += record["text"].length;
|
|
8352
|
+
}
|
|
8353
|
+
}
|
|
8354
|
+
}
|
|
8355
|
+
return chars;
|
|
8356
|
+
}
|
|
8357
|
+
|
|
8066
8358
|
// src/probe/ProbeStrategy.ts
|
|
8067
8359
|
var PROVIDER_PROBE_PLANS = {
|
|
8068
8360
|
claude: {
|
|
@@ -8190,17 +8482,17 @@ var AccountHealthProbeScheduler = class {
|
|
|
8190
8482
|
}
|
|
8191
8483
|
if (readThrew) {
|
|
8192
8484
|
this.record(providerId, accountId, { ts: now, ok: false, status: null, tier: "local" });
|
|
8193
|
-
return { ok: false, marked: false };
|
|
8485
|
+
return { ok: false, marked: false, tier: "local" };
|
|
8194
8486
|
}
|
|
8195
8487
|
if (!token) {
|
|
8196
8488
|
this.health.recordUpstreamOutcome(providerId, accountId, { status: 401, now });
|
|
8197
8489
|
this.record(providerId, accountId, { ts: now, ok: false, status: 401, tier: "local" });
|
|
8198
|
-
return { ok: false, marked: true };
|
|
8490
|
+
return { ok: false, marked: true, tier: "local" };
|
|
8199
8491
|
}
|
|
8200
8492
|
const plan = this.planFor(providerId);
|
|
8201
8493
|
if (plan.kind === "local") {
|
|
8202
8494
|
this.record(providerId, accountId, { ts: now, ok: true, tier: "local" });
|
|
8203
|
-
return { ok: true, marked: false };
|
|
8495
|
+
return { ok: true, marked: false, tier: "local" };
|
|
8204
8496
|
}
|
|
8205
8497
|
const start = this.now();
|
|
8206
8498
|
let status = null;
|
|
@@ -8225,7 +8517,60 @@ var AccountHealthProbeScheduler = class {
|
|
|
8225
8517
|
latencyMs,
|
|
8226
8518
|
tier: "upstream"
|
|
8227
8519
|
});
|
|
8228
|
-
return { ok: status !== null && status < 400, marked };
|
|
8520
|
+
return { ok: status !== null && status < 400, marked, tier: "upstream" };
|
|
8521
|
+
}
|
|
8522
|
+
/**
|
|
8523
|
+
* Manual connection test. Codex performs a real, quota-consuming generation;
|
|
8524
|
+
* every other provider keeps its existing cheap probe. Scheduled sweeps never
|
|
8525
|
+
* call this method, so they remain non-billable.
|
|
8526
|
+
*/
|
|
8527
|
+
async testAccountConnection(providerId, accountId) {
|
|
8528
|
+
if (providerId !== "codex") return this.probeAccount(providerId, accountId);
|
|
8529
|
+
const now = this.now();
|
|
8530
|
+
let token;
|
|
8531
|
+
try {
|
|
8532
|
+
token = await this.store.getAccessTokenForAccount(providerId, accountId);
|
|
8533
|
+
} catch {
|
|
8534
|
+
this.record(providerId, accountId, { ts: now, ok: false, status: null, tier: "local" });
|
|
8535
|
+
return { ok: false, marked: false, tier: "local", model: CODEX_GENERATION_PROBE_MODEL };
|
|
8536
|
+
}
|
|
8537
|
+
if (!token) {
|
|
8538
|
+
this.health.recordUpstreamOutcome(providerId, accountId, { status: 401, now });
|
|
8539
|
+
this.record(providerId, accountId, { ts: now, ok: false, status: 401, tier: "local" });
|
|
8540
|
+
return { ok: false, marked: true, tier: "local", model: CODEX_GENERATION_PROBE_MODEL };
|
|
8541
|
+
}
|
|
8542
|
+
const startedAt = this.now();
|
|
8543
|
+
let attempt = await this.runCodexGenerationAttempt(accountId, token);
|
|
8544
|
+
if (attempt.status === 401 && this.store.refreshAccountToken) {
|
|
8545
|
+
try {
|
|
8546
|
+
if (await this.store.refreshAccountToken(providerId, accountId)) {
|
|
8547
|
+
const refreshed = await this.store.getAccessTokenForAccount(providerId, accountId);
|
|
8548
|
+
if (refreshed) attempt = await this.runCodexGenerationAttempt(accountId, refreshed);
|
|
8549
|
+
}
|
|
8550
|
+
} catch {
|
|
8551
|
+
}
|
|
8552
|
+
}
|
|
8553
|
+
const latencyMs = this.now() - startedAt;
|
|
8554
|
+
const ok = attempt.status !== null && attempt.status >= 200 && attempt.status < 300 && attempt.completed;
|
|
8555
|
+
let marked = false;
|
|
8556
|
+
if (ok) {
|
|
8557
|
+
this.health.clearTransientMark(providerId, accountId);
|
|
8558
|
+
} else if (attempt.status === 401 || attempt.status === 403) {
|
|
8559
|
+
marked = this.applyOutcome(providerId, accountId, attempt.status, attempt.bodyText, now);
|
|
8560
|
+
}
|
|
8561
|
+
this.record(providerId, accountId, {
|
|
8562
|
+
ts: now,
|
|
8563
|
+
ok,
|
|
8564
|
+
status: attempt.status,
|
|
8565
|
+
latencyMs,
|
|
8566
|
+
tier: "generation"
|
|
8567
|
+
});
|
|
8568
|
+
return {
|
|
8569
|
+
ok,
|
|
8570
|
+
marked,
|
|
8571
|
+
tier: "generation",
|
|
8572
|
+
model: CODEX_GENERATION_PROBE_MODEL
|
|
8573
|
+
};
|
|
8229
8574
|
}
|
|
8230
8575
|
/** Per-account rolling history for the authed admin surface (design D5). */
|
|
8231
8576
|
getAllHistory() {
|
|
@@ -8282,6 +8627,24 @@ var AccountHealthProbeScheduler = class {
|
|
|
8282
8627
|
return "";
|
|
8283
8628
|
}
|
|
8284
8629
|
}
|
|
8630
|
+
async runCodexGenerationAttempt(accountId, token) {
|
|
8631
|
+
try {
|
|
8632
|
+
const timeoutMs = Math.max(this.config.timeoutMs, 15e3);
|
|
8633
|
+
const response = await this.fetchImpl(
|
|
8634
|
+
CODEX_GENERATION_PROBE_URL,
|
|
8635
|
+
buildCodexGenerationProbeInit(token, AbortSignal.timeout(timeoutMs)),
|
|
8636
|
+
{ providerId: "codex", accountId, redactBodies: true }
|
|
8637
|
+
);
|
|
8638
|
+
if (response.status < 200 || response.status >= 300) {
|
|
8639
|
+
const bodyText = response.status === 403 ? await this.readBounded(response) : void 0;
|
|
8640
|
+
return { status: response.status, completed: false, bodyText };
|
|
8641
|
+
}
|
|
8642
|
+
const stream = await readCodexGenerationProbeStream(response);
|
|
8643
|
+
return { status: response.status, completed: stream.completed };
|
|
8644
|
+
} catch {
|
|
8645
|
+
return { status: null, completed: false };
|
|
8646
|
+
}
|
|
8647
|
+
}
|
|
8285
8648
|
key(providerId, accountId) {
|
|
8286
8649
|
return `${providerId}${KEY_SEP}${accountId}`;
|
|
8287
8650
|
}
|
|
@@ -8377,7 +8740,7 @@ var AccountHealthSweeper = class {
|
|
|
8377
8740
|
};
|
|
8378
8741
|
|
|
8379
8742
|
// src/audit/AuditPruneSweeper.ts
|
|
8380
|
-
import { existsSync as
|
|
8743
|
+
import { existsSync as existsSync15, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
8381
8744
|
import { join as join7 } from "path";
|
|
8382
8745
|
|
|
8383
8746
|
// src/audit/auditFiles.ts
|
|
@@ -8400,12 +8763,213 @@ function auditFileDateMs(fileName) {
|
|
|
8400
8763
|
return d.getTime();
|
|
8401
8764
|
}
|
|
8402
8765
|
|
|
8766
|
+
// src/audit/auditStats.ts
|
|
8767
|
+
import {
|
|
8768
|
+
createReadStream,
|
|
8769
|
+
existsSync as existsSync14,
|
|
8770
|
+
readFileSync as readFileSync15,
|
|
8771
|
+
readdirSync,
|
|
8772
|
+
statSync as statSync3,
|
|
8773
|
+
writeFileSync as writeFileSync11
|
|
8774
|
+
} from "fs";
|
|
8775
|
+
import { basename, dirname as dirname7, join as join6 } from "path";
|
|
8776
|
+
var SIDECAR_VERSION = 1;
|
|
8777
|
+
var META_PREFIX_BYTES = 64 * 1024;
|
|
8778
|
+
var READ_CHUNK_BYTES = 4 * 1024 * 1024;
|
|
8779
|
+
function auditStatsFileName(auditFile) {
|
|
8780
|
+
return auditFile.replace(/\.jsonl$/, ".stats.json");
|
|
8781
|
+
}
|
|
8782
|
+
function readPersisted(path2) {
|
|
8783
|
+
if (!existsSync14(path2)) return null;
|
|
8784
|
+
try {
|
|
8785
|
+
const value = JSON.parse(readFileSync15(path2, "utf8"));
|
|
8786
|
+
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)) {
|
|
8787
|
+
return null;
|
|
8788
|
+
}
|
|
8789
|
+
return value;
|
|
8790
|
+
} catch {
|
|
8791
|
+
return null;
|
|
8792
|
+
}
|
|
8793
|
+
}
|
|
8794
|
+
function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfter, record) {
|
|
8795
|
+
const statsPath = join6(dirname7(auditPath), auditStatsFileName(basename(auditPath)));
|
|
8796
|
+
const previous = auditBytesBefore === 0 ? {
|
|
8797
|
+
version: SIDECAR_VERSION,
|
|
8798
|
+
auditBytes: 0,
|
|
8799
|
+
requestCount: 0,
|
|
8800
|
+
errorCount: 0,
|
|
8801
|
+
complete: true,
|
|
8802
|
+
minTs: null,
|
|
8803
|
+
maxTs: null
|
|
8804
|
+
} : readPersisted(statsPath);
|
|
8805
|
+
if (!previous || !previous.complete || previous.auditBytes !== auditBytesBefore) return;
|
|
8806
|
+
const next = {
|
|
8807
|
+
version: SIDECAR_VERSION,
|
|
8808
|
+
auditBytes: auditBytesAfter,
|
|
8809
|
+
requestCount: previous.requestCount + 1,
|
|
8810
|
+
errorCount: previous.errorCount + (record.status >= 400 || Boolean(record.error) ? 1 : 0),
|
|
8811
|
+
complete: true,
|
|
8812
|
+
minTs: previous.minTs === null ? record.ts : Math.min(previous.minTs, record.ts),
|
|
8813
|
+
maxTs: previous.maxTs === null ? record.ts : Math.max(previous.maxTs, record.ts)
|
|
8814
|
+
};
|
|
8815
|
+
writeFileSync11(statsPath, JSON.stringify(next), "utf8");
|
|
8816
|
+
}
|
|
8817
|
+
function queryCovers(stats, from, to) {
|
|
8818
|
+
return stats.requestCount === 0 || stats.minTs !== null && stats.maxTs !== null && from <= stats.minTs && to >= stats.maxTs;
|
|
8819
|
+
}
|
|
8820
|
+
function fileOverlaps(file, from, to) {
|
|
8821
|
+
const start = auditFileDateMs(file);
|
|
8822
|
+
if (start === null) return false;
|
|
8823
|
+
const date = new Date(start);
|
|
8824
|
+
const end = new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1).getTime();
|
|
8825
|
+
return end > from && start <= to;
|
|
8826
|
+
}
|
|
8827
|
+
function parseMetadataPrefix(prefix, prefixTruncated) {
|
|
8828
|
+
const text = prefix.toString("utf8");
|
|
8829
|
+
const tsMatch = /(?:^|,)"ts":(-?\d+)/.exec(text);
|
|
8830
|
+
const statusMatch = /(?:^|,)"status":(-?\d+)/.exec(text);
|
|
8831
|
+
const errorMatch = /(?:^|,)"error":"((?:\\.|[^"\\])*)"/.exec(text);
|
|
8832
|
+
const bodyStarted = /,(?:"requestBody"|"responseBody"):/.test(text);
|
|
8833
|
+
return {
|
|
8834
|
+
ts: tsMatch ? Number(tsMatch[1]) : void 0,
|
|
8835
|
+
status: statusMatch ? Number(statusMatch[1]) : void 0,
|
|
8836
|
+
hasError: Boolean(errorMatch?.[1]),
|
|
8837
|
+
complete: Boolean(tsMatch && statusMatch && (!prefixTruncated || bodyStarted))
|
|
8838
|
+
};
|
|
8839
|
+
}
|
|
8840
|
+
async function scanAuditFile(auditPath, startByte, auditBytes, from, to) {
|
|
8841
|
+
let requestCount = 0;
|
|
8842
|
+
let errorCount = 0;
|
|
8843
|
+
let filteredRequestCount = 0;
|
|
8844
|
+
let filteredErrorCount = 0;
|
|
8845
|
+
let minTs = null;
|
|
8846
|
+
let maxTs = null;
|
|
8847
|
+
let complete = true;
|
|
8848
|
+
let prefixParts = [];
|
|
8849
|
+
let prefixBytes = 0;
|
|
8850
|
+
let prefixTruncated = false;
|
|
8851
|
+
const consumeLine = () => {
|
|
8852
|
+
if (prefixBytes === 0 && !prefixTruncated) return;
|
|
8853
|
+
const prefix = Buffer.concat(prefixParts, prefixBytes);
|
|
8854
|
+
const metadata = parseMetadataPrefix(prefix, prefixTruncated);
|
|
8855
|
+
if (!metadata.complete || metadata.ts === void 0 || metadata.status === void 0) {
|
|
8856
|
+
complete = false;
|
|
8857
|
+
} else {
|
|
8858
|
+
requestCount += 1;
|
|
8859
|
+
const isError = metadata.status >= 400 || metadata.hasError;
|
|
8860
|
+
if (isError) errorCount += 1;
|
|
8861
|
+
minTs = minTs === null ? metadata.ts : Math.min(minTs, metadata.ts);
|
|
8862
|
+
maxTs = maxTs === null ? metadata.ts : Math.max(maxTs, metadata.ts);
|
|
8863
|
+
if (metadata.ts >= from && metadata.ts <= to) {
|
|
8864
|
+
filteredRequestCount += 1;
|
|
8865
|
+
if (isError) filteredErrorCount += 1;
|
|
8866
|
+
}
|
|
8867
|
+
}
|
|
8868
|
+
prefixParts = [];
|
|
8869
|
+
prefixBytes = 0;
|
|
8870
|
+
prefixTruncated = false;
|
|
8871
|
+
};
|
|
8872
|
+
if (auditBytes > startByte) {
|
|
8873
|
+
const stream = createReadStream(auditPath, {
|
|
8874
|
+
start: startByte,
|
|
8875
|
+
end: auditBytes - 1,
|
|
8876
|
+
highWaterMark: READ_CHUNK_BYTES
|
|
8877
|
+
});
|
|
8878
|
+
for await (const value of stream) {
|
|
8879
|
+
const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
8880
|
+
let offset = 0;
|
|
8881
|
+
while (offset < chunk.length) {
|
|
8882
|
+
const newline = chunk.indexOf(10, offset);
|
|
8883
|
+
const end = newline === -1 ? chunk.length : newline;
|
|
8884
|
+
if (prefixBytes < META_PREFIX_BYTES) {
|
|
8885
|
+
const retained = Math.min(META_PREFIX_BYTES - prefixBytes, end - offset);
|
|
8886
|
+
if (retained > 0) {
|
|
8887
|
+
prefixParts.push(Buffer.from(chunk.subarray(offset, offset + retained)));
|
|
8888
|
+
prefixBytes += retained;
|
|
8889
|
+
}
|
|
8890
|
+
if (retained < end - offset) prefixTruncated = true;
|
|
8891
|
+
} else if (end > offset) {
|
|
8892
|
+
prefixTruncated = true;
|
|
8893
|
+
}
|
|
8894
|
+
if (newline === -1) break;
|
|
8895
|
+
consumeLine();
|
|
8896
|
+
offset = newline + 1;
|
|
8897
|
+
}
|
|
8898
|
+
}
|
|
8899
|
+
}
|
|
8900
|
+
if (prefixBytes > 0 || prefixTruncated) complete = false;
|
|
8901
|
+
return {
|
|
8902
|
+
all: {
|
|
8903
|
+
version: SIDECAR_VERSION,
|
|
8904
|
+
auditBytes,
|
|
8905
|
+
requestCount,
|
|
8906
|
+
errorCount,
|
|
8907
|
+
complete,
|
|
8908
|
+
minTs,
|
|
8909
|
+
maxTs
|
|
8910
|
+
},
|
|
8911
|
+
filtered: { requestCount: filteredRequestCount, errorCount: filteredErrorCount, complete }
|
|
8912
|
+
};
|
|
8913
|
+
}
|
|
8914
|
+
function mergePersistedStats(previous, appended) {
|
|
8915
|
+
return {
|
|
8916
|
+
version: SIDECAR_VERSION,
|
|
8917
|
+
auditBytes: appended.auditBytes,
|
|
8918
|
+
requestCount: previous.requestCount + appended.requestCount,
|
|
8919
|
+
errorCount: previous.errorCount + appended.errorCount,
|
|
8920
|
+
complete: previous.complete && appended.complete,
|
|
8921
|
+
minTs: previous.minTs === null ? appended.minTs : appended.minTs === null ? previous.minTs : Math.min(previous.minTs, appended.minTs),
|
|
8922
|
+
maxTs: previous.maxTs === null ? appended.maxTs : appended.maxTs === null ? previous.maxTs : Math.max(previous.maxTs, appended.maxTs)
|
|
8923
|
+
};
|
|
8924
|
+
}
|
|
8925
|
+
async function readAuditStats(auditDir, query2 = {}) {
|
|
8926
|
+
if (!existsSync14(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
|
|
8927
|
+
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
8928
|
+
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
8929
|
+
let files;
|
|
8930
|
+
try {
|
|
8931
|
+
files = readdirSync(auditDir).filter((file) => AUDIT_FILE_RE.test(file) && fileOverlaps(file, from, to)).sort();
|
|
8932
|
+
} catch {
|
|
8933
|
+
return { requestCount: 0, errorCount: 0, complete: false };
|
|
8934
|
+
}
|
|
8935
|
+
const total = { requestCount: 0, errorCount: 0, complete: true };
|
|
8936
|
+
for (const file of files) {
|
|
8937
|
+
const auditPath = join6(auditDir, file);
|
|
8938
|
+
try {
|
|
8939
|
+
const auditBytes = statSync3(auditPath).size;
|
|
8940
|
+
const statsPath = join6(auditDir, auditStatsFileName(file));
|
|
8941
|
+
const persisted = readPersisted(statsPath);
|
|
8942
|
+
if (persisted && persisted.complete && persisted.auditBytes === auditBytes && queryCovers(persisted, from, to)) {
|
|
8943
|
+
total.requestCount += persisted.requestCount;
|
|
8944
|
+
total.errorCount += persisted.errorCount;
|
|
8945
|
+
continue;
|
|
8946
|
+
}
|
|
8947
|
+
const resumable = persisted && persisted.complete && persisted.auditBytes < auditBytes && queryCovers(persisted, from, to) ? persisted : null;
|
|
8948
|
+
const scanned = await scanAuditFile(
|
|
8949
|
+
auditPath,
|
|
8950
|
+
resumable?.auditBytes ?? 0,
|
|
8951
|
+
auditBytes,
|
|
8952
|
+
from,
|
|
8953
|
+
to
|
|
8954
|
+
);
|
|
8955
|
+
total.requestCount += scanned.filtered.requestCount + (resumable?.requestCount ?? 0);
|
|
8956
|
+
total.errorCount += scanned.filtered.errorCount + (resumable?.errorCount ?? 0);
|
|
8957
|
+
total.complete = total.complete && scanned.filtered.complete;
|
|
8958
|
+
const current = resumable ? mergePersistedStats(resumable, scanned.all) : scanned.all;
|
|
8959
|
+
if (current.complete) writeFileSync11(statsPath, JSON.stringify(current), "utf8");
|
|
8960
|
+
} catch {
|
|
8961
|
+
total.complete = false;
|
|
8962
|
+
}
|
|
8963
|
+
}
|
|
8964
|
+
return total;
|
|
8965
|
+
}
|
|
8966
|
+
|
|
8403
8967
|
// src/audit/AuditPruneSweeper.ts
|
|
8404
8968
|
var DAY_MS = 24 * 60 * 6e4;
|
|
8405
8969
|
var SWEEP_INTERVAL_MS2 = 60 * 6e4;
|
|
8406
8970
|
var AuditPruneSweeper = class {
|
|
8407
|
-
constructor(
|
|
8408
|
-
this.auditDir =
|
|
8971
|
+
constructor(auditDir, logger, config, intervalMs = SWEEP_INTERVAL_MS2, now = Date.now) {
|
|
8972
|
+
this.auditDir = auditDir;
|
|
8409
8973
|
this.logger = logger;
|
|
8410
8974
|
this.config = config;
|
|
8411
8975
|
this.intervalMs = intervalMs;
|
|
@@ -8452,17 +9016,19 @@ var AuditPruneSweeper = class {
|
|
|
8452
9016
|
if (!this.config.enabled || this.sweeping) return 0;
|
|
8453
9017
|
this.sweeping = true;
|
|
8454
9018
|
try {
|
|
8455
|
-
if (!
|
|
9019
|
+
if (!existsSync15(this.auditDir)) return 0;
|
|
8456
9020
|
const today = new Date(this.now());
|
|
8457
9021
|
const todayMidnight = new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime();
|
|
8458
9022
|
const cutoff = todayMidnight - (this.config.retentionDays - 1) * DAY_MS;
|
|
8459
9023
|
let removed = 0;
|
|
8460
|
-
for (const file of
|
|
9024
|
+
for (const file of readdirSync2(this.auditDir)) {
|
|
8461
9025
|
const dateMs = auditFileDateMs(file);
|
|
8462
9026
|
if (dateMs === null || dateMs >= cutoff) continue;
|
|
8463
9027
|
try {
|
|
8464
9028
|
unlinkSync3(join7(this.auditDir, file));
|
|
8465
9029
|
removed += 1;
|
|
9030
|
+
const statsPath = join7(this.auditDir, auditStatsFileName(file));
|
|
9031
|
+
if (existsSync15(statsPath)) unlinkSync3(statsPath);
|
|
8466
9032
|
} catch (error) {
|
|
8467
9033
|
this.logger.warn("[AuditPruneSweeper] failed to unlink expired audit file", {
|
|
8468
9034
|
file,
|
|
@@ -8484,15 +9050,15 @@ var AuditPruneSweeper = class {
|
|
|
8484
9050
|
};
|
|
8485
9051
|
|
|
8486
9052
|
// src/audit/auditReader.ts
|
|
8487
|
-
import { existsSync as
|
|
9053
|
+
import { existsSync as existsSync16, readdirSync as readdirSync3, readFileSync as readFileSync16 } from "fs";
|
|
8488
9054
|
import { join as join8 } from "path";
|
|
8489
9055
|
var DEFAULT_LIMIT = 200;
|
|
8490
9056
|
var MAX_LIMIT = 2e3;
|
|
8491
|
-
function readAuditRecords(
|
|
8492
|
-
if (!
|
|
9057
|
+
function readAuditRecords(auditDir, query2 = {}) {
|
|
9058
|
+
if (!existsSync16(auditDir)) return [];
|
|
8493
9059
|
let files;
|
|
8494
9060
|
try {
|
|
8495
|
-
files =
|
|
9061
|
+
files = readdirSync3(auditDir).filter((f) => AUDIT_FILE_RE.test(f));
|
|
8496
9062
|
} catch {
|
|
8497
9063
|
return [];
|
|
8498
9064
|
}
|
|
@@ -8503,7 +9069,7 @@ function readAuditRecords(auditDir2, query2 = {}) {
|
|
|
8503
9069
|
for (const file of files.sort().reverse()) {
|
|
8504
9070
|
let raw;
|
|
8505
9071
|
try {
|
|
8506
|
-
raw =
|
|
9072
|
+
raw = readFileSync16(join8(auditDir, file), "utf8");
|
|
8507
9073
|
} catch {
|
|
8508
9074
|
continue;
|
|
8509
9075
|
}
|
|
@@ -8532,11 +9098,11 @@ function isAuditRecord(value) {
|
|
|
8532
9098
|
}
|
|
8533
9099
|
|
|
8534
9100
|
// src/audit/AuditWriter.ts
|
|
8535
|
-
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5 } from "fs";
|
|
9101
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync5, statSync as statSync4 } from "fs";
|
|
8536
9102
|
import { join as join9 } from "path";
|
|
8537
9103
|
var AuditWriter = class {
|
|
8538
|
-
constructor(
|
|
8539
|
-
this.auditDir =
|
|
9104
|
+
constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
|
|
9105
|
+
this.auditDir = auditDir;
|
|
8540
9106
|
this.logger = logger;
|
|
8541
9107
|
this.defer = defer;
|
|
8542
9108
|
}
|
|
@@ -8570,7 +9136,21 @@ var AuditWriter = class {
|
|
|
8570
9136
|
this.dirEnsured = true;
|
|
8571
9137
|
}
|
|
8572
9138
|
const file = join9(this.auditDir, auditFileName(record.ts));
|
|
8573
|
-
|
|
9139
|
+
const line = JSON.stringify(record) + "\n";
|
|
9140
|
+
const auditBytesBefore = existsSync17(file) ? statSync4(file).size : 0;
|
|
9141
|
+
appendFileSync2(file, line, "utf8");
|
|
9142
|
+
try {
|
|
9143
|
+
updateAuditStatsAfterAppend(
|
|
9144
|
+
file,
|
|
9145
|
+
auditBytesBefore,
|
|
9146
|
+
auditBytesBefore + Buffer.byteLength(line, "utf8"),
|
|
9147
|
+
record
|
|
9148
|
+
);
|
|
9149
|
+
} catch (error) {
|
|
9150
|
+
this.logger.warn("[AuditWriter] failed to update audit stats", {
|
|
9151
|
+
error: error instanceof Error ? error.message : String(error)
|
|
9152
|
+
});
|
|
9153
|
+
}
|
|
8574
9154
|
}
|
|
8575
9155
|
};
|
|
8576
9156
|
|
|
@@ -8714,14 +9294,14 @@ var BillingPublisher = class {
|
|
|
8714
9294
|
};
|
|
8715
9295
|
|
|
8716
9296
|
// src/billing/billingReader.ts
|
|
8717
|
-
import { existsSync as
|
|
9297
|
+
import { existsSync as existsSync18, readdirSync as readdirSync4, readFileSync as readFileSync17 } from "fs";
|
|
8718
9298
|
import { join as join11 } from "path";
|
|
8719
9299
|
function readBillingLedger(billingDir) {
|
|
8720
9300
|
const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
|
|
8721
|
-
if (!
|
|
9301
|
+
if (!existsSync18(billingDir)) return view;
|
|
8722
9302
|
let files;
|
|
8723
9303
|
try {
|
|
8724
|
-
files =
|
|
9304
|
+
files = readdirSync4(billingDir);
|
|
8725
9305
|
} catch {
|
|
8726
9306
|
return view;
|
|
8727
9307
|
}
|
|
@@ -8752,7 +9332,7 @@ function readBillingStatus(billingDir) {
|
|
|
8752
9332
|
function parseLines(dir, file) {
|
|
8753
9333
|
let raw;
|
|
8754
9334
|
try {
|
|
8755
|
-
raw =
|
|
9335
|
+
raw = readFileSync17(join11(dir, file), "utf8");
|
|
8756
9336
|
} catch {
|
|
8757
9337
|
return [];
|
|
8758
9338
|
}
|
|
@@ -9124,7 +9704,7 @@ function buildDaemon(config, paths) {
|
|
|
9124
9704
|
normalizeServerConfig(decryptedConfig.server).allowanceScheduling
|
|
9125
9705
|
);
|
|
9126
9706
|
const llmConfig = new ConfigFileProviderConfigSource(decryptedConfig);
|
|
9127
|
-
const keyDb = new JsonOutboundKeyDb(paths.keysPath);
|
|
9707
|
+
const keyDb = new JsonOutboundKeyDb(paths.keysPath, secretBox3);
|
|
9128
9708
|
const voucherDb = new JsonVoucherDb(defaultVouchersPath(paths.configPath));
|
|
9129
9709
|
const settingsStore = new JsonApiServerSettingsStore(paths.configPath, secretBox3);
|
|
9130
9710
|
const integrationStateStore = new IntegrationStateStore(
|
|
@@ -9225,7 +9805,7 @@ function buildDaemon(config, paths) {
|
|
|
9225
9805
|
// lines through the injected logger (honors level/format/file sink).
|
|
9226
9806
|
logger
|
|
9227
9807
|
});
|
|
9228
|
-
const
|
|
9808
|
+
const auditDir = defaultAuditDir(paths.configPath);
|
|
9229
9809
|
const billingDir = defaultBillingDir(paths.configPath);
|
|
9230
9810
|
const adminServer = new AdminServer({
|
|
9231
9811
|
configPath: paths.configPath,
|
|
@@ -9259,10 +9839,16 @@ function buildDaemon(config, paths) {
|
|
|
9259
9839
|
// (NOT widening the least-authority writer — no token-returning read reachable).
|
|
9260
9840
|
oauthSessions: new OAuthSessionStore(),
|
|
9261
9841
|
// Real global fetch by default; a test seam (`paths.oauthExchangeFetch`) can
|
|
9262
|
-
// inject a mock so no real token endpoint is hit
|
|
9263
|
-
//
|
|
9264
|
-
//
|
|
9265
|
-
|
|
9842
|
+
// inject a mock so no real token endpoint is hit (one FetchLike for every
|
|
9843
|
+
// provider — the ctx below only matters on the real egress path).
|
|
9844
|
+
//
|
|
9845
|
+
// upstream-proxy: a PER-PROVIDER factory, so the exchange carries the same
|
|
9846
|
+
// `{ providerId }` ctx the CLI login and the token refresh already pass.
|
|
9847
|
+
// Without it the interactive login resolved only the global/env proxy layers
|
|
9848
|
+
// — `server.proxy.byProvider[...]` was silently skipped — and the call was
|
|
9849
|
+
// excluded from the upstream trace, so a failing login left no evidence.
|
|
9850
|
+
// `redactBodies` keeps the code/verifier + minted token out of that trace.
|
|
9851
|
+
oauthExchangeFetch: paths.oauthExchangeFetch ? () => paths.oauthExchangeFetch : (providerId) => (url, init) => fetchUpstream7(url, init, { providerId, redactBodies: true }),
|
|
9266
9852
|
subscriptionAccountAppender: credentialStore,
|
|
9267
9853
|
// Codex interactive OAuth (app-parity-2 child 5) — the async loopback flow store
|
|
9268
9854
|
// + the one-shot 127.0.0.1:1455 listener. Token captured + persisted daemon-side;
|
|
@@ -9314,7 +9900,8 @@ function buildDaemon(config, paths) {
|
|
|
9314
9900
|
// date-rotated audit store. Bound to the store dir here so the AdminServer
|
|
9315
9901
|
// carries no path/store coupling. Records hold IP/UA/bodies → admin-only,
|
|
9316
9902
|
// NEVER unauth, NEVER on `/health`. Routed in `AdminServer` (not `adminApi.ts`).
|
|
9317
|
-
auditReader: (query2) => readAuditRecords(
|
|
9903
|
+
auditReader: (query2) => readAuditRecords(auditDir, query2),
|
|
9904
|
+
auditStatsReader: (query2) => readAuditStats(auditDir, query2),
|
|
9318
9905
|
// billing-event-stream: the AUTHED `GET /admin/api/billing-status` returns the
|
|
9319
9906
|
// secret-free total/delivered/pending counts of the durable ledger.
|
|
9320
9907
|
billingStatusReader: () => readBillingStatus(billingDir)
|
|
@@ -9324,9 +9911,9 @@ function buildDaemon(config, paths) {
|
|
|
9324
9911
|
fetchImpl: (url, init) => fetchUpstream7(url, init)
|
|
9325
9912
|
});
|
|
9326
9913
|
setWebhookRuntime(webhookDispatcher, getSharedAccountHealth3());
|
|
9327
|
-
const auditWriter = new AuditWriter(
|
|
9328
|
-
const auditPruneSweeper = new AuditPruneSweeper(
|
|
9329
|
-
setAuditRuntime(auditWriter, auditPruneSweeper
|
|
9914
|
+
const auditWriter = new AuditWriter(auditDir, logger);
|
|
9915
|
+
const auditPruneSweeper = new AuditPruneSweeper(auditDir, logger, DEFAULT_AUDIT_CONFIG);
|
|
9916
|
+
setAuditRuntime(auditWriter, auditPruneSweeper);
|
|
9330
9917
|
const billingPublisher = new BillingPublisher(billingDir, logger);
|
|
9331
9918
|
const billingRetrySweeper = new BillingRetrySweeper(
|
|
9332
9919
|
billingDir,
|
|
@@ -9372,7 +9959,7 @@ function buildDaemon(config, paths) {
|
|
|
9372
9959
|
}
|
|
9373
9960
|
function isTokensStoreReadable(tokensPath) {
|
|
9374
9961
|
try {
|
|
9375
|
-
if (!
|
|
9962
|
+
if (!existsSync19(tokensPath)) return true;
|
|
9376
9963
|
accessSync(tokensPath, fsConstants.R_OK);
|
|
9377
9964
|
return true;
|
|
9378
9965
|
} catch {
|
|
@@ -9421,7 +10008,7 @@ function resolveInPathDefault(candidate) {
|
|
|
9421
10008
|
const segments = (process.env["PATH"] ?? "").split(delimiter2).filter(Boolean);
|
|
9422
10009
|
for (const seg of segments) {
|
|
9423
10010
|
const full = join12(seg, candidate);
|
|
9424
|
-
if (
|
|
10011
|
+
if (existsSync20(full)) return full;
|
|
9425
10012
|
}
|
|
9426
10013
|
return null;
|
|
9427
10014
|
}
|
|
@@ -9623,7 +10210,7 @@ async function runLogin(argv, deps) {
|
|
|
9623
10210
|
setUpstreamProxyResolver2(createUpstreamProxyResolver());
|
|
9624
10211
|
try {
|
|
9625
10212
|
const tokensPath = defaultTokensPath(values.config);
|
|
9626
|
-
const exchangeFetch = resolved.tokensFetch ?? ((url, init) => fetchUpstream8(url, init, { providerId: provider }));
|
|
10213
|
+
const exchangeFetch = resolved.tokensFetch ?? ((url, init) => fetchUpstream8(url, init, { providerId: provider, redactBodies: true }));
|
|
9627
10214
|
const store = new JsonSubscriptionCredentialStore(tokensPath, box, exchangeFetch);
|
|
9628
10215
|
const expiresAt = await runProviderLogin(
|
|
9629
10216
|
provider,
|
|
@@ -9909,7 +10496,7 @@ function providersRmKey(configPath, providerId, keyId) {
|
|
|
9909
10496
|
}
|
|
9910
10497
|
|
|
9911
10498
|
// src/commands/secrets.ts
|
|
9912
|
-
import { existsSync as
|
|
10499
|
+
import { existsSync as existsSync21, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "fs";
|
|
9913
10500
|
import { parseArgs as parseArgs7 } from "util";
|
|
9914
10501
|
async function runSecrets(argv) {
|
|
9915
10502
|
const { values, positionals } = parseArgs7({
|
|
@@ -9982,12 +10569,12 @@ function secretsStatus(args) {
|
|
|
9982
10569
|
reportField("admin.token", cfg.admin.token);
|
|
9983
10570
|
}
|
|
9984
10571
|
const tokensPath = defaultTokensPath(args.config);
|
|
9985
|
-
if (
|
|
10572
|
+
if (existsSync21(tokensPath)) {
|
|
9986
10573
|
console.info(`Secret status for ${tokensPath}:`);
|
|
9987
10574
|
reportTokenFields(tokensPath);
|
|
9988
10575
|
}
|
|
9989
10576
|
const integrationsPath = defaultIntegrationsPath(args.config);
|
|
9990
|
-
if (
|
|
10577
|
+
if (existsSync21(integrationsPath)) {
|
|
9991
10578
|
const state = readRawJson(integrationsPath);
|
|
9992
10579
|
const key = state.gatewayKey;
|
|
9993
10580
|
if (key && typeof key === "object" && !Array.isArray(key)) {
|
|
@@ -10041,8 +10628,8 @@ async function secretsRotate(args) {
|
|
|
10041
10628
|
const integrationsPath = defaultIntegrationsPath(args.config);
|
|
10042
10629
|
try {
|
|
10043
10630
|
cfg = loadConfig(args.config);
|
|
10044
|
-
if (
|
|
10045
|
-
if (
|
|
10631
|
+
if (existsSync21(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
|
|
10632
|
+
if (existsSync21(integrationsPath)) {
|
|
10046
10633
|
integrationsPlain = new IntegrationStateStore(integrationsPath, oldBox).load();
|
|
10047
10634
|
}
|
|
10048
10635
|
} finally {
|
|
@@ -10077,20 +10664,20 @@ function secretsDecrypt(args) {
|
|
|
10077
10664
|
let tokensPlain = null;
|
|
10078
10665
|
try {
|
|
10079
10666
|
cfg = loadConfig(args.config);
|
|
10080
|
-
if (
|
|
10667
|
+
if (existsSync21(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
|
|
10081
10668
|
} finally {
|
|
10082
10669
|
setSecretBox(null);
|
|
10083
10670
|
}
|
|
10084
10671
|
saveConfig(args.config, cfg);
|
|
10085
10672
|
if (tokensPlain) {
|
|
10086
|
-
|
|
10673
|
+
writeFileSync12(tokensPath, JSON.stringify(tokensPlain, null, 2) + "\n", "utf8");
|
|
10087
10674
|
}
|
|
10088
10675
|
console.info(`Decrypted secrets to plaintext in ${args.config}` + tokensSuffix(args.config));
|
|
10089
10676
|
}
|
|
10090
10677
|
function readRawConfig(path2) {
|
|
10091
10678
|
let parsed;
|
|
10092
10679
|
try {
|
|
10093
|
-
parsed = JSON.parse(
|
|
10680
|
+
parsed = JSON.parse(readFileSync18(path2, "utf8"));
|
|
10094
10681
|
} catch {
|
|
10095
10682
|
throw new Error(`secrets: cannot read or parse '${path2}'`);
|
|
10096
10683
|
}
|
|
@@ -10098,7 +10685,7 @@ function readRawConfig(path2) {
|
|
|
10098
10685
|
}
|
|
10099
10686
|
function readRawJson(path2) {
|
|
10100
10687
|
try {
|
|
10101
|
-
const parsed = JSON.parse(
|
|
10688
|
+
const parsed = JSON.parse(readFileSync18(path2, "utf8"));
|
|
10102
10689
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
10103
10690
|
return parsed;
|
|
10104
10691
|
}
|
|
@@ -10108,13 +10695,13 @@ function readRawJson(path2) {
|
|
|
10108
10695
|
}
|
|
10109
10696
|
function encryptTokensFileInPlace(configPath, box) {
|
|
10110
10697
|
const tokensPath = defaultTokensPath(configPath);
|
|
10111
|
-
if (!
|
|
10698
|
+
if (!existsSync21(tokensPath)) return;
|
|
10112
10699
|
const plain = decryptTokensFile(tokensPath, box);
|
|
10113
10700
|
writeTokensEncrypted(tokensPath, plain, box);
|
|
10114
10701
|
}
|
|
10115
10702
|
function rewriteIntegrationState(configPath, readBox, writeBox) {
|
|
10116
10703
|
const path2 = defaultIntegrationsPath(configPath);
|
|
10117
|
-
if (!
|
|
10704
|
+
if (!existsSync21(path2)) return;
|
|
10118
10705
|
const state = new IntegrationStateStore(path2, readBox).load();
|
|
10119
10706
|
new IntegrationStateStore(path2, writeBox).save(state);
|
|
10120
10707
|
}
|
|
@@ -10127,7 +10714,7 @@ function writeTokensEncrypted(tokensPath, plain, box) {
|
|
|
10127
10714
|
{ updatedAt: "", ...plain },
|
|
10128
10715
|
box
|
|
10129
10716
|
);
|
|
10130
|
-
|
|
10717
|
+
writeFileSync12(tokensPath, JSON.stringify(encrypted, null, 2) + "\n", "utf8");
|
|
10131
10718
|
}
|
|
10132
10719
|
var TOKEN_FIELDS2 = {
|
|
10133
10720
|
claude: ["accessToken", "refreshToken"],
|
|
@@ -10150,7 +10737,7 @@ function walkTokens(raw, fn) {
|
|
|
10150
10737
|
return next;
|
|
10151
10738
|
}
|
|
10152
10739
|
function tokensSuffix(configPath) {
|
|
10153
|
-
return
|
|
10740
|
+
return existsSync21(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
|
|
10154
10741
|
}
|
|
10155
10742
|
|
|
10156
10743
|
// src/commands/start.ts
|