feat: add OpenCode Go as a third model router

✅ merged · #340 · open-legal-products/mike ← duncanmcqueen/mike · opened 2mo ago by duncanmcqueen · merged 1mo ago by willchen96 · +1,512-275 across 51 files · ↗ on GitHub

From the PR description

Summary

Adds OpenCode Go - opencode.ai's low-cost subscription gateway for open coding models (GLM-5, Kimi K3, Qwen3.8 Max, MiniMax-M3, DeepSeek V4, ...) - as a third model router, alongside OpenRouter and the Vercel AI Gateway.

Rebased. This PR originally stacked on #339, which introduced a separate model-registry / OpenAI-compatible-provider foundation. main has since landed its own router layer (llm/openrouter.ts serving both OpenRouter and Vercel, lib/routerModels.ts, user_router_models, RouterSettingsSection). Rather than land a second, competing provider architecture, the branch has been rewritten onto current main in that idiom. It no longer depends on #339 and is a single self-contained commit. It is also rebased onto the #357 design-system refactor, so the new group is declared once in the shared ModelToggleUI primitive that both the web app and the Word add-in now render from.

What's added

Backend

  • lib/llm/openrouter.ts - opencode-go joins the RouterProvider union: base URL https://opencode.ai/zen/go/v1 (overridable via OPENCODE_GO_BASE_URL, like the other two), its own key lookup and label. OpenRouter's HTTP-Referer/X-Title attribution headers and its reasoning: {...} payload stay OpenRouter-only; OpenCode Go uses reasoning_effort like Vercel.
  • lib/llm/models.ts - providerForModel / resolveModel accept opencode-go/<model>; openCodeGoModelId strips the prefix.
  • lib/userApiKeys.ts + migration 20260820_01 - new opencode-go API-key provider (OPENCODE_API_KEY env or encrypted per-user storage); schema.sql updated to match so the schema-drift check stays green.
  • routes/models.ts - GET /models/opencode-go returns the user's live catalog (key never leaves the server), deduplicated and sorted by label.
  • routes/user.ts - the profile PATCH accepts openCodeGoModels, and the saved selection gates requests through the existing user_router_models choke point, so a hand-crafted request still can't spend the operator's env key on an arbitrary gateway model.

Frontend + Word add-in

  • lib/mikeApi.ts - getOpenCodeGoModels(), opencode-go API-key provider, openCodeGoModels on the profile.
  • Settings → API Keys gains an OpenCode Go field; Settings → Routers gains an OpenCode Go model picker.
  • ModelToggle (web) and the add-in's mirrored catalog gain an "OpenCode Go" group, gated on the user's key.

Design note

A router's slug, model-id prefix and API-key provider name are one string. That invariant already held for openrouter/vercel, so this PR makes the code depend on it explicitly: routerForModelId, the selection lookups, the profile payload fields and the title-model fallback are now driven by ROUTER_SLUGS rather than hard-coded pairs. A fourth router should be mostly a list entry plus a label.

The one place the routers genuinely differ is catalog-id shape: OpenRouter and Vercel publish vendor/model pairs, OpenCode Go publishes bare names (glm-5). Requiring a slash would have rejected its entire catalog, so id validation is per-router on both sides (ROUTER_MODEL_ID_RE in routes/user.ts, ROUTER_MODEL_ID in RouterSettingsSection.tsx) and the shared resolveModel regex has a matching branch.

Validation

Against main @ 5b441de:

  • backend: tsc --noEmit clean; vitest run - 699 passed, 24 skipped (baseline on main: 683 passed; +16 new tests covering the adapter, model ids, the catalog route, the per-router id shapes and the selection gate).
  • frontend: tsc --noEmit clean; eslint 0 errors; vitest run - 466 passed (baseline: 461).
  • word-addin: typecheck:app and typecheck:e2e clean; the cross-package catalog-parity and composer-availability drift guards extended to the new router and passing.

Three frontend files (mikeApi, useSelectedModel, ChatInput.modelSelection) fail identically on unmodified main in my sandbox - Blob.text is not a function and window.localStorage undefined, a local jsdom/env issue rather than a code change. Baseline is 12 failures, this branch is 14; the two extra are new useSelectedModel tests blocked by that same missing localStorage, and CI should run them.

Our analysis

Add OpenCode Go as a third model router — read the full analysis →

Think the analysis missed something the PR description covers?

Commits in this PR (4)

SHA Subject Author Date
23b41c5a feat: add OpenCode Go as a third model router duncanmcqueen 2026-08-20 ↗ GitHub
commit body
OpenCode Go (opencode.ai's low-cost hosted gateway for open coding models
- GLM-5, Kimi K3, Qwen3.8 Max, MiniMax-M3, DeepSeek V4) joins OpenRouter
and the Vercel AI Gateway as a router, reusing the existing router
plumbing end to end rather than adding a parallel one:

- opencode-go/<model> ids stream through the shared router adapter
  against https://opencode.ai/zen/go/v1, with OPENCODE_GO_BASE_URL as the
  usual proxy/test override. OpenRouter's attribution headers and
  reasoning payload stay OpenRouter-only.
- New opencode-go user API key (OPENCODE_API_KEY env or encrypted
  per-user storage) with a Settings > API Keys field and a migration
  widening the user_api_keys provider CHECK.
- GET /models/opencode-go returns the live catalog with the key kept
  server-side; Settings > Routers gains an OpenCode Go picker, and the
  saved selection gates requests through the same user_router_models
  choke point that stops a hand-crafted request from spending the
  operator's env key on an arbitrary gateway model.
- Both model pickers and the Word add-in gain an "OpenCode Go" group.

Router slugs, model-id prefixes and API-key provider names are one string
per router, so routerForModelId, the selection lookups and the profile
payload are now driven by ROUTER_SLUGS instead of a hard-coded pair. The
one place the routers genuinely differ is catalog-id shape: OpenCode Go
publishes bare model names ("glm-5"), so id validation is per-router.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
da8ed0e5 fix(llm): gate OpenCode Go models by protocol willchen96 2026-08-21 ↗ GitHub
3ec6d060 feat(llm): support OpenCode Go Messages models willchen96 2026-08-21 ↗ GitHub
0f006e2c refactor(settings): clarify bring your own keys navigation willchen96 2026-08-21 ↗ GitHub

Capture this PR into my fork

Download a Markdown prompt that tells Claude how to port every commit in this PR into your working tree. Run it via claude -p < capture-pull-340.md from inside the repo you want the changes in.

⬇ Download capture-pull-340.md