@route_33/opencode 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,14 +4,28 @@ An opencode provider plugin for the [route33.ai](https://route33.ai) AI gateway:
4
4
 
5
5
  ## Install
6
6
 
7
- Add to the `plugin` array of your opencode config and restart opencode:
7
+ ```bash
8
+ opencode plugin @route_33/opencode
9
+ ```
10
+
11
+ The installer patches both configs: the server plugin into `opencode.json` and the key-setup wizard (TUI plugin) into `tui.json`. To install manually instead, declare both entries yourself:
8
12
 
9
- ```json
13
+ ```jsonc
14
+ // opencode.json — server plugin (model discovery, cost/limit enrichment, auth)
10
15
  {
11
- "plugin": ["@route_33/opencode@^0.1"]
16
+ "plugin": ["@route_33/opencode@^0.2"]
12
17
  }
13
18
  ```
14
19
 
20
+ ```jsonc
21
+ // tui.json — TUI plugin (the key-setup wizard)
22
+ {
23
+ "plugin": ["@route_33/opencode@^0.2"]
24
+ }
25
+ ```
26
+
27
+ This plugin never writes your config: opencode has no TUI-plugin auto-discovery, so without a `tui.json` entry the wizard simply never appears — the server plugin works regardless.
28
+
15
29
  ## Auth
16
30
 
17
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). `ROUTE33_BASE_URL` overrides the default gateway URL; `ROUTE33_TUI_PROMPT=0` disables the wizard.
package/dist/index.d.ts CHANGED
@@ -10,6 +10,13 @@ import { Plugin } from '@opencode-ai/plugin';
10
10
  * validation — plus the `route_33` auth method and options loader.
11
11
  * Discovery runs once at startup; restart opencode after a key or config
12
12
  * change.
13
+ *
14
+ * The companion TUI plugin (`exports["./tui"]`, the key-setup wizard) is
15
+ * installed by the USER declaring it in `tui.json` — opencode has no TUI
16
+ * plugin auto-discovery, and this plugin never writes user config
17
+ * (upstream calls third-party tui.json mutation undesirable). The
18
+ * sanctioned install is `opencode plugin @route_33/opencode`, which
19
+ * patches both configs.
13
20
  */
14
21
  declare const Route33Plugin: Plugin;
15
22
 
package/dist/index.js CHANGED
@@ -245,215 +245,6 @@ function buildRoute33Auth(env = process.env, worktree) {
245
245
  };
246
246
  }
247
247
 
248
- // src/bootstrap/ensure-tui-registration.ts
249
- import * as path8 from "path";
250
-
251
- // src/bootstrap/global-context.ts
252
- import * as os3 from "os";
253
- import * as path5 from "path";
254
- import { fileURLToPath } from "url";
255
- function isGlobalContext(worktree, moduleUrl) {
256
- try {
257
- if (moduleUrl === void 0) return false;
258
- if (worktree === void 0) return true;
259
- const root = path5.resolve(worktree);
260
- if (root === "/") return true;
261
- const modulePath = fileURLToPath(moduleUrl);
262
- return !modulePath.startsWith(root + path5.sep);
263
- } catch {
264
- return false;
265
- }
266
- }
267
- function usable5(value) {
268
- const trimmed = value?.trim();
269
- return trimmed === void 0 || trimmed === "" ? void 0 : trimmed;
270
- }
271
- function globalTuiJsonPath(env) {
272
- const configHome = usable5(env.XDG_CONFIG_HOME) ?? path5.join(os3.homedir(), ".config");
273
- return path5.join(configHome, "opencode", "tui.json");
274
- }
275
-
276
- // src/bootstrap/tui-json-upsert.ts
277
- import * as fs4 from "fs";
278
- import * as path7 from "path";
279
- import {
280
- applyEdits,
281
- modify,
282
- parse as parseJsonc
283
- } from "jsonc-parser";
284
-
285
- // src/bootstrap/tui-spec-resolution.ts
286
- import * as fs3 from "fs";
287
- import * as os4 from "os";
288
- import * as path6 from "path";
289
- import { fileURLToPath as fileURLToPath2 } from "url";
290
- var NPM_TUI_SPEC = "@route_33/opencode";
291
- var DEAD_SUBPATH_TUI_SPEC = "@route_33/opencode/tui";
292
- var OLD_NPM_TUI_SPEC = "opencode-route33/tui";
293
- var TUI_BUNDLE_NAME = "route33-tui.js";
294
- var TUI_SCHEMA = "https://opencode.ai/tui.json";
295
- var SERVER_BUNDLE_NAME = "route33.js";
296
- var DIST_TUI_ENTRY_SUFFIX = "/opencode-route33/dist/tui/index.js";
297
- var SCOPED_DIST_TUI_ENTRY_SUFFIX = "/@route_33/opencode/dist/tui/index.js";
298
- var NPM_PACKAGE_DIR = path6.join("node_modules", "@route_33", "opencode");
299
- function fileExists(candidate) {
300
- try {
301
- return fs3.statSync(candidate).isFile();
302
- } catch {
303
- return false;
304
- }
305
- }
306
- function npmSpecFromModulePath(selfPath) {
307
- const segments = selfPath.split(path6.sep);
308
- for (let i = 0; i < segments.length - 1; i++) {
309
- const pkgDir = segments[i + 1];
310
- if (segments[i] === "@route_33" && pkgDir !== void 0 && pkgDir.startsWith("opencode@")) {
311
- const pin = pkgDir.slice("opencode@".length);
312
- if (pin !== "") return `${NPM_TUI_SPEC}@${pin}`;
313
- }
314
- }
315
- return selfPath.includes(`${NPM_PACKAGE_DIR}${path6.sep}`) ? NPM_TUI_SPEC : void 0;
316
- }
317
- function specFromModuleUrl(moduleUrl) {
318
- try {
319
- if (moduleUrl === void 0) return void 0;
320
- const selfPath = fileURLToPath2(moduleUrl);
321
- const dir = path6.dirname(selfPath);
322
- if (path6.basename(selfPath) === SERVER_BUNDLE_NAME) {
323
- const sibling = path6.join(dir, TUI_BUNDLE_NAME);
324
- return fileExists(sibling) ? sibling : void 0;
325
- }
326
- if (path6.basename(selfPath) === "index.js" && path6.basename(dir) === "dist") {
327
- const npmSpec = npmSpecFromModulePath(selfPath);
328
- if (npmSpec !== void 0) return npmSpec;
329
- const tuiEntry = path6.join(dir, "tui", "index.js");
330
- return fileExists(tuiEntry) ? tuiEntry : void 0;
331
- }
332
- return void 0;
333
- } catch {
334
- return void 0;
335
- }
336
- }
337
- function resolveTuiSpec(worktree, env, moduleUrl) {
338
- const fromModule = specFromModuleUrl(moduleUrl);
339
- if (fromModule !== void 0) return fromModule;
340
- if (worktree !== void 0 && worktree.trim() !== "" && path6.resolve(worktree) !== "/") {
341
- const local = path6.join(worktree, ".opencode", "plugins", TUI_BUNDLE_NAME);
342
- if (fileExists(local)) return local;
343
- }
344
- const configHome = env.XDG_CONFIG_HOME?.trim() || path6.join(os4.homedir(), ".config");
345
- const global = path6.join(configHome, "opencode", "plugins", TUI_BUNDLE_NAME);
346
- if (fileExists(global)) return global;
347
- return NPM_TUI_SPEC;
348
- }
349
- function isOurEntry(entry) {
350
- return typeof entry === "string" && (entry === NPM_TUI_SPEC || entry.startsWith(`${NPM_TUI_SPEC}@`) || entry === DEAD_SUBPATH_TUI_SPEC || entry === OLD_NPM_TUI_SPEC || entry.endsWith(`/${TUI_BUNDLE_NAME}`) || entry.endsWith(DIST_TUI_ENTRY_SUFFIX) || entry.endsWith(SCOPED_DIST_TUI_ENTRY_SUFFIX));
351
- }
352
-
353
- // src/bootstrap/tui-json-upsert.ts
354
- function isRecord3(value) {
355
- return typeof value === "object" && value !== null && !Array.isArray(value);
356
- }
357
- function fileExists2(candidate) {
358
- try {
359
- return fs4.statSync(candidate).isFile();
360
- } catch {
361
- return false;
362
- }
363
- }
364
- function upsertTuiSpec(file, spec) {
365
- try {
366
- if (!fileExists2(file)) {
367
- fs4.mkdirSync(path7.dirname(file), { recursive: true });
368
- fs4.writeFileSync(
369
- file,
370
- JSON.stringify({ $schema: TUI_SCHEMA, plugin: [spec] }, null, 2) + "\n",
371
- "utf8"
372
- );
373
- return;
374
- }
375
- const text = fs4.readFileSync(file, "utf8");
376
- const errors = [];
377
- const parsed = parseJsonc(text, errors, {
378
- allowTrailingComma: true,
379
- allowEmptyContent: true
380
- });
381
- if (!isRecord3(parsed)) {
382
- logger.warn(
383
- `tui.json at ${file} did not parse to an object \u2014 skipping TUI registration`
384
- );
385
- return;
386
- }
387
- if (parsed.plugin !== void 0 && !Array.isArray(parsed.plugin)) {
388
- logger.warn(
389
- `tui.json at ${file} has a non-array "plugin" key \u2014 skipping TUI registration`
390
- );
391
- return;
392
- }
393
- const entries = Array.isArray(parsed.plugin) ? parsed.plugin : [];
394
- const ourIndices = entries.flatMap(
395
- (entry, index) => isOurEntry(entry) ? [index] : []
396
- );
397
- const firstOurs = ourIndices[0];
398
- if (ourIndices.length === 1 && firstOurs !== void 0 && entries[firstOurs] === spec)
399
- return;
400
- const formattingOptions = { insertSpaces: true, tabSize: 2 };
401
- let next = text;
402
- if (!Array.isArray(parsed.plugin)) {
403
- next = applyEdits(
404
- next,
405
- modify(next, ["plugin"], [spec], { formattingOptions })
406
- );
407
- } else if (firstOurs === void 0) {
408
- next = applyEdits(
409
- next,
410
- modify(next, ["plugin", -1], spec, {
411
- formattingOptions,
412
- isArrayInsertion: true
413
- })
414
- );
415
- } else {
416
- if (entries[firstOurs] !== spec) {
417
- next = applyEdits(
418
- next,
419
- modify(next, ["plugin", firstOurs], spec, { formattingOptions })
420
- );
421
- }
422
- for (const index of ourIndices.slice(1).sort((a, b) => b - a)) {
423
- next = applyEdits(
424
- next,
425
- modify(next, ["plugin", index], void 0, { formattingOptions })
426
- );
427
- }
428
- }
429
- if (next !== text) fs4.writeFileSync(file, next, "utf8");
430
- } catch (error) {
431
- logger.warn(
432
- `could not register the route33 TUI plugin in tui.json: ${String(error)}`
433
- );
434
- }
435
- }
436
-
437
- // src/bootstrap/ensure-tui-registration.ts
438
- function ensureTuiRegistration(deps) {
439
- try {
440
- const { worktree, env } = deps;
441
- if (env.ROUTE33_TUI_PROMPT === "0") return;
442
- const usableWorktree = worktree !== void 0 && worktree.trim() !== "" && path8.resolve(worktree) !== "/" ? worktree : void 0;
443
- const spec = resolveTuiSpec(usableWorktree, env, deps.moduleUrl);
444
- if (usableWorktree !== void 0) {
445
- upsertTuiSpec(path8.join(usableWorktree, ".opencode", "tui.json"), spec);
446
- }
447
- if (isGlobalContext(worktree, deps.moduleUrl)) {
448
- upsertTuiSpec(globalTuiJsonPath(env), spec);
449
- }
450
- } catch (error) {
451
- logger.warn(
452
- `could not register the route33 TUI plugin in tui.json: ${String(error)}`
453
- );
454
- }
455
- }
456
-
457
248
  // src/config/live-auth-fetch.ts
458
249
  function buildLiveAuthFetch(resolveKeyNow, innerFetch) {
459
250
  return (input, init) => {
@@ -595,24 +386,24 @@ function hashKey(key) {
595
386
  }
596
387
 
597
388
  // src/catalog/catalog-cache/read-disk.ts
598
- import * as fs5 from "fs";
389
+ import * as fs3 from "fs";
599
390
 
600
391
  // src/catalog/catalog-cache/cache-file-path.ts
601
- import * as path9 from "path";
392
+ import * as path5 from "path";
602
393
  function cacheFilePath(keyHash, env) {
603
- return path9.join(resolveCacheDir(env), `catalog-${keyHash}.json`);
394
+ return path5.join(resolveCacheDir(env), `catalog-${keyHash}.json`);
604
395
  }
605
396
 
606
397
  // src/catalog/catalog-cache/read-disk.ts
607
- function isRecord4(value) {
398
+ function isRecord3(value) {
608
399
  return typeof value === "object" && value !== null && !Array.isArray(value);
609
400
  }
610
401
  function readDisk(keyHash, env) {
611
402
  try {
612
403
  const parsed = JSON.parse(
613
- fs5.readFileSync(cacheFilePath(keyHash, env), "utf8")
404
+ fs3.readFileSync(cacheFilePath(keyHash, env), "utf8")
614
405
  );
615
- if (!isRecord4(parsed) || typeof parsed.fetchedAt !== "number" || !isRecord4(parsed.models))
406
+ if (!isRecord3(parsed) || typeof parsed.fetchedAt !== "number" || !isRecord3(parsed.models))
616
407
  return void 0;
617
408
  return {
618
409
  fetchedAt: parsed.fetchedAt,
@@ -654,15 +445,15 @@ function readStaleCatalog(opts) {
654
445
  }
655
446
 
656
447
  // src/catalog/catalog-cache/write-catalog.ts
657
- import * as fs6 from "fs";
448
+ import * as fs4 from "fs";
658
449
  function writeCatalog(opts) {
659
450
  const { key, models, env = process.env, now = Date.now() } = opts;
660
451
  const keyHash = hashKey(key);
661
452
  const record = { fetchedAt: now, keyHash, models };
662
453
  memory.set(keyHash, record);
663
454
  try {
664
- fs6.mkdirSync(resolveCacheDir(env), { recursive: true });
665
- fs6.writeFileSync(cacheFilePath(keyHash, env), JSON.stringify(record));
455
+ fs4.mkdirSync(resolveCacheDir(env), { recursive: true });
456
+ fs4.writeFileSync(cacheFilePath(keyHash, env), JSON.stringify(record));
666
457
  } catch (error) {
667
458
  logger.warn(
668
459
  `could not persist catalog cache: ${error instanceof Error ? error.message : String(error)}`
@@ -686,7 +477,7 @@ var CatalogFetchError = class extends Error {
686
477
  var DEFAULT_FETCH_TIMEOUT_MS = 5e3;
687
478
 
688
479
  // src/catalog/extract-pricing.ts
689
- function isRecord5(value) {
480
+ function isRecord4(value) {
690
481
  return typeof value === "object" && value !== null && !Array.isArray(value);
691
482
  }
692
483
  var OPENROUTER_KEYS = [
@@ -743,7 +534,7 @@ function extractModelsDevPricing(raw) {
743
534
  return cost;
744
535
  }
745
536
  function extractPricing(raw) {
746
- if (!isRecord5(raw)) return void 0;
537
+ if (!isRecord4(raw)) return void 0;
747
538
  const hasOpenRouterKeys = OPENROUTER_KEYS.some((key) => key in raw);
748
539
  const hasModelsDevKeys = MODELS_DEV_KEYS.some((key) => key in raw);
749
540
  if (hasOpenRouterKeys && hasModelsDevKeys) return void 0;
@@ -774,12 +565,12 @@ function normalizeId(raw, env = process.env) {
774
565
  }
775
566
 
776
567
  // src/catalog/fetch-catalog.ts
777
- function isRecord6(value) {
568
+ function isRecord5(value) {
778
569
  return typeof value === "object" && value !== null && !Array.isArray(value);
779
570
  }
780
571
  function parseEntries(payload) {
781
572
  if (Array.isArray(payload)) return payload;
782
- if (isRecord6(payload) && Array.isArray(payload.data)) return payload.data;
573
+ if (isRecord5(payload) && Array.isArray(payload.data)) return payload.data;
783
574
  throw new CatalogFetchError(
784
575
  "parse",
785
576
  "response is neither {data:[\u2026]} nor a bare array"
@@ -788,13 +579,13 @@ function parseEntries(payload) {
788
579
  function toCatalogModels(entries, env) {
789
580
  const models = {};
790
581
  for (const entry of entries) {
791
- if (!isRecord6(entry) || typeof entry.id !== "string") continue;
582
+ if (!isRecord5(entry) || typeof entry.id !== "string") continue;
792
583
  const slug2 = normalizeId(entry.id, env);
793
584
  if (!slug2 || models[slug2]) continue;
794
585
  const model = { id: slug2 };
795
586
  const displayName = typeof entry.display_name === "string" ? entry.display_name.trim() : "";
796
587
  if (displayName !== "") model.displayName = displayName;
797
- if (isRecord6(entry.pricing) && extractPricing(entry.pricing) !== void 0)
588
+ if (isRecord5(entry.pricing) && extractPricing(entry.pricing) !== void 0)
798
589
  model.pricing = entry.pricing;
799
590
  models[slug2] = model;
800
591
  }
@@ -1203,11 +994,11 @@ function enrichModel(slug2, sources) {
1203
994
  }
1204
995
 
1205
996
  // src/enrich/models-dev/load-models-dev.ts
1206
- import * as fs7 from "fs";
1207
- import * as path10 from "path";
997
+ import * as fs5 from "fs";
998
+ import * as path6 from "path";
1208
999
 
1209
1000
  // src/enrich/models-dev/is-record.ts
1210
- function isRecord7(value) {
1001
+ function isRecord6(value) {
1211
1002
  return typeof value === "object" && value !== null && !Array.isArray(value);
1212
1003
  }
1213
1004
 
@@ -1226,7 +1017,7 @@ async function fetchLive(fetchImpl) {
1226
1017
  return void 0;
1227
1018
  }
1228
1019
  const body = await response.json();
1229
- return isRecord7(body) ? body : void 0;
1020
+ return isRecord6(body) ? body : void 0;
1230
1021
  } catch (error) {
1231
1022
  logger.warn("models.dev fetch failed:", error);
1232
1023
  return void 0;
@@ -1243,7 +1034,7 @@ function num(value) {
1243
1034
  // src/enrich/models-dev/normalize-cost.ts
1244
1035
  var EXPRESSIBLE_TIER_SIZE = 2e5;
1245
1036
  function parseRateBlock(value) {
1246
- if (!isRecord7(value)) return void 0;
1037
+ if (!isRecord6(value)) return void 0;
1247
1038
  const input = num(value.input);
1248
1039
  const output = num(value.output);
1249
1040
  if (input === void 0 || output === void 0) return void 0;
@@ -1256,7 +1047,7 @@ function parseRateBlock(value) {
1256
1047
  }
1257
1048
  function isExpressibleContextTier(tier) {
1258
1049
  const marker = tier.tier;
1259
- return isRecord7(marker) && marker.type === "context" && marker.size === EXPRESSIBLE_TIER_SIZE;
1050
+ return isRecord6(marker) && marker.type === "context" && marker.size === EXPRESSIBLE_TIER_SIZE;
1260
1051
  }
1261
1052
  function maxRate(current, candidate) {
1262
1053
  const rate = num(candidate);
@@ -1264,7 +1055,7 @@ function maxRate(current, candidate) {
1264
1055
  return current === void 0 || rate > current ? rate : current;
1265
1056
  }
1266
1057
  function normalizeCost(raw) {
1267
- if (!isRecord7(raw)) return void 0;
1058
+ if (!isRecord6(raw)) return void 0;
1268
1059
  const input = num(raw.input);
1269
1060
  const output = num(raw.output);
1270
1061
  if (input === void 0 || output === void 0) return void 0;
@@ -1275,7 +1066,7 @@ function normalizeCost(raw) {
1275
1066
  if (cacheWrite !== void 0) cost.cache_write = cacheWrite;
1276
1067
  const over200k = parseRateBlock(raw.context_over_200k);
1277
1068
  if (over200k) cost.context_over_200k = over200k;
1278
- const tiers = Array.isArray(raw.tiers) ? raw.tiers.filter(isRecord7) : [];
1069
+ const tiers = Array.isArray(raw.tiers) ? raw.tiers.filter(isRecord6) : [];
1279
1070
  for (const tier of tiers) {
1280
1071
  if (isExpressibleContextTier(tier)) {
1281
1072
  if (cost.context_over_200k === void 0) {
@@ -1305,10 +1096,10 @@ function swapLast(value, from, to) {
1305
1096
  return index === -1 ? value : `${value.slice(0, index)}${to}${value.slice(index + 1)}`;
1306
1097
  }
1307
1098
  function probeCandidates(slug2) {
1308
- const sep3 = slug2.indexOf("/");
1309
- if (sep3 <= 0 || sep3 >= slug2.length - 1) return [];
1310
- const vendor = slug2.slice(0, sep3);
1311
- const model = slug2.slice(sep3 + 1);
1099
+ const sep = slug2.indexOf("/");
1100
+ if (sep <= 0 || sep >= slug2.length - 1) return [];
1101
+ const vendor = slug2.slice(0, sep);
1102
+ const model = slug2.slice(sep + 1);
1312
1103
  const vendors = [vendor];
1313
1104
  const alias = VENDOR_ALIASES[vendor];
1314
1105
  if (alias !== void 0 && alias !== vendor) vendors.push(alias);
@@ -1333,7 +1124,7 @@ function probeCandidates(slug2) {
1333
1124
 
1334
1125
  // src/enrich/models-dev/lookup.ts
1335
1126
  function normalizeLimit(raw) {
1336
- if (!isRecord7(raw)) return void 0;
1127
+ if (!isRecord6(raw)) return void 0;
1337
1128
  const context = num(raw.context);
1338
1129
  const output = num(raw.output);
1339
1130
  if (context === void 0 || output === void 0) return void 0;
@@ -1362,9 +1153,9 @@ function buildLookup(model) {
1362
1153
  function lookupSlug(data, slug2) {
1363
1154
  for (const [providerKey, modelKey] of probeCandidates(slug2)) {
1364
1155
  const provider = data[providerKey];
1365
- if (!isRecord7(provider) || !isRecord7(provider.models)) continue;
1156
+ if (!isRecord6(provider) || !isRecord6(provider.models)) continue;
1366
1157
  const model = provider.models[modelKey];
1367
- if (isRecord7(model)) return buildLookup(model);
1158
+ if (isRecord6(model)) return buildLookup(model);
1368
1159
  }
1369
1160
  return void 0;
1370
1161
  }
@@ -1373,10 +1164,10 @@ function lookupSlug(data, slug2) {
1373
1164
  var CACHE_FILE = "models-dev.json";
1374
1165
  function readCache(cachePath) {
1375
1166
  try {
1376
- const parsed = JSON.parse(fs7.readFileSync(cachePath, "utf8"));
1377
- if (!isRecord7(parsed)) return void 0;
1167
+ const parsed = JSON.parse(fs5.readFileSync(cachePath, "utf8"));
1168
+ if (!isRecord6(parsed)) return void 0;
1378
1169
  const fetchedAt = num(parsed.fetchedAt);
1379
- if (fetchedAt === void 0 || !isRecord7(parsed.data)) return void 0;
1170
+ if (fetchedAt === void 0 || !isRecord6(parsed.data)) return void 0;
1380
1171
  return { fetchedAt, data: parsed.data };
1381
1172
  } catch {
1382
1173
  return void 0;
@@ -1384,8 +1175,8 @@ function readCache(cachePath) {
1384
1175
  }
1385
1176
  function writeCache(cachePath, envelope) {
1386
1177
  try {
1387
- fs7.mkdirSync(path10.dirname(cachePath), { recursive: true });
1388
- fs7.writeFileSync(cachePath, JSON.stringify(envelope), "utf8");
1178
+ fs5.mkdirSync(path6.dirname(cachePath), { recursive: true });
1179
+ fs5.writeFileSync(cachePath, JSON.stringify(envelope), "utf8");
1389
1180
  } catch (error) {
1390
1181
  logger.warn("failed to write models.dev cache:", error);
1391
1182
  }
@@ -1398,7 +1189,7 @@ async function loadModelsDev(options = {}) {
1398
1189
  const cacheDir = options.cacheDir ?? resolveCacheDir();
1399
1190
  const ttlMs = options.ttlMs ?? MODELS_DEV_TTL_MS;
1400
1191
  const now = options.now ?? Date.now;
1401
- const cachePath = path10.join(cacheDir, CACHE_FILE);
1192
+ const cachePath = path6.join(cacheDir, CACHE_FILE);
1402
1193
  const cached = readCache(cachePath);
1403
1194
  if (cached && now() - cached.fetchedAt < ttlMs)
1404
1195
  return makeSource("cache", cached.data);
@@ -1548,10 +1339,10 @@ function mergeConfig(cfg, blocks) {
1548
1339
 
1549
1340
  // src/config/collected-ref.ts
1550
1341
  function parseRef(ref, source) {
1551
- const sep3 = ref.indexOf("/");
1552
- if (sep3 <= 0 || sep3 >= ref.length - 1) return void 0;
1553
- const providerId = ref.slice(0, sep3);
1554
- const suffix = ref.slice(sep3 + 1);
1342
+ const sep = ref.indexOf("/");
1343
+ if (sep <= 0 || sep >= ref.length - 1) return void 0;
1344
+ const providerId = ref.slice(0, sep);
1345
+ const suffix = ref.slice(sep + 1);
1555
1346
  if (providerId === "route_33")
1556
1347
  return { providerId, modelKey: suffix, ref, source };
1557
1348
  if (providerId !== "route_33_anthropic") return void 0;
@@ -1581,34 +1372,34 @@ function collectCfgRefs(cfg) {
1581
1372
  }
1582
1373
 
1583
1374
  // src/config/collect-flavour-refs.ts
1584
- import * as fs8 from "fs";
1585
- import * as os5 from "os";
1586
- import * as path11 from "path";
1587
- import { parse as parseJsonc2 } from "jsonc-parser";
1588
- function isRecord8(value) {
1375
+ import * as fs6 from "fs";
1376
+ import * as os3 from "os";
1377
+ import * as path7 from "path";
1378
+ import { parse as parseJsonc } from "jsonc-parser";
1379
+ function isRecord7(value) {
1589
1380
  return typeof value === "object" && value !== null && !Array.isArray(value);
1590
1381
  }
1591
1382
  function findFlavourPath(env, cwd) {
1592
1383
  const candidates = [];
1593
1384
  const configDir = env.OPENCODE_CONFIG_DIR?.trim();
1594
- if (configDir) candidates.push(path11.join(configDir, "oh-my-openagent.jsonc"));
1595
- candidates.push(path11.join(cwd, ".opencode", "oh-my-openagent.jsonc"));
1596
- const configHome = env.XDG_CONFIG_HOME?.trim() || path11.join(os5.homedir(), ".config");
1597
- candidates.push(path11.join(configHome, "opencode", "oh-my-openagent.jsonc"));
1385
+ if (configDir) candidates.push(path7.join(configDir, "oh-my-openagent.jsonc"));
1386
+ candidates.push(path7.join(cwd, ".opencode", "oh-my-openagent.jsonc"));
1387
+ const configHome = env.XDG_CONFIG_HOME?.trim() || path7.join(os3.homedir(), ".config");
1388
+ candidates.push(path7.join(configHome, "opencode", "oh-my-openagent.jsonc"));
1598
1389
  for (const candidate of candidates) {
1599
1390
  try {
1600
- if (fs8.existsSync(candidate)) return candidate;
1391
+ if (fs6.existsSync(candidate)) return candidate;
1601
1392
  } catch {
1602
1393
  }
1603
1394
  }
1604
1395
  return void 0;
1605
1396
  }
1606
1397
  function collectSectionRefs(refs, section, label) {
1607
- if (!isRecord8(section)) return;
1398
+ if (!isRecord7(section)) return;
1608
1399
  for (const [name, entry] of Object.entries(section)) {
1609
- if (!isRecord8(entry)) continue;
1400
+ if (!isRecord7(entry)) continue;
1610
1401
  pushRef(refs, entry.model, `flavour ${label} "${name}"`);
1611
- if (label === "agent" && isRecord8(entry.ultrawork)) {
1402
+ if (label === "agent" && isRecord7(entry.ultrawork)) {
1612
1403
  pushRef(
1613
1404
  refs,
1614
1405
  entry.ultrawork.model,
@@ -1622,8 +1413,8 @@ function collectFlavourRefs(env, cwd) {
1622
1413
  if (flavourPath === void 0) return [];
1623
1414
  const refs = [];
1624
1415
  try {
1625
- const parsed = parseJsonc2(fs8.readFileSync(flavourPath, "utf8"));
1626
- if (!isRecord8(parsed)) {
1416
+ const parsed = parseJsonc(fs6.readFileSync(flavourPath, "utf8"));
1417
+ if (!isRecord7(parsed)) {
1627
1418
  logger.warn(
1628
1419
  `flavour file ${flavourPath} did not parse to an object \u2014 skipping flavour ref validation`
1629
1420
  );
@@ -1802,11 +1593,6 @@ function createConfigHook(options = {}) {
1802
1593
 
1803
1594
  // src/index.ts
1804
1595
  var Route33Plugin = async (input, _options) => {
1805
- ensureTuiRegistration({
1806
- worktree: input?.worktree ?? input?.directory,
1807
- env: process.env,
1808
- moduleUrl: import.meta.url
1809
- });
1810
1596
  ensureWorkspaceKeyGitignore(input?.worktree ?? input?.directory);
1811
1597
  return {
1812
1598
  config: createConfigHook(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@route_33/opencode",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "description": "opencode provider plugin for the route33.ai AI gateway — key-scoped model discovery, cost/limit enrichment, auth, and the key-setup wizard.",
@@ -19,6 +19,10 @@
19
19
  "types": "./dist/index.d.ts",
20
20
  "import": "./dist/index.js"
21
21
  },
22
+ "./server": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js"
25
+ },
22
26
  "./tui": {
23
27
  "types": "./dist/tui/index.d.ts",
24
28
  "import": "./dist/tui/index.js"