proxitor 0.10.1 → 0.12.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 +10 -0
- package/dist/cli.mjs +471 -181
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -4,10 +4,10 @@ import process$1, { stdin, stdout } from "node:process";
|
|
|
4
4
|
import os, { homedir } from "node:os";
|
|
5
5
|
import * as tty$1 from "node:tty";
|
|
6
6
|
import tty, { ReadStream } from "node:tty";
|
|
7
|
-
import { formatWithOptions, styleText } from "node:util";
|
|
7
|
+
import { formatWithOptions, isDeepStrictEqual, styleText } from "node:util";
|
|
8
8
|
import * as l$1 from "node:readline";
|
|
9
9
|
import l__default from "node:readline";
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
|
|
11
11
|
import { dirname, join, resolve, sep } from "node:path";
|
|
12
12
|
import { createServer } from "node:net";
|
|
13
13
|
import { STATUS_CODES, createServer as createServer$1 } from "node:http";
|
|
@@ -16491,18 +16491,29 @@ function findConfigFile(explicitPath) {
|
|
|
16491
16491
|
if (found) return found;
|
|
16492
16492
|
throw new MissingConfigError(getConfigSearchPaths());
|
|
16493
16493
|
}
|
|
16494
|
-
function
|
|
16494
|
+
function parseConfigFile(filePath) {
|
|
16495
16495
|
const content = readFileSync(filePath, "utf-8");
|
|
16496
|
-
let raw;
|
|
16497
16496
|
try {
|
|
16498
|
-
|
|
16497
|
+
return filePath.endsWith(".json") ? JSON.parse(content) : (0, import_dist.parse)(content);
|
|
16499
16498
|
} catch (err) {
|
|
16500
16499
|
throw new ConfigParseError(filePath, err instanceof Error ? err : void 0);
|
|
16501
16500
|
}
|
|
16502
|
-
|
|
16501
|
+
}
|
|
16502
|
+
function readConfigFile(filePath) {
|
|
16503
|
+
const result = proxyConfigFileSchema.safeParse(parseConfigFile(filePath));
|
|
16503
16504
|
if (!result.success) throw new ConfigValidationError(filePath, result.error);
|
|
16504
16505
|
return result.data;
|
|
16505
16506
|
}
|
|
16507
|
+
/**
|
|
16508
|
+
* Validated like readConfigFile, but schema defaults are NOT applied, so absent
|
|
16509
|
+
* keys stay undefined — use when "absent" must differ from "explicit default".
|
|
16510
|
+
*/
|
|
16511
|
+
function readConfigFileRaw(filePath) {
|
|
16512
|
+
const raw = parseConfigFile(filePath);
|
|
16513
|
+
const result = proxyConfigFileSchema.safeParse(raw);
|
|
16514
|
+
if (!result.success) throw new ConfigValidationError(filePath, result.error);
|
|
16515
|
+
return raw ?? {};
|
|
16516
|
+
}
|
|
16506
16517
|
//#endregion
|
|
16507
16518
|
//#region src/openrouter/cache.ts
|
|
16508
16519
|
const CACHE_DIR = join(homedir(), ".cache", "proxitor");
|
|
@@ -17938,7 +17949,7 @@ async function askNormalizeVolatileSystem(message, current, opts) {
|
|
|
17938
17949
|
});
|
|
17939
17950
|
return await select({
|
|
17940
17951
|
message,
|
|
17941
|
-
initialValue: current ?? false,
|
|
17952
|
+
initialValue: current ?? (opts?.removable ? "reset" : false),
|
|
17942
17953
|
options
|
|
17943
17954
|
});
|
|
17944
17955
|
}
|
|
@@ -17967,7 +17978,7 @@ async function askTriState(message, current, hints, opts) {
|
|
|
17967
17978
|
});
|
|
17968
17979
|
return await select({
|
|
17969
17980
|
message,
|
|
17970
|
-
initialValue: current ?? "auto",
|
|
17981
|
+
initialValue: current ?? (opts?.removable ? "reset" : "auto"),
|
|
17971
17982
|
options
|
|
17972
17983
|
});
|
|
17973
17984
|
}
|
|
@@ -18003,7 +18014,7 @@ async function askCacheControlTtl(current, opts) {
|
|
|
18003
18014
|
});
|
|
18004
18015
|
return await select({
|
|
18005
18016
|
message: "Cache TTL",
|
|
18006
|
-
initialValue: current ?? "5m",
|
|
18017
|
+
initialValue: current ?? (opts?.removable ? "reset" : "5m"),
|
|
18007
18018
|
options
|
|
18008
18019
|
});
|
|
18009
18020
|
}
|
|
@@ -18016,14 +18027,14 @@ function applyField(obj, key, field) {
|
|
|
18016
18027
|
else obj[key] = field.value;
|
|
18017
18028
|
}
|
|
18018
18029
|
async function collectSessionTriState(currentSid) {
|
|
18019
|
-
const sid = await askTriState("Session ID mode", currentSid
|
|
18030
|
+
const sid = await askTriState("Session ID mode", currentSid, SESSION_HINTS, { removable: true });
|
|
18020
18031
|
if (typeof sid === "symbol") return null;
|
|
18021
18032
|
if (sid === "reset") return { sessionId: { remove: true } };
|
|
18022
18033
|
return { sessionId: { value: sid } };
|
|
18023
18034
|
}
|
|
18024
18035
|
/** TTL is independent of cache mode. Mode-cancel aborts; TTL-cancel keeps existing TTL. */
|
|
18025
18036
|
async function collectCacheTriState(currentCc, currentTtl, globalTtl) {
|
|
18026
|
-
const cc = await askTriState("Cache control mode", currentCc
|
|
18037
|
+
const cc = await askTriState("Cache control mode", currentCc, CACHE_HINTS, { removable: true });
|
|
18027
18038
|
if (typeof cc === "symbol") return null;
|
|
18028
18039
|
let cacheControl;
|
|
18029
18040
|
if (cc === "reset") cacheControl = { remove: true };
|
|
@@ -18318,30 +18329,116 @@ async function browseModelsCommand(client) {
|
|
|
18318
18329
|
});
|
|
18319
18330
|
}
|
|
18320
18331
|
//#endregion
|
|
18321
|
-
//#region src/commands/config/
|
|
18322
|
-
function
|
|
18323
|
-
|
|
18332
|
+
//#region src/commands/config/cache-control.ts
|
|
18333
|
+
async function cacheControlCommand(opts) {
|
|
18334
|
+
const configPath = requireConfigPath(opts?.configPath);
|
|
18335
|
+
const cfg = readConfigFileRaw(configPath);
|
|
18336
|
+
const rawCc = cfg.cacheControl;
|
|
18337
|
+
const rawTtl = cfg.cacheControlTtl;
|
|
18338
|
+
const effectiveCc = rawCc ?? DEFAULTS.cacheControl;
|
|
18339
|
+
log.info(`Current: cacheControl = ${rawCc === void 0 ? `(default -> ${effectiveCc})` : effectiveCc}`);
|
|
18340
|
+
if (rawTtl) log.info(`Current: cacheControlTtl = ${rawTtl}`);
|
|
18341
|
+
const cc = await askTriState("Cache control mode", rawCc, CACHE_HINTS, { removable: true });
|
|
18342
|
+
if (typeof cc === "symbol") return;
|
|
18343
|
+
const fields = {};
|
|
18344
|
+
fields.cacheControl = cc === "reset" ? void 0 : cc;
|
|
18345
|
+
const ttlResult = await askCacheControlTtl(rawTtl, { removable: true });
|
|
18346
|
+
if (typeof ttlResult === "symbol") {
|
|
18347
|
+
setGlobalConfigFields(configPath, fields);
|
|
18348
|
+
log.success(`cacheControl set to ${cc === "reset" ? "(default)" : cc}`);
|
|
18349
|
+
return;
|
|
18350
|
+
}
|
|
18351
|
+
fields.cacheControlTtl = ttlResult === "reset" ? void 0 : ttlResult;
|
|
18352
|
+
setGlobalConfigFields(configPath, fields);
|
|
18353
|
+
const ccLabel = cc === "reset" ? "(default)" : cc;
|
|
18354
|
+
const ttlLabel = ttlResult === "reset" ? "(default)" : ttlResult;
|
|
18355
|
+
log.success(`cacheControl set to ${ccLabel}, TTL = ${ttlLabel}`);
|
|
18356
|
+
}
|
|
18357
|
+
//#endregion
|
|
18358
|
+
//#region src/commands/config/caching-summary.ts
|
|
18359
|
+
/** Friendly TTL label (omit→strip, skip→passthrough). */
|
|
18360
|
+
function describeTtl(value) {
|
|
18361
|
+
if (value === void 0) return "(default)";
|
|
18362
|
+
if (value === "omit") return "strip";
|
|
18363
|
+
if (value === "skip") return "passthrough";
|
|
18364
|
+
return value;
|
|
18365
|
+
}
|
|
18366
|
+
/** Global NVS: distinguish absent (default-origin) from an explicit on/off. */
|
|
18367
|
+
function globalNvsLabel(value) {
|
|
18368
|
+
if (value === void 0) return `(default -> ${DEFAULTS.normalizeVolatileSystem ? "on" : "off"})`;
|
|
18324
18369
|
return value ? "on" : "off";
|
|
18325
18370
|
}
|
|
18326
|
-
|
|
18327
|
-
|
|
18328
|
-
|
|
18329
|
-
|
|
18330
|
-
|
|
18331
|
-
|
|
18332
|
-
|
|
18333
|
-
if (override.sessionId) parts.push(`session: ${override.sessionId}`);
|
|
18334
|
-
if (override.cacheControl) parts.push(`cache: ${override.cacheControl}`);
|
|
18335
|
-
if (override.normalizeVolatileSystem !== void 0) parts.push(`normalize: ${nvsHint(override.normalizeVolatileSystem)}`);
|
|
18336
|
-
if (override.headers) parts.push(`${Object.keys(override.headers).length} header(s)`);
|
|
18337
|
-
return parts.join(", ") || "(empty)";
|
|
18371
|
+
/** Global tri-state: distinguish absent (default-origin) from an explicit mode. */
|
|
18372
|
+
function globalTriStateLabel(value, fallback) {
|
|
18373
|
+
return value === void 0 ? `(default -> ${fallback})` : value;
|
|
18374
|
+
}
|
|
18375
|
+
function nvsLabel(value, globalValue) {
|
|
18376
|
+
if (value === void 0) return `(inherit -> ${globalValue ? "on" : "off"})`;
|
|
18377
|
+
return value ? "on" : "off";
|
|
18338
18378
|
}
|
|
18339
|
-
function
|
|
18340
|
-
|
|
18341
|
-
if (
|
|
18342
|
-
|
|
18343
|
-
return [cc, ttlLabel].filter(Boolean).join(", ") || "(inherit)";
|
|
18379
|
+
function perModelTtl(current, globalCfg) {
|
|
18380
|
+
if (current.cacheControlTtl !== void 0) return describeTtl(current.cacheControlTtl);
|
|
18381
|
+
if (globalCfg.cacheControlTtl !== void 0) return `(inherit -> ${describeTtl(globalCfg.cacheControlTtl)})`;
|
|
18382
|
+
return "(inherit)";
|
|
18344
18383
|
}
|
|
18384
|
+
function formatGlobalCachingSummary(cfg) {
|
|
18385
|
+
const cc = globalTriStateLabel(cfg.cacheControl, DEFAULTS.cacheControl);
|
|
18386
|
+
const sid = globalTriStateLabel(cfg.sessionId, DEFAULTS.sessionId);
|
|
18387
|
+
const nvs = globalNvsLabel(cfg.normalizeVolatileSystem);
|
|
18388
|
+
return [
|
|
18389
|
+
"Three settings shape the request so cache survives.",
|
|
18390
|
+
"",
|
|
18391
|
+
` cacheControl = ${cc} (activate cache)`,
|
|
18392
|
+
` cacheControlTtl = ${describeTtl(cfg.cacheControlTtl)}`,
|
|
18393
|
+
` sessionId = ${sid} (pin provider)`,
|
|
18394
|
+
` normalizeVolatileSystem = ${nvs} (stable prefix)`,
|
|
18395
|
+
"",
|
|
18396
|
+
"Anthropic -> levers 1+2 - non-Anthropic (qwen/glm) -> all 3"
|
|
18397
|
+
].join("\n");
|
|
18398
|
+
}
|
|
18399
|
+
function formatPerModelCachingSummary(modelKey, current, globalCfg) {
|
|
18400
|
+
const gCc = globalCfg.cacheControl ?? DEFAULTS.cacheControl;
|
|
18401
|
+
const gSid = globalCfg.sessionId ?? DEFAULTS.sessionId;
|
|
18402
|
+
const gNvs = globalCfg.normalizeVolatileSystem ?? DEFAULTS.normalizeVolatileSystem;
|
|
18403
|
+
const cc = current.cacheControl ?? `(inherit -> ${gCc})`;
|
|
18404
|
+
const ttl = perModelTtl(current, globalCfg);
|
|
18405
|
+
const sid = current.sessionId ?? `(inherit -> ${gSid})`;
|
|
18406
|
+
const nvs = nvsLabel(current.normalizeVolatileSystem, gNvs);
|
|
18407
|
+
return [
|
|
18408
|
+
`Caching for "${modelKey}"`,
|
|
18409
|
+
"",
|
|
18410
|
+
` cacheControl = ${cc}`,
|
|
18411
|
+
` cacheControlTtl = ${ttl}`,
|
|
18412
|
+
` sessionId = ${sid}`,
|
|
18413
|
+
` normalizeVolatileSystem = ${nvs}`
|
|
18414
|
+
].join("\n");
|
|
18415
|
+
}
|
|
18416
|
+
//#endregion
|
|
18417
|
+
//#region src/commands/config/equality.ts
|
|
18418
|
+
/** Deep equality for overrides — gates no-op writes. */
|
|
18419
|
+
function overridesEqual(a, b) {
|
|
18420
|
+
return isDeepStrictEqual(a, b);
|
|
18421
|
+
}
|
|
18422
|
+
//#endregion
|
|
18423
|
+
//#region src/commands/config/normalize-system.ts
|
|
18424
|
+
async function normalizeVolatileSystemCommand(opts) {
|
|
18425
|
+
const configPath = requireConfigPath(opts?.configPath);
|
|
18426
|
+
const raw = readConfigFileRaw(configPath).normalizeVolatileSystem;
|
|
18427
|
+
const effective = raw ?? DEFAULTS.normalizeVolatileSystem;
|
|
18428
|
+
log.info(`Current: normalizeVolatileSystem = ${raw === void 0 ? `(default -> ${effective ? "on" : "off"})` : effective}`);
|
|
18429
|
+
const choice = await askNormalizeVolatileSystem("Normalize Claude Code's volatile cch hash in the system prompt? Stabilizes the prefix cache for non-Anthropic providers (qwen/glm/etc.).", raw, {
|
|
18430
|
+
removable: true,
|
|
18431
|
+
resetHint: `remove (default: ${DEFAULTS.normalizeVolatileSystem})`
|
|
18432
|
+
});
|
|
18433
|
+
if (typeof choice === "symbol") return;
|
|
18434
|
+
const fields = {};
|
|
18435
|
+
fields.normalizeVolatileSystem = choice === "reset" ? void 0 : choice;
|
|
18436
|
+
setGlobalConfigFields(configPath, fields);
|
|
18437
|
+
const label = choice === "reset" ? `(default: ${DEFAULTS.normalizeVolatileSystem})` : choice;
|
|
18438
|
+
log.success(`normalizeVolatileSystem set to ${label}`);
|
|
18439
|
+
}
|
|
18440
|
+
//#endregion
|
|
18441
|
+
//#region src/commands/config/override-levers.ts
|
|
18345
18442
|
function readGlobalTtl(configPath) {
|
|
18346
18443
|
if (!configPath) return void 0;
|
|
18347
18444
|
try {
|
|
@@ -18350,32 +18447,6 @@ function readGlobalTtl(configPath) {
|
|
|
18350
18447
|
return;
|
|
18351
18448
|
}
|
|
18352
18449
|
}
|
|
18353
|
-
function showCurrentConfig(modelKey, current) {
|
|
18354
|
-
log.info(`Current config for "${modelKey}":`);
|
|
18355
|
-
if (current.provider) for (const [field, value] of Object.entries(current.provider)) log.info(` provider.${field}: ${JSON.stringify(value)}`);
|
|
18356
|
-
if (current.sessionId) log.info(` sessionId: ${current.sessionId}`);
|
|
18357
|
-
if (current.cacheControl) log.info(` cacheControl: ${current.cacheControl}`);
|
|
18358
|
-
if (current.cacheControlTtl) log.info(` cacheControlTtl: ${current.cacheControlTtl}`);
|
|
18359
|
-
if (current.normalizeVolatileSystem !== void 0) log.info(` normalizeVolatileSystem: ${current.normalizeVolatileSystem}`);
|
|
18360
|
-
if (current.headers) for (const [name, value] of Object.entries(current.headers)) log.info(` headers.${name}: ${value}`);
|
|
18361
|
-
}
|
|
18362
|
-
async function editProvider(modelKey, current, client) {
|
|
18363
|
-
const isPattern = modelKey.includes("*");
|
|
18364
|
-
const mode = await selectRoutingMode("Routing mode");
|
|
18365
|
-
if (isCancel(mode)) return current;
|
|
18366
|
-
if (mode === "skip") {
|
|
18367
|
-
const { provider: _, ...rest } = current;
|
|
18368
|
-
return rest;
|
|
18369
|
-
}
|
|
18370
|
-
const providerOptions = await fetchProvidersForModel(client, modelKey, isPattern);
|
|
18371
|
-
if (!providerOptions) return current;
|
|
18372
|
-
const result = await selectProvidersByMode(mode, providerOptions);
|
|
18373
|
-
if (!result) return current;
|
|
18374
|
-
return {
|
|
18375
|
-
...current,
|
|
18376
|
-
provider: result.provider
|
|
18377
|
-
};
|
|
18378
|
-
}
|
|
18379
18450
|
async function editSessionId(current) {
|
|
18380
18451
|
const result = await collectSessionTriState(current.sessionId);
|
|
18381
18452
|
if (result === null) return current;
|
|
@@ -18383,7 +18454,6 @@ async function editSessionId(current) {
|
|
|
18383
18454
|
applyField(next, "sessionId", result.sessionId);
|
|
18384
18455
|
return next;
|
|
18385
18456
|
}
|
|
18386
|
-
/** @internal */
|
|
18387
18457
|
async function editCacheControl(current, configPath) {
|
|
18388
18458
|
const globalTtl = readGlobalTtl(configPath);
|
|
18389
18459
|
const result = await collectCacheTriState(current.cacheControl, current.cacheControlTtl, globalTtl);
|
|
@@ -18393,7 +18463,6 @@ async function editCacheControl(current, configPath) {
|
|
|
18393
18463
|
applyField(next, "cacheControlTtl", result.cacheControlTtl);
|
|
18394
18464
|
return next;
|
|
18395
18465
|
}
|
|
18396
|
-
/** @internal */
|
|
18397
18466
|
async function editNormalizeVolatileSystem(current) {
|
|
18398
18467
|
const result = await collectNormalizeVolatileSystem(current.normalizeVolatileSystem);
|
|
18399
18468
|
if (result === null) return current;
|
|
@@ -18401,15 +18470,134 @@ async function editNormalizeVolatileSystem(current) {
|
|
|
18401
18470
|
applyField(next, "normalizeVolatileSystem", result.normalizeVolatileSystem);
|
|
18402
18471
|
return next;
|
|
18403
18472
|
}
|
|
18404
|
-
|
|
18405
|
-
|
|
18406
|
-
|
|
18407
|
-
|
|
18408
|
-
|
|
18409
|
-
|
|
18410
|
-
|
|
18473
|
+
//#endregion
|
|
18474
|
+
//#region src/commands/config/session-routing.ts
|
|
18475
|
+
async function sessionRoutingCommand(opts) {
|
|
18476
|
+
const configPath = requireConfigPath(opts?.configPath);
|
|
18477
|
+
const raw = readConfigFileRaw(configPath).sessionId;
|
|
18478
|
+
const effective = raw ?? DEFAULTS.sessionId;
|
|
18479
|
+
log.info(`Current: sessionId = ${raw === void 0 ? `(default -> ${effective})` : effective}`);
|
|
18480
|
+
const result = await askTriState("Session routing mode", raw, SESSION_HINTS, { removable: true });
|
|
18481
|
+
if (typeof result === "symbol") return;
|
|
18482
|
+
if (result === "reset") {
|
|
18483
|
+
setGlobalConfigField(configPath, "sessionId", void 0);
|
|
18484
|
+
log.success("sessionId reset to default (auto)");
|
|
18485
|
+
return;
|
|
18486
|
+
}
|
|
18487
|
+
setGlobalConfigField(configPath, "sessionId", result);
|
|
18488
|
+
log.success(`sessionId set to ${result}`);
|
|
18489
|
+
}
|
|
18490
|
+
//#endregion
|
|
18491
|
+
//#region src/commands/config/caching-menu.ts
|
|
18492
|
+
/** Cache levers: option list + dispatch in one table. */
|
|
18493
|
+
const CACHING_LEVERS = [
|
|
18494
|
+
{
|
|
18495
|
+
value: "cacheControl",
|
|
18496
|
+
label: "Activate caching — mode + TTL (cacheControl)",
|
|
18497
|
+
global: (configPath) => cacheControlCommand({ configPath }),
|
|
18498
|
+
perModel: (current, configPath) => editCacheControl(current, configPath)
|
|
18499
|
+
},
|
|
18500
|
+
{
|
|
18501
|
+
value: "sessionId",
|
|
18502
|
+
label: "Pin provider (sessionId)",
|
|
18503
|
+
global: (configPath) => sessionRoutingCommand({ configPath }),
|
|
18504
|
+
perModel: (current) => editSessionId(current)
|
|
18505
|
+
},
|
|
18506
|
+
{
|
|
18507
|
+
value: "normalizeVolatileSystem",
|
|
18508
|
+
label: "Stabilize prefix (normalizeVolatileSystem)",
|
|
18509
|
+
global: (configPath) => normalizeVolatileSystemCommand({ configPath }),
|
|
18510
|
+
perModel: (current) => editNormalizeVolatileSystem(current)
|
|
18511
|
+
}
|
|
18512
|
+
];
|
|
18513
|
+
async function runCachingLeverMenu(opts) {
|
|
18514
|
+
for (;;) {
|
|
18515
|
+
note(opts.renderNote(), opts.noteTitle);
|
|
18516
|
+
const choice = await select({
|
|
18517
|
+
message: "Tune which lever?",
|
|
18518
|
+
options: [...CACHING_LEVERS.map((lever) => ({
|
|
18519
|
+
value: lever.value,
|
|
18520
|
+
label: lever.label
|
|
18521
|
+
})), {
|
|
18522
|
+
value: "back",
|
|
18523
|
+
label: opts.backLabel
|
|
18524
|
+
}]
|
|
18525
|
+
});
|
|
18526
|
+
if (isCancel(choice) || choice === "back") return;
|
|
18527
|
+
const chosen = CACHING_LEVERS.find((l) => l.value === choice);
|
|
18528
|
+
if (chosen) await opts.onLever(chosen);
|
|
18411
18529
|
}
|
|
18412
18530
|
}
|
|
18531
|
+
async function globalCachingMenu(opts) {
|
|
18532
|
+
const configPath = requireConfigPath(opts?.configPath);
|
|
18533
|
+
await runCachingLeverMenu({
|
|
18534
|
+
noteTitle: "Prompt caching",
|
|
18535
|
+
backLabel: "← Back",
|
|
18536
|
+
renderNote: () => formatGlobalCachingSummary(readConfigFileRaw(configPath)),
|
|
18537
|
+
onLever: (lever) => lever.global(configPath)
|
|
18538
|
+
});
|
|
18539
|
+
}
|
|
18540
|
+
/** Self-persists each lever; returns the latest override. */
|
|
18541
|
+
async function perModelCachingMenu(opts) {
|
|
18542
|
+
let current = opts.current;
|
|
18543
|
+
await runCachingLeverMenu({
|
|
18544
|
+
noteTitle: `Caching for "${opts.modelKey}"`,
|
|
18545
|
+
backLabel: "← Back to override edit",
|
|
18546
|
+
renderNote: () => formatPerModelCachingSummary(opts.modelKey, current, readConfigFile(opts.configPath)),
|
|
18547
|
+
onLever: async (lever) => {
|
|
18548
|
+
const next = await lever.perModel(current, opts.configPath);
|
|
18549
|
+
if (!overridesEqual(next, current)) {
|
|
18550
|
+
setModelOverride(opts.configPath, opts.modelKey, next);
|
|
18551
|
+
current = next;
|
|
18552
|
+
}
|
|
18553
|
+
}
|
|
18554
|
+
});
|
|
18555
|
+
return current;
|
|
18556
|
+
}
|
|
18557
|
+
async function cachingCommand(opts) {
|
|
18558
|
+
intro("Proxitor · Caching");
|
|
18559
|
+
await globalCachingMenu(opts);
|
|
18560
|
+
outro("Bye!");
|
|
18561
|
+
}
|
|
18562
|
+
//#endregion
|
|
18563
|
+
//#region src/commands/config/edit.ts
|
|
18564
|
+
function nvsHint(value) {
|
|
18565
|
+
if (value === void 0) return "(inherit)";
|
|
18566
|
+
return value ? "on" : "off";
|
|
18567
|
+
}
|
|
18568
|
+
function formatOverrideHint(override) {
|
|
18569
|
+
if (!override) return "(empty)";
|
|
18570
|
+
const parts = [];
|
|
18571
|
+
if (override.provider) {
|
|
18572
|
+
const keys = Object.keys(override.provider);
|
|
18573
|
+
parts.push(`provider: ${keys.join(", ")}`);
|
|
18574
|
+
}
|
|
18575
|
+
if (override.sessionId) parts.push(`session: ${override.sessionId}`);
|
|
18576
|
+
if (override.cacheControl) parts.push(`cache: ${override.cacheControl}`);
|
|
18577
|
+
if (override.normalizeVolatileSystem !== void 0) parts.push(`normalize: ${nvsHint(override.normalizeVolatileSystem)}`);
|
|
18578
|
+
if (override.headers) parts.push(`${Object.keys(override.headers).length} header(s)`);
|
|
18579
|
+
return parts.join(", ") || "(empty)";
|
|
18580
|
+
}
|
|
18581
|
+
function formatCachingHint(current) {
|
|
18582
|
+
return `cc ${current.cacheControl ?? "inherit"} · ttl ${current.cacheControlTtl ? describeTtl(current.cacheControlTtl) : "inherit"} · sid ${current.sessionId ?? "inherit"} · nvs ${nvsHint(current.normalizeVolatileSystem)}`;
|
|
18583
|
+
}
|
|
18584
|
+
async function editProvider(modelKey, current, client) {
|
|
18585
|
+
const isPattern = modelKey.includes("*");
|
|
18586
|
+
const mode = await selectRoutingMode("Routing mode");
|
|
18587
|
+
if (isCancel(mode)) return current;
|
|
18588
|
+
if (mode === "skip") {
|
|
18589
|
+
const { provider: _, ...rest } = current;
|
|
18590
|
+
return rest;
|
|
18591
|
+
}
|
|
18592
|
+
const providerOptions = await fetchProvidersForModel(client, modelKey, isPattern);
|
|
18593
|
+
if (!providerOptions) return current;
|
|
18594
|
+
const result = await selectProvidersByMode(mode, providerOptions);
|
|
18595
|
+
if (!result) return current;
|
|
18596
|
+
return {
|
|
18597
|
+
...current,
|
|
18598
|
+
provider: result.provider
|
|
18599
|
+
};
|
|
18600
|
+
}
|
|
18413
18601
|
/** Run the interactive "Edit model override" flow. */
|
|
18414
18602
|
async function editOverrideCommand(client, configPath) {
|
|
18415
18603
|
intro("Edit Model Override");
|
|
@@ -18432,7 +18620,6 @@ async function editOverrideCommand(client, configPath) {
|
|
|
18432
18620
|
if (isCancel(selected)) return;
|
|
18433
18621
|
const modelKey = selected;
|
|
18434
18622
|
let current = overrides[modelKey] ?? {};
|
|
18435
|
-
showCurrentConfig(modelKey, current);
|
|
18436
18623
|
for (;;) {
|
|
18437
18624
|
const field = await select({
|
|
18438
18625
|
message: "Edit which field?",
|
|
@@ -18443,19 +18630,9 @@ async function editOverrideCommand(client, configPath) {
|
|
|
18443
18630
|
hint: formatOverrideHint({ provider: current.provider })
|
|
18444
18631
|
},
|
|
18445
18632
|
{
|
|
18446
|
-
value: "
|
|
18447
|
-
label: "
|
|
18448
|
-
hint: current
|
|
18449
|
-
},
|
|
18450
|
-
{
|
|
18451
|
-
value: "cacheControl",
|
|
18452
|
-
label: "Cache control",
|
|
18453
|
-
hint: formatCacheHint(current.cacheControl, current.cacheControlTtl)
|
|
18454
|
-
},
|
|
18455
|
-
{
|
|
18456
|
-
value: "normalizeVolatileSystem",
|
|
18457
|
-
label: "Normalize volatile system",
|
|
18458
|
-
hint: nvsHint(current.normalizeVolatileSystem)
|
|
18633
|
+
value: "caching",
|
|
18634
|
+
label: "💾 Caching",
|
|
18635
|
+
hint: formatCachingHint(current)
|
|
18459
18636
|
},
|
|
18460
18637
|
{
|
|
18461
18638
|
value: "done",
|
|
@@ -18464,15 +18641,19 @@ async function editOverrideCommand(client, configPath) {
|
|
|
18464
18641
|
]
|
|
18465
18642
|
});
|
|
18466
18643
|
if (isCancel(field) || field === "done") break;
|
|
18467
|
-
|
|
18468
|
-
|
|
18469
|
-
|
|
18470
|
-
|
|
18471
|
-
|
|
18472
|
-
|
|
18644
|
+
if (field === "caching") {
|
|
18645
|
+
current = await perModelCachingMenu({
|
|
18646
|
+
modelKey,
|
|
18647
|
+
current,
|
|
18648
|
+
configPath: resolvedConfigPath
|
|
18649
|
+
});
|
|
18650
|
+
continue;
|
|
18651
|
+
}
|
|
18652
|
+
const before = current;
|
|
18653
|
+
current = await editProvider(modelKey, current, client);
|
|
18654
|
+
if (!overridesEqual(current, before)) setModelOverride(resolvedConfigPath, modelKey, current);
|
|
18473
18655
|
}
|
|
18474
|
-
|
|
18475
|
-
outro("✓ Override updated");
|
|
18656
|
+
outro("✓ Done");
|
|
18476
18657
|
}
|
|
18477
18658
|
//#endregion
|
|
18478
18659
|
//#region src/commands/config/list.ts
|
|
@@ -18998,31 +19179,6 @@ async function runWizard(opts = {}) {
|
|
|
18998
19179
|
}
|
|
18999
19180
|
}
|
|
19000
19181
|
//#endregion
|
|
19001
|
-
//#region src/commands/config/cache-control.ts
|
|
19002
|
-
async function cacheControlCommand(opts) {
|
|
19003
|
-
const configPath = requireConfigPath(opts?.configPath);
|
|
19004
|
-
const cfg = readConfigFile(configPath);
|
|
19005
|
-
const currentCc = cfg.cacheControl ?? DEFAULTS.cacheControl;
|
|
19006
|
-
const currentTtl = cfg.cacheControlTtl;
|
|
19007
|
-
log.info(`Current: cacheControl = ${currentCc}`);
|
|
19008
|
-
if (currentTtl) log.info(`Current: cacheControlTtl = ${currentTtl}`);
|
|
19009
|
-
const cc = await askTriState("Cache control mode", currentCc, CACHE_HINTS, { removable: true });
|
|
19010
|
-
if (typeof cc === "symbol") return;
|
|
19011
|
-
const fields = {};
|
|
19012
|
-
fields.cacheControl = cc === "reset" ? void 0 : cc;
|
|
19013
|
-
const ttlResult = await askCacheControlTtl(currentTtl, { removable: true });
|
|
19014
|
-
if (typeof ttlResult === "symbol") {
|
|
19015
|
-
setGlobalConfigFields(configPath, fields);
|
|
19016
|
-
log.success(`cacheControl set to ${cc === "reset" ? "(default)" : cc}`);
|
|
19017
|
-
return;
|
|
19018
|
-
}
|
|
19019
|
-
fields.cacheControlTtl = ttlResult === "reset" ? void 0 : ttlResult;
|
|
19020
|
-
setGlobalConfigFields(configPath, fields);
|
|
19021
|
-
const ccLabel = cc === "reset" ? "(default)" : cc;
|
|
19022
|
-
const ttlLabel = ttlResult === "reset" ? "(default)" : ttlResult;
|
|
19023
|
-
log.success(`cacheControl set to ${ccLabel}, TTL = ${ttlLabel}`);
|
|
19024
|
-
}
|
|
19025
|
-
//#endregion
|
|
19026
19182
|
//#region src/commands/config/connection.ts
|
|
19027
19183
|
const FIELD_MAP = {
|
|
19028
19184
|
apiKey: {
|
|
@@ -19096,39 +19252,6 @@ async function connectionMenuCommand(opts) {
|
|
|
19096
19252
|
log.success(`${entry.label} updated`);
|
|
19097
19253
|
}
|
|
19098
19254
|
//#endregion
|
|
19099
|
-
//#region src/commands/config/normalize-system.ts
|
|
19100
|
-
async function normalizeVolatileSystemCommand(opts) {
|
|
19101
|
-
const configPath = requireConfigPath(opts?.configPath);
|
|
19102
|
-
const current = readConfigFile(configPath).normalizeVolatileSystem ?? DEFAULTS.normalizeVolatileSystem;
|
|
19103
|
-
log.info(`Current: normalizeVolatileSystem = ${current}`);
|
|
19104
|
-
const choice = await askNormalizeVolatileSystem("Normalize Claude Code's volatile cch hash in the system prompt? Stabilizes the prefix cache for non-Anthropic providers (qwen/glm/etc.).", current, {
|
|
19105
|
-
removable: true,
|
|
19106
|
-
resetHint: `remove (default: ${DEFAULTS.normalizeVolatileSystem})`
|
|
19107
|
-
});
|
|
19108
|
-
if (typeof choice === "symbol") return;
|
|
19109
|
-
const fields = {};
|
|
19110
|
-
fields.normalizeVolatileSystem = choice === "reset" ? void 0 : choice;
|
|
19111
|
-
setGlobalConfigFields(configPath, fields);
|
|
19112
|
-
const label = choice === "reset" ? `(default: ${DEFAULTS.normalizeVolatileSystem})` : choice;
|
|
19113
|
-
log.success(`normalizeVolatileSystem set to ${label}`);
|
|
19114
|
-
}
|
|
19115
|
-
//#endregion
|
|
19116
|
-
//#region src/commands/config/session-routing.ts
|
|
19117
|
-
async function sessionRoutingCommand(opts) {
|
|
19118
|
-
const configPath = requireConfigPath(opts?.configPath);
|
|
19119
|
-
const current = readConfigFile(configPath).sessionId ?? DEFAULTS.sessionId;
|
|
19120
|
-
log.info(`Current: sessionId = ${current}`);
|
|
19121
|
-
const result = await askTriState("Session routing mode", current, SESSION_HINTS, { removable: true });
|
|
19122
|
-
if (typeof result === "symbol") return;
|
|
19123
|
-
if (result === "reset") {
|
|
19124
|
-
setGlobalConfigField(configPath, "sessionId", void 0);
|
|
19125
|
-
log.success("sessionId reset to default (auto)");
|
|
19126
|
-
return;
|
|
19127
|
-
}
|
|
19128
|
-
setGlobalConfigField(configPath, "sessionId", result);
|
|
19129
|
-
log.success(`sessionId set to ${result}`);
|
|
19130
|
-
}
|
|
19131
|
-
//#endregion
|
|
19132
19255
|
//#region src/commands/config.ts
|
|
19133
19256
|
async function runConfigMenu(client) {
|
|
19134
19257
|
intro("Proxitor Config Manager");
|
|
@@ -19150,16 +19273,8 @@ async function runConfigMenu(client) {
|
|
|
19150
19273
|
label: "🔑 API key & connection"
|
|
19151
19274
|
},
|
|
19152
19275
|
{
|
|
19153
|
-
value: "
|
|
19154
|
-
label: "
|
|
19155
|
-
},
|
|
19156
|
-
{
|
|
19157
|
-
value: "cache",
|
|
19158
|
-
label: "💾 Cache control"
|
|
19159
|
-
},
|
|
19160
|
-
{
|
|
19161
|
-
value: "normalize",
|
|
19162
|
-
label: "🧹 Normalize volatile system"
|
|
19276
|
+
value: "caching",
|
|
19277
|
+
label: "💾 Caching"
|
|
19163
19278
|
},
|
|
19164
19279
|
{
|
|
19165
19280
|
value: "_sep2",
|
|
@@ -19212,14 +19327,8 @@ async function runConfigMenu(client) {
|
|
|
19212
19327
|
case "connection":
|
|
19213
19328
|
await connectionMenuCommand();
|
|
19214
19329
|
break;
|
|
19215
|
-
case "
|
|
19216
|
-
await
|
|
19217
|
-
break;
|
|
19218
|
-
case "cache":
|
|
19219
|
-
await cacheControlCommand();
|
|
19220
|
-
break;
|
|
19221
|
-
case "normalize":
|
|
19222
|
-
await normalizeVolatileSystemCommand();
|
|
19330
|
+
case "caching":
|
|
19331
|
+
await globalCachingMenu();
|
|
19223
19332
|
break;
|
|
19224
19333
|
case "add":
|
|
19225
19334
|
await addOverrideCommand({ client });
|
|
@@ -19244,7 +19353,7 @@ async function runConfigMenu(client) {
|
|
|
19244
19353
|
}
|
|
19245
19354
|
//#endregion
|
|
19246
19355
|
//#region src/version.ts
|
|
19247
|
-
const version = "0.
|
|
19356
|
+
const version = "0.12.0";
|
|
19248
19357
|
//#endregion
|
|
19249
19358
|
//#region src/commands/doctor.ts
|
|
19250
19359
|
const DEFAULT_TIMEOUT_MS = 3e3;
|
|
@@ -19482,6 +19591,167 @@ async function doctorCommand(opts = {}) {
|
|
|
19482
19591
|
return exitCode;
|
|
19483
19592
|
}
|
|
19484
19593
|
//#endregion
|
|
19594
|
+
//#region src/config-source.ts
|
|
19595
|
+
/** Default watcher: fs.watchFile polling; returns a stop function. */
|
|
19596
|
+
const watchStat = (filename, pollIntervalMs, onChange) => {
|
|
19597
|
+
watchFile(filename, {
|
|
19598
|
+
interval: pollIntervalMs,
|
|
19599
|
+
persistent: false
|
|
19600
|
+
}, onChange);
|
|
19601
|
+
return () => unwatchFile(filename);
|
|
19602
|
+
};
|
|
19603
|
+
function fmt(value) {
|
|
19604
|
+
if (value === void 0) return "unset";
|
|
19605
|
+
if (value === true) return "on";
|
|
19606
|
+
if (value === false) return "off";
|
|
19607
|
+
return String(value);
|
|
19608
|
+
}
|
|
19609
|
+
/** Cache-lever keys shared by global config + per-model overrides. */
|
|
19610
|
+
const CACHE_LEVER_KEYS = [
|
|
19611
|
+
"cacheControl",
|
|
19612
|
+
"cacheControlTtl",
|
|
19613
|
+
"sessionId",
|
|
19614
|
+
"normalizeVolatileSystem"
|
|
19615
|
+
];
|
|
19616
|
+
const SCALAR_KEYS = [
|
|
19617
|
+
...CACHE_LEVER_KEYS,
|
|
19618
|
+
"authType",
|
|
19619
|
+
"verbose",
|
|
19620
|
+
"bodyLimit",
|
|
19621
|
+
"openrouterBaseUrl"
|
|
19622
|
+
];
|
|
19623
|
+
function canonicalEntries(record) {
|
|
19624
|
+
if (!record) return "";
|
|
19625
|
+
return JSON.stringify(Object.keys(record).sort().map((key) => [key, record[key]]));
|
|
19626
|
+
}
|
|
19627
|
+
/** Field-level diff for a single model override; '' if nothing changed. */
|
|
19628
|
+
function overrideFieldDiff(prev, next) {
|
|
19629
|
+
const parts = [];
|
|
19630
|
+
for (const key of CACHE_LEVER_KEYS) if (prev?.[key] !== next?.[key]) parts.push(`${key}: ${fmt(prev?.[key])}→${fmt(next?.[key])}`);
|
|
19631
|
+
if (JSON.stringify(buildProviderRouting(prev?.provider)) !== JSON.stringify(buildProviderRouting(next?.provider))) parts.push("provider routing");
|
|
19632
|
+
if (canonicalEntries(prev?.headers) !== canonicalEntries(next?.headers)) parts.push("headers");
|
|
19633
|
+
return parts.join(", ");
|
|
19634
|
+
}
|
|
19635
|
+
/** Per-override diff: +added, -removed, `key (fields)` for modified; '' if unchanged. */
|
|
19636
|
+
function summarizeModelOverridesDiff(prev, next) {
|
|
19637
|
+
const prevKeys = new Set(Object.keys(prev ?? {}));
|
|
19638
|
+
const nextKeys = new Set(Object.keys(next ?? {}));
|
|
19639
|
+
const parts = [];
|
|
19640
|
+
for (const key of [...new Set([...prevKeys, ...nextKeys])].sort()) {
|
|
19641
|
+
const inPrev = prevKeys.has(key);
|
|
19642
|
+
const inNext = nextKeys.has(key);
|
|
19643
|
+
if (inPrev && inNext) {
|
|
19644
|
+
const fields = overrideFieldDiff(prev?.[key], next?.[key]);
|
|
19645
|
+
if (fields) parts.push(`${key} (${fields})`);
|
|
19646
|
+
} else if (inNext) parts.push(`+${key}`);
|
|
19647
|
+
else parts.push(`-${key}`);
|
|
19648
|
+
}
|
|
19649
|
+
return parts.join(", ");
|
|
19650
|
+
}
|
|
19651
|
+
/** Diff of cache-relevant fields; '' if nothing changed. */
|
|
19652
|
+
function summarizeChanges(prev, next) {
|
|
19653
|
+
const parts = [];
|
|
19654
|
+
for (const key of SCALAR_KEYS) if (prev[key] !== next[key]) parts.push(`${key}: ${fmt(prev[key])}→${fmt(next[key])}`);
|
|
19655
|
+
if (JSON.stringify(buildProviderRouting(prev.provider)) !== JSON.stringify(buildProviderRouting(next.provider))) parts.push("provider routing");
|
|
19656
|
+
const overridesDiff = summarizeModelOverridesDiff(prev.modelOverrides, next.modelOverrides);
|
|
19657
|
+
if (overridesDiff) parts.push(`modelOverrides: ${overridesDiff}`);
|
|
19658
|
+
if (canonicalEntries(prev.headers) !== canonicalEntries(next.headers)) parts.push("headers");
|
|
19659
|
+
return parts.join(", ");
|
|
19660
|
+
}
|
|
19661
|
+
function createConfigSource(options) {
|
|
19662
|
+
return new FileWatchingConfigSource(options);
|
|
19663
|
+
}
|
|
19664
|
+
var FileWatchingConfigSource = class {
|
|
19665
|
+
current;
|
|
19666
|
+
loadOptions;
|
|
19667
|
+
load;
|
|
19668
|
+
pollIntervalMs;
|
|
19669
|
+
watch;
|
|
19670
|
+
boundHost;
|
|
19671
|
+
boundPort;
|
|
19672
|
+
resolvedPath;
|
|
19673
|
+
stopWatch;
|
|
19674
|
+
loading = false;
|
|
19675
|
+
pending = false;
|
|
19676
|
+
watching = false;
|
|
19677
|
+
constructor(options) {
|
|
19678
|
+
this.current = options.initial;
|
|
19679
|
+
this.loadOptions = options.loadOptions;
|
|
19680
|
+
this.load = options.load ?? loadConfig;
|
|
19681
|
+
this.pollIntervalMs = options.pollIntervalMs ?? 1e3;
|
|
19682
|
+
this.watch = options.watch ?? watchStat;
|
|
19683
|
+
this.boundHost = options.initial.host;
|
|
19684
|
+
this.boundPort = options.initial.port;
|
|
19685
|
+
this.resolvedPath = options.loadOptions.noConfig ? null : tryFindConfigFile(options.loadOptions.configPath);
|
|
19686
|
+
}
|
|
19687
|
+
get() {
|
|
19688
|
+
return this.current;
|
|
19689
|
+
}
|
|
19690
|
+
async reload() {
|
|
19691
|
+
if (this.loading) {
|
|
19692
|
+
this.pending = true;
|
|
19693
|
+
return { ok: true };
|
|
19694
|
+
}
|
|
19695
|
+
this.loading = true;
|
|
19696
|
+
try {
|
|
19697
|
+
const next = await this.load(this.loadOptions);
|
|
19698
|
+
const restartNeeded = next.host !== this.boundHost || next.port !== this.boundPort;
|
|
19699
|
+
let diff = "";
|
|
19700
|
+
try {
|
|
19701
|
+
diff = summarizeChanges(this.current, next);
|
|
19702
|
+
} catch {
|
|
19703
|
+
diff = "";
|
|
19704
|
+
}
|
|
19705
|
+
this.current = next;
|
|
19706
|
+
if (restartNeeded) logger.warn("host/port changed — restart proxitor to apply (live reload does not re-bind the socket)");
|
|
19707
|
+
logger.info(`Config reloaded${diff ? ` — ${diff}` : " (no material changes)"}`);
|
|
19708
|
+
return { ok: true };
|
|
19709
|
+
} catch (error) {
|
|
19710
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
19711
|
+
logger.error(`Config reload failed — keeping previous config: ${msg}`);
|
|
19712
|
+
return {
|
|
19713
|
+
ok: false,
|
|
19714
|
+
error: msg
|
|
19715
|
+
};
|
|
19716
|
+
} finally {
|
|
19717
|
+
this.loading = false;
|
|
19718
|
+
if (this.pending) {
|
|
19719
|
+
this.pending = false;
|
|
19720
|
+
this.reload();
|
|
19721
|
+
}
|
|
19722
|
+
}
|
|
19723
|
+
}
|
|
19724
|
+
start() {
|
|
19725
|
+
if (this.watching) return;
|
|
19726
|
+
if (!this.resolvedPath) {
|
|
19727
|
+
logger.info("Live config reload disabled (no config file)");
|
|
19728
|
+
return;
|
|
19729
|
+
}
|
|
19730
|
+
this.watching = true;
|
|
19731
|
+
const path = this.resolvedPath;
|
|
19732
|
+
this.stopWatch = this.watch(path, this.pollIntervalMs, (curr, prev) => {
|
|
19733
|
+
try {
|
|
19734
|
+
this.onStat(path, curr, prev);
|
|
19735
|
+
} catch {}
|
|
19736
|
+
});
|
|
19737
|
+
}
|
|
19738
|
+
onStat(path, curr, prev) {
|
|
19739
|
+
if (curr.nlink === 0) {
|
|
19740
|
+
logger.warn(`config file disappeared — keeping current config (${path})`);
|
|
19741
|
+
return;
|
|
19742
|
+
}
|
|
19743
|
+
if (curr.mtimeMs === prev.mtimeMs) return;
|
|
19744
|
+
this.reload();
|
|
19745
|
+
}
|
|
19746
|
+
stop() {
|
|
19747
|
+
if (this.watching) {
|
|
19748
|
+
this.stopWatch?.();
|
|
19749
|
+
this.stopWatch = void 0;
|
|
19750
|
+
this.watching = false;
|
|
19751
|
+
}
|
|
19752
|
+
}
|
|
19753
|
+
};
|
|
19754
|
+
//#endregion
|
|
19485
19755
|
//#region node_modules/.pnpm/@hono+node-server@2.0.4_hono@4.12.25/node_modules/@hono/node-server/dist/index.mjs
|
|
19486
19756
|
var RequestError = class extends Error {
|
|
19487
19757
|
constructor(message, options) {
|
|
@@ -22876,20 +23146,23 @@ const injectChain = [
|
|
|
22876
23146
|
normalizeVolatileSystemMiddleware,
|
|
22877
23147
|
injectSessionId
|
|
22878
23148
|
];
|
|
22879
|
-
function createProxyServer(
|
|
23149
|
+
function createProxyServer(source, onReady) {
|
|
22880
23150
|
const app = new Hono();
|
|
22881
|
-
const globalRouting = buildProviderRouting(config.provider);
|
|
22882
|
-
const modelOverrideKeys = Object.keys(config.modelOverrides ?? []);
|
|
22883
23151
|
app.use("*", async (c, next) => {
|
|
22884
|
-
c.set("config",
|
|
23152
|
+
c.set("config", source.get());
|
|
22885
23153
|
await next();
|
|
22886
23154
|
});
|
|
22887
|
-
app.get("/health", (c) =>
|
|
22888
|
-
|
|
22889
|
-
|
|
22890
|
-
|
|
22891
|
-
|
|
22892
|
-
|
|
23155
|
+
app.get("/health", (c) => {
|
|
23156
|
+
const config = source.get();
|
|
23157
|
+
const globalRouting = buildProviderRouting(config.provider);
|
|
23158
|
+
const modelOverrideKeys = Object.keys(config.modelOverrides ?? []);
|
|
23159
|
+
return c.json({
|
|
23160
|
+
ok: true,
|
|
23161
|
+
upstream: config.openrouterBaseUrl,
|
|
23162
|
+
provider: globalRouting ?? "not configured",
|
|
23163
|
+
modelOverrides: modelOverrideKeys
|
|
23164
|
+
});
|
|
23165
|
+
});
|
|
22893
23166
|
for (const path of INJECT_PATHS) app.post(path, setupRequest, readBody, ...injectChain, buildUpstreamReq, forwardRequest);
|
|
22894
23167
|
app.all("*", setupRequest, readBody, resolveConfig, buildUpstreamReq, forwardRequest);
|
|
22895
23168
|
app.onError((err, c) => {
|
|
@@ -22900,20 +23173,22 @@ function createProxyServer(config, onReady) {
|
|
|
22900
23173
|
type: "proxy_internal_error"
|
|
22901
23174
|
} }, { status: 500 });
|
|
22902
23175
|
});
|
|
23176
|
+
const initial = source.get();
|
|
22903
23177
|
return serve({
|
|
22904
23178
|
fetch: app.fetch,
|
|
22905
|
-
port:
|
|
22906
|
-
hostname:
|
|
23179
|
+
port: initial.port,
|
|
23180
|
+
hostname: initial.host
|
|
22907
23181
|
}, onReady);
|
|
22908
23182
|
}
|
|
22909
23183
|
const SHUTDOWN_TIMEOUT_MS = 1e4;
|
|
22910
|
-
function startProxyServer(
|
|
22911
|
-
const server = createProxyServer(
|
|
23184
|
+
function startProxyServer(source, onReady) {
|
|
23185
|
+
const server = createProxyServer(source, onReady);
|
|
22912
23186
|
let shuttingDown = false;
|
|
22913
23187
|
function shutdown(signal) {
|
|
22914
23188
|
if (shuttingDown) return;
|
|
22915
23189
|
shuttingDown = true;
|
|
22916
23190
|
logger.info(`${signal} received — draining active connections…`);
|
|
23191
|
+
source.stop();
|
|
22917
23192
|
const timer = setTimeout(() => {
|
|
22918
23193
|
logger.warn("Forcing shutdown — drain timeout exceeded");
|
|
22919
23194
|
process.exit(1);
|
|
@@ -23031,17 +23306,24 @@ const startCommand = (0, import_cjs.command)({
|
|
|
23031
23306
|
},
|
|
23032
23307
|
handler: async ({ configPath, port, host, noConfig, openrouterKey, verbose }) => {
|
|
23033
23308
|
try {
|
|
23034
|
-
const
|
|
23309
|
+
const loadOptions = {
|
|
23035
23310
|
configPath,
|
|
23036
23311
|
noConfig,
|
|
23037
23312
|
port,
|
|
23038
23313
|
host,
|
|
23039
23314
|
openrouterKey,
|
|
23040
23315
|
verbose
|
|
23316
|
+
};
|
|
23317
|
+
const cfg = await loadConfig(loadOptions);
|
|
23318
|
+
const source = createConfigSource({
|
|
23319
|
+
loadOptions,
|
|
23320
|
+
initial: cfg
|
|
23041
23321
|
});
|
|
23042
|
-
|
|
23322
|
+
source.start();
|
|
23323
|
+
startProxyServer(source, () => {
|
|
23043
23324
|
logger.ready(`Proxitor proxy listening on ${cfg.host}:${cfg.port}`);
|
|
23044
23325
|
logger.info("Routing requests to OpenRouter");
|
|
23326
|
+
if (source.resolvedPath) logger.info(`Watching ${source.resolvedPath} for changes (live reload)`);
|
|
23045
23327
|
});
|
|
23046
23328
|
} catch (error) {
|
|
23047
23329
|
logger.error("Failed to start proxy:", error);
|
|
@@ -23082,6 +23364,14 @@ const configCli = (0, import_cjs.subcommands)({
|
|
|
23082
23364
|
});
|
|
23083
23365
|
}
|
|
23084
23366
|
}),
|
|
23367
|
+
cache: (0, import_cjs.command)({
|
|
23368
|
+
name: "cache",
|
|
23369
|
+
description: "Tune prompt-caching settings (interactive)",
|
|
23370
|
+
args: { ...configArgs },
|
|
23371
|
+
handler: async (args) => {
|
|
23372
|
+
await cachingCommand({ configPath: args.configPath });
|
|
23373
|
+
}
|
|
23374
|
+
}),
|
|
23085
23375
|
edit: (0, import_cjs.command)({
|
|
23086
23376
|
name: "edit",
|
|
23087
23377
|
description: "Edit an existing model override (interactive)",
|