@route_33/opencode 0.4.2 → 0.4.4
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 +6 -4
- package/dist/index.js +52 -129
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -24,12 +24,14 @@ The installer patches both configs: the server plugin into `opencode.json` and t
|
|
|
24
24
|
}
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
This plugin never writes your `opencode.json` or `opencode.jsonc`: opencode has no TUI-plugin auto-discovery, so without a `tui.json` entry the wizard simply never appears — the server plugin works regardless. The
|
|
27
|
+
This plugin never writes your `opencode.json` or `opencode.jsonc`: opencode has no TUI-plugin auto-discovery, so without a `tui.json` entry the wizard simply never appears — the server plugin works regardless. The only files the plugin ever writes inside your repo are an optional `.opencode/route33.key` (git-ignored automatically) and your own `.opencode/route33-overrides.json` (yours to commit or ignore).
|
|
28
28
|
|
|
29
29
|
## Auth
|
|
30
30
|
|
|
31
31
|
Run `opencode auth login` and choose `route_33`, or set the `ROUTE33_API_KEY` environment variable. Without a key, the TUI wizard offers to save one: per-project inside a git workspace, global otherwise. A globally stored key applies only outside git workspaces — inside a workspace, keys resolve from the environment, the workspace config, or `.opencode/route33.key` (per-project keys are the metering boundary). Skipping the project-key wizard snoozes it for 7 days — "Skip" for that workspace only, "Skip for all workspaces" everywhere (both live in opencode's TUI kv store; remove `project-key-prompt.dismissed-at*` from `~/.local/state/opencode/kv.json` to re-arm sooner). `ROUTE33_BASE_URL` overrides the default gateway URL; `ROUTE33_TUI_PROMPT=0` disables the wizard.
|
|
32
32
|
|
|
33
|
+
Your key is never written into the resolved provider configuration: authentication is attached per request at fetch time, so `opencode debug config` output contains no credential material.
|
|
34
|
+
|
|
33
35
|
## Model catalog
|
|
34
36
|
|
|
35
37
|
The catalog is discovered dynamically. The plugin calls `GET {baseURL}/models` with your resolved API key, caches the response on disk, and injects only the models that key can see. The refresh ladder is:
|
|
@@ -39,7 +41,7 @@ The catalog is discovered dynamically. The plugin calls `GET {baseURL}/models` w
|
|
|
39
41
|
3. If the live fetch fails, return the stale cache.
|
|
40
42
|
4. If there is no cache and no live response, preserve existing generated entries and inject no replacement models.
|
|
41
43
|
|
|
42
|
-
The plugin does not embed or serve a static catalog. Catalog cache envelopes are versioned; cache files from older plugin versions are ignored after an upgrade and repopulated from the gateway.
|
|
44
|
+
The plugin does not embed or serve a static catalog. The on-disk cache is keyed by gateway URL **and** API key, so the same key pointed at a different gateway (`ROUTE33_BASE_URL`, e.g. a local cluster) never shares a cache with production. Catalog cache envelopes are versioned; cache files from older plugin versions are ignored after an upgrade and repopulated from the gateway.
|
|
43
45
|
|
|
44
46
|
If discovery yields no catalog, the plugin logs exactly one warning:
|
|
45
47
|
|
|
@@ -64,7 +66,7 @@ The normative broadcast contract is [docs/gateway-limits-variants-broadcast.md](
|
|
|
64
66
|
|
|
65
67
|
## Managed model entries
|
|
66
68
|
|
|
67
|
-
Gateway-discovered `provider.route_33.models` entries are managed
|
|
69
|
+
Gateway-discovered `provider.route_33.models` entries are fully managed: every startup rebuilds the map from the current broadcast, then deep-merges your overrides on top. The ownership rule is the whole design — *in the broadcast (or in your overrides) ⇒ kept; anything else ⇒ dropped and logged* — so retired gateway models disappear automatically and no bookkeeping file is needed. (Versions ≤0.4.3 kept a `.opencode/route33-generated.json` ledger for this; the plugin deletes that obsolete file on first run.)
|
|
68
70
|
|
|
69
71
|
Keep customizations in `.opencode/route33-overrides.json`, because the installed OpenCode `ProviderConfig` does not allow unknown provider-level keys such as `modelOverrides`. Overrides deep-merge onto matching generated entries and can add custom models:
|
|
70
72
|
|
|
@@ -77,7 +79,7 @@ Keep customizations in `.opencode/route33-overrides.json`, because the installed
|
|
|
77
79
|
}
|
|
78
80
|
```
|
|
79
81
|
|
|
80
|
-
If the gateway fetch fails with no usable cache, regeneration is skipped: existing entries are never pruned
|
|
82
|
+
If the gateway fetch fails with no usable cache, regeneration is skipped entirely: existing entries are never pruned on zero information.
|
|
81
83
|
|
|
82
84
|
`ROUTE33_ENRICH_CAPS` has been removed; capability backfill from `models.dev` is now always on.
|
|
83
85
|
|
package/dist/index.js
CHANGED
|
@@ -289,16 +289,12 @@ function buildOpenAiChatCompatFetch(fetchImpl) {
|
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
// src/config/build-options.ts
|
|
292
|
-
function buildOptions(baseURL,
|
|
292
|
+
function buildOptions(baseURL, resolveKeyNow = () => void 0) {
|
|
293
293
|
const options = { baseURL };
|
|
294
294
|
options.fetch = buildLiveAuthFetch(
|
|
295
295
|
resolveKeyNow,
|
|
296
296
|
buildOpenAiChatCompatFetch()
|
|
297
297
|
);
|
|
298
|
-
if (apiKey !== void 0) {
|
|
299
|
-
options.apiKey = apiKey;
|
|
300
|
-
options.headers = { Authorization: `Bearer ${apiKey}` };
|
|
301
|
-
}
|
|
302
298
|
return options;
|
|
303
299
|
}
|
|
304
300
|
|
|
@@ -350,7 +346,7 @@ function vendorOf(slug) {
|
|
|
350
346
|
return slug.slice(0, slug.indexOf("/"));
|
|
351
347
|
}
|
|
352
348
|
function buildProviderBlocks(enriched, options) {
|
|
353
|
-
const {
|
|
349
|
+
const { baseURL } = options;
|
|
354
350
|
const resolveKeyNow = options.resolveKeyNow ?? (() => void 0);
|
|
355
351
|
const route33Models = {};
|
|
356
352
|
for (const slug of Object.keys(enriched).sort()) {
|
|
@@ -369,7 +365,7 @@ function buildProviderBlocks(enriched, options) {
|
|
|
369
365
|
route_33: {
|
|
370
366
|
npm: "@ai-sdk/openai-compatible",
|
|
371
367
|
name: PROVIDER_DISPLAY_NAME,
|
|
372
|
-
options: buildOptions(baseURL,
|
|
368
|
+
options: buildOptions(baseURL, resolveKeyNow),
|
|
373
369
|
models: route33Models
|
|
374
370
|
}
|
|
375
371
|
};
|
|
@@ -380,8 +376,9 @@ var memory = /* @__PURE__ */ new Map();
|
|
|
380
376
|
|
|
381
377
|
// src/catalog/catalog-cache/hash-key.ts
|
|
382
378
|
import { createHash } from "crypto";
|
|
383
|
-
function hashKey(key) {
|
|
384
|
-
return createHash("sha256").update(
|
|
379
|
+
function hashKey(baseURL, key) {
|
|
380
|
+
return createHash("sha256").update(`${baseURL}
|
|
381
|
+
${key}`).digest("hex").slice(0, 12);
|
|
385
382
|
}
|
|
386
383
|
|
|
387
384
|
// src/catalog/catalog-cache/read-disk.ts
|
|
@@ -426,8 +423,8 @@ function isFresh(record, now, ttl) {
|
|
|
426
423
|
return now - record.fetchedAt <= ttl;
|
|
427
424
|
}
|
|
428
425
|
function readFreshCatalog(opts) {
|
|
429
|
-
const { key, env = process.env, now = Date.now() } = opts;
|
|
430
|
-
const keyHash = hashKey(key);
|
|
426
|
+
const { baseURL, key, env = process.env, now = Date.now() } = opts;
|
|
427
|
+
const keyHash = hashKey(baseURL, key);
|
|
431
428
|
const ttl = catalogTtlMs(env);
|
|
432
429
|
const inMemory = memory.get(keyHash);
|
|
433
430
|
if (inMemory && isFresh(inMemory, now, ttl)) return inMemory.models;
|
|
@@ -441,16 +438,16 @@ function readFreshCatalog(opts) {
|
|
|
441
438
|
|
|
442
439
|
// src/catalog/catalog-cache/read-stale-catalog.ts
|
|
443
440
|
function readStaleCatalog(opts) {
|
|
444
|
-
const { key, env = process.env } = opts;
|
|
445
|
-
const keyHash = hashKey(key);
|
|
441
|
+
const { baseURL, key, env = process.env } = opts;
|
|
442
|
+
const keyHash = hashKey(baseURL, key);
|
|
446
443
|
return (memory.get(keyHash) ?? readDisk(keyHash, env))?.models;
|
|
447
444
|
}
|
|
448
445
|
|
|
449
446
|
// src/catalog/catalog-cache/write-catalog.ts
|
|
450
447
|
import * as fs4 from "fs";
|
|
451
448
|
function writeCatalog(opts) {
|
|
452
|
-
const { key, models, env = process.env, now = Date.now() } = opts;
|
|
453
|
-
const keyHash = hashKey(key);
|
|
449
|
+
const { baseURL, key, models, env = process.env, now = Date.now() } = opts;
|
|
450
|
+
const keyHash = hashKey(baseURL, key);
|
|
454
451
|
const record = { fetchedAt: now, keyHash, models };
|
|
455
452
|
memory.set(keyHash, record);
|
|
456
453
|
try {
|
|
@@ -700,7 +697,7 @@ async function runLadder(opts) {
|
|
|
700
697
|
if (env[ENV_DISCOVERY] === "0")
|
|
701
698
|
return noneResult(`discovery disabled via ${ENV_DISCOVERY}=0`, true);
|
|
702
699
|
if (!key) return noneResult("no API key resolved", false);
|
|
703
|
-
const fresh = readFreshCatalog({ key, env, now });
|
|
700
|
+
const fresh = readFreshCatalog({ baseURL, key, env, now });
|
|
704
701
|
if (fresh) {
|
|
705
702
|
logger.info(
|
|
706
703
|
`catalog source: cache (${Object.keys(fresh).length} model(s))`
|
|
@@ -711,7 +708,7 @@ async function runLadder(opts) {
|
|
|
711
708
|
const live = await fetchCatalog({ baseURL, key, env, fetchImpl });
|
|
712
709
|
if (Object.keys(live).length === 0)
|
|
713
710
|
throw new Error("gateway returned an empty catalog");
|
|
714
|
-
writeCatalog({ key, models: live, env, now });
|
|
711
|
+
writeCatalog({ baseURL, key, models: live, env, now });
|
|
715
712
|
logger.info(`catalog source: live (${Object.keys(live).length} model(s))`);
|
|
716
713
|
return { models: live, source: "live" };
|
|
717
714
|
} catch (error) {
|
|
@@ -719,7 +716,7 @@ async function runLadder(opts) {
|
|
|
719
716
|
`live catalog fetch failed \u2014 trying stale cache: ${error instanceof Error ? error.message : String(error)}`
|
|
720
717
|
);
|
|
721
718
|
}
|
|
722
|
-
const stale = readStaleCatalog({ key, env });
|
|
719
|
+
const stale = readStaleCatalog({ baseURL, key, env });
|
|
723
720
|
if (stale) {
|
|
724
721
|
logger.info(
|
|
725
722
|
`catalog source: stale-cache (${Object.keys(stale).length} model(s))`
|
|
@@ -1065,102 +1062,36 @@ function mergeConfig(cfg, blocks) {
|
|
|
1065
1062
|
}
|
|
1066
1063
|
}
|
|
1067
1064
|
|
|
1068
|
-
// src/config/
|
|
1069
|
-
function cloneEntry(entry) {
|
|
1070
|
-
return { ...entry };
|
|
1071
|
-
}
|
|
1072
|
-
function cloneModels(patch) {
|
|
1073
|
-
const models = {};
|
|
1074
|
-
for (const [key, entry] of Object.entries(patch.models)) {
|
|
1075
|
-
models[key] = cloneEntry(entry);
|
|
1076
|
-
}
|
|
1077
|
-
return models;
|
|
1078
|
-
}
|
|
1079
|
-
function fillModelEntry(existing, patchEntry) {
|
|
1080
|
-
for (const [field, value] of Object.entries(patchEntry)) {
|
|
1081
|
-
if (value === void 0) continue;
|
|
1082
|
-
if (field === "limit" || existing[field] === void 0)
|
|
1083
|
-
existing[field] = value;
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
function mergeModels(target, patch) {
|
|
1087
|
-
if (target.models === void 0) {
|
|
1088
|
-
target.models = cloneModels(patch);
|
|
1089
|
-
return;
|
|
1090
|
-
}
|
|
1091
|
-
const models = target.models;
|
|
1092
|
-
for (const [key, patchEntry] of Object.entries(patch.models)) {
|
|
1093
|
-
const existing = models[key];
|
|
1094
|
-
if (existing === void 0) {
|
|
1095
|
-
models[key] = cloneEntry(patchEntry);
|
|
1096
|
-
} else {
|
|
1097
|
-
fillModelEntry(existing, cloneEntry(patchEntry));
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
// src/config/generation-manifest.ts
|
|
1065
|
+
// src/config/read-model-overrides.ts
|
|
1103
1066
|
import * as fs6 from "fs";
|
|
1104
1067
|
import * as path7 from "path";
|
|
1105
1068
|
var SIDECAR_DIR = ".opencode";
|
|
1106
|
-
var MANIFEST_FILE = "route33-generated.json";
|
|
1107
1069
|
var OVERRIDES_FILE = "route33-overrides.json";
|
|
1108
1070
|
function isRecord7(value) {
|
|
1109
1071
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1110
1072
|
}
|
|
1111
|
-
function generatedPath(root) {
|
|
1112
|
-
return path7.join(root, SIDECAR_DIR, MANIFEST_FILE);
|
|
1113
|
-
}
|
|
1114
|
-
function overridesPath(root) {
|
|
1115
|
-
return path7.join(root, SIDECAR_DIR, OVERRIDES_FILE);
|
|
1116
|
-
}
|
|
1117
|
-
function parseManifest(value) {
|
|
1118
|
-
if (!isRecord7(value) || value.version !== 1 || !Array.isArray(value.generatedKeys) || typeof value.lastBroadcastHash !== "string") return void 0;
|
|
1119
|
-
if (!value.generatedKeys.every((key) => typeof key === "string")) return void 0;
|
|
1120
|
-
return { version: 1, generatedKeys: value.generatedKeys, lastBroadcastHash: value.lastBroadcastHash };
|
|
1121
|
-
}
|
|
1122
1073
|
function parseOverrides(value) {
|
|
1123
1074
|
if (!isRecord7(value) || !isRecord7(value.models) || !Object.values(value.models).every(isRecord7)) return void 0;
|
|
1124
1075
|
const models = {};
|
|
1125
1076
|
for (const [key, entry] of Object.entries(value.models)) if (isRecord7(entry)) models[key] = entry;
|
|
1126
1077
|
return models;
|
|
1127
1078
|
}
|
|
1128
|
-
function readJson(file) {
|
|
1129
|
-
try {
|
|
1130
|
-
if (!fs6.existsSync(file)) return void 0;
|
|
1131
|
-
const parsed = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
1132
|
-
return parsed;
|
|
1133
|
-
} catch (error) {
|
|
1134
|
-
logger.info(`route_33: unable to read generated-model sidecar: ${error instanceof Error ? error.message : String(error)}`);
|
|
1135
|
-
return void 0;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
function readGenerationManifest(root) {
|
|
1139
|
-
const file = generatedPath(root);
|
|
1140
|
-
const manifest = parseManifest(readJson(file));
|
|
1141
|
-
if (manifest === void 0 && fs6.existsSync(file)) logger.info("route_33: generated-model manifest is malformed; treating this run as first-run regeneration");
|
|
1142
|
-
return manifest;
|
|
1143
|
-
}
|
|
1144
1079
|
function readModelOverrides(root) {
|
|
1145
|
-
const file =
|
|
1146
|
-
|
|
1147
|
-
if (overrides === void 0 && fs6.existsSync(file)) logger.info("route_33: model overrides sidecar is malformed; ignoring it for this run");
|
|
1148
|
-
return overrides ?? {};
|
|
1149
|
-
}
|
|
1150
|
-
function writeGenerationManifest(root, manifest) {
|
|
1080
|
+
const file = path7.join(root, SIDECAR_DIR, OVERRIDES_FILE);
|
|
1081
|
+
let parsed;
|
|
1151
1082
|
try {
|
|
1152
|
-
fs6.
|
|
1153
|
-
|
|
1154
|
-
`);
|
|
1155
|
-
return true;
|
|
1083
|
+
if (!fs6.existsSync(file)) return {};
|
|
1084
|
+
parsed = JSON.parse(fs6.readFileSync(file, "utf8"));
|
|
1156
1085
|
} catch (error) {
|
|
1157
|
-
logger.info(`route_33: unable to
|
|
1158
|
-
return
|
|
1086
|
+
logger.info(`route_33: unable to read model overrides sidecar: ${error instanceof Error ? error.message : String(error)}`);
|
|
1087
|
+
return {};
|
|
1159
1088
|
}
|
|
1089
|
+
const overrides = parseOverrides(parsed);
|
|
1090
|
+
if (overrides === void 0) logger.info("route_33: model overrides sidecar is malformed; ignoring it for this run");
|
|
1091
|
+
return overrides ?? {};
|
|
1160
1092
|
}
|
|
1161
1093
|
|
|
1162
1094
|
// src/config/regenerate-models.ts
|
|
1163
|
-
import { createHash as createHash2 } from "crypto";
|
|
1164
1095
|
function isRecord8(value) {
|
|
1165
1096
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1166
1097
|
}
|
|
@@ -1169,49 +1100,46 @@ function clone(value) {
|
|
|
1169
1100
|
if (!isRecord8(value)) return value;
|
|
1170
1101
|
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, clone(item)]));
|
|
1171
1102
|
}
|
|
1172
|
-
function
|
|
1103
|
+
function cloneEntry(entry) {
|
|
1173
1104
|
const copy = {};
|
|
1174
1105
|
for (const [key, value] of Object.entries(entry)) copy[key] = clone(value);
|
|
1175
1106
|
return copy;
|
|
1176
1107
|
}
|
|
1177
1108
|
function mergeEntry(base, override) {
|
|
1178
|
-
const result =
|
|
1109
|
+
const result = cloneEntry(base);
|
|
1179
1110
|
for (const [key, value] of Object.entries(override)) {
|
|
1180
1111
|
const existing = result[key];
|
|
1181
1112
|
result[key] = isRecord8(existing) && isRecord8(value) ? mergeEntry(existing, value) : clone(value);
|
|
1182
1113
|
}
|
|
1183
1114
|
return result;
|
|
1184
1115
|
}
|
|
1185
|
-
function stableJson(value) {
|
|
1186
|
-
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
1187
|
-
if (!isRecord8(value)) return JSON.stringify(value);
|
|
1188
|
-
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
|
|
1189
|
-
}
|
|
1190
|
-
function broadcastHash(models) {
|
|
1191
|
-
return createHash2("sha256").update(stableJson(models)).digest("hex");
|
|
1192
|
-
}
|
|
1193
1116
|
function regenerateModels(input) {
|
|
1194
1117
|
if (input.source === "none" && input.failedLiveFetch === true) {
|
|
1195
1118
|
const models2 = {};
|
|
1196
1119
|
for (const [key, entry] of Object.entries(input.currentModels ?? {}))
|
|
1197
|
-
if (entry !== void 0) models2[key] =
|
|
1120
|
+
if (entry !== void 0) models2[key] = cloneEntry(entry);
|
|
1198
1121
|
return { models: models2, prunedKeys: [], skipped: true };
|
|
1199
1122
|
}
|
|
1200
|
-
const current = input.currentModels ?? {};
|
|
1201
|
-
const generatedKeys = input.previousManifest?.generatedKeys ?? Object.keys(current);
|
|
1202
1123
|
const models = {};
|
|
1203
|
-
for (const [key, entry] of Object.entries(
|
|
1204
|
-
if (!generatedKeys.includes(key) && entry !== void 0) models[key] = cloneEntry2(entry);
|
|
1205
|
-
}
|
|
1206
|
-
for (const [key, entry] of Object.entries(input.freshModels)) models[key] = cloneEntry2(entry);
|
|
1124
|
+
for (const [key, entry] of Object.entries(input.freshModels)) models[key] = cloneEntry(entry);
|
|
1207
1125
|
for (const [key, override] of Object.entries(input.overrides ?? {}))
|
|
1208
1126
|
models[key] = mergeEntry(models[key] ?? {}, override);
|
|
1209
|
-
const
|
|
1210
|
-
return {
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1127
|
+
const prunedKeys = Object.keys(input.currentModels ?? {}).filter((key) => input.freshModels[key] === void 0 && input.overrides?.[key] === void 0).sort();
|
|
1128
|
+
return { models, prunedKeys };
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/config/remove-legacy-manifest.ts
|
|
1132
|
+
import * as fs7 from "fs";
|
|
1133
|
+
import * as path8 from "path";
|
|
1134
|
+
function removeLegacyManifest(root) {
|
|
1135
|
+
const file = path8.join(root, ".opencode", "route33-generated.json");
|
|
1136
|
+
try {
|
|
1137
|
+
if (!fs7.existsSync(file)) return;
|
|
1138
|
+
fs7.rmSync(file);
|
|
1139
|
+
logger.info("route_33: removed obsolete route33-generated.json (regeneration is stateless since 0.4.4)");
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
logger.info(`route_33: unable to remove legacy route33-generated.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
1142
|
+
}
|
|
1215
1143
|
}
|
|
1216
1144
|
|
|
1217
1145
|
// src/config/collected-ref.ts
|
|
@@ -1441,6 +1369,7 @@ function createConfigHook(options = {}) {
|
|
|
1441
1369
|
let key;
|
|
1442
1370
|
let catalogAvailable = false;
|
|
1443
1371
|
try {
|
|
1372
|
+
removeLegacyManifest(cwd);
|
|
1444
1373
|
key = resolveKey(cfg, env, cwd);
|
|
1445
1374
|
const baseURL = resolveBaseUrl(cfg, env, cwd);
|
|
1446
1375
|
const catalog = await getCatalog({
|
|
@@ -1457,11 +1386,10 @@ function createConfigHook(options = {}) {
|
|
|
1457
1386
|
const modelsDev = await loadModelsDev(modelsDevOptions);
|
|
1458
1387
|
const enriched = enrichCatalog(catalog, modelsDev, env);
|
|
1459
1388
|
const blocks = buildProviderBlocks(enriched, {
|
|
1460
|
-
apiKey: key,
|
|
1461
1389
|
baseURL,
|
|
1462
1390
|
// Per-request resolution for the live-auth fetch: a prompt-entered
|
|
1463
|
-
// key applies to the very next request, no restart. The
|
|
1464
|
-
//
|
|
1391
|
+
// key applies to the very next request, no restart. The block never
|
|
1392
|
+
// carries the key itself, so config dumps stay credential-free.
|
|
1465
1393
|
resolveKeyNow: () => resolveKey(void 0, env, cwd)
|
|
1466
1394
|
});
|
|
1467
1395
|
mergeConfig(cfg, blocks);
|
|
@@ -1471,21 +1399,16 @@ function createConfigHook(options = {}) {
|
|
|
1471
1399
|
const regeneration = regenerateModels({
|
|
1472
1400
|
currentModels: provider.models,
|
|
1473
1401
|
freshModels: blocks.route_33.models,
|
|
1474
|
-
previousManifest: readGenerationManifest(cwd),
|
|
1475
1402
|
overrides: readModelOverrides(cwd),
|
|
1476
1403
|
source: catalog.source,
|
|
1477
1404
|
failedLiveFetch: catalog.liveFetchFailed
|
|
1478
1405
|
});
|
|
1479
1406
|
if (regeneration.skipped) {
|
|
1480
|
-
logger.info("route_33: catalog fetch failed without a cache; preserving existing
|
|
1407
|
+
logger.info("route_33: catalog fetch failed without a cache; preserving existing model entries");
|
|
1481
1408
|
} else {
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
provider.models = regeneration.models;
|
|
1486
|
-
}
|
|
1487
|
-
for (const key2 of regeneration.prunedKeys)
|
|
1488
|
-
logger.info(`route_33: removed stale model entry '${key2}' (no longer in the gateway catalog); move custom entries to route_33 modelOverrides \u2014 see README`);
|
|
1409
|
+
provider.models = regeneration.models;
|
|
1410
|
+
for (const dropped of regeneration.prunedKeys)
|
|
1411
|
+
logger.info(`route_33: dropped model entry '${dropped}' (not in the gateway catalog); custom models belong in .opencode/route33-overrides.json \u2014 see README`);
|
|
1489
1412
|
}
|
|
1490
1413
|
}
|
|
1491
1414
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@route_33/opencode",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
-
"description": "opencode provider plugin for the route33.ai AI gateway
|
|
6
|
+
"description": "opencode provider plugin for the route33.ai AI gateway \u2014 key-scoped model discovery, cost/limit enrichment, auth, and the key-setup wizard.",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/Cronos-AI-Lab/harness-bench.git",
|