fix: pivot installer from Catwalk to Models.dev#124
Merged
Conversation
OpenCode validates model IDs against the Models.dev registry at runtime (per https://opencode.ai/docs/models/), but the installer was using Catwalk — which uses different provider IDs for AWS Bedrock (`bedrock` vs `amazon-bedrock`) and Google Vertex (`vertexai` vs `google-vertex-anthropic`). Every Bedrock and Vertex preset emitted by the installer crashed at agent invocation with `Agent … configured model … is not valid`. Only Anthropic direct worked because its provider ID is identical in both registries. Pivot the installer to Models.dev — the same source of truth the runtime consults — eliminating the ID schism at the root. New module `src/cli/models-dev.ts` fetches, types, and tier-suggests from `models.dev/api.json`. Bedrock preset now uses `amazon-bedrock/global.anthropic.*` (CRIS global route for highest availability); Vertex preset uses `google-vertex-anthropic/*@default`. Anthropic preset unchanged. Validator (`src/model-validator.ts`) now flags any ID with a `bedrock/` or `vertex(ai)/` prefix as invalid and suggests the correct Models.dev replacement. Runtime warns users on next startup; `doctor` enumerates offenders. No auto-rewrite of user config. Delete `src/cli/catwalk.ts` and `test/catwalk.test.ts` — no other consumers. Add `test/models-dev.test.ts` (16 new tests covering fetch, suggestTiers, pickBedrockTierIds). Update all existing tests to assert the new behavior; the "valid IDs" buckets previously locked Catwalk-style IDs as correct (they weren't). Full suite: 878 pass, 0 fail, 47 files. Plan: ~/.glorious/opencode/glorious-opencode/plans/fix-amazon-bedrock-provider-prefix.md
308ffc3 to
a6b7910
Compare
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pivot installer from Catwalk to Models.dev (fix amazon-bedrock prefix)
Goal
OpenCode's runtime resolves
<provider_id>/<model_id>against Models.dev's registry (verified from docs athttps://opencode.ai/docs/models/: "OpenCode uses the AI SDK and Models.dev to support 75+ LLM providers"). The plugin's installer fetches provider/model data from a different registry — Catwalk athttps://catwalk.charm.land/v2/providers— to offer provider selection and suggest deep/mid/fast tiers. The two registries disagree on provider IDs for at least Bedrock (Catwalk:"bedrock", Models.dev:"amazon-bedrock") and Vertex (Catwalk: no entry; Models.dev:"google-vertex-anthropic"). The installer's hardcodedMODEL_PRESETS, its dynamic Catwalk-driven path, the validator's suggestion map, and multiple test fixtures all emit Catwalk-style IDs. OpenCode then rejects those IDs at agent invocation withAgent <name>'s configured model <id> is not valid.Fix by pivoting the installer to use Models.dev as its data source — the same registry the runtime consults. This collapses the dual-source bug class entirely: what the installer writes into opencode.json is what the runtime validates against.
Out-of-the-box-broken providers before this fix: Bedrock, Vertex. Anthropic (direct) has worked because its provider ID happens to be identical in both registries.
Constraints
suggestTiers(provider)produces${provider.id}/${model.id}refs — when we switch the input shape from Catwalk's schema to Models.dev's, the ref format must match what the runtime expects.amazon-bedrock/global.anthropic.*(decided above). Models without aglobal.variant fall back to non-prefixedanthropic.*(example: Opus 4.1 has noglobal.variant on Models.dev).@glrs-dev/harness-opencode@<pre-fix>tuple-formharness.modelsblock may still exist in user configs with Catwalk-style IDs (bedrock/anthropic.*). The validator must flag these with anamazon-bedrock/global.anthropic.*suggestion.test/catwalk.test.ts:105-121"produces correct refs for real Bedrock provider shape") must be rewritten or deleted, not preserved.bun run build && bun run typecheck && bun testmust pass.minorbump (user-visible fix, changes the installer's output and flags previously-silently-accepted IDs — this is not a pure patch).Acceptance criteria
src/cli/models-dev.tsexports:interface ModelsDevModel { id: string; name?: string; ... }— typed subset of Models.dev's model shapeinterface ModelsDevProvider { id: string; name: string; models: Record<string, ModelsDevModel>; }— typed subset of Models.dev's provider shapeasync function fetchModelsDevProviders(): Promise<ModelsDevProvider[] | null>— fetcheshttps://models.dev/api.json, returns the provider list. Returns null on network error (not throws).function suggestTiersFromModelsDev(provider: ModelsDevProvider): { deep: string; mid: string; fast: string }— cost-sorted tier suggestion. Uses${provider.id}/${model.id}ref format.function pickBedrockTierIds(models: ModelsDevProvider): { deep: string; mid: string; fast: string }— specialized Bedrock picker preferringglobal.CRIS variants, falling back to non-prefixedanthropic.*where global doesn't exist.src/cli/install.ts:MODEL_PRESETSrewritten: Anthropic preset unchanged; Bedrock preset becomes{ providerId: "amazon-bedrock", deep: "amazon-bedrock/global.anthropic.claude-opus-4-7", mid: "amazon-bedrock/global.anthropic.claude-sonnet-4-6", fast: "amazon-bedrock/global.anthropic.claude-haiku-4-5-20251001-v1:0" }; Vertex preset becomes{ providerId: "google-vertex-anthropic", deep: "google-vertex-anthropic/claude-opus-4-7@default", mid: "google-vertex-anthropic/claude-sonnet-4-6@default", fast: "google-vertex-anthropic/claude-haiku-4-5@20251001" }.src/cli/install.ts:379-418is replaced with the Models.dev equivalent usingfetchModelsDevProviders+suggestTiersFromModelsDev. If Models.dev fetch fails, fall through to hardcodedMODEL_PRESETS.amazon-bedrock/global.anthropic.claude-opus-4-7.src/cli/catwalk.tseither (a) deleted entirely if nothing else depends on it, or (b) retained as-is but marked@deprecatedin JSDoc with a comment pointing atsrc/cli/models-dev.ts. Audit:grep -rn "catwalk\|suggestTiers\|fetchProviders" src/ test/before deciding.src/model-validator.ts:LEGACY_PRE_100_PATTERNextended to match^bedrock\/(any Bedrock-style Catwalk ID — all are broken at runtime) AND^vertex(ai)?\/(same — all broken). Keep the existing pre-fix: correct agent modes, model IDs, and add Catwalk-powered installer #100 no-subpath form as a sub-case.LEGACY_TO_CATWALKrenamed toLEGACY_TO_MODELS_DEVto reflect the correct target. Every suggestion value rewritten to a Models.dev-valid ID. Entries added forbedrock/anthropic.claude-opus-4-6,bedrock/anthropic.claude-opus-4-7,bedrock/anthropic.claude-sonnet-4-6,bedrock/anthropic.claude-haiku-4-5-20251001-v1:0→ theiramazon-bedrock/global.anthropic.*equivalents. Vertex entries rewritten togoogle-vertex-anthropic/claude-*@default.test/model-validator.test.ts:bedrock/anthropic.*entries (4 lines). Removevertexai/claude-*@YYYYMMDDentries. Add positive cases foramazon-bedrock/global.anthropic.claude-opus-4-7,amazon-bedrock/us.anthropic.claude-sonnet-4-6,amazon-bedrock/anthropic.claude-opus-4-6-v1(non-CRIS),google-vertex-anthropic/claude-opus-4-7@default,anthropic/claude-opus-4-7(unchanged — still valid).bedrock/anthropic.claude-opus-4-6→amazon-bedrock/global.anthropic.claude-opus-4-7,bedrock/anthropic.claude-sonnet-4-6→amazon-bedrock/global.anthropic.claude-sonnet-4-6,bedrock/anthropic.claude-haiku-4-5-20251001-v1:0→amazon-bedrock/global.anthropic.claude-haiku-4-5-20251001-v1:0,vertexai/claude-opus-4-6@20250610→google-vertex-anthropic/claude-opus-4-6@default. Existing no-subpath entries (bedrock/claude-opus-4, etc.) updated to Models.dev suggestions.test/catwalk.test.ts:// locks legacy behavior for backward compat; new installer uses src/cli/models-dev.ts. If catwalk.ts deleted, the test file deleted.test/models-dev.test.ts(NEW):fetchModelsDevProviders— mock fetch, assert typed return, assert null on network error.suggestTiersFromModelsDev— feed fixture with three models of differing costs, assert deep = most expensive, fast = cheapest, format${provider.id}/${model.id}.pickBedrockTierIds— fixture containing bothglobal.anthropic.claude-opus-4-7andanthropic.claude-opus-4-7siblings; assert picker returns theglobal.variant. Second fixture whereglobal.missing for Opus 4.1 — assert falls back toanthropic.claude-opus-4-1-20250805-v1:0.test/harness-models.test.ts:bedrock/anthropic.*string literals updated. Lines 293 and 331 now usebedrock/anthropic.claude-opus-4-6as an invalid test case (move to "flags bad ID" test, not "does not warn"). Add a new "does not warn when overrides are valid Models.dev IDs" case usingamazon-bedrock/global.anthropic.*.test/doctor.test.ts:amazon-bedrock/global.anthropic.claude-opus-4-7andamazon-bedrock/global.anthropic.claude-sonnet-4-6..changeset/pivot-installer-to-models-dev.md(NEW) withminorbump and the description below.bun run build && bun run typecheck && bun testpass.node -e "const v = require('./dist/model-validator.js'); console.log(v.validateModelOverride('bedrock/anthropic.claude-opus-4-6'))"returns{ valid: false, reason: ..., suggestion: 'amazon-bedrock/global.anthropic.claude-opus-4-7' }.node dist/cli.js installin a sandbox ($XDG_CONFIG_HOME→ tempdir), pick AWS Bedrock preset, verifyamazon-bedrock/global.anthropic.appears 3× in emitted opencode.json.File-level changes
src/cli/models-dev.ts (NEW)
https://models.dev/api.json, parse top-level object keyed by provider ID (not an array — verify during implementation; the explore-agent returned a map shape when finding"amazon-bedrock"as top-level key). Export the typed provider/model shapes needed by the installer. ImplementsuggestTiersFromModelsDevmirroring the cost-sorted algorithm fromsrc/cli/catwalk.ts:59-95. ImplementpickBedrockTierIdswith theglobal.preference.MODEL_PRESETSon any failure.src/cli/install.ts
MODEL_PRESETSentries for Bedrock and Vertex (line 66-88) per acceptance criteria. Leave Anthropic entry unchanged../catwalk.jswith import from./models-dev.js(line 33). UpdatefetchProviders→fetchModelsDevProviders,suggestTiers→suggestTiersFromModelsDev.MODEL_PRESETSfallback; always be ready to offer presets if fetch fails.src/cli/catwalk.ts (DELETE)
src/cli/install.ts:33(changing) andtest/catwalk.test.ts:2(also deleting). Confirmed viagrep -rn "catwalk" src/ test/— no other importers.src/model-validator.ts
LEGACY_PRE_100_PATTERNto also flag^bedrock\/and^vertex(ai)?\/catch-alls (these provider prefixes are never valid at runtime; any ID starting with them is broken). RenameLEGACY_TO_CATWALK→LEGACY_TO_MODELS_DEV. Rewrite all suggestion values to Models.dev IDs. Add entries for the off-by-one forms (bedrock/anthropic.*). Update module-level JSDoc explaining the dual-registry problem and why this validator exists.test/model-validator.test.ts
test/catwalk.test.ts (DELETE)
src/cli/catwalk.tsis being deleted too; no point keeping the test for a deleted module.test/models-dev.test.ts (NEW)
test/harness-models.test.ts
anthropic/claude-opus-4-7— still valid), one for Models.dev Bedrock (amazon-bedrock/global.anthropic.*— now valid). Move anybedrock/anthropic.*strings from "valid" assertions to "flags bad ID" assertions.test/doctor.test.ts
.changeset/pivot-installer-to-models-dev.md (NEW)
bunx changeset, pickminor. Body:Test plan
bun test test/models-dev.test.ts.bun test test/model-validator.test.ts.bun test test/harness-models.test.ts.bun test test/doctor.test.ts.bun test test/catwalk.test.tsif retained; otherwise confirm file is gone and no other test imports fromsrc/cli/catwalk.ts.bun test. Expect 0 failures.bun run typecheck— no errors. Models.dev response types must flow through.bun run build— dist output includesmodels-dev.js/.d.ts.npm publish --dry-run— verify tarball content.node -e "const v = require('./dist/model-validator.js'); console.log(v.validateModelOverride('bedrock/anthropic.claude-opus-4-6'))"→{ valid: false, suggestion: 'amazon-bedrock/global.anthropic.claude-opus-4-7', ... }.XDG_CONFIG_HOME=/tmp/smoke node dist/cli.js install, pick AWS Bedrock, verifyamazon-bedrock/global.anthropic.3× in/tmp/smoke/opencode/opencode.json.Out of scope
"gemini"in Catwalk, likely"google"or"google-generative-ai"in Models.dev — unverified). MODEL_PRESETS currently only ships those three; if a user hits a Gemini bug they'll file a separate issue.src/config-hook.ts'sresolveHarnessModels(). It already calls the validator; the validator is the only thing that changes.Open questions
All resolved during grounding pass.
src/cli/install.ts:33andtest/catwalk.test.ts:2import it. Decision: delete both files and remove the dependency entirely. Cleaner than deprecating in-place.Record<string, Provider>(object map keyed by provider ID).Provider.modelsis also an object map keyed by model ID. Iterate viaObject.entries(...)both levels.fetchModelsDevProviders()wraps fetch + JSON.parse in try/catch, returnsnullon any failure (network, HTTP non-2xx, parse error, unexpected shape). Installer falls through to hardcodedMODEL_PRESETS.model.cost.inputandmodel.cost.output(numbers, USD per 1M tokens).cost.cache_readandcost.cache_writeare optional. Catwalk's flatcost_per_1m_in/cost_per_1m_outdo not exist on Models.dev — the new tier suggester readsmodel.cost.input+model.cost.outputfor cost-sort.Concrete type definitions (resolved)
Progress log
opencode models(94amazon-bedrock/*, 0bedrock/*, 0vertexai/*) and docs verification (Models.dev is the runtime source of truth).global.CRIS prefix.