Files
OmniRoute/@omniroute/opencode-plugin-v2
Dizzle 3156643f6c fix(opencode): require an http(s) baseURL in both OpenCode plugins (#13142)
Real and nasty precisely because it is silent: `z.string().url()` accepts `localhost:20128` as scheme `localhost:` plus a path, every model gets published with an unusable api url, and the failure happens inside the client so the gateway logs show nothing. Backing the option schema, the publish boundary and the snapshot filter with one `isHttpUrl` in v2 is the right call — those three cannot drift apart. Duplicating the predicate in v1 rather than sharing it is also correct, since the two packages ship independently.

---

Validated in one consolidated worktree cut from `release/v3.8.51`, boarded together with the rest of this batch — zero conflicts between them.

- `typecheck:core` clean; `check:changelog-integrity` OK
- complexity 2799 / baseline 3218 and cognitive-complexity 1265 / baseline 1437 — both under baseline
- 86 focused assertions green across the batch's 10 unit test files, plus 16/16 on the v1 plugin option schema and 16/16 on the v2 option tests
- `check-file-size` rebaselined for this batch's real growth (annotation `_rebaseline_2026_09_11_mergebatch_v3851_maxmad_opencode`, landed on #13141). `open-sse/utils/stream.ts` was deliberately left frozen: it is already 3115 > 3098 on the pure tip with zero contribution from this batch.

⚠️ base-red inherited: #12732 — `Docs Gates`, `Merge integrity`, `No new ESLint warnings`, `Unit Tests fast-path` and `Fast Quality Gates` all reproduce on the pure `release/v3.8.51` tip (provider count 356 vs the 358 the modules define, SKILL.md drift, and the `stream.ts` freeze above). None of them touch these diffs.

Thanks @maxmad64bis.
2026-09-11 13:51:17 -03:00
..

@omniroute/opencode-plugin-v2

OpenCode v2 plugin (define({ id, setup }), Promise API) that publishes the live OmniRoute catalog — models from /v1/models, combos from /api/combos (least-common-denominator join), auto-combos from /api/combos/auto, enrichment (names + pricing), and usable-provider filtering — into the v2 catalog.transform, with key + env auth via integration.transform.

Companion to @omniroute/opencode-plugin (OpenCode v1, same repo). The two packages are independent: this one carries its own catalog-mapping logic and the v1 plugin is left untouched.

Install

npm install @omniroute/opencode-plugin-v2

opencode.json:

{
  "plugins": [
    {
      "package": "@omniroute/opencode-plugin-v2",
      "options": {
        "providerId": "omniroute",
        "baseURL": "http://localhost:20128"
      }
    }
  ]
}

Credentials

The plugin needs a gateway key to read the catalog, and looks for one in this order:

  1. The credential you connected in OpenCode. The plugin registers an integration, so opencode auth (or the Connect action in the model picker) can store a key for it. Nothing is written to opencode.json — this is the recommended route.
  2. apiKey in the plugin options, when you want a per-project override. Remember that this puts the key in a config file you may be committing.
  3. OMNIROUTE_API_KEY in the environment.

If none of the three yields a key, the catalog is empty and the plugin says so once at startup rather than leaving you with a silent empty model list.

The management token is a different key

Combos, provider health and enrichment (display names, pricing, free-tier budgets) come from the gateway's /api/* endpoints, which most deployments gate behind a management token rather than the inference key. Set it explicitly:

"options": {
  "baseURL": "http://localhost:20128",
  "managementReadToken": "<management read token>"
}

Left unset, managementReadToken falls back to apiKey for backwards compatibility. When a gateway rejects that fallback, the catalog still publishes — but with raw model ids instead of display names, no canonical alias dedupe, no pricing and no combos. The plugin warns once per endpoint when this happens, naming the endpoint and the consequence, so the degraded catalog is never a mystery.

Options

Key Default Notes
providerId "omniroute" Provider id and integration id; models publish under <providerId>/…
baseURL required OmniRoute gateway root (no /v1 suffix needed)
apiKey connected credential, then OMNIROUTE_API_KEY Chat key for /v1/* — see Credentials
managementReadToken falls back to apiKey Management key for /api/* (combos, providers, enrichment) — usually not the same key
displayName "OmniRoute" Provider display name
timeoutMs 10000 Per-endpoint fetch timeout (auto-combos use 5s)
modelCacheTtlMs 300000 Catalog cache TTL; disk snapshot warms cold starts
timeouts per-endpoint override { models, combos, autoCombos, enrichment } in ms; falls back to timeoutMs
enrichment true Fetch names + pricing (/api/pricing*, /api/free-tier/summary)
providerTag true Prefix a display name with the upstream provider it routes to
geminiSanitization true Strip $schema/additionalProperties from tool schemas sent to Gemini models ($ref tools are forwarded untouched)
usableOnly false Filter to healthy provisioned providers (/api/providers)
visibleModels / hiddenModels [] Exact-or-suffix allowlists, deny wins
apiFormat.allowAnthropic false Route allowlisted ids to the Anthropic API block
apiFormat.anthropicModels [] Full model ids routed to Anthropic
apiFormat.anthropicPrefixes v1 defaults Deprecated, warns once — prefer anthropicModels
logLevel / startupDebug warn / false Logger verbosity

Tool calling on Gemini models

Gemini answers 400 INVALID_ARGUMENT — for the whole request, not just the offending tool — when a tool declaration carries $schema or additionalProperties. Anything that emits standard JSON Schema therefore breaks tool calling as soon as the chain routes to Gemini.

The plugin strips those keywords from tool schemas bound for a Gemini model of this provider, and leaves every other request untouched. A tool carrying a $ref is forwarded untouched instead of stripped: removing the reference would widen the schema to "accept anything". Set "geminiSanitization": false to turn it off.

Migrating from the v1 plugin

The v2 plugin publishes provider id X bare. The v1 plugin published opencode-X (native-adapter gate). Sessions pinned to opencode-X/... must re-select the model under X/....

License

MIT