Skip to content

feat: auto-sync models from CommandCode API at startup - #1

Open
augustoolucas wants to merge 3 commits into
brent-weatherall:mainfrom
augustoolucas:feat/auto-sync-models
Open

feat: auto-sync models from CommandCode API at startup#1
augustoolucas wants to merge 3 commits into
brent-weatherall:mainfrom
augustoolucas:feat/auto-sync-models

Conversation

@augustoolucas

@augustoolucas augustoolucas commented Jun 4, 2026

Copy link
Copy Markdown

Auto-syncs the model list from the CommandCode API on opencode startup, so context sizes and new models are kept up to date without manual intervention. The local models.json is still bundled with the plugin as an offline fallback.

Motivation

The local models.json was getting stale — context sizes were wrong (e.g. xiaomi/mimo-v2.5-pro was 128K instead of 1M) and new models required a manual sync to be added.

Changes

  • plugin.ts: config hook now fetches /provider/v1/models on startup (5s timeout, AbortController)
  • plugin.ts: merges API response with local models.json — API provides context_length, local provides curated cost/tier/reasoning
  • plugin.ts: persists merged result back to models.json for offline use
  • plugin.ts: idempotent saveModels (skips write when content unchanged)
  • plugin.ts: empty API responses are treated as failure to avoid wiping local models
  • models.json: 6 new models added, 4 context sizes corrected
  • README.md: documents auto-sync behavior
  • tests/unit/plugin.test.ts: 9 new tests (6 pure unit tests for mergeModels, 3 integration tests using existing mockFetch/mockFetchTrack helpers)

Test plan

  • bun test — 82 pass, 0 fail (was 73)
  • bun run typecheck — clean
  • Manual: restart opencode with COMMANDCODE_API_KEY set, verify models.json timestamp updates and new context sizes take effect

Compatibility

  • No breaking changes — the plugin still works fully offline using the bundled models.json
  • The auto-sync is a background convenience and is best-effort — if the API is unreachable, the plugin falls back to the bundled models.json
  • scripts/sync-models.ts is kept for users who want explicit control

Notes for reviewer

  • mergeModels and ModelEntry are exported to enable unit testing
  • saveModels is wrapped in try/catch with silent failure (per existing project pattern)
  • All filesystem writes are gated behind successful API response (non-empty data array)

- Fetch model list from CommandCode API on plugin config hook
- Merge with local models.json (API provides context_length, local provides curated cost/tier/reasoning)
- Persist merged result back to models.json for offline fallback
- Treat empty API response as failure to avoid wiping local models
- Add idempotency to saveModels (only write when content changed)
- Add new models and fix context sizes in models.json
- Add unit tests for mergeModels (pure) and integration tests using existing mockFetch helpers
@bolatovmaksat

Copy link
Copy Markdown

Nice 👍
Waiting to be merged and released

- Switch from disk write to in-memory merge; sync updates no longer
  persist between startups
- Add config-file opt-out at
  ~/.config/opencode/commandcode-go-opencode-provider.json
  with `disableModelSync: true` to skip the API fetch entirely
- loadModels: throw actionable error on missing/corrupt bundled
  models.json ("please reinstall commandcode-go-opencode-provider",
  original error chained via Error.cause)
- Tests: add withFakeConfig / withMissingModels / withCorruptModels
  helpers and structural assertions for bundled, opt-out, and
  load-error paths
- README: document the opt-out workflow and bundled-list refresh
  via `bun run sync`
@augustoolucas
augustoolucas marked this pull request as draft June 6, 2026 23:26
@KRoperUK

Copy link
Copy Markdown

Really like this direction — auto-syncing from the API is the right fix for the staleness. I built a small patch on top of this branch with three proposed tweaks and opened it as #5 (stacked on these commits, so it's just the one extra commit to review):

  1. Sync without an API key. fetchModelsFromApi currently returns early if COMMANDCODE_API_KEY isn't set, but /provider/v1/models is public — it returns the full catalog unauthenticated (no Authorization header needed). Since /connect users don't export that env var, auto-sync silently no-ops for most people as written. Patch runs the request unauthenticated when there's no key, and still sends Bearer <key> when there is.

  2. Use the API's name field. The response includes name (e.g. "MiMo V2.5 Pro"); right now new models get id.split("/").pop() ("mimo-v2.5-pro"). Capturing name in ApiModel fixes the display name.

  3. Tier + pricing for API-only models. tier is hardcoded open-source (wrong for any future bare-id premium model) and cost is a fabricated 0.5/2. Patch derives tier from the id namespace and zeroes unknown cost so it doesn't feed invented pricing into accounting.

All your existing tests pass (I reworked the "missing key" one to assert the keyless fetch path, and added a name test) — 87 pass, typecheck clean. Happy to fold these into your branch directly if you'd prefer that over a stacked PR — whatever's easiest for you and @brent-weatherall.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants