saros-proxy 1.0.0 → 1.1.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 +83 -16
- package/assets/models.json +3349 -0
- package/dist/cli/autostart.d.ts +13 -6
- package/dist/cli/autostart.js +317 -60
- package/dist/cli/autostart.js.map +1 -1
- package/dist/cli/daemon.js +89 -14
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/harness-models.d.ts +5 -4
- package/dist/cli/harness-models.js +77 -30
- package/dist/cli/harness-models.js.map +1 -1
- package/dist/cli/help.js +5 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/log-render.d.ts +16 -0
- package/dist/cli/log-render.js +82 -0
- package/dist/cli/log-render.js.map +1 -0
- package/dist/cli/opencode-config.d.ts +1 -0
- package/dist/cli/opencode-config.js +7 -6
- package/dist/cli/opencode-config.js.map +1 -1
- package/dist/config.js +20 -4
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -1
- package/dist/index.js +29 -36
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +16 -0
- package/dist/logger.js +56 -23
- package/dist/logger.js.map +1 -1
- package/dist/model-ladder.d.ts +30 -0
- package/dist/model-ladder.js +45 -0
- package/dist/model-ladder.js.map +1 -0
- package/dist/models-catalog.d.ts +25 -4
- package/dist/models-catalog.js +41 -12
- package/dist/models-catalog.js.map +1 -1
- package/dist/models-dev.d.ts +72 -0
- package/dist/models-dev.js +196 -0
- package/dist/models-dev.js.map +1 -0
- package/dist/models-refresh.d.ts +144 -0
- package/dist/models-refresh.js +522 -0
- package/dist/models-refresh.js.map +1 -0
- package/dist/models-sync.d.ts +6 -10
- package/dist/models-sync.js +26 -60
- package/dist/models-sync.js.map +1 -1
- package/package.json +5 -5
- package/models.json +0 -1440
package/dist/models-catalog.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* models-catalog.ts — Single source of truth for the bundled model catalog.
|
|
3
3
|
*
|
|
4
|
-
* `models.json`
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* `assets/models.json` is the canonical definition — generated by
|
|
5
|
+
* `npm run models:refresh`, never edited by hand. It is read from disk at
|
|
6
|
+
* runtime (not imported) because `rootDir` is `./src` while the file lives
|
|
7
|
+
* outside it. The same path is used by the daemon to seed
|
|
7
8
|
* `~/.config/saros/models.json`.
|
|
8
9
|
*
|
|
9
10
|
* A missing or malformed catalog degrades to an empty map (callers fall back
|
|
@@ -12,9 +13,29 @@
|
|
|
12
13
|
/**
|
|
13
14
|
* Load the model catalog from disk, with in-memory caching.
|
|
14
15
|
*
|
|
15
|
-
* @param filePath — Catalog path (defaults to the bundled `models.json`)
|
|
16
|
+
* @param filePath — Catalog path (defaults to the bundled `assets/models.json`)
|
|
16
17
|
* @returns The parsed catalog, or `{}` when the file is missing/invalid
|
|
17
18
|
*/
|
|
18
19
|
export declare function loadModelsCatalog(filePath?: string): Record<string, unknown>;
|
|
19
20
|
/** Clear the in-memory cache — test helper. */
|
|
20
21
|
export declare function resetModelsCatalogCache(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Parse catalog content, throwing on invalid JSON or a non-object payload.
|
|
24
|
+
*
|
|
25
|
+
* `loadModelsCatalog` degrades to an empty map instead; this is for callers
|
|
26
|
+
* that must tell a corrupt file apart from an empty one (the daemon seed).
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseCatalogObject(raw: string): Record<string, unknown>;
|
|
29
|
+
/** Stable on-disk shape: 2-space JSON with a trailing newline. */
|
|
30
|
+
export declare function serializeCatalogObject(models: Record<string, unknown>): string;
|
|
31
|
+
/**
|
|
32
|
+
* Add the bundle's missing keys to a user catalog.
|
|
33
|
+
*
|
|
34
|
+
* Existing keys always win — a value the user customized is never overwritten.
|
|
35
|
+
* Returns `added: []` when the user copy already covers the bundle, so the
|
|
36
|
+
* caller can skip the write entirely.
|
|
37
|
+
*/
|
|
38
|
+
export declare function mergeMissingModels(existing: Record<string, unknown>, bundle: Record<string, unknown>): {
|
|
39
|
+
models: Record<string, unknown>;
|
|
40
|
+
added: string[];
|
|
41
|
+
};
|
package/dist/models-catalog.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* models-catalog.ts — Single source of truth for the bundled model catalog.
|
|
3
3
|
*
|
|
4
|
-
* `models.json`
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* `assets/models.json` is the canonical definition — generated by
|
|
5
|
+
* `npm run models:refresh`, never edited by hand. It is read from disk at
|
|
6
|
+
* runtime (not imported) because `rootDir` is `./src` while the file lives
|
|
7
|
+
* outside it. The same path is used by the daemon to seed
|
|
7
8
|
* `~/.config/saros/models.json`.
|
|
8
9
|
*
|
|
9
10
|
* A missing or malformed catalog degrades to an empty map (callers fall back
|
|
@@ -15,14 +16,14 @@ import { fileURLToPath } from 'node:url';
|
|
|
15
16
|
import { logger } from './logger.js';
|
|
16
17
|
/** Package root — works both from `src/` (tsx/vitest) and `dist/` (build). */
|
|
17
18
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
-
/** Bundled catalog path: `<package root>/models.json`. */
|
|
19
|
-
const BUNDLED_MODELS_PATH = join(PACKAGE_ROOT, 'models.json');
|
|
19
|
+
/** Bundled catalog path: `<package root>/assets/models.json`. */
|
|
20
|
+
const BUNDLED_MODELS_PATH = join(PACKAGE_ROOT, 'assets', 'models.json');
|
|
20
21
|
/** Parsed catalogs, keyed by absolute path (parsed once per process). */
|
|
21
22
|
const cache = new Map();
|
|
22
23
|
/**
|
|
23
24
|
* Load the model catalog from disk, with in-memory caching.
|
|
24
25
|
*
|
|
25
|
-
* @param filePath — Catalog path (defaults to the bundled `models.json`)
|
|
26
|
+
* @param filePath — Catalog path (defaults to the bundled `assets/models.json`)
|
|
26
27
|
* @returns The parsed catalog, or `{}` when the file is missing/invalid
|
|
27
28
|
*/
|
|
28
29
|
export function loadModelsCatalog(filePath = BUNDLED_MODELS_PATH) {
|
|
@@ -40,16 +41,44 @@ export function resetModelsCatalogCache() {
|
|
|
40
41
|
/** Read and validate one catalog file. Never throws. */
|
|
41
42
|
function readCatalog(filePath) {
|
|
42
43
|
try {
|
|
43
|
-
|
|
44
|
-
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
45
|
-
logger.warn({ path: filePath }, 'models catalog is not a JSON object; using empty catalog');
|
|
46
|
-
return {};
|
|
47
|
-
}
|
|
48
|
-
return parsed;
|
|
44
|
+
return parseCatalogObject(readFileSync(filePath, 'utf-8'));
|
|
49
45
|
}
|
|
50
46
|
catch (err) {
|
|
51
47
|
logger.warn({ err, path: filePath }, 'failed to load models catalog; using empty catalog');
|
|
52
48
|
return {};
|
|
53
49
|
}
|
|
54
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Parse catalog content, throwing on invalid JSON or a non-object payload.
|
|
53
|
+
*
|
|
54
|
+
* `loadModelsCatalog` degrades to an empty map instead; this is for callers
|
|
55
|
+
* that must tell a corrupt file apart from an empty one (the daemon seed).
|
|
56
|
+
*/
|
|
57
|
+
export function parseCatalogObject(raw) {
|
|
58
|
+
const parsed = JSON.parse(raw);
|
|
59
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
60
|
+
throw new Error('models catalog is not a JSON object');
|
|
61
|
+
}
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
64
|
+
/** Stable on-disk shape: 2-space JSON with a trailing newline. */
|
|
65
|
+
export function serializeCatalogObject(models) {
|
|
66
|
+
return `${JSON.stringify(models, null, 2)}\n`;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Add the bundle's missing keys to a user catalog.
|
|
70
|
+
*
|
|
71
|
+
* Existing keys always win — a value the user customized is never overwritten.
|
|
72
|
+
* Returns `added: []` when the user copy already covers the bundle, so the
|
|
73
|
+
* caller can skip the write entirely.
|
|
74
|
+
*/
|
|
75
|
+
export function mergeMissingModels(existing, bundle) {
|
|
76
|
+
const added = Object.keys(bundle).filter((key) => !(key in existing));
|
|
77
|
+
if (added.length === 0)
|
|
78
|
+
return { models: existing, added };
|
|
79
|
+
const models = { ...existing };
|
|
80
|
+
for (const key of added)
|
|
81
|
+
models[key] = bundle[key];
|
|
82
|
+
return { models, added };
|
|
83
|
+
}
|
|
55
84
|
//# sourceMappingURL=models-catalog.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models-catalog.js","sourceRoot":"","sources":["../src/models-catalog.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"models-catalog.js","sourceRoot":"","sources":["../src/models-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,8EAA8E;AAC9E,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAE5E,iEAAiE;AACjE,MAAM,mBAAmB,GAAG,IAAI,CAAC,YAAY,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;AAExE,yEAAyE;AACzE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAmC,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAQ,GAAW,mBAAmB;IACtE,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAE1B,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACrC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC5B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,uBAAuB;IACrC,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC;AAED,wDAAwD;AACxD,SAAS,WAAW,CAAC,QAAgB;IACnC,IAAI,CAAC;QACH,OAAO,kBAAkB,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,oDAAoD,CAAC,CAAC;QAC3F,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAExC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,MAAiC,CAAC;AAC3C,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,sBAAsB,CAAC,MAA+B;IACpE,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAChD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAiC,EACjC,MAA+B;IAE/B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC;IACtE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAE3D,MAAM,MAAM,GAA4B,EAAE,GAAG,QAAQ,EAAE,CAAC;IACxD,KAAK,MAAM,GAAG,IAAI,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACnD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* models-dev.ts — Read models.dev canonical metadata per provider.
|
|
3
|
+
*
|
|
4
|
+
* `GET https://models.dev/api.json` is one document keyed by provider id
|
|
5
|
+
* (235 providers, 5.9 MB on 2026-11-06). Saros only consumes the providers it
|
|
6
|
+
* proxies; this module owns the fetch, the per-provider extraction and the TTL
|
|
7
|
+
* cache so every consumer (opencode.json sync, pi/omp harness sync) reads the
|
|
8
|
+
* same slice the same way.
|
|
9
|
+
*
|
|
10
|
+
* The request sends a stable CLI `User-Agent`. models.dev's edge has answered
|
|
11
|
+
* 403 to a library-default UA while `User-Agent: cli` got 200 (measured
|
|
12
|
+
* 2026-11-06); a recheck minutes later returned 200 for both, so treat the
|
|
13
|
+
* header as a fixed contract — it keeps the fetch independent of whatever UA
|
|
14
|
+
* the local HTTP stack defaults to — not as a reproducible gate.
|
|
15
|
+
*/
|
|
16
|
+
/** Model id → models.dev metadata entry. */
|
|
17
|
+
export type ModelsDevModelMap = Record<string, Record<string, unknown>>;
|
|
18
|
+
/** Identity sent to models.dev — a stable CLI UA, never the HTTP stack default. */
|
|
19
|
+
export declare const MODELS_DEV_USER_AGENT = "saros-proxy";
|
|
20
|
+
/**
|
|
21
|
+
* Extract one provider's model map from a parsed models.dev document.
|
|
22
|
+
*
|
|
23
|
+
* Pure — callers handle JSON parsing and error recovery.
|
|
24
|
+
*
|
|
25
|
+
* @param parsed — Parsed models.dev JSON (`{ providerId: { models: {...} } }`)
|
|
26
|
+
* @param providerId — models.dev provider id (e.g. "opencode-go")
|
|
27
|
+
* @returns Model id → metadata map, or `{}` when the provider is absent
|
|
28
|
+
* or malformed
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractProviderModels(parsed: Record<string, unknown>, providerId: string): ModelsDevModelMap;
|
|
31
|
+
/**
|
|
32
|
+
* Pick only whitelisted fields from a models.dev metadata entry.
|
|
33
|
+
*
|
|
34
|
+
* Whitelisting keeps provider-specific/unknown keys (e.g. `provider`,
|
|
35
|
+
* `release_date`) out of configs written by saros.
|
|
36
|
+
*/
|
|
37
|
+
export declare function pickModelsDevFields(entry: Record<string, unknown>): Record<string, unknown>;
|
|
38
|
+
/**
|
|
39
|
+
* Bare names that two or more entries of one catalog normalize to.
|
|
40
|
+
*
|
|
41
|
+
* Matching by such a name would return whichever entry `Object.keys` yields
|
|
42
|
+
* first — silently pricing/limiting the wrong model — so callers must fall
|
|
43
|
+
* back to exact ids. `models-refresh` aborts the generation on the same
|
|
44
|
+
* condition; runtime stays lenient but warns once per slice.
|
|
45
|
+
*
|
|
46
|
+
* @param models — id → entry catalog (models.dev slice or bundled catalog)
|
|
47
|
+
* @param normalize — id → bare name function for that catalog's id shape
|
|
48
|
+
*/
|
|
49
|
+
export declare function collidingBareNames(models: Record<string, unknown>, normalize: (id: string) => string): Set<string>;
|
|
50
|
+
/**
|
|
51
|
+
* Find a model entry inside one provider's slice.
|
|
52
|
+
*
|
|
53
|
+
* Exact id first, then the bare name: live catalogs prefix ids
|
|
54
|
+
* (`deepseek/deepseek-v4-flash`) while models.dev may key the same model
|
|
55
|
+
* bare, and vice versa. Matching stays inside the provider slice so a
|
|
56
|
+
* commandcode model never receives opencode-go prices.
|
|
57
|
+
*
|
|
58
|
+
* @returns The metadata entry, or undefined when not found
|
|
59
|
+
*/
|
|
60
|
+
export declare function findProviderModel(models: ModelsDevModelMap, modelId: string): Record<string, unknown> | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Fetch one provider's model map, with the shared TTL cache.
|
|
63
|
+
*
|
|
64
|
+
* The document is fetched once and split per provider, so asking for both
|
|
65
|
+
* opencode-go and commandcode costs a single request.
|
|
66
|
+
*
|
|
67
|
+
* @returns Model id → metadata map; `{}` when the provider is absent;
|
|
68
|
+
* null when the document could not be fetched/parsed
|
|
69
|
+
*/
|
|
70
|
+
export declare function fetchModelsDevProvider(providerId: string): Promise<ModelsDevModelMap | null>;
|
|
71
|
+
/** Clear the models.dev document cache (test helper). */
|
|
72
|
+
export declare function resetModelsDevCache(): void;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* models-dev.ts — Read models.dev canonical metadata per provider.
|
|
3
|
+
*
|
|
4
|
+
* `GET https://models.dev/api.json` is one document keyed by provider id
|
|
5
|
+
* (235 providers, 5.9 MB on 2026-11-06). Saros only consumes the providers it
|
|
6
|
+
* proxies; this module owns the fetch, the per-provider extraction and the TTL
|
|
7
|
+
* cache so every consumer (opencode.json sync, pi/omp harness sync) reads the
|
|
8
|
+
* same slice the same way.
|
|
9
|
+
*
|
|
10
|
+
* The request sends a stable CLI `User-Agent`. models.dev's edge has answered
|
|
11
|
+
* 403 to a library-default UA while `User-Agent: cli` got 200 (measured
|
|
12
|
+
* 2026-11-06); a recheck minutes later returned 200 for both, so treat the
|
|
13
|
+
* header as a fixed contract — it keeps the fetch independent of whatever UA
|
|
14
|
+
* the local HTTP stack defaults to — not as a reproducible gate.
|
|
15
|
+
*/
|
|
16
|
+
import { MODELS_DEV_CACHE_TTL_MS, MODELS_DEV_SAFE_FIELDS, MODELS_DEV_TIMEOUT_MS, MODELS_DEV_URL, } from './constants.js';
|
|
17
|
+
import { logger } from './logger.js';
|
|
18
|
+
/** Identity sent to models.dev — a stable CLI UA, never the HTTP stack default. */
|
|
19
|
+
export const MODELS_DEV_USER_AGENT = 'saros-proxy';
|
|
20
|
+
/** Cached parsed document. Null = never fetched / reset. */
|
|
21
|
+
let documentCache = null;
|
|
22
|
+
/**
|
|
23
|
+
* Fetch in progress, if any. Concurrent cold-cache callers await this instead
|
|
24
|
+
* of each pulling the 5.9 MB document.
|
|
25
|
+
*/
|
|
26
|
+
let documentRequest = null;
|
|
27
|
+
/**
|
|
28
|
+
* Extract one provider's model map from a parsed models.dev document.
|
|
29
|
+
*
|
|
30
|
+
* Pure — callers handle JSON parsing and error recovery.
|
|
31
|
+
*
|
|
32
|
+
* @param parsed — Parsed models.dev JSON (`{ providerId: { models: {...} } }`)
|
|
33
|
+
* @param providerId — models.dev provider id (e.g. "opencode-go")
|
|
34
|
+
* @returns Model id → metadata map, or `{}` when the provider is absent
|
|
35
|
+
* or malformed
|
|
36
|
+
*/
|
|
37
|
+
export function extractProviderModels(parsed, providerId) {
|
|
38
|
+
const provider = parsed[providerId];
|
|
39
|
+
if (typeof provider !== 'object' || provider === null)
|
|
40
|
+
return {};
|
|
41
|
+
const models = provider.models;
|
|
42
|
+
if (typeof models !== 'object' || models === null || Array.isArray(models))
|
|
43
|
+
return {};
|
|
44
|
+
return models;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Pick only whitelisted fields from a models.dev metadata entry.
|
|
48
|
+
*
|
|
49
|
+
* Whitelisting keeps provider-specific/unknown keys (e.g. `provider`,
|
|
50
|
+
* `release_date`) out of configs written by saros.
|
|
51
|
+
*/
|
|
52
|
+
export function pickModelsDevFields(entry) {
|
|
53
|
+
const result = {};
|
|
54
|
+
for (const key of MODELS_DEV_SAFE_FIELDS) {
|
|
55
|
+
if (key in entry) {
|
|
56
|
+
result[key] = entry[key];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Normalize a model id for cross-catalog matching: strip a provider routing
|
|
63
|
+
* suffix (`…@commandcode`), keep the last path segment, lowercase and turn
|
|
64
|
+
* underscores into hyphens.
|
|
65
|
+
*/
|
|
66
|
+
function bareModelName(modelId) {
|
|
67
|
+
const withoutSuffix = modelId.split('@')[0];
|
|
68
|
+
const parts = withoutSuffix.split('/');
|
|
69
|
+
return parts[parts.length - 1].toLowerCase().replaceAll('_', '-');
|
|
70
|
+
}
|
|
71
|
+
/** Catalogs already checked for bare-name collisions (one warning per slice). */
|
|
72
|
+
const collisionReported = new WeakSet();
|
|
73
|
+
/**
|
|
74
|
+
* Bare names that two or more entries of one catalog normalize to.
|
|
75
|
+
*
|
|
76
|
+
* Matching by such a name would return whichever entry `Object.keys` yields
|
|
77
|
+
* first — silently pricing/limiting the wrong model — so callers must fall
|
|
78
|
+
* back to exact ids. `models-refresh` aborts the generation on the same
|
|
79
|
+
* condition; runtime stays lenient but warns once per slice.
|
|
80
|
+
*
|
|
81
|
+
* @param models — id → entry catalog (models.dev slice or bundled catalog)
|
|
82
|
+
* @param normalize — id → bare name function for that catalog's id shape
|
|
83
|
+
*/
|
|
84
|
+
export function collidingBareNames(models, normalize) {
|
|
85
|
+
const counts = new Map();
|
|
86
|
+
for (const [id, entry] of Object.entries(models)) {
|
|
87
|
+
if (typeof entry !== 'object' || entry === null || Array.isArray(entry))
|
|
88
|
+
continue;
|
|
89
|
+
const bare = normalize(id);
|
|
90
|
+
if (!bare)
|
|
91
|
+
continue;
|
|
92
|
+
counts.set(bare, (counts.get(bare) ?? 0) + 1);
|
|
93
|
+
}
|
|
94
|
+
const collisions = new Set([...counts].filter(([, count]) => count > 1).map(([bare]) => bare));
|
|
95
|
+
if (collisions.size > 0 && !collisionReported.has(models)) {
|
|
96
|
+
collisionReported.add(models);
|
|
97
|
+
logger.warn({ bareNames: [...collisions] }, 'model catalog has bare-name collisions; only exact ids will match');
|
|
98
|
+
}
|
|
99
|
+
return collisions;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Find a model entry inside one provider's slice.
|
|
103
|
+
*
|
|
104
|
+
* Exact id first, then the bare name: live catalogs prefix ids
|
|
105
|
+
* (`deepseek/deepseek-v4-flash`) while models.dev may key the same model
|
|
106
|
+
* bare, and vice versa. Matching stays inside the provider slice so a
|
|
107
|
+
* commandcode model never receives opencode-go prices.
|
|
108
|
+
*
|
|
109
|
+
* @returns The metadata entry, or undefined when not found
|
|
110
|
+
*/
|
|
111
|
+
export function findProviderModel(models, modelId) {
|
|
112
|
+
const exact = models[modelId];
|
|
113
|
+
if (typeof exact === 'object' && exact !== null && !Array.isArray(exact)) {
|
|
114
|
+
return exact;
|
|
115
|
+
}
|
|
116
|
+
const bare = bareModelName(modelId);
|
|
117
|
+
if (!bare)
|
|
118
|
+
return undefined;
|
|
119
|
+
// Two ids share this bare name: matching one of them would be arbitrary.
|
|
120
|
+
if (collidingBareNames(models, bareModelName).has(bare))
|
|
121
|
+
return undefined;
|
|
122
|
+
for (const [id, entry] of Object.entries(models)) {
|
|
123
|
+
if (typeof entry !== 'object' || entry === null || Array.isArray(entry))
|
|
124
|
+
continue;
|
|
125
|
+
if (bareModelName(id) === bare)
|
|
126
|
+
return entry;
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Fetch and parse the models.dev document. Never throws; failures are not
|
|
132
|
+
* cached, so a later call retries.
|
|
133
|
+
*/
|
|
134
|
+
async function loadModelsDevDocument() {
|
|
135
|
+
try {
|
|
136
|
+
const response = await fetch(MODELS_DEV_URL, {
|
|
137
|
+
headers: {
|
|
138
|
+
'User-Agent': MODELS_DEV_USER_AGENT,
|
|
139
|
+
Accept: 'application/json',
|
|
140
|
+
},
|
|
141
|
+
signal: AbortSignal.timeout(MODELS_DEV_TIMEOUT_MS),
|
|
142
|
+
});
|
|
143
|
+
if (!response.ok)
|
|
144
|
+
return null;
|
|
145
|
+
const parsed = JSON.parse(await response.text());
|
|
146
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
147
|
+
return null;
|
|
148
|
+
documentCache = { ts: Date.now(), parsed: parsed };
|
|
149
|
+
return documentCache.parsed;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Fetch (or reuse) the parsed models.dev document.
|
|
157
|
+
*
|
|
158
|
+
* Concurrent cold-cache callers share one request: the pending promise is
|
|
159
|
+
* cached, not just its result. A successful document is then cached for
|
|
160
|
+
* `MODELS_DEV_CACHE_TTL_MS`.
|
|
161
|
+
*/
|
|
162
|
+
async function fetchModelsDevDocument() {
|
|
163
|
+
if (documentCache !== null && Date.now() - documentCache.ts < MODELS_DEV_CACHE_TTL_MS) {
|
|
164
|
+
return documentCache.parsed;
|
|
165
|
+
}
|
|
166
|
+
if (documentRequest !== null)
|
|
167
|
+
return documentRequest;
|
|
168
|
+
const request = loadModelsDevDocument().finally(() => {
|
|
169
|
+
// Only clear our own request: a reset() may already have installed one.
|
|
170
|
+
if (documentRequest === request)
|
|
171
|
+
documentRequest = null;
|
|
172
|
+
});
|
|
173
|
+
documentRequest = request;
|
|
174
|
+
return request;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Fetch one provider's model map, with the shared TTL cache.
|
|
178
|
+
*
|
|
179
|
+
* The document is fetched once and split per provider, so asking for both
|
|
180
|
+
* opencode-go and commandcode costs a single request.
|
|
181
|
+
*
|
|
182
|
+
* @returns Model id → metadata map; `{}` when the provider is absent;
|
|
183
|
+
* null when the document could not be fetched/parsed
|
|
184
|
+
*/
|
|
185
|
+
export async function fetchModelsDevProvider(providerId) {
|
|
186
|
+
const parsed = await fetchModelsDevDocument();
|
|
187
|
+
if (parsed === null)
|
|
188
|
+
return null;
|
|
189
|
+
return extractProviderModels(parsed, providerId);
|
|
190
|
+
}
|
|
191
|
+
/** Clear the models.dev document cache (test helper). */
|
|
192
|
+
export function resetModelsDevCache() {
|
|
193
|
+
documentCache = null;
|
|
194
|
+
documentRequest = null;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=models-dev.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models-dev.js","sourceRoot":"","sources":["../src/models-dev.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* models-refresh.ts — Deterministic generator for the bundled model catalog.
|
|
3
|
+
*
|
|
4
|
+
* `npm run models:refresh` (scripts/refresh-models.ts) is the only writer of
|
|
5
|
+
* `assets/models.json`. It merges models.dev metadata for the two providers
|
|
6
|
+
* this proxy serves with the live catalogs, so a model that no longer exists
|
|
7
|
+
* upstream leaves the bundle and a new one enters without a hand edit.
|
|
8
|
+
*
|
|
9
|
+
* The merge is a pure function (`mergeModelCatalog`) so the catalog rules are
|
|
10
|
+
* testable without network access:
|
|
11
|
+
* - membership follows the live catalogs: a model the provider serves enters
|
|
12
|
+
* with the metadata the live payload carries even when models.dev omits it,
|
|
13
|
+
* and it stays while the provider keeps advertising it;
|
|
14
|
+
* - `opencode-go` metadata wins when both providers know a model;
|
|
15
|
+
* - a field already in the current file survives when models.dev is silent
|
|
16
|
+
* about it (models.dev drops fields occasionally and a regeneration must
|
|
17
|
+
* not discard the value);
|
|
18
|
+
* - two raw ids that collapse to the same bare key abort the refresh;
|
|
19
|
+
* - output keys, including nested object keys, are sorted so a refresh diff
|
|
20
|
+
* only shows real changes;
|
|
21
|
+
* - a failed fetch — or an empty/partial response — aborts before the file is
|
|
22
|
+
* read, and a merge that would drop more than half of the current models
|
|
23
|
+
* needs `allowShrink` before it writes anything.
|
|
24
|
+
*/
|
|
25
|
+
/** Public opencode-go catalog — no auth, but Cloudflare wants a CLI signature. */
|
|
26
|
+
export declare const OPENCODE_GO_MODELS_URL = "https://opencode.ai/zen/go/v1/models";
|
|
27
|
+
/** Public CommandCode catalog (200 with any or no Authorization header). */
|
|
28
|
+
export declare const COMMANDCODE_MODELS_URL = "https://api.commandcode.ai/provider/v1/models";
|
|
29
|
+
/**
|
|
30
|
+
* models.dev sits behind Cloudflare and answers 403 `error code: 1010` to
|
|
31
|
+
* browser-like or missing signatures. Any stable non-browser UA works.
|
|
32
|
+
*/
|
|
33
|
+
export declare const MODELS_REFRESH_USER_AGENT = "saros-proxy-models-refresh";
|
|
34
|
+
/** Minimal fetch surface, injected in tests. */
|
|
35
|
+
export type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
|
|
36
|
+
/** Fetch and validate the models.dev catalog. */
|
|
37
|
+
export declare function fetchModelsDevCatalog(fetchImpl?: FetchLike, timeoutMs?: number): Promise<Record<string, unknown>>;
|
|
38
|
+
/**
|
|
39
|
+
* One entry of a live provider catalog: the id plus the metadata the payload
|
|
40
|
+
* carries. `name` and `context_length` seed a catalog entry when models.dev
|
|
41
|
+
* does not know the model yet.
|
|
42
|
+
*/
|
|
43
|
+
export interface LiveModel {
|
|
44
|
+
id: string;
|
|
45
|
+
name?: string;
|
|
46
|
+
context_length?: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fetch the opencode-go live catalog.
|
|
50
|
+
*
|
|
51
|
+
* The list is authoritative for membership: models.dev keeps models the
|
|
52
|
+
* provider already retired (`space-bunny`, `cat-jam-free`, …), and publishing
|
|
53
|
+
* those as connectable models is what this gate prevents.
|
|
54
|
+
*/
|
|
55
|
+
export declare function fetchOpencodeGoModels(fetchImpl?: FetchLike, timeoutMs?: number): Promise<LiveModel[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Fetch the CommandCode live catalog.
|
|
58
|
+
*
|
|
59
|
+
* Only models that advertise `/chat/completions` are usable through the proxy,
|
|
60
|
+
* which relays chat/completions shapes. `claude-*` entries list `/messages`
|
|
61
|
+
* only (verified live: chat/completions → unsupported_model); a vendor-prefixed
|
|
62
|
+
* Claude model would slip past a bare `^claude` test, so the endpoint field —
|
|
63
|
+
* not the id — decides.
|
|
64
|
+
*/
|
|
65
|
+
export declare function fetchCommandcodeModels(fetchImpl?: FetchLike, timeoutMs?: number): Promise<LiveModel[]>;
|
|
66
|
+
/** Default network sources for the CLI. */
|
|
67
|
+
export interface RefreshFetchers {
|
|
68
|
+
modelsDev: () => Promise<Record<string, unknown>>;
|
|
69
|
+
opencodeModels: () => Promise<readonly LiveModel[]>;
|
|
70
|
+
commandcodeModels: () => Promise<readonly LiveModel[]>;
|
|
71
|
+
}
|
|
72
|
+
/** Build the real fetchers — the only place the CLI touches the network. */
|
|
73
|
+
export declare function defaultRefreshFetchers(): RefreshFetchers;
|
|
74
|
+
/**
|
|
75
|
+
* Return one provider's `models` map from a parsed models.dev payload.
|
|
76
|
+
*
|
|
77
|
+
* Thin delegate: the parse lives in `models-dev.ts` so the generator and the
|
|
78
|
+
* runtime read agree on what a provider slice is.
|
|
79
|
+
*/
|
|
80
|
+
export declare function extractModelsDevProviderModels(parsed: Record<string, unknown>, providerId: string): Record<string, Record<string, unknown>>;
|
|
81
|
+
export interface ModelCatalogMergeInput {
|
|
82
|
+
/** Parsed `https://models.dev/api.json`. */
|
|
83
|
+
modelsDev: Record<string, unknown>;
|
|
84
|
+
/** Live opencode-go `/models` entries. */
|
|
85
|
+
opencodeModels: readonly LiveModel[];
|
|
86
|
+
/** Live CommandCode `/provider/v1/models` entries (chat/completions only). */
|
|
87
|
+
commandcodeModels: readonly LiveModel[];
|
|
88
|
+
/** Current bundled catalog, parsed; null/absent for a fresh file. */
|
|
89
|
+
current?: Record<string, unknown> | null;
|
|
90
|
+
}
|
|
91
|
+
export interface ModelCatalogMergeResult {
|
|
92
|
+
/** Merged catalog with alphabetically sorted keys (top level and nested). */
|
|
93
|
+
models: Record<string, Record<string, unknown>>;
|
|
94
|
+
/** Keys that were absent from the current file. */
|
|
95
|
+
added: string[];
|
|
96
|
+
/** Keys that disappeared from both upstream catalogs. */
|
|
97
|
+
removed: string[];
|
|
98
|
+
/** Keys present in both files whose content changed. */
|
|
99
|
+
changed: string[];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Merge models.dev metadata with the live catalogs and the current file.
|
|
103
|
+
*
|
|
104
|
+
* Membership is decided by upstream: only ids that the live catalog advertises
|
|
105
|
+
* are kept, and a live id enters even when models.dev has never heard of it.
|
|
106
|
+
* Fields inside a surviving entry are additive — the current file's value
|
|
107
|
+
* survives whenever the upstream source does not carry that field.
|
|
108
|
+
*/
|
|
109
|
+
export declare function mergeModelCatalog(input: ModelCatalogMergeInput): ModelCatalogMergeResult;
|
|
110
|
+
/** Stable on-disk format: 2-space indent, sorted keys, trailing newline. */
|
|
111
|
+
export declare function serializeModelCatalog(models: Record<string, unknown>): string;
|
|
112
|
+
/** Parse a catalog file. `null`/empty content is an empty catalog. */
|
|
113
|
+
export declare function parseCatalogContent(raw: string | null): Record<string, unknown>;
|
|
114
|
+
export interface RefreshIo {
|
|
115
|
+
/** Read the current catalog file, or null when it does not exist. */
|
|
116
|
+
read: () => string | null;
|
|
117
|
+
/** Write the new catalog file. */
|
|
118
|
+
write: (content: string) => void;
|
|
119
|
+
}
|
|
120
|
+
export interface RefreshOptions {
|
|
121
|
+
/** Show the diff and skip the write. */
|
|
122
|
+
dryRun?: boolean;
|
|
123
|
+
/** Allow a merge that removes more than half of the current models. */
|
|
124
|
+
allowShrink?: boolean;
|
|
125
|
+
}
|
|
126
|
+
export interface ModelCatalogRefreshResult extends ModelCatalogMergeResult {
|
|
127
|
+
previousContent: string | null;
|
|
128
|
+
nextContent: string;
|
|
129
|
+
/** False on a dry run and when the generated bytes already match the file. */
|
|
130
|
+
written: boolean;
|
|
131
|
+
/** True when only key order/whitespace differs from the file on disk. */
|
|
132
|
+
formattingOnly: boolean;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Fetch, merge, and (unless dry-running) write the catalog.
|
|
136
|
+
*
|
|
137
|
+
* Sources are validated before the file is read: a 2xx with an empty body (a
|
|
138
|
+
* Cloudflare hiccup, a partial rollout) used to empty the catalog and mark
|
|
139
|
+
* every model removed. A merge that would drop more than half of the current
|
|
140
|
+
* models is treated the same way unless `allowShrink` opts in.
|
|
141
|
+
*/
|
|
142
|
+
export declare function refreshModelCatalog(io: RefreshIo, fetchers?: RefreshFetchers, options?: RefreshOptions): Promise<ModelCatalogRefreshResult>;
|
|
143
|
+
/** Human-readable model-level diff between the old and the new catalog. */
|
|
144
|
+
export declare function formatCatalogDiff(previous: Record<string, unknown> | null, next: Record<string, unknown>): string;
|