@route_33/opencode 0.4.3 → 0.4.5

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 CHANGED
@@ -24,13 +24,13 @@ 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 server plugin may write its own `.opencode/route33-generated.json` sidecar to track catalog-generated entries.
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.
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. One inert exception: the options carry the non-secret marker `__route33_fetch_shim_auth__` as `apiKey`, because the native per-model SDKs used for `anthropic/`, `openai/`, and `mistralai/` models (`@ai-sdk/anthropic`, `@ai-sdk/openai`, `@ai-sdk/mistral`) refuse to construct a request without one — the marker satisfies that null check, never authenticates anything (the gateway reads only the request-time `Authorization` header, which the fetch shim injects), and no vendor env var (`ANTHROPIC_API_KEY` etc.) is needed or consulted. The marker is skipped by key resolution and never overrides an `apiKey` you authored yourself; it is visible in config dumps precisely because it is not a secret.
34
34
 
35
35
  ## Model catalog
36
36
 
@@ -41,7 +41,7 @@ The catalog is discovered dynamically. The plugin calls `GET {baseURL}/models` w
41
41
  3. If the live fetch fails, return the stale cache.
42
42
  4. If there is no cache and no live response, preserve existing generated entries and inject no replacement models.
43
43
 
44
- 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.
45
45
 
46
46
  If discovery yields no catalog, the plugin logs exactly one warning:
47
47
 
@@ -66,7 +66,7 @@ The normative broadcast contract is [docs/gateway-limits-variants-broadcast.md](
66
66
 
67
67
  ## Managed model entries
68
68
 
69
- Gateway-discovered `provider.route_33.models` entries are managed and regenerated at startup. On the first 0.4.2 run, existing route_33 model entries are adopted as generated; entries no longer served by the gateway are pruned and a manifest is saved at `.opencode/route33-generated.json`. Later runs only remove keys listed in that manifest, then replace them with the current broadcast.
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.)
70
70
 
71
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:
72
72
 
@@ -79,7 +79,7 @@ Keep customizations in `.opencode/route33-overrides.json`, because the installed
79
79
  }
80
80
  ```
81
81
 
82
- If the gateway fetch fails with no usable cache, regeneration is skipped: existing entries are never pruned. Sidecar I/O failures also preserve the previous add-only behavior for that run.
82
+ If the gateway fetch fails with no usable cache, regeneration is skipped entirely: existing entries are never pruned on zero information.
83
83
 
84
84
  `ROUTE33_ENRICH_CAPS` has been removed; capability backfill from `models.dev` is now always on.
85
85
 
package/dist/index.js CHANGED
@@ -94,6 +94,9 @@ var logger = {
94
94
  // src/shared/models-dev-ttl-ms.ts
95
95
  var MODELS_DEV_TTL_MS = 864e5;
96
96
 
97
+ // src/shared/native-sdk-api-key-placeholder.ts
98
+ var NATIVE_SDK_API_KEY_PLACEHOLDER = "__route33_fetch_shim_auth__";
99
+
97
100
  // src/shared/resolve-cache-dir.ts
98
101
  import * as os2 from "os";
99
102
  import * as path3 from "path";
@@ -217,9 +220,10 @@ function usable4(value) {
217
220
  }
218
221
  function keyFromConfig(cfg) {
219
222
  const literal = usable4(cfg?.provider?.route_33?.options?.apiKey);
220
- if (literal !== void 0 && !literal.includes(UNRESOLVED_TEMPLATE_MARKER2))
221
- return literal;
222
- return void 0;
223
+ if (literal === void 0) return void 0;
224
+ if (literal.includes(UNRESOLVED_TEMPLATE_MARKER2)) return void 0;
225
+ if (literal === NATIVE_SDK_API_KEY_PLACEHOLDER) return void 0;
226
+ return literal;
223
227
  }
224
228
  function resolveKey(cfg, env = process.env, worktree) {
225
229
  return usable4(env[ENV_API_KEY]) ?? keyFromConfig(cfg) ?? readWorkspaceKey(worktree) ?? (worktree !== void 0 && isInsideGitWorktree(worktree) ? void 0 : readAuthEntry(env).key);
@@ -290,7 +294,10 @@ function buildOpenAiChatCompatFetch(fetchImpl) {
290
294
 
291
295
  // src/config/build-options.ts
292
296
  function buildOptions(baseURL, resolveKeyNow = () => void 0) {
293
- const options = { baseURL };
297
+ const options = {
298
+ baseURL,
299
+ apiKey: NATIVE_SDK_API_KEY_PLACEHOLDER
300
+ };
294
301
  options.fetch = buildLiveAuthFetch(
295
302
  resolveKeyNow,
296
303
  buildOpenAiChatCompatFetch()
@@ -376,8 +383,9 @@ var memory = /* @__PURE__ */ new Map();
376
383
 
377
384
  // src/catalog/catalog-cache/hash-key.ts
378
385
  import { createHash } from "crypto";
379
- function hashKey(key) {
380
- return createHash("sha256").update(key).digest("hex").slice(0, 12);
386
+ function hashKey(baseURL, key) {
387
+ return createHash("sha256").update(`${baseURL}
388
+ ${key}`).digest("hex").slice(0, 12);
381
389
  }
382
390
 
383
391
  // src/catalog/catalog-cache/read-disk.ts
@@ -422,8 +430,8 @@ function isFresh(record, now, ttl) {
422
430
  return now - record.fetchedAt <= ttl;
423
431
  }
424
432
  function readFreshCatalog(opts) {
425
- const { key, env = process.env, now = Date.now() } = opts;
426
- const keyHash = hashKey(key);
433
+ const { baseURL, key, env = process.env, now = Date.now() } = opts;
434
+ const keyHash = hashKey(baseURL, key);
427
435
  const ttl = catalogTtlMs(env);
428
436
  const inMemory = memory.get(keyHash);
429
437
  if (inMemory && isFresh(inMemory, now, ttl)) return inMemory.models;
@@ -437,16 +445,16 @@ function readFreshCatalog(opts) {
437
445
 
438
446
  // src/catalog/catalog-cache/read-stale-catalog.ts
439
447
  function readStaleCatalog(opts) {
440
- const { key, env = process.env } = opts;
441
- const keyHash = hashKey(key);
448
+ const { baseURL, key, env = process.env } = opts;
449
+ const keyHash = hashKey(baseURL, key);
442
450
  return (memory.get(keyHash) ?? readDisk(keyHash, env))?.models;
443
451
  }
444
452
 
445
453
  // src/catalog/catalog-cache/write-catalog.ts
446
454
  import * as fs4 from "fs";
447
455
  function writeCatalog(opts) {
448
- const { key, models, env = process.env, now = Date.now() } = opts;
449
- const keyHash = hashKey(key);
456
+ const { baseURL, key, models, env = process.env, now = Date.now() } = opts;
457
+ const keyHash = hashKey(baseURL, key);
450
458
  const record = { fetchedAt: now, keyHash, models };
451
459
  memory.set(keyHash, record);
452
460
  try {
@@ -622,6 +630,7 @@ function toCatalogModels(entries, env) {
622
630
  if (capabilities !== void 0) model.capabilities = capabilities;
623
631
  const variants = parseVariants(entry.variants);
624
632
  if (variants !== void 0) model.variants = variants;
633
+ if (typeof entry.hasZdrLane === "boolean") model.hasZdrLane = entry.hasZdrLane;
625
634
  const modalities = parseModalities(entry.modalities);
626
635
  if (modalities !== void 0) model.modalities = modalities;
627
636
  if (typeof entry.attachment === "boolean") model.attachment = entry.attachment;
@@ -696,7 +705,7 @@ async function runLadder(opts) {
696
705
  if (env[ENV_DISCOVERY] === "0")
697
706
  return noneResult(`discovery disabled via ${ENV_DISCOVERY}=0`, true);
698
707
  if (!key) return noneResult("no API key resolved", false);
699
- const fresh = readFreshCatalog({ key, env, now });
708
+ const fresh = readFreshCatalog({ baseURL, key, env, now });
700
709
  if (fresh) {
701
710
  logger.info(
702
711
  `catalog source: cache (${Object.keys(fresh).length} model(s))`
@@ -707,7 +716,7 @@ async function runLadder(opts) {
707
716
  const live = await fetchCatalog({ baseURL, key, env, fetchImpl });
708
717
  if (Object.keys(live).length === 0)
709
718
  throw new Error("gateway returned an empty catalog");
710
- writeCatalog({ key, models: live, env, now });
719
+ writeCatalog({ baseURL, key, models: live, env, now });
711
720
  logger.info(`catalog source: live (${Object.keys(live).length} model(s))`);
712
721
  return { models: live, source: "live" };
713
722
  } catch (error) {
@@ -715,7 +724,7 @@ async function runLadder(opts) {
715
724
  `live catalog fetch failed \u2014 trying stale cache: ${error instanceof Error ? error.message : String(error)}`
716
725
  );
717
726
  }
718
- const stale = readStaleCatalog({ key, env });
727
+ const stale = readStaleCatalog({ baseURL, key, env });
719
728
  if (stale) {
720
729
  logger.info(
721
730
  `catalog source: stale-cache (${Object.keys(stale).length} model(s))`
@@ -1035,6 +1044,11 @@ var FORCED_OPTION_FIELDS = /* @__PURE__ */ new Set([
1035
1044
  "baseURL",
1036
1045
  "headers"
1037
1046
  ]);
1047
+ function isForcedValue(field, value) {
1048
+ if (field === "apiKey" && value === NATIVE_SDK_API_KEY_PLACEHOLDER)
1049
+ return false;
1050
+ return FORCED_OPTION_FIELDS.has(field);
1051
+ }
1038
1052
  function mergeProvider(target, patch) {
1039
1053
  if (target.npm === void 0) target.npm = patch.npm;
1040
1054
  if (target.name === void 0) target.name = patch.name;
@@ -1044,7 +1058,7 @@ function mergeProvider(target, patch) {
1044
1058
  const options = target.options;
1045
1059
  for (const [field, value] of Object.entries(patch.options)) {
1046
1060
  if (value === void 0) continue;
1047
- if (FORCED_OPTION_FIELDS.has(field) || options[field] === void 0)
1061
+ if (isForcedValue(field, value) || options[field] === void 0)
1048
1062
  options[field] = value;
1049
1063
  }
1050
1064
  }
@@ -1061,102 +1075,36 @@ function mergeConfig(cfg, blocks) {
1061
1075
  }
1062
1076
  }
1063
1077
 
1064
- // src/config/merge-models.ts
1065
- function cloneEntry(entry) {
1066
- return { ...entry };
1067
- }
1068
- function cloneModels(patch) {
1069
- const models = {};
1070
- for (const [key, entry] of Object.entries(patch.models)) {
1071
- models[key] = cloneEntry(entry);
1072
- }
1073
- return models;
1074
- }
1075
- function fillModelEntry(existing, patchEntry) {
1076
- for (const [field, value] of Object.entries(patchEntry)) {
1077
- if (value === void 0) continue;
1078
- if (field === "limit" || existing[field] === void 0)
1079
- existing[field] = value;
1080
- }
1081
- }
1082
- function mergeModels(target, patch) {
1083
- if (target.models === void 0) {
1084
- target.models = cloneModels(patch);
1085
- return;
1086
- }
1087
- const models = target.models;
1088
- for (const [key, patchEntry] of Object.entries(patch.models)) {
1089
- const existing = models[key];
1090
- if (existing === void 0) {
1091
- models[key] = cloneEntry(patchEntry);
1092
- } else {
1093
- fillModelEntry(existing, cloneEntry(patchEntry));
1094
- }
1095
- }
1096
- }
1097
-
1098
- // src/config/generation-manifest.ts
1078
+ // src/config/read-model-overrides.ts
1099
1079
  import * as fs6 from "fs";
1100
1080
  import * as path7 from "path";
1101
1081
  var SIDECAR_DIR = ".opencode";
1102
- var MANIFEST_FILE = "route33-generated.json";
1103
1082
  var OVERRIDES_FILE = "route33-overrides.json";
1104
1083
  function isRecord7(value) {
1105
1084
  return typeof value === "object" && value !== null && !Array.isArray(value);
1106
1085
  }
1107
- function generatedPath(root) {
1108
- return path7.join(root, SIDECAR_DIR, MANIFEST_FILE);
1109
- }
1110
- function overridesPath(root) {
1111
- return path7.join(root, SIDECAR_DIR, OVERRIDES_FILE);
1112
- }
1113
- function parseManifest(value) {
1114
- if (!isRecord7(value) || value.version !== 1 || !Array.isArray(value.generatedKeys) || typeof value.lastBroadcastHash !== "string") return void 0;
1115
- if (!value.generatedKeys.every((key) => typeof key === "string")) return void 0;
1116
- return { version: 1, generatedKeys: value.generatedKeys, lastBroadcastHash: value.lastBroadcastHash };
1117
- }
1118
1086
  function parseOverrides(value) {
1119
1087
  if (!isRecord7(value) || !isRecord7(value.models) || !Object.values(value.models).every(isRecord7)) return void 0;
1120
1088
  const models = {};
1121
1089
  for (const [key, entry] of Object.entries(value.models)) if (isRecord7(entry)) models[key] = entry;
1122
1090
  return models;
1123
1091
  }
1124
- function readJson(file) {
1125
- try {
1126
- if (!fs6.existsSync(file)) return void 0;
1127
- const parsed = JSON.parse(fs6.readFileSync(file, "utf8"));
1128
- return parsed;
1129
- } catch (error) {
1130
- logger.info(`route_33: unable to read generated-model sidecar: ${error instanceof Error ? error.message : String(error)}`);
1131
- return void 0;
1132
- }
1133
- }
1134
- function readGenerationManifest(root) {
1135
- const file = generatedPath(root);
1136
- const manifest = parseManifest(readJson(file));
1137
- if (manifest === void 0 && fs6.existsSync(file)) logger.info("route_33: generated-model manifest is malformed; treating this run as first-run regeneration");
1138
- return manifest;
1139
- }
1140
1092
  function readModelOverrides(root) {
1141
- const file = overridesPath(root);
1142
- const overrides = parseOverrides(readJson(file));
1143
- if (overrides === void 0 && fs6.existsSync(file)) logger.info("route_33: model overrides sidecar is malformed; ignoring it for this run");
1144
- return overrides ?? {};
1145
- }
1146
- function writeGenerationManifest(root, manifest) {
1093
+ const file = path7.join(root, SIDECAR_DIR, OVERRIDES_FILE);
1094
+ let parsed;
1147
1095
  try {
1148
- fs6.mkdirSync(path7.join(root, SIDECAR_DIR), { recursive: true });
1149
- fs6.writeFileSync(generatedPath(root), `${JSON.stringify(manifest, null, 2)}
1150
- `);
1151
- return true;
1096
+ if (!fs6.existsSync(file)) return {};
1097
+ parsed = JSON.parse(fs6.readFileSync(file, "utf8"));
1152
1098
  } catch (error) {
1153
- logger.info(`route_33: unable to write generated-model manifest; using add-only behavior this run: ${error instanceof Error ? error.message : String(error)}`);
1154
- return false;
1099
+ logger.info(`route_33: unable to read model overrides sidecar: ${error instanceof Error ? error.message : String(error)}`);
1100
+ return {};
1155
1101
  }
1102
+ const overrides = parseOverrides(parsed);
1103
+ if (overrides === void 0) logger.info("route_33: model overrides sidecar is malformed; ignoring it for this run");
1104
+ return overrides ?? {};
1156
1105
  }
1157
1106
 
1158
1107
  // src/config/regenerate-models.ts
1159
- import { createHash as createHash2 } from "crypto";
1160
1108
  function isRecord8(value) {
1161
1109
  return typeof value === "object" && value !== null && !Array.isArray(value);
1162
1110
  }
@@ -1165,49 +1113,46 @@ function clone(value) {
1165
1113
  if (!isRecord8(value)) return value;
1166
1114
  return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, clone(item)]));
1167
1115
  }
1168
- function cloneEntry2(entry) {
1116
+ function cloneEntry(entry) {
1169
1117
  const copy = {};
1170
1118
  for (const [key, value] of Object.entries(entry)) copy[key] = clone(value);
1171
1119
  return copy;
1172
1120
  }
1173
1121
  function mergeEntry(base, override) {
1174
- const result = cloneEntry2(base);
1122
+ const result = cloneEntry(base);
1175
1123
  for (const [key, value] of Object.entries(override)) {
1176
1124
  const existing = result[key];
1177
1125
  result[key] = isRecord8(existing) && isRecord8(value) ? mergeEntry(existing, value) : clone(value);
1178
1126
  }
1179
1127
  return result;
1180
1128
  }
1181
- function stableJson(value) {
1182
- if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
1183
- if (!isRecord8(value)) return JSON.stringify(value);
1184
- return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(",")}}`;
1185
- }
1186
- function broadcastHash(models) {
1187
- return createHash2("sha256").update(stableJson(models)).digest("hex");
1188
- }
1189
1129
  function regenerateModels(input) {
1190
1130
  if (input.source === "none" && input.failedLiveFetch === true) {
1191
1131
  const models2 = {};
1192
1132
  for (const [key, entry] of Object.entries(input.currentModels ?? {}))
1193
- if (entry !== void 0) models2[key] = cloneEntry2(entry);
1133
+ if (entry !== void 0) models2[key] = cloneEntry(entry);
1194
1134
  return { models: models2, prunedKeys: [], skipped: true };
1195
1135
  }
1196
- const current = input.currentModels ?? {};
1197
- const generatedKeys = input.previousManifest?.generatedKeys ?? Object.keys(current);
1198
1136
  const models = {};
1199
- for (const [key, entry] of Object.entries(current)) {
1200
- if (!generatedKeys.includes(key) && entry !== void 0) models[key] = cloneEntry2(entry);
1201
- }
1202
- for (const [key, entry] of Object.entries(input.freshModels)) models[key] = cloneEntry2(entry);
1137
+ for (const [key, entry] of Object.entries(input.freshModels)) models[key] = cloneEntry(entry);
1203
1138
  for (const [key, override] of Object.entries(input.overrides ?? {}))
1204
1139
  models[key] = mergeEntry(models[key] ?? {}, override);
1205
- const freshKeys = Object.keys(input.freshModels).sort();
1206
- return {
1207
- models,
1208
- manifest: { version: 1, generatedKeys: freshKeys, lastBroadcastHash: broadcastHash(input.freshModels) },
1209
- prunedKeys: generatedKeys.filter((key) => input.freshModels[key] === void 0).sort()
1210
- };
1140
+ const prunedKeys = Object.keys(input.currentModels ?? {}).filter((key) => input.freshModels[key] === void 0 && input.overrides?.[key] === void 0).sort();
1141
+ return { models, prunedKeys };
1142
+ }
1143
+
1144
+ // src/config/remove-legacy-manifest.ts
1145
+ import * as fs7 from "fs";
1146
+ import * as path8 from "path";
1147
+ function removeLegacyManifest(root) {
1148
+ const file = path8.join(root, ".opencode", "route33-generated.json");
1149
+ try {
1150
+ if (!fs7.existsSync(file)) return;
1151
+ fs7.rmSync(file);
1152
+ logger.info("route_33: removed obsolete route33-generated.json (regeneration is stateless since 0.4.4)");
1153
+ } catch (error) {
1154
+ logger.info(`route_33: unable to remove legacy route33-generated.json: ${error instanceof Error ? error.message : String(error)}`);
1155
+ }
1211
1156
  }
1212
1157
 
1213
1158
  // src/config/collected-ref.ts
@@ -1437,6 +1382,7 @@ function createConfigHook(options = {}) {
1437
1382
  let key;
1438
1383
  let catalogAvailable = false;
1439
1384
  try {
1385
+ removeLegacyManifest(cwd);
1440
1386
  key = resolveKey(cfg, env, cwd);
1441
1387
  const baseURL = resolveBaseUrl(cfg, env, cwd);
1442
1388
  const catalog = await getCatalog({
@@ -1466,21 +1412,16 @@ function createConfigHook(options = {}) {
1466
1412
  const regeneration = regenerateModels({
1467
1413
  currentModels: provider.models,
1468
1414
  freshModels: blocks.route_33.models,
1469
- previousManifest: readGenerationManifest(cwd),
1470
1415
  overrides: readModelOverrides(cwd),
1471
1416
  source: catalog.source,
1472
1417
  failedLiveFetch: catalog.liveFetchFailed
1473
1418
  });
1474
1419
  if (regeneration.skipped) {
1475
- logger.info("route_33: catalog fetch failed without a cache; preserving existing managed model entries");
1420
+ logger.info("route_33: catalog fetch failed without a cache; preserving existing model entries");
1476
1421
  } else {
1477
- if (regeneration.manifest !== void 0 && !writeGenerationManifest(cwd, regeneration.manifest)) {
1478
- mergeModels(provider, blocks.route_33);
1479
- } else {
1480
- provider.models = regeneration.models;
1481
- }
1482
- for (const key2 of regeneration.prunedKeys)
1483
- 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`);
1422
+ provider.models = regeneration.models;
1423
+ for (const dropped of regeneration.prunedKeys)
1424
+ logger.info(`route_33: dropped model entry '${dropped}' (not in the gateway catalog); custom models belong in .opencode/route33-overrides.json \u2014 see README`);
1484
1425
  }
1485
1426
  }
1486
1427
  } catch (error) {
package/dist/tui/index.js CHANGED
@@ -31,6 +31,9 @@ var logger = {
31
31
  }
32
32
  };
33
33
 
34
+ // src/shared/native-sdk-api-key-placeholder.ts
35
+ var NATIVE_SDK_API_KEY_PLACEHOLDER = "__route33_fetch_shim_auth__";
36
+
34
37
  // src/shared/resolve-cache-dir.ts
35
38
  import * as os from "os";
36
39
  import * as path2 from "path";
@@ -142,9 +145,10 @@ function usable3(value) {
142
145
  }
143
146
  function keyFromConfig(cfg) {
144
147
  const literal = usable3(cfg?.provider?.route_33?.options?.apiKey);
145
- if (literal !== void 0 && !literal.includes(UNRESOLVED_TEMPLATE_MARKER))
146
- return literal;
147
- return void 0;
148
+ if (literal === void 0) return void 0;
149
+ if (literal.includes(UNRESOLVED_TEMPLATE_MARKER)) return void 0;
150
+ if (literal === NATIVE_SDK_API_KEY_PLACEHOLDER) return void 0;
151
+ return literal;
148
152
  }
149
153
  function resolveKey(cfg, env = process.env, worktree) {
150
154
  return usable3(env[ENV_API_KEY]) ?? keyFromConfig(cfg) ?? readWorkspaceKey(worktree) ?? (worktree !== void 0 && isInsideGitWorktree(worktree) ? void 0 : readAuthEntry(env).key);
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@route_33/opencode",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
- "description": "opencode provider plugin for the route33.ai AI gateway — key-scoped model discovery, cost/limit enrichment, auth, and the key-setup wizard.",
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",