llm-relay 0.0.1
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 +236 -0
- package/config.example.json +45 -0
- package/dist/anthropic.d.ts +49 -0
- package/dist/anthropic.js +35 -0
- package/dist/anthropic.js.map +1 -0
- package/dist/backend.d.ts +36 -0
- package/dist/backend.js +135 -0
- package/dist/backend.js.map +1 -0
- package/dist/catalog.d.ts +55 -0
- package/dist/catalog.js +148 -0
- package/dist/catalog.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +137 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +88 -0
- package/dist/config.js +248 -0
- package/dist/config.js.map +1 -0
- package/dist/emitSse.d.ts +18 -0
- package/dist/emitSse.js +88 -0
- package/dist/emitSse.js.map +1 -0
- package/dist/log.d.ts +26 -0
- package/dist/log.js +24 -0
- package/dist/log.js.map +1 -0
- package/dist/registry.d.ts +47 -0
- package/dist/registry.js +80 -0
- package/dist/registry.js.map +1 -0
- package/dist/repair.d.ts +24 -0
- package/dist/repair.js +44 -0
- package/dist/repair.js.map +1 -0
- package/dist/reshaper.d.ts +46 -0
- package/dist/reshaper.js +147 -0
- package/dist/reshaper.js.map +1 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +619 -0
- package/dist/server.js.map +1 -0
- package/dist/sse.d.ts +2 -0
- package/dist/sse.js +105 -0
- package/dist/sse.js.map +1 -0
- package/dist/validator.d.ts +35 -0
- package/dist/validator.js +126 -0
- package/dist/validator.js.map +1 -0
- package/docs/tier-data.json +4601 -0
- package/package.json +39 -0
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
const DEFAULT_TTL_MS = 10 * 60 * 1000; // 10 min
|
|
5
|
+
const DEFAULT_CACHE = join(homedir(), ".llm-relay", "models-cache.json");
|
|
6
|
+
/**
|
|
7
|
+
* Live per-provider model catalog — model ids are DISCOVERED from each provider's
|
|
8
|
+
* OpenAI-compatible `/models` endpoint, never hand-maintained. In-memory TTL cache
|
|
9
|
+
* backed by a small on-disk cache so restarts start warm. Fail-open everywhere: a
|
|
10
|
+
* fetch failure serves the last-known list (or an empty one), never blocks routing.
|
|
11
|
+
*/
|
|
12
|
+
export class ModelCatalog {
|
|
13
|
+
mem = new Map();
|
|
14
|
+
ttlMs;
|
|
15
|
+
cachePath;
|
|
16
|
+
loaded = false;
|
|
17
|
+
/** Providers with a background refresh in flight — dedups stampeding probes. */
|
|
18
|
+
refreshing = new Set();
|
|
19
|
+
constructor(opts = {}) {
|
|
20
|
+
this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
21
|
+
this.cachePath = opts.cachePath === undefined ? DEFAULT_CACHE : opts.cachePath;
|
|
22
|
+
}
|
|
23
|
+
loadDisk() {
|
|
24
|
+
if (this.loaded)
|
|
25
|
+
return;
|
|
26
|
+
this.loaded = true;
|
|
27
|
+
if (!this.cachePath)
|
|
28
|
+
return;
|
|
29
|
+
try {
|
|
30
|
+
const j = JSON.parse(readFileSync(this.cachePath, "utf8"));
|
|
31
|
+
for (const [k, v] of Object.entries(j)) {
|
|
32
|
+
if (v && typeof v.fetchedAt === "number" && Array.isArray(v.models))
|
|
33
|
+
this.mem.set(k, v);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* no cache yet — first run */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
saveDisk() {
|
|
41
|
+
if (!this.cachePath)
|
|
42
|
+
return;
|
|
43
|
+
try {
|
|
44
|
+
mkdirSync(dirname(this.cachePath), { recursive: true });
|
|
45
|
+
const obj = {};
|
|
46
|
+
for (const [k, v] of this.mem)
|
|
47
|
+
obj[k] = v;
|
|
48
|
+
writeFileSync(this.cachePath, JSON.stringify(obj, null, 2) + "\n");
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* best-effort cache; never fatal */
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Cached model ids for a provider; empty array if fetch fails and no prior cache. */
|
|
55
|
+
cached(name) {
|
|
56
|
+
this.loadDisk();
|
|
57
|
+
return this.mem.get(name)?.models;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Live model ids for a provider, cached with TTL. `force` bypasses the TTL.
|
|
61
|
+
*
|
|
62
|
+
* STALE-WHILE-REVALIDATE: a fresh cache (within TTL) is served directly; a STALE
|
|
63
|
+
* cache is ALSO served immediately while a background refresh updates it — so a
|
|
64
|
+
* discovery/liveness probe (`GET /registry`) NEVER blocks on an upstream refetch.
|
|
65
|
+
* Blocking `await` happens only on a genuine cold start (no prior at all, in memory
|
|
66
|
+
* or on disk). Serves a stale cache if a blocking refresh fails; returns [] only
|
|
67
|
+
* when there is no cache AND the fetch fails. `force` still awaits (explicit refresh).
|
|
68
|
+
*/
|
|
69
|
+
async list(name, cfg, opts = {}) {
|
|
70
|
+
this.loadDisk();
|
|
71
|
+
const now = opts.now ?? Date.now();
|
|
72
|
+
const prior = this.mem.get(name);
|
|
73
|
+
if (!opts.force && prior && now - prior.fetchedAt < this.ttlMs)
|
|
74
|
+
return prior.models;
|
|
75
|
+
// Stale cache present: serve it now, refresh in the background. A probe must
|
|
76
|
+
// never pay a multi-second upstream fan-probe just because the TTL lapsed.
|
|
77
|
+
if (!opts.force && prior) {
|
|
78
|
+
this.refreshInBackground(name, cfg, opts.fetchFn);
|
|
79
|
+
return prior.models;
|
|
80
|
+
}
|
|
81
|
+
// Cold (no prior) or forced: block once to obtain / renew the list.
|
|
82
|
+
try {
|
|
83
|
+
const models = await this.fetch(cfg, opts.fetchFn ?? fetch);
|
|
84
|
+
this.mem.set(name, { fetchedAt: now, models });
|
|
85
|
+
this.saveDisk();
|
|
86
|
+
return models;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return prior?.models ?? [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Fire-and-forget catalog refresh for a stale provider, deduped so repeated probes
|
|
94
|
+
* during one refresh window spawn at most one upstream fetch. A failed refresh
|
|
95
|
+
* leaves the stale entry in place (fail-open); it is retried on the next `list`.
|
|
96
|
+
*/
|
|
97
|
+
refreshInBackground(name, cfg, fetchFn) {
|
|
98
|
+
if (this.refreshing.has(name))
|
|
99
|
+
return;
|
|
100
|
+
this.refreshing.add(name);
|
|
101
|
+
void (async () => {
|
|
102
|
+
try {
|
|
103
|
+
const models = await this.fetch(cfg, fetchFn ?? fetch);
|
|
104
|
+
this.mem.set(name, { fetchedAt: Date.now(), models });
|
|
105
|
+
this.saveDisk();
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
/* keep the stale entry; next list retries */
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
this.refreshing.delete(name);
|
|
112
|
+
}
|
|
113
|
+
})();
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Whether a provider serves a model. Returns null when the catalog is
|
|
117
|
+
* unavailable (no cache and fetch failed) — callers treat null as "unknown,
|
|
118
|
+
* proceed" so routing never hard-fails on a catalog miss.
|
|
119
|
+
*/
|
|
120
|
+
async has(name, cfg, model, opts = {}) {
|
|
121
|
+
const models = await this.list(name, cfg, opts);
|
|
122
|
+
if (models.length === 0 && !this.cached(name))
|
|
123
|
+
return null;
|
|
124
|
+
return models.includes(model);
|
|
125
|
+
}
|
|
126
|
+
async fetch(cfg, fetchFn) {
|
|
127
|
+
// Anthropic-kind backends have no OpenAI-style /models list we consume.
|
|
128
|
+
if (cfg.kind !== "openai")
|
|
129
|
+
return [];
|
|
130
|
+
const key = cfg.authEnv ? process.env[cfg.authEnv]?.trim() : undefined;
|
|
131
|
+
const headers = {};
|
|
132
|
+
if (key) {
|
|
133
|
+
if (cfg.authHeader === "authorization")
|
|
134
|
+
headers["authorization"] = `Bearer ${key}`;
|
|
135
|
+
else
|
|
136
|
+
headers["x-api-key"] = key;
|
|
137
|
+
}
|
|
138
|
+
const res = await fetchFn(cfg.base + "/models", { headers });
|
|
139
|
+
if (!res.ok)
|
|
140
|
+
throw new Error(`models fetch HTTP ${res.status}`);
|
|
141
|
+
const j = (await res.json());
|
|
142
|
+
return (j.data ?? [])
|
|
143
|
+
.map((m) => m.id)
|
|
144
|
+
.filter((s) => typeof s === "string")
|
|
145
|
+
.sort();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { loadConfig } from "./config.js";
|
|
3
|
+
import { createProxy } from "./server.js";
|
|
4
|
+
import { ModelCatalog } from "./catalog.js";
|
|
5
|
+
function argValue(flag) {
|
|
6
|
+
const i = process.argv.indexOf(flag);
|
|
7
|
+
return i !== -1 ? process.argv[i + 1] : undefined;
|
|
8
|
+
}
|
|
9
|
+
const HELP = `llm-relay — loopback Anthropic-Messages proxy that validates/repairs tool calls.
|
|
10
|
+
|
|
11
|
+
Multi-provider: config declares a providers{} registry; a request's model routes
|
|
12
|
+
to one provider by namespace ("nim/z-ai/glm-5.2") or by Claude tier (routing.tiers).
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
llm-relay [--config <path>] [overrides] start the proxy
|
|
16
|
+
llm-relay models [--provider <name>] [--refresh] list live models per provider
|
|
17
|
+
|
|
18
|
+
--config <path> Config file (default: config.json)
|
|
19
|
+
|
|
20
|
+
Overrides (win over the config file, so routing can be repointed without editing it):
|
|
21
|
+
--default <prov/model> routing.default (fallback provider/model)
|
|
22
|
+
--mode <detect|repair> mode
|
|
23
|
+
--listen <host:port> listen address (loopback only)
|
|
24
|
+
|
|
25
|
+
Model ids are discovered dynamically from each provider's /models endpoint and
|
|
26
|
+
cached (~/.llm-relay/models-cache.json, 10-min TTL). "llm-relay models" lists
|
|
27
|
+
them; --refresh forces a re-fetch. On startup the proxy warms the cache and warns
|
|
28
|
+
about any routing target its provider does not serve.
|
|
29
|
+
|
|
30
|
+
Config string values may reference environment variables as \${NAME}
|
|
31
|
+
(e.g. "base": "\${LLM_BACKEND_BASE_URL}"); an unset var is a startup error.
|
|
32
|
+
`;
|
|
33
|
+
/** Split a "provider/model" spec on the first slash. */
|
|
34
|
+
function splitSpec(spec) {
|
|
35
|
+
const i = spec.indexOf("/");
|
|
36
|
+
return i === -1 ? { provider: spec } : { provider: spec.slice(0, i), model: spec.slice(i + 1) };
|
|
37
|
+
}
|
|
38
|
+
function loadOrExit() {
|
|
39
|
+
const configPath = argValue("--config") ?? "config.json";
|
|
40
|
+
const overrides = {
|
|
41
|
+
routeDefault: argValue("--default"),
|
|
42
|
+
mode: argValue("--mode"),
|
|
43
|
+
listen: argValue("--listen"),
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
return loadConfig(configPath, overrides);
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
process.stderr.write(`llm-relay: ${e.message}\n`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** `llm-relay models` — dynamic, cached model discovery per provider. */
|
|
54
|
+
async function runModels() {
|
|
55
|
+
const cfg = loadOrExit();
|
|
56
|
+
const only = argValue("--provider");
|
|
57
|
+
const force = process.argv.includes("--refresh");
|
|
58
|
+
const catalog = new ModelCatalog();
|
|
59
|
+
const names = Object.keys(cfg.providers).filter((n) => !only || n === only);
|
|
60
|
+
if (names.length === 0) {
|
|
61
|
+
process.stderr.write(`llm-relay: no provider named "${only}"\n`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
for (const name of names) {
|
|
65
|
+
const p = cfg.providers[name];
|
|
66
|
+
if (p.kind !== "openai") {
|
|
67
|
+
process.stdout.write(`\n${name} (${p.kind}) — no /models endpoint consumed\n`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
let models = [];
|
|
71
|
+
try {
|
|
72
|
+
models = await catalog.list(name, p, { force });
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
/* fail-open */
|
|
76
|
+
}
|
|
77
|
+
if (models.length === 0) {
|
|
78
|
+
process.stdout.write(`\n${name} — (no models: unreachable, unauthorized, or empty catalog)\n`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
process.stdout.write(`\n${name} — ${models.length} models:\n`);
|
|
82
|
+
for (const m of models)
|
|
83
|
+
process.stdout.write(` ${m}\n`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Warm every provider's catalog and warn about any routing target the provider
|
|
88
|
+
* does not serve — non-blocking (fire-and-forget) so it never delays listen().
|
|
89
|
+
*/
|
|
90
|
+
async function warmAndValidate(cfg, catalog) {
|
|
91
|
+
const specs = [cfg.routing.default, ...Object.values(cfg.routing.tiers)];
|
|
92
|
+
const seen = new Set();
|
|
93
|
+
for (const spec of specs) {
|
|
94
|
+
if (seen.has(spec))
|
|
95
|
+
continue;
|
|
96
|
+
seen.add(spec);
|
|
97
|
+
const { provider, model } = splitSpec(spec);
|
|
98
|
+
const p = cfg.providers[provider];
|
|
99
|
+
if (!p || p.kind !== "openai" || !model)
|
|
100
|
+
continue;
|
|
101
|
+
const known = await catalog.has(provider, p, model);
|
|
102
|
+
if (known === false) {
|
|
103
|
+
process.stderr.write(`llm-relay: routing target "${spec}" — provider "${provider}" does not list model "${model}". ` +
|
|
104
|
+
`Requests routed here will fail; run "llm-relay models --provider ${provider}" to see valid ids.\n`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function runProxy() {
|
|
109
|
+
const cfg = loadOrExit();
|
|
110
|
+
const catalog = new ModelCatalog();
|
|
111
|
+
const server = createProxy(cfg, { catalog });
|
|
112
|
+
server.listen(cfg.port, cfg.host, () => {
|
|
113
|
+
const providers = Object.keys(cfg.providers).join(",");
|
|
114
|
+
process.stderr.write(`llm-relay listening on http://${cfg.host}:${cfg.port} ` +
|
|
115
|
+
`(mode=${cfg.mode}, providers=[${providers}], default=${cfg.routing.default})\n`);
|
|
116
|
+
void warmAndValidate(cfg, catalog);
|
|
117
|
+
});
|
|
118
|
+
for (const sig of ["SIGINT", "SIGTERM"]) {
|
|
119
|
+
process.on(sig, () => server.close(() => process.exit(0)));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function main() {
|
|
123
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
124
|
+
process.stdout.write(HELP);
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
if (process.argv[2] === "models") {
|
|
128
|
+
runModels().catch((e) => {
|
|
129
|
+
process.stderr.write(`repair-proxy: ${e.message}\n`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
runProxy();
|
|
135
|
+
}
|
|
136
|
+
main();
|
|
137
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export type Mode = "detect" | "repair" | "strict";
|
|
2
|
+
export type AuthHeader = "x-api-key" | "authorization";
|
|
3
|
+
export type Kind = "anthropic" | "openai";
|
|
4
|
+
export interface ReshaperConfig {
|
|
5
|
+
base: string;
|
|
6
|
+
model: string;
|
|
7
|
+
/** "anthropic": call /v1/messages. "openai": call /chat/completions (NIM/vLLM). */
|
|
8
|
+
kind: Kind;
|
|
9
|
+
authEnv?: string;
|
|
10
|
+
authHeader: AuthHeader;
|
|
11
|
+
timeoutMs: number;
|
|
12
|
+
}
|
|
13
|
+
/** One HTTP backend provider in the registry (NIM, OpenRouter, Gemini API, …). */
|
|
14
|
+
export interface ProviderConfig {
|
|
15
|
+
base: string;
|
|
16
|
+
/**
|
|
17
|
+
* "anthropic": provider speaks Anthropic Messages; forward as-is.
|
|
18
|
+
* "openai": provider is OpenAI-compatible (NIM/vLLM/OpenRouter/Gemini-openai);
|
|
19
|
+
* the proxy translates request+response via llm-bridge.
|
|
20
|
+
*/
|
|
21
|
+
kind: Kind;
|
|
22
|
+
authEnv?: string;
|
|
23
|
+
/** Which header to inject the provider key into. Default: authorization (openai) / x-api-key (anthropic). */
|
|
24
|
+
authHeader: AuthHeader;
|
|
25
|
+
/** Backend request deadline in ms. Default 120000. */
|
|
26
|
+
timeoutMs: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* How an inbound request's `model` maps to a provider + backend model.
|
|
30
|
+
* - `default`: fallback "provider/model" spec when nothing else matches.
|
|
31
|
+
* - `tiers`: Claude tier name ("opus"/"sonnet"/"haiku"/"fable") → "provider/model".
|
|
32
|
+
* A request may also address a provider directly with a namespaced model id
|
|
33
|
+
* ("nim/z-ai/glm-5.2") — the prefix picks the provider, the rest is the model.
|
|
34
|
+
*/
|
|
35
|
+
export interface Routing {
|
|
36
|
+
default: string;
|
|
37
|
+
tiers: Record<string, string>;
|
|
38
|
+
}
|
|
39
|
+
/** A request routed to a concrete provider + backend model. */
|
|
40
|
+
export interface ResolvedTarget {
|
|
41
|
+
provider: string;
|
|
42
|
+
base: string;
|
|
43
|
+
kind: Kind;
|
|
44
|
+
/** Real backend model id (required for openai; absent = anthropic passthrough). */
|
|
45
|
+
model?: string;
|
|
46
|
+
authEnv?: string;
|
|
47
|
+
authHeader: AuthHeader;
|
|
48
|
+
timeoutMs: number;
|
|
49
|
+
}
|
|
50
|
+
export interface Config {
|
|
51
|
+
host: string;
|
|
52
|
+
port: number;
|
|
53
|
+
providers: Record<string, ProviderConfig>;
|
|
54
|
+
routing: Routing;
|
|
55
|
+
mode: Mode;
|
|
56
|
+
/** Explicit global reshaper override; otherwise repair reshapes on the resolved target itself. */
|
|
57
|
+
reshaper?: ReshaperConfig;
|
|
58
|
+
repair: {
|
|
59
|
+
maxAttempts: number;
|
|
60
|
+
destructiveTools: string[];
|
|
61
|
+
};
|
|
62
|
+
log: {
|
|
63
|
+
level: "metadata" | "silent";
|
|
64
|
+
file: string | null;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
declare const DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
|
|
68
|
+
export { DEFAULT_ANTHROPIC_VERSION };
|
|
69
|
+
/** A routing failure — surfaced to the client as a clean 400, never a crash. */
|
|
70
|
+
export declare class RoutingError extends Error {
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve an inbound `model` to a concrete provider + backend model. Throws
|
|
74
|
+
* RoutingError if the spec names an unknown provider or omits a model an
|
|
75
|
+
* openai provider requires.
|
|
76
|
+
*/
|
|
77
|
+
export declare function resolveTarget(model: string | null, cfg: Config): ResolvedTarget;
|
|
78
|
+
/** The reshaper for a resolved target when no explicit global reshaper is set. */
|
|
79
|
+
export declare function reshaperForTarget(target: ResolvedTarget): ReshaperConfig | undefined;
|
|
80
|
+
/** CLI overrides, applied over the file so routing can be repointed without editing it. */
|
|
81
|
+
export interface ConfigOverrides {
|
|
82
|
+
listen?: string | undefined;
|
|
83
|
+
/** Override routing.default with a "provider/model" spec. */
|
|
84
|
+
routeDefault?: string | undefined;
|
|
85
|
+
mode?: string | undefined;
|
|
86
|
+
}
|
|
87
|
+
/** Load + validate a config file, failing loudly on anything unusable. */
|
|
88
|
+
export declare function loadConfig(path: string, overrides?: ConfigOverrides): Config;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
const DEFAULT_DESTRUCTIVE = ["rm", "delete", "remove", "push", "force", "overwrite", "drop", "reset"];
|
|
3
|
+
const DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
|
|
4
|
+
export { DEFAULT_ANTHROPIC_VERSION };
|
|
5
|
+
/** Claude tier names, longest-first so "haiku"/"sonnet" match before generic bits. */
|
|
6
|
+
const TIER_NAMES = ["opus", "sonnet", "haiku", "fable"];
|
|
7
|
+
/** A routing failure — surfaced to the client as a clean 400, never a crash. */
|
|
8
|
+
export class RoutingError extends Error {
|
|
9
|
+
}
|
|
10
|
+
/** Detect the Claude tier a model id belongs to, or null. */
|
|
11
|
+
function detectTier(model) {
|
|
12
|
+
const m = model.toLowerCase();
|
|
13
|
+
for (const t of TIER_NAMES)
|
|
14
|
+
if (m.includes(t))
|
|
15
|
+
return t;
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Pick the "provider/model" spec for an inbound model id:
|
|
20
|
+
* 1. namespaced (`known-provider/…`) → use verbatim;
|
|
21
|
+
* 2. a Claude tier with a configured mapping → that tier's spec;
|
|
22
|
+
* 3. otherwise the routing default.
|
|
23
|
+
*/
|
|
24
|
+
function pickSpec(model, cfg) {
|
|
25
|
+
if (model) {
|
|
26
|
+
const slash = model.indexOf("/");
|
|
27
|
+
if (slash !== -1 && cfg.providers[model.slice(0, slash)])
|
|
28
|
+
return model;
|
|
29
|
+
const tier = detectTier(model);
|
|
30
|
+
if (tier && cfg.routing.tiers[tier])
|
|
31
|
+
return cfg.routing.tiers[tier];
|
|
32
|
+
}
|
|
33
|
+
return cfg.routing.default;
|
|
34
|
+
}
|
|
35
|
+
/** Split a "provider/model" spec into its parts (model may contain further slashes). */
|
|
36
|
+
function splitSpec(spec) {
|
|
37
|
+
const slash = spec.indexOf("/");
|
|
38
|
+
if (slash === -1)
|
|
39
|
+
return { provider: spec };
|
|
40
|
+
return { provider: spec.slice(0, slash), model: spec.slice(slash + 1) };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve an inbound `model` to a concrete provider + backend model. Throws
|
|
44
|
+
* RoutingError if the spec names an unknown provider or omits a model an
|
|
45
|
+
* openai provider requires.
|
|
46
|
+
*/
|
|
47
|
+
export function resolveTarget(model, cfg) {
|
|
48
|
+
const spec = pickSpec(model, cfg);
|
|
49
|
+
const { provider, model: realModel } = splitSpec(spec);
|
|
50
|
+
const p = cfg.providers[provider];
|
|
51
|
+
if (!p) {
|
|
52
|
+
throw new RoutingError(`no provider "${provider}" configured (routed from model "${model ?? "<none>"}" → "${spec}")`);
|
|
53
|
+
}
|
|
54
|
+
if (p.kind === "openai" && !realModel) {
|
|
55
|
+
throw new RoutingError(`provider "${provider}" is openai and needs a model id (spec "${spec}")`);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
provider,
|
|
59
|
+
base: p.base,
|
|
60
|
+
kind: p.kind,
|
|
61
|
+
authHeader: p.authHeader,
|
|
62
|
+
timeoutMs: p.timeoutMs,
|
|
63
|
+
...(realModel !== undefined ? { model: realModel } : {}),
|
|
64
|
+
...(p.authEnv ? { authEnv: p.authEnv } : {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** The reshaper for a resolved target when no explicit global reshaper is set. */
|
|
68
|
+
export function reshaperForTarget(target) {
|
|
69
|
+
if (target.kind === "openai" && target.model !== undefined) {
|
|
70
|
+
return {
|
|
71
|
+
base: target.base,
|
|
72
|
+
model: target.model,
|
|
73
|
+
kind: "openai",
|
|
74
|
+
authHeader: target.authHeader,
|
|
75
|
+
timeoutMs: Math.min(target.timeoutMs, 60000),
|
|
76
|
+
...(target.authEnv ? { authEnv: target.authEnv } : {}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Expand `${ENV}` references in a config string against process.env, failing
|
|
83
|
+
* loudly if a referenced variable is unset — so a missing provider URL/key is a
|
|
84
|
+
* clear startup error, never a silent empty value.
|
|
85
|
+
*/
|
|
86
|
+
function expandEnv(value, where) {
|
|
87
|
+
return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_m, name) => {
|
|
88
|
+
const v = process.env[name];
|
|
89
|
+
if (v === undefined)
|
|
90
|
+
throw new Error(`config.${where} references unset env var \${${name}}`);
|
|
91
|
+
return v;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
function parseAuthHeader(raw, dflt) {
|
|
95
|
+
return raw === "authorization" ? "authorization" : raw === "x-api-key" ? "x-api-key" : dflt;
|
|
96
|
+
}
|
|
97
|
+
/** Load + validate a config file, failing loudly on anything unusable. */
|
|
98
|
+
export function loadConfig(path, overrides = {}) {
|
|
99
|
+
let parsed;
|
|
100
|
+
try {
|
|
101
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
throw new Error(`could not read/parse config at ${path}: ${e.message}`);
|
|
105
|
+
}
|
|
106
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
107
|
+
throw new Error(`config at ${path} is not a JSON object`);
|
|
108
|
+
}
|
|
109
|
+
const c = parsed;
|
|
110
|
+
if (overrides.listen !== undefined)
|
|
111
|
+
c.listen = overrides.listen;
|
|
112
|
+
if (overrides.mode !== undefined)
|
|
113
|
+
c.mode = overrides.mode;
|
|
114
|
+
const listen = typeof c.listen === "string" ? expandEnv(c.listen, "listen") : "127.0.0.1:8791";
|
|
115
|
+
const [host, portStr] = splitHostPort(listen);
|
|
116
|
+
const port = Number(portStr);
|
|
117
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
118
|
+
throw new Error(`config.listen has invalid port: "${listen}"`);
|
|
119
|
+
}
|
|
120
|
+
// This tool holds provider keys and does no auth of its own — loopback only.
|
|
121
|
+
if (host !== "127.0.0.1" && host !== "localhost" && host !== "::1") {
|
|
122
|
+
throw new Error(`config.listen must bind loopback (127.0.0.1/localhost/::1), got "${host}". ` +
|
|
123
|
+
`Refusing to expose a keyed, unauthenticated proxy on a non-loopback address.`);
|
|
124
|
+
}
|
|
125
|
+
const providers = parseProviders(c.providers);
|
|
126
|
+
const routing = parseRouting(c.routing, providers, overrides.routeDefault);
|
|
127
|
+
const mode = normalizeMode(c.mode);
|
|
128
|
+
// A repair-mode target reshapes on itself (openai) or via the explicit global
|
|
129
|
+
// reshaper. An anthropic provider has no fixed model id, so if any provider is
|
|
130
|
+
// anthropic and no explicit reshaper is set, repair can't reshape it — reject.
|
|
131
|
+
const reshaper = parseReshaper(c.reshaper);
|
|
132
|
+
if (mode === "repair" && !reshaper) {
|
|
133
|
+
const anthropicProvider = Object.entries(providers).find(([, p]) => p.kind === "anthropic");
|
|
134
|
+
if (anthropicProvider) {
|
|
135
|
+
throw new Error(`mode "repair" requires a config.reshaper { base, model, authEnv } because ` +
|
|
136
|
+
`provider "${anthropicProvider[0]}" is anthropic (no fixed model to reshape on)`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const repairRaw = (c.repair ?? {});
|
|
140
|
+
const maxAttempts = typeof repairRaw.maxAttempts === "number" && repairRaw.maxAttempts > 0
|
|
141
|
+
? Math.floor(repairRaw.maxAttempts)
|
|
142
|
+
: 2;
|
|
143
|
+
const destructiveTools = Array.isArray(repairRaw.destructiveTools)
|
|
144
|
+
? repairRaw.destructiveTools.filter((s) => typeof s === "string")
|
|
145
|
+
: DEFAULT_DESTRUCTIVE;
|
|
146
|
+
const logRaw = (c.log ?? {});
|
|
147
|
+
const level = logRaw.level === "silent" ? "silent" : "metadata";
|
|
148
|
+
const file = typeof logRaw.file === "string" ? logRaw.file : null;
|
|
149
|
+
return {
|
|
150
|
+
host,
|
|
151
|
+
port,
|
|
152
|
+
providers,
|
|
153
|
+
routing,
|
|
154
|
+
mode,
|
|
155
|
+
...(reshaper ? { reshaper } : {}),
|
|
156
|
+
repair: { maxAttempts, destructiveTools },
|
|
157
|
+
log: { level, file },
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function parseProviders(raw) {
|
|
161
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
162
|
+
throw new Error(`config.providers (object of named providers) is required`);
|
|
163
|
+
}
|
|
164
|
+
const out = {};
|
|
165
|
+
for (const [name, v] of Object.entries(raw)) {
|
|
166
|
+
if (typeof v !== "object" || v === null) {
|
|
167
|
+
throw new Error(`config.providers.${name} must be an object`);
|
|
168
|
+
}
|
|
169
|
+
const p = v;
|
|
170
|
+
if (typeof p.base !== "string") {
|
|
171
|
+
throw new Error(`config.providers.${name}.base (string URL) is required`);
|
|
172
|
+
}
|
|
173
|
+
const kind = p.kind === "openai" ? "openai" : "anthropic";
|
|
174
|
+
const defaultAuthHeader = kind === "openai" ? "authorization" : "x-api-key";
|
|
175
|
+
out[name] = {
|
|
176
|
+
base: expandEnv(p.base, `providers.${name}.base`).trim().replace(/\/+$/, ""),
|
|
177
|
+
kind,
|
|
178
|
+
authHeader: parseAuthHeader(p.authHeader, defaultAuthHeader),
|
|
179
|
+
timeoutMs: typeof p.timeoutMs === "number" && p.timeoutMs > 0 ? p.timeoutMs : 120000,
|
|
180
|
+
...(typeof p.authEnv === "string" ? { authEnv: p.authEnv } : {}),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (Object.keys(out).length === 0) {
|
|
184
|
+
throw new Error(`config.providers must define at least one provider`);
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
function parseRouting(raw, providers, overrideDefault) {
|
|
189
|
+
const r = (typeof raw === "object" && raw !== null ? raw : {});
|
|
190
|
+
const dflt = overrideDefault !== undefined ? overrideDefault : r.default;
|
|
191
|
+
if (typeof dflt !== "string" || dflt.length === 0) {
|
|
192
|
+
throw new Error(`config.routing.default ("provider/model") is required`);
|
|
193
|
+
}
|
|
194
|
+
const tiers = {};
|
|
195
|
+
if (typeof r.tiers === "object" && r.tiers !== null) {
|
|
196
|
+
for (const [k, v] of Object.entries(r.tiers)) {
|
|
197
|
+
if (typeof v === "string" && v.length > 0)
|
|
198
|
+
tiers[k] = v;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const routing = { default: dflt, tiers };
|
|
202
|
+
// Fail loudly at load time if any spec names an unknown provider.
|
|
203
|
+
assertSpecResolvable(routing.default, providers, "routing.default");
|
|
204
|
+
for (const [tier, spec] of Object.entries(tiers)) {
|
|
205
|
+
assertSpecResolvable(spec, providers, `routing.tiers.${tier}`);
|
|
206
|
+
}
|
|
207
|
+
return routing;
|
|
208
|
+
}
|
|
209
|
+
function assertSpecResolvable(spec, providers, where) {
|
|
210
|
+
const { provider, model } = splitSpec(spec);
|
|
211
|
+
const p = providers[provider];
|
|
212
|
+
if (!p)
|
|
213
|
+
throw new Error(`config.${where} "${spec}" names unknown provider "${provider}"`);
|
|
214
|
+
if (p.kind === "openai" && !model)
|
|
215
|
+
throw new Error(`config.${where} "${spec}" needs a model id for openai provider "${provider}"`);
|
|
216
|
+
}
|
|
217
|
+
function parseReshaper(raw) {
|
|
218
|
+
if (typeof raw !== "object" || raw === null)
|
|
219
|
+
return undefined;
|
|
220
|
+
const r = raw;
|
|
221
|
+
if (typeof r.base !== "string" || typeof r.model !== "string")
|
|
222
|
+
return undefined;
|
|
223
|
+
const kind = r.kind === "openai" ? "openai" : "anthropic";
|
|
224
|
+
const defaultAuthHeader = kind === "openai" ? "authorization" : "x-api-key";
|
|
225
|
+
return {
|
|
226
|
+
base: expandEnv(r.base, "reshaper.base").trim().replace(/\/+$/, ""),
|
|
227
|
+
model: expandEnv(r.model, "reshaper.model"),
|
|
228
|
+
kind,
|
|
229
|
+
authHeader: parseAuthHeader(r.authHeader, defaultAuthHeader),
|
|
230
|
+
timeoutMs: typeof r.timeoutMs === "number" && r.timeoutMs > 0 ? r.timeoutMs : 60000,
|
|
231
|
+
...(typeof r.authEnv === "string" ? { authEnv: r.authEnv } : {}),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function splitHostPort(listen) {
|
|
235
|
+
const bracket = /^\[(.+)\]:(\d+)$/.exec(listen);
|
|
236
|
+
if (bracket)
|
|
237
|
+
return [bracket[1], bracket[2]];
|
|
238
|
+
const idx = listen.lastIndexOf(":");
|
|
239
|
+
if (idx === -1)
|
|
240
|
+
return ["127.0.0.1", listen];
|
|
241
|
+
return [listen.slice(0, idx) || "127.0.0.1", listen.slice(idx + 1)];
|
|
242
|
+
}
|
|
243
|
+
function normalizeMode(v) {
|
|
244
|
+
if (v === "detect" || v === "repair" || v === "strict")
|
|
245
|
+
return v;
|
|
246
|
+
return "detect";
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|