@omnicross/daemon 0.4.4 → 0.4.5
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 +2015 -993
- package/dist/cli.js +1705 -667
- package/dist/index.cjs +2017 -992
- package/dist/index.d.cts +143 -2
- package/dist/index.d.ts +143 -2
- package/dist/index.js +1712 -674
- package/package.json +6 -6
package/dist/cli.js
CHANGED
|
@@ -1155,7 +1155,7 @@ import {
|
|
|
1155
1155
|
} from "@omnicross/core/search";
|
|
1156
1156
|
|
|
1157
1157
|
// src/bootstrap.ts
|
|
1158
|
-
import { accessSync, constants as fsConstants, existsSync as
|
|
1158
|
+
import { accessSync, constants as fsConstants, existsSync as existsSync31, mkdirSync as mkdirSync9 } from "fs";
|
|
1159
1159
|
import { dirname as dirname17 } from "path";
|
|
1160
1160
|
import { DEFAULT_AUDIT_CONFIG } from "@omnicross/contracts/audit-types";
|
|
1161
1161
|
import { DEFAULT_BILLING_CONFIG } from "@omnicross/contracts/billing-types";
|
|
@@ -1352,14 +1352,14 @@ async function runKimiDevicePoll(sessionId, deviceCode, deviceId, fingerprint, s
|
|
|
1352
1352
|
{
|
|
1353
1353
|
fingerprint,
|
|
1354
1354
|
deadlineMs: DEFAULT_KIMI_OAUTH_TTL_MS,
|
|
1355
|
-
sleep: (ms) => new Promise((
|
|
1355
|
+
sleep: (ms) => new Promise((resolve12, reject) => {
|
|
1356
1356
|
const onAbort = () => {
|
|
1357
1357
|
clearTimeout(timer);
|
|
1358
1358
|
reject(new Error("login: cancelled"));
|
|
1359
1359
|
};
|
|
1360
1360
|
const timer = setTimeout(() => {
|
|
1361
1361
|
signal.removeEventListener("abort", onAbort);
|
|
1362
|
-
|
|
1362
|
+
resolve12();
|
|
1363
1363
|
}, ms);
|
|
1364
1364
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
1365
1365
|
})
|
|
@@ -1435,14 +1435,14 @@ async function runGrokDevicePoll(sessionId, tokenEndpoint, deviceCode, signal, d
|
|
|
1435
1435
|
fetchImpl,
|
|
1436
1436
|
{
|
|
1437
1437
|
deadlineMs: DEFAULT_GROK_OAUTH_TTL_MS,
|
|
1438
|
-
sleep: (ms) => new Promise((
|
|
1438
|
+
sleep: (ms) => new Promise((resolve12, reject) => {
|
|
1439
1439
|
const onAbort = () => {
|
|
1440
1440
|
clearTimeout(timer);
|
|
1441
1441
|
reject(new Error("login: cancelled"));
|
|
1442
1442
|
};
|
|
1443
1443
|
const timer = setTimeout(() => {
|
|
1444
1444
|
signal.removeEventListener("abort", onAbort);
|
|
1445
|
-
|
|
1445
|
+
resolve12();
|
|
1446
1446
|
}, ms);
|
|
1447
1447
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
1448
1448
|
})
|
|
@@ -1520,14 +1520,14 @@ async function runCopilotDevicePoll(sessionId, deviceCode, signal, deps, enterpr
|
|
|
1520
1520
|
{
|
|
1521
1521
|
deadlineMs: DEFAULT_COPILOT_OAUTH_TTL_MS,
|
|
1522
1522
|
...enterpriseUrl ? { enterpriseUrl } : {},
|
|
1523
|
-
sleep: (ms) => new Promise((
|
|
1523
|
+
sleep: (ms) => new Promise((resolve12, reject) => {
|
|
1524
1524
|
const onAbort = () => {
|
|
1525
1525
|
clearTimeout(timer);
|
|
1526
1526
|
reject(new Error("login: cancelled"));
|
|
1527
1527
|
};
|
|
1528
1528
|
const timer = setTimeout(() => {
|
|
1529
1529
|
signal.removeEventListener("abort", onAbort);
|
|
1530
|
-
|
|
1530
|
+
resolve12();
|
|
1531
1531
|
}, ms);
|
|
1532
1532
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
1533
1533
|
})
|
|
@@ -3913,19 +3913,19 @@ function teardown() {
|
|
|
3913
3913
|
|
|
3914
3914
|
// src/admin/webhookTestApi.ts
|
|
3915
3915
|
function readJsonBody(req) {
|
|
3916
|
-
return new Promise((
|
|
3916
|
+
return new Promise((resolve12) => {
|
|
3917
3917
|
const chunks = [];
|
|
3918
3918
|
req.on("data", (c) => chunks.push(c));
|
|
3919
3919
|
req.on("end", () => {
|
|
3920
3920
|
try {
|
|
3921
3921
|
const raw = Buffer.concat(chunks).toString("utf8");
|
|
3922
3922
|
const parsed = raw ? JSON.parse(raw) : {};
|
|
3923
|
-
|
|
3923
|
+
resolve12(parsed && typeof parsed === "object" ? parsed : {});
|
|
3924
3924
|
} catch {
|
|
3925
|
-
|
|
3925
|
+
resolve12({});
|
|
3926
3926
|
}
|
|
3927
3927
|
});
|
|
3928
|
-
req.on("error", () =>
|
|
3928
|
+
req.on("error", () => resolve12({}));
|
|
3929
3929
|
});
|
|
3930
3930
|
}
|
|
3931
3931
|
async function handleWebhookTest(req, res) {
|
|
@@ -4028,13 +4028,771 @@ async function handleRouteLeaseApi(req, res, path2, deps) {
|
|
|
4028
4028
|
}
|
|
4029
4029
|
}
|
|
4030
4030
|
|
|
4031
|
+
// src/admin/codexSessionManager.ts
|
|
4032
|
+
import { homedir as homedir2 } from "os";
|
|
4033
|
+
import { basename, join as join6, resolve, win32 } from "path";
|
|
4034
|
+
import {
|
|
4035
|
+
copyFile,
|
|
4036
|
+
open,
|
|
4037
|
+
readdir,
|
|
4038
|
+
readFile,
|
|
4039
|
+
rename,
|
|
4040
|
+
stat,
|
|
4041
|
+
unlink,
|
|
4042
|
+
writeFile
|
|
4043
|
+
} from "fs/promises";
|
|
4044
|
+
import { existsSync as existsSync5 } from "fs";
|
|
4045
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4046
|
+
import { TextDecoder as TextDecoder2 } from "util";
|
|
4047
|
+
var PROVIDER_PROPERTY_NAMES = /* @__PURE__ */ new Set(["model_provider", "model_provider_id"]);
|
|
4048
|
+
var FIRST_LINE_LIMIT = 4 * 1024 * 1024;
|
|
4049
|
+
var PROVIDER_ID_LIMIT = 256;
|
|
4050
|
+
var CodexSessionManagerError = class extends Error {
|
|
4051
|
+
constructor(message) {
|
|
4052
|
+
super(message);
|
|
4053
|
+
this.name = "CodexSessionManagerError";
|
|
4054
|
+
}
|
|
4055
|
+
};
|
|
4056
|
+
var CodexSessionManager = class {
|
|
4057
|
+
codexHome;
|
|
4058
|
+
stateDatabasePath;
|
|
4059
|
+
mutationTail = Promise.resolve();
|
|
4060
|
+
constructor(options = {}) {
|
|
4061
|
+
const configuredHome = options.codexHome?.trim() || process.env["CODEX_HOME"]?.trim();
|
|
4062
|
+
this.codexHome = configuredHome || join6(homedir2(), ".codex");
|
|
4063
|
+
this.stateDatabasePath = options.stateDatabasePath?.trim() || join6(this.codexHome, "state_5.sqlite");
|
|
4064
|
+
}
|
|
4065
|
+
async list(projectPath) {
|
|
4066
|
+
const project = await validateProjectPath(projectPath);
|
|
4067
|
+
const [files, state] = await Promise.all([
|
|
4068
|
+
scanSessionFiles(join6(this.codexHome, "sessions")),
|
|
4069
|
+
readStateThreads(this.stateDatabasePath)
|
|
4070
|
+
]);
|
|
4071
|
+
return mergeSessionMetadata(project.displayPath, this.codexHome, files, state);
|
|
4072
|
+
}
|
|
4073
|
+
async preview(input) {
|
|
4074
|
+
const normalized2 = normalizeApplyInput(input);
|
|
4075
|
+
const project = await validateProjectPath(normalized2.projectPath);
|
|
4076
|
+
const [files, state] = await Promise.all([
|
|
4077
|
+
scanSessionFiles(join6(this.codexHome, "sessions")),
|
|
4078
|
+
readStateThreads(this.stateDatabasePath)
|
|
4079
|
+
]);
|
|
4080
|
+
const snapshot = mergeSessionMetadata(project.displayPath, this.codexHome, files, state);
|
|
4081
|
+
const plans = await buildProviderPlans(normalized2, snapshot, state.rows);
|
|
4082
|
+
return {
|
|
4083
|
+
projectPath: project.displayPath,
|
|
4084
|
+
fromProvider: normalized2.fromProvider ?? null,
|
|
4085
|
+
toProvider: normalized2.toProvider,
|
|
4086
|
+
stateDatabase: state.status,
|
|
4087
|
+
sessions: plans,
|
|
4088
|
+
warnings: snapshot.warnings
|
|
4089
|
+
};
|
|
4090
|
+
}
|
|
4091
|
+
async apply(input) {
|
|
4092
|
+
return this.withMutationLock(() => this.applyLocked(input));
|
|
4093
|
+
}
|
|
4094
|
+
async applyLocked(input) {
|
|
4095
|
+
const normalized2 = normalizeApplyInput(input);
|
|
4096
|
+
const project = await validateProjectPath(normalized2.projectPath);
|
|
4097
|
+
const [files, state] = await Promise.all([
|
|
4098
|
+
scanSessionFiles(join6(this.codexHome, "sessions")),
|
|
4099
|
+
readStateThreads(this.stateDatabasePath)
|
|
4100
|
+
]);
|
|
4101
|
+
if (!state.status.available) {
|
|
4102
|
+
throw new CodexSessionManagerError(
|
|
4103
|
+
state.status.reason || `Codex state database is unavailable: ${state.status.path}`
|
|
4104
|
+
);
|
|
4105
|
+
}
|
|
4106
|
+
const snapshot = mergeSessionMetadata(project.displayPath, this.codexHome, files, state);
|
|
4107
|
+
const plans = await buildProviderPlans(normalized2, snapshot, state.rows);
|
|
4108
|
+
const selected = plans.filter((plan) => plan.action !== "no_change");
|
|
4109
|
+
const blocked = selected.filter((plan) => plan.action === "blocked");
|
|
4110
|
+
if (blocked.length > 0) {
|
|
4111
|
+
const details = blocked.map((plan) => `${plan.id}: ${plan.reason || "blocked"}`).join("; ");
|
|
4112
|
+
throw new CodexSessionManagerError(`cannot update selected Codex sessions: ${details}`);
|
|
4113
|
+
}
|
|
4114
|
+
if (selected.length === 0) {
|
|
4115
|
+
return {
|
|
4116
|
+
ok: true,
|
|
4117
|
+
projectPath: project.displayPath,
|
|
4118
|
+
fromProvider: normalized2.fromProvider ?? null,
|
|
4119
|
+
toProvider: normalized2.toProvider,
|
|
4120
|
+
updatedSessions: 0,
|
|
4121
|
+
jsonlFiles: 0,
|
|
4122
|
+
jsonlFields: 0,
|
|
4123
|
+
sqliteRows: 0,
|
|
4124
|
+
backups: []
|
|
4125
|
+
};
|
|
4126
|
+
}
|
|
4127
|
+
const fileChanges = [];
|
|
4128
|
+
for (const plan of selected) {
|
|
4129
|
+
const file = files.get(plan.id);
|
|
4130
|
+
if (!file || file.status !== "ready") {
|
|
4131
|
+
throw new CodexSessionManagerError(
|
|
4132
|
+
`rollout for session '${plan.id}' is unavailable; no files were changed`
|
|
4133
|
+
);
|
|
4134
|
+
}
|
|
4135
|
+
const before = await snapshotFile(file.filePath);
|
|
4136
|
+
const inspection = await inspectAndTransformFile(file.filePath, normalized2.fromProvider, normalized2.toProvider);
|
|
4137
|
+
const after = await snapshotFile(file.filePath);
|
|
4138
|
+
if (!sameFileSnapshot(before, after)) {
|
|
4139
|
+
throw new CodexSessionManagerError(
|
|
4140
|
+
`rollout '${file.filePath}' changed while it was being read; no files were changed`
|
|
4141
|
+
);
|
|
4142
|
+
}
|
|
4143
|
+
if (inspection.changedFields > 0) {
|
|
4144
|
+
fileChanges.push({
|
|
4145
|
+
id: plan.id,
|
|
4146
|
+
filePath: file.filePath,
|
|
4147
|
+
before,
|
|
4148
|
+
transformedText: inspection.transformedText,
|
|
4149
|
+
changedFields: inspection.changedFields
|
|
4150
|
+
});
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
const stateRowsById = new Map(state.rows.map((row) => [row.id, row]));
|
|
4154
|
+
const dbChanges = plans.filter((plan) => plan.action === "update").map((plan) => stateRowsById.get(plan.id)).filter((row) => {
|
|
4155
|
+
if (!row) return false;
|
|
4156
|
+
if (normalized2.fromProvider && row.modelProvider !== normalized2.fromProvider) return false;
|
|
4157
|
+
return row.modelProvider !== normalized2.toProvider;
|
|
4158
|
+
});
|
|
4159
|
+
const temporaryFiles = [];
|
|
4160
|
+
const backups = [];
|
|
4161
|
+
let database;
|
|
4162
|
+
let committed = false;
|
|
4163
|
+
try {
|
|
4164
|
+
for (const change of fileChanges) {
|
|
4165
|
+
const tempPath = `${change.filePath}.provider-switch-${randomUUID2()}.tmp`;
|
|
4166
|
+
await writeFile(tempPath, change.transformedText, "utf8");
|
|
4167
|
+
temporaryFiles.push(tempPath);
|
|
4168
|
+
}
|
|
4169
|
+
if (dbChanges.length > 0) {
|
|
4170
|
+
const sqlite = await loadSqlite();
|
|
4171
|
+
database = openDatabase(sqlite, this.stateDatabasePath, false);
|
|
4172
|
+
const databaseBackup = await createDatabaseBackup(sqlite, database, this.stateDatabasePath);
|
|
4173
|
+
backups.push(databaseBackup);
|
|
4174
|
+
database.exec("BEGIN IMMEDIATE");
|
|
4175
|
+
assertDatabaseRowsUnchanged(database, dbChanges);
|
|
4176
|
+
}
|
|
4177
|
+
for (let index = 0; index < fileChanges.length; index += 1) {
|
|
4178
|
+
const change = fileChanges[index];
|
|
4179
|
+
const tempPath = temporaryFiles[index];
|
|
4180
|
+
const current = await snapshotFile(change.filePath);
|
|
4181
|
+
if (!sameFileSnapshot(change.before, current)) {
|
|
4182
|
+
throw new CodexSessionManagerError(
|
|
4183
|
+
`rollout '${change.filePath}' changed before replacement; no files were changed`
|
|
4184
|
+
);
|
|
4185
|
+
}
|
|
4186
|
+
const backupPath = await createUniqueBackupPath(change.filePath);
|
|
4187
|
+
await copyFile(change.filePath, backupPath);
|
|
4188
|
+
backups.push(backupPath);
|
|
4189
|
+
await replaceFileAtomically(tempPath, change.filePath);
|
|
4190
|
+
temporaryFiles[index] = "";
|
|
4191
|
+
}
|
|
4192
|
+
if (database && dbChanges.length > 0) {
|
|
4193
|
+
const update = database.prepare("UPDATE threads SET model_provider = ? WHERE id = ?");
|
|
4194
|
+
for (const row of dbChanges) update.run(normalized2.toProvider, row.id);
|
|
4195
|
+
database.exec("COMMIT");
|
|
4196
|
+
committed = true;
|
|
4197
|
+
} else {
|
|
4198
|
+
committed = true;
|
|
4199
|
+
}
|
|
4200
|
+
return {
|
|
4201
|
+
ok: true,
|
|
4202
|
+
projectPath: project.displayPath,
|
|
4203
|
+
fromProvider: normalized2.fromProvider ?? null,
|
|
4204
|
+
toProvider: normalized2.toProvider,
|
|
4205
|
+
updatedSessions: (/* @__PURE__ */ new Set([...fileChanges.map((change) => change.id), ...dbChanges.map((row) => row.id)])).size,
|
|
4206
|
+
jsonlFiles: fileChanges.length,
|
|
4207
|
+
jsonlFields: fileChanges.reduce((total, change) => total + change.changedFields, 0),
|
|
4208
|
+
sqliteRows: dbChanges.length,
|
|
4209
|
+
backups
|
|
4210
|
+
};
|
|
4211
|
+
} catch (error) {
|
|
4212
|
+
if (database?.isTransaction) {
|
|
4213
|
+
try {
|
|
4214
|
+
database.exec("ROLLBACK");
|
|
4215
|
+
} catch {
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
if (!committed) {
|
|
4219
|
+
try {
|
|
4220
|
+
await restoreBackups(backups.filter((path2) => /\.jsonl\.provider-switch-[^/\\]+\.bak$/iu.test(path2)));
|
|
4221
|
+
} catch (restoreError) {
|
|
4222
|
+
const original = error instanceof Error ? error.message : String(error);
|
|
4223
|
+
const restoration = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
4224
|
+
throw new CodexSessionManagerError(
|
|
4225
|
+
`${original}; JSONL restoration also failed: ${restoration}`
|
|
4226
|
+
);
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
throw error;
|
|
4230
|
+
} finally {
|
|
4231
|
+
if (database?.isOpen) database.close();
|
|
4232
|
+
for (const tempPath of temporaryFiles) {
|
|
4233
|
+
if (!tempPath) continue;
|
|
4234
|
+
await unlinkIfPresent(tempPath);
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
async withMutationLock(operation) {
|
|
4239
|
+
const previous = this.mutationTail;
|
|
4240
|
+
let release;
|
|
4241
|
+
this.mutationTail = new Promise((resolve12) => {
|
|
4242
|
+
release = resolve12;
|
|
4243
|
+
});
|
|
4244
|
+
await previous;
|
|
4245
|
+
try {
|
|
4246
|
+
return await operation();
|
|
4247
|
+
} finally {
|
|
4248
|
+
release();
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
};
|
|
4252
|
+
function normalizeApplyInput(input) {
|
|
4253
|
+
const projectPath = typeof input.projectPath === "string" ? input.projectPath.trim() : "";
|
|
4254
|
+
const toProvider = typeof input.toProvider === "string" ? input.toProvider.trim() : "";
|
|
4255
|
+
const fromProvider = typeof input.fromProvider === "string" ? input.fromProvider.trim() : void 0;
|
|
4256
|
+
if (!projectPath) throw new CodexSessionManagerError("projectPath must be a non-empty string");
|
|
4257
|
+
validateProviderId(toProvider, "toProvider");
|
|
4258
|
+
if (fromProvider) validateProviderId(fromProvider, "fromProvider");
|
|
4259
|
+
if (!Array.isArray(input.sessionIds) || input.sessionIds.length === 0) {
|
|
4260
|
+
throw new CodexSessionManagerError("sessionIds must contain at least one session id");
|
|
4261
|
+
}
|
|
4262
|
+
const sessionIds = [
|
|
4263
|
+
...new Set(
|
|
4264
|
+
input.sessionIds.filter((id) => typeof id === "string" && id.trim().length > 0).map((id) => id.trim())
|
|
4265
|
+
)
|
|
4266
|
+
];
|
|
4267
|
+
if (sessionIds.length === 0) throw new CodexSessionManagerError("sessionIds must contain at least one session id");
|
|
4268
|
+
if (sessionIds.length > 1e4) throw new CodexSessionManagerError("too many sessionIds");
|
|
4269
|
+
return { projectPath, sessionIds, toProvider, ...fromProvider ? { fromProvider } : {} };
|
|
4270
|
+
}
|
|
4271
|
+
function validateProviderId(value, name) {
|
|
4272
|
+
if (!value) throw new CodexSessionManagerError(`${name} must be a non-empty string`);
|
|
4273
|
+
if (value.length > PROVIDER_ID_LIMIT || /[\u0000-\u001f\u007f]/u.test(value)) {
|
|
4274
|
+
throw new CodexSessionManagerError(`${name} is invalid`);
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
async function validateProjectPath(projectPath) {
|
|
4278
|
+
if (typeof projectPath !== "string" || !projectPath.trim()) {
|
|
4279
|
+
throw new CodexSessionManagerError("projectPath must be a non-empty string");
|
|
4280
|
+
}
|
|
4281
|
+
const displayPath = resolve(projectPath.trim());
|
|
4282
|
+
let info;
|
|
4283
|
+
try {
|
|
4284
|
+
info = await stat(displayPath);
|
|
4285
|
+
} catch {
|
|
4286
|
+
throw new CodexSessionManagerError(`project path does not exist: ${displayPath}`);
|
|
4287
|
+
}
|
|
4288
|
+
if (!info.isDirectory()) throw new CodexSessionManagerError(`project path is not a directory: ${displayPath}`);
|
|
4289
|
+
return { displayPath };
|
|
4290
|
+
}
|
|
4291
|
+
async function scanSessionFiles(sessionsRoot) {
|
|
4292
|
+
const files = await collectJsonlFiles(sessionsRoot);
|
|
4293
|
+
const records = /* @__PURE__ */ new Map();
|
|
4294
|
+
for (const filePath of files) {
|
|
4295
|
+
let fileStat;
|
|
4296
|
+
try {
|
|
4297
|
+
fileStat = await stat(filePath);
|
|
4298
|
+
const firstLine = await readFirstLine(filePath);
|
|
4299
|
+
const parsed = parseSessionMetadata(firstLine, filePath);
|
|
4300
|
+
if (!parsed.id) continue;
|
|
4301
|
+
const record = {
|
|
4302
|
+
id: parsed.id,
|
|
4303
|
+
filePath,
|
|
4304
|
+
cwd: parsed.cwd,
|
|
4305
|
+
jsonlProvider: parsed.provider,
|
|
4306
|
+
timestamp: parsed.timestamp,
|
|
4307
|
+
size: fileStat.size,
|
|
4308
|
+
mtimeMs: fileStat.mtimeMs,
|
|
4309
|
+
status: "ready"
|
|
4310
|
+
};
|
|
4311
|
+
const existing = records.get(record.id);
|
|
4312
|
+
if (!existing || record.mtimeMs > existing.mtimeMs) records.set(record.id, record);
|
|
4313
|
+
} catch {
|
|
4314
|
+
const id = sessionIdFromRolloutPath(filePath);
|
|
4315
|
+
if (!id) continue;
|
|
4316
|
+
let fallbackStat;
|
|
4317
|
+
try {
|
|
4318
|
+
fallbackStat = await stat(filePath);
|
|
4319
|
+
} catch {
|
|
4320
|
+
continue;
|
|
4321
|
+
}
|
|
4322
|
+
const existing = records.get(id);
|
|
4323
|
+
if (!existing || fallbackStat.mtimeMs > existing.mtimeMs) {
|
|
4324
|
+
records.set(id, {
|
|
4325
|
+
id,
|
|
4326
|
+
filePath,
|
|
4327
|
+
cwd: null,
|
|
4328
|
+
jsonlProvider: null,
|
|
4329
|
+
timestamp: null,
|
|
4330
|
+
size: fallbackStat.size,
|
|
4331
|
+
mtimeMs: fallbackStat.mtimeMs,
|
|
4332
|
+
status: "unreadable_rollout"
|
|
4333
|
+
});
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
4337
|
+
return records;
|
|
4338
|
+
}
|
|
4339
|
+
async function collectJsonlFiles(root) {
|
|
4340
|
+
if (!existsSync5(root)) return [];
|
|
4341
|
+
const result = [];
|
|
4342
|
+
const pending = [root];
|
|
4343
|
+
while (pending.length > 0) {
|
|
4344
|
+
const current = pending.pop();
|
|
4345
|
+
let entries;
|
|
4346
|
+
try {
|
|
4347
|
+
entries = await readdir(current, { withFileTypes: true });
|
|
4348
|
+
} catch {
|
|
4349
|
+
continue;
|
|
4350
|
+
}
|
|
4351
|
+
for (const entry of entries) {
|
|
4352
|
+
const fullPath = join6(current, entry.name);
|
|
4353
|
+
if (entry.isDirectory()) pending.push(fullPath);
|
|
4354
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith(".jsonl")) result.push(fullPath);
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
return result.sort((a, b) => a.localeCompare(b));
|
|
4358
|
+
}
|
|
4359
|
+
async function readFirstLine(filePath) {
|
|
4360
|
+
const handle = await open(filePath, "r");
|
|
4361
|
+
const chunks = [];
|
|
4362
|
+
let total = 0;
|
|
4363
|
+
try {
|
|
4364
|
+
while (total < FIRST_LINE_LIMIT) {
|
|
4365
|
+
const chunk = Buffer.allocUnsafe(Math.min(128 * 1024, FIRST_LINE_LIMIT - total));
|
|
4366
|
+
const result = await handle.read(chunk, 0, chunk.length, null);
|
|
4367
|
+
if (result.bytesRead === 0) break;
|
|
4368
|
+
const used = chunk.subarray(0, result.bytesRead);
|
|
4369
|
+
const lineEnd = findLineEnd(used);
|
|
4370
|
+
if (lineEnd >= 0) {
|
|
4371
|
+
chunks.push(used.subarray(0, lineEnd));
|
|
4372
|
+
return decodeUtf8(Buffer.concat(chunks));
|
|
4373
|
+
}
|
|
4374
|
+
chunks.push(used);
|
|
4375
|
+
total += result.bytesRead;
|
|
4376
|
+
}
|
|
4377
|
+
} finally {
|
|
4378
|
+
await handle.close();
|
|
4379
|
+
}
|
|
4380
|
+
if (total > 0 && total < FIRST_LINE_LIMIT) return decodeUtf8(Buffer.concat(chunks));
|
|
4381
|
+
throw new CodexSessionManagerError(`session metadata line is too large or incomplete: ${filePath}`);
|
|
4382
|
+
}
|
|
4383
|
+
function findLineEnd(buffer) {
|
|
4384
|
+
for (let index = 0; index < buffer.length; index += 1) {
|
|
4385
|
+
if (buffer[index] === 10 || buffer[index] === 13) return index;
|
|
4386
|
+
}
|
|
4387
|
+
return -1;
|
|
4388
|
+
}
|
|
4389
|
+
function parseSessionMetadata(line, filePath) {
|
|
4390
|
+
const value = JSON.parse(line.replace(/^\uFEFF/u, ""));
|
|
4391
|
+
if (!isRecord6(value)) throw new Error("metadata is not an object");
|
|
4392
|
+
const payload = isRecord6(value["payload"]) ? value["payload"] : {};
|
|
4393
|
+
const id = sessionIdFromRolloutPath(filePath) || firstString(payload["session_id"], payload["id"]);
|
|
4394
|
+
return {
|
|
4395
|
+
id,
|
|
4396
|
+
cwd: firstString(payload["cwd"]),
|
|
4397
|
+
provider: firstString(payload["model_provider"]),
|
|
4398
|
+
timestamp: firstString(payload["timestamp"]) || firstString(value["timestamp"])
|
|
4399
|
+
};
|
|
4400
|
+
}
|
|
4401
|
+
function sessionIdFromRolloutPath(filePath) {
|
|
4402
|
+
const match = basename(filePath).match(/([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})\.jsonl$/iu);
|
|
4403
|
+
return match?.[1] ?? null;
|
|
4404
|
+
}
|
|
4405
|
+
async function readStateThreads(databasePath) {
|
|
4406
|
+
const unavailable = (reason) => ({
|
|
4407
|
+
status: { path: databasePath, available: false, reason },
|
|
4408
|
+
rows: []
|
|
4409
|
+
});
|
|
4410
|
+
if (!existsSync5(databasePath)) return unavailable(`Codex state database not found: ${databasePath}`);
|
|
4411
|
+
let sqlite;
|
|
4412
|
+
try {
|
|
4413
|
+
sqlite = await loadSqlite();
|
|
4414
|
+
} catch (error) {
|
|
4415
|
+
return unavailable(error instanceof Error ? error.message : String(error));
|
|
4416
|
+
}
|
|
4417
|
+
let database;
|
|
4418
|
+
try {
|
|
4419
|
+
database = openDatabase(sqlite, databasePath, true);
|
|
4420
|
+
const columns = new Set(
|
|
4421
|
+
database.prepare("PRAGMA table_info('threads')").all().map((row) => String(row["name"] ?? ""))
|
|
4422
|
+
);
|
|
4423
|
+
for (const required of ["id", "rollout_path", "cwd", "model_provider"]) {
|
|
4424
|
+
if (!columns.has(required)) return unavailable(`state_5.sqlite is missing threads.${required}`);
|
|
4425
|
+
}
|
|
4426
|
+
const selected = ["id", "rollout_path", "cwd", "model_provider", "model", "created_at", "updated_at", "created_at_ms", "updated_at_ms"].filter((column) => columns.has(column));
|
|
4427
|
+
const rows = database.prepare(`SELECT ${selected.join(", ")} FROM threads`).all();
|
|
4428
|
+
return {
|
|
4429
|
+
status: { path: databasePath, available: true },
|
|
4430
|
+
rows: rows.map(toStateThreadRow)
|
|
4431
|
+
};
|
|
4432
|
+
} catch (error) {
|
|
4433
|
+
return unavailable(`could not read Codex state database: ${error instanceof Error ? error.message : String(error)}`);
|
|
4434
|
+
} finally {
|
|
4435
|
+
if (database?.isOpen) database.close();
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
async function loadSqlite() {
|
|
4439
|
+
const specifier = "node:sqlite";
|
|
4440
|
+
try {
|
|
4441
|
+
return await import(specifier);
|
|
4442
|
+
} catch (error) {
|
|
4443
|
+
const cause = error instanceof Error ? error.message : String(error);
|
|
4444
|
+
throw new CodexSessionManagerError(
|
|
4445
|
+
`Codex session SQLite support requires Node.js 22.16 or newer (node:sqlite); this daemon runs ${process.version} (import failed: ${cause})`
|
|
4446
|
+
);
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
function openDatabase(sqlite, databasePath, readOnly) {
|
|
4450
|
+
const database = new sqlite.DatabaseSync(databasePath, { readOnly, timeout: 5e3 });
|
|
4451
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
4452
|
+
return database;
|
|
4453
|
+
}
|
|
4454
|
+
function toStateThreadRow(row) {
|
|
4455
|
+
return {
|
|
4456
|
+
id: String(row["id"] ?? ""),
|
|
4457
|
+
rolloutPath: String(row["rollout_path"] ?? ""),
|
|
4458
|
+
cwd: String(row["cwd"] ?? ""),
|
|
4459
|
+
modelProvider: String(row["model_provider"] ?? ""),
|
|
4460
|
+
model: row["model"] == null ? null : String(row["model"]),
|
|
4461
|
+
createdAt: timestampFromSqlite(row["created_at_ms"] ?? row["created_at"]),
|
|
4462
|
+
updatedAt: timestampFromSqlite(row["updated_at_ms"] ?? row["updated_at"])
|
|
4463
|
+
};
|
|
4464
|
+
}
|
|
4465
|
+
function timestampFromSqlite(value) {
|
|
4466
|
+
const numeric = typeof value === "bigint" ? Number(value) : typeof value === "number" ? value : Number(value);
|
|
4467
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return null;
|
|
4468
|
+
const milliseconds = numeric < 1e11 ? numeric * 1e3 : numeric;
|
|
4469
|
+
const date = new Date(milliseconds);
|
|
4470
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
4471
|
+
}
|
|
4472
|
+
function mergeSessionMetadata(projectPath, codexHome, files, state) {
|
|
4473
|
+
const stateRows = new Map(state.rows.map((row) => [row.id, row]));
|
|
4474
|
+
const ids = /* @__PURE__ */ new Set();
|
|
4475
|
+
for (const [id, file] of files) {
|
|
4476
|
+
if (file.cwd && isPathInside(projectPath, file.cwd)) ids.add(id);
|
|
4477
|
+
}
|
|
4478
|
+
for (const row of state.rows) {
|
|
4479
|
+
if (isPathInside(projectPath, row.cwd)) ids.add(row.id);
|
|
4480
|
+
}
|
|
4481
|
+
const sessions2 = [];
|
|
4482
|
+
for (const id of ids) {
|
|
4483
|
+
const file = files.get(id);
|
|
4484
|
+
const row = stateRows.get(id);
|
|
4485
|
+
const cwd = row?.cwd || file?.cwd || projectPath;
|
|
4486
|
+
if (!isPathInside(projectPath, cwd)) continue;
|
|
4487
|
+
const rolloutPath = file?.filePath || row?.rolloutPath || "";
|
|
4488
|
+
const provider = row?.modelProvider || file?.jsonlProvider || null;
|
|
4489
|
+
sessions2.push({
|
|
4490
|
+
id,
|
|
4491
|
+
cwd,
|
|
4492
|
+
rolloutPath,
|
|
4493
|
+
provider,
|
|
4494
|
+
jsonlProvider: file?.jsonlProvider ?? null,
|
|
4495
|
+
model: row?.model ?? null,
|
|
4496
|
+
createdAt: row?.createdAt ?? file?.timestamp ?? null,
|
|
4497
|
+
updatedAt: row?.updatedAt ?? (file ? new Date(file.mtimeMs).toISOString() : null),
|
|
4498
|
+
fileSize: file?.size ?? null,
|
|
4499
|
+
fileModifiedAt: file ? new Date(file.mtimeMs).toISOString() : null,
|
|
4500
|
+
status: file?.status ?? "missing_rollout",
|
|
4501
|
+
inStateDatabase: Boolean(row)
|
|
4502
|
+
});
|
|
4503
|
+
}
|
|
4504
|
+
sessions2.sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? "") || a.id.localeCompare(b.id));
|
|
4505
|
+
const warnings = [];
|
|
4506
|
+
if (!state.status.available) warnings.push(state.status.reason || "state database unavailable");
|
|
4507
|
+
return { projectPath, codexHome, stateDatabase: state.status, sessions: sessions2, warnings };
|
|
4508
|
+
}
|
|
4509
|
+
async function buildProviderPlans(input, snapshot, stateRows) {
|
|
4510
|
+
const byId = new Map(snapshot.sessions.map((session) => [session.id, session]));
|
|
4511
|
+
const stateById = new Map(stateRows.map((row) => [row.id, row]));
|
|
4512
|
+
const plans = [];
|
|
4513
|
+
for (const id of input.sessionIds) {
|
|
4514
|
+
const session = byId.get(id);
|
|
4515
|
+
const row = stateById.get(id);
|
|
4516
|
+
if (!session) {
|
|
4517
|
+
plans.push({
|
|
4518
|
+
id,
|
|
4519
|
+
provider: null,
|
|
4520
|
+
model: null,
|
|
4521
|
+
rolloutPath: "",
|
|
4522
|
+
status: "blocked",
|
|
4523
|
+
providers: [],
|
|
4524
|
+
matchingFields: 0,
|
|
4525
|
+
changedFields: 0,
|
|
4526
|
+
sqliteWillUpdate: false,
|
|
4527
|
+
action: "blocked",
|
|
4528
|
+
reason: "session is not associated with the requested project"
|
|
4529
|
+
});
|
|
4530
|
+
continue;
|
|
4531
|
+
}
|
|
4532
|
+
if (session.status !== "ready") {
|
|
4533
|
+
plans.push({
|
|
4534
|
+
id,
|
|
4535
|
+
provider: session.provider,
|
|
4536
|
+
model: session.model,
|
|
4537
|
+
rolloutPath: session.rolloutPath,
|
|
4538
|
+
status: session.status,
|
|
4539
|
+
providers: [],
|
|
4540
|
+
matchingFields: 0,
|
|
4541
|
+
changedFields: 0,
|
|
4542
|
+
sqliteWillUpdate: false,
|
|
4543
|
+
action: "blocked",
|
|
4544
|
+
reason: "rollout file is missing or unreadable"
|
|
4545
|
+
});
|
|
4546
|
+
continue;
|
|
4547
|
+
}
|
|
4548
|
+
const inspection = await inspectAndTransformFile(session.rolloutPath, input.fromProvider, input.toProvider);
|
|
4549
|
+
const sqliteWillUpdate = Boolean(
|
|
4550
|
+
row && (!input.fromProvider || row.modelProvider === input.fromProvider) && row.modelProvider !== input.toProvider
|
|
4551
|
+
);
|
|
4552
|
+
const changed = inspection.changedFields > 0 || sqliteWillUpdate;
|
|
4553
|
+
plans.push({
|
|
4554
|
+
id,
|
|
4555
|
+
provider: session.provider,
|
|
4556
|
+
model: session.model,
|
|
4557
|
+
rolloutPath: session.rolloutPath,
|
|
4558
|
+
status: "ready",
|
|
4559
|
+
providers: inspection.providers,
|
|
4560
|
+
matchingFields: inspection.matchingFields,
|
|
4561
|
+
changedFields: inspection.changedFields,
|
|
4562
|
+
sqliteWillUpdate,
|
|
4563
|
+
action: changed ? "update" : "no_change"
|
|
4564
|
+
});
|
|
4565
|
+
}
|
|
4566
|
+
return plans;
|
|
4567
|
+
}
|
|
4568
|
+
async function inspectAndTransformFile(filePath, fromProvider, toProvider) {
|
|
4569
|
+
const bytes = await readFile(filePath);
|
|
4570
|
+
const originalText = decodeUtf8(bytes);
|
|
4571
|
+
const parts = originalText.split(/(\r\n|\n|\r)/u);
|
|
4572
|
+
const providers = /* @__PURE__ */ new Set();
|
|
4573
|
+
let matchingFields = 0;
|
|
4574
|
+
let changedFields = 0;
|
|
4575
|
+
const hasBom = parts[0].startsWith("\uFEFF");
|
|
4576
|
+
for (let index = 0; index < parts.length; index += 2) {
|
|
4577
|
+
const line = parts[index];
|
|
4578
|
+
if (!line.trim()) continue;
|
|
4579
|
+
let value;
|
|
4580
|
+
try {
|
|
4581
|
+
value = JSON.parse(line.replace(index === 0 ? /^\uFEFF/u : /^/u, ""));
|
|
4582
|
+
} catch (error) {
|
|
4583
|
+
throw new CodexSessionManagerError(
|
|
4584
|
+
`invalid JSONL at ${filePath}:${Math.floor(index / 2) + 1}: ${error instanceof Error ? error.message : String(error)}`
|
|
4585
|
+
);
|
|
4586
|
+
}
|
|
4587
|
+
const result = replaceStructuredProviderFields(value, fromProvider, toProvider, providers);
|
|
4588
|
+
matchingFields += result.matchingFields;
|
|
4589
|
+
changedFields += result.changedFields;
|
|
4590
|
+
if (result.changedFields > 0) {
|
|
4591
|
+
parts[index] = `${index === 0 && hasBom ? "\uFEFF" : ""}${JSON.stringify(value)}`;
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
return {
|
|
4595
|
+
transformedText: parts.join(""),
|
|
4596
|
+
providers: [...providers].sort(),
|
|
4597
|
+
matchingFields,
|
|
4598
|
+
changedFields
|
|
4599
|
+
};
|
|
4600
|
+
}
|
|
4601
|
+
function replaceStructuredProviderFields(value, fromProvider, toProvider, providers = /* @__PURE__ */ new Set()) {
|
|
4602
|
+
let matchingFields = 0;
|
|
4603
|
+
let changedFields = 0;
|
|
4604
|
+
const visit = (node) => {
|
|
4605
|
+
if (Array.isArray(node)) {
|
|
4606
|
+
for (const child of node) visit(child);
|
|
4607
|
+
return;
|
|
4608
|
+
}
|
|
4609
|
+
if (!isRecord6(node)) return;
|
|
4610
|
+
for (const [key, child] of Object.entries(node)) {
|
|
4611
|
+
if (PROVIDER_PROPERTY_NAMES.has(key) && typeof child === "string") {
|
|
4612
|
+
providers.add(child);
|
|
4613
|
+
if (!fromProvider || child === fromProvider) {
|
|
4614
|
+
matchingFields += 1;
|
|
4615
|
+
if (child !== toProvider) {
|
|
4616
|
+
node[key] = toProvider;
|
|
4617
|
+
changedFields += 1;
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
visit(node[key]);
|
|
4622
|
+
}
|
|
4623
|
+
};
|
|
4624
|
+
visit(value);
|
|
4625
|
+
return { matchingFields, changedFields };
|
|
4626
|
+
}
|
|
4627
|
+
async function snapshotFile(filePath) {
|
|
4628
|
+
const info = await stat(filePath);
|
|
4629
|
+
return { size: info.size, mtimeMs: info.mtimeMs };
|
|
4630
|
+
}
|
|
4631
|
+
function sameFileSnapshot(a, b) {
|
|
4632
|
+
return a.size === b.size && a.mtimeMs === b.mtimeMs;
|
|
4633
|
+
}
|
|
4634
|
+
async function createUniqueBackupPath(filePath) {
|
|
4635
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[.:]/gu, "-");
|
|
4636
|
+
let candidate = `${filePath}.provider-switch-${stamp}.bak`;
|
|
4637
|
+
let suffix = 1;
|
|
4638
|
+
while (existsSync5(candidate)) {
|
|
4639
|
+
candidate = `${filePath}.provider-switch-${stamp}-${suffix}.bak`;
|
|
4640
|
+
suffix += 1;
|
|
4641
|
+
}
|
|
4642
|
+
return candidate;
|
|
4643
|
+
}
|
|
4644
|
+
async function createDatabaseBackup(sqlite, database, databasePath) {
|
|
4645
|
+
const backupPath = await createUniqueBackupPath(databasePath);
|
|
4646
|
+
if (typeof sqlite.backup !== "function") {
|
|
4647
|
+
throw new CodexSessionManagerError(
|
|
4648
|
+
"Codex session updates require the node:sqlite backup API (Node.js 22.16 or newer)"
|
|
4649
|
+
);
|
|
4650
|
+
}
|
|
4651
|
+
await sqlite.backup(database, backupPath);
|
|
4652
|
+
return backupPath;
|
|
4653
|
+
}
|
|
4654
|
+
function assertDatabaseRowsUnchanged(database, rows) {
|
|
4655
|
+
const read = database.prepare("SELECT model_provider FROM threads WHERE id = ?");
|
|
4656
|
+
for (const row of rows) {
|
|
4657
|
+
const current = read.get(row.id);
|
|
4658
|
+
if (!current || String(current["model_provider"] ?? "") !== row.modelProvider) {
|
|
4659
|
+
throw new CodexSessionManagerError(
|
|
4660
|
+
`SQLite session '${row.id}' changed while the update was prepared; no files were changed`
|
|
4661
|
+
);
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
async function replaceFileAtomically(tempPath, targetPath) {
|
|
4666
|
+
try {
|
|
4667
|
+
await rename(tempPath, targetPath);
|
|
4668
|
+
} catch (error) {
|
|
4669
|
+
if (process.platform !== "win32") throw error;
|
|
4670
|
+
await copyFile(tempPath, targetPath);
|
|
4671
|
+
await unlinkIfPresent(tempPath);
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
async function restoreBackups(backupPaths) {
|
|
4675
|
+
for (const backupPath of backupPaths.reverse()) {
|
|
4676
|
+
if (!existsSync5(backupPath)) continue;
|
|
4677
|
+
const targetPath = backupPath.replace(/\.provider-switch-[^.]+(?:-[0-9]+)?\.bak$/u, "");
|
|
4678
|
+
if (targetPath === backupPath) continue;
|
|
4679
|
+
const tempPath = `${targetPath}.provider-restore-${randomUUID2()}.tmp`;
|
|
4680
|
+
try {
|
|
4681
|
+
await copyFile(backupPath, tempPath);
|
|
4682
|
+
await replaceFileAtomically(tempPath, targetPath);
|
|
4683
|
+
} finally {
|
|
4684
|
+
await unlinkIfPresent(tempPath);
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
}
|
|
4688
|
+
async function unlinkIfPresent(filePath) {
|
|
4689
|
+
try {
|
|
4690
|
+
await unlink(filePath);
|
|
4691
|
+
} catch {
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4694
|
+
function decodeUtf8(bytes) {
|
|
4695
|
+
try {
|
|
4696
|
+
return new TextDecoder2("utf-8", { fatal: true }).decode(bytes);
|
|
4697
|
+
} catch {
|
|
4698
|
+
throw new CodexSessionManagerError("Codex session contains invalid UTF-8");
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4701
|
+
function isRecord6(value) {
|
|
4702
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4703
|
+
}
|
|
4704
|
+
function firstString(...values) {
|
|
4705
|
+
for (const value of values) if (typeof value === "string" && value.trim()) return value;
|
|
4706
|
+
return null;
|
|
4707
|
+
}
|
|
4708
|
+
function normalizeComparablePath(value) {
|
|
4709
|
+
const stripped = value.trim().replace(/^\\\\\?\\/u, "");
|
|
4710
|
+
if (process.platform === "win32") {
|
|
4711
|
+
const normalized2 = win32.normalize(stripped).replace(/[\\/]+$/u, "");
|
|
4712
|
+
return normalized2.toLowerCase();
|
|
4713
|
+
}
|
|
4714
|
+
return stripped.replace(/\/+$/u, "") || "/";
|
|
4715
|
+
}
|
|
4716
|
+
function isPathInside(projectPath, candidatePath) {
|
|
4717
|
+
const project = normalizeComparablePath(projectPath);
|
|
4718
|
+
const candidate = normalizeComparablePath(candidatePath);
|
|
4719
|
+
if (project === candidate) return true;
|
|
4720
|
+
const separator = process.platform === "win32" ? "\\" : "/";
|
|
4721
|
+
return candidate.startsWith(`${project}${separator}`);
|
|
4722
|
+
}
|
|
4723
|
+
|
|
4724
|
+
// src/admin/codexSessionApi.ts
|
|
4725
|
+
async function handleCodexSessionApi(req, res, path2, manager) {
|
|
4726
|
+
if (!manager) return writeJsonError(res, 501, "Codex session management is not available");
|
|
4727
|
+
try {
|
|
4728
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
4729
|
+
if (path2 === "/admin/api/codex-sessions" && (method === "GET" || method === "HEAD")) {
|
|
4730
|
+
const projectPath = new URL(req.url ?? "/", "http://localhost").searchParams.get("projectPath") ?? "";
|
|
4731
|
+
return writeJson(res, 200, await manager.list(projectPath));
|
|
4732
|
+
}
|
|
4733
|
+
if (path2 === "/admin/api/codex-sessions/preview" && method === "POST") {
|
|
4734
|
+
const body = await readJsonBody2(req);
|
|
4735
|
+
return writeJson(res, 200, await manager.preview(parseApplyInput(body)));
|
|
4736
|
+
}
|
|
4737
|
+
if (path2 === "/admin/api/codex-sessions/apply" && method === "POST") {
|
|
4738
|
+
const body = await readJsonBody2(req);
|
|
4739
|
+
return writeJson(res, 200, await manager.apply(parseApplyInput(body)));
|
|
4740
|
+
}
|
|
4741
|
+
return writeJsonError(res, 404, "unknown Codex session admin route");
|
|
4742
|
+
} catch (error) {
|
|
4743
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4744
|
+
return writeJsonError(res, error instanceof CodexSessionManagerError ? 400 : 500, message);
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
async function readJsonBody2(req) {
|
|
4748
|
+
const chunks = [];
|
|
4749
|
+
for await (const chunk of req) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
4750
|
+
if (chunks.length === 0) return {};
|
|
4751
|
+
try {
|
|
4752
|
+
const value = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
4753
|
+
return isRecord7(value) ? value : {};
|
|
4754
|
+
} catch {
|
|
4755
|
+
throw new CodexSessionManagerError("request body must be a JSON object");
|
|
4756
|
+
}
|
|
4757
|
+
}
|
|
4758
|
+
function parseApplyInput(body) {
|
|
4759
|
+
const projectPath = body["projectPath"];
|
|
4760
|
+
const sessionIds = body["sessionIds"];
|
|
4761
|
+
const toProvider = body["toProvider"];
|
|
4762
|
+
const fromProvider = body["fromProvider"];
|
|
4763
|
+
if (typeof projectPath !== "string") throw new CodexSessionManagerError("projectPath must be a string");
|
|
4764
|
+
if (!Array.isArray(sessionIds) || !sessionIds.every((value) => typeof value === "string")) {
|
|
4765
|
+
throw new CodexSessionManagerError("sessionIds must be an array of strings");
|
|
4766
|
+
}
|
|
4767
|
+
if (typeof toProvider !== "string") throw new CodexSessionManagerError("toProvider must be a string");
|
|
4768
|
+
if (fromProvider !== void 0 && typeof fromProvider !== "string") {
|
|
4769
|
+
throw new CodexSessionManagerError("fromProvider must be a string when provided");
|
|
4770
|
+
}
|
|
4771
|
+
return {
|
|
4772
|
+
projectPath,
|
|
4773
|
+
sessionIds,
|
|
4774
|
+
toProvider,
|
|
4775
|
+
...fromProvider === void 0 ? {} : { fromProvider }
|
|
4776
|
+
};
|
|
4777
|
+
}
|
|
4778
|
+
function writeJson(res, status, body) {
|
|
4779
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
4780
|
+
res.end(JSON.stringify(body));
|
|
4781
|
+
}
|
|
4782
|
+
function writeJsonError(res, status, message) {
|
|
4783
|
+
writeJson(res, status, { error: { type: "admin_api_error", message } });
|
|
4784
|
+
}
|
|
4785
|
+
function isRecord7(value) {
|
|
4786
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
4787
|
+
}
|
|
4788
|
+
|
|
4031
4789
|
// src/admin/adminApi.ts
|
|
4032
4790
|
import http from "http";
|
|
4033
4791
|
import {
|
|
4034
4792
|
createNamedKey,
|
|
4035
4793
|
DEFAULT_IMAGES_SERVER_CONFIG,
|
|
4036
4794
|
DEFAULT_SEARCH_SERVER_CONFIG as DEFAULT_SEARCH_SERVER_CONFIG2,
|
|
4037
|
-
effectiveOutboundPermissions as
|
|
4795
|
+
effectiveOutboundPermissions as effectiveOutboundPermissions3,
|
|
4038
4796
|
gatewayBindingToEndpointConfig,
|
|
4039
4797
|
isKindMappedEndpoint,
|
|
4040
4798
|
loadServerConfig as loadServerConfig3,
|
|
@@ -4052,6 +4810,34 @@ import { getSharedAccountHealth } from "@omnicross/core/pipeline/SubscriptionAcc
|
|
|
4052
4810
|
import { fetchUpstream as fetchUpstream10 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
4053
4811
|
import { mergeExtraHeaders } from "@omnicross/core";
|
|
4054
4812
|
|
|
4813
|
+
// src/admin/testEgressIdentity.ts
|
|
4814
|
+
import {
|
|
4815
|
+
getOpenCodeGoUserAgent as getOpenCodeGoUserAgent2,
|
|
4816
|
+
OPENCODE_SESSION_HEADER
|
|
4817
|
+
} from "@omnicross/core/provider-proxy/identity/openCodeGoHeaders";
|
|
4818
|
+
var ADMIN_PROBE_OPENCODE_SESSION = "omnicross-admin-probe";
|
|
4819
|
+
function isOpenCodeUpstream(baseUrl) {
|
|
4820
|
+
if (!baseUrl) return false;
|
|
4821
|
+
try {
|
|
4822
|
+
const host = new URL(baseUrl).hostname.toLowerCase();
|
|
4823
|
+
return host === "opencode.ai" || host.endsWith(".opencode.ai");
|
|
4824
|
+
} catch {
|
|
4825
|
+
return false;
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
function hasHeader2(headers, name) {
|
|
4829
|
+
const lower = name.toLowerCase();
|
|
4830
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === lower);
|
|
4831
|
+
}
|
|
4832
|
+
function applyAdminProbeIdentity(headers, row) {
|
|
4833
|
+
if (!hasHeader2(headers, "user-agent")) {
|
|
4834
|
+
headers["user-agent"] = getOpenCodeGoUserAgent2();
|
|
4835
|
+
}
|
|
4836
|
+
if (isOpenCodeUpstream(row.baseUrl) && !hasHeader2(headers, OPENCODE_SESSION_HEADER)) {
|
|
4837
|
+
headers[OPENCODE_SESSION_HEADER] = ADMIN_PROBE_OPENCODE_SESSION;
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
|
|
4055
4841
|
// src/image-generation/imagesConfigValidation.ts
|
|
4056
4842
|
import { validateImagesServerConfig } from "@omnicross/core/outbound-api";
|
|
4057
4843
|
|
|
@@ -4059,7 +4845,7 @@ import { validateImagesServerConfig } from "@omnicross/core/outbound-api";
|
|
|
4059
4845
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
4060
4846
|
import {
|
|
4061
4847
|
chmodSync as chmodSync2,
|
|
4062
|
-
existsSync as
|
|
4848
|
+
existsSync as existsSync6,
|
|
4063
4849
|
lstatSync,
|
|
4064
4850
|
mkdirSync as mkdirSync3,
|
|
4065
4851
|
realpathSync,
|
|
@@ -4067,20 +4853,20 @@ import {
|
|
|
4067
4853
|
statSync as statSync4,
|
|
4068
4854
|
unlinkSync as unlinkSync2
|
|
4069
4855
|
} from "fs";
|
|
4070
|
-
import { homedir as
|
|
4856
|
+
import { homedir as homedir3, tmpdir } from "os";
|
|
4071
4857
|
import {
|
|
4072
|
-
basename,
|
|
4858
|
+
basename as basename2,
|
|
4073
4859
|
dirname as dirname4,
|
|
4074
4860
|
isAbsolute,
|
|
4075
|
-
join as
|
|
4861
|
+
join as join7,
|
|
4076
4862
|
parse,
|
|
4077
4863
|
relative,
|
|
4078
|
-
resolve
|
|
4864
|
+
resolve as resolve2
|
|
4079
4865
|
} from "path";
|
|
4080
4866
|
var OPAQUE_NAME = /^(?:file|directory)-[a-f0-9]{32}(?:\.(?:bin|json|tmp))?$/u;
|
|
4081
4867
|
var MOUNT_MANIFEST_NAME = "catalog.v1.json";
|
|
4082
4868
|
function normalized(path2) {
|
|
4083
|
-
const canonical =
|
|
4869
|
+
const canonical = resolve2(path2);
|
|
4084
4870
|
return process.platform === "win32" ? canonical.toLowerCase() : canonical;
|
|
4085
4871
|
}
|
|
4086
4872
|
function samePath(left, right) {
|
|
@@ -4091,26 +4877,26 @@ function isSameOrDescendant(candidate, parent) {
|
|
|
4091
4877
|
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
4092
4878
|
}
|
|
4093
4879
|
function assertNoSymlinkComponents(target) {
|
|
4094
|
-
const absolute =
|
|
4880
|
+
const absolute = resolve2(target);
|
|
4095
4881
|
const filesystemRoot = parse(absolute).root;
|
|
4096
4882
|
let cursor = filesystemRoot;
|
|
4097
4883
|
for (const segment of relative(filesystemRoot, absolute).split(/[\\/]+/u).filter(Boolean)) {
|
|
4098
|
-
cursor =
|
|
4099
|
-
if (!
|
|
4884
|
+
cursor = join7(cursor, segment);
|
|
4885
|
+
if (!existsSync6(cursor)) break;
|
|
4100
4886
|
if (lstatSync(cursor).isSymbolicLink()) {
|
|
4101
4887
|
throw new TypeError("image storage paths must not traverse a symbolic link");
|
|
4102
4888
|
}
|
|
4103
4889
|
}
|
|
4104
4890
|
}
|
|
4105
4891
|
function isInsideDetectedWorktree(target) {
|
|
4106
|
-
let cursor =
|
|
4107
|
-
while (!
|
|
4892
|
+
let cursor = resolve2(target);
|
|
4893
|
+
while (!existsSync6(cursor)) {
|
|
4108
4894
|
const parent = dirname4(cursor);
|
|
4109
4895
|
if (parent === cursor) break;
|
|
4110
4896
|
cursor = parent;
|
|
4111
4897
|
}
|
|
4112
4898
|
while (true) {
|
|
4113
|
-
if (
|
|
4899
|
+
if (existsSync6(join7(cursor, ".git"))) return true;
|
|
4114
4900
|
const parent = dirname4(cursor);
|
|
4115
4901
|
if (parent === cursor) return false;
|
|
4116
4902
|
cursor = parent;
|
|
@@ -4118,15 +4904,15 @@ function isInsideDetectedWorktree(target) {
|
|
|
4118
4904
|
}
|
|
4119
4905
|
function isBroadRoot(target, options) {
|
|
4120
4906
|
const filesystemRoot = parse(target).root;
|
|
4121
|
-
const processDirectory =
|
|
4122
|
-
const userHome =
|
|
4123
|
-
const temporaryDirectory =
|
|
4907
|
+
const processDirectory = resolve2(options.processDirectory ?? process.cwd());
|
|
4908
|
+
const userHome = resolve2(options.userHome ?? homedir3());
|
|
4909
|
+
const temporaryDirectory = resolve2(options.temporaryDirectory ?? tmpdir());
|
|
4124
4910
|
return samePath(target, filesystemRoot) || samePath(target, userHome) || samePath(target, temporaryDirectory) || isSameOrDescendant(processDirectory, target) || isSameOrDescendant(userHome, target) || isSameOrDescendant(temporaryDirectory, target);
|
|
4125
4911
|
}
|
|
4126
4912
|
function validateImageRootCandidate(candidate, options = {}) {
|
|
4127
4913
|
const label = options.label ?? "image storage root";
|
|
4128
4914
|
if (!candidate.trim() || !isAbsolute(candidate)) return [`${label} must be an absolute path`];
|
|
4129
|
-
const target =
|
|
4915
|
+
const target = resolve2(candidate);
|
|
4130
4916
|
const errors = [];
|
|
4131
4917
|
if (isBroadRoot(target, options)) errors.push(`${label} is too broad`);
|
|
4132
4918
|
try {
|
|
@@ -4164,22 +4950,22 @@ var DaemonImagePathResolver = class {
|
|
|
4164
4950
|
#issued = /* @__PURE__ */ new WeakSet();
|
|
4165
4951
|
constructor(options) {
|
|
4166
4952
|
if (!isAbsolute(options.configPath)) throw new TypeError("daemon configPath must be absolute");
|
|
4167
|
-
const applicationDataRoot =
|
|
4953
|
+
const applicationDataRoot = resolve2(dirname4(options.configPath));
|
|
4168
4954
|
const applicationErrors = validateImageRootCandidate(applicationDataRoot, {
|
|
4169
4955
|
...options,
|
|
4170
4956
|
label: "daemon application data root"
|
|
4171
4957
|
});
|
|
4172
4958
|
if (applicationErrors.length > 0) throw new TypeError(applicationErrors.join("; "));
|
|
4173
|
-
const imagesRoot =
|
|
4174
|
-
const durableRoot =
|
|
4959
|
+
const imagesRoot = join7(applicationDataRoot, "images");
|
|
4960
|
+
const durableRoot = resolve2(options.storageRoot ?? join7(imagesRoot, "storage"));
|
|
4175
4961
|
const durableErrors = validateImageRootCandidate(durableRoot, {
|
|
4176
4962
|
...options,
|
|
4177
4963
|
label: "image durable storage root"
|
|
4178
4964
|
});
|
|
4179
4965
|
const reservedRoots = [
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4966
|
+
join7(imagesRoot, "temporary"),
|
|
4967
|
+
join7(imagesRoot, "evidence"),
|
|
4968
|
+
join7(imagesRoot, "mount-catalog")
|
|
4183
4969
|
];
|
|
4184
4970
|
if (reservedRoots.some((reserved) => isSameOrDescendant(durableRoot, reserved) || isSameOrDescendant(reserved, durableRoot))) {
|
|
4185
4971
|
durableErrors.push("image durable storage root must not overlap daemon image control roots");
|
|
@@ -4188,13 +4974,13 @@ var DaemonImagePathResolver = class {
|
|
|
4188
4974
|
const paths = Object.freeze({
|
|
4189
4975
|
applicationDataRoot,
|
|
4190
4976
|
imagesRoot,
|
|
4191
|
-
temporaryRoot:
|
|
4977
|
+
temporaryRoot: join7(imagesRoot, "temporary"),
|
|
4192
4978
|
durableRoot,
|
|
4193
|
-
artifactsRoot:
|
|
4194
|
-
stateRoot:
|
|
4195
|
-
evidenceRoot:
|
|
4196
|
-
mountManifestRoot:
|
|
4197
|
-
mountManifestPath:
|
|
4979
|
+
artifactsRoot: join7(durableRoot, "artifacts"),
|
|
4980
|
+
stateRoot: join7(durableRoot, "state"),
|
|
4981
|
+
evidenceRoot: join7(imagesRoot, "evidence"),
|
|
4982
|
+
mountManifestRoot: join7(imagesRoot, "mount-catalog"),
|
|
4983
|
+
mountManifestPath: join7(imagesRoot, "mount-catalog", MOUNT_MANIFEST_NAME)
|
|
4198
4984
|
});
|
|
4199
4985
|
const rootEntries = [
|
|
4200
4986
|
["temporary", paths.temporaryRoot],
|
|
@@ -4226,7 +5012,7 @@ var DaemonImagePathResolver = class {
|
|
|
4226
5012
|
/** Revalidate immediately before unlinking a resolver-issued file capability. */
|
|
4227
5013
|
removeFile(target) {
|
|
4228
5014
|
const path2 = this.verifyDestructiveTarget(target, ["opaque-file", "mount-manifest"]);
|
|
4229
|
-
if (!
|
|
5015
|
+
if (!existsSync6(path2)) return;
|
|
4230
5016
|
const info = lstatSync(path2);
|
|
4231
5017
|
if (info.isSymbolicLink() || !info.isFile()) {
|
|
4232
5018
|
throw new TypeError("refusing to unlink an unverified image file");
|
|
@@ -4236,7 +5022,7 @@ var DaemonImagePathResolver = class {
|
|
|
4236
5022
|
/** Only empty opaque directories may be removed until the owned-marker layer is composed. */
|
|
4237
5023
|
removeEmptyDirectory(target) {
|
|
4238
5024
|
const path2 = this.verifyDestructiveTarget(target, ["opaque-directory"]);
|
|
4239
|
-
if (!
|
|
5025
|
+
if (!existsSync6(path2)) return;
|
|
4240
5026
|
const info = lstatSync(path2);
|
|
4241
5027
|
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
4242
5028
|
throw new TypeError("refusing to remove an unverified image directory");
|
|
@@ -4246,7 +5032,7 @@ var DaemonImagePathResolver = class {
|
|
|
4246
5032
|
issue(area, name, kind) {
|
|
4247
5033
|
const handle = Object.freeze({
|
|
4248
5034
|
area,
|
|
4249
|
-
absolutePath:
|
|
5035
|
+
absolutePath: join7(this.#roots[area].path, name),
|
|
4250
5036
|
kind
|
|
4251
5037
|
});
|
|
4252
5038
|
this.#issued.add(handle);
|
|
@@ -4261,11 +5047,11 @@ var DaemonImagePathResolver = class {
|
|
|
4261
5047
|
}
|
|
4262
5048
|
const root = this.#roots[target.area];
|
|
4263
5049
|
assertRootIdentity(root);
|
|
4264
|
-
const candidate =
|
|
5050
|
+
const candidate = resolve2(target.absolutePath);
|
|
4265
5051
|
if (!samePath(dirname4(candidate), root.path) || !isSameOrDescendant(candidate, root.path)) {
|
|
4266
5052
|
throw new TypeError("refusing a destructive operation outside the verified image root");
|
|
4267
5053
|
}
|
|
4268
|
-
const name =
|
|
5054
|
+
const name = basename2(candidate);
|
|
4269
5055
|
const validName = target.kind === "mount-manifest" ? name === MOUNT_MANIFEST_NAME : OPAQUE_NAME.test(name);
|
|
4270
5056
|
if (!validName) throw new TypeError("refusing a caller-derived image filename");
|
|
4271
5057
|
assertNoSymlinkComponents(candidate);
|
|
@@ -4729,29 +5515,29 @@ function resolveEnvKey(rawKey) {
|
|
|
4729
5515
|
|
|
4730
5516
|
// src/integrations/IntegrationManager.ts
|
|
4731
5517
|
import { createHash } from "crypto";
|
|
4732
|
-
import { existsSync as
|
|
4733
|
-
import { homedir as
|
|
4734
|
-
import { join as
|
|
5518
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, unlinkSync as unlinkSync4 } from "fs";
|
|
5519
|
+
import { homedir as homedir4 } from "os";
|
|
5520
|
+
import { join as join8, resolve as resolve4 } from "path";
|
|
4735
5521
|
import {
|
|
4736
5522
|
createIntegrationKey,
|
|
4737
5523
|
effectiveOutboundPermissions
|
|
4738
5524
|
} from "@omnicross/core";
|
|
4739
5525
|
|
|
4740
5526
|
// src/integrations/codexAuthHelper.ts
|
|
4741
|
-
import { resolve as
|
|
5527
|
+
import { resolve as resolve3 } from "path";
|
|
4742
5528
|
function currentProcessCodexAuthHelper(configPath, masterKeyFilePath) {
|
|
4743
5529
|
const entrypoint = process.argv[1];
|
|
4744
5530
|
if (!entrypoint) throw new Error("cannot configure Codex auth helper without a daemon CLI entrypoint");
|
|
4745
5531
|
return {
|
|
4746
5532
|
command: process.execPath,
|
|
4747
5533
|
args: [
|
|
4748
|
-
|
|
5534
|
+
resolve3(entrypoint),
|
|
4749
5535
|
"integrations",
|
|
4750
5536
|
"token",
|
|
4751
5537
|
"codex",
|
|
4752
5538
|
"--config",
|
|
4753
|
-
|
|
4754
|
-
...masterKeyFilePath ? ["--master-key-file",
|
|
5539
|
+
resolve3(configPath),
|
|
5540
|
+
...masterKeyFilePath ? ["--master-key-file", resolve3(masterKeyFilePath)] : []
|
|
4755
5541
|
]
|
|
4756
5542
|
};
|
|
4757
5543
|
}
|
|
@@ -4759,7 +5545,7 @@ function currentProcessCodexAuthHelper(configPath, masterKeyFilePath) {
|
|
|
4759
5545
|
// src/integrations/IntegrationStateStore.ts
|
|
4760
5546
|
import {
|
|
4761
5547
|
chmodSync as chmodSync3,
|
|
4762
|
-
existsSync as
|
|
5548
|
+
existsSync as existsSync7,
|
|
4763
5549
|
mkdirSync as mkdirSync4,
|
|
4764
5550
|
readFileSync as readFileSync6,
|
|
4765
5551
|
renameSync as renameSync3,
|
|
@@ -4776,7 +5562,7 @@ var IntegrationStateStore = class {
|
|
|
4776
5562
|
path;
|
|
4777
5563
|
box;
|
|
4778
5564
|
load() {
|
|
4779
|
-
if (!
|
|
5565
|
+
if (!existsSync7(this.path)) return { ...EMPTY_STATE, clients: {} };
|
|
4780
5566
|
let raw;
|
|
4781
5567
|
try {
|
|
4782
5568
|
raw = JSON.parse(readFileSync6(this.path, "utf8"));
|
|
@@ -4882,7 +5668,7 @@ function atomicWrite(path2, content) {
|
|
|
4882
5668
|
}
|
|
4883
5669
|
throw error;
|
|
4884
5670
|
} finally {
|
|
4885
|
-
if (
|
|
5671
|
+
if (existsSync7(path2)) {
|
|
4886
5672
|
try {
|
|
4887
5673
|
chmodSync3(path2, 384);
|
|
4888
5674
|
} catch {
|
|
@@ -5067,7 +5853,7 @@ var IntegrationManager = class {
|
|
|
5067
5853
|
constructor(options) {
|
|
5068
5854
|
this.options = options;
|
|
5069
5855
|
assertLoopbackGatewayUrl(options.gatewayBaseUrl);
|
|
5070
|
-
this.homeDir = options.homeDir ??
|
|
5856
|
+
this.homeDir = options.homeDir ?? homedir4();
|
|
5071
5857
|
this.codexAuthHelper = options.codexAuthHelper ?? currentProcessCodexAuthHelper(options.configPath);
|
|
5072
5858
|
}
|
|
5073
5859
|
options;
|
|
@@ -5081,7 +5867,7 @@ var IntegrationManager = class {
|
|
|
5081
5867
|
async plan(client, configPath = this.defaultConfigPath(client)) {
|
|
5082
5868
|
const state = this.options.stateStore.load();
|
|
5083
5869
|
const record = state.clients[client];
|
|
5084
|
-
const target = record?.configPath ??
|
|
5870
|
+
const target = record?.configPath ?? resolve4(configPath);
|
|
5085
5871
|
const status = await this.statusFor(client, state, await this.options.keyDb.outboundApiKeysList());
|
|
5086
5872
|
const changes = client === "codex" ? ["model_provider", "model_providers.omnicross", "model_providers.omnicross.auth"] : ["env.ANTHROPIC_BASE_URL", "env.ANTHROPIC_AUTH_TOKEN", "env.ANTHROPIC_API_KEY"];
|
|
5087
5873
|
if (!record) {
|
|
@@ -5097,7 +5883,7 @@ var IntegrationManager = class {
|
|
|
5097
5883
|
return { client, configPath: target, action: "repair", canApply: true, changes, warnings };
|
|
5098
5884
|
}
|
|
5099
5885
|
async install(client, configPath = this.defaultConfigPath(client)) {
|
|
5100
|
-
const target =
|
|
5886
|
+
const target = resolve4(configPath);
|
|
5101
5887
|
const state = this.options.stateStore.load();
|
|
5102
5888
|
const previousState = cloneState(state);
|
|
5103
5889
|
const existingRecord = state.clients[client];
|
|
@@ -5304,6 +6090,33 @@ var IntegrationManager = class {
|
|
|
5304
6090
|
}
|
|
5305
6091
|
return details.secret;
|
|
5306
6092
|
}
|
|
6093
|
+
/**
|
|
6094
|
+
* Resolve ONE access key's plaintext by id — the `--key-id` variant the
|
|
6095
|
+
* command-auth helper serves for key-scoped Codex launches (each terminal
|
|
6096
|
+
* picks its own gateway key, so concurrent sessions can route to different
|
|
6097
|
+
* upstreams through their keys' bindings). Enforces the SAME usability
|
|
6098
|
+
* contract as the client-bound path: existing, enabled, not revoked,
|
|
6099
|
+
* revealable, and holding the codex-required endpoint permissions.
|
|
6100
|
+
*/
|
|
6101
|
+
async getKeyToken(keyId) {
|
|
6102
|
+
const rows = await this.options.keyDb.outboundApiKeysList();
|
|
6103
|
+
const row = rows.find((candidate) => candidate.id === keyId);
|
|
6104
|
+
if (!row) {
|
|
6105
|
+
throw new IntegrationConflictError(`access key '${keyId}' does not exist`);
|
|
6106
|
+
}
|
|
6107
|
+
const secret = await this.options.keyDb.outboundApiKeysReveal(keyId);
|
|
6108
|
+
if (!row.enabled || row.revokedAt !== null || !secret) {
|
|
6109
|
+
throw new IntegrationConflictError(
|
|
6110
|
+
`access key '${keyId}' is disabled, revoked, or not revealable`
|
|
6111
|
+
);
|
|
6112
|
+
}
|
|
6113
|
+
if (!hasRequiredPermissions(row, "codex")) {
|
|
6114
|
+
throw new IntegrationConflictError(
|
|
6115
|
+
`access key '${keyId}' lacks the responses+images endpoint permissions Codex requires`
|
|
6116
|
+
);
|
|
6117
|
+
}
|
|
6118
|
+
return secret;
|
|
6119
|
+
}
|
|
5307
6120
|
/** Compatibility alias for callers predating per-client bindings. */
|
|
5308
6121
|
async getGatewayToken(client = "codex") {
|
|
5309
6122
|
return this.getIntegrationToken(client);
|
|
@@ -5444,7 +6257,7 @@ var IntegrationManager = class {
|
|
|
5444
6257
|
}
|
|
5445
6258
|
}
|
|
5446
6259
|
defaultConfigPath(client) {
|
|
5447
|
-
return client === "codex" ?
|
|
6260
|
+
return client === "codex" ? join8(this.homeDir, ".codex", "config.toml") : join8(this.homeDir, ".claude", "settings.json");
|
|
5448
6261
|
}
|
|
5449
6262
|
renderInstalled(client, base, secret) {
|
|
5450
6263
|
if (client === "claude") {
|
|
@@ -5487,7 +6300,7 @@ function cloneState(state) {
|
|
|
5487
6300
|
};
|
|
5488
6301
|
}
|
|
5489
6302
|
function readOptional(path2) {
|
|
5490
|
-
return
|
|
6303
|
+
return existsSync8(path2) ? readFileSync7(path2, "utf8") : null;
|
|
5491
6304
|
}
|
|
5492
6305
|
function sha256(value) {
|
|
5493
6306
|
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
@@ -5547,7 +6360,7 @@ function writeOptional(path2, content) {
|
|
|
5547
6360
|
atomicWrite(path2, content);
|
|
5548
6361
|
return;
|
|
5549
6362
|
}
|
|
5550
|
-
if (
|
|
6363
|
+
if (existsSync8(path2)) unlinkSync4(path2);
|
|
5551
6364
|
}
|
|
5552
6365
|
async function bestEffortRevoke(db, keyId) {
|
|
5553
6366
|
try {
|
|
@@ -5887,7 +6700,7 @@ import { SUBSCRIPTION_MODEL_CATALOG } from "@omnicross/contracts/subscription-mo
|
|
|
5887
6700
|
import { ANTIGRAVITY_CODE_ASSIST_ENDPOINT as ANTIGRAVITY_CODE_ASSIST_ENDPOINT2 } from "@omnicross/core/auth/GeminiCodeAssistProjectResolver";
|
|
5888
6701
|
import { fetchUpstream as fetchUpstream9 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
5889
6702
|
import { getAntigravityUserAgent as getAntigravityUserAgent2 } from "@omnicross/core/transformer/transformers/antigravityIdentity";
|
|
5890
|
-
function
|
|
6703
|
+
function isRecord8(value) {
|
|
5891
6704
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5892
6705
|
}
|
|
5893
6706
|
function optionalString(value) {
|
|
@@ -5900,13 +6713,13 @@ function optionalBoolean(value) {
|
|
|
5900
6713
|
return typeof value === "boolean" ? value : void 0;
|
|
5901
6714
|
}
|
|
5902
6715
|
function parseAntigravityAvailableModels(payload) {
|
|
5903
|
-
if (!
|
|
6716
|
+
if (!isRecord8(payload)) return [];
|
|
5904
6717
|
const models = payload["models"];
|
|
5905
|
-
if (!
|
|
6718
|
+
if (!isRecord8(models)) return [];
|
|
5906
6719
|
const out = [];
|
|
5907
6720
|
for (const [id, raw] of Object.entries(models)) {
|
|
5908
6721
|
if (ANTIGRAVITY_DISCOVERY_DENYLIST.has(id)) continue;
|
|
5909
|
-
if (!
|
|
6722
|
+
if (!isRecord8(raw)) continue;
|
|
5910
6723
|
if (raw["isInternal"] === true) continue;
|
|
5911
6724
|
out.push({
|
|
5912
6725
|
id,
|
|
@@ -5967,7 +6780,7 @@ async function fetchAntigravityAvailableModels(accessToken, fetchImpl = (url, in
|
|
|
5967
6780
|
}
|
|
5968
6781
|
if (!response.ok) return null;
|
|
5969
6782
|
const payload = await response.json().catch(() => null);
|
|
5970
|
-
if (!
|
|
6783
|
+
if (!isRecord8(payload) || !isRecord8(payload["models"])) return null;
|
|
5971
6784
|
return parseAntigravityAvailableModels(payload);
|
|
5972
6785
|
}
|
|
5973
6786
|
async function handleAntigravityModelsRoute(deps) {
|
|
@@ -6323,21 +7136,26 @@ async function exchangeGemini(code, codeVerifier, exchangeFetch) {
|
|
|
6323
7136
|
|
|
6324
7137
|
// src/admin/cliLaunch.ts
|
|
6325
7138
|
import { exec, spawn } from "child_process";
|
|
6326
|
-
import { randomUUID as
|
|
6327
|
-
import { chmodSync as chmodSync4, existsSync as
|
|
7139
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
7140
|
+
import { chmodSync as chmodSync4, existsSync as existsSync9, mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
6328
7141
|
import { createServer } from "net";
|
|
6329
7142
|
import { tmpdir as tmpdir2 } from "os";
|
|
6330
|
-
import { delimiter, join as
|
|
7143
|
+
import { delimiter, join as join9 } from "path";
|
|
6331
7144
|
import {
|
|
6332
7145
|
buildChatCliLaunchConfig,
|
|
6333
7146
|
buildClaudeCliLaunchConfig,
|
|
6334
7147
|
buildCodexLaunchConfig,
|
|
6335
|
-
buildGeminiCliLaunchConfig
|
|
7148
|
+
buildGeminiCliLaunchConfig,
|
|
7149
|
+
CODEX_PROXY_PROVIDER_NAME
|
|
6336
7150
|
} from "@omnicross/cli-launcher";
|
|
6337
7151
|
import {
|
|
6338
7152
|
ROUTE_LEASE_REQUEST_SCHEMA,
|
|
6339
7153
|
RouteLeaseError as RouteLeaseError2
|
|
6340
7154
|
} from "@omnicross/core/provider-proxy";
|
|
7155
|
+
import {
|
|
7156
|
+
candidateGatewayBindings,
|
|
7157
|
+
effectiveOutboundPermissions as effectiveOutboundPermissions2
|
|
7158
|
+
} from "@omnicross/core/outbound-api";
|
|
6341
7159
|
|
|
6342
7160
|
// src/routeLeaseRenewal.ts
|
|
6343
7161
|
var TERMINAL_LEASE_TTL_SECONDS = 600;
|
|
@@ -6384,8 +7202,8 @@ function isLaunchCliId(id) {
|
|
|
6384
7202
|
function probeDefault(candidate) {
|
|
6385
7203
|
const segments = (process.env["PATH"] ?? "").split(delimiter).filter(Boolean);
|
|
6386
7204
|
for (const seg of segments) {
|
|
6387
|
-
const full =
|
|
6388
|
-
if (
|
|
7205
|
+
const full = join9(seg, candidate);
|
|
7206
|
+
if (existsSync9(full)) return full;
|
|
6389
7207
|
}
|
|
6390
7208
|
return null;
|
|
6391
7209
|
}
|
|
@@ -6418,6 +7236,75 @@ function resolveLaunchTarget(providers, requested) {
|
|
|
6418
7236
|
function firstModel(p) {
|
|
6419
7237
|
return p.models?.[0] ?? p.modelConfigs?.[0]?.id;
|
|
6420
7238
|
}
|
|
7239
|
+
var CMD_METACHAR_RE = /[&|<>^%]/;
|
|
7240
|
+
var KEY_SCOPED_CODEX_PERMISSIONS = ["responses", "images"];
|
|
7241
|
+
function buildKeyScopedCodexArgs(input) {
|
|
7242
|
+
let root = input.gatewayBaseUrl;
|
|
7243
|
+
while (root.endsWith("/")) root = root.slice(0, -1);
|
|
7244
|
+
const name = CODEX_PROXY_PROVIDER_NAME;
|
|
7245
|
+
const helperArgs = [...input.authHelper.args, "--key-id", input.keyId];
|
|
7246
|
+
return [
|
|
7247
|
+
"-c",
|
|
7248
|
+
`model_provider="${name}"`,
|
|
7249
|
+
"-c",
|
|
7250
|
+
`model_providers.${name}.name="OmniCross Local Gateway"`,
|
|
7251
|
+
"-c",
|
|
7252
|
+
`model_providers.${name}.base_url="${root}/v1"`,
|
|
7253
|
+
"-c",
|
|
7254
|
+
`model_providers.${name}.wire_api="responses"`,
|
|
7255
|
+
"-c",
|
|
7256
|
+
`model_providers.${name}.supports_websockets=false`,
|
|
7257
|
+
"-c",
|
|
7258
|
+
`model_providers.${name}.http_headers={"X-OpenAI-Actor-Authorization"="omnicross"}`,
|
|
7259
|
+
"-c",
|
|
7260
|
+
`model_providers.${name}.auth.command=${JSON.stringify(input.authHelper.command)}`,
|
|
7261
|
+
"-c",
|
|
7262
|
+
`model_providers.${name}.auth.args=${JSON.stringify(helperArgs)}`,
|
|
7263
|
+
"-c",
|
|
7264
|
+
`model_providers.${name}.auth.refresh_interval_ms=0`,
|
|
7265
|
+
"-c",
|
|
7266
|
+
`model_providers.${name}.auth.timeout_ms=5000`,
|
|
7267
|
+
"-c",
|
|
7268
|
+
"disable_response_storage=true"
|
|
7269
|
+
];
|
|
7270
|
+
}
|
|
7271
|
+
async function preflightKeyScopedLaunch(deps, keyId) {
|
|
7272
|
+
if (!deps.gatewayRunning) {
|
|
7273
|
+
return {
|
|
7274
|
+
ok: false,
|
|
7275
|
+
status: 409,
|
|
7276
|
+
message: "the outbound gateway is not running \u2014 key-scoped launches route through it"
|
|
7277
|
+
};
|
|
7278
|
+
}
|
|
7279
|
+
const rows = await deps.keyDb.outboundApiKeysList();
|
|
7280
|
+
const row = rows.find((candidate) => candidate.id === keyId);
|
|
7281
|
+
if (!row) return { ok: false, status: 404, message: `access key '${keyId}' does not exist` };
|
|
7282
|
+
if (!row.enabled || row.revokedAt !== null) {
|
|
7283
|
+
return { ok: false, status: 400, message: `access key '${row.name}' is disabled or revoked` };
|
|
7284
|
+
}
|
|
7285
|
+
const secret = await deps.keyDb.outboundApiKeysReveal(keyId);
|
|
7286
|
+
if (!secret) {
|
|
7287
|
+
return { ok: false, status: 400, message: `access key '${row.name}' is not revealable` };
|
|
7288
|
+
}
|
|
7289
|
+
const allowed = effectiveOutboundPermissions2(row.allowedEndpoints);
|
|
7290
|
+
for (const permission of KEY_SCOPED_CODEX_PERMISSIONS) {
|
|
7291
|
+
if (!allowed.includes(permission)) {
|
|
7292
|
+
return {
|
|
7293
|
+
ok: false,
|
|
7294
|
+
status: 400,
|
|
7295
|
+
message: `access key '${row.name}' lacks the '${permission}' endpoint permission Codex requires`
|
|
7296
|
+
};
|
|
7297
|
+
}
|
|
7298
|
+
}
|
|
7299
|
+
if (candidateGatewayBindings(deps.bindings, keyId, "responses").length === 0) {
|
|
7300
|
+
return {
|
|
7301
|
+
ok: false,
|
|
7302
|
+
status: 400,
|
|
7303
|
+
message: `access key '${row.name}' has no enabled responses route \u2014 bind it to a downstream route on the API Service page first`
|
|
7304
|
+
};
|
|
7305
|
+
}
|
|
7306
|
+
return { ok: true, keyName: row.name };
|
|
7307
|
+
}
|
|
6421
7308
|
async function buildLaunchEnv(cli, llmConfig, target) {
|
|
6422
7309
|
const common = {
|
|
6423
7310
|
llmConfig,
|
|
@@ -6489,10 +7376,10 @@ function openTerminal({ cli, command, extraArgs, env, cwd, platform, onFailure }
|
|
|
6489
7376
|
const runLine = [command, ...extraArgs].map(shq).join(" ");
|
|
6490
7377
|
const script = `${cwd ? `cd ${shq(cwd)}; ` : ""}${runLine}`;
|
|
6491
7378
|
if (platform === "darwin") {
|
|
6492
|
-
const launchDir = mkdtempSync(
|
|
6493
|
-
const commandFile =
|
|
6494
|
-
const bootstrapFile =
|
|
6495
|
-
const socketPath = macIpc.socketPath ??
|
|
7379
|
+
const launchDir = mkdtempSync(join9(tmpdir2(), "omnicross-terminal-"));
|
|
7380
|
+
const commandFile = join9(launchDir, "launch.command");
|
|
7381
|
+
const bootstrapFile = join9(launchDir, "bootstrap.cjs");
|
|
7382
|
+
const socketPath = macIpc.socketPath ?? join9(launchDir, "descriptor.sock");
|
|
6496
7383
|
const openerEnv = { ...process.env };
|
|
6497
7384
|
for (const key of Object.keys(env)) delete openerEnv[key];
|
|
6498
7385
|
let claimed = false;
|
|
@@ -6621,10 +7508,10 @@ function resetCliSessions() {
|
|
|
6621
7508
|
function errBody(message) {
|
|
6622
7509
|
return { error: { type: "admin_api_error", message } };
|
|
6623
7510
|
}
|
|
6624
|
-
var defaultCommandRunner = (command) => new Promise((
|
|
7511
|
+
var defaultCommandRunner = (command) => new Promise((resolve12) => {
|
|
6625
7512
|
exec(command, { timeout: 18e4 }, (err9, _stdout, stderr) => {
|
|
6626
|
-
if (err9)
|
|
6627
|
-
else
|
|
7513
|
+
if (err9) resolve12({ ok: false, error: stderr.trim() || err9.message });
|
|
7514
|
+
else resolve12({ ok: true });
|
|
6628
7515
|
});
|
|
6629
7516
|
});
|
|
6630
7517
|
async function handleCliInstall(cli, runner = defaultCommandRunner) {
|
|
@@ -6663,44 +7550,84 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
6663
7550
|
if (!isCliInstalled(meta.command, platform, probe)) {
|
|
6664
7551
|
return { status: 400, body: errBody(`"${meta.command}" is not installed (not found on PATH)`) };
|
|
6665
7552
|
}
|
|
7553
|
+
const keyId = typeof body["keyId"] === "string" && body["keyId"].trim() ? body["keyId"].trim() : void 0;
|
|
6666
7554
|
let target;
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
|
|
6670
|
-
model: typeof body["model"] === "string" ? body["model"] : void 0
|
|
6671
|
-
});
|
|
6672
|
-
} catch (err9) {
|
|
6673
|
-
return { status: 400, body: errBody(err9 instanceof Error ? err9.message : "no launch target") };
|
|
6674
|
-
}
|
|
6675
|
-
const id = randomUUID2();
|
|
7555
|
+
let keyLaunch;
|
|
7556
|
+
const id = randomUUID3();
|
|
6676
7557
|
let leaseId2;
|
|
6677
7558
|
let launch;
|
|
6678
|
-
|
|
6679
|
-
if (
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
const
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
}
|
|
6697
|
-
}
|
|
6698
|
-
}
|
|
6699
|
-
|
|
7559
|
+
if (keyId) {
|
|
7560
|
+
if (cli !== "codex") {
|
|
7561
|
+
return { status: 400, body: errBody("key-scoped launch is only supported for codex") };
|
|
7562
|
+
}
|
|
7563
|
+
const deps = ctx.keyScoped;
|
|
7564
|
+
if (!deps) {
|
|
7565
|
+
return { status: 501, body: errBody("key-scoped launch is not available in this build") };
|
|
7566
|
+
}
|
|
7567
|
+
const preflight = await preflightKeyScopedLaunch(deps, keyId);
|
|
7568
|
+
if (!preflight.ok) return { status: preflight.status, body: errBody(preflight.message) };
|
|
7569
|
+
if (platform === "win32") {
|
|
7570
|
+
const unsafe = [deps.codexAuthHelper.command, ...deps.codexAuthHelper.args].filter((value) => CMD_METACHAR_RE.test(value));
|
|
7571
|
+
if (unsafe.length > 0) {
|
|
7572
|
+
return {
|
|
7573
|
+
status: 400,
|
|
7574
|
+
body: errBody(
|
|
7575
|
+
"the Codex auth-helper path contains cmd.exe metacharacters and cannot be passed through a Windows terminal launch"
|
|
7576
|
+
)
|
|
7577
|
+
};
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7580
|
+
keyLaunch = { keyId, keyName: preflight.keyName };
|
|
7581
|
+
launch = {
|
|
7582
|
+
env: {},
|
|
7583
|
+
extraArgs: buildKeyScopedCodexArgs({
|
|
7584
|
+
gatewayBaseUrl: deps.gatewayBaseUrl,
|
|
7585
|
+
authHelper: deps.codexAuthHelper,
|
|
7586
|
+
keyId
|
|
7587
|
+
}),
|
|
7588
|
+
// No route or lease exists to release — the gateway key outlives the
|
|
7589
|
+
// terminal and its bindings route every request.
|
|
7590
|
+
onSessionEnd: () => {
|
|
7591
|
+
}
|
|
7592
|
+
};
|
|
7593
|
+
} else {
|
|
7594
|
+
let resolved;
|
|
7595
|
+
try {
|
|
7596
|
+
resolved = resolveLaunchTarget(ctx.providers, {
|
|
7597
|
+
providerId: typeof body["providerId"] === "string" ? body["providerId"] : void 0,
|
|
7598
|
+
model: typeof body["model"] === "string" ? body["model"] : void 0
|
|
7599
|
+
});
|
|
7600
|
+
} catch (err9) {
|
|
7601
|
+
return { status: 400, body: errBody(err9 instanceof Error ? err9.message : "no launch target") };
|
|
7602
|
+
}
|
|
7603
|
+
target = resolved;
|
|
7604
|
+
try {
|
|
7605
|
+
if ((cli === "claude" || cli === "codex") && ctx.routeLeaseManager) {
|
|
7606
|
+
const outcome = await ctx.routeLeaseManager.createFromRequest({
|
|
7607
|
+
schemaVersion: ROUTE_LEASE_REQUEST_SCHEMA,
|
|
7608
|
+
consumer: "omnicross-terminal",
|
|
7609
|
+
runtime: cli,
|
|
7610
|
+
upstream: { kind: "provider", providerId: resolved.providerId },
|
|
7611
|
+
model: resolved.model,
|
|
7612
|
+
execution: { sessionId: id }
|
|
7613
|
+
}, `omnicross-terminal:${id}`);
|
|
7614
|
+
leaseId2 = outcome.result.leaseId;
|
|
7615
|
+
const stopRenewal = startTerminalLeaseRenewal(ctx.routeLeaseManager, leaseId2);
|
|
7616
|
+
launch = {
|
|
7617
|
+
env: outcome.result.launch.env,
|
|
7618
|
+
extraArgs: outcome.result.launch.extraArgs,
|
|
7619
|
+
onSessionEnd: () => {
|
|
7620
|
+
stopRenewal();
|
|
7621
|
+
ctx.routeLeaseManager?.release(outcome.result.leaseId);
|
|
7622
|
+
}
|
|
7623
|
+
};
|
|
7624
|
+
} else {
|
|
7625
|
+
launch = await buildLaunchEnv(cli, ctx.llmConfig, resolved);
|
|
7626
|
+
}
|
|
7627
|
+
} catch (err9) {
|
|
7628
|
+
const status = err9 instanceof RouteLeaseError2 ? err9.status : 400;
|
|
7629
|
+
return { status, body: errBody(err9 instanceof Error ? err9.message : "failed to build launch env") };
|
|
6700
7630
|
}
|
|
6701
|
-
} catch (err9) {
|
|
6702
|
-
const status = err9 instanceof RouteLeaseError2 ? err9.status : 400;
|
|
6703
|
-
return { status, body: errBody(err9 instanceof Error ? err9.message : "failed to build launch env") };
|
|
6704
7631
|
}
|
|
6705
7632
|
const cwd = typeof body["cwd"] === "string" && body["cwd"].trim() ? body["cwd"].trim() : void 0;
|
|
6706
7633
|
const opener = ctx.opener ?? defaultTerminalOpener;
|
|
@@ -6739,15 +7666,19 @@ async function handleCliLaunch(cli, body, ctx) {
|
|
|
6739
7666
|
sessions.set(id, {
|
|
6740
7667
|
id,
|
|
6741
7668
|
cli,
|
|
6742
|
-
providerId: target
|
|
6743
|
-
model: target
|
|
7669
|
+
providerId: target?.providerId ?? "",
|
|
7670
|
+
model: target?.model ?? "",
|
|
7671
|
+
...keyLaunch ? { keyId: keyLaunch.keyId, keyName: keyLaunch.keyName } : {},
|
|
6744
7672
|
...leaseId2 ? { leaseId: leaseId2 } : {},
|
|
6745
7673
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6746
7674
|
onSessionEnd
|
|
6747
7675
|
});
|
|
6748
7676
|
published = true;
|
|
6749
7677
|
if (ended) sessions.delete(id);
|
|
6750
|
-
return {
|
|
7678
|
+
return {
|
|
7679
|
+
status: 200,
|
|
7680
|
+
body: keyLaunch ? { sessionId: id, keyId: keyLaunch.keyId, keyName: keyLaunch.keyName } : { sessionId: id, providerId: target?.providerId, model: target?.model }
|
|
7681
|
+
};
|
|
6751
7682
|
}
|
|
6752
7683
|
|
|
6753
7684
|
// src/admin/auditConfigBody.ts
|
|
@@ -7081,17 +8012,17 @@ function sanitizeResultField(value, cap) {
|
|
|
7081
8012
|
const text = typeof value === "string" ? value : value === null || value === void 0 ? "" : String(value);
|
|
7082
8013
|
return text.replace(/[\u0000-\u001f\u007f]/gu, "").slice(0, cap);
|
|
7083
8014
|
}
|
|
7084
|
-
function
|
|
8015
|
+
function writeJson2(res, status, body) {
|
|
7085
8016
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
7086
8017
|
res.end(JSON.stringify(body));
|
|
7087
8018
|
}
|
|
7088
8019
|
function writeErr(res, status, message) {
|
|
7089
|
-
|
|
8020
|
+
writeJson2(res, status, { error: { type: "admin_api_error", message } });
|
|
7090
8021
|
}
|
|
7091
8022
|
var SEARCH_MAX_BODY_BYTES = 64 * 1024;
|
|
7092
8023
|
var SearchBodyTooLargeError = class extends Error {
|
|
7093
8024
|
};
|
|
7094
|
-
async function
|
|
8025
|
+
async function readJsonBody3(req) {
|
|
7095
8026
|
const chunks = [];
|
|
7096
8027
|
let bytes = 0;
|
|
7097
8028
|
for await (const chunk of req) {
|
|
@@ -7111,7 +8042,7 @@ async function readJsonBody2(req) {
|
|
|
7111
8042
|
}
|
|
7112
8043
|
async function readBodyOrReject(req, res) {
|
|
7113
8044
|
try {
|
|
7114
|
-
return await
|
|
8045
|
+
return await readJsonBody3(req);
|
|
7115
8046
|
} catch (error) {
|
|
7116
8047
|
if (error instanceof SearchBodyTooLargeError) {
|
|
7117
8048
|
writeErr(res, 400, error.message);
|
|
@@ -7169,7 +8100,7 @@ async function handleSearchDiagnostics(res, deps) {
|
|
|
7169
8100
|
},
|
|
7170
8101
|
applySemantics: { codex: "immediate", rest: "restart" }
|
|
7171
8102
|
};
|
|
7172
|
-
return
|
|
8103
|
+
return writeJson2(res, 200, { diagnostics: snapshot });
|
|
7173
8104
|
}
|
|
7174
8105
|
function persistedSearchContributions(search, fetchImpl) {
|
|
7175
8106
|
if (fetchImpl) {
|
|
@@ -7215,11 +8146,11 @@ async function handleSearchTest(req, res, deps) {
|
|
|
7215
8146
|
checkedAt
|
|
7216
8147
|
);
|
|
7217
8148
|
const response = { diagnostic, resultCount: results.length };
|
|
7218
|
-
return
|
|
8149
|
+
return writeJson2(res, 200, { result: response });
|
|
7219
8150
|
} catch (error) {
|
|
7220
8151
|
const diagnostic = classifyLiveSearchOutcome(contribution.id, { kind: "failure", error }, checkedAt);
|
|
7221
8152
|
const response = { diagnostic };
|
|
7222
|
-
return
|
|
8153
|
+
return writeJson2(res, 200, { result: response });
|
|
7223
8154
|
}
|
|
7224
8155
|
}
|
|
7225
8156
|
async function handleSearchQuery(req, res, deps) {
|
|
@@ -7282,18 +8213,18 @@ async function handleSearchQuery(req, res, deps) {
|
|
|
7282
8213
|
resultCount: sanitized.length,
|
|
7283
8214
|
results: sanitized
|
|
7284
8215
|
};
|
|
7285
|
-
return
|
|
8216
|
+
return writeJson2(res, 200, { result: response });
|
|
7286
8217
|
} catch (error) {
|
|
7287
8218
|
const diagnostic = classifyLiveSearchOutcome(providerId, { kind: "failure", error }, checkedAt);
|
|
7288
8219
|
const response = { diagnostic };
|
|
7289
|
-
return
|
|
8220
|
+
return writeJson2(res, 200, { result: response });
|
|
7290
8221
|
}
|
|
7291
8222
|
}
|
|
7292
8223
|
|
|
7293
8224
|
// src/admin/searchAdminView.ts
|
|
7294
8225
|
var API_KEY_PROVIDERS = /* @__PURE__ */ new Set(["tavily", "jina", "zhipu", "z.ai"]);
|
|
7295
8226
|
var BASIC_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["searxng"]);
|
|
7296
|
-
function
|
|
8227
|
+
function isRecord9(value) {
|
|
7297
8228
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7298
8229
|
}
|
|
7299
8230
|
function redactSearchServerConfig(search) {
|
|
@@ -7343,13 +8274,13 @@ function resolveSecretField(entry, field, stored) {
|
|
|
7343
8274
|
else delete entry[field];
|
|
7344
8275
|
}
|
|
7345
8276
|
function preserveSearchSecrets(incoming, current) {
|
|
7346
|
-
if (!
|
|
8277
|
+
if (!isRecord9(incoming)) return incoming;
|
|
7347
8278
|
const section = { ...incoming };
|
|
7348
8279
|
const providersValue = section["providers"];
|
|
7349
|
-
if (!
|
|
8280
|
+
if (!isRecord9(providersValue)) return section;
|
|
7350
8281
|
const providers = {};
|
|
7351
8282
|
for (const [id, entryValue] of Object.entries(providersValue)) {
|
|
7352
|
-
if (!
|
|
8283
|
+
if (!isRecord9(entryValue)) {
|
|
7353
8284
|
providers[id] = entryValue;
|
|
7354
8285
|
continue;
|
|
7355
8286
|
}
|
|
@@ -7427,7 +8358,8 @@ function parseKeyPolicyBody(body) {
|
|
|
7427
8358
|
var ENDPOINTS = /* @__PURE__ */ new Set(["chat", "responses", "messages", "gemini"]);
|
|
7428
8359
|
var TARGET_KINDS = /* @__PURE__ */ new Set(["account", "account-group", "account-pool", "provider"]);
|
|
7429
8360
|
var FALLBACKS = /* @__PURE__ */ new Set(["next", "fail", "global"]);
|
|
7430
|
-
|
|
8361
|
+
var EFFORT_MAX_LENGTH = 32;
|
|
8362
|
+
function isRecord10(value) {
|
|
7431
8363
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
7432
8364
|
}
|
|
7433
8365
|
function nonBlank(value) {
|
|
@@ -7447,7 +8379,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
7447
8379
|
const ids = /* @__PURE__ */ new Set();
|
|
7448
8380
|
raw.forEach((entry, index) => {
|
|
7449
8381
|
const path2 = `bindings[${index}]`;
|
|
7450
|
-
if (!
|
|
8382
|
+
if (!isRecord10(entry)) {
|
|
7451
8383
|
errors.push(`${path2} must be an object`);
|
|
7452
8384
|
return;
|
|
7453
8385
|
}
|
|
@@ -7476,12 +8408,18 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
7476
8408
|
} else if (entry.modelMappings.length > 100) {
|
|
7477
8409
|
errors.push(`${path2}.modelMappings cannot contain more than 100 entries`);
|
|
7478
8410
|
} else if (entry.modelMappings.some(
|
|
7479
|
-
(mapping) => !
|
|
8411
|
+
(mapping) => !isRecord10(mapping) || !nonBlank(mapping.source) || !nonBlank(mapping.target)
|
|
7480
8412
|
)) {
|
|
7481
8413
|
errors.push(`${path2}.modelMappings must contain non-empty source and target strings`);
|
|
8414
|
+
} else if (entry.modelMappings.some(
|
|
8415
|
+
(mapping) => mapping.effort !== void 0 && (typeof mapping.effort !== "string" || mapping.effort.trim() === "" || mapping.effort.trim().length > EFFORT_MAX_LENGTH)
|
|
8416
|
+
)) {
|
|
8417
|
+
errors.push(
|
|
8418
|
+
`${path2}.modelMappings effort must be a non-empty string of at most ${EFFORT_MAX_LENGTH} characters`
|
|
8419
|
+
);
|
|
7482
8420
|
}
|
|
7483
8421
|
}
|
|
7484
|
-
if (!
|
|
8422
|
+
if (!isRecord10(entry.target) || !TARGET_KINDS.has(String(entry.target.kind))) {
|
|
7485
8423
|
errors.push(`${path2}.target is invalid`);
|
|
7486
8424
|
} else {
|
|
7487
8425
|
if (!nonBlank(entry.target.providerId)) errors.push(`${path2}.target.providerId is required`);
|
|
@@ -7496,7 +8434,7 @@ function validateGatewayBindingsSegment(patch) {
|
|
|
7496
8434
|
}
|
|
7497
8435
|
}
|
|
7498
8436
|
if (entry.modelMap !== void 0) {
|
|
7499
|
-
if (!
|
|
8437
|
+
if (!isRecord10(entry.modelMap) || Object.values(entry.modelMap).some((value) => typeof value !== "string")) {
|
|
7500
8438
|
errors.push(`${path2}.modelMap must contain string values`);
|
|
7501
8439
|
}
|
|
7502
8440
|
}
|
|
@@ -7522,23 +8460,23 @@ import {
|
|
|
7522
8460
|
toVoucherInfo,
|
|
7523
8461
|
voucherCodePrefix
|
|
7524
8462
|
} from "@omnicross/core/outbound-api";
|
|
7525
|
-
function
|
|
8463
|
+
function writeJson3(res, status, body) {
|
|
7526
8464
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
7527
8465
|
res.end(JSON.stringify(body));
|
|
7528
8466
|
}
|
|
7529
8467
|
function writeErr2(res, status, message) {
|
|
7530
|
-
|
|
8468
|
+
writeJson3(res, status, { error: { type: "voucher_error", message } });
|
|
7531
8469
|
}
|
|
7532
|
-
function
|
|
7533
|
-
return new Promise((
|
|
8470
|
+
function readJsonBody4(req) {
|
|
8471
|
+
return new Promise((resolve12, reject) => {
|
|
7534
8472
|
const chunks = [];
|
|
7535
8473
|
req.on("data", (c) => chunks.push(c));
|
|
7536
8474
|
req.on("end", () => {
|
|
7537
8475
|
const raw = Buffer.concat(chunks).toString("utf8");
|
|
7538
|
-
if (!raw.trim()) return
|
|
8476
|
+
if (!raw.trim()) return resolve12({});
|
|
7539
8477
|
try {
|
|
7540
8478
|
const parsed = JSON.parse(raw);
|
|
7541
|
-
|
|
8479
|
+
resolve12(parsed && typeof parsed === "object" ? parsed : {});
|
|
7542
8480
|
} catch {
|
|
7543
8481
|
reject(new Error("invalid-json"));
|
|
7544
8482
|
}
|
|
@@ -7588,13 +8526,13 @@ async function handleVoucher(req, res, method, rest, deps) {
|
|
|
7588
8526
|
if (!voucherDb) return writeErr2(res, 501, "Voucher feature is not available");
|
|
7589
8527
|
if (method === "GET" && rest.length === 0) {
|
|
7590
8528
|
const rows = await voucherDb.voucherList();
|
|
7591
|
-
return
|
|
8529
|
+
return writeJson3(res, 200, { vouchers: rows.map(toVoucherInfo) });
|
|
7592
8530
|
}
|
|
7593
8531
|
if (method === "POST" && rest.length === 0) {
|
|
7594
8532
|
if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
|
|
7595
8533
|
let body;
|
|
7596
8534
|
try {
|
|
7597
|
-
body = await
|
|
8535
|
+
body = await readJsonBody4(req);
|
|
7598
8536
|
} catch {
|
|
7599
8537
|
return writeErr2(res, 400, "Invalid JSON in request body");
|
|
7600
8538
|
}
|
|
@@ -7607,7 +8545,7 @@ async function handleVoucher(req, res, method, rest, deps) {
|
|
|
7607
8545
|
codePrefix: voucherCodePrefix(code),
|
|
7608
8546
|
...parsed.input
|
|
7609
8547
|
});
|
|
7610
|
-
return
|
|
8548
|
+
return writeJson3(res, 201, {
|
|
7611
8549
|
id: created.id,
|
|
7612
8550
|
codePrefix: created.codePrefix,
|
|
7613
8551
|
type: created.type,
|
|
@@ -7620,7 +8558,7 @@ async function handleVoucher(req, res, method, rest, deps) {
|
|
|
7620
8558
|
if (method === "POST" && id && rest[1] === "revoke") {
|
|
7621
8559
|
if (!await voucherEnabled(deps)) return writeErr2(res, 403, "Voucher feature is disabled");
|
|
7622
8560
|
const ok = await voucherDb.voucherRevokeCas(id, Date.now());
|
|
7623
|
-
return
|
|
8561
|
+
return writeJson3(res, ok ? 200 : 409, { ok });
|
|
7624
8562
|
}
|
|
7625
8563
|
return writeErr2(res, 405, `method ${method} not allowed on voucher`);
|
|
7626
8564
|
}
|
|
@@ -7781,7 +8719,7 @@ import {
|
|
|
7781
8719
|
} from "@omnicross/core/outbound-api";
|
|
7782
8720
|
|
|
7783
8721
|
// src/ports/account-multi.ts
|
|
7784
|
-
import { randomUUID as
|
|
8722
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
7785
8723
|
var PROVIDER_KEYS = {
|
|
7786
8724
|
claude: { block: "claude", accounts: "claudeAccounts", active: "activeClaudeAccountId" },
|
|
7787
8725
|
codex: { block: "codex", accounts: "codexAccounts", active: "activeCodexAccountId" },
|
|
@@ -7855,7 +8793,7 @@ function migrateLazily(config) {
|
|
|
7855
8793
|
}
|
|
7856
8794
|
function addAccount(config, p, tokens, label) {
|
|
7857
8795
|
const accounts = [...getAccounts(config, p)];
|
|
7858
|
-
const id =
|
|
8796
|
+
const id = randomUUID4();
|
|
7859
8797
|
accounts.push({
|
|
7860
8798
|
id,
|
|
7861
8799
|
label: label ?? `Account ${accounts.length + 1}`,
|
|
@@ -8544,22 +9482,22 @@ async function handlePricingResolveConflicts(body, deps) {
|
|
|
8544
9482
|
}
|
|
8545
9483
|
|
|
8546
9484
|
// src/admin/accountAllowanceApi.ts
|
|
8547
|
-
function
|
|
9485
|
+
function writeJson4(res, status, body) {
|
|
8548
9486
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
8549
9487
|
res.end(JSON.stringify(body));
|
|
8550
9488
|
}
|
|
8551
9489
|
function writeError2(res, status, message) {
|
|
8552
|
-
|
|
9490
|
+
writeJson4(res, status, { error: { type: "account_allowance_error", message } });
|
|
8553
9491
|
}
|
|
8554
9492
|
function readJson2(req) {
|
|
8555
|
-
return new Promise((
|
|
9493
|
+
return new Promise((resolve12, reject) => {
|
|
8556
9494
|
const chunks = [];
|
|
8557
9495
|
req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
8558
9496
|
req.on("end", () => {
|
|
8559
9497
|
try {
|
|
8560
9498
|
const text = Buffer.concat(chunks).toString("utf8");
|
|
8561
9499
|
const parsed = text ? JSON.parse(text) : {};
|
|
8562
|
-
|
|
9500
|
+
resolve12(parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {});
|
|
8563
9501
|
} catch (error) {
|
|
8564
9502
|
reject(error);
|
|
8565
9503
|
}
|
|
@@ -8582,7 +9520,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
8582
9520
|
if (!service.getSchedulingStatus) {
|
|
8583
9521
|
return writeError2(res, 501, "allowance scheduling diagnostics are not available");
|
|
8584
9522
|
}
|
|
8585
|
-
return
|
|
9523
|
+
return writeJson4(res, 200, { scheduling: service.getSchedulingStatus() });
|
|
8586
9524
|
}
|
|
8587
9525
|
if (method === "GET") {
|
|
8588
9526
|
const params = query(req);
|
|
@@ -8593,7 +9531,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
8593
9531
|
}
|
|
8594
9532
|
const accountId = rest.length >= 2 ? rest[1] : params.get("accountId") ?? void 0;
|
|
8595
9533
|
const allowances = await service.list({ providerId, accountId });
|
|
8596
|
-
return
|
|
9534
|
+
return writeJson4(res, 200, { allowances });
|
|
8597
9535
|
}
|
|
8598
9536
|
if (method === "POST" && rest[0] === "refresh") {
|
|
8599
9537
|
const body = await readJson2(req);
|
|
@@ -8609,7 +9547,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
8609
9547
|
if (accountId && allowances2.length === 0) {
|
|
8610
9548
|
return writeError2(res, 404, `Codex account '${accountId}' not found`);
|
|
8611
9549
|
}
|
|
8612
|
-
return
|
|
9550
|
+
return writeJson4(res, 200, { allowances: allowances2 });
|
|
8613
9551
|
}
|
|
8614
9552
|
if (requestedProvider === "kimi") {
|
|
8615
9553
|
if (!service.refreshKimi) {
|
|
@@ -8619,7 +9557,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
8619
9557
|
if (accountId && allowances2.length === 0) {
|
|
8620
9558
|
return writeError2(res, 404, `Kimi account '${accountId}' not found`);
|
|
8621
9559
|
}
|
|
8622
|
-
return
|
|
9560
|
+
return writeJson4(res, 200, { allowances: allowances2 });
|
|
8623
9561
|
}
|
|
8624
9562
|
if (requestedProvider === "opencodego") {
|
|
8625
9563
|
if (!service.refreshOpenCodeGo) {
|
|
@@ -8629,7 +9567,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
8629
9567
|
if (accountId && allowances2.length === 0) {
|
|
8630
9568
|
return writeError2(res, 404, `OpenCodeGo account '${accountId}' not found`);
|
|
8631
9569
|
}
|
|
8632
|
-
return
|
|
9570
|
+
return writeJson4(res, 200, { allowances: allowances2 });
|
|
8633
9571
|
}
|
|
8634
9572
|
if (requestedProvider === "copilot") {
|
|
8635
9573
|
if (!service.refreshCopilot) {
|
|
@@ -8639,7 +9577,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
8639
9577
|
if (accountId && allowances2.length === 0) {
|
|
8640
9578
|
return writeError2(res, 404, `Copilot account '${accountId}' not found`);
|
|
8641
9579
|
}
|
|
8642
|
-
return
|
|
9580
|
+
return writeJson4(res, 200, { allowances: allowances2 });
|
|
8643
9581
|
}
|
|
8644
9582
|
if (requestedProvider === "grok") {
|
|
8645
9583
|
if (!service.refreshGrok) {
|
|
@@ -8649,7 +9587,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
8649
9587
|
if (accountId && allowances2.length === 0) {
|
|
8650
9588
|
return writeError2(res, 404, `Grok account '${accountId}' not found`);
|
|
8651
9589
|
}
|
|
8652
|
-
return
|
|
9590
|
+
return writeJson4(res, 200, { allowances: allowances2 });
|
|
8653
9591
|
}
|
|
8654
9592
|
if (requestedProvider === "antigravity") {
|
|
8655
9593
|
if (!service.refreshAntigravity) {
|
|
@@ -8659,7 +9597,7 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
8659
9597
|
if (accountId && allowances2.length === 0) {
|
|
8660
9598
|
return writeError2(res, 404, `Antigravity account '${accountId}' not found`);
|
|
8661
9599
|
}
|
|
8662
|
-
return
|
|
9600
|
+
return writeJson4(res, 200, { allowances: allowances2 });
|
|
8663
9601
|
}
|
|
8664
9602
|
if (requestedProvider === "gemini") {
|
|
8665
9603
|
if (!service.refreshGemini) {
|
|
@@ -8669,13 +9607,13 @@ async function handleAccountAllowanceApi(req, res, method, rest, service) {
|
|
|
8669
9607
|
if (accountId && allowances2.length === 0) {
|
|
8670
9608
|
return writeError2(res, 404, `Gemini account '${accountId}' not found`);
|
|
8671
9609
|
}
|
|
8672
|
-
return
|
|
9610
|
+
return writeJson4(res, 200, { allowances: allowances2 });
|
|
8673
9611
|
}
|
|
8674
9612
|
const allowances = await service.refreshClaude(accountId);
|
|
8675
9613
|
if (accountId && allowances.length === 0) {
|
|
8676
9614
|
return writeError2(res, 404, `Claude account '${accountId}' not found`);
|
|
8677
9615
|
}
|
|
8678
|
-
return
|
|
9616
|
+
return writeJson4(res, 200, { allowances });
|
|
8679
9617
|
}
|
|
8680
9618
|
return writeError2(res, 405, `method ${method} not allowed on account allowances`);
|
|
8681
9619
|
}
|
|
@@ -8687,14 +9625,14 @@ import {
|
|
|
8687
9625
|
} from "@omnicross/core/pipeline/AccountRouteActivity";
|
|
8688
9626
|
import { getSharedOverloadCounter } from "@omnicross/core/pipeline/ServerOverloadCounter";
|
|
8689
9627
|
function readBody(req) {
|
|
8690
|
-
return new Promise((
|
|
9628
|
+
return new Promise((resolve12, reject) => {
|
|
8691
9629
|
const chunks = [];
|
|
8692
9630
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
8693
|
-
req.on("end", () =>
|
|
9631
|
+
req.on("end", () => resolve12(Buffer.concat(chunks).toString("utf8")));
|
|
8694
9632
|
req.on("error", reject);
|
|
8695
9633
|
});
|
|
8696
9634
|
}
|
|
8697
|
-
async function
|
|
9635
|
+
async function readJsonBody5(req) {
|
|
8698
9636
|
const raw = await readBody(req);
|
|
8699
9637
|
if (!raw.trim()) return {};
|
|
8700
9638
|
try {
|
|
@@ -8704,12 +9642,12 @@ async function readJsonBody4(req) {
|
|
|
8704
9642
|
return {};
|
|
8705
9643
|
}
|
|
8706
9644
|
}
|
|
8707
|
-
function
|
|
9645
|
+
function writeJson5(res, status, body) {
|
|
8708
9646
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
8709
9647
|
res.end(JSON.stringify(body));
|
|
8710
9648
|
}
|
|
8711
|
-
function
|
|
8712
|
-
|
|
9649
|
+
function writeJsonError2(res, status, message) {
|
|
9650
|
+
writeJson5(res, status, { error: { type: "admin_api_error", message } });
|
|
8713
9651
|
}
|
|
8714
9652
|
function maskProviderApiKey(apiKey) {
|
|
8715
9653
|
if (!apiKey) return "";
|
|
@@ -8730,7 +9668,7 @@ function toKeyInfo(row) {
|
|
|
8730
9668
|
lastUsedAt: row.lastUsedAt,
|
|
8731
9669
|
revoked: row.revokedAt !== null,
|
|
8732
9670
|
kind: row.kind,
|
|
8733
|
-
allowedEndpoints: [...
|
|
9671
|
+
allowedEndpoints: [...effectiveOutboundPermissions3(row.allowedEndpoints)],
|
|
8734
9672
|
legacyPermissions: row.allowedEndpoints === void 0,
|
|
8735
9673
|
loopbackOnly: row.loopbackOnly,
|
|
8736
9674
|
maxConcurrency: row.maxConcurrency,
|
|
@@ -8748,7 +9686,12 @@ function toKeyInfo(row) {
|
|
|
8748
9686
|
// Per-key model restriction (#6) — secret-free scalars the UI pre-fills.
|
|
8749
9687
|
enableModelRestriction: row.enableModelRestriction,
|
|
8750
9688
|
restrictionMode: row.restrictionMode,
|
|
8751
|
-
restrictedModels: row.restrictedModels
|
|
9689
|
+
restrictedModels: row.restrictedModels,
|
|
9690
|
+
// Direct upstream passthrough target (key→upstream binding) — references
|
|
9691
|
+
// only, never a credential. Absent = the key is served by the downstream
|
|
9692
|
+
// routes. `boundUpstreamProviderId` is the legacy first-cut shape.
|
|
9693
|
+
boundUpstream: row.boundUpstream,
|
|
9694
|
+
boundUpstreamProviderId: row.boundUpstreamProviderId
|
|
8752
9695
|
};
|
|
8753
9696
|
}
|
|
8754
9697
|
function toProviderView(row) {
|
|
@@ -8842,10 +9785,10 @@ async function handleAdminApi(req, res, path2, deps) {
|
|
|
8842
9785
|
case "pricing":
|
|
8843
9786
|
return await handlePricing(req, res, method, rest, deps);
|
|
8844
9787
|
default:
|
|
8845
|
-
return
|
|
9788
|
+
return writeJsonError2(res, 404, `unknown admin resource '${resource}'`);
|
|
8846
9789
|
}
|
|
8847
9790
|
} catch (err9) {
|
|
8848
|
-
|
|
9791
|
+
writeJsonError2(res, 500, err9 instanceof Error ? err9.message : String(err9));
|
|
8849
9792
|
}
|
|
8850
9793
|
}
|
|
8851
9794
|
function requestQuery(req) {
|
|
@@ -8854,35 +9797,35 @@ function requestQuery(req) {
|
|
|
8854
9797
|
return new URLSearchParams(qIdx >= 0 ? raw.slice(qIdx + 1) : "");
|
|
8855
9798
|
}
|
|
8856
9799
|
function writeResult(res, result) {
|
|
8857
|
-
|
|
9800
|
+
writeJson5(res, result.status, result.body);
|
|
8858
9801
|
}
|
|
8859
9802
|
async function handleUsage(req, res, method, rest, deps) {
|
|
8860
|
-
if (method !== "GET") return
|
|
9803
|
+
if (method !== "GET") return writeJsonError2(res, 405, `method ${method} not allowed on usage`);
|
|
8861
9804
|
return writeResult(res, await handleUsageGet(rest[0], requestQuery(req), deps));
|
|
8862
9805
|
}
|
|
8863
9806
|
async function handleDashboardRoute(res, method, deps) {
|
|
8864
|
-
if (method !== "GET") return
|
|
9807
|
+
if (method !== "GET") return writeJsonError2(res, 405, `method ${method} not allowed on dashboard`);
|
|
8865
9808
|
const result = await handleDashboard(deps);
|
|
8866
|
-
return
|
|
9809
|
+
return writeJson5(res, result.status, result.body);
|
|
8867
9810
|
}
|
|
8868
9811
|
async function handlePricing(req, res, method, rest, deps) {
|
|
8869
9812
|
if (rest.length === 0) {
|
|
8870
9813
|
if (method === "GET") return writeResult(res, await handlePricingList(deps));
|
|
8871
9814
|
if (method === "PUT") {
|
|
8872
|
-
return writeResult(res, await handlePricingUpsert(await
|
|
9815
|
+
return writeResult(res, await handlePricingUpsert(await readJsonBody5(req), deps));
|
|
8873
9816
|
}
|
|
8874
9817
|
if (method === "DELETE") {
|
|
8875
9818
|
return writeResult(res, await handlePricingDelete(requestQuery(req), deps));
|
|
8876
9819
|
}
|
|
8877
|
-
return
|
|
9820
|
+
return writeJsonError2(res, 405, `method ${method} not allowed on pricing`);
|
|
8878
9821
|
}
|
|
8879
9822
|
if (method === "POST" && rest.length === 1 && rest[0] === "fetch-latest") {
|
|
8880
9823
|
return writeResult(res, await handlePricingFetchLatest(deps));
|
|
8881
9824
|
}
|
|
8882
9825
|
if (method === "POST" && rest.length === 1 && rest[0] === "resolve-conflicts") {
|
|
8883
|
-
return writeResult(res, await handlePricingResolveConflicts(await
|
|
9826
|
+
return writeResult(res, await handlePricingResolveConflicts(await readJsonBody5(req), deps));
|
|
8884
9827
|
}
|
|
8885
|
-
return
|
|
9828
|
+
return writeJsonError2(res, 404, `unknown pricing route '${rest.join("/")}'`);
|
|
8886
9829
|
}
|
|
8887
9830
|
function migrationDeps(deps) {
|
|
8888
9831
|
return {
|
|
@@ -8893,16 +9836,16 @@ function migrationDeps(deps) {
|
|
|
8893
9836
|
};
|
|
8894
9837
|
}
|
|
8895
9838
|
async function handleMigrationExport(req, res, method, deps) {
|
|
8896
|
-
if (method !== "POST") return
|
|
8897
|
-
const body = await
|
|
9839
|
+
if (method !== "POST") return writeJsonError2(res, 405, `method ${method} not allowed on export`);
|
|
9840
|
+
const body = await readJsonBody5(req);
|
|
8898
9841
|
const result = await handleExport(body, migrationDeps(deps));
|
|
8899
|
-
return
|
|
9842
|
+
return writeJson5(res, result.status, result.body);
|
|
8900
9843
|
}
|
|
8901
9844
|
async function handleMigrationImport(req, res, method, deps) {
|
|
8902
|
-
if (method !== "POST") return
|
|
8903
|
-
const body = await
|
|
9845
|
+
if (method !== "POST") return writeJsonError2(res, 405, `method ${method} not allowed on import`);
|
|
9846
|
+
const body = await readJsonBody5(req);
|
|
8904
9847
|
const result = await handleImport(body, migrationDeps(deps));
|
|
8905
|
-
return
|
|
9848
|
+
return writeJson5(res, result.status, result.body);
|
|
8906
9849
|
}
|
|
8907
9850
|
async function handleProviders(req, res, method, rest, deps) {
|
|
8908
9851
|
const cfg = loadConfig(deps.configPath);
|
|
@@ -8935,52 +9878,52 @@ async function handleProviders(req, res, method, rest, deps) {
|
|
|
8935
9878
|
}
|
|
8936
9879
|
if (method === "GET" && rest.length === 2 && rest[1] === "reveal-key") {
|
|
8937
9880
|
const row = cfg.providers.find((p) => p.id === rest[0]);
|
|
8938
|
-
if (!row) return
|
|
8939
|
-
return
|
|
9881
|
+
if (!row) return writeJsonError2(res, 404, `provider '${rest[0]}' not found`);
|
|
9882
|
+
return writeJson5(res, 200, { apiKey: row.apiKey ?? "" });
|
|
8940
9883
|
}
|
|
8941
9884
|
if (method === "GET") {
|
|
8942
|
-
return
|
|
9885
|
+
return writeJson5(res, 200, { providers: cfg.providers.map(toProviderView) });
|
|
8943
9886
|
}
|
|
8944
9887
|
if (method === "POST") {
|
|
8945
|
-
const body = await
|
|
9888
|
+
const body = await readJsonBody5(req);
|
|
8946
9889
|
const provider = parseProviderInput(body, void 0);
|
|
8947
|
-
if (!provider) return
|
|
9890
|
+
if (!provider) return writeJsonError2(res, 400, "invalid provider (id, apiFormat, baseUrl required)");
|
|
8948
9891
|
if (cfg.providers.some((p) => p.id === provider.id)) {
|
|
8949
|
-
return
|
|
9892
|
+
return writeJsonError2(res, 409, `provider '${provider.id}' already exists`);
|
|
8950
9893
|
}
|
|
8951
9894
|
cfg.providers.push(provider);
|
|
8952
9895
|
persistProviders(cfg, deps);
|
|
8953
|
-
return
|
|
9896
|
+
return writeJson5(res, 201, { provider: toProviderView(provider) });
|
|
8954
9897
|
}
|
|
8955
9898
|
const id = rest[0];
|
|
8956
|
-
if (!id) return
|
|
9899
|
+
if (!id) return writeJsonError2(res, 400, "provider id required in path");
|
|
8957
9900
|
const idx = cfg.providers.findIndex((p) => p.id === id);
|
|
8958
|
-
if (idx < 0) return
|
|
9901
|
+
if (idx < 0) return writeJsonError2(res, 404, `provider '${id}' not found`);
|
|
8959
9902
|
if (method === "PUT") {
|
|
8960
|
-
const body = await
|
|
9903
|
+
const body = await readJsonBody5(req);
|
|
8961
9904
|
const existing = cfg.providers[idx];
|
|
8962
9905
|
const updated = parseProviderInput(body, existing);
|
|
8963
|
-
if (!updated) return
|
|
9906
|
+
if (!updated) return writeJsonError2(res, 400, "invalid provider (apiFormat, baseUrl required)");
|
|
8964
9907
|
cfg.providers[idx] = updated;
|
|
8965
9908
|
persistProviders(cfg, deps);
|
|
8966
|
-
return
|
|
9909
|
+
return writeJson5(res, 200, { provider: toProviderView(updated) });
|
|
8967
9910
|
}
|
|
8968
9911
|
if (method === "DELETE") {
|
|
8969
9912
|
cfg.providers.splice(idx, 1);
|
|
8970
9913
|
persistProviders(cfg, deps);
|
|
8971
|
-
return
|
|
9914
|
+
return writeJson5(res, 200, { ok: true });
|
|
8972
9915
|
}
|
|
8973
|
-
return
|
|
9916
|
+
return writeJsonError2(res, 405, `method ${method} not allowed on providers`);
|
|
8974
9917
|
}
|
|
8975
9918
|
function persistProviders(cfg, deps) {
|
|
8976
9919
|
saveConfig(deps.configPath, cfg);
|
|
8977
9920
|
deps.llmConfig.reload(cfg);
|
|
8978
9921
|
}
|
|
8979
9922
|
async function handleProviderReorder(req, res, cfg, deps) {
|
|
8980
|
-
const body = await
|
|
9923
|
+
const body = await readJsonBody5(req);
|
|
8981
9924
|
const rawOrder = body["order"];
|
|
8982
9925
|
if (!Array.isArray(rawOrder)) {
|
|
8983
|
-
return
|
|
9926
|
+
return writeJsonError2(res, 400, "reorder requires { order: string[] }");
|
|
8984
9927
|
}
|
|
8985
9928
|
const order = rawOrder.filter((x) => typeof x === "string");
|
|
8986
9929
|
const byId = new Map(cfg.providers.map((p) => [p.id, p]));
|
|
@@ -9001,17 +9944,17 @@ async function handleProviderReorder(req, res, cfg, deps) {
|
|
|
9001
9944
|
}
|
|
9002
9945
|
cfg.providers = reordered;
|
|
9003
9946
|
persistProviders(cfg, deps);
|
|
9004
|
-
return
|
|
9947
|
+
return writeJson5(res, 200, { ok: true, providers: cfg.providers.map(toProviderView) });
|
|
9005
9948
|
}
|
|
9006
9949
|
function expandRowExtraHeaders(row) {
|
|
9007
9950
|
return mergeExtraHeaders({}, row.extraHeaders);
|
|
9008
9951
|
}
|
|
9009
9952
|
async function handleDiscoverModels(res, id, cfg) {
|
|
9010
|
-
if (!id) return
|
|
9953
|
+
if (!id) return writeJsonError2(res, 400, "provider id required in path");
|
|
9011
9954
|
const row = cfg.providers.find((p) => p.id === id);
|
|
9012
|
-
if (!row) return
|
|
9955
|
+
if (!row) return writeJsonError2(res, 404, `provider '${id}' not found`);
|
|
9013
9956
|
if (row.apiFormat !== "openai" && row.apiFormat !== "openai-response") {
|
|
9014
|
-
return
|
|
9957
|
+
return writeJson5(res, 200, { models: [], unsupportedFormat: true });
|
|
9015
9958
|
}
|
|
9016
9959
|
const resolvedKey = resolveEnvKey(row.apiKey);
|
|
9017
9960
|
const base = row.baseUrl.replace(/\/+$/, "");
|
|
@@ -9020,6 +9963,7 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
9020
9963
|
const headers = { Accept: "application/json" };
|
|
9021
9964
|
if (resolvedKey) headers["Authorization"] = `Bearer ${resolvedKey}`;
|
|
9022
9965
|
Object.assign(headers, expandRowExtraHeaders(row));
|
|
9966
|
+
applyAdminProbeIdentity(headers, row);
|
|
9023
9967
|
const response = await fetchUpstream10(url, { method: "GET", headers }, { providerId: "byo" });
|
|
9024
9968
|
if (!response.ok) {
|
|
9025
9969
|
const text = await response.text().catch(() => "");
|
|
@@ -9029,32 +9973,32 @@ async function handleDiscoverModels(res, id, cfg) {
|
|
|
9029
9973
|
message = parsed?.error?.message || parsed?.message || message;
|
|
9030
9974
|
} catch {
|
|
9031
9975
|
}
|
|
9032
|
-
return
|
|
9976
|
+
return writeJson5(res, 200, {
|
|
9033
9977
|
models: [],
|
|
9034
9978
|
error: `discovery failed (${response.status})${message ? `: ${message}` : ""}`
|
|
9035
9979
|
});
|
|
9036
9980
|
}
|
|
9037
9981
|
const data = await response.json();
|
|
9038
9982
|
const models = Array.isArray(data?.data) ? data.data.map((m) => typeof m?.id === "string" ? m.id : "").filter((m) => m.length > 0) : [];
|
|
9039
|
-
return
|
|
9983
|
+
return writeJson5(res, 200, { models });
|
|
9040
9984
|
} catch (err9) {
|
|
9041
9985
|
const message = err9 instanceof Error ? err9.message : String(err9);
|
|
9042
|
-
return
|
|
9986
|
+
return writeJson5(res, 200, { models: [], error: `discovery failed: ${message}` });
|
|
9043
9987
|
}
|
|
9044
9988
|
}
|
|
9045
9989
|
async function handleTestModel(req, res, id, cfg) {
|
|
9046
|
-
if (!id) return
|
|
9990
|
+
if (!id) return writeJsonError2(res, 400, "provider id required in path");
|
|
9047
9991
|
const row = cfg.providers.find((p) => p.id === id);
|
|
9048
|
-
if (!row) return
|
|
9049
|
-
const body = await
|
|
9992
|
+
if (!row) return writeJsonError2(res, 404, `provider '${id}' not found`);
|
|
9993
|
+
const body = await readJsonBody5(req);
|
|
9050
9994
|
const model = typeof body["model"] === "string" ? body["model"].trim() : "";
|
|
9051
|
-
if (!model) return
|
|
9995
|
+
if (!model) return writeJsonError2(res, 400, "test requires a { model } string");
|
|
9052
9996
|
if (row.apiFormat === "gemini") {
|
|
9053
|
-
return
|
|
9997
|
+
return writeJson5(res, 200, { ok: false, unsupportedFormat: true });
|
|
9054
9998
|
}
|
|
9055
9999
|
const resolvedKey = resolveEnvKey(row.apiKey);
|
|
9056
10000
|
if (!resolvedKey) {
|
|
9057
|
-
return
|
|
10001
|
+
return writeJson5(res, 200, { ok: false, message: "no API key configured for this provider" });
|
|
9058
10002
|
}
|
|
9059
10003
|
let url = row.baseUrl.replace(/\/+$/, "");
|
|
9060
10004
|
const prompt = "Reply with the single word: OK.";
|
|
@@ -9078,6 +10022,7 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
9078
10022
|
};
|
|
9079
10023
|
}
|
|
9080
10024
|
Object.assign(headers, expandRowExtraHeaders(row));
|
|
10025
|
+
applyAdminProbeIdentity(headers, row);
|
|
9081
10026
|
const startedAt = Date.now();
|
|
9082
10027
|
try {
|
|
9083
10028
|
const response = await fetchUpstream10(
|
|
@@ -9094,9 +10039,9 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
9094
10039
|
message = parsed?.error?.message || parsed?.message || message;
|
|
9095
10040
|
} catch {
|
|
9096
10041
|
}
|
|
9097
|
-
return
|
|
10042
|
+
return writeJson5(res, 200, { ok: false, status: response.status, latencyMs, message });
|
|
9098
10043
|
}
|
|
9099
|
-
return
|
|
10044
|
+
return writeJson5(res, 200, {
|
|
9100
10045
|
ok: true,
|
|
9101
10046
|
status: response.status,
|
|
9102
10047
|
latencyMs,
|
|
@@ -9104,7 +10049,7 @@ async function handleTestModel(req, res, id, cfg) {
|
|
|
9104
10049
|
});
|
|
9105
10050
|
} catch (err9) {
|
|
9106
10051
|
const message = err9 instanceof Error ? err9.message : String(err9);
|
|
9107
|
-
return
|
|
10052
|
+
return writeJson5(res, 200, { ok: false, latencyMs: Date.now() - startedAt, message });
|
|
9108
10053
|
}
|
|
9109
10054
|
}
|
|
9110
10055
|
function extractSampleText(text, apiFormat) {
|
|
@@ -9141,9 +10086,9 @@ function toPoolKeyView(row, cooldown, deps) {
|
|
|
9141
10086
|
});
|
|
9142
10087
|
}
|
|
9143
10088
|
async function handleProviderKeys(res, id, cfg, deps) {
|
|
9144
|
-
if (!id) return
|
|
10089
|
+
if (!id) return writeJsonError2(res, 400, "provider id required in path");
|
|
9145
10090
|
const row = cfg.providers.find((p) => p.id === id);
|
|
9146
|
-
if (!row) return
|
|
10091
|
+
if (!row) return writeJsonError2(res, 404, `provider '${id}' not found`);
|
|
9147
10092
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
9148
10093
|
const views = toPoolKeyView(row, cooldown, deps);
|
|
9149
10094
|
if (deps.providerKeyQuota) {
|
|
@@ -9155,19 +10100,19 @@ async function handleProviderKeys(res, id, cfg, deps) {
|
|
|
9155
10100
|
if (settled.status === "fulfilled" && settled.value) view.quota = settled.value;
|
|
9156
10101
|
});
|
|
9157
10102
|
}
|
|
9158
|
-
return
|
|
10103
|
+
return writeJson5(res, 200, { keys: views });
|
|
9159
10104
|
}
|
|
9160
10105
|
async function handleProviderKeyQuotaRefresh(res, id, keyId, cfg, deps) {
|
|
9161
|
-
if (!deps.providerKeyQuota) return
|
|
9162
|
-
if (!id || !keyId) return
|
|
10106
|
+
if (!deps.providerKeyQuota) return writeJsonError2(res, 501, "provider key quota is not available");
|
|
10107
|
+
if (!id || !keyId) return writeJsonError2(res, 400, "provider id and key id required in path");
|
|
9163
10108
|
const row = cfg.providers.find((p) => p.id === id);
|
|
9164
|
-
if (!row) return
|
|
10109
|
+
if (!row) return writeJsonError2(res, 404, `provider '${id}' not found`);
|
|
9165
10110
|
try {
|
|
9166
10111
|
const quota = await deps.providerKeyQuota.quotaFor(row, keyId, { force: true });
|
|
9167
|
-
if (!quota) return
|
|
9168
|
-
return
|
|
10112
|
+
if (!quota) return writeJsonError2(res, 404, `no quota endpoint for key '${keyId}'`);
|
|
10113
|
+
return writeJson5(res, 200, { quota });
|
|
9169
10114
|
} catch {
|
|
9170
|
-
return
|
|
10115
|
+
return writeJsonError2(res, 502, "quota refresh failed");
|
|
9171
10116
|
}
|
|
9172
10117
|
}
|
|
9173
10118
|
function parsePoolKeyInput(body, existing) {
|
|
@@ -9184,12 +10129,12 @@ function parsePoolKeyInput(body, existing) {
|
|
|
9184
10129
|
return out;
|
|
9185
10130
|
}
|
|
9186
10131
|
async function handleAddProviderKey(req, res, id, cfg, deps) {
|
|
9187
|
-
if (!id) return
|
|
10132
|
+
if (!id) return writeJsonError2(res, 400, "provider id required in path");
|
|
9188
10133
|
const idx = cfg.providers.findIndex((p) => p.id === id);
|
|
9189
|
-
if (idx < 0) return
|
|
9190
|
-
const body = await
|
|
10134
|
+
if (idx < 0) return writeJsonError2(res, 404, `provider '${id}' not found`);
|
|
10135
|
+
const body = await readJsonBody5(req);
|
|
9191
10136
|
const parsed = parsePoolKeyInput(body);
|
|
9192
|
-
if (!parsed.apiKey) return
|
|
10137
|
+
if (!parsed.apiKey) return writeJsonError2(res, 400, "add requires a non-empty apiKey");
|
|
9193
10138
|
const keyId = `key-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
9194
10139
|
const entry = { id: keyId, apiKey: parsed.apiKey };
|
|
9195
10140
|
if (parsed.label !== void 0) entry.label = parsed.label;
|
|
@@ -9199,17 +10144,17 @@ async function handleAddProviderKey(req, res, id, cfg, deps) {
|
|
|
9199
10144
|
row.apiKeys = [...row.apiKeys ?? [], entry];
|
|
9200
10145
|
persistProviders(cfg, deps);
|
|
9201
10146
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
9202
|
-
return
|
|
10147
|
+
return writeJson5(res, 201, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
9203
10148
|
}
|
|
9204
10149
|
async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
9205
|
-
if (!id) return
|
|
9206
|
-
if (!keyId) return
|
|
10150
|
+
if (!id) return writeJsonError2(res, 400, "provider id required in path");
|
|
10151
|
+
if (!keyId) return writeJsonError2(res, 400, "key id required in path");
|
|
9207
10152
|
const idx = cfg.providers.findIndex((p) => p.id === id);
|
|
9208
|
-
if (idx < 0) return
|
|
10153
|
+
if (idx < 0) return writeJsonError2(res, 404, `provider '${id}' not found`);
|
|
9209
10154
|
const row = cfg.providers[idx];
|
|
9210
10155
|
const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
|
|
9211
|
-
if (keyIdx < 0) return
|
|
9212
|
-
const body = await
|
|
10156
|
+
if (keyIdx < 0) return writeJsonError2(res, 404, `pool key '${keyId}' not found`);
|
|
10157
|
+
const body = await readJsonBody5(req);
|
|
9213
10158
|
const existing = row.apiKeys[keyIdx];
|
|
9214
10159
|
const parsed = parsePoolKeyInput(body, existing);
|
|
9215
10160
|
const entry = { id: keyId, apiKey: parsed.apiKey || existing.apiKey };
|
|
@@ -9219,35 +10164,35 @@ async function handleUpdateProviderKey(req, res, id, keyId, cfg, deps) {
|
|
|
9219
10164
|
row.apiKeys[keyIdx] = entry;
|
|
9220
10165
|
persistProviders(cfg, deps);
|
|
9221
10166
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
9222
|
-
return
|
|
10167
|
+
return writeJson5(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
9223
10168
|
}
|
|
9224
10169
|
async function handleDeleteProviderKey(res, id, keyId, cfg, deps) {
|
|
9225
|
-
if (!id) return
|
|
9226
|
-
if (!keyId) return
|
|
10170
|
+
if (!id) return writeJsonError2(res, 400, "provider id required in path");
|
|
10171
|
+
if (!keyId) return writeJsonError2(res, 400, "key id required in path");
|
|
9227
10172
|
const idx = cfg.providers.findIndex((p) => p.id === id);
|
|
9228
|
-
if (idx < 0) return
|
|
10173
|
+
if (idx < 0) return writeJsonError2(res, 404, `provider '${id}' not found`);
|
|
9229
10174
|
const row = cfg.providers[idx];
|
|
9230
10175
|
const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
|
|
9231
|
-
if (keyIdx < 0) return
|
|
10176
|
+
if (keyIdx < 0) return writeJsonError2(res, 404, `pool key '${keyId}' not found`);
|
|
9232
10177
|
row.apiKeys.splice(keyIdx, 1);
|
|
9233
10178
|
if (row.apiKeys.length === 0) row.apiKeys = void 0;
|
|
9234
10179
|
persistProviders(cfg, deps);
|
|
9235
10180
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
9236
|
-
return
|
|
10181
|
+
return writeJson5(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
9237
10182
|
}
|
|
9238
10183
|
async function handleToggleProviderKey(req, res, id, keyId, cfg, deps) {
|
|
9239
|
-
if (!id) return
|
|
9240
|
-
if (!keyId) return
|
|
10184
|
+
if (!id) return writeJsonError2(res, 400, "provider id required in path");
|
|
10185
|
+
if (!keyId) return writeJsonError2(res, 400, "key id required in path");
|
|
9241
10186
|
const idx = cfg.providers.findIndex((p) => p.id === id);
|
|
9242
|
-
if (idx < 0) return
|
|
10187
|
+
if (idx < 0) return writeJsonError2(res, 404, `provider '${id}' not found`);
|
|
9243
10188
|
const row = cfg.providers[idx];
|
|
9244
10189
|
const keyIdx = (row.apiKeys ?? []).findIndex((k) => k.id === keyId);
|
|
9245
|
-
if (keyIdx < 0) return
|
|
9246
|
-
const body = await
|
|
10190
|
+
if (keyIdx < 0) return writeJsonError2(res, 404, `pool key '${keyId}' not found`);
|
|
10191
|
+
const body = await readJsonBody5(req);
|
|
9247
10192
|
row.apiKeys[keyIdx].enabled = Boolean(body["enabled"]);
|
|
9248
10193
|
persistProviders(cfg, deps);
|
|
9249
10194
|
const cooldown = await deps.apiKeyPool.getKeyHealth(id);
|
|
9250
|
-
return
|
|
10195
|
+
return writeJson5(res, 200, { keys: toPoolKeyView(row, cooldown, deps) });
|
|
9251
10196
|
}
|
|
9252
10197
|
function parseApiKeysInput(raw, existing) {
|
|
9253
10198
|
if (!Array.isArray(raw)) return existing;
|
|
@@ -9419,7 +10364,7 @@ function parseProviderInput(body, existing) {
|
|
|
9419
10364
|
};
|
|
9420
10365
|
}
|
|
9421
10366
|
function handlePresets(res, method) {
|
|
9422
|
-
if (method !== "GET") return
|
|
10367
|
+
if (method !== "GET") return writeJsonError2(res, 405, `method ${method} not allowed on presets`);
|
|
9423
10368
|
const { mappable, excluded } = listMappablePresets();
|
|
9424
10369
|
const presets = mappable.map((p) => ({
|
|
9425
10370
|
id: p.id,
|
|
@@ -9438,13 +10383,13 @@ function handlePresets(res, method) {
|
|
|
9438
10383
|
// row (the write gateway re-validates via the shared allowlist).
|
|
9439
10384
|
extraHeaders: p.extraHeaders
|
|
9440
10385
|
}));
|
|
9441
|
-
return
|
|
10386
|
+
return writeJson5(res, 200, { presets, excluded });
|
|
9442
10387
|
}
|
|
9443
10388
|
async function handleKeys(req, res, method, rest, deps) {
|
|
9444
10389
|
if (method === "GET" && rest.length === 0) {
|
|
9445
10390
|
const rows = await deps.keyDb.outboundApiKeysList();
|
|
9446
10391
|
const reader = deps.keySpendReader;
|
|
9447
|
-
if (!reader) return
|
|
10392
|
+
if (!reader) return writeJson5(res, 200, { keys: rows.map(toKeyInfo) });
|
|
9448
10393
|
const now = Date.now();
|
|
9449
10394
|
const keys = await Promise.all(
|
|
9450
10395
|
rows.map(async (row) => {
|
|
@@ -9456,13 +10401,13 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
9456
10401
|
return info;
|
|
9457
10402
|
})
|
|
9458
10403
|
);
|
|
9459
|
-
return
|
|
10404
|
+
return writeJson5(res, 200, { keys });
|
|
9460
10405
|
}
|
|
9461
10406
|
if (method === "POST" && rest.length === 0) {
|
|
9462
|
-
const body = await
|
|
10407
|
+
const body = await readJsonBody5(req);
|
|
9463
10408
|
const name = typeof body["name"] === "string" && body["name"].trim() ? body["name"].trim() : "key";
|
|
9464
10409
|
const created = await createNamedKey(deps.keyDb, name);
|
|
9465
|
-
return
|
|
10410
|
+
return writeJson5(res, 201, {
|
|
9466
10411
|
id: created.id,
|
|
9467
10412
|
name: created.name,
|
|
9468
10413
|
keyPrefix: created.keyPrefix,
|
|
@@ -9473,10 +10418,10 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
9473
10418
|
}
|
|
9474
10419
|
if (method === "GET" && rest.length === 2 && rest[1] === "reveal") {
|
|
9475
10420
|
const revealed = await deps.keyDb.outboundApiKeysReveal(rest[0]);
|
|
9476
|
-
if (revealed !== null) return
|
|
10421
|
+
if (revealed !== null) return writeJson5(res, 200, { key: revealed });
|
|
9477
10422
|
const exists = (await deps.keyDb.outboundApiKeysList()).some((r) => r.id === rest[0]);
|
|
9478
|
-
if (!exists) return
|
|
9479
|
-
return
|
|
10423
|
+
if (!exists) return writeJsonError2(res, 404, `key '${rest[0]}' not found`);
|
|
10424
|
+
return writeJsonError2(
|
|
9480
10425
|
res,
|
|
9481
10426
|
409,
|
|
9482
10427
|
`key '${rest[0]}' is not revealable (created before revealable key storage)`
|
|
@@ -9486,55 +10431,55 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
9486
10431
|
const action = rest[1];
|
|
9487
10432
|
if (method === "POST" && id && action === "revoke") {
|
|
9488
10433
|
const bound = await integrationKeyRequirement(deps, id);
|
|
9489
|
-
if (bound) return
|
|
10434
|
+
if (bound) return writeJsonError2(res, 409, bound);
|
|
9490
10435
|
const ok = await deps.keyDb.outboundApiKeysRevoke(id);
|
|
9491
|
-
return
|
|
10436
|
+
return writeJson5(res, ok ? 200 : 404, { ok });
|
|
9492
10437
|
}
|
|
9493
10438
|
if (method === "DELETE" && id && !action) {
|
|
9494
10439
|
const bound = await integrationKeyRequirement(deps, id);
|
|
9495
|
-
if (bound) return
|
|
10440
|
+
if (bound) return writeJsonError2(res, 409, bound);
|
|
9496
10441
|
const ok = await deps.keyDb.outboundApiKeysDelete(id);
|
|
9497
|
-
return
|
|
10442
|
+
return writeJson5(res, ok ? 200 : 404, { ok });
|
|
9498
10443
|
}
|
|
9499
10444
|
if (method === "POST" && id && action === "enabled") {
|
|
9500
|
-
const body = await
|
|
10445
|
+
const body = await readJsonBody5(req);
|
|
9501
10446
|
const enabled = body["enabled"] === true;
|
|
9502
10447
|
if (!enabled) {
|
|
9503
10448
|
const bound = await integrationKeyRequirement(deps, id);
|
|
9504
|
-
if (bound) return
|
|
10449
|
+
if (bound) return writeJsonError2(res, 409, bound);
|
|
9505
10450
|
}
|
|
9506
10451
|
const ok = await deps.keyDb.outboundApiKeysSetEnabled(id, enabled);
|
|
9507
|
-
return
|
|
10452
|
+
return writeJson5(res, ok ? 200 : 404, { ok, enabled });
|
|
9508
10453
|
}
|
|
9509
10454
|
if (method === "POST" && id && action === "permissions") {
|
|
9510
|
-
const body = await
|
|
10455
|
+
const body = await readJsonBody5(req);
|
|
9511
10456
|
if (Object.keys(body).length !== 1 || !Object.prototype.hasOwnProperty.call(body, "permissions")) {
|
|
9512
|
-
return
|
|
10457
|
+
return writeJsonError2(res, 400, "body must contain only permissions");
|
|
9513
10458
|
}
|
|
9514
10459
|
let permissions;
|
|
9515
10460
|
try {
|
|
9516
10461
|
permissions = validateOutboundPermissions(body["permissions"]);
|
|
9517
10462
|
} catch {
|
|
9518
|
-
return
|
|
10463
|
+
return writeJsonError2(
|
|
9519
10464
|
res,
|
|
9520
10465
|
400,
|
|
9521
10466
|
"permissions must be an array of unique chat, responses, messages, gemini, or images values"
|
|
9522
10467
|
);
|
|
9523
10468
|
}
|
|
9524
10469
|
const before = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
|
|
9525
|
-
if (!before) return
|
|
9526
|
-
if (before.revokedAt !== null) return
|
|
10470
|
+
if (!before) return writeJson5(res, 404, { ok: false });
|
|
10471
|
+
if (before.revokedAt !== null) return writeJson5(res, 409, { ok: false });
|
|
9527
10472
|
const required = await integrationKeyRequirement(deps, id, permissions);
|
|
9528
|
-
if (required) return
|
|
10473
|
+
if (required) return writeJsonError2(res, 409, required);
|
|
9529
10474
|
const ok = await deps.keyDb.outboundApiKeysSetPermissions(id, permissions);
|
|
9530
10475
|
if (!ok) {
|
|
9531
10476
|
const current = (await deps.keyDb.outboundApiKeysList()).find((row) => row.id === id);
|
|
9532
|
-
return
|
|
10477
|
+
return writeJson5(res, current?.revokedAt !== null ? 409 : 404, { ok: false });
|
|
9533
10478
|
}
|
|
9534
|
-
return
|
|
10479
|
+
return writeJson5(res, 200, { ok: true, allowedEndpoints: permissions });
|
|
9535
10480
|
}
|
|
9536
10481
|
if (method === "POST" && id && action === "max-concurrency") {
|
|
9537
|
-
const body = await
|
|
10482
|
+
const body = await readJsonBody5(req);
|
|
9538
10483
|
const raw = body["maxConcurrency"];
|
|
9539
10484
|
let value;
|
|
9540
10485
|
if (raw === null) {
|
|
@@ -9542,23 +10487,73 @@ async function handleKeys(req, res, method, rest, deps) {
|
|
|
9542
10487
|
} else if (typeof raw === "number" && Number.isInteger(raw) && raw >= 1 && raw <= 1e3) {
|
|
9543
10488
|
value = raw;
|
|
9544
10489
|
} else {
|
|
9545
|
-
return
|
|
10490
|
+
return writeJsonError2(
|
|
9546
10491
|
res,
|
|
9547
10492
|
400,
|
|
9548
10493
|
"maxConcurrency must be an integer 1..1000 or null"
|
|
9549
10494
|
);
|
|
9550
10495
|
}
|
|
9551
10496
|
const ok = await deps.keyDb.outboundApiKeysSetMaxConcurrency(id, value);
|
|
9552
|
-
return
|
|
10497
|
+
return writeJson5(res, ok ? 200 : 404, { ok, maxConcurrency: value });
|
|
10498
|
+
}
|
|
10499
|
+
if (method === "POST" && id && action === "upstream") {
|
|
10500
|
+
const body = await readJsonBody5(req);
|
|
10501
|
+
let raw;
|
|
10502
|
+
if (Object.prototype.hasOwnProperty.call(body, "target")) {
|
|
10503
|
+
raw = body["target"];
|
|
10504
|
+
} else if (Object.prototype.hasOwnProperty.call(body, "providerId")) {
|
|
10505
|
+
const legacy = body["providerId"];
|
|
10506
|
+
raw = legacy === null || legacy === void 0 ? null : { kind: "provider", providerId: legacy };
|
|
10507
|
+
} else {
|
|
10508
|
+
return writeJsonError2(res, 400, "body must contain target (or the legacy providerId)");
|
|
10509
|
+
}
|
|
10510
|
+
if (raw === null || raw === void 0) {
|
|
10511
|
+
const ok2 = await deps.keyDb.outboundApiKeysSetUpstream(id, null);
|
|
10512
|
+
return writeJson5(res, ok2 ? 200 : 404, { ok: ok2, target: null });
|
|
10513
|
+
}
|
|
10514
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
10515
|
+
return writeJsonError2(res, 400, "target must be an object or null");
|
|
10516
|
+
}
|
|
10517
|
+
const t = raw;
|
|
10518
|
+
const kind = t["kind"];
|
|
10519
|
+
const providerId = typeof t["providerId"] === "string" ? t["providerId"].trim() : "";
|
|
10520
|
+
if (!providerId) {
|
|
10521
|
+
return writeJsonError2(res, 400, "target.providerId is required");
|
|
10522
|
+
}
|
|
10523
|
+
if (kind === "provider") {
|
|
10524
|
+
const cfg = loadConfig(deps.configPath);
|
|
10525
|
+
if (!cfg.providers.some((p) => p.id === providerId)) {
|
|
10526
|
+
return writeJsonError2(res, 404, `provider '${providerId}' not found`);
|
|
10527
|
+
}
|
|
10528
|
+
} else if (kind === "account" || kind === "account-group" || kind === "account-pool") {
|
|
10529
|
+
if (providerId !== "claude" && providerId !== "kimi") {
|
|
10530
|
+
return writeJsonError2(
|
|
10531
|
+
res,
|
|
10532
|
+
400,
|
|
10533
|
+
`subscription provider '${providerId}' cannot be direct-bound: its upstream wire differs from the client wire (translation required \u2014 use a downstream route); only claude and kimi subscriptions speak the same Anthropic Messages wire as the client`
|
|
10534
|
+
);
|
|
10535
|
+
}
|
|
10536
|
+
if (kind === "account" && typeof t["accountId"] !== "string") {
|
|
10537
|
+
return writeJsonError2(res, 400, "target.accountId is required for kind account");
|
|
10538
|
+
}
|
|
10539
|
+
if (kind === "account-group" && typeof t["group"] !== "string") {
|
|
10540
|
+
return writeJsonError2(res, 400, "target.group is required for kind account-group");
|
|
10541
|
+
}
|
|
10542
|
+
} else {
|
|
10543
|
+
return writeJsonError2(res, 400, "target.kind must be provider, account, account-group, or account-pool");
|
|
10544
|
+
}
|
|
10545
|
+
const target = raw;
|
|
10546
|
+
const ok = await deps.keyDb.outboundApiKeysSetUpstream(id, target);
|
|
10547
|
+
return writeJson5(res, ok ? 200 : 404, { ok, target });
|
|
9553
10548
|
}
|
|
9554
10549
|
if (method === "POST" && id && action === "policy") {
|
|
9555
|
-
const body = await
|
|
10550
|
+
const body = await readJsonBody5(req);
|
|
9556
10551
|
const parsed = parseKeyPolicyBody(body);
|
|
9557
|
-
if (!parsed.ok) return
|
|
10552
|
+
if (!parsed.ok) return writeJsonError2(res, 400, parsed.message);
|
|
9558
10553
|
const ok = await deps.keyDb.outboundApiKeysSetPolicy(id, parsed.policy);
|
|
9559
|
-
return
|
|
10554
|
+
return writeJson5(res, ok ? 200 : 404, { ok });
|
|
9560
10555
|
}
|
|
9561
|
-
return
|
|
10556
|
+
return writeJsonError2(res, 405, `method ${method} not allowed on keys`);
|
|
9562
10557
|
}
|
|
9563
10558
|
function validateQueueSegments(patch) {
|
|
9564
10559
|
const errors = [];
|
|
@@ -9722,17 +10717,17 @@ async function handleServer(req, res, method, deps) {
|
|
|
9722
10717
|
search: redactSearchServerConfig(config.search)
|
|
9723
10718
|
};
|
|
9724
10719
|
}
|
|
9725
|
-
return
|
|
10720
|
+
return writeJson5(res, 200, { server: projectImagesConfigForAdmin(server) });
|
|
9726
10721
|
}
|
|
9727
10722
|
if (method === "PUT") {
|
|
9728
|
-
const patch = await
|
|
10723
|
+
const patch = await readJsonBody5(req);
|
|
9729
10724
|
const queueErrors = validateQueueSegments(patch);
|
|
9730
10725
|
if (queueErrors.length > 0) {
|
|
9731
|
-
return
|
|
10726
|
+
return writeJsonError2(res, 400, `invalid queue config: ${queueErrors.join("; ")}`);
|
|
9732
10727
|
}
|
|
9733
10728
|
const allowanceErrors = validateAllowanceSchedulingSegment(patch);
|
|
9734
10729
|
if (allowanceErrors.length > 0) {
|
|
9735
|
-
return
|
|
10730
|
+
return writeJsonError2(
|
|
9736
10731
|
res,
|
|
9737
10732
|
400,
|
|
9738
10733
|
`invalid allowance scheduling config: ${allowanceErrors.join("; ")}`
|
|
@@ -9740,19 +10735,19 @@ async function handleServer(req, res, method, deps) {
|
|
|
9740
10735
|
}
|
|
9741
10736
|
const bindingErrors = validateGatewayBindingsSegment(patch);
|
|
9742
10737
|
if (bindingErrors.length > 0) {
|
|
9743
|
-
return
|
|
10738
|
+
return writeJsonError2(res, 400, `invalid gateway bindings: ${bindingErrors.join("; ")}`);
|
|
9744
10739
|
}
|
|
9745
10740
|
const webhookErrors = validateWebhookSegment(patch);
|
|
9746
10741
|
if (webhookErrors.length > 0) {
|
|
9747
|
-
return
|
|
10742
|
+
return writeJsonError2(res, 400, `invalid webhook config: ${webhookErrors.join("; ")}`);
|
|
9748
10743
|
}
|
|
9749
10744
|
const auditErrors = validateAuditSegment(patch);
|
|
9750
10745
|
if (auditErrors.length > 0) {
|
|
9751
|
-
return
|
|
10746
|
+
return writeJsonError2(res, 400, `invalid audit config: ${auditErrors.join("; ")}`);
|
|
9752
10747
|
}
|
|
9753
10748
|
const billingErrors = validateBillingSegment(patch);
|
|
9754
10749
|
if (billingErrors.length > 0) {
|
|
9755
|
-
return
|
|
10750
|
+
return writeJsonError2(res, 400, `invalid billing config: ${billingErrors.join("; ")}`);
|
|
9756
10751
|
}
|
|
9757
10752
|
const current = await loadServerConfig3(deps.settingsStore);
|
|
9758
10753
|
let effectivePatch = patch;
|
|
@@ -9771,7 +10766,7 @@ async function handleServer(req, res, method, deps) {
|
|
|
9771
10766
|
remoteResolverAvailable: deps.imageRemoteResolverAvailable
|
|
9772
10767
|
});
|
|
9773
10768
|
if (imageErrors.length > 0) {
|
|
9774
|
-
return
|
|
10769
|
+
return writeJsonError2(res, 400, `invalid Images config: ${imageErrors.join("; ")}`);
|
|
9775
10770
|
}
|
|
9776
10771
|
effectivePatch = { ...effectivePatch, images };
|
|
9777
10772
|
}
|
|
@@ -9782,7 +10777,7 @@ async function handleServer(req, res, method, deps) {
|
|
|
9782
10777
|
);
|
|
9783
10778
|
const searchErrors = validateSearchServerConfig(searchPatch);
|
|
9784
10779
|
if (searchErrors.length > 0) {
|
|
9785
|
-
return
|
|
10780
|
+
return writeJsonError2(res, 400, `invalid search config: ${searchErrors.join("; ")}`);
|
|
9786
10781
|
}
|
|
9787
10782
|
effectivePatch = {
|
|
9788
10783
|
...effectivePatch,
|
|
@@ -9824,7 +10819,7 @@ async function handleServer(req, res, method, deps) {
|
|
|
9824
10819
|
});
|
|
9825
10820
|
} catch (error) {
|
|
9826
10821
|
if (error instanceof ServerConfigTransactionError) {
|
|
9827
|
-
return
|
|
10822
|
+
return writeJsonError2(res, 500, error.message);
|
|
9828
10823
|
}
|
|
9829
10824
|
throw error;
|
|
9830
10825
|
}
|
|
@@ -9845,24 +10840,27 @@ async function handleServer(req, res, method, deps) {
|
|
|
9845
10840
|
...merged,
|
|
9846
10841
|
search: redactSearchServerConfig(merged.search)
|
|
9847
10842
|
} : merged;
|
|
9848
|
-
return
|
|
10843
|
+
return writeJson5(res, 200, { server: projectImagesConfigForAdmin(mergedForAdmin) });
|
|
9849
10844
|
}
|
|
9850
|
-
return
|
|
10845
|
+
return writeJsonError2(res, 405, `method ${method} not allowed on server`);
|
|
9851
10846
|
}
|
|
9852
10847
|
async function handleAccounts(req, res, method, rest, deps) {
|
|
9853
10848
|
if (rest[0] === "route-activity" && rest.length === 1) {
|
|
9854
10849
|
if (method !== "GET") {
|
|
9855
|
-
return
|
|
10850
|
+
return writeJsonError2(res, 405, `method ${method} not allowed on account route activity`);
|
|
9856
10851
|
}
|
|
9857
10852
|
const query2 = requestQuery(req);
|
|
9858
10853
|
const parsedLimit = Number(query2.get("limit") ?? "100");
|
|
10854
|
+
const kindParam = query2.get("credentialKind");
|
|
10855
|
+
const credentialKind = kindParam === "subscription-account" || kindParam === "provider-key" ? kindParam : void 0;
|
|
9859
10856
|
const records = getSharedAccountRouteActivity().list({
|
|
9860
10857
|
providerId: query2.get("providerId") ?? void 0,
|
|
9861
10858
|
accountId: query2.get("accountId") ?? void 0,
|
|
9862
10859
|
sessionKey: query2.get("sessionKey") ?? void 0,
|
|
10860
|
+
credentialKind,
|
|
9863
10861
|
limit: Number.isFinite(parsedLimit) ? parsedLimit : 100
|
|
9864
10862
|
});
|
|
9865
|
-
return
|
|
10863
|
+
return writeJson5(res, 200, {
|
|
9866
10864
|
available: true,
|
|
9867
10865
|
records,
|
|
9868
10866
|
capacity: ACCOUNT_ROUTE_ACTIVITY_LIMIT,
|
|
@@ -9871,14 +10869,14 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
9871
10869
|
}
|
|
9872
10870
|
if (rest[0] === "overload-counters" && rest.length === 1) {
|
|
9873
10871
|
if (method !== "GET") {
|
|
9874
|
-
return
|
|
10872
|
+
return writeJsonError2(res, 405, `method ${method} not allowed on overload counters`);
|
|
9875
10873
|
}
|
|
9876
10874
|
const query2 = requestQuery(req);
|
|
9877
10875
|
const entries = getSharedOverloadCounter().list({
|
|
9878
10876
|
providerId: query2.get("providerId") ?? void 0,
|
|
9879
10877
|
accountId: query2.get("accountId") ?? void 0
|
|
9880
10878
|
});
|
|
9881
|
-
return
|
|
10879
|
+
return writeJson5(res, 200, {
|
|
9882
10880
|
available: true,
|
|
9883
10881
|
entries,
|
|
9884
10882
|
collectedAt: Date.now()
|
|
@@ -9897,21 +10895,21 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
9897
10895
|
const result = await handleAntigravityModelsRoute({
|
|
9898
10896
|
resolveAntigravityAccessToken: deps.resolveAntigravityAccessToken ?? (async () => null)
|
|
9899
10897
|
});
|
|
9900
|
-
return
|
|
10898
|
+
return writeJson5(res, result.status, result.body);
|
|
9901
10899
|
}
|
|
9902
10900
|
if (method === "GET" && rest.length === 0) {
|
|
9903
10901
|
const accounts = await deps.subscriptionAccounts.listAll();
|
|
9904
10902
|
const providerAccounts = await deps.subscriptionTokenWriter.listSanitizedAccounts();
|
|
9905
10903
|
const externalCli = await deps.subscriptionTokenWriter.listExternalCliAvailability();
|
|
9906
|
-
return
|
|
10904
|
+
return writeJson5(res, 200, { accounts, providerAccounts, externalCli });
|
|
9907
10905
|
}
|
|
9908
10906
|
if (method === "POST" && rest[0] === "batch" && rest.length === 1) {
|
|
9909
|
-
const body = await
|
|
10907
|
+
const body = await readJsonBody5(req);
|
|
9910
10908
|
const parsed = validateAccountBatchBody(body);
|
|
9911
|
-
if (!parsed) return
|
|
10909
|
+
if (!parsed) return writeJsonError2(res, 400, "invalid account batch request");
|
|
9912
10910
|
const result = await deps.subscriptionTokenWriter.batchManageAccounts(parsed.refs, parsed.mutation);
|
|
9913
10911
|
if (!result.ok) {
|
|
9914
|
-
return
|
|
10912
|
+
return writeJsonError2(
|
|
9915
10913
|
res,
|
|
9916
10914
|
404,
|
|
9917
10915
|
`account '${result.missing.accountId}' not found for provider '${result.missing.providerId}'`
|
|
@@ -9922,23 +10920,23 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
9922
10920
|
deps.accountAllowanceService?.removeAccountSnapshot?.(ref.providerId, ref.accountId);
|
|
9923
10921
|
}
|
|
9924
10922
|
}
|
|
9925
|
-
return
|
|
10923
|
+
return writeJson5(res, 200, { ok: true, affected: result.affected });
|
|
9926
10924
|
}
|
|
9927
10925
|
if (method === "GET" && (rest[0] === "codex" || rest[0] === "kimi" || rest[0] === "grok" || rest[0] === "copilot" || rest[0] === "antigravity") && rest[1] === "oauth" && rest[3] === "status") {
|
|
9928
10926
|
const result = rest[0] === "codex" ? handleCodexOAuthStatus(rest[2], deps) : rest[0] === "kimi" ? handleKimiOAuthStatus(rest[2], deps) : rest[0] === "grok" ? handleGrokOAuthStatus(rest[2], deps) : rest[0] === "copilot" ? handleCopilotOAuthStatus(rest[2], deps) : handleAntigravityOAuthStatus(rest[2], deps);
|
|
9929
|
-
return
|
|
10927
|
+
return writeJson5(res, result.status, result.body);
|
|
9930
10928
|
}
|
|
9931
10929
|
if (method === "DELETE" && (rest[0] === "codex" || rest[0] === "kimi" || rest[0] === "grok" || rest[0] === "copilot" || rest[0] === "antigravity") && rest[1] === "oauth" && rest[2]) {
|
|
9932
10930
|
const result = rest[0] === "codex" ? handleCodexOAuthCancel(rest[2], deps) : rest[0] === "kimi" ? handleKimiOAuthCancel(rest[2], deps) : rest[0] === "grok" ? handleGrokOAuthCancel(rest[2], deps) : rest[0] === "copilot" ? handleCopilotOAuthCancel(rest[2], deps) : handleAntigravityOAuthCancel(rest[2], deps);
|
|
9933
|
-
return
|
|
10931
|
+
return writeJson5(res, result.status, result.body);
|
|
9934
10932
|
}
|
|
9935
10933
|
if (method === "GET" && rest.length === 3 && rest[2] === "diagnostics") {
|
|
9936
10934
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
9937
|
-
if (!providerId) return
|
|
10935
|
+
if (!providerId) return writeJsonError2(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
9938
10936
|
const accountId = rest[1];
|
|
9939
10937
|
const listed = await deps.subscriptionTokenWriter.listSanitizedAccounts();
|
|
9940
10938
|
if (!(listed[providerId] ?? []).some((account) => account.id === accountId)) {
|
|
9941
|
-
return
|
|
10939
|
+
return writeJsonError2(res, 404, `account '${accountId}' not found`);
|
|
9942
10940
|
}
|
|
9943
10941
|
const health2 = getSharedAccountHealth().getDiagnostics({ providerId, accountId });
|
|
9944
10942
|
const allowance = deps.accountAllowanceService?.getSchedulingStatus?.()?.history.filter((entry) => entry.providerId === providerId && entry.accountId === accountId).map((entry) => ({
|
|
@@ -9952,88 +10950,88 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
9952
10950
|
resumeAt: entry.resumeAt
|
|
9953
10951
|
})) ?? [];
|
|
9954
10952
|
const diagnostics = [...health2, ...allowance].sort((left, right) => right.at - left.at).slice(0, 200);
|
|
9955
|
-
return
|
|
10953
|
+
return writeJson5(res, 200, { diagnostics });
|
|
9956
10954
|
}
|
|
9957
10955
|
if (method === "GET" && rest.length === 3 && rest[2] === "events") {
|
|
9958
10956
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
9959
|
-
if (!providerId) return
|
|
10957
|
+
if (!providerId) return writeJsonError2(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
9960
10958
|
const accountId = rest[1];
|
|
9961
10959
|
const listed = await deps.subscriptionTokenWriter.listSanitizedAccounts();
|
|
9962
10960
|
if (!(listed[providerId] ?? []).some((account) => account.id === accountId)) {
|
|
9963
|
-
return
|
|
10961
|
+
return writeJsonError2(res, 404, `account '${accountId}' not found`);
|
|
9964
10962
|
}
|
|
9965
10963
|
const snapshot = deps.accountProbeService?.getAllHistory().find((entry) => entry.providerId === providerId && entry.accountId === accountId);
|
|
9966
10964
|
const diagnostics = getSharedAccountHealth().getDiagnostics({ providerId, accountId });
|
|
9967
|
-
return
|
|
10965
|
+
return writeJson5(res, 200, { events: snapshot?.records ?? [], diagnostics });
|
|
9968
10966
|
}
|
|
9969
10967
|
if (method === "PATCH" && rest.length === 2) {
|
|
9970
10968
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
9971
|
-
if (!providerId) return
|
|
9972
|
-
const body = await
|
|
10969
|
+
if (!providerId) return writeJsonError2(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
10970
|
+
const body = await readJsonBody5(req);
|
|
9973
10971
|
const patch = validateAccountMetadataPatch(body);
|
|
9974
|
-
if (!patch) return
|
|
10972
|
+
if (!patch) return writeJsonError2(res, 400, "invalid account metadata patch");
|
|
9975
10973
|
const result = await deps.subscriptionTokenWriter.patchAccountMetadata(providerId, rest[1], patch);
|
|
9976
|
-
if (!result.ok) return
|
|
9977
|
-
return
|
|
10974
|
+
if (!result.ok) return writeJsonError2(res, 404, `account '${rest[1]}' not found`);
|
|
10975
|
+
return writeJson5(res, 200, { ok: true });
|
|
9978
10976
|
}
|
|
9979
10977
|
if (method === "PUT" || method === "POST" || method === "DELETE") {
|
|
9980
10978
|
const providerId = asSubscriptionProviderId(rest[0]);
|
|
9981
10979
|
if (!providerId) {
|
|
9982
|
-
return
|
|
10980
|
+
return writeJsonError2(res, 400, `unknown subscription provider '${rest[0] ?? ""}'`);
|
|
9983
10981
|
}
|
|
9984
10982
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "start") {
|
|
9985
10983
|
if (providerId === "codex") {
|
|
9986
10984
|
const result2 = handleCodexOAuthStart(deps);
|
|
9987
|
-
return
|
|
10985
|
+
return writeJson5(res, result2.status, result2.body);
|
|
9988
10986
|
}
|
|
9989
10987
|
if (providerId === "kimi") {
|
|
9990
10988
|
const result2 = await handleKimiOAuthStart(deps);
|
|
9991
|
-
return
|
|
10989
|
+
return writeJson5(res, result2.status, result2.body);
|
|
9992
10990
|
}
|
|
9993
10991
|
if (providerId === "grok") {
|
|
9994
10992
|
const result2 = await handleGrokOAuthStart(deps);
|
|
9995
|
-
return
|
|
10993
|
+
return writeJson5(res, result2.status, result2.body);
|
|
9996
10994
|
}
|
|
9997
10995
|
if (providerId === "copilot") {
|
|
9998
|
-
const body2 = await
|
|
10996
|
+
const body2 = await readJsonBody5(req);
|
|
9999
10997
|
const result2 = await handleCopilotOAuthStart(deps, body2["enterpriseUrl"]);
|
|
10000
|
-
return
|
|
10998
|
+
return writeJson5(res, result2.status, result2.body);
|
|
10001
10999
|
}
|
|
10002
11000
|
if (providerId === "antigravity") {
|
|
10003
11001
|
const result2 = handleAntigravityOAuthStart(deps);
|
|
10004
|
-
return
|
|
11002
|
+
return writeJson5(res, result2.status, result2.body);
|
|
10005
11003
|
}
|
|
10006
11004
|
const result = handleOAuthStart(providerId, deps);
|
|
10007
|
-
return
|
|
11005
|
+
return writeJson5(res, result.status, result.body);
|
|
10008
11006
|
}
|
|
10009
11007
|
if (method === "POST" && rest[1] === "oauth" && rest[2] === "complete") {
|
|
10010
|
-
const body2 = await
|
|
11008
|
+
const body2 = await readJsonBody5(req);
|
|
10011
11009
|
const result = await handleOAuthComplete(providerId, body2, deps);
|
|
10012
|
-
return
|
|
11010
|
+
return writeJson5(res, result.status, result.body);
|
|
10013
11011
|
}
|
|
10014
11012
|
if (method === "POST" && rest[1] === "accounts") {
|
|
10015
|
-
const body2 = await
|
|
11013
|
+
const body2 = await readJsonBody5(req);
|
|
10016
11014
|
const block = validateTokenBody(providerId, body2);
|
|
10017
11015
|
if (!block) {
|
|
10018
|
-
return
|
|
11016
|
+
return writeJsonError2(res, 400, `malformed token body for provider '${providerId}'`);
|
|
10019
11017
|
}
|
|
10020
11018
|
const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
|
|
10021
11019
|
await deps.subscriptionAccountAppender.appendProviderAccount(providerId, block, label);
|
|
10022
11020
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
10023
|
-
return
|
|
11021
|
+
return writeJson5(res, 200, status2 ? { account: status2 } : { ok: true });
|
|
10024
11022
|
}
|
|
10025
11023
|
if (method === "POST" && rest[1] === "import-external") {
|
|
10026
11024
|
if (providerId !== "claude" && providerId !== "codex") {
|
|
10027
|
-
return
|
|
11025
|
+
return writeJsonError2(res, 400, `provider '${providerId}' has no external CLI store`);
|
|
10028
11026
|
}
|
|
10029
|
-
const body2 = await
|
|
11027
|
+
const body2 = await readJsonBody5(req);
|
|
10030
11028
|
const label = typeof body2["label"] === "string" && body2["label"].trim() ? body2["label"].trim() : void 0;
|
|
10031
11029
|
const result = await deps.subscriptionTokenWriter.importExternalCliAccount(providerId, label);
|
|
10032
11030
|
if (!result.ok) {
|
|
10033
|
-
return
|
|
11031
|
+
return writeJsonError2(res, 409, `no usable external ${providerId} CLI credential found`);
|
|
10034
11032
|
}
|
|
10035
11033
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
10036
|
-
return
|
|
11034
|
+
return writeJson5(res, 200, {
|
|
10037
11035
|
ok: true,
|
|
10038
11036
|
account: status2 ?? void 0,
|
|
10039
11037
|
nativeCredentialMode: result.nativeCredentialMode,
|
|
@@ -10043,22 +11041,22 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
10043
11041
|
}
|
|
10044
11042
|
if (method === "POST" && rest[1] === "refresh") {
|
|
10045
11043
|
if (providerId === "opencodego") {
|
|
10046
|
-
return
|
|
11044
|
+
return writeJsonError2(res, 400, "opencodego credentials are not refreshable");
|
|
10047
11045
|
}
|
|
10048
11046
|
const writer2 = deps.subscriptionTokenWriter;
|
|
10049
11047
|
const ok = providerId === "claude" ? await writer2.refreshClaudeToken() : providerId === "codex" ? await writer2.refreshCodexToken() : await writer2.refreshGeminiToken();
|
|
10050
11048
|
const status2 = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
10051
|
-
return
|
|
11049
|
+
return writeJson5(res, 200, { ok, account: status2 ?? void 0 });
|
|
10052
11050
|
}
|
|
10053
11051
|
if (method === "POST" && rest.length === 3 && rest[2] === "test") {
|
|
10054
11052
|
const accountId = rest[1];
|
|
10055
|
-
if (!deps.accountProbeService) return
|
|
11053
|
+
if (!deps.accountProbeService) return writeJsonError2(res, 501, "account probe service unavailable");
|
|
10056
11054
|
const listed = await deps.subscriptionTokenWriter.listSanitizedAccounts();
|
|
10057
11055
|
if (!(listed[providerId] ?? []).some((account) => account.id === accountId)) {
|
|
10058
|
-
return
|
|
11056
|
+
return writeJsonError2(res, 404, `account '${accountId}' not found`);
|
|
10059
11057
|
}
|
|
10060
11058
|
const result = await deps.accountProbeService.testAccountConnection(providerId, accountId);
|
|
10061
|
-
return
|
|
11059
|
+
return writeJson5(res, 200, {
|
|
10062
11060
|
ok: result.ok,
|
|
10063
11061
|
marked: result.marked,
|
|
10064
11062
|
tier: result.tier,
|
|
@@ -10067,176 +11065,189 @@ async function handleAccounts(req, res, method, rest, deps) {
|
|
|
10067
11065
|
}
|
|
10068
11066
|
if (method === "POST" && rest[2] === "label") {
|
|
10069
11067
|
const accountId = rest[1];
|
|
10070
|
-
const body2 = await
|
|
11068
|
+
const body2 = await readJsonBody5(req);
|
|
10071
11069
|
const label = typeof body2["label"] === "string" ? body2["label"].trim() : "";
|
|
10072
11070
|
const result = await deps.subscriptionTokenWriter.renameAccount(providerId, accountId, label);
|
|
10073
|
-
if (!result.ok) return
|
|
10074
|
-
return
|
|
11071
|
+
if (!result.ok) return writeJsonError2(res, 404, `account '${accountId}' not found`);
|
|
11072
|
+
return writeJson5(res, 200, { ok: true });
|
|
10075
11073
|
}
|
|
10076
11074
|
if (method === "POST" && rest[2] === "priority") {
|
|
10077
11075
|
const accountId = rest[1];
|
|
10078
|
-
const body2 = await
|
|
11076
|
+
const body2 = await readJsonBody5(req);
|
|
10079
11077
|
const raw = body2["priority"];
|
|
10080
11078
|
const priority = typeof raw === "number" ? raw : Number(raw);
|
|
10081
11079
|
if (!Number.isFinite(priority)) {
|
|
10082
|
-
return
|
|
11080
|
+
return writeJsonError2(res, 400, "priority must be a finite number");
|
|
10083
11081
|
}
|
|
10084
11082
|
const result = await deps.subscriptionTokenWriter.setAccountPriority(providerId, accountId, priority);
|
|
10085
|
-
if (!result.ok) return
|
|
10086
|
-
return
|
|
11083
|
+
if (!result.ok) return writeJsonError2(res, 404, `account '${accountId}' not found`);
|
|
11084
|
+
return writeJson5(res, 200, { ok: true });
|
|
10087
11085
|
}
|
|
10088
11086
|
if (method === "POST" && rest[2] === "proxy") {
|
|
10089
11087
|
const accountId = rest[1];
|
|
10090
|
-
const body2 = await
|
|
11088
|
+
const body2 = await readJsonBody5(req);
|
|
10091
11089
|
const rawProxy = body2["proxy"];
|
|
10092
11090
|
let proxy;
|
|
10093
11091
|
if (rawProxy !== null && rawProxy !== void 0) {
|
|
10094
11092
|
proxy = normalizeProxyConfig(rawProxy);
|
|
10095
|
-
if (!proxy) return
|
|
11093
|
+
if (!proxy) return writeJsonError2(res, 400, "invalid proxy config");
|
|
10096
11094
|
}
|
|
10097
11095
|
const result = await deps.subscriptionTokenWriter.setAccountProxy(providerId, accountId, proxy);
|
|
10098
|
-
if (!result.ok) return
|
|
10099
|
-
return
|
|
11096
|
+
if (!result.ok) return writeJsonError2(res, 404, `account '${accountId}' not found`);
|
|
11097
|
+
return writeJson5(res, 200, { ok: true });
|
|
10100
11098
|
}
|
|
10101
11099
|
if (method === "POST" && rest[2] === "supported-models") {
|
|
10102
11100
|
const accountId = rest[1];
|
|
10103
|
-
const body2 = await
|
|
11101
|
+
const body2 = await readJsonBody5(req);
|
|
10104
11102
|
const parsed = validateSupportedModelsBody(body2["supportedModels"]);
|
|
10105
|
-
if (!parsed.ok) return
|
|
11103
|
+
if (!parsed.ok) return writeJsonError2(res, 400, "invalid supportedModels");
|
|
10106
11104
|
const result = await deps.subscriptionTokenWriter.setAccountSupportedModels(providerId, accountId, parsed.value);
|
|
10107
|
-
if (!result.ok) return
|
|
10108
|
-
return
|
|
11105
|
+
if (!result.ok) return writeJsonError2(res, 404, `account '${accountId}' not found`);
|
|
11106
|
+
return writeJson5(res, 200, { ok: true });
|
|
10109
11107
|
}
|
|
10110
11108
|
if (method === "PUT" && rest[1] === "active") {
|
|
10111
|
-
const body2 = await
|
|
11109
|
+
const body2 = await readJsonBody5(req);
|
|
10112
11110
|
const id = typeof body2["id"] === "string" ? body2["id"] : "";
|
|
10113
|
-
if (!id) return
|
|
11111
|
+
if (!id) return writeJsonError2(res, 400, "active switch requires { id }");
|
|
10114
11112
|
const result = await deps.subscriptionTokenWriter.setActiveAccount(providerId, id);
|
|
10115
|
-
if (!result.ok) return
|
|
10116
|
-
return
|
|
11113
|
+
if (!result.ok) return writeJsonError2(res, 404, `account '${id}' not found`);
|
|
11114
|
+
return writeJson5(res, 200, { ok: true });
|
|
10117
11115
|
}
|
|
10118
11116
|
if (method === "DELETE" && rest.length === 2) {
|
|
10119
11117
|
const accountId = rest[1];
|
|
10120
11118
|
const result = await deps.subscriptionTokenWriter.removeAccount(providerId, accountId);
|
|
10121
|
-
if (!result.removed) return
|
|
11119
|
+
if (!result.removed) return writeJsonError2(res, 404, `account '${accountId}' not found`);
|
|
10122
11120
|
deps.accountAllowanceService?.removeAccountSnapshot?.(providerId, accountId);
|
|
10123
|
-
return
|
|
11121
|
+
return writeJson5(res, 200, { ok: true });
|
|
10124
11122
|
}
|
|
10125
11123
|
if (method === "DELETE" && rest.length === 1) {
|
|
10126
11124
|
await deps.subscriptionTokenWriter.clearProvider(providerId);
|
|
10127
11125
|
deps.accountAllowanceService?.removeProviderSnapshots?.(providerId);
|
|
10128
|
-
return
|
|
11126
|
+
return writeJson5(res, 200, { ok: true });
|
|
10129
11127
|
}
|
|
10130
11128
|
if (method === "DELETE") {
|
|
10131
|
-
return
|
|
11129
|
+
return writeJsonError2(res, 405, "method DELETE not allowed on this accounts path");
|
|
10132
11130
|
}
|
|
10133
|
-
const body = await
|
|
11131
|
+
const body = await readJsonBody5(req);
|
|
10134
11132
|
const config = validateTokenBody(providerId, body);
|
|
10135
11133
|
if (!config) {
|
|
10136
|
-
return
|
|
11134
|
+
return writeJsonError2(res, 400, `malformed token body for provider '${providerId}'`);
|
|
10137
11135
|
}
|
|
10138
11136
|
await deps.subscriptionTokenWriter.writeProviderTokens(providerId, config);
|
|
10139
11137
|
const status = await statusEntryFor(deps.subscriptionAccounts, providerId);
|
|
10140
|
-
return
|
|
11138
|
+
return writeJson5(res, 200, status ? { account: status } : { ok: true });
|
|
10141
11139
|
}
|
|
10142
|
-
return
|
|
11140
|
+
return writeJsonError2(res, 405, `method ${method} not allowed on accounts`);
|
|
10143
11141
|
}
|
|
10144
11142
|
async function handleCli(req, res, method, rest, deps) {
|
|
10145
11143
|
if (method === "GET" && rest.length === 0) {
|
|
10146
11144
|
const result = handleCliList(process.platform, deps.cliPathProbe);
|
|
10147
|
-
return
|
|
11145
|
+
return writeJson5(res, result.status, result.body);
|
|
10148
11146
|
}
|
|
10149
11147
|
if (method === "GET" && rest[0] === "sessions") {
|
|
10150
11148
|
const result = handleCliSessions();
|
|
10151
|
-
return
|
|
11149
|
+
return writeJson5(res, result.status, result.body);
|
|
10152
11150
|
}
|
|
10153
11151
|
if (method === "DELETE" && rest[0] === "sessions" && rest[1]) {
|
|
10154
11152
|
const result = handleCliStop(rest[1]);
|
|
10155
|
-
return
|
|
11153
|
+
return writeJson5(res, result.status, result.body);
|
|
10156
11154
|
}
|
|
10157
11155
|
if (method === "POST" && rest[1] === "install") {
|
|
10158
11156
|
const cli = rest[0];
|
|
10159
11157
|
if (!isLaunchCliId(cli)) {
|
|
10160
|
-
return
|
|
11158
|
+
return writeJsonError2(res, 400, `unknown cli '${cli ?? ""}'`);
|
|
10161
11159
|
}
|
|
10162
11160
|
const result = await handleCliInstall(cli, deps.cliCommandRunner);
|
|
10163
|
-
return
|
|
11161
|
+
return writeJson5(res, result.status, result.body);
|
|
10164
11162
|
}
|
|
10165
11163
|
if (method === "POST" && rest[1] === "launch") {
|
|
10166
11164
|
const cli = rest[0];
|
|
10167
11165
|
if (!isLaunchCliId(cli)) {
|
|
10168
|
-
return
|
|
11166
|
+
return writeJsonError2(res, 400, `unknown cli '${cli ?? ""}'`);
|
|
10169
11167
|
}
|
|
10170
|
-
const body = await
|
|
11168
|
+
const body = await readJsonBody5(req);
|
|
10171
11169
|
const providers = loadConfig(deps.configPath).providers ?? [];
|
|
11170
|
+
const codexAuthHelper = deps.codexAuthHelper;
|
|
11171
|
+
const keyScoped = codexAuthHelper ? await (async () => {
|
|
11172
|
+
const gateway = deps.outboundApiServer.getStatus();
|
|
11173
|
+
const serverConfig = await loadServerConfig3(deps.settingsStore);
|
|
11174
|
+
return {
|
|
11175
|
+
keyDb: deps.keyDb,
|
|
11176
|
+
bindings: serverConfig.bindings ?? [],
|
|
11177
|
+
gatewayRunning: gateway.running,
|
|
11178
|
+
gatewayBaseUrl: gateway.loopbackUrl ?? `http://127.0.0.1:${gateway.port}`,
|
|
11179
|
+
codexAuthHelper
|
|
11180
|
+
};
|
|
11181
|
+
})() : void 0;
|
|
10172
11182
|
const result = await handleCliLaunch(cli, body, {
|
|
10173
11183
|
llmConfig: deps.llmConfig,
|
|
10174
11184
|
providers,
|
|
10175
11185
|
routeLeaseManager: deps.routeLeaseManager,
|
|
11186
|
+
keyScoped,
|
|
10176
11187
|
opener: deps.cliTerminalOpener,
|
|
10177
11188
|
probe: deps.cliPathProbe
|
|
10178
11189
|
});
|
|
10179
|
-
return
|
|
11190
|
+
return writeJson5(res, result.status, result.body);
|
|
10180
11191
|
}
|
|
10181
|
-
return
|
|
11192
|
+
return writeJsonError2(res, 405, `method ${method} not allowed on cli`);
|
|
10182
11193
|
}
|
|
10183
11194
|
async function handleIntegrations(req, res, method, rest, deps) {
|
|
10184
11195
|
const factory = deps.integrationManagerFactory;
|
|
10185
|
-
if (!factory) return
|
|
11196
|
+
if (!factory) return writeJsonError2(res, 501, "native CLI integration is not available");
|
|
10186
11197
|
const manager = factory();
|
|
10187
11198
|
try {
|
|
10188
11199
|
if (method === "GET" && rest.length === 0) {
|
|
10189
|
-
return
|
|
11200
|
+
return writeJson5(res, 200, {
|
|
10190
11201
|
integrations: await manager.listStatus(),
|
|
10191
11202
|
gateway: deps.outboundApiServer.getStatus()
|
|
10192
11203
|
});
|
|
10193
11204
|
}
|
|
10194
11205
|
if (method === "POST" && rest.length === 1 && rest[0] === "rotate") {
|
|
10195
11206
|
await manager.rotateGatewayKey();
|
|
10196
|
-
return
|
|
11207
|
+
return writeJson5(res, 200, { ok: true, integrations: await manager.listStatus() });
|
|
10197
11208
|
}
|
|
10198
11209
|
const client = rest[0];
|
|
10199
11210
|
if (!isIntegrationClient(client)) {
|
|
10200
|
-
return
|
|
11211
|
+
return writeJsonError2(res, 400, `unknown integration client '${client ?? ""}'`);
|
|
10201
11212
|
}
|
|
10202
11213
|
if (method === "POST" && rest[1] === "key") {
|
|
10203
|
-
const body = await
|
|
11214
|
+
const body = await readJsonBody5(req);
|
|
10204
11215
|
if (Object.keys(body).length !== 1 || typeof body.keyId !== "string" || !body.keyId.trim()) {
|
|
10205
|
-
return
|
|
11216
|
+
return writeJsonError2(res, 400, "body must contain a non-empty keyId string");
|
|
10206
11217
|
}
|
|
10207
11218
|
const status = await manager.bindIntegrationKey(client, body.keyId.trim());
|
|
10208
|
-
return
|
|
11219
|
+
return writeJson5(res, 200, { integration: status });
|
|
10209
11220
|
}
|
|
10210
11221
|
if (method === "POST" && rest[1] === "plan") {
|
|
10211
|
-
const body = await
|
|
11222
|
+
const body = await readJsonBody5(req);
|
|
10212
11223
|
const configPath = body.configPath;
|
|
10213
11224
|
if (configPath !== void 0 && typeof configPath !== "string") {
|
|
10214
|
-
return
|
|
11225
|
+
return writeJsonError2(res, 400, "configPath must be a string");
|
|
10215
11226
|
}
|
|
10216
11227
|
const plan = await manager.plan(client, configPath);
|
|
10217
|
-
return
|
|
11228
|
+
return writeJson5(res, 200, { plan });
|
|
10218
11229
|
}
|
|
10219
11230
|
if (method === "POST" && (rest[1] === "install" || rest[1] === "apply")) {
|
|
10220
|
-
const body = await
|
|
11231
|
+
const body = await readJsonBody5(req);
|
|
10221
11232
|
const configPath = body.configPath;
|
|
10222
11233
|
if (configPath !== void 0 && typeof configPath !== "string") {
|
|
10223
|
-
return
|
|
11234
|
+
return writeJsonError2(res, 400, "configPath must be a string");
|
|
10224
11235
|
}
|
|
10225
11236
|
const status = await manager.install(client, configPath);
|
|
10226
|
-
return
|
|
11237
|
+
return writeJson5(res, 200, { integration: status });
|
|
10227
11238
|
}
|
|
10228
11239
|
if (method === "POST" && rest[1] === "repair") {
|
|
10229
11240
|
const status = await manager.repair(client);
|
|
10230
|
-
return
|
|
11241
|
+
return writeJson5(res, 200, { integration: status });
|
|
10231
11242
|
}
|
|
10232
11243
|
if (method === "DELETE" && rest.length === 1 || method === "POST" && rest[1] === "remove") {
|
|
10233
11244
|
const status = await manager.remove(client);
|
|
10234
|
-
return
|
|
11245
|
+
return writeJson5(res, 200, { integration: status });
|
|
10235
11246
|
}
|
|
10236
|
-
return
|
|
11247
|
+
return writeJsonError2(res, 405, `method ${method} not allowed on integrations`);
|
|
10237
11248
|
} catch (error) {
|
|
10238
11249
|
if (error instanceof IntegrationConflictError) {
|
|
10239
|
-
return
|
|
11250
|
+
return writeJsonError2(res, 409, error.message);
|
|
10240
11251
|
}
|
|
10241
11252
|
throw error;
|
|
10242
11253
|
}
|
|
@@ -10379,7 +11390,7 @@ function imageProviderEvidence(images, capabilities) {
|
|
|
10379
11390
|
async function handleImagesVerifyLive(req, res, deps) {
|
|
10380
11391
|
const verifier = deps.imageLiveVerifier;
|
|
10381
11392
|
if (!verifier) {
|
|
10382
|
-
return
|
|
11393
|
+
return writeJsonError2(res, 501, "Images live verification is not available");
|
|
10383
11394
|
}
|
|
10384
11395
|
const serverConfig = await loadServerConfig3(deps.settingsStore);
|
|
10385
11396
|
const images = serverConfig.images ?? DEFAULT_IMAGES_SERVER_CONFIG;
|
|
@@ -10388,7 +11399,7 @@ async function handleImagesVerifyLive(req, res, deps) {
|
|
|
10388
11399
|
req.on("close", () => controller.abort());
|
|
10389
11400
|
const result = await verifier.verifyLive(images, controller.signal);
|
|
10390
11401
|
const antigravityRouted = Object.values(images.models).includes("antigravity-subscription");
|
|
10391
|
-
return
|
|
11402
|
+
return writeJson5(res, 200, {
|
|
10392
11403
|
...result,
|
|
10393
11404
|
...antigravityRouted ? { antigravityDeferred: true } : {}
|
|
10394
11405
|
});
|
|
@@ -10396,18 +11407,18 @@ async function handleImagesVerifyLive(req, res, deps) {
|
|
|
10396
11407
|
async function handleImages(req, res, method, rest, deps) {
|
|
10397
11408
|
if (rest.length === 1 && rest[0] === "verify-live") {
|
|
10398
11409
|
if (method !== "POST") {
|
|
10399
|
-
return
|
|
11410
|
+
return writeJsonError2(res, 405, `method ${method} not allowed on Images verify-live`);
|
|
10400
11411
|
}
|
|
10401
11412
|
return handleImagesVerifyLive(req, res, deps);
|
|
10402
11413
|
}
|
|
10403
11414
|
if (rest.length !== 1 || rest[0] !== "capabilities") {
|
|
10404
|
-
return
|
|
11415
|
+
return writeJsonError2(res, 404, "unknown Images admin resource");
|
|
10405
11416
|
}
|
|
10406
11417
|
if (method !== "GET") {
|
|
10407
|
-
return
|
|
11418
|
+
return writeJsonError2(res, 405, `method ${method} not allowed on Images capabilities`);
|
|
10408
11419
|
}
|
|
10409
11420
|
const reader = deps.imageRuntimeStatus;
|
|
10410
|
-
if (!reader) return
|
|
11421
|
+
if (!reader) return writeJsonError2(res, 501, "Images runtime status is not available");
|
|
10411
11422
|
const serverConfig = await loadServerConfig3(deps.settingsStore);
|
|
10412
11423
|
const images = serverConfig.images ?? DEFAULT_IMAGES_SERVER_CONFIG;
|
|
10413
11424
|
const lifecycle = reader.status();
|
|
@@ -10428,7 +11439,7 @@ async function handleImages(req, res, method, rest, deps) {
|
|
|
10428
11439
|
httpLeases: safeStatusCount(generation.httpLeases),
|
|
10429
11440
|
hostedLeases: safeStatusCount(generation.hostedLeases)
|
|
10430
11441
|
}));
|
|
10431
|
-
return
|
|
11442
|
+
return writeJson5(res, 200, {
|
|
10432
11443
|
configured: {
|
|
10433
11444
|
enabled: images.enabled,
|
|
10434
11445
|
provider: images.models[images.defaultModel] ?? "codex-subscription",
|
|
@@ -10455,7 +11466,7 @@ async function handleImages(req, res, method, rest, deps) {
|
|
|
10455
11466
|
});
|
|
10456
11467
|
}
|
|
10457
11468
|
async function handleStatus(res, method, deps) {
|
|
10458
|
-
if (method !== "GET") return
|
|
11469
|
+
if (method !== "GET") return writeJsonError2(res, 405, `method ${method} not allowed on status`);
|
|
10459
11470
|
const status = deps.outboundApiServer.getStatus();
|
|
10460
11471
|
const serverConfig = await loadServerConfig3(deps.settingsStore);
|
|
10461
11472
|
const endpoints = ["chat", "responses", "messages", "gemini"].map((endpoint) => {
|
|
@@ -10495,14 +11506,14 @@ async function handleStatus(res, method, deps) {
|
|
|
10495
11506
|
})() : void 0;
|
|
10496
11507
|
if (status.running) {
|
|
10497
11508
|
const queueStatus = deps.outboundApiServer.getQueueStatus();
|
|
10498
|
-
return
|
|
11509
|
+
return writeJson5(res, 200, {
|
|
10499
11510
|
...status,
|
|
10500
11511
|
endpoints,
|
|
10501
11512
|
queueStatus,
|
|
10502
11513
|
...imageRuntime ? { imageRuntime } : {}
|
|
10503
11514
|
});
|
|
10504
11515
|
}
|
|
10505
|
-
return
|
|
11516
|
+
return writeJson5(res, 200, {
|
|
10506
11517
|
...status,
|
|
10507
11518
|
endpoints,
|
|
10508
11519
|
...imageRuntime ? { imageRuntime } : {}
|
|
@@ -10525,23 +11536,23 @@ function resolvePlaygroundPath(endpoint, body) {
|
|
|
10525
11536
|
}
|
|
10526
11537
|
}
|
|
10527
11538
|
async function handlePlayground(req, res, method, deps) {
|
|
10528
|
-
if (method !== "POST") return
|
|
10529
|
-
const body = await
|
|
11539
|
+
if (method !== "POST") return writeJsonError2(res, 405, `method ${method} not allowed on playground`);
|
|
11540
|
+
const body = await readJsonBody5(req);
|
|
10530
11541
|
const endpoint = typeof body["endpoint"] === "string" ? body["endpoint"] : "";
|
|
10531
11542
|
const key = typeof body["key"] === "string" ? body["key"] : "";
|
|
10532
11543
|
const payload = body["body"];
|
|
10533
11544
|
const status = deps.outboundApiServer.getStatus();
|
|
10534
|
-
if (!status.running || !status.port) return
|
|
10535
|
-
const path2 = resolvePlaygroundPath(endpoint,
|
|
10536
|
-
if (!path2) return
|
|
11545
|
+
if (!status.running || !status.port) return writeJsonError2(res, 503, "outbound server not running");
|
|
11546
|
+
const path2 = resolvePlaygroundPath(endpoint, isRecord11(payload) ? payload : {});
|
|
11547
|
+
if (!path2) return writeJsonError2(res, 400, `unknown endpoint '${endpoint}'`);
|
|
10537
11548
|
const upstreamBody = typeof payload === "string" ? payload : JSON.stringify(payload ?? {});
|
|
10538
11549
|
await proxyToOutbound(res, status.port, path2, key, upstreamBody);
|
|
10539
11550
|
}
|
|
10540
|
-
function
|
|
11551
|
+
function isRecord11(v) {
|
|
10541
11552
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
10542
11553
|
}
|
|
10543
11554
|
function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
10544
|
-
return new Promise((
|
|
11555
|
+
return new Promise((resolve12) => {
|
|
10545
11556
|
const upstream = http.request(
|
|
10546
11557
|
{
|
|
10547
11558
|
host: "127.0.0.1",
|
|
@@ -10562,14 +11573,14 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
|
10562
11573
|
proxRes.on("data", (chunk) => res.write(chunk));
|
|
10563
11574
|
proxRes.on("end", () => {
|
|
10564
11575
|
res.end();
|
|
10565
|
-
|
|
11576
|
+
resolve12();
|
|
10566
11577
|
});
|
|
10567
11578
|
}
|
|
10568
11579
|
);
|
|
10569
11580
|
upstream.on("error", (err9) => {
|
|
10570
|
-
if (!res.headersSent)
|
|
11581
|
+
if (!res.headersSent) writeJsonError2(res, 502, `playground proxy failed: ${err9.message}`);
|
|
10571
11582
|
else res.end();
|
|
10572
|
-
|
|
11583
|
+
resolve12();
|
|
10573
11584
|
});
|
|
10574
11585
|
upstream.write(body);
|
|
10575
11586
|
upstream.end();
|
|
@@ -10577,8 +11588,8 @@ function proxyToOutbound(res, outboundPort, path2, key, body) {
|
|
|
10577
11588
|
}
|
|
10578
11589
|
|
|
10579
11590
|
// src/admin/uiStatic.ts
|
|
10580
|
-
import { existsSync as
|
|
10581
|
-
import { readFile } from "fs/promises";
|
|
11591
|
+
import { existsSync as existsSync10, statSync as statSync5 } from "fs";
|
|
11592
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
10582
11593
|
import { createRequire } from "module";
|
|
10583
11594
|
import path from "path";
|
|
10584
11595
|
var CONTENT_TYPES = {
|
|
@@ -10600,13 +11611,13 @@ var CONTENT_TYPES = {
|
|
|
10600
11611
|
function resolveUiDist() {
|
|
10601
11612
|
const fromEnv = process.env["OMNICROSS_UI_DIST"];
|
|
10602
11613
|
if (fromEnv) {
|
|
10603
|
-
return
|
|
11614
|
+
return existsSync10(path.join(fromEnv, "index.html")) ? path.resolve(fromEnv) : null;
|
|
10604
11615
|
}
|
|
10605
11616
|
try {
|
|
10606
11617
|
const req = createRequire(typeof __filename !== "undefined" ? __filename : import.meta.url);
|
|
10607
11618
|
const pkgJson = req.resolve("@omnicross/ui/package.json");
|
|
10608
11619
|
const dist = path.join(path.dirname(pkgJson), "dist");
|
|
10609
|
-
return
|
|
11620
|
+
return existsSync10(path.join(dist, "index.html")) ? dist : null;
|
|
10610
11621
|
} catch {
|
|
10611
11622
|
return null;
|
|
10612
11623
|
}
|
|
@@ -10655,7 +11666,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
10655
11666
|
return true;
|
|
10656
11667
|
}
|
|
10657
11668
|
let target = filePath;
|
|
10658
|
-
if (!
|
|
11669
|
+
if (!existsSync10(target) || statSync5(target).isDirectory()) {
|
|
10659
11670
|
if (path.extname(rel) === "") {
|
|
10660
11671
|
target = path.join(uiDist, "index.html");
|
|
10661
11672
|
} else {
|
|
@@ -10664,7 +11675,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
10664
11675
|
return true;
|
|
10665
11676
|
}
|
|
10666
11677
|
}
|
|
10667
|
-
const body = await
|
|
11678
|
+
const body = await readFile2(target);
|
|
10668
11679
|
const type = CONTENT_TYPES[path.extname(target).toLowerCase()] ?? "application/octet-stream";
|
|
10669
11680
|
res.writeHead(200, { "Content-Type": type, "Content-Length": body.length });
|
|
10670
11681
|
res.end(req.method === "HEAD" ? void 0 : body);
|
|
@@ -10672,7 +11683,7 @@ async function handleUiStatic(req, res, urlPath, uiDist) {
|
|
|
10672
11683
|
}
|
|
10673
11684
|
|
|
10674
11685
|
// src/admin/version.ts
|
|
10675
|
-
var DAEMON_VERSION = true ? "0.4.
|
|
11686
|
+
var DAEMON_VERSION = true ? "0.4.5" : "0.0.0-dev";
|
|
10676
11687
|
|
|
10677
11688
|
// src/admin/AdminServer.ts
|
|
10678
11689
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
@@ -10711,14 +11722,14 @@ var AdminServer = class {
|
|
|
10711
11722
|
}
|
|
10712
11723
|
/** Bind once; on EADDRINUSE retry with an ephemeral port (port 0). */
|
|
10713
11724
|
listen(bindAddr, port) {
|
|
10714
|
-
return new Promise((
|
|
11725
|
+
return new Promise((resolve12, reject) => {
|
|
10715
11726
|
const server = http2.createServer((req, res) => {
|
|
10716
11727
|
this.onRequest(req, res);
|
|
10717
11728
|
});
|
|
10718
11729
|
const onError = (err9) => {
|
|
10719
11730
|
if (err9.code === "EADDRINUSE" && port !== 0) {
|
|
10720
11731
|
server.removeListener("error", onError);
|
|
10721
|
-
this.listen(bindAddr, 0).then(
|
|
11732
|
+
this.listen(bindAddr, 0).then(resolve12, reject);
|
|
10722
11733
|
return;
|
|
10723
11734
|
}
|
|
10724
11735
|
reject(err9);
|
|
@@ -10730,7 +11741,7 @@ var AdminServer = class {
|
|
|
10730
11741
|
server.removeListener("error", onError);
|
|
10731
11742
|
server.on("error", (e) => this.deps.logger.error("[AdminServer] server error", e));
|
|
10732
11743
|
this.server = server;
|
|
10733
|
-
|
|
11744
|
+
resolve12(addr.port);
|
|
10734
11745
|
} else {
|
|
10735
11746
|
reject(new Error("Failed to get admin server address"));
|
|
10736
11747
|
}
|
|
@@ -10804,6 +11815,10 @@ var AdminServer = class {
|
|
|
10804
11815
|
await handleRouteLeaseApi(req, res, path2, this.deps);
|
|
10805
11816
|
return;
|
|
10806
11817
|
}
|
|
11818
|
+
if (path2 === "/admin/api/codex-sessions" || path2 === "/admin/api/codex-sessions/preview" || path2 === "/admin/api/codex-sessions/apply") {
|
|
11819
|
+
await handleCodexSessionApi(req, res, path2, this.deps.codexSessionManager);
|
|
11820
|
+
return;
|
|
11821
|
+
}
|
|
10807
11822
|
if (path2.startsWith("/admin/api/")) {
|
|
10808
11823
|
await handleAdminApi(req, res, path2, this.deps);
|
|
10809
11824
|
return;
|
|
@@ -10827,8 +11842,8 @@ var AdminServer = class {
|
|
|
10827
11842
|
if (!server) return;
|
|
10828
11843
|
this.server = null;
|
|
10829
11844
|
this.boundPort = 0;
|
|
10830
|
-
return new Promise((
|
|
10831
|
-
server.close(() =>
|
|
11845
|
+
return new Promise((resolve12) => {
|
|
11846
|
+
server.close(() => resolve12());
|
|
10832
11847
|
});
|
|
10833
11848
|
}
|
|
10834
11849
|
/** A live status snapshot. */
|
|
@@ -10967,7 +11982,7 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
|
|
|
10967
11982
|
const port = binding.port ?? LOOPBACK_PORT;
|
|
10968
11983
|
const callbackPath = binding.path ?? CALLBACK_PATH;
|
|
10969
11984
|
const label = binding.label ?? "codex";
|
|
10970
|
-
return new Promise((
|
|
11985
|
+
return new Promise((resolve12, reject) => {
|
|
10971
11986
|
let settled = false;
|
|
10972
11987
|
const finish = (server2, fn) => {
|
|
10973
11988
|
if (settled) return;
|
|
@@ -10999,7 +12014,7 @@ function awaitLoopbackCode(expectedState, timeoutMs = DEFAULT_TIMEOUT_MS, signal
|
|
|
10999
12014
|
}
|
|
11000
12015
|
res.writeHead(200, HTML_HEADERS);
|
|
11001
12016
|
res.end(pageHtml("Login complete."));
|
|
11002
|
-
finish(server, () =>
|
|
12017
|
+
finish(server, () => resolve12(code));
|
|
11003
12018
|
});
|
|
11004
12019
|
const abort = () => finish(server, () => reject(new Error("login: cancelled")));
|
|
11005
12020
|
if (signal?.aborted) {
|
|
@@ -11128,7 +12143,7 @@ function secondsUntil9(instant, now) {
|
|
|
11128
12143
|
if (!instant) return void 0;
|
|
11129
12144
|
return Math.max(0, Math.floor((Date.parse(instant) - now) / 1e3));
|
|
11130
12145
|
}
|
|
11131
|
-
function
|
|
12146
|
+
function isRecord12(value) {
|
|
11132
12147
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
11133
12148
|
}
|
|
11134
12149
|
function detectProviderKeyQuotaAdapter(baseUrl) {
|
|
@@ -11195,17 +12210,17 @@ function zaiWindowIdLabel(durationMs) {
|
|
|
11195
12210
|
return { id: "quota", label: "Quota" };
|
|
11196
12211
|
}
|
|
11197
12212
|
function parseZaiQuotaPayload(payload, now) {
|
|
11198
|
-
if (!
|
|
11199
|
-
const data =
|
|
12213
|
+
if (!isRecord12(payload)) return null;
|
|
12214
|
+
const data = isRecord12(payload["data"]) ? payload["data"] : payload;
|
|
11200
12215
|
if (payload["success"] === false) return null;
|
|
11201
12216
|
const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
|
|
11202
12217
|
const byWindow = /* @__PURE__ */ new Map();
|
|
11203
12218
|
for (const raw of limits) {
|
|
11204
|
-
if (!
|
|
12219
|
+
if (!isRecord12(raw)) continue;
|
|
11205
12220
|
const item = raw;
|
|
11206
12221
|
if (item.type === void 0) continue;
|
|
11207
12222
|
const details = raw["usageDetails"];
|
|
11208
|
-
if (Array.isArray(details) && details.some((d) =>
|
|
12223
|
+
if (Array.isArray(details) && details.some((d) => isRecord12(d) && d["modelCode"] === "zread")) {
|
|
11209
12224
|
continue;
|
|
11210
12225
|
}
|
|
11211
12226
|
const durationMs = zaiWindowDurationMs(item);
|
|
@@ -11238,7 +12253,7 @@ function parseZaiQuotaPayload(payload, now) {
|
|
|
11238
12253
|
var MINIMAX_STATUS_EXHAUSTED = 2;
|
|
11239
12254
|
var MINIMAX_SHARED_BUCKET = "general";
|
|
11240
12255
|
function parseMiniMaxBucket(value) {
|
|
11241
|
-
if (!
|
|
12256
|
+
if (!isRecord12(value)) return null;
|
|
11242
12257
|
const modelName = typeof value["model_name"] === "string" ? value["model_name"].trim() : "";
|
|
11243
12258
|
if (!modelName) return null;
|
|
11244
12259
|
const instant = (v) => {
|
|
@@ -11270,9 +12285,9 @@ function minimaxWindow(id, label, windowMinutes, resetsAtMs, remainingPercent, s
|
|
|
11270
12285
|
};
|
|
11271
12286
|
}
|
|
11272
12287
|
function parseMiniMaxTokenPlanPayload(payload, now) {
|
|
11273
|
-
if (!
|
|
12288
|
+
if (!isRecord12(payload)) return null;
|
|
11274
12289
|
const baseResp = payload["base_resp"];
|
|
11275
|
-
if (!
|
|
12290
|
+
if (!isRecord12(baseResp) || baseResp["status_code"] !== 0) return null;
|
|
11276
12291
|
const buckets = Array.isArray(payload["model_remains"]) ? payload["model_remains"] : [];
|
|
11277
12292
|
let general = null;
|
|
11278
12293
|
for (const raw of buckets) {
|
|
@@ -11305,11 +12320,11 @@ function parseMiniMaxTokenPlanPayload(payload, now) {
|
|
|
11305
12320
|
];
|
|
11306
12321
|
}
|
|
11307
12322
|
function parseUmansUsagePayload(payload, now) {
|
|
11308
|
-
if (!
|
|
11309
|
-
const limits =
|
|
11310
|
-
const requests = limits &&
|
|
11311
|
-
const usage =
|
|
11312
|
-
const window =
|
|
12323
|
+
if (!isRecord12(payload)) return null;
|
|
12324
|
+
const limits = isRecord12(payload["limits"]) ? payload["limits"] : void 0;
|
|
12325
|
+
const requests = limits && isRecord12(limits["requests"]) ? limits["requests"] : void 0;
|
|
12326
|
+
const usage = isRecord12(payload["usage"]) ? payload["usage"] : void 0;
|
|
12327
|
+
const window = isRecord12(payload["window"]) ? payload["window"] : void 0;
|
|
11313
12328
|
const hardCap = finiteNumber5(requests?.["hard_cap"]);
|
|
11314
12329
|
const softLimit = finiteNumber5(requests?.["limit"]);
|
|
11315
12330
|
const requestsInWindow = finiteNumber5(usage?.["requests_in_window"]);
|
|
@@ -11336,9 +12351,9 @@ function parseUmansUsagePayload(payload, now) {
|
|
|
11336
12351
|
];
|
|
11337
12352
|
}
|
|
11338
12353
|
function parseSyntheticQuotasPayload(payload, now) {
|
|
11339
|
-
if (!
|
|
11340
|
-
const fiveHour =
|
|
11341
|
-
const weekly =
|
|
12354
|
+
if (!isRecord12(payload)) return null;
|
|
12355
|
+
const fiveHour = isRecord12(payload["rollingFiveHourLimit"]) ? payload["rollingFiveHourLimit"] : void 0;
|
|
12356
|
+
const weekly = isRecord12(payload["weeklyTokenLimit"]) ? payload["weeklyTokenLimit"] : void 0;
|
|
11342
12357
|
const windows = [];
|
|
11343
12358
|
if (fiveHour) {
|
|
11344
12359
|
const max = finiteNumber5(fiveHour["max"]);
|
|
@@ -11379,12 +12394,12 @@ var CLINE_WINDOW_CONFIG = {
|
|
|
11379
12394
|
monthly: { id: "thirty-day", label: "30 days", minutes: 30 * 24 * 60 }
|
|
11380
12395
|
};
|
|
11381
12396
|
function parseClinePassUsageLimitsPayload(payload, now) {
|
|
11382
|
-
if (!
|
|
11383
|
-
const data =
|
|
12397
|
+
if (!isRecord12(payload)) return null;
|
|
12398
|
+
const data = isRecord12(payload["data"]) ? payload["data"] : payload;
|
|
11384
12399
|
const limits = Array.isArray(data["limits"]) ? data["limits"] : [];
|
|
11385
12400
|
const windows = [];
|
|
11386
12401
|
for (const raw of limits) {
|
|
11387
|
-
if (!
|
|
12402
|
+
if (!isRecord12(raw)) continue;
|
|
11388
12403
|
const config = CLINE_WINDOW_CONFIG[typeof raw["type"] === "string" ? raw["type"] : ""];
|
|
11389
12404
|
if (!config) continue;
|
|
11390
12405
|
const usedPercent = finitePercent4(raw["percentUsed"]);
|
|
@@ -11553,7 +12568,7 @@ import { createHmac as createHmac2, randomBytes as randomBytes5 } from "crypto";
|
|
|
11553
12568
|
import {
|
|
11554
12569
|
chmodSync as chmodSync6,
|
|
11555
12570
|
closeSync as closeSync3,
|
|
11556
|
-
existsSync as
|
|
12571
|
+
existsSync as existsSync12,
|
|
11557
12572
|
fsyncSync as fsyncSync2,
|
|
11558
12573
|
lstatSync as lstatSync3,
|
|
11559
12574
|
openSync as openSync3,
|
|
@@ -11562,7 +12577,7 @@ import {
|
|
|
11562
12577
|
unlinkSync as unlinkSync5,
|
|
11563
12578
|
writeFileSync as writeFileSync8
|
|
11564
12579
|
} from "fs";
|
|
11565
|
-
import { basename as
|
|
12580
|
+
import { basename as basename3, dirname as dirname6, join as join11, resolve as resolve5 } from "path";
|
|
11566
12581
|
import { IMAGE_SERVER_HARD_CEILINGS } from "@omnicross/core/outbound-api";
|
|
11567
12582
|
|
|
11568
12583
|
// src/image-generation/imageTenantHmac.ts
|
|
@@ -11571,7 +12586,7 @@ import {
|
|
|
11571
12586
|
chmodSync as chmodSync5,
|
|
11572
12587
|
closeSync as closeSync2,
|
|
11573
12588
|
constants,
|
|
11574
|
-
existsSync as
|
|
12589
|
+
existsSync as existsSync11,
|
|
11575
12590
|
fstatSync,
|
|
11576
12591
|
fsyncSync,
|
|
11577
12592
|
lstatSync as lstatSync2,
|
|
@@ -11579,7 +12594,7 @@ import {
|
|
|
11579
12594
|
readFileSync as readFileSync8,
|
|
11580
12595
|
writeFileSync as writeFileSync7
|
|
11581
12596
|
} from "fs";
|
|
11582
|
-
import { join as
|
|
12597
|
+
import { join as join10 } from "path";
|
|
11583
12598
|
var TENANT_SALT_NAME = "tenant-hmac-salt.v1.bin";
|
|
11584
12599
|
var TENANT_KEY_PATTERN = /^[a-f0-9]{64}$/u;
|
|
11585
12600
|
var REFERENCE_DOMAIN = Buffer.from("omnicross:image-reference:tenant:v1\0", "utf8");
|
|
@@ -11597,8 +12612,8 @@ function deriveImageTenantHmac(salt, purpose, tenantId) {
|
|
|
11597
12612
|
}
|
|
11598
12613
|
function loadOrCreateImageTenantHmacSalt(paths, random) {
|
|
11599
12614
|
const root = paths.verifiedRoot("mountManifest");
|
|
11600
|
-
const path2 =
|
|
11601
|
-
if (!
|
|
12615
|
+
const path2 = join10(root, TENANT_SALT_NAME);
|
|
12616
|
+
if (!existsSync11(path2)) {
|
|
11602
12617
|
const salt = random(32);
|
|
11603
12618
|
if (salt.byteLength !== 32) throw new TypeError("image tenant HMAC salt generator returned invalid bytes");
|
|
11604
12619
|
let fd2;
|
|
@@ -11703,8 +12718,8 @@ function validateObservation(value) {
|
|
|
11703
12718
|
}
|
|
11704
12719
|
}
|
|
11705
12720
|
function samePath2(left, right) {
|
|
11706
|
-
const a =
|
|
11707
|
-
const b =
|
|
12721
|
+
const a = resolve5(left);
|
|
12722
|
+
const b = resolve5(right);
|
|
11708
12723
|
return process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
11709
12724
|
}
|
|
11710
12725
|
function capabilityValues(entry) {
|
|
@@ -11766,7 +12781,7 @@ var FileCodexImageCapabilityEvidenceManifestOwner = class {
|
|
|
11766
12781
|
this.#replaceManifest = options.replaceManifest ?? ((target, contents) => {
|
|
11767
12782
|
this.#atomicReplace(target, contents);
|
|
11768
12783
|
});
|
|
11769
|
-
if (
|
|
12784
|
+
if (existsSync12(this.#manifestPath())) this.#load();
|
|
11770
12785
|
}
|
|
11771
12786
|
createSource(ttlMs) {
|
|
11772
12787
|
return new FileCodexImageCapabilityEvidenceSource({ owner: this, ttlMs });
|
|
@@ -11878,7 +12893,7 @@ var FileCodexImageCapabilityEvidenceManifestOwner = class {
|
|
|
11878
12893
|
return Math.min(entry.expiresAt, entry.verifiedAt + ttlMs);
|
|
11879
12894
|
}
|
|
11880
12895
|
#manifestPath() {
|
|
11881
|
-
return
|
|
12896
|
+
return join11(this.#paths.verifiedRoot("evidence"), MANIFEST_NAME);
|
|
11882
12897
|
}
|
|
11883
12898
|
#serialized(entries, revision) {
|
|
11884
12899
|
const manifest = {
|
|
@@ -11923,15 +12938,15 @@ var FileCodexImageCapabilityEvidenceManifestOwner = class {
|
|
|
11923
12938
|
this.#entries = entries;
|
|
11924
12939
|
}
|
|
11925
12940
|
#refresh() {
|
|
11926
|
-
if (!
|
|
12941
|
+
if (!existsSync12(this.#manifestPath())) return;
|
|
11927
12942
|
this.#load();
|
|
11928
12943
|
}
|
|
11929
12944
|
#atomicReplace(targetPath, contents) {
|
|
11930
12945
|
const root = this.#paths.verifiedRoot("evidence");
|
|
11931
|
-
if (!samePath2(dirname6(
|
|
12946
|
+
if (!samePath2(dirname6(resolve5(targetPath)), root) || basename3(targetPath) !== MANIFEST_NAME) {
|
|
11932
12947
|
throw new TypeError("Codex image evidence manifest target is invalid");
|
|
11933
12948
|
}
|
|
11934
|
-
const temporaryPath =
|
|
12949
|
+
const temporaryPath = join11(
|
|
11935
12950
|
root,
|
|
11936
12951
|
`.codex-image-evidence.${process.pid}.${this.#random(8).toString("hex")}.tmp`
|
|
11937
12952
|
);
|
|
@@ -11951,7 +12966,7 @@ var FileCodexImageCapabilityEvidenceManifestOwner = class {
|
|
|
11951
12966
|
} catch {
|
|
11952
12967
|
}
|
|
11953
12968
|
}
|
|
11954
|
-
if (
|
|
12969
|
+
if (existsSync12(temporaryPath)) {
|
|
11955
12970
|
try {
|
|
11956
12971
|
unlinkSync5(temporaryPath);
|
|
11957
12972
|
} catch {
|
|
@@ -12543,11 +13558,11 @@ var DaemonImageExecutionScheduler = class {
|
|
|
12543
13558
|
retrySafety: "before_acceptance"
|
|
12544
13559
|
});
|
|
12545
13560
|
}
|
|
12546
|
-
return new Promise((
|
|
13561
|
+
return new Promise((resolve12, reject) => {
|
|
12547
13562
|
const waiter = {
|
|
12548
13563
|
tenantKey,
|
|
12549
13564
|
signal: request.signal,
|
|
12550
|
-
resolve:
|
|
13565
|
+
resolve: resolve12,
|
|
12551
13566
|
reject,
|
|
12552
13567
|
onAbort: () => void 0,
|
|
12553
13568
|
settled: false
|
|
@@ -12710,7 +13725,7 @@ import {
|
|
|
12710
13725
|
ImageGenerationError as ImageGenerationError3,
|
|
12711
13726
|
ImageRequestResourceScope
|
|
12712
13727
|
} from "@omnicross/core/image-generation";
|
|
12713
|
-
import { dirname as dirname7, resolve as
|
|
13728
|
+
import { dirname as dirname7, resolve as resolve6 } from "path";
|
|
12714
13729
|
function capacityExceeded() {
|
|
12715
13730
|
throw new ImageGenerationError3("image_too_large");
|
|
12716
13731
|
}
|
|
@@ -12795,10 +13810,10 @@ var DaemonImageActiveScopeRegistry = class {
|
|
|
12795
13810
|
#temporaryRoot;
|
|
12796
13811
|
#active = /* @__PURE__ */ new Set();
|
|
12797
13812
|
constructor(paths) {
|
|
12798
|
-
this.#temporaryRoot =
|
|
13813
|
+
this.#temporaryRoot = resolve6(paths.paths.temporaryRoot);
|
|
12799
13814
|
}
|
|
12800
13815
|
register(privateDirectory) {
|
|
12801
|
-
const normalized2 =
|
|
13816
|
+
const normalized2 = resolve6(privateDirectory);
|
|
12802
13817
|
if (dirname7(normalized2) !== this.#temporaryRoot || this.#active.has(normalized2)) {
|
|
12803
13818
|
throw new TypeError("image temporary scope directory is invalid or already active");
|
|
12804
13819
|
}
|
|
@@ -12811,7 +13826,7 @@ var DaemonImageActiveScopeRegistry = class {
|
|
|
12811
13826
|
};
|
|
12812
13827
|
}
|
|
12813
13828
|
isActive(privateDirectory) {
|
|
12814
|
-
return this.#active.has(
|
|
13829
|
+
return this.#active.has(resolve6(privateDirectory));
|
|
12815
13830
|
}
|
|
12816
13831
|
status() {
|
|
12817
13832
|
return Object.freeze({ activeDirectories: this.#active.size });
|
|
@@ -13105,14 +14120,14 @@ function createImageRuntimeGeneration(options) {
|
|
|
13105
14120
|
|
|
13106
14121
|
// src/image-generation/ImageStartupReconciler.ts
|
|
13107
14122
|
import {
|
|
13108
|
-
existsSync as
|
|
14123
|
+
existsSync as existsSync16,
|
|
13109
14124
|
lstatSync as lstatSync7,
|
|
13110
14125
|
readFileSync as readFileSync13,
|
|
13111
14126
|
readdirSync as readdirSync6,
|
|
13112
14127
|
rmdirSync as rmdirSync2,
|
|
13113
14128
|
unlinkSync as unlinkSync9
|
|
13114
14129
|
} from "fs";
|
|
13115
|
-
import { basename as
|
|
14130
|
+
import { basename as basename7, dirname as dirname11, isAbsolute as isAbsolute3, relative as relative2, resolve as resolve10 } from "path";
|
|
13116
14131
|
import {
|
|
13117
14132
|
IMAGE_REQUEST_DIRECTORY_MARKER_CONTENT,
|
|
13118
14133
|
IMAGE_REQUEST_DIRECTORY_MARKER_NAME
|
|
@@ -13122,7 +14137,7 @@ import {
|
|
|
13122
14137
|
import { randomBytes as randomBytes9 } from "crypto";
|
|
13123
14138
|
import {
|
|
13124
14139
|
closeSync as closeSync6,
|
|
13125
|
-
existsSync as
|
|
14140
|
+
existsSync as existsSync15,
|
|
13126
14141
|
fsyncSync as fsyncSync5,
|
|
13127
14142
|
lstatSync as lstatSync6,
|
|
13128
14143
|
openSync as openSync6,
|
|
@@ -13132,7 +14147,7 @@ import {
|
|
|
13132
14147
|
unlinkSync as unlinkSync8,
|
|
13133
14148
|
writeFileSync as writeFileSync11
|
|
13134
14149
|
} from "fs";
|
|
13135
|
-
import { basename as
|
|
14150
|
+
import { basename as basename6, dirname as dirname10, isAbsolute as isAbsolute2, join as join14, resolve as resolve9 } from "path";
|
|
13136
14151
|
import { ImageGenerationError as ImageGenerationError7 } from "@omnicross/core/image-generation";
|
|
13137
14152
|
|
|
13138
14153
|
// src/image-generation/FileImageReferenceStore.ts
|
|
@@ -13141,7 +14156,7 @@ import {
|
|
|
13141
14156
|
closeSync as closeSync4,
|
|
13142
14157
|
constants as constants2,
|
|
13143
14158
|
createReadStream,
|
|
13144
|
-
existsSync as
|
|
14159
|
+
existsSync as existsSync13,
|
|
13145
14160
|
fstatSync as fstatSync2,
|
|
13146
14161
|
fsyncSync as fsyncSync3,
|
|
13147
14162
|
lstatSync as lstatSync4,
|
|
@@ -13153,8 +14168,8 @@ import {
|
|
|
13153
14168
|
unlinkSync as unlinkSync6,
|
|
13154
14169
|
writeFileSync as writeFileSync9
|
|
13155
14170
|
} from "fs";
|
|
13156
|
-
import { open } from "fs/promises";
|
|
13157
|
-
import { basename as
|
|
14171
|
+
import { open as open2 } from "fs/promises";
|
|
14172
|
+
import { basename as basename4, dirname as dirname8, join as join12, resolve as resolve7 } from "path";
|
|
13158
14173
|
import { Readable } from "stream";
|
|
13159
14174
|
import {
|
|
13160
14175
|
ImageGenerationError as ImageGenerationError5
|
|
@@ -13180,8 +14195,8 @@ function safeInteger(value) {
|
|
|
13180
14195
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
13181
14196
|
}
|
|
13182
14197
|
function samePath3(left, right) {
|
|
13183
|
-
const normalizedLeft =
|
|
13184
|
-
const normalizedRight =
|
|
14198
|
+
const normalizedLeft = resolve7(left);
|
|
14199
|
+
const normalizedRight = resolve7(right);
|
|
13185
14200
|
return process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight;
|
|
13186
14201
|
}
|
|
13187
14202
|
function validEntry2(value) {
|
|
@@ -13483,7 +14498,7 @@ var FileImageReferenceStore = class {
|
|
|
13483
14498
|
const ownedArtifact = ARTIFACT_FILE.test(name);
|
|
13484
14499
|
const incomplete = /^artifact-[a-f0-9]{32}\.tmp$/u.test(name);
|
|
13485
14500
|
if ((!ownedArtifact || referenced.has(name)) && !incomplete) continue;
|
|
13486
|
-
const path2 =
|
|
14501
|
+
const path2 = resolve7(root, name);
|
|
13487
14502
|
let info;
|
|
13488
14503
|
try {
|
|
13489
14504
|
info = lstatSync4(path2);
|
|
@@ -13634,10 +14649,10 @@ var FileImageReferenceStore = class {
|
|
|
13634
14649
|
async writeArtifact(asset, limits) {
|
|
13635
14650
|
const root = this.#paths.verifiedRoot("artifacts");
|
|
13636
14651
|
const suffix = this.#random(16).toString("hex");
|
|
13637
|
-
const tempPath =
|
|
14652
|
+
const tempPath = join12(root, `artifact-${suffix}.tmp`);
|
|
13638
14653
|
const fileName = `artifact-${suffix}.bin`;
|
|
13639
|
-
const finalPath =
|
|
13640
|
-
const handle = await
|
|
14654
|
+
const finalPath = join12(root, fileName);
|
|
14655
|
+
const handle = await open2(tempPath, "wx", 384);
|
|
13641
14656
|
let reader;
|
|
13642
14657
|
let observed = 0;
|
|
13643
14658
|
let writeFailure;
|
|
@@ -13686,8 +14701,8 @@ var FileImageReferenceStore = class {
|
|
|
13686
14701
|
artifactPath(fileName) {
|
|
13687
14702
|
if (!ARTIFACT_FILE.test(fileName)) throw new TypeError("invalid image artifact filename");
|
|
13688
14703
|
const root = this.#paths.verifiedRoot("artifacts");
|
|
13689
|
-
const path2 =
|
|
13690
|
-
if (!samePath3(dirname8(path2), root) ||
|
|
14704
|
+
const path2 = resolve7(root, fileName);
|
|
14705
|
+
if (!samePath3(dirname8(path2), root) || basename4(path2) !== fileName) {
|
|
13691
14706
|
throw new TypeError("image artifact escaped its root");
|
|
13692
14707
|
}
|
|
13693
14708
|
return path2;
|
|
@@ -13710,12 +14725,12 @@ var FileImageReferenceStore = class {
|
|
|
13710
14725
|
}
|
|
13711
14726
|
safeUnlinkArtifactPath(path2) {
|
|
13712
14727
|
const root = this.#paths.verifiedRoot("artifacts");
|
|
13713
|
-
const target =
|
|
13714
|
-
const name =
|
|
14728
|
+
const target = resolve7(path2);
|
|
14729
|
+
const name = basename4(target);
|
|
13715
14730
|
if (!samePath3(dirname8(target), root) || !/^artifact-[a-f0-9]{32}\.(?:bin|tmp)$/u.test(name)) {
|
|
13716
14731
|
throw new TypeError("refusing to unlink an unverified image artifact");
|
|
13717
14732
|
}
|
|
13718
|
-
if (!
|
|
14733
|
+
if (!existsSync13(target)) return;
|
|
13719
14734
|
const info = lstatSync4(target);
|
|
13720
14735
|
if (info.isSymbolicLink() || !info.isFile()) throw new TypeError("refusing to unlink an unverified image artifact");
|
|
13721
14736
|
unlinkSync6(target);
|
|
@@ -13737,7 +14752,7 @@ var FileImageReferenceStore = class {
|
|
|
13737
14752
|
});
|
|
13738
14753
|
}
|
|
13739
14754
|
manifestPath() {
|
|
13740
|
-
return
|
|
14755
|
+
return join12(this.#paths.verifiedRoot("state"), MANIFEST_NAME2);
|
|
13741
14756
|
}
|
|
13742
14757
|
persist(entries, tombstones) {
|
|
13743
14758
|
const manifest = {
|
|
@@ -13753,10 +14768,10 @@ var FileImageReferenceStore = class {
|
|
|
13753
14768
|
}
|
|
13754
14769
|
atomicReplace(targetPath, contents) {
|
|
13755
14770
|
const root = this.#paths.verifiedRoot("state");
|
|
13756
|
-
if (!samePath3(dirname8(
|
|
14771
|
+
if (!samePath3(dirname8(resolve7(targetPath)), root) || basename4(targetPath) !== MANIFEST_NAME2) {
|
|
13757
14772
|
throw new TypeError("invalid image reference manifest target");
|
|
13758
14773
|
}
|
|
13759
|
-
const temporaryPath =
|
|
14774
|
+
const temporaryPath = join12(root, `.references.${process.pid}.${this.#random(8).toString("hex")}.tmp`);
|
|
13760
14775
|
let fd;
|
|
13761
14776
|
try {
|
|
13762
14777
|
fd = openSync4(temporaryPath, "wx", 384);
|
|
@@ -13772,7 +14787,7 @@ var FileImageReferenceStore = class {
|
|
|
13772
14787
|
} catch {
|
|
13773
14788
|
}
|
|
13774
14789
|
}
|
|
13775
|
-
if (
|
|
14790
|
+
if (existsSync13(temporaryPath)) {
|
|
13776
14791
|
try {
|
|
13777
14792
|
unlinkSync6(temporaryPath);
|
|
13778
14793
|
} catch {
|
|
@@ -13782,7 +14797,7 @@ var FileImageReferenceStore = class {
|
|
|
13782
14797
|
}
|
|
13783
14798
|
loadManifest() {
|
|
13784
14799
|
const path2 = this.manifestPath();
|
|
13785
|
-
if (!
|
|
14800
|
+
if (!existsSync13(path2)) return;
|
|
13786
14801
|
const info = lstatSync4(path2);
|
|
13787
14802
|
if (info.isSymbolicLink() || !info.isFile() || info.size > MAX_MANIFEST_BYTES2) {
|
|
13788
14803
|
throw new TypeError("image reference manifest is invalid");
|
|
@@ -13831,7 +14846,7 @@ var FileImageReferenceStore = class {
|
|
|
13831
14846
|
import { randomBytes as randomBytes8 } from "crypto";
|
|
13832
14847
|
import {
|
|
13833
14848
|
closeSync as closeSync5,
|
|
13834
|
-
existsSync as
|
|
14849
|
+
existsSync as existsSync14,
|
|
13835
14850
|
fsyncSync as fsyncSync4,
|
|
13836
14851
|
lstatSync as lstatSync5,
|
|
13837
14852
|
openSync as openSync5,
|
|
@@ -13840,7 +14855,7 @@ import {
|
|
|
13840
14855
|
unlinkSync as unlinkSync7,
|
|
13841
14856
|
writeFileSync as writeFileSync10
|
|
13842
14857
|
} from "fs";
|
|
13843
|
-
import { basename as
|
|
14858
|
+
import { basename as basename5, dirname as dirname9, join as join13, resolve as resolve8 } from "path";
|
|
13844
14859
|
import { ImageGenerationError as ImageGenerationError6 } from "@omnicross/core/image-generation";
|
|
13845
14860
|
var MANIFEST_VERSION3 = 1;
|
|
13846
14861
|
var MANIFEST_NAME3 = "responses-image-state.v1.json";
|
|
@@ -13893,8 +14908,8 @@ function validPendingReferenceDelete(value) {
|
|
|
13893
14908
|
return exactKeys2(row, ["callId", "referenceTenantKey", "referenceId", "expiresAt"]) && typeof row.callId === "string" && CALL_ID_PATTERN.test(row.callId) && isImageTenantHmac(row.referenceTenantKey) && typeof row.referenceId === "string" && REFERENCE_ID_PATTERN.test(row.referenceId) && safeTimestamp2(row.expiresAt);
|
|
13894
14909
|
}
|
|
13895
14910
|
function samePath4(left, right) {
|
|
13896
|
-
const normalizedLeft =
|
|
13897
|
-
const normalizedRight =
|
|
14911
|
+
const normalizedLeft = resolve8(left);
|
|
14912
|
+
const normalizedRight = resolve8(right);
|
|
13898
14913
|
return process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight;
|
|
13899
14914
|
}
|
|
13900
14915
|
function sameBinding(left, right) {
|
|
@@ -14377,7 +15392,7 @@ var FileResponsesImageStateStore = class {
|
|
|
14377
15392
|
});
|
|
14378
15393
|
}
|
|
14379
15394
|
manifestPath() {
|
|
14380
|
-
return
|
|
15395
|
+
return join13(this.#paths.verifiedRoot("state"), MANIFEST_NAME3);
|
|
14381
15396
|
}
|
|
14382
15397
|
persist(calls, responses, tombstones, pendingReferenceDeletes) {
|
|
14383
15398
|
const manifest = {
|
|
@@ -14397,10 +15412,10 @@ var FileResponsesImageStateStore = class {
|
|
|
14397
15412
|
}
|
|
14398
15413
|
atomicReplace(targetPath, contents) {
|
|
14399
15414
|
const root = this.#paths.verifiedRoot("state");
|
|
14400
|
-
if (!samePath4(dirname9(
|
|
15415
|
+
if (!samePath4(dirname9(resolve8(targetPath)), root) || basename5(targetPath) !== MANIFEST_NAME3) {
|
|
14401
15416
|
throw new TypeError("invalid responses image state manifest target");
|
|
14402
15417
|
}
|
|
14403
|
-
const temporaryPath =
|
|
15418
|
+
const temporaryPath = join13(
|
|
14404
15419
|
root,
|
|
14405
15420
|
`.responses-image-state.${process.pid}.${this.#random(8).toString("hex")}.tmp`
|
|
14406
15421
|
);
|
|
@@ -14419,7 +15434,7 @@ var FileResponsesImageStateStore = class {
|
|
|
14419
15434
|
} catch {
|
|
14420
15435
|
}
|
|
14421
15436
|
}
|
|
14422
|
-
if (
|
|
15437
|
+
if (existsSync14(temporaryPath)) {
|
|
14423
15438
|
try {
|
|
14424
15439
|
unlinkSync7(temporaryPath);
|
|
14425
15440
|
} catch {
|
|
@@ -14429,7 +15444,7 @@ var FileResponsesImageStateStore = class {
|
|
|
14429
15444
|
}
|
|
14430
15445
|
loadManifest() {
|
|
14431
15446
|
const path2 = this.manifestPath();
|
|
14432
|
-
if (!
|
|
15447
|
+
if (!existsSync14(path2)) return;
|
|
14433
15448
|
const info = lstatSync5(path2);
|
|
14434
15449
|
if (info.isSymbolicLink() || !info.isFile() || info.size > MAX_MANIFEST_BYTES3) {
|
|
14435
15450
|
throw new TypeError("responses image state manifest is invalid");
|
|
@@ -14523,8 +15538,8 @@ function validMount(value) {
|
|
|
14523
15538
|
return exactKeys3(row, ["id", "durableRoot", "createdAt"]) && typeof row.id === "string" && MOUNT_ID_PATTERN.test(row.id) && typeof row.durableRoot === "string" && isAbsolute2(row.durableRoot) && safeInteger2(row.createdAt);
|
|
14524
15539
|
}
|
|
14525
15540
|
function samePath5(left, right) {
|
|
14526
|
-
const normalizedLeft =
|
|
14527
|
-
const normalizedRight =
|
|
15541
|
+
const normalizedLeft = resolve9(left);
|
|
15542
|
+
const normalizedRight = resolve9(right);
|
|
14528
15543
|
return process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight;
|
|
14529
15544
|
}
|
|
14530
15545
|
var ImageStorageMountCatalog = class {
|
|
@@ -14552,7 +15567,7 @@ var ImageStorageMountCatalog = class {
|
|
|
14552
15567
|
this.#catalogResolver = this.createResolver(options.activeStorageRoot);
|
|
14553
15568
|
this.#reconcileCorruptManifests = options.reconcileCorruptManifests ?? false;
|
|
14554
15569
|
this.#replaceCatalog = options.replaceCatalog ?? ((target, contents) => this.atomicReplace(target, contents));
|
|
14555
|
-
if (
|
|
15570
|
+
if (existsSync15(this.catalogPath())) {
|
|
14556
15571
|
try {
|
|
14557
15572
|
this.loadCatalog();
|
|
14558
15573
|
} catch (error) {
|
|
@@ -14804,15 +15819,15 @@ var ImageStorageMountCatalog = class {
|
|
|
14804
15819
|
}
|
|
14805
15820
|
quarantineManifest(resolver, area, name, label) {
|
|
14806
15821
|
const root = resolver.verifiedRoot(area);
|
|
14807
|
-
const source =
|
|
14808
|
-
if (!
|
|
15822
|
+
const source = join14(root, name);
|
|
15823
|
+
if (!existsSync15(source)) throw new TypeError("corrupt image manifest is missing");
|
|
14809
15824
|
const info = lstatSync6(source);
|
|
14810
15825
|
if (info.isSymbolicLink() || !info.isFile()) {
|
|
14811
15826
|
throw new TypeError("refusing to quarantine an unverified image manifest");
|
|
14812
15827
|
}
|
|
14813
15828
|
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
14814
|
-
const target =
|
|
14815
|
-
if (
|
|
15829
|
+
const target = join14(root, `.corrupt-${label}-${this.#random(8).toString("hex")}.json`);
|
|
15830
|
+
if (existsSync15(target)) continue;
|
|
14816
15831
|
resolver.verifiedRoot(area);
|
|
14817
15832
|
renameSync7(source, target);
|
|
14818
15833
|
this.#corruptManifestsQuarantined += 1;
|
|
@@ -14857,10 +15872,10 @@ var ImageStorageMountCatalog = class {
|
|
|
14857
15872
|
}
|
|
14858
15873
|
atomicReplace(targetPath, contents) {
|
|
14859
15874
|
const root = this.#catalogResolver.verifiedRoot("mountManifest");
|
|
14860
|
-
if (!samePath5(dirname10(
|
|
15875
|
+
if (!samePath5(dirname10(resolve9(targetPath)), root) || basename6(targetPath) !== CATALOG_NAME) {
|
|
14861
15876
|
throw new TypeError("invalid image storage mount catalog target");
|
|
14862
15877
|
}
|
|
14863
|
-
const temporaryPath =
|
|
15878
|
+
const temporaryPath = join14(
|
|
14864
15879
|
root,
|
|
14865
15880
|
`.catalog.${process.pid}.${this.#random(8).toString("hex")}.tmp`
|
|
14866
15881
|
);
|
|
@@ -14879,7 +15894,7 @@ var ImageStorageMountCatalog = class {
|
|
|
14879
15894
|
} catch {
|
|
14880
15895
|
}
|
|
14881
15896
|
}
|
|
14882
|
-
if (
|
|
15897
|
+
if (existsSync15(temporaryPath)) {
|
|
14883
15898
|
try {
|
|
14884
15899
|
unlinkSync8(temporaryPath);
|
|
14885
15900
|
} catch {
|
|
@@ -15101,12 +16116,12 @@ function positiveInteger4(value, name) {
|
|
|
15101
16116
|
return value;
|
|
15102
16117
|
}
|
|
15103
16118
|
function samePath6(left, right) {
|
|
15104
|
-
const normalizedLeft =
|
|
15105
|
-
const normalizedRight =
|
|
16119
|
+
const normalizedLeft = resolve10(left);
|
|
16120
|
+
const normalizedRight = resolve10(right);
|
|
15106
16121
|
return process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight;
|
|
15107
16122
|
}
|
|
15108
16123
|
function isDirectChild(path2, root) {
|
|
15109
|
-
const target =
|
|
16124
|
+
const target = resolve10(path2);
|
|
15110
16125
|
const rel = relative2(root, target);
|
|
15111
16126
|
return !samePath6(target, root) && !isAbsolute3(rel) && !rel.startsWith("..") && samePath6(dirname11(target), root);
|
|
15112
16127
|
}
|
|
@@ -15228,8 +16243,8 @@ var ImageStartupReconciler = class {
|
|
|
15228
16243
|
let removed = 0;
|
|
15229
16244
|
let invalid = 0;
|
|
15230
16245
|
for (const name of readdirSync6(root).filter((value) => pattern.test(value)).slice(0, limit)) {
|
|
15231
|
-
const path2 =
|
|
15232
|
-
if (!isDirectChild(path2, root) ||
|
|
16246
|
+
const path2 = resolve10(root, name);
|
|
16247
|
+
if (!isDirectChild(path2, root) || basename7(path2) !== name) {
|
|
15233
16248
|
invalid += 1;
|
|
15234
16249
|
continue;
|
|
15235
16250
|
}
|
|
@@ -15251,7 +16266,7 @@ var ImageStartupReconciler = class {
|
|
|
15251
16266
|
let invalid = 0;
|
|
15252
16267
|
let active = 0;
|
|
15253
16268
|
for (const name of readdirSync6(root).slice(0, this.#maxTemporaryDirectoriesPerPass)) {
|
|
15254
|
-
const path2 =
|
|
16269
|
+
const path2 = resolve10(root, name);
|
|
15255
16270
|
if (!OWNED_TEMPORARY_DIRECTORY.test(name) || !isDirectChild(path2, root)) {
|
|
15256
16271
|
foreign += 1;
|
|
15257
16272
|
continue;
|
|
@@ -15266,8 +16281,8 @@ var ImageStartupReconciler = class {
|
|
|
15266
16281
|
active += 1;
|
|
15267
16282
|
continue;
|
|
15268
16283
|
}
|
|
15269
|
-
const markerPath =
|
|
15270
|
-
if (!isDirectChild(markerPath, path2) || !
|
|
16284
|
+
const markerPath = resolve10(path2, IMAGE_REQUEST_DIRECTORY_MARKER_NAME);
|
|
16285
|
+
if (!isDirectChild(markerPath, path2) || !existsSync16(markerPath)) {
|
|
15271
16286
|
foreign += 1;
|
|
15272
16287
|
continue;
|
|
15273
16288
|
}
|
|
@@ -15299,7 +16314,7 @@ var ImageStartupReconciler = class {
|
|
|
15299
16314
|
throw new TypeError("refusing to remove an unsupported temporary descendant");
|
|
15300
16315
|
}
|
|
15301
16316
|
for (const name of readdirSync6(path2)) {
|
|
15302
|
-
this.removeTreeWithoutFollowingSymlinks(
|
|
16317
|
+
this.removeTreeWithoutFollowingSymlinks(resolve10(path2, name), path2);
|
|
15303
16318
|
}
|
|
15304
16319
|
rmdirSync2(path2);
|
|
15305
16320
|
}
|
|
@@ -15768,8 +16783,8 @@ function createHostedImageContributionFactory(manager) {
|
|
|
15768
16783
|
function deferredRecord(generation, phase) {
|
|
15769
16784
|
let resolveDisposed;
|
|
15770
16785
|
let rejectDisposed;
|
|
15771
|
-
const disposed = new Promise((
|
|
15772
|
-
resolveDisposed =
|
|
16786
|
+
const disposed = new Promise((resolve12, reject) => {
|
|
16787
|
+
resolveDisposed = resolve12;
|
|
15773
16788
|
rejectDisposed = reject;
|
|
15774
16789
|
});
|
|
15775
16790
|
void disposed.catch(() => void 0);
|
|
@@ -16348,7 +17363,7 @@ function toLLMProvider(row) {
|
|
|
16348
17363
|
// src/ports/ConfigurableLogger.ts
|
|
16349
17364
|
import {
|
|
16350
17365
|
createWriteStream,
|
|
16351
|
-
existsSync as
|
|
17366
|
+
existsSync as existsSync17,
|
|
16352
17367
|
renameSync as renameSync8,
|
|
16353
17368
|
statSync as statSync6,
|
|
16354
17369
|
unlinkSync as unlinkSync10
|
|
@@ -16400,7 +17415,7 @@ var ConfigurableLogger = class {
|
|
|
16400
17415
|
this.fileStream = null;
|
|
16401
17416
|
this.rotateQueue = [];
|
|
16402
17417
|
if (!stream) return Promise.resolve();
|
|
16403
|
-
return new Promise((
|
|
17418
|
+
return new Promise((resolve12) => stream.end(() => resolve12()));
|
|
16404
17419
|
}
|
|
16405
17420
|
emit(level, message, error, meta) {
|
|
16406
17421
|
if (LEVEL_ORDER[level] > this.threshold) return;
|
|
@@ -16482,12 +17497,12 @@ var ConfigurableLogger = class {
|
|
|
16482
17497
|
/** `<file>.N` unlinked, `<file>.k` → `<file>.k+1`, `<file>` → `<file>.1`. */
|
|
16483
17498
|
shiftGenerations(path2) {
|
|
16484
17499
|
const oldest = `${path2}.${this.maxFiles}`;
|
|
16485
|
-
if (
|
|
17500
|
+
if (existsSync17(oldest)) unlinkSync10(oldest);
|
|
16486
17501
|
for (let i = this.maxFiles - 1; i >= 1; i--) {
|
|
16487
17502
|
const from = `${path2}.${i}`;
|
|
16488
|
-
if (
|
|
17503
|
+
if (existsSync17(from)) renameSync8(from, `${path2}.${i + 1}`);
|
|
16489
17504
|
}
|
|
16490
|
-
if (
|
|
17505
|
+
if (existsSync17(path2)) renameSync8(path2, `${path2}.1`);
|
|
16491
17506
|
}
|
|
16492
17507
|
/**
|
|
16493
17508
|
* Lazily open the append-only file stream; disable the sink on any error. The
|
|
@@ -16498,7 +17513,7 @@ var ConfigurableLogger = class {
|
|
|
16498
17513
|
if (this.fileDisabled || !this.filePath) return null;
|
|
16499
17514
|
if (this.fileStream) return this.fileStream;
|
|
16500
17515
|
try {
|
|
16501
|
-
this.fileBytes =
|
|
17516
|
+
this.fileBytes = existsSync17(this.filePath) ? statSync6(this.filePath).size : 0;
|
|
16502
17517
|
const stream = createWriteStream(this.filePath, { flags: "a" });
|
|
16503
17518
|
stream.on("error", () => {
|
|
16504
17519
|
this.fileDisabled = true;
|
|
@@ -16571,7 +17586,7 @@ function safeStringify(value) {
|
|
|
16571
17586
|
import { randomBytes as randomBytes10 } from "crypto";
|
|
16572
17587
|
import {
|
|
16573
17588
|
closeSync as closeSync7,
|
|
16574
|
-
existsSync as
|
|
17589
|
+
existsSync as existsSync18,
|
|
16575
17590
|
fsyncSync as fsyncSync6,
|
|
16576
17591
|
openSync as openSync7,
|
|
16577
17592
|
readFileSync as readFileSync14,
|
|
@@ -16579,12 +17594,12 @@ import {
|
|
|
16579
17594
|
unlinkSync as unlinkSync11,
|
|
16580
17595
|
writeFileSync as writeFileSync12
|
|
16581
17596
|
} from "fs";
|
|
16582
|
-
import { basename as
|
|
17597
|
+
import { basename as basename8, dirname as dirname12, join as join15 } from "path";
|
|
16583
17598
|
import { OUTBOUND_API_SERVER_CONFIG_KEY } from "@omnicross/core/outbound-api";
|
|
16584
17599
|
function atomicReplaceDocument(targetPath, contents) {
|
|
16585
|
-
const tempPath =
|
|
17600
|
+
const tempPath = join15(
|
|
16586
17601
|
dirname12(targetPath),
|
|
16587
|
-
`.${
|
|
17602
|
+
`.${basename8(targetPath)}.${process.pid}.${randomBytes10(8).toString("hex")}.tmp`
|
|
16588
17603
|
);
|
|
16589
17604
|
let fd;
|
|
16590
17605
|
try {
|
|
@@ -16601,7 +17616,7 @@ function atomicReplaceDocument(targetPath, contents) {
|
|
|
16601
17616
|
} catch {
|
|
16602
17617
|
}
|
|
16603
17618
|
}
|
|
16604
|
-
if (
|
|
17619
|
+
if (existsSync18(tempPath)) {
|
|
16605
17620
|
try {
|
|
16606
17621
|
unlinkSync11(tempPath);
|
|
16607
17622
|
} catch {
|
|
@@ -16644,13 +17659,13 @@ var JsonApiServerSettingsStore = class {
|
|
|
16644
17659
|
}
|
|
16645
17660
|
/** Capture the exact prior document for an admin transaction rollback. */
|
|
16646
17661
|
captureDocumentSnapshot() {
|
|
16647
|
-
if (!
|
|
17662
|
+
if (!existsSync18(this.configPath)) return { existed: false };
|
|
16648
17663
|
return { existed: true, bytes: readFileSync14(this.configPath) };
|
|
16649
17664
|
}
|
|
16650
17665
|
/** Restore exact prior bytes (including unrelated fields and encrypted secrets). */
|
|
16651
17666
|
restoreDocumentSnapshot(snapshot) {
|
|
16652
17667
|
if (!snapshot.existed) {
|
|
16653
|
-
if (
|
|
17668
|
+
if (existsSync18(this.configPath)) unlinkSync11(this.configPath);
|
|
16654
17669
|
return;
|
|
16655
17670
|
}
|
|
16656
17671
|
if (!snapshot.bytes) throw new TypeError("settings snapshot is missing prior bytes");
|
|
@@ -16687,13 +17702,13 @@ var JsonApiServerSettingsStore = class {
|
|
|
16687
17702
|
};
|
|
16688
17703
|
|
|
16689
17704
|
// src/ports/JsonlUsageEventStore.ts
|
|
16690
|
-
import { randomUUID as
|
|
17705
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
16691
17706
|
import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
|
|
16692
|
-
import { join as
|
|
17707
|
+
import { join as join19 } from "path";
|
|
16693
17708
|
|
|
16694
17709
|
// src/usage/usageFiles.ts
|
|
16695
|
-
import { readdir } from "fs/promises";
|
|
16696
|
-
import { dirname as dirname13, join as
|
|
17710
|
+
import { readdir as readdir2 } from "fs/promises";
|
|
17711
|
+
import { dirname as dirname13, join as join16 } from "path";
|
|
16697
17712
|
var USAGE_DIR_NAME = "usage";
|
|
16698
17713
|
var USAGE_SHARD_RE = /^usage-(\d{4})-(\d{2})-(\d{2})\.jsonl$/;
|
|
16699
17714
|
var USAGE_ROLLUP_RE = /^usage-(\d{4})-(\d{2})-(\d{2})\.rollup\.json$/;
|
|
@@ -16710,7 +17725,7 @@ function usageRollupName(dayKey) {
|
|
|
16710
17725
|
return `usage-${dayKey}.rollup.json`;
|
|
16711
17726
|
}
|
|
16712
17727
|
function usageDirFor(eventsPath) {
|
|
16713
|
-
return
|
|
17728
|
+
return join16(dirname13(eventsPath), USAGE_DIR_NAME);
|
|
16714
17729
|
}
|
|
16715
17730
|
function dayKeyStartTs(dayKey) {
|
|
16716
17731
|
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(dayKey);
|
|
@@ -16731,7 +17746,7 @@ function dayKeyEndTs(dayKey) {
|
|
|
16731
17746
|
async function listUsageDays(usageDir) {
|
|
16732
17747
|
let names;
|
|
16733
17748
|
try {
|
|
16734
|
-
names = await
|
|
17749
|
+
names = await readdir2(usageDir);
|
|
16735
17750
|
} catch {
|
|
16736
17751
|
return [];
|
|
16737
17752
|
}
|
|
@@ -16971,12 +17986,12 @@ function isUsageDayRollup(parsed) {
|
|
|
16971
17986
|
}
|
|
16972
17987
|
|
|
16973
17988
|
// src/usage/usageRollupStore.ts
|
|
16974
|
-
import { mkdir, readFile as
|
|
16975
|
-
import { join as
|
|
17989
|
+
import { mkdir, readFile as readFile3, rename as rename2, unlink as unlink2, writeFile as writeFile2 } from "fs/promises";
|
|
17990
|
+
import { join as join18 } from "path";
|
|
16976
17991
|
|
|
16977
17992
|
// src/usage/usageShardCache.ts
|
|
16978
|
-
import { open as
|
|
16979
|
-
import { join as
|
|
17993
|
+
import { open as open3 } from "fs/promises";
|
|
17994
|
+
import { join as join17 } from "path";
|
|
16980
17995
|
|
|
16981
17996
|
// src/usage/usageRow.ts
|
|
16982
17997
|
var NUMERIC_FIELDS = [
|
|
@@ -17036,10 +18051,10 @@ var NEWLINE2 = 10;
|
|
|
17036
18051
|
var READ_CHUNK_BYTES = 8 * 1024 * 1024;
|
|
17037
18052
|
var DEFAULT_MAX_RESIDENT_DAYS = 3;
|
|
17038
18053
|
async function streamShardRows(usageDir, dayKey, onRow) {
|
|
17039
|
-
const path2 =
|
|
18054
|
+
const path2 = join17(usageDir, usageShardName(dayKey));
|
|
17040
18055
|
let handle;
|
|
17041
18056
|
try {
|
|
17042
|
-
handle = await
|
|
18057
|
+
handle = await open3(path2, "r");
|
|
17043
18058
|
} catch {
|
|
17044
18059
|
return;
|
|
17045
18060
|
}
|
|
@@ -17069,7 +18084,7 @@ async function streamShardRows(usageDir, dayKey, onRow) {
|
|
|
17069
18084
|
async function shardSize(usageDir, dayKey) {
|
|
17070
18085
|
let handle;
|
|
17071
18086
|
try {
|
|
17072
|
-
handle = await
|
|
18087
|
+
handle = await open3(join17(usageDir, usageShardName(dayKey)), "r");
|
|
17073
18088
|
} catch {
|
|
17074
18089
|
return null;
|
|
17075
18090
|
}
|
|
@@ -17125,10 +18140,10 @@ var UsageShardCache = class {
|
|
|
17125
18140
|
} else {
|
|
17126
18141
|
this.touch(dayKey);
|
|
17127
18142
|
}
|
|
17128
|
-
const path2 =
|
|
18143
|
+
const path2 = join17(this.usageDir, usageShardName(dayKey));
|
|
17129
18144
|
let handle;
|
|
17130
18145
|
try {
|
|
17131
|
-
handle = await
|
|
18146
|
+
handle = await open3(path2, "r");
|
|
17132
18147
|
} catch {
|
|
17133
18148
|
return entry;
|
|
17134
18149
|
}
|
|
@@ -17247,7 +18262,7 @@ var UsageRollupStore = class {
|
|
|
17247
18262
|
}
|
|
17248
18263
|
async readSidecar(dayKey) {
|
|
17249
18264
|
try {
|
|
17250
|
-
const raw = await
|
|
18265
|
+
const raw = await readFile3(join18(this.usageDir, usageRollupName(dayKey)), "utf8");
|
|
17251
18266
|
const parsed = JSON.parse(raw);
|
|
17252
18267
|
return isUsageDayRollup(parsed) && parsed.date === dayKey ? parsed : null;
|
|
17253
18268
|
} catch {
|
|
@@ -17255,14 +18270,14 @@ var UsageRollupStore = class {
|
|
|
17255
18270
|
}
|
|
17256
18271
|
}
|
|
17257
18272
|
async writeSidecar(dayKey, rollup) {
|
|
17258
|
-
const target =
|
|
18273
|
+
const target = join18(this.usageDir, usageRollupName(dayKey));
|
|
17259
18274
|
const temp = `${target}.tmp`;
|
|
17260
18275
|
try {
|
|
17261
18276
|
await mkdir(this.usageDir, { recursive: true });
|
|
17262
|
-
await
|
|
17263
|
-
await
|
|
18277
|
+
await writeFile2(temp, JSON.stringify(rollup), "utf8");
|
|
18278
|
+
await rename2(temp, target);
|
|
17264
18279
|
} catch {
|
|
17265
|
-
await
|
|
18280
|
+
await unlink2(temp).catch(() => {
|
|
17266
18281
|
});
|
|
17267
18282
|
}
|
|
17268
18283
|
}
|
|
@@ -17337,13 +18352,13 @@ var JsonlUsageEventStore = class {
|
|
|
17337
18352
|
async insert(input) {
|
|
17338
18353
|
const row = {
|
|
17339
18354
|
...input,
|
|
17340
|
-
id:
|
|
18355
|
+
id: randomUUID5(),
|
|
17341
18356
|
ts: input.ts ?? Date.now()
|
|
17342
18357
|
};
|
|
17343
18358
|
const dayKey = usageDayKey(row.ts);
|
|
17344
18359
|
this.ensureDir();
|
|
17345
18360
|
const line = JSON.stringify(row) + "\n";
|
|
17346
|
-
appendFileSync(
|
|
18361
|
+
appendFileSync(join19(this.usageDir, usageShardName(dayKey)), line, "utf8");
|
|
17347
18362
|
if (dayKey !== this.lastAppendDay) {
|
|
17348
18363
|
this.rollups.invalidate(dayKey);
|
|
17349
18364
|
this.lastAppendDay = dayKey;
|
|
@@ -17718,7 +18733,7 @@ function bucketLabel(bucketStartTs, bucket) {
|
|
|
17718
18733
|
}
|
|
17719
18734
|
|
|
17720
18735
|
// src/ports/JsonOutboundKeyDb.ts
|
|
17721
|
-
import { existsSync as
|
|
18736
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
|
|
17722
18737
|
import {
|
|
17723
18738
|
validateOutboundPermissions as validateOutboundPermissions3
|
|
17724
18739
|
} from "@omnicross/core";
|
|
@@ -17727,18 +18742,18 @@ import {
|
|
|
17727
18742
|
import { randomBytes as randomBytes11 } from "crypto";
|
|
17728
18743
|
import {
|
|
17729
18744
|
closeSync as closeSync8,
|
|
17730
|
-
existsSync as
|
|
18745
|
+
existsSync as existsSync19,
|
|
17731
18746
|
fsyncSync as fsyncSync7,
|
|
17732
18747
|
openSync as openSync8,
|
|
17733
18748
|
renameSync as renameSync10,
|
|
17734
18749
|
unlinkSync as unlinkSync12,
|
|
17735
18750
|
writeFileSync as writeFileSync13
|
|
17736
18751
|
} from "fs";
|
|
17737
|
-
import { basename as
|
|
18752
|
+
import { basename as basename9, dirname as dirname14, join as join20 } from "path";
|
|
17738
18753
|
function atomicReplaceUtf8(targetPath, contents) {
|
|
17739
|
-
const tempPath =
|
|
18754
|
+
const tempPath = join20(
|
|
17740
18755
|
dirname14(targetPath),
|
|
17741
|
-
`.${
|
|
18756
|
+
`.${basename9(targetPath)}.${process.pid}.${randomBytes11(8).toString("hex")}.tmp`
|
|
17742
18757
|
);
|
|
17743
18758
|
let fd;
|
|
17744
18759
|
try {
|
|
@@ -17755,7 +18770,7 @@ function atomicReplaceUtf8(targetPath, contents) {
|
|
|
17755
18770
|
} catch {
|
|
17756
18771
|
}
|
|
17757
18772
|
}
|
|
17758
|
-
if (
|
|
18773
|
+
if (existsSync19(tempPath)) {
|
|
17759
18774
|
try {
|
|
17760
18775
|
unlinkSync12(tempPath);
|
|
17761
18776
|
} catch {
|
|
@@ -17866,6 +18881,19 @@ var JsonOutboundKeyDb = class {
|
|
|
17866
18881
|
return true;
|
|
17867
18882
|
});
|
|
17868
18883
|
}
|
|
18884
|
+
async outboundApiKeysSetUpstream(id, target) {
|
|
18885
|
+
return this.mutateRow(id, (row) => {
|
|
18886
|
+
if (row.revokedAt !== null) return false;
|
|
18887
|
+
if (target === null) {
|
|
18888
|
+
delete row.boundUpstream;
|
|
18889
|
+
delete row.boundUpstreamProviderId;
|
|
18890
|
+
} else {
|
|
18891
|
+
row.boundUpstream = target;
|
|
18892
|
+
delete row.boundUpstreamProviderId;
|
|
18893
|
+
}
|
|
18894
|
+
return true;
|
|
18895
|
+
});
|
|
18896
|
+
}
|
|
17869
18897
|
async outboundApiKeysSetPolicy(id, policy) {
|
|
17870
18898
|
return this.mutateRow(id, (row) => {
|
|
17871
18899
|
if (row.revokedAt !== null) return false;
|
|
@@ -17908,7 +18936,7 @@ var JsonOutboundKeyDb = class {
|
|
|
17908
18936
|
}
|
|
17909
18937
|
/** Read the key rows, tolerating a missing/corrupt file (→ empty list). */
|
|
17910
18938
|
readRows() {
|
|
17911
|
-
if (!
|
|
18939
|
+
if (!existsSync20(this.keysPath)) return [];
|
|
17912
18940
|
try {
|
|
17913
18941
|
const parsed = JSON.parse(readFileSync15(this.keysPath, "utf8"));
|
|
17914
18942
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -17927,8 +18955,8 @@ function applyPolicyField(row, field, value) {
|
|
|
17927
18955
|
}
|
|
17928
18956
|
|
|
17929
18957
|
// src/ports/JsonPricingStore.ts
|
|
17930
|
-
import { existsSync as
|
|
17931
|
-
import { randomUUID as
|
|
18958
|
+
import { existsSync as existsSync21, readFileSync as readFileSync16, renameSync as renameSync11, rmSync as rmSync3, writeFileSync as writeFileSync14 } from "fs";
|
|
18959
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
17932
18960
|
var JsonPricingStore = class {
|
|
17933
18961
|
constructor(pricingPath) {
|
|
17934
18962
|
this.pricingPath = pricingPath;
|
|
@@ -17942,7 +18970,7 @@ var JsonPricingStore = class {
|
|
|
17942
18970
|
* otherwise unusable pricing table after a crash or manual file edit.
|
|
17943
18971
|
*/
|
|
17944
18972
|
hasUsableSnapshot() {
|
|
17945
|
-
if (!
|
|
18973
|
+
if (!existsSync21(this.pricingPath)) return false;
|
|
17946
18974
|
try {
|
|
17947
18975
|
const parsed = JSON.parse(readFileSync16(this.pricingPath, "utf8"));
|
|
17948
18976
|
return Array.isArray(parsed) && parsed.some(isUsablePricingRow);
|
|
@@ -18057,7 +19085,7 @@ var JsonPricingStore = class {
|
|
|
18057
19085
|
}
|
|
18058
19086
|
/** Read the pricing rows, tolerating a missing/corrupt file (→ empty list). */
|
|
18059
19087
|
readRows() {
|
|
18060
|
-
if (!
|
|
19088
|
+
if (!existsSync21(this.pricingPath)) return [];
|
|
18061
19089
|
try {
|
|
18062
19090
|
const parsed = JSON.parse(readFileSync16(this.pricingPath, "utf8"));
|
|
18063
19091
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -18066,7 +19094,7 @@ var JsonPricingStore = class {
|
|
|
18066
19094
|
}
|
|
18067
19095
|
}
|
|
18068
19096
|
writeRows(rows) {
|
|
18069
|
-
const temporaryPath = `${this.pricingPath}.${process.pid}.${
|
|
19097
|
+
const temporaryPath = `${this.pricingPath}.${process.pid}.${randomUUID6()}.tmp`;
|
|
18070
19098
|
try {
|
|
18071
19099
|
writeFileSync14(temporaryPath, JSON.stringify(rows, null, 2) + "\n", {
|
|
18072
19100
|
encoding: "utf8",
|
|
@@ -18089,7 +19117,7 @@ function isUsablePricingRow(value) {
|
|
|
18089
19117
|
}
|
|
18090
19118
|
|
|
18091
19119
|
// src/pricing/PricingRefreshScheduler.ts
|
|
18092
|
-
import { existsSync as
|
|
19120
|
+
import { existsSync as existsSync22, readFileSync as readFileSync17, renameSync as renameSync12, writeFileSync as writeFileSync15 } from "fs";
|
|
18093
19121
|
var EMPTY_STATE2 = {
|
|
18094
19122
|
lastAttemptAt: null,
|
|
18095
19123
|
lastSuccessAt: null,
|
|
@@ -18127,7 +19155,7 @@ var PricingRefreshScheduler = class {
|
|
|
18127
19155
|
this.timer = null;
|
|
18128
19156
|
}
|
|
18129
19157
|
getState() {
|
|
18130
|
-
if (!
|
|
19158
|
+
if (!existsSync22(this.statePath)) return { ...EMPTY_STATE2, sources: [] };
|
|
18131
19159
|
try {
|
|
18132
19160
|
const value = JSON.parse(readFileSync17(this.statePath, "utf8"));
|
|
18133
19161
|
return {
|
|
@@ -18192,7 +19220,7 @@ function finiteOrNull(value) {
|
|
|
18192
19220
|
}
|
|
18193
19221
|
|
|
18194
19222
|
// src/ports/JsonVoucherDb.ts
|
|
18195
|
-
import { existsSync as
|
|
19223
|
+
import { existsSync as existsSync23, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "fs";
|
|
18196
19224
|
var JsonVoucherDb = class {
|
|
18197
19225
|
constructor(vouchersPath) {
|
|
18198
19226
|
this.vouchersPath = vouchersPath;
|
|
@@ -18270,7 +19298,7 @@ var JsonVoucherDb = class {
|
|
|
18270
19298
|
}
|
|
18271
19299
|
/** Read the voucher rows, tolerating a missing/corrupt file (→ empty list). */
|
|
18272
19300
|
readRows() {
|
|
18273
|
-
if (!
|
|
19301
|
+
if (!existsSync23(this.vouchersPath)) return [];
|
|
18274
19302
|
try {
|
|
18275
19303
|
const parsed = JSON.parse(readFileSync18(this.vouchersPath, "utf8"));
|
|
18276
19304
|
return Array.isArray(parsed) ? parsed : [];
|
|
@@ -18284,7 +19312,7 @@ var JsonVoucherDb = class {
|
|
|
18284
19312
|
};
|
|
18285
19313
|
|
|
18286
19314
|
// src/ports/JsonSubscriptionCredentialStore.ts
|
|
18287
|
-
import { existsSync as
|
|
19315
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync6, readFileSync as readFileSync20, renameSync as renameSync13 } from "fs";
|
|
18288
19316
|
import { dirname as dirname15 } from "path";
|
|
18289
19317
|
import { getAntigravityProjectResolver as getAntigravityProjectResolver2 } from "@omnicross/core/auth/GeminiCodeAssistProjectResolver";
|
|
18290
19318
|
import { getSharedAccountHealth as getSharedAccountHealth2 } from "@omnicross/core/pipeline/SubscriptionAccountHealth";
|
|
@@ -18342,11 +19370,11 @@ function findDuplicateCredentialIds(accounts) {
|
|
|
18342
19370
|
}
|
|
18343
19371
|
|
|
18344
19372
|
// src/ports/external-cli-credentials.ts
|
|
18345
|
-
import { existsSync as
|
|
18346
|
-
import { homedir as
|
|
18347
|
-
import { join as
|
|
18348
|
-
function externalStorePath(provider, home =
|
|
18349
|
-
return provider === "claude" ?
|
|
19373
|
+
import { existsSync as existsSync24, readFileSync as readFileSync19 } from "fs";
|
|
19374
|
+
import { homedir as homedir5 } from "os";
|
|
19375
|
+
import { join as join21 } from "path";
|
|
19376
|
+
function externalStorePath(provider, home = homedir5()) {
|
|
19377
|
+
return provider === "claude" ? join21(home, ".claude", ".credentials.json") : join21(home, ".codex", "auth.json");
|
|
18350
19378
|
}
|
|
18351
19379
|
function decodeJwtExpiryMs(token) {
|
|
18352
19380
|
try {
|
|
@@ -18393,9 +19421,9 @@ function parseCodexTokensEnvelope(raw) {
|
|
|
18393
19421
|
}
|
|
18394
19422
|
return parsed;
|
|
18395
19423
|
}
|
|
18396
|
-
function readExternalCliCredentials(provider, home =
|
|
19424
|
+
function readExternalCliCredentials(provider, home = homedir5()) {
|
|
18397
19425
|
const path2 = externalStorePath(provider, home);
|
|
18398
|
-
if (!
|
|
19426
|
+
if (!existsSync24(path2)) return null;
|
|
18399
19427
|
let raw;
|
|
18400
19428
|
try {
|
|
18401
19429
|
const parsed = JSON.parse(readFileSync19(path2, "utf8"));
|
|
@@ -19202,7 +20230,7 @@ var JsonSubscriptionCredentialStore = class {
|
|
|
19202
20230
|
* its parse try.
|
|
19203
20231
|
*/
|
|
19204
20232
|
readConfig() {
|
|
19205
|
-
if (!
|
|
20233
|
+
if (!existsSync25(this.tokensPath)) return { updatedAt: "" };
|
|
19206
20234
|
let parsed;
|
|
19207
20235
|
try {
|
|
19208
20236
|
const raw = JSON.parse(readFileSync20(this.tokensPath, "utf8"));
|
|
@@ -19779,21 +20807,21 @@ var AccountHealthSweeper = class {
|
|
|
19779
20807
|
};
|
|
19780
20808
|
|
|
19781
20809
|
// src/audit/AuditPruneSweeper.ts
|
|
19782
|
-
import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as
|
|
19783
|
-
import { join as
|
|
20810
|
+
import { createReadStream as createReadStream3, createWriteStream as createWriteStream2, existsSync as existsSync27, readdirSync as readdirSync8, rmSync as rmSync4, unlinkSync as unlinkSync13 } from "fs";
|
|
20811
|
+
import { join as join23 } from "path";
|
|
19784
20812
|
import { pipeline } from "stream/promises";
|
|
19785
20813
|
import { createGzip } from "zlib";
|
|
19786
20814
|
|
|
19787
20815
|
// src/audit/auditStats.ts
|
|
19788
20816
|
import {
|
|
19789
20817
|
createReadStream as createReadStream2,
|
|
19790
|
-
existsSync as
|
|
20818
|
+
existsSync as existsSync26,
|
|
19791
20819
|
readFileSync as readFileSync21,
|
|
19792
20820
|
readdirSync as readdirSync7,
|
|
19793
20821
|
statSync as statSync7,
|
|
19794
20822
|
writeFileSync as writeFileSync17
|
|
19795
20823
|
} from "fs";
|
|
19796
|
-
import { basename as
|
|
20824
|
+
import { basename as basename10, dirname as dirname16, join as join22 } from "path";
|
|
19797
20825
|
var SIDECAR_VERSION = 1;
|
|
19798
20826
|
var META_PREFIX_BYTES = 64 * 1024;
|
|
19799
20827
|
var READ_CHUNK_BYTES2 = 4 * 1024 * 1024;
|
|
@@ -19801,7 +20829,7 @@ function auditStatsFileName(auditFile) {
|
|
|
19801
20829
|
return auditFile.replace(/\.jsonl$/, ".stats.json");
|
|
19802
20830
|
}
|
|
19803
20831
|
function readPersisted(path2) {
|
|
19804
|
-
if (!
|
|
20832
|
+
if (!existsSync26(path2)) return null;
|
|
19805
20833
|
try {
|
|
19806
20834
|
const value = JSON.parse(readFileSync21(path2, "utf8"));
|
|
19807
20835
|
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)) {
|
|
@@ -19813,7 +20841,7 @@ function readPersisted(path2) {
|
|
|
19813
20841
|
}
|
|
19814
20842
|
}
|
|
19815
20843
|
function updateAuditStatsAfterAppend(auditPath, auditBytesBefore, auditBytesAfter, record) {
|
|
19816
|
-
const statsPath =
|
|
20844
|
+
const statsPath = join22(dirname16(auditPath), auditStatsFileName(basename10(auditPath)));
|
|
19817
20845
|
const previous = auditBytesBefore === 0 ? {
|
|
19818
20846
|
version: SIDECAR_VERSION,
|
|
19819
20847
|
auditBytes: 0,
|
|
@@ -19944,20 +20972,20 @@ function mergePersistedStats(previous, appended) {
|
|
|
19944
20972
|
};
|
|
19945
20973
|
}
|
|
19946
20974
|
async function readAuditStats(auditDir, query2 = {}) {
|
|
19947
|
-
if (!
|
|
20975
|
+
if (!existsSync26(auditDir)) return { requestCount: 0, errorCount: 0, complete: true };
|
|
19948
20976
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
19949
20977
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
19950
20978
|
let sources;
|
|
19951
20979
|
try {
|
|
19952
20980
|
sources = readdirSync7(auditDir).filter((name) => fileOverlaps(name, from, to)).sort().map(
|
|
19953
20981
|
(name) => AUDIT_DAY_DIR_RE.test(name) ? {
|
|
19954
|
-
auditPath:
|
|
19955
|
-
statsPath:
|
|
20982
|
+
auditPath: join22(auditDir, name, AUDIT_META_FILE),
|
|
20983
|
+
statsPath: join22(auditDir, name, auditStatsFileName(AUDIT_META_FILE))
|
|
19956
20984
|
} : {
|
|
19957
|
-
auditPath:
|
|
19958
|
-
statsPath:
|
|
20985
|
+
auditPath: join22(auditDir, name),
|
|
20986
|
+
statsPath: join22(auditDir, auditStatsFileName(name))
|
|
19959
20987
|
}
|
|
19960
|
-
).filter((source) =>
|
|
20988
|
+
).filter((source) => existsSync26(source.auditPath));
|
|
19961
20989
|
} catch {
|
|
19962
20990
|
return { requestCount: 0, errorCount: 0, complete: false };
|
|
19963
20991
|
}
|
|
@@ -20055,7 +21083,7 @@ var AuditPruneSweeper = class {
|
|
|
20055
21083
|
if (!this.config.enabled || this.sweeping) return 0;
|
|
20056
21084
|
this.sweeping = true;
|
|
20057
21085
|
try {
|
|
20058
|
-
if (!
|
|
21086
|
+
if (!existsSync27(this.auditDir)) return 0;
|
|
20059
21087
|
const cutoff = this.todayMidnight() - (this.config.retentionDays - 1) * DAY_MS4;
|
|
20060
21088
|
let removed = 0;
|
|
20061
21089
|
for (const name of readdirSync8(this.auditDir)) {
|
|
@@ -20063,11 +21091,11 @@ var AuditPruneSweeper = class {
|
|
|
20063
21091
|
if (dateMs === null || dateMs >= cutoff) continue;
|
|
20064
21092
|
try {
|
|
20065
21093
|
if (isAuditDayDir(name)) {
|
|
20066
|
-
rmSync4(
|
|
21094
|
+
rmSync4(join23(this.auditDir, name), { recursive: true, force: true });
|
|
20067
21095
|
} else {
|
|
20068
|
-
unlinkSync13(
|
|
20069
|
-
const statsPath =
|
|
20070
|
-
if (
|
|
21096
|
+
unlinkSync13(join23(this.auditDir, name));
|
|
21097
|
+
const statsPath = join23(this.auditDir, auditStatsFileName(name));
|
|
21098
|
+
if (existsSync27(statsPath)) unlinkSync13(statsPath);
|
|
20071
21099
|
}
|
|
20072
21100
|
removed += 1;
|
|
20073
21101
|
} catch (error) {
|
|
@@ -20097,14 +21125,14 @@ var AuditPruneSweeper = class {
|
|
|
20097
21125
|
if (!this.config.enabled || this.archiving) return 0;
|
|
20098
21126
|
this.archiving = true;
|
|
20099
21127
|
try {
|
|
20100
|
-
if (!
|
|
21128
|
+
if (!existsSync27(this.auditDir)) return 0;
|
|
20101
21129
|
const today = this.todayMidnight();
|
|
20102
21130
|
let compressed = 0;
|
|
20103
21131
|
for (const name of readdirSync8(this.auditDir)) {
|
|
20104
21132
|
if (compressed >= ARCHIVE_BATCH) break;
|
|
20105
21133
|
const dateMs = auditFileDateMs(name);
|
|
20106
21134
|
if (dateMs === null || dateMs >= today || !isAuditDayDir(name)) continue;
|
|
20107
|
-
const dayPath =
|
|
21135
|
+
const dayPath = join23(this.auditDir, name);
|
|
20108
21136
|
try {
|
|
20109
21137
|
const compaction = compactAuditDay(dayPath);
|
|
20110
21138
|
if (compaction.shards > 0) {
|
|
@@ -20122,7 +21150,7 @@ var AuditPruneSweeper = class {
|
|
|
20122
21150
|
});
|
|
20123
21151
|
}
|
|
20124
21152
|
compressed += await this.archiveDay(
|
|
20125
|
-
|
|
21153
|
+
join23(dayPath, AUDIT_BODIES_DIR),
|
|
20126
21154
|
ARCHIVE_BATCH - compressed
|
|
20127
21155
|
);
|
|
20128
21156
|
}
|
|
@@ -20148,10 +21176,10 @@ var AuditPruneSweeper = class {
|
|
|
20148
21176
|
let compressed = 0;
|
|
20149
21177
|
for (const shard of shards) {
|
|
20150
21178
|
if (compressed >= budget) break;
|
|
20151
|
-
const source =
|
|
21179
|
+
const source = join23(bodiesPath, shard);
|
|
20152
21180
|
const target = `${source}.gz`;
|
|
20153
21181
|
try {
|
|
20154
|
-
if (
|
|
21182
|
+
if (existsSync27(target)) {
|
|
20155
21183
|
unlinkSync13(source);
|
|
20156
21184
|
continue;
|
|
20157
21185
|
}
|
|
@@ -20160,7 +21188,7 @@ var AuditPruneSweeper = class {
|
|
|
20160
21188
|
compressed += 1;
|
|
20161
21189
|
} catch (error) {
|
|
20162
21190
|
try {
|
|
20163
|
-
if (
|
|
21191
|
+
if (existsSync27(target)) unlinkSync13(target);
|
|
20164
21192
|
} catch {
|
|
20165
21193
|
}
|
|
20166
21194
|
this.logger.warn("[AuditPruneSweeper] failed to archive audit body shard", {
|
|
@@ -20175,8 +21203,8 @@ var AuditPruneSweeper = class {
|
|
|
20175
21203
|
|
|
20176
21204
|
// src/usage/usageMigrate.ts
|
|
20177
21205
|
import { createReadStream as createReadStream4 } from "fs";
|
|
20178
|
-
import { mkdir as mkdir2, open as
|
|
20179
|
-
import { join as
|
|
21206
|
+
import { mkdir as mkdir2, open as open4, readdir as readdir3, rename as rename3, rm, stat as stat2, unlink as unlink3, writeFile as writeFile3 } from "fs/promises";
|
|
21207
|
+
import { join as join24 } from "path";
|
|
20180
21208
|
import { createInterface } from "readline";
|
|
20181
21209
|
var FLUSH_BYTES = 4 * 1024 * 1024;
|
|
20182
21210
|
async function writeLine(writer2, line) {
|
|
@@ -20200,14 +21228,14 @@ var IDLE = {
|
|
|
20200
21228
|
};
|
|
20201
21229
|
async function migrateLegacyUsageEvents(opts) {
|
|
20202
21230
|
const { eventsPath, usageDir, logger } = opts;
|
|
20203
|
-
const legacy = await
|
|
21231
|
+
const legacy = await stat2(eventsPath).catch(() => null);
|
|
20204
21232
|
if (!legacy || !legacy.isFile()) return IDLE;
|
|
20205
21233
|
if (legacy.size === 0) {
|
|
20206
|
-
await
|
|
21234
|
+
await unlink3(eventsPath).catch(() => {
|
|
20207
21235
|
});
|
|
20208
21236
|
return { ...IDLE, migrated: true };
|
|
20209
21237
|
}
|
|
20210
|
-
const scratch =
|
|
21238
|
+
const scratch = join24(usageDir, USAGE_MIGRATING_DIR);
|
|
20211
21239
|
logger?.info("[usage] migrating legacy usage-events.jsonl into day shards", {
|
|
20212
21240
|
bytes: legacy.size
|
|
20213
21241
|
});
|
|
@@ -20236,7 +21264,7 @@ async function migrateLegacyUsageEvents(opts) {
|
|
|
20236
21264
|
let writer2 = writers.get(dayKey);
|
|
20237
21265
|
if (!writer2) {
|
|
20238
21266
|
writer2 = {
|
|
20239
|
-
handle: await
|
|
21267
|
+
handle: await open4(join24(scratch, usageShardName(dayKey)), "a"),
|
|
20240
21268
|
buffer: [],
|
|
20241
21269
|
bytes: 0
|
|
20242
21270
|
};
|
|
@@ -20266,17 +21294,17 @@ async function migrateLegacyUsageEvents(opts) {
|
|
|
20266
21294
|
const today = usageDayKey(opts.now ?? Date.now());
|
|
20267
21295
|
for (const [dayKey, acc] of rollups) {
|
|
20268
21296
|
if (dayKey === today) continue;
|
|
20269
|
-
const size = await
|
|
21297
|
+
const size = await stat2(join24(scratch, usageShardName(dayKey))).then((s) => s.size).catch(() => null);
|
|
20270
21298
|
if (size === null) continue;
|
|
20271
|
-
await
|
|
20272
|
-
|
|
21299
|
+
await writeFile3(
|
|
21300
|
+
join24(scratch, usageRollupName(dayKey)),
|
|
20273
21301
|
JSON.stringify(acc.finish(dayKey, size)),
|
|
20274
21302
|
"utf8"
|
|
20275
21303
|
);
|
|
20276
21304
|
}
|
|
20277
21305
|
await mkdir2(usageDir, { recursive: true });
|
|
20278
|
-
const staged = await
|
|
20279
|
-
const existing = new Set(await
|
|
21306
|
+
const staged = await readdir3(scratch);
|
|
21307
|
+
const existing = new Set(await readdir3(usageDir).catch(() => []));
|
|
20280
21308
|
const collisions = staged.filter((name) => existing.has(name));
|
|
20281
21309
|
if (collisions.length > 0) {
|
|
20282
21310
|
const reason = `refusing to overwrite existing shards in ${usageDir} (${collisions.slice(0, 3).join(", ")}${collisions.length > 3 ? ", \u2026" : ""}); a previous migration may have partially committed \u2014 move or remove them and restart`;
|
|
@@ -20286,11 +21314,11 @@ async function migrateLegacyUsageEvents(opts) {
|
|
|
20286
21314
|
return { ...IDLE, linesRead, rowsWritten, skipped, reason };
|
|
20287
21315
|
}
|
|
20288
21316
|
for (const name of staged) {
|
|
20289
|
-
await
|
|
21317
|
+
await rename3(join24(scratch, name), join24(usageDir, name));
|
|
20290
21318
|
}
|
|
20291
21319
|
await rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
20292
21320
|
});
|
|
20293
|
-
await
|
|
21321
|
+
await unlink3(eventsPath);
|
|
20294
21322
|
const days = rollups.size;
|
|
20295
21323
|
logger?.info("[usage] migration complete; legacy usage-events.jsonl removed", {
|
|
20296
21324
|
linesRead,
|
|
@@ -20311,8 +21339,8 @@ async function closeAll(writers) {
|
|
|
20311
21339
|
}
|
|
20312
21340
|
|
|
20313
21341
|
// src/usage/UsagePruneSweeper.ts
|
|
20314
|
-
import { unlink as
|
|
20315
|
-
import { join as
|
|
21342
|
+
import { unlink as unlink4 } from "fs/promises";
|
|
21343
|
+
import { join as join25 } from "path";
|
|
20316
21344
|
var DAY_MS5 = 24 * 60 * 6e4;
|
|
20317
21345
|
var SWEEP_INTERVAL_MS3 = 60 * 6e4;
|
|
20318
21346
|
var DEFAULT_USAGE_RETENTION_DAYS = 90;
|
|
@@ -20393,7 +21421,7 @@ var UsagePruneSweeper = class {
|
|
|
20393
21421
|
continue;
|
|
20394
21422
|
}
|
|
20395
21423
|
try {
|
|
20396
|
-
await
|
|
21424
|
+
await unlink4(join25(this.usageDir, usageShardName(entry.dayKey)));
|
|
20397
21425
|
removed += 1;
|
|
20398
21426
|
} catch (error) {
|
|
20399
21427
|
this.logger.warn("[usage] retention: could not remove expired shard", {
|
|
@@ -20428,8 +21456,8 @@ var UsagePruneSweeper = class {
|
|
|
20428
21456
|
};
|
|
20429
21457
|
|
|
20430
21458
|
// src/audit/auditReader.ts
|
|
20431
|
-
import { existsSync as
|
|
20432
|
-
import { join as
|
|
21459
|
+
import { existsSync as existsSync28, readdirSync as readdirSync9 } from "fs";
|
|
21460
|
+
import { join as join26 } from "path";
|
|
20433
21461
|
var DEFAULT_LIMIT = 200;
|
|
20434
21462
|
var MAX_LIMIT = 2e3;
|
|
20435
21463
|
var OVERSCAN = 256;
|
|
@@ -20445,10 +21473,10 @@ function daySources(auditDir) {
|
|
|
20445
21473
|
const dateMs = auditFileDateMs(name);
|
|
20446
21474
|
if (dateMs === null) continue;
|
|
20447
21475
|
if (AUDIT_DAY_DIR_RE.test(name)) {
|
|
20448
|
-
const path2 =
|
|
20449
|
-
if (
|
|
21476
|
+
const path2 = join26(auditDir, name, AUDIT_META_FILE);
|
|
21477
|
+
if (existsSync28(path2)) sources.push({ path: path2, dateMs });
|
|
20450
21478
|
} else if (AUDIT_FILE_RE.test(name)) {
|
|
20451
|
-
sources.push({ path:
|
|
21479
|
+
sources.push({ path: join26(auditDir, name), dateMs });
|
|
20452
21480
|
}
|
|
20453
21481
|
}
|
|
20454
21482
|
return sources.sort((a, b) => b.dateMs - a.dateMs);
|
|
@@ -20464,7 +21492,7 @@ function toMetaRecord(record) {
|
|
|
20464
21492
|
return { ...meta, hasBody: true };
|
|
20465
21493
|
}
|
|
20466
21494
|
function readAuditRecords(auditDir, query2 = {}) {
|
|
20467
|
-
if (!
|
|
21495
|
+
if (!existsSync28(auditDir)) return [];
|
|
20468
21496
|
const from = typeof query2.from === "number" ? query2.from : -Infinity;
|
|
20469
21497
|
const to = typeof query2.to === "number" ? query2.to : Infinity;
|
|
20470
21498
|
const limit = Math.min(MAX_LIMIT, Math.max(1, Math.trunc(query2.limit ?? DEFAULT_LIMIT)));
|
|
@@ -20492,8 +21520,8 @@ function readAuditRecords(auditDir, query2 = {}) {
|
|
|
20492
21520
|
}
|
|
20493
21521
|
|
|
20494
21522
|
// src/audit/AuditWriter.ts
|
|
20495
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
20496
|
-
import { join as
|
|
21523
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync29, mkdirSync as mkdirSync7, statSync as statSync8 } from "fs";
|
|
21524
|
+
import { join as join27 } from "path";
|
|
20497
21525
|
var AuditWriter = class {
|
|
20498
21526
|
constructor(auditDir, logger, defer = (fn) => setTimeout(fn, 0)) {
|
|
20499
21527
|
this.auditDir = auditDir;
|
|
@@ -20533,7 +21561,7 @@ var AuditWriter = class {
|
|
|
20533
21561
|
*/
|
|
20534
21562
|
appendNow(record) {
|
|
20535
21563
|
const dayDir = auditDayDirName(record.ts);
|
|
20536
|
-
const dayPath = this.ensureDir(
|
|
21564
|
+
const dayPath = this.ensureDir(join27(this.auditDir, dayDir));
|
|
20537
21565
|
this.appendMeta(dayPath, record);
|
|
20538
21566
|
this.appendBody(dayPath, dayDir, record);
|
|
20539
21567
|
}
|
|
@@ -20548,9 +21576,9 @@ var AuditWriter = class {
|
|
|
20548
21576
|
/** Write the body-free metadata line + refresh the exact-count sidecar. */
|
|
20549
21577
|
appendMeta(dayPath, record) {
|
|
20550
21578
|
const { requestBody: _req, responseBody: _res, ...meta } = record;
|
|
20551
|
-
const file =
|
|
21579
|
+
const file = join27(dayPath, AUDIT_META_FILE);
|
|
20552
21580
|
const line = JSON.stringify(meta) + "\n";
|
|
20553
|
-
const bytesBefore =
|
|
21581
|
+
const bytesBefore = existsSync29(file) ? statSync8(file).size : 0;
|
|
20554
21582
|
appendFileSync2(file, line, "utf8");
|
|
20555
21583
|
try {
|
|
20556
21584
|
updateAuditStatsAfterAppend(
|
|
@@ -20582,8 +21610,8 @@ var AuditWriter = class {
|
|
|
20582
21610
|
try {
|
|
20583
21611
|
const line = encodeBodyEntry(record, sessionKey, dayDir, this.bases);
|
|
20584
21612
|
if (line === null) return;
|
|
20585
|
-
const bodiesPath = this.ensureDir(
|
|
20586
|
-
appendFileSync2(
|
|
21613
|
+
const bodiesPath = this.ensureDir(join27(dayPath, AUDIT_BODIES_DIR));
|
|
21614
|
+
appendFileSync2(join27(bodiesPath, auditBodyFileName(sessionKey)), line + "\n", "utf8");
|
|
20587
21615
|
} catch (error) {
|
|
20588
21616
|
this.bases.forget(sessionKey);
|
|
20589
21617
|
this.logger.warn("[AuditWriter] failed to append audit body shard", {
|
|
@@ -20597,7 +21625,7 @@ var AuditWriter = class {
|
|
|
20597
21625
|
// src/billing/BillingPublisher.ts
|
|
20598
21626
|
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync8 } from "fs";
|
|
20599
21627
|
import { createHmac as createHmac5 } from "crypto";
|
|
20600
|
-
import { join as
|
|
21628
|
+
import { join as join28 } from "path";
|
|
20601
21629
|
import { fetchUpstream as fetchUpstream14 } from "@omnicross/core/pipeline/upstreamFetch";
|
|
20602
21630
|
|
|
20603
21631
|
// src/billing/billingFiles.ts
|
|
@@ -20668,7 +21696,7 @@ var BillingPublisher = class {
|
|
|
20668
21696
|
*/
|
|
20669
21697
|
appendNow(event) {
|
|
20670
21698
|
this.ensureDir();
|
|
20671
|
-
const file =
|
|
21699
|
+
const file = join28(this.billingDir, billingFileName(event.ts));
|
|
20672
21700
|
appendFileSync3(file, JSON.stringify(event) + "\n", "utf8");
|
|
20673
21701
|
}
|
|
20674
21702
|
/**
|
|
@@ -20718,7 +21746,7 @@ var BillingPublisher = class {
|
|
|
20718
21746
|
markDelivered(event) {
|
|
20719
21747
|
try {
|
|
20720
21748
|
this.ensureDir();
|
|
20721
|
-
const file =
|
|
21749
|
+
const file = join28(this.billingDir, deliveredFileName(event.ts));
|
|
20722
21750
|
appendFileSync3(file, JSON.stringify({ id: event.id, deliveredAt: this.now() }) + "\n", "utf8");
|
|
20723
21751
|
} catch (error) {
|
|
20724
21752
|
this.logger.warn("[BillingPublisher] failed to append delivery marker", {
|
|
@@ -20734,11 +21762,11 @@ var BillingPublisher = class {
|
|
|
20734
21762
|
};
|
|
20735
21763
|
|
|
20736
21764
|
// src/billing/billingReader.ts
|
|
20737
|
-
import { existsSync as
|
|
20738
|
-
import { join as
|
|
21765
|
+
import { existsSync as existsSync30, readdirSync as readdirSync10, readFileSync as readFileSync22 } from "fs";
|
|
21766
|
+
import { join as join29 } from "path";
|
|
20739
21767
|
function readBillingLedger(billingDir) {
|
|
20740
21768
|
const view = { events: [], deliveredIds: /* @__PURE__ */ new Set() };
|
|
20741
|
-
if (!
|
|
21769
|
+
if (!existsSync30(billingDir)) return view;
|
|
20742
21770
|
let files;
|
|
20743
21771
|
try {
|
|
20744
21772
|
files = readdirSync10(billingDir);
|
|
@@ -20772,7 +21800,7 @@ function readBillingStatus(billingDir) {
|
|
|
20772
21800
|
function parseLines(dir, file) {
|
|
20773
21801
|
let raw;
|
|
20774
21802
|
try {
|
|
20775
|
-
raw = readFileSync22(
|
|
21803
|
+
raw = readFileSync22(join29(dir, file), "utf8");
|
|
20776
21804
|
} catch {
|
|
20777
21805
|
return [];
|
|
20778
21806
|
}
|
|
@@ -21579,6 +22607,7 @@ function buildDaemon(config, paths) {
|
|
|
21579
22607
|
});
|
|
21580
22608
|
const auditDir = defaultAuditDir(paths.configPath);
|
|
21581
22609
|
const billingDir = defaultBillingDir(paths.configPath);
|
|
22610
|
+
const codexSessionManager = new CodexSessionManager();
|
|
21582
22611
|
const adminServer = new AdminServer({
|
|
21583
22612
|
configPath: paths.configPath,
|
|
21584
22613
|
llmConfig,
|
|
@@ -21676,6 +22705,10 @@ function buildDaemon(config, paths) {
|
|
|
21676
22705
|
cliTerminalOpener: paths.cliTerminalOpener,
|
|
21677
22706
|
cliPathProbe: paths.cliPathProbe,
|
|
21678
22707
|
cliCommandRunner: paths.cliCommandRunner,
|
|
22708
|
+
// One helper invocation shared by the integration install and KEY-SCOPED
|
|
22709
|
+
// launches (the latter append `--key-id` per spawn) — same entrypoint, same
|
|
22710
|
+
// config/master-key resolution, so the two paths can never drift.
|
|
22711
|
+
codexAuthHelper: currentProcessCodexAuthHelper(paths.configPath, paths.masterKeyFilePath),
|
|
21679
22712
|
integrationManagerFactory: () => {
|
|
21680
22713
|
const live = outboundApiServer.getStatus();
|
|
21681
22714
|
const port = live.port || decryptedConfig.server?.port || DEFAULT_OUTBOUND_PORT;
|
|
@@ -21720,7 +22753,8 @@ function buildDaemon(config, paths) {
|
|
|
21720
22753
|
auditCompactor: () => compactAllClosedAuditDays(auditDir),
|
|
21721
22754
|
// billing-event-stream: the AUTHED `GET /admin/api/billing-status` returns the
|
|
21722
22755
|
// secret-free total/delivered/pending counts of the durable ledger.
|
|
21723
|
-
billingStatusReader: () => readBillingStatus(billingDir)
|
|
22756
|
+
billingStatusReader: () => readBillingStatus(billingDir),
|
|
22757
|
+
codexSessionManager
|
|
21724
22758
|
});
|
|
21725
22759
|
const webhookDispatcher = new WebhookDispatcher({
|
|
21726
22760
|
logger,
|
|
@@ -21786,6 +22820,7 @@ function buildDaemon(config, paths) {
|
|
|
21786
22820
|
pricingEngine,
|
|
21787
22821
|
pricingRefreshScheduler,
|
|
21788
22822
|
usageRecorder,
|
|
22823
|
+
codexSessionManager,
|
|
21789
22824
|
adminServer,
|
|
21790
22825
|
tokenRefreshScheduler,
|
|
21791
22826
|
accountHealthSweeper,
|
|
@@ -21801,7 +22836,7 @@ function buildDaemon(config, paths) {
|
|
|
21801
22836
|
}
|
|
21802
22837
|
function isTokensStoreReadable(tokensPath) {
|
|
21803
22838
|
try {
|
|
21804
|
-
if (!
|
|
22839
|
+
if (!existsSync31(tokensPath)) return true;
|
|
21805
22840
|
accessSync(tokensPath, fsConstants.R_OK);
|
|
21806
22841
|
return true;
|
|
21807
22842
|
} catch {
|
|
@@ -22340,7 +23375,7 @@ async function runImportCcr(argv) {
|
|
|
22340
23375
|
}
|
|
22341
23376
|
|
|
22342
23377
|
// src/commands/integrations.ts
|
|
22343
|
-
import { resolve as
|
|
23378
|
+
import { resolve as resolve11 } from "path";
|
|
22344
23379
|
import { parseArgs as parseArgs4 } from "util";
|
|
22345
23380
|
async function runIntegrations(argv) {
|
|
22346
23381
|
const { values, positionals } = parseArgs4({
|
|
@@ -22349,7 +23384,8 @@ async function runIntegrations(argv) {
|
|
|
22349
23384
|
config: { type: "string", short: "c" },
|
|
22350
23385
|
"gateway-base-url": { type: "string" },
|
|
22351
23386
|
target: { type: "string" },
|
|
22352
|
-
"master-key-file": { type: "string" }
|
|
23387
|
+
"master-key-file": { type: "string" },
|
|
23388
|
+
"key-id": { type: "string" }
|
|
22353
23389
|
},
|
|
22354
23390
|
allowPositionals: true
|
|
22355
23391
|
});
|
|
@@ -22362,7 +23398,7 @@ async function runIntegrations(argv) {
|
|
|
22362
23398
|
const savedUrl = saved.clients.codex?.gatewayBaseUrl ?? saved.clients.claude?.gatewayBaseUrl;
|
|
22363
23399
|
const gatewayBaseUrl = values["gateway-base-url"] ?? savedUrl ?? "http://127.0.0.1:8765";
|
|
22364
23400
|
const manager = new IntegrationManager({
|
|
22365
|
-
configPath:
|
|
23401
|
+
configPath: resolve11(values.config),
|
|
22366
23402
|
gatewayBaseUrl,
|
|
22367
23403
|
keyDb: new JsonOutboundKeyDb(defaultKeysPath(values.config), secretBox3),
|
|
22368
23404
|
stateStore: store,
|
|
@@ -22370,7 +23406,9 @@ async function runIntegrations(argv) {
|
|
|
22370
23406
|
});
|
|
22371
23407
|
if (action === "token") {
|
|
22372
23408
|
if (client !== "codex") throw new Error("integrations token: expected client 'codex'");
|
|
22373
|
-
|
|
23409
|
+
const keyId = values["key-id"];
|
|
23410
|
+
const token = keyId ? await manager.getKeyToken(keyId) : await manager.getIntegrationToken(client);
|
|
23411
|
+
process.stdout.write(`${token}
|
|
22374
23412
|
`);
|
|
22375
23413
|
return;
|
|
22376
23414
|
}
|
|
@@ -22456,9 +23494,9 @@ async function keysRevoke(db, id) {
|
|
|
22456
23494
|
|
|
22457
23495
|
// src/commands/launch.ts
|
|
22458
23496
|
import { spawn as spawn2 } from "child_process";
|
|
22459
|
-
import { randomUUID as
|
|
22460
|
-
import { existsSync as
|
|
22461
|
-
import { delimiter as delimiter2, join as
|
|
23497
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
23498
|
+
import { existsSync as existsSync32 } from "fs";
|
|
23499
|
+
import { delimiter as delimiter2, join as join30 } from "path";
|
|
22462
23500
|
import { parseArgs as parseArgs6 } from "util";
|
|
22463
23501
|
import {
|
|
22464
23502
|
buildChatCliLaunchConfig as buildChatCliLaunchConfig2,
|
|
@@ -22504,8 +23542,8 @@ function buildCliSpawnPlan(opts) {
|
|
|
22504
23542
|
function resolveInPathDefault(candidate) {
|
|
22505
23543
|
const segments = (process.env["PATH"] ?? "").split(delimiter2).filter(Boolean);
|
|
22506
23544
|
for (const seg of segments) {
|
|
22507
|
-
const full =
|
|
22508
|
-
if (
|
|
23545
|
+
const full = join30(seg, candidate);
|
|
23546
|
+
if (existsSync32(full)) return full;
|
|
22509
23547
|
}
|
|
22510
23548
|
return null;
|
|
22511
23549
|
}
|
|
@@ -22585,7 +23623,7 @@ async function runLaunch(argv, deps) {
|
|
|
22585
23623
|
}
|
|
22586
23624
|
async function buildLaunchConfig(cli, daemon, opts) {
|
|
22587
23625
|
if (cli === "claude" || cli === "codex") {
|
|
22588
|
-
const internalId =
|
|
23626
|
+
const internalId = randomUUID7();
|
|
22589
23627
|
const outcome = await daemon.routeLeaseManager.createFromRequest({
|
|
22590
23628
|
schemaVersion: ROUTE_LEASE_REQUEST_SCHEMA2,
|
|
22591
23629
|
consumer: "omnicross-terminal",
|
|
@@ -22643,7 +23681,7 @@ async function shutdownLaunchDaemon(daemon) {
|
|
|
22643
23681
|
daemon.pricingRefreshScheduler.dispose();
|
|
22644
23682
|
}
|
|
22645
23683
|
function spawnCliInherit(plan) {
|
|
22646
|
-
return new Promise((
|
|
23684
|
+
return new Promise((resolve12, reject) => {
|
|
22647
23685
|
const child = spawn2(plan.command, plan.args, {
|
|
22648
23686
|
stdio: "inherit",
|
|
22649
23687
|
env: plan.env,
|
|
@@ -22677,7 +23715,7 @@ function spawnCliInherit(plan) {
|
|
|
22677
23715
|
});
|
|
22678
23716
|
child.on("exit", (code, signal) => {
|
|
22679
23717
|
detach();
|
|
22680
|
-
|
|
23718
|
+
resolve12(code ?? (signal ? 1 : 0));
|
|
22681
23719
|
});
|
|
22682
23720
|
});
|
|
22683
23721
|
}
|
|
@@ -23045,30 +24083,30 @@ function buildOpenBrowserCommand(platform, url) {
|
|
|
23045
24083
|
return { command: "xdg-open", args: [url] };
|
|
23046
24084
|
}
|
|
23047
24085
|
function openBrowser(url) {
|
|
23048
|
-
return new Promise((
|
|
24086
|
+
return new Promise((resolve12) => {
|
|
23049
24087
|
try {
|
|
23050
24088
|
const { command, args } = buildOpenBrowserCommand(process.platform, url);
|
|
23051
24089
|
const child = spawn3(command, args, { stdio: "ignore", detached: true });
|
|
23052
|
-
child.on("error", () =>
|
|
24090
|
+
child.on("error", () => resolve12(false));
|
|
23053
24091
|
child.unref();
|
|
23054
|
-
|
|
24092
|
+
resolve12(true);
|
|
23055
24093
|
} catch {
|
|
23056
|
-
|
|
24094
|
+
resolve12(false);
|
|
23057
24095
|
}
|
|
23058
24096
|
});
|
|
23059
24097
|
}
|
|
23060
24098
|
function promptPaste(prompt) {
|
|
23061
24099
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
23062
|
-
return new Promise((
|
|
24100
|
+
return new Promise((resolve12) => {
|
|
23063
24101
|
rl.question(prompt, (answer) => {
|
|
23064
24102
|
rl.close();
|
|
23065
|
-
|
|
24103
|
+
resolve12(answer);
|
|
23066
24104
|
});
|
|
23067
24105
|
});
|
|
23068
24106
|
}
|
|
23069
24107
|
|
|
23070
24108
|
// src/commands/providers.ts
|
|
23071
|
-
import { randomUUID as
|
|
24109
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
23072
24110
|
import { parseArgs as parseArgs8 } from "util";
|
|
23073
24111
|
async function runProviders(argv) {
|
|
23074
24112
|
const { values, positionals } = parseArgs8({
|
|
@@ -23190,7 +24228,7 @@ function providersAddKey(configPath, providerId, opts) {
|
|
|
23190
24228
|
const cfg = loadConfig(configPath);
|
|
23191
24229
|
const row = cfg.providers.find((p) => p.id === providerId);
|
|
23192
24230
|
if (!row) throw new Error(`providers add-key: unknown provider '${providerId}'`);
|
|
23193
|
-
const entry = { id:
|
|
24231
|
+
const entry = { id: randomUUID8(), apiKey: opts.key };
|
|
23194
24232
|
if (opts.label) entry.label = opts.label;
|
|
23195
24233
|
if (opts.weight !== void 0) {
|
|
23196
24234
|
const w = Number(opts.weight);
|
|
@@ -23218,7 +24256,7 @@ function providersRmKey(configPath, providerId, keyId) {
|
|
|
23218
24256
|
}
|
|
23219
24257
|
|
|
23220
24258
|
// src/commands/secrets.ts
|
|
23221
|
-
import { existsSync as
|
|
24259
|
+
import { existsSync as existsSync33, readFileSync as readFileSync24 } from "fs";
|
|
23222
24260
|
import { parseArgs as parseArgs9 } from "util";
|
|
23223
24261
|
async function runSecrets(argv) {
|
|
23224
24262
|
const { values, positionals } = parseArgs9({
|
|
@@ -23291,12 +24329,12 @@ function secretsStatus(args) {
|
|
|
23291
24329
|
reportField("admin.token", cfg.admin.token);
|
|
23292
24330
|
}
|
|
23293
24331
|
const tokensPath = defaultTokensPath(args.config);
|
|
23294
|
-
if (
|
|
24332
|
+
if (existsSync33(tokensPath)) {
|
|
23295
24333
|
console.info(`Secret status for ${tokensPath}:`);
|
|
23296
24334
|
reportTokenFields(tokensPath);
|
|
23297
24335
|
}
|
|
23298
24336
|
const integrationsPath = defaultIntegrationsPath(args.config);
|
|
23299
|
-
if (
|
|
24337
|
+
if (existsSync33(integrationsPath)) {
|
|
23300
24338
|
const state = readRawJson(integrationsPath);
|
|
23301
24339
|
const key = state.gatewayKey;
|
|
23302
24340
|
if (key && typeof key === "object" && !Array.isArray(key)) {
|
|
@@ -23350,8 +24388,8 @@ async function secretsRotate(args) {
|
|
|
23350
24388
|
const integrationsPath = defaultIntegrationsPath(args.config);
|
|
23351
24389
|
try {
|
|
23352
24390
|
cfg = loadConfig(args.config);
|
|
23353
|
-
if (
|
|
23354
|
-
if (
|
|
24391
|
+
if (existsSync33(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, oldBox);
|
|
24392
|
+
if (existsSync33(integrationsPath)) {
|
|
23355
24393
|
integrationsPlain = new IntegrationStateStore(integrationsPath, oldBox).load();
|
|
23356
24394
|
}
|
|
23357
24395
|
} finally {
|
|
@@ -23386,7 +24424,7 @@ function secretsDecrypt(args) {
|
|
|
23386
24424
|
let tokensPlain = null;
|
|
23387
24425
|
try {
|
|
23388
24426
|
cfg = loadConfig(args.config);
|
|
23389
|
-
if (
|
|
24427
|
+
if (existsSync33(tokensPath)) tokensPlain = decryptTokensFile(tokensPath, box);
|
|
23390
24428
|
} finally {
|
|
23391
24429
|
setSecretBox(null);
|
|
23392
24430
|
}
|
|
@@ -23417,13 +24455,13 @@ function readRawJson(path2) {
|
|
|
23417
24455
|
}
|
|
23418
24456
|
function encryptTokensFileInPlace(configPath, box) {
|
|
23419
24457
|
const tokensPath = defaultTokensPath(configPath);
|
|
23420
|
-
if (!
|
|
24458
|
+
if (!existsSync33(tokensPath)) return;
|
|
23421
24459
|
const plain = decryptTokensFile(tokensPath, box);
|
|
23422
24460
|
writeTokensEncrypted(tokensPath, plain, box);
|
|
23423
24461
|
}
|
|
23424
24462
|
function rewriteIntegrationState(configPath, readBox, writeBox) {
|
|
23425
24463
|
const path2 = defaultIntegrationsPath(configPath);
|
|
23426
|
-
if (!
|
|
24464
|
+
if (!existsSync33(path2)) return;
|
|
23427
24465
|
const state = new IntegrationStateStore(path2, readBox).load();
|
|
23428
24466
|
new IntegrationStateStore(path2, writeBox).save(state);
|
|
23429
24467
|
}
|
|
@@ -23463,7 +24501,7 @@ function walkTokens(raw, fn) {
|
|
|
23463
24501
|
return next;
|
|
23464
24502
|
}
|
|
23465
24503
|
function tokensSuffix(configPath) {
|
|
23466
|
-
return
|
|
24504
|
+
return existsSync33(defaultTokensPath(configPath)) ? " (+ tokens.json)" : "";
|
|
23467
24505
|
}
|
|
23468
24506
|
|
|
23469
24507
|
// src/commands/start.ts
|
|
@@ -23599,7 +24637,7 @@ async function runStart(argv) {
|
|
|
23599
24637
|
// src/commands/ui.ts
|
|
23600
24638
|
async function runUi(argv, deps) {
|
|
23601
24639
|
const start = deps?.start ?? runStart;
|
|
23602
|
-
const
|
|
24640
|
+
const open5 = deps?.openBrowser ?? openBrowser;
|
|
23603
24641
|
const noOpen = argv.includes("--no-open");
|
|
23604
24642
|
const startArgv = argv.filter((a) => a !== "--no-open");
|
|
23605
24643
|
if (!resolveUiDist()) {
|
|
@@ -23615,7 +24653,7 @@ async function runUi(argv, deps) {
|
|
|
23615
24653
|
const uiUrl = `${dashboardUrl}/ui/`;
|
|
23616
24654
|
console.info(`Control Panel: ${uiUrl}`);
|
|
23617
24655
|
if (noOpen) return;
|
|
23618
|
-
const launched = await
|
|
24656
|
+
const launched = await open5(uiUrl).catch(() => false);
|
|
23619
24657
|
if (!launched) {
|
|
23620
24658
|
console.info("(Could not open a browser automatically \u2014 open the URL above manually.)");
|
|
23621
24659
|
}
|