proxitor 0.10.1 → 0.11.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/dist/cli.mjs +166 -18
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import tty, { ReadStream } from "node:tty";
|
|
|
7
7
|
import { formatWithOptions, 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";
|
|
@@ -19244,7 +19244,7 @@ async function runConfigMenu(client) {
|
|
|
19244
19244
|
}
|
|
19245
19245
|
//#endregion
|
|
19246
19246
|
//#region src/version.ts
|
|
19247
|
-
const version = "0.
|
|
19247
|
+
const version = "0.11.0";
|
|
19248
19248
|
//#endregion
|
|
19249
19249
|
//#region src/commands/doctor.ts
|
|
19250
19250
|
const DEFAULT_TIMEOUT_MS = 3e3;
|
|
@@ -19482,6 +19482,142 @@ async function doctorCommand(opts = {}) {
|
|
|
19482
19482
|
return exitCode;
|
|
19483
19483
|
}
|
|
19484
19484
|
//#endregion
|
|
19485
|
+
//#region src/config-source.ts
|
|
19486
|
+
/** Default watcher: fs.watchFile polling; returns a stop function. */
|
|
19487
|
+
const watchStat = (filename, pollIntervalMs, onChange) => {
|
|
19488
|
+
watchFile(filename, {
|
|
19489
|
+
interval: pollIntervalMs,
|
|
19490
|
+
persistent: false
|
|
19491
|
+
}, onChange);
|
|
19492
|
+
return () => unwatchFile(filename);
|
|
19493
|
+
};
|
|
19494
|
+
function fmt(value) {
|
|
19495
|
+
if (value === void 0) return "unset";
|
|
19496
|
+
if (value === true) return "on";
|
|
19497
|
+
if (value === false) return "off";
|
|
19498
|
+
return String(value);
|
|
19499
|
+
}
|
|
19500
|
+
const SCALAR_KEYS = [
|
|
19501
|
+
"cacheControl",
|
|
19502
|
+
"cacheControlTtl",
|
|
19503
|
+
"sessionId",
|
|
19504
|
+
"normalizeVolatileSystem",
|
|
19505
|
+
"authType",
|
|
19506
|
+
"verbose",
|
|
19507
|
+
"bodyLimit",
|
|
19508
|
+
"openrouterBaseUrl"
|
|
19509
|
+
];
|
|
19510
|
+
function canonicalEntries(record) {
|
|
19511
|
+
if (!record) return "";
|
|
19512
|
+
return JSON.stringify(Object.keys(record).sort().map((key) => [key, record[key]]));
|
|
19513
|
+
}
|
|
19514
|
+
/** Diff of cache-relevant fields; '' if nothing changed. */
|
|
19515
|
+
function summarizeChanges(prev, next) {
|
|
19516
|
+
const parts = [];
|
|
19517
|
+
for (const key of SCALAR_KEYS) if (prev[key] !== next[key]) parts.push(`${key}: ${fmt(prev[key])}→${fmt(next[key])}`);
|
|
19518
|
+
if (JSON.stringify(buildProviderRouting(prev.provider)) !== JSON.stringify(buildProviderRouting(next.provider))) parts.push("provider routing");
|
|
19519
|
+
if (canonicalEntries(prev.modelOverrides) !== canonicalEntries(next.modelOverrides)) {
|
|
19520
|
+
const prevCount = prev.modelOverrides ? Object.keys(prev.modelOverrides).length : 0;
|
|
19521
|
+
const nextCount = next.modelOverrides ? Object.keys(next.modelOverrides).length : 0;
|
|
19522
|
+
parts.push(`modelOverrides: ${prevCount}→${nextCount}`);
|
|
19523
|
+
}
|
|
19524
|
+
if (canonicalEntries(prev.headers) !== canonicalEntries(next.headers)) parts.push("headers");
|
|
19525
|
+
return parts.join(", ");
|
|
19526
|
+
}
|
|
19527
|
+
function createConfigSource(options) {
|
|
19528
|
+
return new FileWatchingConfigSource(options);
|
|
19529
|
+
}
|
|
19530
|
+
var FileWatchingConfigSource = class {
|
|
19531
|
+
current;
|
|
19532
|
+
loadOptions;
|
|
19533
|
+
load;
|
|
19534
|
+
pollIntervalMs;
|
|
19535
|
+
watch;
|
|
19536
|
+
boundHost;
|
|
19537
|
+
boundPort;
|
|
19538
|
+
resolvedPath;
|
|
19539
|
+
stopWatch;
|
|
19540
|
+
loading = false;
|
|
19541
|
+
pending = false;
|
|
19542
|
+
watching = false;
|
|
19543
|
+
constructor(options) {
|
|
19544
|
+
this.current = options.initial;
|
|
19545
|
+
this.loadOptions = options.loadOptions;
|
|
19546
|
+
this.load = options.load ?? loadConfig;
|
|
19547
|
+
this.pollIntervalMs = options.pollIntervalMs ?? 1e3;
|
|
19548
|
+
this.watch = options.watch ?? watchStat;
|
|
19549
|
+
this.boundHost = options.initial.host;
|
|
19550
|
+
this.boundPort = options.initial.port;
|
|
19551
|
+
this.resolvedPath = options.loadOptions.noConfig ? null : tryFindConfigFile(options.loadOptions.configPath);
|
|
19552
|
+
}
|
|
19553
|
+
get() {
|
|
19554
|
+
return this.current;
|
|
19555
|
+
}
|
|
19556
|
+
async reload() {
|
|
19557
|
+
if (this.loading) {
|
|
19558
|
+
this.pending = true;
|
|
19559
|
+
return { ok: true };
|
|
19560
|
+
}
|
|
19561
|
+
this.loading = true;
|
|
19562
|
+
try {
|
|
19563
|
+
const next = await this.load(this.loadOptions);
|
|
19564
|
+
const restartNeeded = next.host !== this.boundHost || next.port !== this.boundPort;
|
|
19565
|
+
let diff = "";
|
|
19566
|
+
try {
|
|
19567
|
+
diff = summarizeChanges(this.current, next);
|
|
19568
|
+
} catch {
|
|
19569
|
+
diff = "";
|
|
19570
|
+
}
|
|
19571
|
+
this.current = next;
|
|
19572
|
+
if (restartNeeded) logger.warn("host/port changed — restart proxitor to apply (live reload does not re-bind the socket)");
|
|
19573
|
+
logger.info(`Config reloaded${diff ? ` — ${diff}` : " (no material changes)"}`);
|
|
19574
|
+
return { ok: true };
|
|
19575
|
+
} catch (error) {
|
|
19576
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
19577
|
+
logger.error(`Config reload failed — keeping previous config: ${msg}`);
|
|
19578
|
+
return {
|
|
19579
|
+
ok: false,
|
|
19580
|
+
error: msg
|
|
19581
|
+
};
|
|
19582
|
+
} finally {
|
|
19583
|
+
this.loading = false;
|
|
19584
|
+
if (this.pending) {
|
|
19585
|
+
this.pending = false;
|
|
19586
|
+
this.reload();
|
|
19587
|
+
}
|
|
19588
|
+
}
|
|
19589
|
+
}
|
|
19590
|
+
start() {
|
|
19591
|
+
if (this.watching) return;
|
|
19592
|
+
if (!this.resolvedPath) {
|
|
19593
|
+
logger.info("Live config reload disabled (no config file)");
|
|
19594
|
+
return;
|
|
19595
|
+
}
|
|
19596
|
+
this.watching = true;
|
|
19597
|
+
const path = this.resolvedPath;
|
|
19598
|
+
this.stopWatch = this.watch(path, this.pollIntervalMs, (curr, prev) => {
|
|
19599
|
+
try {
|
|
19600
|
+
this.onStat(path, curr, prev);
|
|
19601
|
+
} catch {}
|
|
19602
|
+
});
|
|
19603
|
+
}
|
|
19604
|
+
onStat(path, curr, prev) {
|
|
19605
|
+
if (curr.nlink === 0) {
|
|
19606
|
+
logger.warn(`config file disappeared — keeping current config (${path})`);
|
|
19607
|
+
return;
|
|
19608
|
+
}
|
|
19609
|
+
if (curr.mtimeMs === prev.mtimeMs) return;
|
|
19610
|
+
this.reload();
|
|
19611
|
+
}
|
|
19612
|
+
stop() {
|
|
19613
|
+
if (this.watching) {
|
|
19614
|
+
this.stopWatch?.();
|
|
19615
|
+
this.stopWatch = void 0;
|
|
19616
|
+
this.watching = false;
|
|
19617
|
+
}
|
|
19618
|
+
}
|
|
19619
|
+
};
|
|
19620
|
+
//#endregion
|
|
19485
19621
|
//#region node_modules/.pnpm/@hono+node-server@2.0.4_hono@4.12.25/node_modules/@hono/node-server/dist/index.mjs
|
|
19486
19622
|
var RequestError = class extends Error {
|
|
19487
19623
|
constructor(message, options) {
|
|
@@ -22876,20 +23012,23 @@ const injectChain = [
|
|
|
22876
23012
|
normalizeVolatileSystemMiddleware,
|
|
22877
23013
|
injectSessionId
|
|
22878
23014
|
];
|
|
22879
|
-
function createProxyServer(
|
|
23015
|
+
function createProxyServer(source, onReady) {
|
|
22880
23016
|
const app = new Hono();
|
|
22881
|
-
const globalRouting = buildProviderRouting(config.provider);
|
|
22882
|
-
const modelOverrideKeys = Object.keys(config.modelOverrides ?? []);
|
|
22883
23017
|
app.use("*", async (c, next) => {
|
|
22884
|
-
c.set("config",
|
|
23018
|
+
c.set("config", source.get());
|
|
22885
23019
|
await next();
|
|
22886
23020
|
});
|
|
22887
|
-
app.get("/health", (c) =>
|
|
22888
|
-
|
|
22889
|
-
|
|
22890
|
-
|
|
22891
|
-
|
|
22892
|
-
|
|
23021
|
+
app.get("/health", (c) => {
|
|
23022
|
+
const config = source.get();
|
|
23023
|
+
const globalRouting = buildProviderRouting(config.provider);
|
|
23024
|
+
const modelOverrideKeys = Object.keys(config.modelOverrides ?? []);
|
|
23025
|
+
return c.json({
|
|
23026
|
+
ok: true,
|
|
23027
|
+
upstream: config.openrouterBaseUrl,
|
|
23028
|
+
provider: globalRouting ?? "not configured",
|
|
23029
|
+
modelOverrides: modelOverrideKeys
|
|
23030
|
+
});
|
|
23031
|
+
});
|
|
22893
23032
|
for (const path of INJECT_PATHS) app.post(path, setupRequest, readBody, ...injectChain, buildUpstreamReq, forwardRequest);
|
|
22894
23033
|
app.all("*", setupRequest, readBody, resolveConfig, buildUpstreamReq, forwardRequest);
|
|
22895
23034
|
app.onError((err, c) => {
|
|
@@ -22900,20 +23039,22 @@ function createProxyServer(config, onReady) {
|
|
|
22900
23039
|
type: "proxy_internal_error"
|
|
22901
23040
|
} }, { status: 500 });
|
|
22902
23041
|
});
|
|
23042
|
+
const initial = source.get();
|
|
22903
23043
|
return serve({
|
|
22904
23044
|
fetch: app.fetch,
|
|
22905
|
-
port:
|
|
22906
|
-
hostname:
|
|
23045
|
+
port: initial.port,
|
|
23046
|
+
hostname: initial.host
|
|
22907
23047
|
}, onReady);
|
|
22908
23048
|
}
|
|
22909
23049
|
const SHUTDOWN_TIMEOUT_MS = 1e4;
|
|
22910
|
-
function startProxyServer(
|
|
22911
|
-
const server = createProxyServer(
|
|
23050
|
+
function startProxyServer(source, onReady) {
|
|
23051
|
+
const server = createProxyServer(source, onReady);
|
|
22912
23052
|
let shuttingDown = false;
|
|
22913
23053
|
function shutdown(signal) {
|
|
22914
23054
|
if (shuttingDown) return;
|
|
22915
23055
|
shuttingDown = true;
|
|
22916
23056
|
logger.info(`${signal} received — draining active connections…`);
|
|
23057
|
+
source.stop();
|
|
22917
23058
|
const timer = setTimeout(() => {
|
|
22918
23059
|
logger.warn("Forcing shutdown — drain timeout exceeded");
|
|
22919
23060
|
process.exit(1);
|
|
@@ -23031,17 +23172,24 @@ const startCommand = (0, import_cjs.command)({
|
|
|
23031
23172
|
},
|
|
23032
23173
|
handler: async ({ configPath, port, host, noConfig, openrouterKey, verbose }) => {
|
|
23033
23174
|
try {
|
|
23034
|
-
const
|
|
23175
|
+
const loadOptions = {
|
|
23035
23176
|
configPath,
|
|
23036
23177
|
noConfig,
|
|
23037
23178
|
port,
|
|
23038
23179
|
host,
|
|
23039
23180
|
openrouterKey,
|
|
23040
23181
|
verbose
|
|
23182
|
+
};
|
|
23183
|
+
const cfg = await loadConfig(loadOptions);
|
|
23184
|
+
const source = createConfigSource({
|
|
23185
|
+
loadOptions,
|
|
23186
|
+
initial: cfg
|
|
23041
23187
|
});
|
|
23042
|
-
|
|
23188
|
+
source.start();
|
|
23189
|
+
startProxyServer(source, () => {
|
|
23043
23190
|
logger.ready(`Proxitor proxy listening on ${cfg.host}:${cfg.port}`);
|
|
23044
23191
|
logger.info("Routing requests to OpenRouter");
|
|
23192
|
+
if (source.resolvedPath) logger.info(`Watching ${source.resolvedPath} for changes (live reload)`);
|
|
23045
23193
|
});
|
|
23046
23194
|
} catch (error) {
|
|
23047
23195
|
logger.error("Failed to start proxy:", error);
|