@agent-proxy/gateway 0.14.1 → 0.15.0
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/README.md +19 -0
- package/dist/index.js +653 -139
- package/dist/ui/index.html +8 -8
- package/package.json +11 -10
package/dist/index.js
CHANGED
|
@@ -12,8 +12,8 @@ var __export = (target, all) => {
|
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
// src/cli.ts
|
|
15
|
-
import
|
|
16
|
-
import
|
|
15
|
+
import fs24 from "fs";
|
|
16
|
+
import path17 from "path";
|
|
17
17
|
import { createHash as createHash3 } from "crypto";
|
|
18
18
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
19
19
|
import bytes2 from "bytes";
|
|
@@ -7525,8 +7525,8 @@ function codexCatalogFile(paths) {
|
|
|
7525
7525
|
}
|
|
7526
7526
|
async function rebuildStaticCatalog(paths, config, source, { refreshAdapters = false } = {}) {
|
|
7527
7527
|
const adapters = await source.collect(refreshAdapters, paths.runtimeHome);
|
|
7528
|
-
|
|
7529
|
-
|
|
7528
|
+
for (const failure of adapters.failures)
|
|
7529
|
+
console.warn(`WARNING: ${failure}\uFF1B\u5DF2\u8DF3\u8FC7\u8BE5\u63D2\u4EF6\uFF0C\u4E0D\u5F71\u54CD\u6A21\u578B\u9009\u62E9\u4FDD\u5B58\u3002`);
|
|
7530
7530
|
return writeStaticCatalog(paths, config, adapters.entries, (model) => source.ownsModel(model));
|
|
7531
7531
|
}
|
|
7532
7532
|
function writeStaticCatalog(paths, config, adapterModels, ownsModel = () => false) {
|
|
@@ -7588,8 +7588,8 @@ async function updateCodexModelCatalog(paths, config, catalogSource, {
|
|
|
7588
7588
|
snapshotWritten = true;
|
|
7589
7589
|
} else if (refreshAdapters) {
|
|
7590
7590
|
const adapters = await catalogSource.collect(true, paths.runtimeHome);
|
|
7591
|
-
|
|
7592
|
-
|
|
7591
|
+
for (const failure of adapters.failures)
|
|
7592
|
+
console.warn(`WARNING: ${failure}\uFF1B\u5DF2\u8DF3\u8FC7\u8BE5\u63D2\u4EF6\uFF0C\u4E0D\u5F71\u54CD\u6A21\u578B\u9009\u62E9\u4FDD\u5B58\u3002`);
|
|
7593
7593
|
count = adapters.entries.length;
|
|
7594
7594
|
}
|
|
7595
7595
|
if (tomlChanged) {
|
|
@@ -8512,6 +8512,7 @@ function modelCatalogResponse(catalog, clientVersion, owner, prefix = "cliproxy/
|
|
|
8512
8512
|
data: catalog.models.map((model) => ({
|
|
8513
8513
|
id: model.slug,
|
|
8514
8514
|
object: "model",
|
|
8515
|
+
...typeof model.display_name === "string" && model.display_name ? { display_name: model.display_name, name: model.display_name } : {},
|
|
8515
8516
|
owned_by: registry.match(model.slug)?.plugin.id ?? (owner === "mixed" ? model.slug.startsWith(prefix) ? "cliproxy" : "openai" : owner)
|
|
8516
8517
|
}))
|
|
8517
8518
|
});
|
|
@@ -8616,17 +8617,17 @@ function excludedModelResponse(model) {
|
|
|
8616
8617
|
}
|
|
8617
8618
|
}, { status: 404, headers: { "x-codex-cliproxy-gateway": "model-excluded" } });
|
|
8618
8619
|
}
|
|
8619
|
-
async function
|
|
8620
|
+
async function availablePluginCatalog(registry, processLog, mountPath, operation) {
|
|
8620
8621
|
const catalogs = await Promise.all(registry.plugins.map(async (plugin) => {
|
|
8621
8622
|
try {
|
|
8622
|
-
return await plugin.refreshCatalog();
|
|
8623
|
+
return await (operation === "startup" ? plugin.refreshCatalog() : plugin.reloadCatalog());
|
|
8623
8624
|
} catch {
|
|
8624
8625
|
logGatewayError(processLog, {
|
|
8625
8626
|
requestTime: localTime(),
|
|
8626
|
-
method: "START",
|
|
8627
|
+
method: operation === "startup" ? "START" : "RELOAD",
|
|
8627
8628
|
url: `${mountPath}/models`,
|
|
8628
8629
|
status: 503,
|
|
8629
|
-
message: `\u63D2\u4EF6 ${plugin.id} \u542F\u52A8\u76EE\u5F55\u5237\u65B0\u4E0D\u53EF\u7528\uFF1B\u5C06\u5C1D\u8BD5\u7F13\u5B58\u76EE\u5F55\uFF0C\u5176\u4F59\u63D2\u4EF6\u548C default \u7EE7\u7EED\u5C31\u7EEA\u3002`
|
|
8630
|
+
message: `\u63D2\u4EF6 ${plugin.id} ${operation === "startup" ? "\u542F\u52A8\u76EE\u5F55\u5237\u65B0" : "\u76EE\u5F55\u70ED\u52A0\u8F7D"}\u4E0D\u53EF\u7528\uFF1B\u5C06\u5C1D\u8BD5\u7F13\u5B58\u76EE\u5F55\uFF0C\u5176\u4F59\u63D2\u4EF6\u548C default \u7EE7\u7EED\u5C31\u7EEA\u3002`
|
|
8630
8631
|
});
|
|
8631
8632
|
try {
|
|
8632
8633
|
return await plugin.getCatalog();
|
|
@@ -8649,7 +8650,7 @@ function createPluginGatewayHandler(config, registry, apiKey = readApiKey(), rea
|
|
|
8649
8650
|
let modelExclusion = compileModelFilter(config.excludedModels);
|
|
8650
8651
|
let closed = false;
|
|
8651
8652
|
let ready;
|
|
8652
|
-
const modelsReady = () => ready ??= registry.ready().then(() => config.upstreamOnly === true ?
|
|
8653
|
+
const modelsReady = () => ready ??= registry.ready().then(() => config.upstreamOnly === true ? availablePluginCatalog(registry, processLog, mountPath, "startup") : registry.getCatalog()).then((catalog) => {
|
|
8653
8654
|
if (!closed && runtimePaths && config.upstreamOnly === true) {
|
|
8654
8655
|
writeStaticCatalog(runtimePaths, config, catalog.models, ownsLocalModel);
|
|
8655
8656
|
}
|
|
@@ -8658,7 +8659,7 @@ function createPluginGatewayHandler(config, registry, apiKey = readApiKey(), rea
|
|
|
8658
8659
|
await modelsReady().catch(() => {});
|
|
8659
8660
|
if (closed)
|
|
8660
8661
|
return;
|
|
8661
|
-
const catalog = await registry
|
|
8662
|
+
const catalog = await availablePluginCatalog(registry, processLog, mountPath, "reload");
|
|
8662
8663
|
if (closed)
|
|
8663
8664
|
return;
|
|
8664
8665
|
const nextFilter = compileModelFilter(next.excludedModels);
|
|
@@ -9356,14 +9357,452 @@ function updateCodexModelCatalog2(paths, config, dependencies = {}, options = {}
|
|
|
9356
9357
|
managedFiles: managedRuntimeCatalogFiles(paths)
|
|
9357
9358
|
});
|
|
9358
9359
|
}
|
|
9359
|
-
|
|
9360
|
+
|
|
9361
|
+
// src/models-push.ts
|
|
9362
|
+
import fs18 from "fs";
|
|
9363
|
+
function pushTimestamp(date = new Date) {
|
|
9364
|
+
return date.toISOString().replace(/[-:]/g, "").slice(0, 15).replace("T", "");
|
|
9365
|
+
}
|
|
9366
|
+
function writeIfChanged(file, contents) {
|
|
9367
|
+
if (fs18.existsSync(file) && fs18.readFileSync(file, "utf8") === contents)
|
|
9368
|
+
return;
|
|
9369
|
+
const backup = `${file}.bak-agent-proxy-${pushTimestamp()}`;
|
|
9370
|
+
fs18.copyFileSync(file, backup);
|
|
9371
|
+
atomicWrite(file, contents);
|
|
9372
|
+
return backup;
|
|
9373
|
+
}
|
|
9374
|
+
function writeAllOrRollback(entries) {
|
|
9375
|
+
const written = [];
|
|
9376
|
+
try {
|
|
9377
|
+
for (const { file, contents } of entries) {
|
|
9378
|
+
const backup = writeIfChanged(file, contents);
|
|
9379
|
+
if (backup !== undefined)
|
|
9380
|
+
written.push({ file, backup });
|
|
9381
|
+
}
|
|
9382
|
+
} catch (error) {
|
|
9383
|
+
const unrestored = [];
|
|
9384
|
+
for (const { file, backup } of written) {
|
|
9385
|
+
try {
|
|
9386
|
+
fs18.copyFileSync(backup, file);
|
|
9387
|
+
} catch {
|
|
9388
|
+
unrestored.push(file);
|
|
9389
|
+
}
|
|
9390
|
+
}
|
|
9391
|
+
if (unrestored.length === 0)
|
|
9392
|
+
throw error;
|
|
9393
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9394
|
+
throw new Error(`${message}; failed to restore: ${unrestored.join(", ")} (their backups remain beside the files)`);
|
|
9395
|
+
}
|
|
9396
|
+
return written.map((item) => item.backup);
|
|
9397
|
+
}
|
|
9398
|
+
function modelReasoningEfforts(entry) {
|
|
9399
|
+
const raw = entry.supported_reasoning_levels;
|
|
9400
|
+
if (!Array.isArray(raw))
|
|
9401
|
+
return [];
|
|
9402
|
+
const efforts = [];
|
|
9403
|
+
for (const item of raw) {
|
|
9404
|
+
const effort = typeof item === "string" ? item : item && typeof item === "object" && typeof item.effort === "string" ? item.effort : undefined;
|
|
9405
|
+
if (typeof effort === "string" && effort && !efforts.includes(effort))
|
|
9406
|
+
efforts.push(effort);
|
|
9407
|
+
}
|
|
9408
|
+
return efforts;
|
|
9409
|
+
}
|
|
9410
|
+
function modelContextWindow(entry) {
|
|
9411
|
+
for (const key of ["context_window", "max_context_window"]) {
|
|
9412
|
+
const value = entry[key];
|
|
9413
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0)
|
|
9414
|
+
return value;
|
|
9415
|
+
}
|
|
9416
|
+
return;
|
|
9417
|
+
}
|
|
9418
|
+
function modelInputModalities(entry) {
|
|
9419
|
+
const raw = entry.input_modalities;
|
|
9420
|
+
if (!Array.isArray(raw))
|
|
9421
|
+
return ["text"];
|
|
9422
|
+
const modalities = raw.filter((item) => item === "text" || item === "image");
|
|
9423
|
+
return modalities.length > 0 ? [...new Set(modalities)] : ["text"];
|
|
9424
|
+
}
|
|
9425
|
+
async function pushModelsToTargets(options) {
|
|
9426
|
+
for (const failure of options.failures) {
|
|
9427
|
+
console.log(`WARNING: ${failure}; its models are not included in this push.`);
|
|
9428
|
+
}
|
|
9429
|
+
const scoped = filterExcludedModels({ models: [...options.entries] }, options.config.excludedModels, isInstalledPluginModel).models;
|
|
9430
|
+
if (scoped.length === 0) {
|
|
9431
|
+
throw new Error("model push aborted; no local adapter models to push" + " (enable an adapter via config --zcode on / --codebuddy on / \u2026 and refresh its catalog first)");
|
|
9432
|
+
}
|
|
9433
|
+
const known = options.targets.map((target) => target.id);
|
|
9434
|
+
const requested = options.targetOption === undefined ? known : [...new Set(options.targetOption.split(",").map((id) => id.trim().toLowerCase()).filter(Boolean))];
|
|
9435
|
+
if (requested.length === 0)
|
|
9436
|
+
throw new Error("--push requires at least one target name (or no value to push every target)");
|
|
9437
|
+
const selected = requested.map((id) => {
|
|
9438
|
+
const target = options.targets.find((candidate) => candidate.id === id);
|
|
9439
|
+
if (!target)
|
|
9440
|
+
throw new Error(`unknown --push target "${id}"; available targets: ${known.join(", ")}`);
|
|
9441
|
+
return target;
|
|
9442
|
+
});
|
|
9443
|
+
const failed = [];
|
|
9444
|
+
for (const target of selected) {
|
|
9445
|
+
try {
|
|
9446
|
+
target.assertInstalled();
|
|
9447
|
+
const outcome = target.push(scoped);
|
|
9448
|
+
console.log(outcome.summary);
|
|
9449
|
+
for (const note of outcome.notes)
|
|
9450
|
+
console.log(` ${note}`);
|
|
9451
|
+
} catch (error) {
|
|
9452
|
+
failed.push(target.id);
|
|
9453
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9454
|
+
console.log(`WARNING: ${target.id} push failed: ${message}`);
|
|
9455
|
+
}
|
|
9456
|
+
}
|
|
9457
|
+
if (failed.length > 0)
|
|
9458
|
+
throw new Error(`model push failed for: ${failed.join(", ")} (see warnings above)`);
|
|
9459
|
+
}
|
|
9460
|
+
|
|
9461
|
+
// src/zcode-client-sync.ts
|
|
9462
|
+
import fs19 from "fs";
|
|
9463
|
+
import os3 from "os";
|
|
9464
|
+
import path13 from "path";
|
|
9465
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
9466
|
+
import { isZcodeModel } from "@agent-proxy/zcode/catalog";
|
|
9467
|
+
var ZCODE_PROVIDER_DISPLAY_NAME = "AgentProxy";
|
|
9468
|
+
function isManagedProviderName(name) {
|
|
9469
|
+
return typeof name === "string" && name.toLowerCase().replace(/-/g, "") === "agentproxy";
|
|
9470
|
+
}
|
|
9471
|
+
var ZCODE_API_KEY_PLACEHOLDER = "EMPTY";
|
|
9472
|
+
var ZCODE_PROVIDER_GROUP = "standard-personal";
|
|
9473
|
+
function resolveZcodeClientPaths(env = process.env) {
|
|
9474
|
+
const home = env.ZCODE_HOME || path13.join(env.HOME || os3.homedir(), ".zcode");
|
|
9475
|
+
const v2 = path13.join(home, "v2");
|
|
9476
|
+
return { home, v2, configFile: path13.join(v2, "config.json"), providerConfigFile: path13.join(v2, "provider_config.json") };
|
|
9477
|
+
}
|
|
9478
|
+
function synthesizeZcodeModelDefinition(entry) {
|
|
9479
|
+
const definition = {};
|
|
9480
|
+
definition.name = typeof entry.display_name === "string" && entry.display_name ? entry.display_name : entry.slug;
|
|
9481
|
+
const efforts = modelReasoningEfforts(entry);
|
|
9482
|
+
if (efforts.length > 0) {
|
|
9483
|
+
const declared = typeof entry.default_reasoning_level === "string" ? entry.default_reasoning_level : undefined;
|
|
9484
|
+
definition.reasoning = {
|
|
9485
|
+
enabled: true,
|
|
9486
|
+
variants: [...efforts],
|
|
9487
|
+
defaultVariant: declared && efforts.includes(declared) ? declared : efforts[efforts.length - 1]
|
|
9488
|
+
};
|
|
9489
|
+
}
|
|
9490
|
+
const context = modelContextWindow(entry);
|
|
9491
|
+
if (context !== undefined)
|
|
9492
|
+
definition.limit = { context };
|
|
9493
|
+
definition.modalities = { input: modelInputModalities(entry), output: ["text"] };
|
|
9494
|
+
definition.zcode = { modalitiesConfigured: true, modified: true };
|
|
9495
|
+
return definition;
|
|
9496
|
+
}
|
|
9497
|
+
function isRecord3(value) {
|
|
9498
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9499
|
+
}
|
|
9500
|
+
function readJsonFile(file) {
|
|
9501
|
+
const parsed = JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
9502
|
+
if (!isRecord3(parsed))
|
|
9503
|
+
throw new Error(`${file} does not contain a JSON object`);
|
|
9504
|
+
return parsed;
|
|
9505
|
+
}
|
|
9506
|
+
function pushZcodeModels(models, options) {
|
|
9507
|
+
const paths = resolveZcodeClientPaths(options.env);
|
|
9508
|
+
const pushModels = models.filter((model) => !isZcodeModel(model.slug));
|
|
9509
|
+
if (pushModels.length === 0) {
|
|
9510
|
+
throw new Error("zcode push aborted; no models remain after excluding ZCode plugin models");
|
|
9511
|
+
}
|
|
9512
|
+
const providerDoc = readJsonFile(paths.providerConfigFile);
|
|
9513
|
+
const configDoc = readJsonFile(paths.configFile);
|
|
9514
|
+
const providerRules = providerDoc.config?.providerConfigRules?.providerRules;
|
|
9515
|
+
if (!Array.isArray(providerRules)) {
|
|
9516
|
+
throw new Error(`${paths.providerConfigFile}: config.providerConfigRules.providerRules is missing or not an array`);
|
|
9517
|
+
}
|
|
9518
|
+
const modelRules = providerDoc.config?.modelConfigRules?.providerModelRules;
|
|
9519
|
+
if (!Array.isArray(modelRules)) {
|
|
9520
|
+
throw new Error(`${paths.providerConfigFile}: config.modelConfigRules.providerModelRules is missing or not an array`);
|
|
9521
|
+
}
|
|
9522
|
+
configDoc.provider ??= {};
|
|
9523
|
+
const existingRule = providerRules.find((item) => isRecord3(item) && isManagedProviderName(item.providerName));
|
|
9524
|
+
const baseURL = `http://${options.config.host}:${options.config.port}`;
|
|
9525
|
+
let rule = existingRule;
|
|
9526
|
+
let providerId = rule && typeof rule.providerId === "string" ? rule.providerId : undefined;
|
|
9527
|
+
if (providerId === undefined) {
|
|
9528
|
+
providerId = randomUUID5();
|
|
9529
|
+
rule = {
|
|
9530
|
+
providerId,
|
|
9531
|
+
providerName: ZCODE_PROVIDER_DISPLAY_NAME,
|
|
9532
|
+
config: {
|
|
9533
|
+
group: ZCODE_PROVIDER_GROUP,
|
|
9534
|
+
access: { type: "api-key", apiKey: ZCODE_API_KEY_PLACEHOLDER },
|
|
9535
|
+
api: { type: "anthropic-messages", baseUrl: baseURL },
|
|
9536
|
+
personalModelIds: [],
|
|
9537
|
+
modelOrder: []
|
|
9538
|
+
}
|
|
9539
|
+
};
|
|
9540
|
+
providerRules.push(rule);
|
|
9541
|
+
if (Array.isArray(providerDoc.config?.providerOrder) && !providerDoc.config.providerOrder.includes(providerId)) {
|
|
9542
|
+
providerDoc.config.providerOrder.push(providerId);
|
|
9543
|
+
}
|
|
9544
|
+
} else {
|
|
9545
|
+
const managedRule = rule;
|
|
9546
|
+
if (Array.isArray(providerDoc.config?.providerOrder) && !providerDoc.config.providerOrder.includes(providerId)) {
|
|
9547
|
+
providerDoc.config.providerOrder.push(providerId);
|
|
9548
|
+
}
|
|
9549
|
+
if (managedRule.providerName !== ZCODE_PROVIDER_DISPLAY_NAME)
|
|
9550
|
+
managedRule.providerName = ZCODE_PROVIDER_DISPLAY_NAME;
|
|
9551
|
+
}
|
|
9552
|
+
if (!isRecord3(rule.config))
|
|
9553
|
+
throw new Error(`${paths.providerConfigFile}: managed provider rule has no config object`);
|
|
9554
|
+
if (rule.config.group !== ZCODE_PROVIDER_GROUP)
|
|
9555
|
+
rule.config.group = ZCODE_PROVIDER_GROUP;
|
|
9556
|
+
let entry = configDoc.provider[providerId];
|
|
9557
|
+
if (!isRecord3(entry)) {
|
|
9558
|
+
entry = {
|
|
9559
|
+
name: ZCODE_PROVIDER_DISPLAY_NAME,
|
|
9560
|
+
kind: "anthropic",
|
|
9561
|
+
options: { apiKey: ZCODE_API_KEY_PLACEHOLDER, apiKeyRequired: true, baseURL },
|
|
9562
|
+
source: "custom",
|
|
9563
|
+
models: {}
|
|
9564
|
+
};
|
|
9565
|
+
configDoc.provider[providerId] = entry;
|
|
9566
|
+
} else if (entry.name !== ZCODE_PROVIDER_DISPLAY_NAME) {
|
|
9567
|
+
entry.name = ZCODE_PROVIDER_DISPLAY_NAME;
|
|
9568
|
+
}
|
|
9569
|
+
const deletedModels = isRecord3(entry.zcode) ? entry.zcode.deletedModels : undefined;
|
|
9570
|
+
const deleted = new Set((Array.isArray(deletedModels) ? deletedModels : []).filter((item) => typeof item === "string").map((slug) => slug.toLowerCase()));
|
|
9571
|
+
const newSlugs = pushModels.filter((model) => !deleted.has(model.slug.toLowerCase())).map((model) => model.slug);
|
|
9572
|
+
if (newSlugs.length === 0) {
|
|
9573
|
+
throw new Error("zcode push aborted; every remaining model is hidden by ZCode deletedModels");
|
|
9574
|
+
}
|
|
9575
|
+
const newSet = new Set(newSlugs.map((slug) => slug.toLowerCase()));
|
|
9576
|
+
const bySlug = new Map(pushModels.map((model) => [model.slug.toLowerCase(), model]));
|
|
9577
|
+
const oldOrder = Array.isArray(rule.config.modelOrder) ? rule.config.modelOrder.filter((item) => typeof item === "string") : [];
|
|
9578
|
+
const oldSet = new Set(oldOrder.map((slug) => slug.toLowerCase()));
|
|
9579
|
+
const addedCount = newSlugs.filter((slug) => !oldSet.has(slug.toLowerCase())).length;
|
|
9580
|
+
const removedCount = oldOrder.filter((slug) => !newSet.has(slug.toLowerCase())).length;
|
|
9581
|
+
const unchangedCount = newSlugs.length - addedCount;
|
|
9582
|
+
rule.config.modelOrder = [...newSlugs];
|
|
9583
|
+
rule.config.personalModelIds = [...newSlugs];
|
|
9584
|
+
const syncedRules = newSlugs.map((slug) => {
|
|
9585
|
+
const model = bySlug.get(slug.toLowerCase());
|
|
9586
|
+
const context = modelContextWindow(model);
|
|
9587
|
+
return {
|
|
9588
|
+
modelId: slug,
|
|
9589
|
+
providerId,
|
|
9590
|
+
config: {
|
|
9591
|
+
properties: {
|
|
9592
|
+
...context === undefined ? {} : { contextWindow: context },
|
|
9593
|
+
inputFormat: { supportsImage: modelInputModalities(model).includes("image") }
|
|
9594
|
+
}
|
|
9595
|
+
}
|
|
9596
|
+
};
|
|
9597
|
+
});
|
|
9598
|
+
providerDoc.config.modelConfigRules.providerModelRules = [
|
|
9599
|
+
...modelRules.filter((item) => !(isRecord3(item) && item.providerId === providerId)),
|
|
9600
|
+
...syncedRules
|
|
9601
|
+
];
|
|
9602
|
+
const manualRules = providerDoc.config.modelConfigRules.manualProviderModelRules;
|
|
9603
|
+
if (Array.isArray(manualRules)) {
|
|
9604
|
+
providerDoc.config.modelConfigRules.manualProviderModelRules = manualRules.filter((item) => !(isRecord3(item) && item.providerId === providerId));
|
|
9605
|
+
}
|
|
9606
|
+
entry.models = Object.fromEntries(newSlugs.map((slug) => [
|
|
9607
|
+
slug,
|
|
9608
|
+
synthesizeZcodeModelDefinition(bySlug.get(slug.toLowerCase()))
|
|
9609
|
+
]));
|
|
9610
|
+
const pendingWrites = [
|
|
9611
|
+
{ file: paths.providerConfigFile, contents: `${JSON.stringify(providerDoc, null, 2)}
|
|
9612
|
+
` },
|
|
9613
|
+
{ file: paths.configFile, contents: `${JSON.stringify(configDoc, null, 2)}
|
|
9614
|
+
` }
|
|
9615
|
+
];
|
|
9616
|
+
const displayOrderFile = path13.join(paths.v2, "model-provider-display-order.json");
|
|
9617
|
+
if (fs19.existsSync(displayOrderFile)) {
|
|
9618
|
+
try {
|
|
9619
|
+
const displayOrder = JSON.parse(fs19.readFileSync(displayOrderFile, "utf8"));
|
|
9620
|
+
if (Array.isArray(displayOrder.providerIds) && !displayOrder.providerIds.includes(providerId)) {
|
|
9621
|
+
displayOrder.providerIds.push(providerId);
|
|
9622
|
+
displayOrder.updatedAt = Date.now();
|
|
9623
|
+
pendingWrites.push({ file: displayOrderFile, contents: `${JSON.stringify(displayOrder, null, 2)}
|
|
9624
|
+
` });
|
|
9625
|
+
}
|
|
9626
|
+
} catch {}
|
|
9627
|
+
}
|
|
9628
|
+
const backups = writeAllOrRollback(pendingWrites);
|
|
9629
|
+
return {
|
|
9630
|
+
summary: backups.length === 0 ? `zcode: provider "${ZCODE_PROVIDER_DISPLAY_NAME}" already up to date (${newSlugs.length} models)` : `zcode: ${newSlugs.length} models pushed to provider "${ZCODE_PROVIDER_DISPLAY_NAME}"` + ` (+${addedCount} added, -${removedCount} removed, ${unchangedCount} unchanged)`,
|
|
9631
|
+
notes: [
|
|
9632
|
+
...backups.map((backup) => `backup: ${backup}`),
|
|
9633
|
+
"note: the gateway /v1/messages endpoint currently converts only OpenCode Zen models end-to-end;" + " models from other sources may fail over this provider",
|
|
9634
|
+
"restart ZCode to refresh its model list"
|
|
9635
|
+
]
|
|
9636
|
+
};
|
|
9637
|
+
}
|
|
9638
|
+
function createZcodePushTarget(options) {
|
|
9639
|
+
return {
|
|
9640
|
+
id: "zcode",
|
|
9641
|
+
assertInstalled() {
|
|
9642
|
+
const paths = resolveZcodeClientPaths(options.env);
|
|
9643
|
+
if (!fs19.existsSync(paths.configFile) || !fs19.existsSync(paths.providerConfigFile)) {
|
|
9644
|
+
throw new Error(`ZCode is not installed (expected ${paths.v2}/config.json and provider_config.json);` + ` install ZCode or point ZCODE_HOME at its config directory`);
|
|
9645
|
+
}
|
|
9646
|
+
},
|
|
9647
|
+
push(models) {
|
|
9648
|
+
return pushZcodeModels(models, options);
|
|
9649
|
+
}
|
|
9650
|
+
};
|
|
9651
|
+
}
|
|
9652
|
+
|
|
9653
|
+
// src/dsh-client-sync.ts
|
|
9360
9654
|
import fs20 from "fs";
|
|
9655
|
+
import os4 from "os";
|
|
9361
9656
|
import path14 from "path";
|
|
9657
|
+
import YAML, { isMap, isSeq, YAMLMap, YAMLSeq } from "yaml";
|
|
9658
|
+
var DSH_PROVIDER_KEY = "agent-proxy";
|
|
9659
|
+
var DSH_PROVIDER_DISPLAY_NAME = "AgentProxy";
|
|
9660
|
+
var DSH_PROVIDER_API = "anthropic-messages";
|
|
9661
|
+
var DSH_API_KEY_ENV = "AGENT_PROXY_API_KEY";
|
|
9662
|
+
var DSH_LLM_ENTRY_ID = "llm-pi-ai";
|
|
9663
|
+
var DSH_PATCH_FILE = "cordis.patch.yml";
|
|
9664
|
+
function resolveDshClientPaths(env = process.env) {
|
|
9665
|
+
const home = env.DSH_HOME || path14.join(env.HOME || os4.homedir(), ".dsh");
|
|
9666
|
+
return { home, profilesDir: path14.join(home, "profiles") };
|
|
9667
|
+
}
|
|
9668
|
+
var DSH_EFFORT_KEYS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
9669
|
+
function dshReasoningEfforts(efforts) {
|
|
9670
|
+
const mapped = {};
|
|
9671
|
+
for (const effort of efforts) {
|
|
9672
|
+
const key = effort === "none" ? "off" : effort;
|
|
9673
|
+
if (!DSH_EFFORT_KEYS.includes(key))
|
|
9674
|
+
continue;
|
|
9675
|
+
if (!(key in mapped))
|
|
9676
|
+
mapped[key] = effort;
|
|
9677
|
+
}
|
|
9678
|
+
return mapped;
|
|
9679
|
+
}
|
|
9680
|
+
function synthesizeDshModel(entry) {
|
|
9681
|
+
const model = {
|
|
9682
|
+
id: entry.slug,
|
|
9683
|
+
name: typeof entry.display_name === "string" && entry.display_name ? entry.display_name : entry.slug
|
|
9684
|
+
};
|
|
9685
|
+
const context = modelContextWindow(entry);
|
|
9686
|
+
if (context !== undefined)
|
|
9687
|
+
model.contextWindow = context;
|
|
9688
|
+
model.input = modelInputModalities(entry);
|
|
9689
|
+
const efforts = modelReasoningEfforts(entry);
|
|
9690
|
+
if (efforts.length > 0) {
|
|
9691
|
+
const reasoningEfforts = dshReasoningEfforts(efforts);
|
|
9692
|
+
if (Object.keys(reasoningEfforts).length > 0)
|
|
9693
|
+
model.reasoningEfforts = reasoningEfforts;
|
|
9694
|
+
}
|
|
9695
|
+
return model;
|
|
9696
|
+
}
|
|
9697
|
+
function parseDshPatch(file, profile) {
|
|
9698
|
+
const raw = fs20.readFileSync(file, "utf8");
|
|
9699
|
+
const doc = YAML.parseDocument(raw);
|
|
9700
|
+
if (doc.errors.length > 0) {
|
|
9701
|
+
const first = doc.errors[0];
|
|
9702
|
+
throw new Error(`${file} is not valid YAML (${first.message}); fix or restore it (see its .bak-* backups), then rerun models --push`);
|
|
9703
|
+
}
|
|
9704
|
+
if (doc.contents === null)
|
|
9705
|
+
doc.contents = new YAMLSeq;
|
|
9706
|
+
if (!isSeq(doc.contents))
|
|
9707
|
+
throw new Error(`${file}: expected a top-level YAML array of patch entries`);
|
|
9708
|
+
const documents = [];
|
|
9709
|
+
for (const item of doc.contents.items) {
|
|
9710
|
+
if (isMap(item) && item.toJS(doc).id === DSH_LLM_ENTRY_ID)
|
|
9711
|
+
documents.push({ file, profile, doc, entry: item });
|
|
9712
|
+
}
|
|
9713
|
+
return documents;
|
|
9714
|
+
}
|
|
9715
|
+
function pushDshModels(models, options) {
|
|
9716
|
+
const paths = resolveDshClientPaths(options.env);
|
|
9717
|
+
if (!fs20.existsSync(paths.profilesDir)) {
|
|
9718
|
+
throw new Error(`dsh profiles directory is missing (${paths.profilesDir}); reinstall dsh or point DSH_HOME at it`);
|
|
9719
|
+
}
|
|
9720
|
+
const profileNames = fs20.readdirSync(paths.profilesDir, { withFileTypes: true }).filter((item) => item.isDirectory()).map((item) => item.name).sort();
|
|
9721
|
+
const touched = [];
|
|
9722
|
+
for (const profile of profileNames) {
|
|
9723
|
+
const file = path14.join(paths.profilesDir, profile, DSH_PATCH_FILE);
|
|
9724
|
+
if (!fs20.existsSync(file))
|
|
9725
|
+
continue;
|
|
9726
|
+
touched.push(...parseDshPatch(file, profile));
|
|
9727
|
+
}
|
|
9728
|
+
if (touched.length === 0) {
|
|
9729
|
+
throw new Error(`dsh is installed but no profile has an "${DSH_LLM_ENTRY_ID}" patch entry under ${paths.profilesDir};` + " configure any custom provider inside dsh once so the patch layer exists, then rerun models --push");
|
|
9730
|
+
}
|
|
9731
|
+
const baseURL = `http://${options.config.host}:${options.config.port}`;
|
|
9732
|
+
const createdProfiles = [];
|
|
9733
|
+
const pendingWrites = [];
|
|
9734
|
+
for (const { file, profile, doc, entry } of touched) {
|
|
9735
|
+
const before = doc.toString();
|
|
9736
|
+
let config;
|
|
9737
|
+
const rawConfig = entry.get("config");
|
|
9738
|
+
if (isMap(rawConfig))
|
|
9739
|
+
config = rawConfig;
|
|
9740
|
+
else {
|
|
9741
|
+
config = new YAMLMap;
|
|
9742
|
+
entry.set("config", config);
|
|
9743
|
+
}
|
|
9744
|
+
let providers;
|
|
9745
|
+
const rawProviders = config.get("providers");
|
|
9746
|
+
if (isMap(rawProviders))
|
|
9747
|
+
providers = rawProviders;
|
|
9748
|
+
else {
|
|
9749
|
+
providers = new YAMLMap;
|
|
9750
|
+
config.set("providers", providers);
|
|
9751
|
+
}
|
|
9752
|
+
let provider;
|
|
9753
|
+
const rawProvider = providers.get(DSH_PROVIDER_KEY);
|
|
9754
|
+
if (isMap(rawProvider))
|
|
9755
|
+
provider = rawProvider;
|
|
9756
|
+
else {
|
|
9757
|
+
provider = doc.createNode({
|
|
9758
|
+
displayName: DSH_PROVIDER_DISPLAY_NAME,
|
|
9759
|
+
apiKeyEnv: DSH_API_KEY_ENV,
|
|
9760
|
+
api: DSH_PROVIDER_API,
|
|
9761
|
+
baseURL
|
|
9762
|
+
});
|
|
9763
|
+
providers.set(DSH_PROVIDER_KEY, provider);
|
|
9764
|
+
createdProfiles.push(profile);
|
|
9765
|
+
}
|
|
9766
|
+
provider.set("models", doc.createNode(models.map(synthesizeDshModel)));
|
|
9767
|
+
const after = doc.toString();
|
|
9768
|
+
if (after === before)
|
|
9769
|
+
continue;
|
|
9770
|
+
pendingWrites.push({ file, contents: after });
|
|
9771
|
+
}
|
|
9772
|
+
const backups = writeAllOrRollback(pendingWrites);
|
|
9773
|
+
const managedProfiles = [...new Set(touched.map((item) => item.profile))].join(", ");
|
|
9774
|
+
return {
|
|
9775
|
+
summary: backups.length === 0 ? `dsh: provider "${DSH_PROVIDER_KEY}" already up to date (${models.length} models, profiles: ${managedProfiles})` : `dsh: ${models.length} models pushed to provider "${DSH_PROVIDER_KEY}"` + ` (${DSH_PROVIDER_DISPLAY_NAME}, ${DSH_PROVIDER_API}; profiles: ${managedProfiles})`,
|
|
9776
|
+
notes: [
|
|
9777
|
+
...createdProfiles.length > 0 ? [`provider "${DSH_PROVIDER_KEY}" created in: ${createdProfiles.join(", ")}` + ` (apiKey is a placeholder; the gateway ignores client keys \u2014 its plugins authorize upstreams themselves)`] : [],
|
|
9778
|
+
...backups.map((backup) => `backup: ${backup}`),
|
|
9779
|
+
"note: the gateway /v1/messages endpoint currently converts only OpenCode Zen models end-to-end;" + " models from other sources may fail over this provider",
|
|
9780
|
+
"restart DeepSeek Harness (dsh) to refresh its model list"
|
|
9781
|
+
]
|
|
9782
|
+
};
|
|
9783
|
+
}
|
|
9784
|
+
function createDshPushTarget(options) {
|
|
9785
|
+
return {
|
|
9786
|
+
id: "dsh",
|
|
9787
|
+
assertInstalled() {
|
|
9788
|
+
const paths = resolveDshClientPaths(options.env);
|
|
9789
|
+
if (!fs20.existsSync(paths.home)) {
|
|
9790
|
+
throw new Error(`dsh (DeepSeek Harness) is not installed (no ${paths.home}); install dsh or point DSH_HOME at it`);
|
|
9791
|
+
}
|
|
9792
|
+
},
|
|
9793
|
+
push(models) {
|
|
9794
|
+
return pushDshModels(models, options);
|
|
9795
|
+
}
|
|
9796
|
+
};
|
|
9797
|
+
}
|
|
9798
|
+
// src/webui.ts
|
|
9799
|
+
import fs23 from "fs";
|
|
9800
|
+
import path16 from "path";
|
|
9362
9801
|
import { randomBytes } from "crypto";
|
|
9363
9802
|
|
|
9364
9803
|
// src/app-server.ts
|
|
9365
|
-
import
|
|
9366
|
-
import
|
|
9804
|
+
import fs21 from "fs";
|
|
9805
|
+
import path15 from "path";
|
|
9367
9806
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
9368
9807
|
import { dlopen } from "bun:ffi";
|
|
9369
9808
|
var GLOBAL_OPTIONS_WITH_VALUE = new Set([
|
|
@@ -9524,22 +9963,22 @@ function listLinuxProcesses() {
|
|
|
9524
9963
|
const uid = process.getuid?.();
|
|
9525
9964
|
if (uid === undefined)
|
|
9526
9965
|
throw new Error("Current user ID is unavailable");
|
|
9527
|
-
const entries =
|
|
9966
|
+
const entries = fs21.readdirSync("/proc", { withFileTypes: true });
|
|
9528
9967
|
const processes = [];
|
|
9529
9968
|
for (const entry of entries) {
|
|
9530
9969
|
if (!entry.isDirectory() || !/^\d+$/.test(entry.name))
|
|
9531
9970
|
continue;
|
|
9532
9971
|
const root = `/proc/${entry.name}`;
|
|
9533
9972
|
try {
|
|
9534
|
-
const status =
|
|
9973
|
+
const status = fs21.readFileSync(`${root}/status`, "utf8");
|
|
9535
9974
|
const owner = status.match(/^Uid:\s+(\d+)/m)?.[1];
|
|
9536
9975
|
if (owner !== String(uid))
|
|
9537
9976
|
continue;
|
|
9538
|
-
const executable =
|
|
9977
|
+
const executable = fs21.readlinkSync(`${root}/exe`);
|
|
9539
9978
|
if (!isCodexExecutable(executable) && !/^codex-code-mode-host(?:\.exe)?$/i.test(executableName(executable)))
|
|
9540
9979
|
continue;
|
|
9541
|
-
const args =
|
|
9542
|
-
const stat =
|
|
9980
|
+
const args = fs21.readFileSync(`${root}/cmdline`).toString("utf8").split("\x00").filter(Boolean);
|
|
9981
|
+
const stat = fs21.readFileSync(`${root}/stat`, "utf8");
|
|
9543
9982
|
const fields = stat.slice(stat.lastIndexOf(")") + 2).split(" ");
|
|
9544
9983
|
const startedAt = fields[19];
|
|
9545
9984
|
if (!startedAt || args.length === 0)
|
|
@@ -9584,9 +10023,9 @@ function parseWindowsCommandLine(commandLine) {
|
|
|
9584
10023
|
}
|
|
9585
10024
|
function windowsSystemPath(...segments) {
|
|
9586
10025
|
const systemRoot = process.env.SystemRoot || process.env.windir;
|
|
9587
|
-
if (!systemRoot || !
|
|
10026
|
+
if (!systemRoot || !path15.win32.isAbsolute(systemRoot))
|
|
9588
10027
|
throw new Error("Windows system root is unavailable");
|
|
9589
|
-
return
|
|
10028
|
+
return path15.win32.join(systemRoot, ...segments);
|
|
9590
10029
|
}
|
|
9591
10030
|
function listWindowsProcesses() {
|
|
9592
10031
|
const powershell = windowsSystemPath("System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
@@ -9638,7 +10077,7 @@ function terminateSystemProcess(pid) {
|
|
|
9638
10077
|
}
|
|
9639
10078
|
try {
|
|
9640
10079
|
const taskkill = windowsSystemPath("System32", "taskkill.exe");
|
|
9641
|
-
if (!
|
|
10080
|
+
if (!fs21.existsSync(taskkill))
|
|
9642
10081
|
throw new Error("taskkill.exe not found");
|
|
9643
10082
|
execFileSync4(taskkill, ["/PID", String(pid), "/T", "/F"], { stdio: "ignore" });
|
|
9644
10083
|
} catch {
|
|
@@ -9713,9 +10152,9 @@ async function stopCodexAppServers(runtime = SYSTEM_RUNTIME) {
|
|
|
9713
10152
|
}
|
|
9714
10153
|
|
|
9715
10154
|
// src/config-update.ts
|
|
9716
|
-
import
|
|
10155
|
+
import fs22 from "fs";
|
|
9717
10156
|
import bytes from "bytes";
|
|
9718
|
-
import { isZcodeModel, zcodeStaticModelPrefixes } from "@agent-proxy/zcode/catalog";
|
|
10157
|
+
import { isZcodeModel as isZcodeModel2, zcodeStaticModelPrefixes } from "@agent-proxy/zcode/catalog";
|
|
9719
10158
|
import { isCodebuddyAccountValue } from "@agent-proxy/codebuddy/credentials";
|
|
9720
10159
|
import { CODEBUDDY_PREFIX, isCodebuddyModel, WORKBUDDY_PREFIX, codebuddyModelPrefixes } from "@agent-proxy/codebuddy/catalog";
|
|
9721
10160
|
import { isQoderModel, QODER_CN_PREFIX, QODER_INTL_PREFIX } from "@agent-proxy/qoder/catalog";
|
|
@@ -9904,7 +10343,7 @@ function expandExcludedModelGroups(groupsInput, definitions) {
|
|
|
9904
10343
|
return normalizeExcludedModels(rules);
|
|
9905
10344
|
}
|
|
9906
10345
|
function applySelectedModelsPatch(paths, selectedModels, syncState = true) {
|
|
9907
|
-
if (!
|
|
10346
|
+
if (!fs22.existsSync(paths.gatewayConfig))
|
|
9908
10347
|
throw new Error("Gateway is not installed");
|
|
9909
10348
|
const config = readGatewayConfigFile(paths.gatewayConfig);
|
|
9910
10349
|
const before = Array.isArray(config.selectedModels) ? config.selectedModels : [];
|
|
@@ -9914,8 +10353,8 @@ function applySelectedModelsPatch(paths, selectedModels, syncState = true) {
|
|
|
9914
10353
|
config.selectedModels = selectedModels;
|
|
9915
10354
|
validateConfiguredPlugins(config);
|
|
9916
10355
|
writeGatewayConfigFile(paths.gatewayConfig, config);
|
|
9917
|
-
if (syncState &&
|
|
9918
|
-
const state = JSON.parse(
|
|
10356
|
+
if (syncState && fs22.existsSync(paths.stateFile)) {
|
|
10357
|
+
const state = JSON.parse(fs22.readFileSync(paths.stateFile, "utf8"));
|
|
9919
10358
|
state.config = config;
|
|
9920
10359
|
atomicWrite(paths.stateFile, `${JSON.stringify(state, null, 2)}
|
|
9921
10360
|
`);
|
|
@@ -9932,7 +10371,7 @@ function applyWebUiConfigPatch(paths, patch, syncState = true, { write = true }
|
|
|
9932
10371
|
if (!SUPPORTED_PATCH_FIELDS.has(key))
|
|
9933
10372
|
throw new Error(`Unsupported field: ${key}`);
|
|
9934
10373
|
}
|
|
9935
|
-
if (!
|
|
10374
|
+
if (!fs22.existsSync(paths.gatewayConfig))
|
|
9936
10375
|
throw new Error("Gateway is not installed");
|
|
9937
10376
|
const config = readGatewayConfigFile(paths.gatewayConfig);
|
|
9938
10377
|
const before = config;
|
|
@@ -10033,8 +10472,8 @@ function applyWebUiConfigPatch(paths, patch, syncState = true, { write = true }
|
|
|
10033
10472
|
if (!write)
|
|
10034
10473
|
return { config, applied };
|
|
10035
10474
|
writeGatewayConfigFile(paths.gatewayConfig, config);
|
|
10036
|
-
if (syncState &&
|
|
10037
|
-
const state = JSON.parse(
|
|
10475
|
+
if (syncState && fs22.existsSync(paths.stateFile)) {
|
|
10476
|
+
const state = JSON.parse(fs22.readFileSync(paths.stateFile, "utf8"));
|
|
10038
10477
|
state.config = config;
|
|
10039
10478
|
atomicWrite(paths.stateFile, `${JSON.stringify(state, null, 2)}
|
|
10040
10479
|
`);
|
|
@@ -10043,20 +10482,20 @@ function applyWebUiConfigPatch(paths, patch, syncState = true, { write = true }
|
|
|
10043
10482
|
return { config, applied };
|
|
10044
10483
|
}
|
|
10045
10484
|
function markPendingRestart(stateFile) {
|
|
10046
|
-
if (!
|
|
10485
|
+
if (!fs22.existsSync(stateFile))
|
|
10047
10486
|
return;
|
|
10048
10487
|
try {
|
|
10049
|
-
const state = JSON.parse(
|
|
10488
|
+
const state = JSON.parse(fs22.readFileSync(stateFile, "utf8"));
|
|
10050
10489
|
state.pendingRestart = true;
|
|
10051
10490
|
atomicWrite(stateFile, `${JSON.stringify(state, null, 2)}
|
|
10052
10491
|
`);
|
|
10053
10492
|
} catch {}
|
|
10054
10493
|
}
|
|
10055
10494
|
function clearPendingRestart(stateFile) {
|
|
10056
|
-
if (!
|
|
10495
|
+
if (!fs22.existsSync(stateFile))
|
|
10057
10496
|
return;
|
|
10058
10497
|
try {
|
|
10059
|
-
const state = JSON.parse(
|
|
10498
|
+
const state = JSON.parse(fs22.readFileSync(stateFile, "utf8"));
|
|
10060
10499
|
if (state.pendingRestart !== true)
|
|
10061
10500
|
return;
|
|
10062
10501
|
state.pendingRestart = false;
|
|
@@ -10082,14 +10521,14 @@ var REQUEST_LOG_TAIL_BYTES = 64 * 1024;
|
|
|
10082
10521
|
var REQUEST_LOG_DEFAULT_PAGE_SIZE = 100;
|
|
10083
10522
|
var REQUEST_LOG_MAX_PAGE_SIZE = 500;
|
|
10084
10523
|
var RESTART_DELAY_MS = 400;
|
|
10085
|
-
var UI_HTML_PATH =
|
|
10524
|
+
var UI_HTML_PATH = path16.resolve(import.meta.dir, "../dist/ui/index.html");
|
|
10086
10525
|
function webUiContextForInstance(configPath, defaultPaths) {
|
|
10087
10526
|
const resolved = realPathOrResolve(configPath);
|
|
10088
10527
|
if (resolved === realPathOrResolve(defaultPaths.gatewayConfig))
|
|
10089
10528
|
return { paths: defaultPaths };
|
|
10090
10529
|
return {
|
|
10091
10530
|
paths: {
|
|
10092
|
-
...resolvePaths({ HOME: defaultPaths.home, CODEX_HOME: defaultPaths.codexHome },
|
|
10531
|
+
...resolvePaths({ HOME: defaultPaths.home, CODEX_HOME: defaultPaths.codexHome }, path16.dirname(resolved)),
|
|
10093
10532
|
gatewayConfig: resolved
|
|
10094
10533
|
},
|
|
10095
10534
|
instanceOnly: true
|
|
@@ -10100,7 +10539,7 @@ function isLoopbackHost(host) {
|
|
|
10100
10539
|
}
|
|
10101
10540
|
function ensureUiToken(file) {
|
|
10102
10541
|
try {
|
|
10103
|
-
const existing =
|
|
10542
|
+
const existing = fs23.readFileSync(file, "utf8").trim();
|
|
10104
10543
|
if (existing)
|
|
10105
10544
|
return existing;
|
|
10106
10545
|
} catch {}
|
|
@@ -10111,7 +10550,7 @@ function ensureUiToken(file) {
|
|
|
10111
10550
|
}
|
|
10112
10551
|
function readUiToken(file) {
|
|
10113
10552
|
try {
|
|
10114
|
-
const value =
|
|
10553
|
+
const value = fs23.readFileSync(file, "utf8").trim();
|
|
10115
10554
|
return value || null;
|
|
10116
10555
|
} catch {
|
|
10117
10556
|
return null;
|
|
@@ -10127,22 +10566,22 @@ var TAIL_EXPAND_CAP_BYTES = 1024 * 1024;
|
|
|
10127
10566
|
function tailFile(file, maxBytes) {
|
|
10128
10567
|
let stat;
|
|
10129
10568
|
try {
|
|
10130
|
-
stat =
|
|
10569
|
+
stat = fs23.statSync(file);
|
|
10131
10570
|
} catch {
|
|
10132
10571
|
return null;
|
|
10133
10572
|
}
|
|
10134
10573
|
try {
|
|
10135
10574
|
if (stat.size <= maxBytes) {
|
|
10136
|
-
return { text:
|
|
10575
|
+
return { text: fs23.readFileSync(file, "utf8"), truncated: false };
|
|
10137
10576
|
}
|
|
10138
|
-
const fd =
|
|
10577
|
+
const fd = fs23.openSync(file, "r");
|
|
10139
10578
|
try {
|
|
10140
10579
|
let window = maxBytes;
|
|
10141
10580
|
for (;; ) {
|
|
10142
10581
|
const start = Math.max(0, stat.size - window);
|
|
10143
10582
|
const length = stat.size - start;
|
|
10144
10583
|
const buffer = Buffer.alloc(length);
|
|
10145
|
-
|
|
10584
|
+
fs23.readSync(fd, buffer, 0, length, start);
|
|
10146
10585
|
const text = buffer.toString("utf8");
|
|
10147
10586
|
const firstNewline = text.indexOf(`
|
|
10148
10587
|
`);
|
|
@@ -10164,7 +10603,7 @@ function tailFile(file, maxBytes) {
|
|
|
10164
10603
|
window = Math.min(stat.size, window * 2);
|
|
10165
10604
|
}
|
|
10166
10605
|
} finally {
|
|
10167
|
-
|
|
10606
|
+
fs23.closeSync(fd);
|
|
10168
10607
|
}
|
|
10169
10608
|
} catch {
|
|
10170
10609
|
return null;
|
|
@@ -10200,7 +10639,7 @@ function uiHtmlResponse(ctx) {
|
|
|
10200
10639
|
const uiHtmlPath = ctx.uiHtmlPath ?? UI_HTML_PATH;
|
|
10201
10640
|
let html;
|
|
10202
10641
|
try {
|
|
10203
|
-
html =
|
|
10642
|
+
html = fs23.readFileSync(uiHtmlPath, "utf8");
|
|
10204
10643
|
} catch {
|
|
10205
10644
|
return Response.json({
|
|
10206
10645
|
error: {
|
|
@@ -10231,7 +10670,7 @@ function faviconResponse() {
|
|
|
10231
10670
|
}
|
|
10232
10671
|
function readCodexConfigManaged(stateFile) {
|
|
10233
10672
|
try {
|
|
10234
|
-
const state = JSON.parse(
|
|
10673
|
+
const state = JSON.parse(fs23.readFileSync(stateFile, "utf8"));
|
|
10235
10674
|
return state.codexConfigManaged !== false;
|
|
10236
10675
|
} catch {
|
|
10237
10676
|
return true;
|
|
@@ -10239,10 +10678,10 @@ function readCodexConfigManaged(stateFile) {
|
|
|
10239
10678
|
}
|
|
10240
10679
|
function manualCodexConfigGuidance(paths, config) {
|
|
10241
10680
|
const gatewayBaseUrl = `http://${config.host}:${config.port}${MOUNT_PATH}`;
|
|
10242
|
-
const source =
|
|
10681
|
+
const source = fs23.existsSync(paths.configToml) ? fs23.readFileSync(paths.configToml, "utf8") : "";
|
|
10243
10682
|
let staticCatalogAvailable = false;
|
|
10244
10683
|
try {
|
|
10245
|
-
const catalog = JSON.parse(
|
|
10684
|
+
const catalog = JSON.parse(fs23.readFileSync(codexCatalogFile(paths), "utf8"));
|
|
10246
10685
|
staticCatalogAvailable = Array.isArray(catalog.models) && catalog.models.length > 0;
|
|
10247
10686
|
} catch {}
|
|
10248
10687
|
const staticCatalogActive = config.upstreamOnly === true && staticCatalogAvailable;
|
|
@@ -10329,7 +10768,7 @@ function configResponse(paths, providerDeps, instanceOnly = false) {
|
|
|
10329
10768
|
excludedOther
|
|
10330
10769
|
},
|
|
10331
10770
|
detected: {
|
|
10332
|
-
zcode: zcodeConfigPresent(providerDeps?.zcodeHome ??
|
|
10771
|
+
zcode: zcodeConfigPresent(providerDeps?.zcodeHome ?? path16.join(paths.home, ".zcode")),
|
|
10333
10772
|
codebuddy: codebuddyCredentialsPresent(codebuddyAuthDir),
|
|
10334
10773
|
codebuddyAccountLabel: accountLabel ?? null,
|
|
10335
10774
|
codebuddyAccounts,
|
|
@@ -10384,13 +10823,13 @@ function requestLogsResponse(config, paths, url) {
|
|
|
10384
10823
|
const dir = requestLogDir(paths);
|
|
10385
10824
|
let names;
|
|
10386
10825
|
try {
|
|
10387
|
-
names =
|
|
10826
|
+
names = fs23.readdirSync(dir);
|
|
10388
10827
|
} catch {
|
|
10389
10828
|
return Response.json({ files: [], total: 0, offset: 0, limit: REQUEST_LOG_DEFAULT_PAGE_SIZE, logging });
|
|
10390
10829
|
}
|
|
10391
10830
|
const all = names.filter((name) => isRequestLogName(name)).flatMap((name) => {
|
|
10392
10831
|
try {
|
|
10393
|
-
const stat =
|
|
10832
|
+
const stat = fs23.statSync(path16.join(dir, name));
|
|
10394
10833
|
return [{
|
|
10395
10834
|
name,
|
|
10396
10835
|
size: stat.size,
|
|
@@ -10448,8 +10887,8 @@ function modelSaveRollback(paths, config) {
|
|
|
10448
10887
|
codexCatalogFile(paths),
|
|
10449
10888
|
paths.configToml
|
|
10450
10889
|
])];
|
|
10451
|
-
const previous = files.map((file) => ({ file, source:
|
|
10452
|
-
const readToml = () =>
|
|
10890
|
+
const previous = files.map((file) => ({ file, source: fs23.existsSync(file) ? fs23.readFileSync(file) : undefined }));
|
|
10891
|
+
const readToml = () => fs23.existsSync(paths.configToml) ? fs23.readFileSync(paths.configToml, "utf8") : undefined;
|
|
10453
10892
|
let expectedToml = readToml();
|
|
10454
10893
|
const restore = () => {
|
|
10455
10894
|
for (const { file, source } of previous) {
|
|
@@ -10457,8 +10896,8 @@ function modelSaveRollback(paths, config) {
|
|
|
10457
10896
|
continue;
|
|
10458
10897
|
if (source)
|
|
10459
10898
|
atomicWrite(file, source.toString("utf8"));
|
|
10460
|
-
else if (
|
|
10461
|
-
|
|
10899
|
+
else if (fs23.existsSync(file))
|
|
10900
|
+
fs23.unlinkSync(file);
|
|
10462
10901
|
}
|
|
10463
10902
|
logConfigChange(paths.stdoutLog, {
|
|
10464
10903
|
command: "webui model rollback",
|
|
@@ -10579,7 +11018,7 @@ async function handleWebUiRequestCore(request, config, ctx, port) {
|
|
|
10579
11018
|
}
|
|
10580
11019
|
const structural = applied.some((change) => change.field !== "excludedModels");
|
|
10581
11020
|
const modelsChanged = selection !== undefined || applied.some((change) => change.field === "excludedModels" || change.field === "codebuddyAccount" || next.upstreamOnly === true && ["zcode", "codebuddy", "qoder", "agy", "opencodeZen"].includes(change.field));
|
|
10582
|
-
const restarting = structural && managesService &&
|
|
11021
|
+
const restarting = structural && managesService && fs23.existsSync(ctx.paths.launchAgent);
|
|
10583
11022
|
if (restarting) {
|
|
10584
11023
|
markPendingRestart(ctx.paths.stateFile);
|
|
10585
11024
|
(ctx.scheduleRestart ?? defaultScheduleRestart)(ctx.paths);
|
|
@@ -10736,7 +11175,7 @@ Usage:
|
|
|
10736
11175
|
agent-proxy start|stop
|
|
10737
11176
|
agent-proxy restart [--restart-codex]
|
|
10738
11177
|
agent-proxy serve [--config PATH]
|
|
10739
|
-
agent-proxy models [--sync] [--select SELECTOR] [--exclude [PATTERNS]] [--restart-codex]
|
|
11178
|
+
agent-proxy models [--sync] [--select SELECTOR] [--exclude [PATTERNS]] [--push [TARGETS]] [--restart-codex]
|
|
10740
11179
|
agent-proxy config [--zcode on|off] [--codebuddy on|off] [--qoder on|off] [--agy on|off] [--opencode-zen on|off] [--log on|off] [--debug on|off] [--max-request-logs N] [--max-log-size SIZE]
|
|
10741
11180
|
agent-proxy codebuddy --switch
|
|
10742
11181
|
agent-proxy web
|
|
@@ -10791,6 +11230,19 @@ Models:
|
|
|
10791
11230
|
compatibility model (space toggles exclusion, enter
|
|
10792
11231
|
saves)
|
|
10793
11232
|
--model-merge-json URL update the cached models.json override
|
|
11233
|
+
--push [TARGETS] push the local agent plugin models into client tools;
|
|
11234
|
+
a checkbox picker confirms the list first (every model
|
|
11235
|
+
preselected \u2014 uncheck what you do not want and press
|
|
11236
|
+
Enter; requires a terminal) and the managed
|
|
11237
|
+
provider's model list is then fully replaced by ID
|
|
11238
|
+
(manual additions inside the managed provider are
|
|
11239
|
+
removed); upstream cliproxy/newapi and official models
|
|
11240
|
+
are not pushed (authorization); the zcode plugin's own
|
|
11241
|
+
models are never pushed to zcode; each tool gets a
|
|
11242
|
+
managed "agent-proxy" provider created on first push;
|
|
11243
|
+
targets: zcode, dsh; without a value every target is
|
|
11244
|
+
pushed; each modified file is backed up first; always
|
|
11245
|
+
runs after --sync/--exclude
|
|
10794
11246
|
--restart-codex stop Codex app-server after sync or exclusion changes
|
|
10795
11247
|
to refresh the model picker; active tasks may error
|
|
10796
11248
|
and require recovery or reopening
|
|
@@ -10873,6 +11325,15 @@ function parseArgs(args) {
|
|
|
10873
11325
|
}
|
|
10874
11326
|
continue;
|
|
10875
11327
|
}
|
|
11328
|
+
if (key === "push") {
|
|
11329
|
+
if (next === undefined || next.startsWith("--"))
|
|
11330
|
+
options.push = true;
|
|
11331
|
+
else {
|
|
11332
|
+
options.push = next;
|
|
11333
|
+
i += 1;
|
|
11334
|
+
}
|
|
11335
|
+
continue;
|
|
11336
|
+
}
|
|
10876
11337
|
if (next === undefined || next.startsWith("--"))
|
|
10877
11338
|
throw new Error(`--${key} requires a value`);
|
|
10878
11339
|
options[key] = next;
|
|
@@ -10905,7 +11366,7 @@ function confirmInstallOverwrite() {
|
|
|
10905
11366
|
let bytes3 = 0;
|
|
10906
11367
|
try {
|
|
10907
11368
|
while (bytes3 < buffer.length) {
|
|
10908
|
-
const read =
|
|
11369
|
+
const read = fs24.readSync(0, buffer, bytes3, buffer.length - bytes3, null);
|
|
10909
11370
|
if (read <= 0)
|
|
10910
11371
|
break;
|
|
10911
11372
|
bytes3 += read;
|
|
@@ -10923,7 +11384,7 @@ function promptModifyCodexConfig() {
|
|
|
10923
11384
|
let bytes3 = 0;
|
|
10924
11385
|
try {
|
|
10925
11386
|
while (bytes3 < buffer.length) {
|
|
10926
|
-
const read =
|
|
11387
|
+
const read = fs24.readSync(0, buffer, bytes3, buffer.length - bytes3, null);
|
|
10927
11388
|
if (read <= 0)
|
|
10928
11389
|
break;
|
|
10929
11390
|
bytes3 += read;
|
|
@@ -11063,13 +11524,13 @@ function timestamp() {
|
|
|
11063
11524
|
return new Date().toISOString().replace(/[-:]/g, "").slice(0, 15).replace("T", "");
|
|
11064
11525
|
}
|
|
11065
11526
|
function backupConfig(file) {
|
|
11066
|
-
|
|
11067
|
-
const existed =
|
|
11527
|
+
fs24.mkdirSync(path17.dirname(file), { recursive: true });
|
|
11528
|
+
const existed = fs24.existsSync(file);
|
|
11068
11529
|
const backup = `${file}.bak-cliproxy-gateway-${timestamp()}`;
|
|
11069
11530
|
if (existed)
|
|
11070
|
-
|
|
11531
|
+
fs24.copyFileSync(file, backup);
|
|
11071
11532
|
else
|
|
11072
|
-
|
|
11533
|
+
fs24.writeFileSync(backup, "", { mode: 384 });
|
|
11073
11534
|
return { existed, backup };
|
|
11074
11535
|
}
|
|
11075
11536
|
function hash2(contents) {
|
|
@@ -11077,10 +11538,10 @@ function hash2(contents) {
|
|
|
11077
11538
|
}
|
|
11078
11539
|
function restoreBackup(file, record) {
|
|
11079
11540
|
if (record.existed) {
|
|
11080
|
-
|
|
11081
|
-
|
|
11541
|
+
fs24.mkdirSync(path17.dirname(file), { recursive: true });
|
|
11542
|
+
fs24.copyFileSync(record.backup, file);
|
|
11082
11543
|
} else {
|
|
11083
|
-
|
|
11544
|
+
fs24.rmSync(file, { force: true });
|
|
11084
11545
|
}
|
|
11085
11546
|
}
|
|
11086
11547
|
function restoreManagedCodexToml(paths, state, currentToml) {
|
|
@@ -11090,12 +11551,12 @@ function restoreManagedCodexToml(paths, state, currentToml) {
|
|
|
11090
11551
|
if (state.installedConfigHash !== undefined && hash2(currentToml) === state.installedConfigHash) {
|
|
11091
11552
|
restoreBackup(paths.configToml, state.configBackup);
|
|
11092
11553
|
} else {
|
|
11093
|
-
const backupToml =
|
|
11554
|
+
const backupToml = fs24.readFileSync(state.configBackup.backup, "utf8");
|
|
11094
11555
|
atomicWrite(paths.configToml, restoreRootTomlKeys(currentToml, backupToml, MANAGED_CONFIG_KEYS));
|
|
11095
11556
|
}
|
|
11096
11557
|
}
|
|
11097
11558
|
function loadJson(file) {
|
|
11098
|
-
return JSON.parse(
|
|
11559
|
+
return JSON.parse(fs24.readFileSync(file, "utf8"));
|
|
11099
11560
|
}
|
|
11100
11561
|
function writeJson(file, value) {
|
|
11101
11562
|
atomicWrite(file, `${JSON.stringify(value, null, 2)}
|
|
@@ -11140,13 +11601,13 @@ function removeManagedRuntimeFiles(paths, options = {}) {
|
|
|
11140
11601
|
...options.preserveGatewayConfig ? [] : [paths.gatewayConfig],
|
|
11141
11602
|
...options.preserveState ? [] : [paths.stateFile],
|
|
11142
11603
|
...managedRuntimeCatalogFiles(paths),
|
|
11143
|
-
|
|
11604
|
+
path17.join(paths.runtimeHome, "catalog-metadata.json"),
|
|
11144
11605
|
paths.modelMergeFile,
|
|
11145
11606
|
paths.stdoutLog,
|
|
11146
|
-
|
|
11147
|
-
|
|
11607
|
+
path17.join(paths.runtimeHome, "webui.log"),
|
|
11608
|
+
path17.join(paths.runtimeHome, LEGACY_STDERR_LOG)
|
|
11148
11609
|
]) {
|
|
11149
|
-
|
|
11610
|
+
fs24.rmSync(file, { force: true });
|
|
11150
11611
|
}
|
|
11151
11612
|
}
|
|
11152
11613
|
async function waitForHealth(url, attempts = 100, expectedMarker = instanceMarker(resolvePaths().runtimeHome)) {
|
|
@@ -11221,7 +11682,7 @@ function recordConfigAudit(command, config, before, paths, extraChanges = []) {
|
|
|
11221
11682
|
}
|
|
11222
11683
|
async function restartGatewayOnce(paths, config) {
|
|
11223
11684
|
const markPending = (pending) => {
|
|
11224
|
-
if (!
|
|
11685
|
+
if (!fs24.existsSync(paths.stateFile))
|
|
11225
11686
|
return;
|
|
11226
11687
|
const state = loadJson(paths.stateFile);
|
|
11227
11688
|
state.pendingRestart = pending;
|
|
@@ -11311,9 +11772,9 @@ async function installCurrent(options, completeMigration) {
|
|
|
11311
11772
|
requireMacOS();
|
|
11312
11773
|
requireBun();
|
|
11313
11774
|
const requestedUpstreamOnly = upstreamOnlyOption(options);
|
|
11314
|
-
const previousConfig =
|
|
11775
|
+
const previousConfig = fs24.existsSync(resolvePaths().gatewayConfig) ? loadGatewayConfig(resolvePaths().gatewayConfig) : undefined;
|
|
11315
11776
|
const upstreamOnly = options["upstream-only"] !== undefined || options["cpa-only"] !== undefined ? requestedUpstreamOnly : previousConfig?.upstreamOnly ?? requestedUpstreamOnly;
|
|
11316
|
-
const priorInstallState =
|
|
11777
|
+
const priorInstallState = fs24.existsSync(resolvePaths().stateFile) ? loadJson(resolvePaths().stateFile) : undefined;
|
|
11317
11778
|
let manualCodexConfig = options["manual-codex-config"] === true || priorInstallState?.codexConfigManaged === false;
|
|
11318
11779
|
if (options["manual-codex-config"] === true && upstreamOnly && resolveCodexConfigConflict({ skipPrompt: options.yes === true }) === "managed") {
|
|
11319
11780
|
manualCodexConfig = false;
|
|
@@ -11321,15 +11782,15 @@ async function installCurrent(options, completeMigration) {
|
|
|
11321
11782
|
}
|
|
11322
11783
|
const paths = resolvePaths();
|
|
11323
11784
|
assertCodexHomeAvailable(paths, manualCodexConfig);
|
|
11324
|
-
const switching =
|
|
11785
|
+
const switching = fs24.existsSync(paths.stateFile);
|
|
11325
11786
|
const portValue = stringOption(options, "port");
|
|
11326
11787
|
const port = portValue ? Number(portValue) : undefined;
|
|
11327
11788
|
if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65535)) {
|
|
11328
11789
|
throw new Error("Invalid port");
|
|
11329
11790
|
}
|
|
11330
|
-
const currentToml =
|
|
11791
|
+
const currentToml = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
|
|
11331
11792
|
const modelMergeJson = stringOption(options, "model-merge-json");
|
|
11332
|
-
const previousGatewayConfig =
|
|
11793
|
+
const previousGatewayConfig = fs24.existsSync(paths.gatewayConfig) ? fs24.readFileSync(paths.gatewayConfig, "utf8") : undefined;
|
|
11333
11794
|
const currentGatewayConfig = previousGatewayConfig === undefined ? {} : loadGatewayConfig(paths.gatewayConfig);
|
|
11334
11795
|
const overrides = {};
|
|
11335
11796
|
if (port !== undefined)
|
|
@@ -11373,7 +11834,7 @@ async function installCurrent(options, completeMigration) {
|
|
|
11373
11834
|
});
|
|
11374
11835
|
const { modelsConfigFile, rules } = await loadModelOverrideRules(paths, config, Boolean(modelMergeJson));
|
|
11375
11836
|
const explicitSelect = stringOption(options, "select") !== undefined;
|
|
11376
|
-
const reuseCatalog = switching && !explicitSelect && !Boolean(modelMergeJson) &&
|
|
11837
|
+
const reuseCatalog = switching && !explicitSelect && !Boolean(modelMergeJson) && fs24.existsSync(configuredCatalogFile(paths, config));
|
|
11377
11838
|
let selectedModels;
|
|
11378
11839
|
const selectNone = isSelectNone(installSelector);
|
|
11379
11840
|
const proxyCatalog = selectNone ? { models: [] } : await fetchUpstreamCatalog(config.upstreamBaseUrl, apiKey, configuredUpstreamType(config), upstreamClientVersion(paths, configuredUpstreamType(config)), newapiCatalogOptions(configuredUpstreamType(config), rules));
|
|
@@ -11396,14 +11857,14 @@ async function installCurrent(options, completeMigration) {
|
|
|
11396
11857
|
}
|
|
11397
11858
|
config.selectedModels = selectedModels;
|
|
11398
11859
|
const { patchedToml: modelCatalogToml } = manualCodexConfig ? { patchedToml: currentToml } : applyModelCatalogToml(currentToml, upstreamOnly, paths, codexCatalogFile(paths), managedRuntimeCatalogFiles(paths));
|
|
11399
|
-
const previousState = switching ?
|
|
11860
|
+
const previousState = switching ? fs24.readFileSync(paths.stateFile, "utf8") : undefined;
|
|
11400
11861
|
const priorState = switching ? loadJson(paths.stateFile) : undefined;
|
|
11401
11862
|
const previousManaged = priorState ? isCodexConfigManaged(priorState) && priorState.uninstallClientRestored !== true : true;
|
|
11402
11863
|
let configBackup;
|
|
11403
11864
|
let rollbackCodexClaim;
|
|
11404
11865
|
let installationTouched = false;
|
|
11405
11866
|
let launchInstalled = false;
|
|
11406
|
-
const previousPlist =
|
|
11867
|
+
const previousPlist = fs24.existsSync(paths.launchAgent) ? fs24.readFileSync(paths.launchAgent, "utf8") : undefined;
|
|
11407
11868
|
const previousServiceLoaded = previousPlist !== undefined && launchAgentStatus(gatewayServiceLabel(paths.instanceSuffix)) !== null;
|
|
11408
11869
|
let launchTouched = false;
|
|
11409
11870
|
let tomlUnchanged = false;
|
|
@@ -11475,7 +11936,7 @@ async function installCurrent(options, completeMigration) {
|
|
|
11475
11936
|
}
|
|
11476
11937
|
writeJson(paths.stateFile, state);
|
|
11477
11938
|
}
|
|
11478
|
-
const cliPath =
|
|
11939
|
+
const cliPath = fs24.realpathSync(process.argv[1]);
|
|
11479
11940
|
launchTouched = true;
|
|
11480
11941
|
installLaunchAgent({
|
|
11481
11942
|
bunPath: process.execPath,
|
|
@@ -11571,7 +12032,7 @@ async function installCurrent(options, completeMigration) {
|
|
|
11571
12032
|
}
|
|
11572
12033
|
console.log("Codex ChatGPT OAuth was not modified.");
|
|
11573
12034
|
if (manualCodexConfig) {
|
|
11574
|
-
const currentTomlAfterInstall =
|
|
12035
|
+
const currentTomlAfterInstall = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
|
|
11575
12036
|
const configuredCatalog = readRootTomlString(currentTomlAfterInstall, "model_catalog_json");
|
|
11576
12037
|
const staticCatalogActive = upstreamOnly;
|
|
11577
12038
|
console.log("WARNING: --manual-codex-config keeps ~/.codex/config.toml untouched. Configure it manually:");
|
|
@@ -11600,7 +12061,7 @@ async function installCurrent(options, completeMigration) {
|
|
|
11600
12061
|
async function uninstall(options) {
|
|
11601
12062
|
requireMacOS();
|
|
11602
12063
|
const paths = resolvePaths();
|
|
11603
|
-
if (!
|
|
12064
|
+
if (!fs24.existsSync(paths.stateFile))
|
|
11604
12065
|
throw new Error("No managed installation found");
|
|
11605
12066
|
const state = loadJson(paths.stateFile);
|
|
11606
12067
|
const restoresClient = isCodexConfigManaged(state) && state.uninstallClientRestored !== true;
|
|
@@ -11608,12 +12069,12 @@ async function uninstall(options) {
|
|
|
11608
12069
|
assertCodexHomeAvailable(paths, false);
|
|
11609
12070
|
uninstallLaunchAgent(paths.launchAgent);
|
|
11610
12071
|
uninstallLaunchAgent(paths.webUiLaunchAgent);
|
|
11611
|
-
const legacyCatalogFile =
|
|
12072
|
+
const legacyCatalogFile = path17.join(paths.codexHome, "cliproxy-catalog.json");
|
|
11612
12073
|
if (restoresClient) {
|
|
11613
|
-
const currentToml =
|
|
12074
|
+
const currentToml = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
|
|
11614
12075
|
restoreManagedCodexToml(paths, state, currentToml);
|
|
11615
12076
|
if (readRootTomlString(currentToml, "model_catalog_json") === legacyCatalogFile) {
|
|
11616
|
-
|
|
12077
|
+
fs24.rmSync(legacyCatalogFile, { force: true });
|
|
11617
12078
|
}
|
|
11618
12079
|
invalidateModelsCache(paths.modelsCacheFile);
|
|
11619
12080
|
state.uninstallClientRestored = true;
|
|
@@ -11622,7 +12083,7 @@ async function uninstall(options) {
|
|
|
11622
12083
|
deleteApiKey();
|
|
11623
12084
|
removeManagedRuntimeFiles(paths, { preserveGatewayConfig: true, preserveState: true });
|
|
11624
12085
|
releaseCodexHome(paths);
|
|
11625
|
-
|
|
12086
|
+
fs24.rmSync(paths.stateFile, { force: true });
|
|
11626
12087
|
if (isCodexConfigManaged(state)) {
|
|
11627
12088
|
console.log("Uninstalled. Managed config.toml values were restored; config.json was preserved.");
|
|
11628
12089
|
} else {
|
|
@@ -11637,7 +12098,7 @@ function configuredSelectedModels(paths, config) {
|
|
|
11637
12098
|
if (Array.isArray(config.selectedModels))
|
|
11638
12099
|
return config.selectedModels;
|
|
11639
12100
|
const catalogFile = configuredCatalogFile(paths, config);
|
|
11640
|
-
if (!
|
|
12101
|
+
if (!fs24.existsSync(catalogFile))
|
|
11641
12102
|
return [];
|
|
11642
12103
|
const prefix = configuredModelPrefix(config);
|
|
11643
12104
|
try {
|
|
@@ -11662,7 +12123,7 @@ async function collectCompatibleModels(config, dependencies = {}, { includeUpstr
|
|
|
11662
12123
|
});
|
|
11663
12124
|
if (includeUpstream) {
|
|
11664
12125
|
try {
|
|
11665
|
-
const proxy = JSON.parse(
|
|
12126
|
+
const proxy = JSON.parse(fs24.readFileSync(configuredCatalogFile(paths, config), "utf8"));
|
|
11666
12127
|
if (!Array.isArray(proxy.models))
|
|
11667
12128
|
throw new Error("catalog file does not contain a models array");
|
|
11668
12129
|
const prefix = config.upstreamOnly === true ? "" : configuredModelPrefix(config);
|
|
@@ -11757,26 +12218,65 @@ async function excludeModels(paths, config, option, restartCodex = false, depend
|
|
|
11757
12218
|
}
|
|
11758
12219
|
async function withModelFileRollback(paths, catalogPath, action) {
|
|
11759
12220
|
const files = [...new Set([paths.gatewayConfig, paths.stateFile, catalogPath, codexCatalogFile(paths), paths.configToml])];
|
|
11760
|
-
const saved = files.map((file) => ({ file, contents:
|
|
12221
|
+
const saved = files.map((file) => ({ file, contents: fs24.existsSync(file) ? fs24.readFileSync(file, "utf8") : undefined }));
|
|
11761
12222
|
try {
|
|
11762
12223
|
return await action();
|
|
11763
12224
|
} catch (error) {
|
|
11764
12225
|
for (const { file, contents } of saved) {
|
|
11765
12226
|
if (contents !== undefined) {
|
|
11766
|
-
if (!
|
|
12227
|
+
if (!fs24.existsSync(file) || fs24.readFileSync(file, "utf8") !== contents)
|
|
11767
12228
|
atomicWrite(file, contents);
|
|
11768
|
-
} else if (
|
|
11769
|
-
|
|
12229
|
+
} else if (fs24.existsSync(file))
|
|
12230
|
+
fs24.rmSync(file, { force: true });
|
|
11770
12231
|
}
|
|
11771
12232
|
throw error;
|
|
11772
12233
|
}
|
|
11773
12234
|
}
|
|
12235
|
+
async function runModelsPush(targetOption, config, dependencies = {}) {
|
|
12236
|
+
const targets = [
|
|
12237
|
+
createZcodePushTarget({ config }),
|
|
12238
|
+
createDshPushTarget({ config })
|
|
12239
|
+
];
|
|
12240
|
+
if (targetOption !== undefined) {
|
|
12241
|
+
const known = targets.map((target) => target.id);
|
|
12242
|
+
for (const id of [...new Set(targetOption.split(",").map((value) => value.trim().toLowerCase()).filter(Boolean))]) {
|
|
12243
|
+
if (!known.includes(id))
|
|
12244
|
+
throw new Error(`unknown --push target "${id}"; available targets: ${known.join(", ")}`);
|
|
12245
|
+
}
|
|
12246
|
+
}
|
|
12247
|
+
const { entries, failures } = await collectAdapterModels(config, {}, { refresh: false });
|
|
12248
|
+
const scoped = filterExcludedModels({ models: [...entries] }, config.excludedModels, isInstalledPluginModel).models;
|
|
12249
|
+
if (scoped.length === 0) {
|
|
12250
|
+
throw new Error("model push aborted; no local adapter models to push" + " (enable an adapter via config --zcode on / --codebuddy on / \u2026 and refresh its catalog first)");
|
|
12251
|
+
}
|
|
12252
|
+
const input = dependencies.input ?? process.stdin;
|
|
12253
|
+
const output = dependencies.output ?? process.stdout;
|
|
12254
|
+
if (!input.isTTY || !output.isTTY) {
|
|
12255
|
+
throw new Error("models --push requires an interactive terminal to confirm the model list" + " (every model is preselected and Enter pushes them all)");
|
|
12256
|
+
}
|
|
12257
|
+
const selectedSlugs = await chooseModels({
|
|
12258
|
+
availableModels: scoped,
|
|
12259
|
+
currentSelection: scoped.map((model) => model.slug),
|
|
12260
|
+
requireNonEmpty: true,
|
|
12261
|
+
input,
|
|
12262
|
+
output,
|
|
12263
|
+
title: "Local plugin models - space toggles, Enter pushes"
|
|
12264
|
+
});
|
|
12265
|
+
const selectedSet = new Set(selectedSlugs);
|
|
12266
|
+
await pushModelsToTargets({
|
|
12267
|
+
targetOption,
|
|
12268
|
+
entries: scoped.filter((model) => selectedSet.has(model.slug)),
|
|
12269
|
+
failures,
|
|
12270
|
+
config,
|
|
12271
|
+
targets
|
|
12272
|
+
});
|
|
12273
|
+
}
|
|
11774
12274
|
async function models(options) {
|
|
11775
12275
|
const restartCodex = options["restart-codex"] === true;
|
|
11776
12276
|
const selector = stringOption(options, "select");
|
|
11777
12277
|
const modelMergeJson = stringOption(options, "model-merge-json");
|
|
11778
12278
|
const paths = resolvePaths();
|
|
11779
|
-
if (!
|
|
12279
|
+
if (!fs24.existsSync(paths.gatewayConfig))
|
|
11780
12280
|
throw new Error("Gateway is not installed");
|
|
11781
12281
|
const config = loadGatewayConfig(paths.gatewayConfig);
|
|
11782
12282
|
const auditBefore = { ...config };
|
|
@@ -11784,22 +12284,31 @@ async function models(options) {
|
|
|
11784
12284
|
if (modelMergeJson)
|
|
11785
12285
|
config.modelMergeJson = modelMergeJson;
|
|
11786
12286
|
const currentSelection = configuredSelectedModels(paths, config);
|
|
12287
|
+
const pushDeferred = async () => {
|
|
12288
|
+
if (options.push !== undefined) {
|
|
12289
|
+
await runModelsPush(options.push === true ? undefined : String(options.push), config);
|
|
12290
|
+
}
|
|
12291
|
+
};
|
|
11787
12292
|
if (options.exclude !== undefined) {
|
|
11788
12293
|
if (options.sync === true)
|
|
11789
12294
|
throw new Error("--exclude cannot be combined with --sync");
|
|
11790
12295
|
if (options.select !== undefined)
|
|
11791
12296
|
throw new Error("--select cannot be combined with --exclude");
|
|
11792
12297
|
await excludeModels(paths, config, options.exclude, restartCodex);
|
|
12298
|
+
await pushDeferred();
|
|
11793
12299
|
return;
|
|
11794
12300
|
}
|
|
11795
12301
|
if (options.sync !== true) {
|
|
11796
|
-
|
|
11797
|
-
|
|
12302
|
+
if (options.push === undefined) {
|
|
12303
|
+
printCurrentModels(config, currentSelection);
|
|
12304
|
+
await printCompatibleModels(config);
|
|
12305
|
+
}
|
|
12306
|
+
await pushDeferred();
|
|
11798
12307
|
return;
|
|
11799
12308
|
}
|
|
11800
|
-
const manualCodexConfig =
|
|
11801
|
-
const source =
|
|
11802
|
-
const legacyCatalogFile =
|
|
12309
|
+
const manualCodexConfig = fs24.existsSync(paths.stateFile) && !isCodexConfigManaged(loadJson(paths.stateFile));
|
|
12310
|
+
const source = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
|
|
12311
|
+
const legacyCatalogFile = path17.join(paths.codexHome, "cliproxy-catalog.json");
|
|
11803
12312
|
if (!manualCodexConfig) {
|
|
11804
12313
|
applyModelCatalogToml(source, upstreamOnly, paths, codexCatalogFile(paths), managedRuntimeCatalogFiles(paths));
|
|
11805
12314
|
}
|
|
@@ -11824,7 +12333,7 @@ async function models(options) {
|
|
|
11824
12333
|
const result2 = await rebuildCatalog(paths, config, selectedProxyModels, modelsConfigFile);
|
|
11825
12334
|
config.selectedModels = selectedModels;
|
|
11826
12335
|
writeGatewayConfig(paths.gatewayConfig, config);
|
|
11827
|
-
if (
|
|
12336
|
+
if (fs24.existsSync(paths.stateFile)) {
|
|
11828
12337
|
const state = loadJson(paths.stateFile);
|
|
11829
12338
|
state.config = config;
|
|
11830
12339
|
writeJson(paths.stateFile, state);
|
|
@@ -11845,16 +12354,17 @@ async function models(options) {
|
|
|
11845
12354
|
} else {
|
|
11846
12355
|
console.log("Dynamic catalog synced; Codex refreshes /models periodically, but the current model picker may require --restart-codex.");
|
|
11847
12356
|
}
|
|
11848
|
-
|
|
12357
|
+
} else {
|
|
12358
|
+
await refreshCodexAppServer();
|
|
11849
12359
|
}
|
|
11850
|
-
await
|
|
12360
|
+
await pushDeferred();
|
|
11851
12361
|
}
|
|
11852
12362
|
async function status() {
|
|
11853
12363
|
const paths = resolvePaths();
|
|
11854
|
-
const installed =
|
|
11855
|
-
const config =
|
|
12364
|
+
const installed = fs24.existsSync(paths.stateFile);
|
|
12365
|
+
const config = fs24.existsSync(paths.gatewayConfig) ? loadGatewayConfig(paths.gatewayConfig) : undefined;
|
|
11856
12366
|
const service = process.platform === "darwin" ? launchAgentStatus(gatewayServiceLabel(paths.instanceSuffix)) : null;
|
|
11857
|
-
const source =
|
|
12367
|
+
const source = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
|
|
11858
12368
|
const configuredBaseUrl = readRootTomlString(source, "openai_base_url");
|
|
11859
12369
|
const configuredCatalog = readRootTomlString(source, "model_catalog_json");
|
|
11860
12370
|
const configuredRealtimeWsBaseUrl = readRootTomlString(source, "experimental_realtime_ws_base_url");
|
|
@@ -11882,7 +12392,7 @@ async function status() {
|
|
|
11882
12392
|
modelCatalogJson: configuredCatalog,
|
|
11883
12393
|
authJsonModified: false,
|
|
11884
12394
|
cachedCatalogPath: configuredCatalogFile(paths, config ?? {}),
|
|
11885
|
-
cachedCatalogPresent:
|
|
12395
|
+
cachedCatalogPresent: fs24.existsSync(configuredCatalogFile(paths, config ?? {})),
|
|
11886
12396
|
upstreamOnly: config?.upstreamOnly === true,
|
|
11887
12397
|
upstreamType: config?.upstreamType ?? "cliproxy",
|
|
11888
12398
|
codexClientVersion: resolveCodexClientVersion(paths.upstreamModelsCacheFile)
|
|
@@ -11892,10 +12402,10 @@ function serve(options) {
|
|
|
11892
12402
|
requireBun();
|
|
11893
12403
|
const paths = resolvePaths();
|
|
11894
12404
|
const configPath = realPathOrResolve(stringOption(options, "config") || paths.gatewayConfig);
|
|
11895
|
-
if (!
|
|
12405
|
+
if (!fs24.existsSync(configPath))
|
|
11896
12406
|
throw new Error(`Gateway config not found: ${configPath}`);
|
|
11897
12407
|
const config = loadGatewayConfig(configPath);
|
|
11898
|
-
const isProductionInstance = configPath === realPathOrResolve(
|
|
12408
|
+
const isProductionInstance = configPath === realPathOrResolve(path17.join(paths.runtimeHome, "config.json"));
|
|
11899
12409
|
const processLog = isProductionInstance ? { file: paths.stdoutLog, maxBytes: config.maxGatewayLogBytes ?? 0 } : undefined;
|
|
11900
12410
|
startGateway(config, loadRealtimeProviderMode(paths.configToml), paths.upstreamModelsCacheFile, { codexModelsCacheFile: paths.modelsCacheFile }, processLog, { codexModelsCacheFile: paths.modelsCacheFile }, { codexModelsCacheFile: paths.modelsCacheFile }, { codexModelsCacheFile: paths.modelsCacheFile }, { codexModelsCacheFile: paths.modelsCacheFile });
|
|
11901
12411
|
if (isProductionInstance)
|
|
@@ -11927,7 +12437,7 @@ async function waitForWebUi(port, timeoutMs = 1e4) {
|
|
|
11927
12437
|
throw new Error(`Web UI did not come up on port ${port} within ${timeoutMs / 1000}s`);
|
|
11928
12438
|
}
|
|
11929
12439
|
function webUiLogPath(paths) {
|
|
11930
|
-
return
|
|
12440
|
+
return path17.join(paths.runtimeHome, "webui.log");
|
|
11931
12441
|
}
|
|
11932
12442
|
function runWebUiForeground(paths, config, openBrowser) {
|
|
11933
12443
|
const ctx = webUiContextForInstance(paths.gatewayConfig, paths);
|
|
@@ -11963,7 +12473,7 @@ async function ensureGatewayRunning(paths, config) {
|
|
|
11963
12473
|
async function startWebUiService(paths, config) {
|
|
11964
12474
|
startWebUiLaunchAgent(paths.webUiLaunchAgent, {
|
|
11965
12475
|
bunPath: process.execPath,
|
|
11966
|
-
cliPath:
|
|
12476
|
+
cliPath: fs24.realpathSync(process.argv[1]),
|
|
11967
12477
|
codexHome: paths.codexHome,
|
|
11968
12478
|
logPath: webUiLogPath(paths),
|
|
11969
12479
|
label: webUiServiceLabel(paths.instanceSuffix),
|
|
@@ -11979,7 +12489,7 @@ async function webCommand(options) {
|
|
|
11979
12489
|
if (process.env.CODEX_CLIPROXY_UI_SERVICE === "1") {
|
|
11980
12490
|
requireBun();
|
|
11981
12491
|
const paths2 = resolvePaths();
|
|
11982
|
-
if (!
|
|
12492
|
+
if (!fs24.existsSync(paths2.gatewayConfig)) {
|
|
11983
12493
|
throw new Error(`Gateway config not found: ${paths2.gatewayConfig}`);
|
|
11984
12494
|
}
|
|
11985
12495
|
const config2 = loadGatewayConfig(paths2.gatewayConfig);
|
|
@@ -11991,7 +12501,7 @@ async function webCommand(options) {
|
|
|
11991
12501
|
}
|
|
11992
12502
|
requireMacOS();
|
|
11993
12503
|
const paths = resolvePaths();
|
|
11994
|
-
if (!
|
|
12504
|
+
if (!fs24.existsSync(paths.stateFile))
|
|
11995
12505
|
throw new Error("Gateway is not installed");
|
|
11996
12506
|
const config = loadGatewayConfig(paths.gatewayConfig);
|
|
11997
12507
|
if (!isLoopbackHost(config.host)) {
|
|
@@ -12067,7 +12577,7 @@ async function promptCodebuddyAccountSelection(dependencies = {}) {
|
|
|
12067
12577
|
const items = [
|
|
12068
12578
|
{
|
|
12069
12579
|
value: "auto",
|
|
12070
|
-
label:
|
|
12580
|
+
label: current ? `Auto(${current.label})` : "Auto",
|
|
12071
12581
|
selectable: true
|
|
12072
12582
|
},
|
|
12073
12583
|
...entries.filter((entry) => entry.file !== resolved).map((entry) => ({
|
|
@@ -12117,7 +12627,7 @@ async function writeConfigAndRestart(paths, config, auditBefore, command, applie
|
|
|
12117
12627
|
validateConfiguredPlugins(config);
|
|
12118
12628
|
const persist = async () => {
|
|
12119
12629
|
writeGatewayConfig(paths.gatewayConfig, config);
|
|
12120
|
-
if (
|
|
12630
|
+
if (fs24.existsSync(paths.stateFile)) {
|
|
12121
12631
|
const state = loadJson(paths.stateFile);
|
|
12122
12632
|
state.config = config;
|
|
12123
12633
|
writeJson(paths.stateFile, state);
|
|
@@ -12141,7 +12651,7 @@ async function writeConfigAndRestart(paths, config, auditBefore, command, applie
|
|
|
12141
12651
|
console.log(result.loaded ? "Gateway model configuration reloaded." : "Models saved; the gateway will load them when started.");
|
|
12142
12652
|
return;
|
|
12143
12653
|
}
|
|
12144
|
-
if (
|
|
12654
|
+
if (fs24.existsSync(paths.launchAgent)) {
|
|
12145
12655
|
await restartGatewayOnce(paths, config);
|
|
12146
12656
|
console.log("Gateway restarted to apply the new configuration.");
|
|
12147
12657
|
} else {
|
|
@@ -12161,7 +12671,7 @@ async function configCommand(options) {
|
|
|
12161
12671
|
const maxLogsOption = stringOption(options, "max-request-logs");
|
|
12162
12672
|
const maxLogSizeOption = stringOption(options, "max-log-size");
|
|
12163
12673
|
const paths = resolvePaths();
|
|
12164
|
-
if (!
|
|
12674
|
+
if (!fs24.existsSync(paths.gatewayConfig))
|
|
12165
12675
|
throw new Error("Gateway is not installed");
|
|
12166
12676
|
const config = loadGatewayConfig(paths.gatewayConfig);
|
|
12167
12677
|
const auditBefore = { ...config };
|
|
@@ -12201,9 +12711,9 @@ async function configCommand(options) {
|
|
|
12201
12711
|
config.upstreamOnly = upstreamOnlyTarget;
|
|
12202
12712
|
applied.push(`Default upstream-only routing ${upstreamOnlyTarget ? "enabled" : "disabled"}.`);
|
|
12203
12713
|
}
|
|
12204
|
-
const managesCodex = !
|
|
12714
|
+
const managesCodex = !fs24.existsSync(paths.stateFile) || isCodexConfigManaged(loadJson(paths.stateFile));
|
|
12205
12715
|
if (managesCodex) {
|
|
12206
|
-
const source =
|
|
12716
|
+
const source = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
|
|
12207
12717
|
applyModelCatalogToml(source, config.upstreamOnly === true, paths, codexCatalogFile(paths), managedRuntimeCatalogFiles(paths));
|
|
12208
12718
|
}
|
|
12209
12719
|
if (zcodeTarget !== undefined) {
|
|
@@ -12242,8 +12752,8 @@ async function configCommand(options) {
|
|
|
12242
12752
|
config.debug = debugTarget;
|
|
12243
12753
|
applied.push(`Debug dump ${debugTarget ? "enabled" : "disabled"} ` + `(upstream error request bodies go to: ${requestLogDir(paths)}).`);
|
|
12244
12754
|
}
|
|
12245
|
-
const previousConfig =
|
|
12246
|
-
const previousState =
|
|
12755
|
+
const previousConfig = fs24.readFileSync(paths.gatewayConfig, "utf8");
|
|
12756
|
+
const previousState = fs24.existsSync(paths.stateFile) ? fs24.readFileSync(paths.stateFile, "utf8") : undefined;
|
|
12247
12757
|
try {
|
|
12248
12758
|
await withModelFileRollback(paths, configuredCatalogFile(paths, config), async () => {
|
|
12249
12759
|
await writeConfigAndRestart(paths, config, auditBefore, "config", applied, {
|
|
@@ -12252,14 +12762,14 @@ async function configCommand(options) {
|
|
|
12252
12762
|
});
|
|
12253
12763
|
const changesModels = upstreamOnlyTarget !== undefined || zcodeTarget !== undefined || codebuddyTarget !== undefined || qoderTarget !== undefined || agyTarget !== undefined || opencodeZenTarget !== undefined;
|
|
12254
12764
|
if (changesModels) {
|
|
12255
|
-
await updateCodexModelCatalog2(paths, config, {}, { refreshAdapters: !
|
|
12765
|
+
await updateCodexModelCatalog2(paths, config, {}, { refreshAdapters: !fs24.existsSync(paths.launchAgent) });
|
|
12256
12766
|
}
|
|
12257
12767
|
});
|
|
12258
12768
|
} catch (error) {
|
|
12259
12769
|
atomicWrite(paths.gatewayConfig, previousConfig);
|
|
12260
12770
|
if (previousState !== undefined)
|
|
12261
12771
|
atomicWrite(paths.stateFile, previousState);
|
|
12262
|
-
if (
|
|
12772
|
+
if (fs24.existsSync(paths.launchAgent)) {
|
|
12263
12773
|
try {
|
|
12264
12774
|
await restartGatewayOnce(paths, loadGatewayConfig(paths.gatewayConfig));
|
|
12265
12775
|
} catch {
|
|
@@ -12274,7 +12784,7 @@ async function configCommand(options) {
|
|
|
12274
12784
|
async function codebuddyCommand(dependencies = {}) {
|
|
12275
12785
|
requireMacOS();
|
|
12276
12786
|
const paths = resolvePaths();
|
|
12277
|
-
if (!
|
|
12787
|
+
if (!fs24.existsSync(paths.gatewayConfig))
|
|
12278
12788
|
throw new Error("Gateway is not installed");
|
|
12279
12789
|
const config = loadGatewayConfig(paths.gatewayConfig);
|
|
12280
12790
|
const auditBefore = { ...config };
|
|
@@ -12283,6 +12793,10 @@ async function codebuddyCommand(dependencies = {}) {
|
|
|
12283
12793
|
console.log("CodeBuddy account switch cancelled; no account change was saved.");
|
|
12284
12794
|
return;
|
|
12285
12795
|
}
|
|
12796
|
+
if (selection === (config.codebuddyAccount ?? "auto")) {
|
|
12797
|
+
console.log("CodeBuddy account unchanged; nothing to save.");
|
|
12798
|
+
return;
|
|
12799
|
+
}
|
|
12286
12800
|
config.codebuddyAccount = selection;
|
|
12287
12801
|
await writeConfigAndRestart(paths, config, auditBefore, "codebuddy --switch", [
|
|
12288
12802
|
codebuddyAccountApplied(selection)
|
|
@@ -12291,7 +12805,7 @@ async function codebuddyCommand(dependencies = {}) {
|
|
|
12291
12805
|
async function controlGateway(action, restartCodex = false) {
|
|
12292
12806
|
requireMacOS();
|
|
12293
12807
|
const paths = resolvePaths();
|
|
12294
|
-
if (!
|
|
12808
|
+
if (!fs24.existsSync(paths.stateFile))
|
|
12295
12809
|
throw new Error("Gateway is not installed");
|
|
12296
12810
|
if (action === "stop") {
|
|
12297
12811
|
stopLaunchAgent(paths.launchAgent);
|
|
@@ -12303,7 +12817,7 @@ async function controlGateway(action, restartCodex = false) {
|
|
|
12303
12817
|
if (action === "start") {
|
|
12304
12818
|
startLaunchAgent(paths.launchAgent, gatewayServiceLabel(paths.instanceSuffix));
|
|
12305
12819
|
} else {
|
|
12306
|
-
const source =
|
|
12820
|
+
const source = fs24.existsSync(paths.configToml) ? fs24.readFileSync(paths.configToml, "utf8") : "";
|
|
12307
12821
|
const gatewayBaseUrl = `http://${config.host}:${config.port}${MOUNT_PATH}`;
|
|
12308
12822
|
const state = loadJson(paths.stateFile);
|
|
12309
12823
|
if (isCodexConfigManaged(state)) {
|
|
@@ -12335,15 +12849,15 @@ function ownsManagedCodexConfig(paths, state, legacyBaseUrl) {
|
|
|
12335
12849
|
const owner = codexOwnerRuntimeHome(paths);
|
|
12336
12850
|
if (owner !== undefined)
|
|
12337
12851
|
return owner === realPathOrResolve(paths.runtimeHome);
|
|
12338
|
-
if (!
|
|
12852
|
+
if (!fs24.existsSync(paths.configToml))
|
|
12339
12853
|
return false;
|
|
12340
|
-
const currentUrl = readRootTomlString(
|
|
12854
|
+
const currentUrl = readRootTomlString(fs24.readFileSync(paths.configToml, "utf8"), "openai_base_url");
|
|
12341
12855
|
return currentUrl === state.gatewayBaseUrl || currentUrl === legacyBaseUrl;
|
|
12342
12856
|
}
|
|
12343
12857
|
function syncGatewayConfigFile(paths, configFile = paths.gatewayConfig) {
|
|
12344
|
-
if (!
|
|
12858
|
+
if (!fs24.existsSync(configFile))
|
|
12345
12859
|
return;
|
|
12346
|
-
const managesState = realPathOrResolve(configFile) === realPathOrResolve(
|
|
12860
|
+
const managesState = realPathOrResolve(configFile) === realPathOrResolve(path17.join(paths.runtimeHome, "config.json"));
|
|
12347
12861
|
const raw = loadJson(configFile);
|
|
12348
12862
|
if (!isJsonObject(raw))
|
|
12349
12863
|
throw new Error(`Gateway config must be a JSON object: ${configFile}`);
|
|
@@ -12384,11 +12898,11 @@ function syncGatewayConfigFile(paths, configFile = paths.gatewayConfig) {
|
|
|
12384
12898
|
if (legacyMount && legacyMount !== MOUNT_PATH) {
|
|
12385
12899
|
const gatewayBaseUrl = `http://${current.host}:${current.port}${MOUNT_PATH}`;
|
|
12386
12900
|
const legacyBaseUrl = `http://${current.host}:${current.port}${legacyMount}`;
|
|
12387
|
-
const state = managesState &&
|
|
12901
|
+
const state = managesState && fs24.existsSync(paths.stateFile) ? loadJson(paths.stateFile) : undefined;
|
|
12388
12902
|
if (state?.uninstallClientRestored === true) {} else if (state && isCodexConfigManaged(state)) {
|
|
12389
12903
|
if (ownsManagedCodexConfig(paths, state, legacyBaseUrl)) {
|
|
12390
|
-
if (
|
|
12391
|
-
const source =
|
|
12904
|
+
if (fs24.existsSync(paths.configToml)) {
|
|
12905
|
+
const source = fs24.readFileSync(paths.configToml, "utf8");
|
|
12392
12906
|
const patched = managedCodexServiceToml(source, gatewayBaseUrl);
|
|
12393
12907
|
if (patched !== source)
|
|
12394
12908
|
atomicWrite(paths.configToml, patched);
|
|
@@ -12416,7 +12930,7 @@ function syncGatewayConfigFile(paths, configFile = paths.gatewayConfig) {
|
|
|
12416
12930
|
if (current.configVersion === GATEWAY_CONFIG_VERSION) {
|
|
12417
12931
|
if (dirty) {
|
|
12418
12932
|
writeGatewayConfig(configFile, current);
|
|
12419
|
-
if (managesState &&
|
|
12933
|
+
if (managesState && fs24.existsSync(paths.stateFile)) {
|
|
12420
12934
|
const state = loadJson(paths.stateFile);
|
|
12421
12935
|
state.config = current;
|
|
12422
12936
|
writeJson(paths.stateFile, state);
|
|
@@ -12436,7 +12950,7 @@ function syncGatewayConfigFile(paths, configFile = paths.gatewayConfig) {
|
|
|
12436
12950
|
});
|
|
12437
12951
|
}
|
|
12438
12952
|
writeGatewayConfig(configFile, merged.config);
|
|
12439
|
-
if (managesState &&
|
|
12953
|
+
if (managesState && fs24.existsSync(paths.stateFile)) {
|
|
12440
12954
|
const state = loadJson(paths.stateFile);
|
|
12441
12955
|
state.version = 4;
|
|
12442
12956
|
state.config = merged.config;
|
|
@@ -12455,7 +12969,7 @@ var COMMAND_OPTIONS = {
|
|
|
12455
12969
|
uninstall: ["restart-codex"],
|
|
12456
12970
|
restart: ["restart-codex"],
|
|
12457
12971
|
serve: ["config"],
|
|
12458
|
-
models: ["sync", "select", "restart-codex", "model-merge-json", "exclude"],
|
|
12972
|
+
models: ["sync", "select", "restart-codex", "model-merge-json", "exclude", "push"],
|
|
12459
12973
|
config: ["upstream-only", "restart-codex", "zcode", "codebuddy", "qoder", "agy", "opencode-zen", "log", "debug", "max-request-logs", "max-log-size"],
|
|
12460
12974
|
codebuddy: ["switch"],
|
|
12461
12975
|
web: ["start", "daemon", "status", "stop", "restart"]
|
|
@@ -12506,18 +13020,18 @@ async function runCli(args) {
|
|
|
12506
13020
|
}
|
|
12507
13021
|
const configOption = command === "serve" ? stringOption(options, "config") : undefined;
|
|
12508
13022
|
const configPath = configOption ? realPathOrResolve(configOption) : undefined;
|
|
12509
|
-
let paths = configPath ? { ...resolvePaths(process.env,
|
|
13023
|
+
let paths = configPath ? { ...resolvePaths(process.env, path17.dirname(configPath)), gatewayConfig: configPath } : resolvePaths();
|
|
12510
13024
|
const migration = command === "install" ? prepareDefaultInstallMigration(paths) : undefined;
|
|
12511
13025
|
paths = migration?.paths ?? resolveInstalledCodexPaths(paths);
|
|
12512
13026
|
return runWithInstancePaths(paths, async () => {
|
|
12513
|
-
if (
|
|
13027
|
+
if (fs24.existsSync(paths.stateFile)) {
|
|
12514
13028
|
const state = loadJson(paths.stateFile);
|
|
12515
13029
|
if (state.uninstallClientRestored === true && !["install", "uninstall", "status"].includes(command)) {
|
|
12516
13030
|
throw new Error("An uninstall is unfinished; rerun uninstall or install --yes before managing this instance");
|
|
12517
13031
|
}
|
|
12518
13032
|
}
|
|
12519
13033
|
const writesClient = command === "restart" || command === "codebuddy" || command === "models" && (options.sync === true || options.exclude !== undefined) || command === "config" && Object.keys(options).length > 0;
|
|
12520
|
-
if (writesClient &&
|
|
13034
|
+
if (writesClient && fs24.existsSync(paths.stateFile)) {
|
|
12521
13035
|
const state = loadJson(paths.stateFile);
|
|
12522
13036
|
if (isCodexConfigManaged(state))
|
|
12523
13037
|
assertCodexHomeAvailable(paths, false);
|