Skip to content

Commit 2888d26

Browse files
committed
fix: prefer compatible advertised model routes
1 parent 1e20382 commit 2888d26

7 files changed

Lines changed: 77 additions & 61 deletions

File tree

‎README.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ This package is based on **[FanFan4204/opencode-commandcode-provider](https://gi
2525
- Vision vs text-only comes from the Command Code CLI catalog (`inputModalities` on every SKU). [models.dev](https://models.dev) only adds extra inputs (video/audio/pdf) when it matches.
2626
- Reasoning effort **variants** on models that declare `reasoningEfforts`.
2727
- Release date, family, input limits, model status, vendor context limits, and matched context-price tiers flow through the bundled catalog. V2 gets native release, family, input, status, and tier fields; V1 keeps its supported fields and base prices, with `context_over_200k` where representable.
28-
- The provider's `supported_endpoints` metadata chooses each model's API route. Responses-capable models use `@ai-sdk/openai` on V1 and `aisdk:@ai-sdk/openai` on V2; Claude falls back to Anthropic Messages when endpoint metadata is absent. The V1 and V2 Responses mappings passed official Docker checks against a local mock stream, and a GOAT-key DeepSeek control request succeeded in OpenCode V2. Claude Sonnet 4.6 returned `MODEL_NOT_IN_PLAN` with the message “available in Pro and above plans or extra on-demand usage”; this confirms the account restriction, not successful Anthropic routing. Entitled Claude access has not been live-verified. If a model fails on an account entitled to use it, please [open an issue](https://github.com/BrainerVirus/opencode-commandcode/issues) with the model ID and OpenCode version, or submit a PR with a reproducible fix.
28+
- The provider's `supported_endpoints` metadata chooses each model's API route. When a model advertises Messages, it uses Anthropic Messages; otherwise Chat Completions is preferred when available, and Responses is used when it is the only advertised route. This avoids malformed annotation events seen on the provider's Responses stream while preserving Responses-only models. V1 maps Responses to `@ai-sdk/openai`; V2 maps it to `aisdk:@ai-sdk/openai`. Claude Sonnet 4.6 returned `MODEL_NOT_IN_PLAN` with the message “available in Pro and above plans or extra on-demand usage”; entitled Claude access has not been live-verified. If a model fails on an account entitled to use it, please [open an issue](https://github.com/BrainerVirus/opencode-commandcode/issues) with the model ID and OpenCode version, or submit a PR with a reproducible fix.
2929
- Quiet OpenCode startup (diagnostics go to `startup.json`, not stdout).
3030

3131
## How it works
3232

3333
For published plugin versions, the six-hour CI schedule extracts the latest `command-code` npm bundle and refreshes callable models plus endpoint metadata, even when the CLI version is unchanged. It opens a catalog PR only when generated artifacts change; at startup the plugin loads those artifacts and registers them with OpenCode — never the other way around.
3434

3535
- **Extract + filter** — model entries (ids, names, reasoning, `inputModalities`, limits) are evaluated out of the minified CLI bundle (`src/catalog.ts`), then intersected with the callable IDs reported by the provider API.
36-
- **Metadata + costs merge** — vendor context length tightens only a fallback context limit, while `supported_endpoints` selects Chat Completions, Responses, or Messages per model; missing endpoint metadata preserves the last known route data. models.dev contributes release date, family, input limit, status, modalities, and cost tiers when present. Tier rows are accepted only when their base prices match this Command Code catalog. Base costs use CLI bundle → official Command Code docs → free SKUs (`$0`) → [models.dev](https://models.dev) reference prices → unmatched placeholder. Anything still unmatched marks the catalog `degraded`. This runs at sync time only; runtime never fetches metadata or prices.
36+
- **Metadata + costs merge** — vendor context length tightens only a fallback context limit, while `supported_endpoints` selects Messages, Chat Completions, or Responses per model in that preference order; missing endpoint metadata preserves the last known route data. models.dev contributes release date, family, input limit, status, modalities, and cost tiers when present. Tier rows are accepted only when their base prices match this Command Code catalog. Base costs use CLI bundle → official Command Code docs → free SKUs (`$0`) → [models.dev](https://models.dev) reference prices → unmatched placeholder. Anything still unmatched marks the catalog `degraded`. This runs at sync time only; runtime never fetches metadata or prices.
3737
- **Artifacts** — `models.json` (the catalog), `_version.txt` (upstream version), `manifest.json` (counts, per-source cost stats, `healthy`/`degraded`/`broken` status).
3838
- **Version-specific registration** — V1 uses the `plugin` config key, `server()` provider map, and V1 auth callback. V2 uses `plugins`, provider/model transforms, and a `commandcode` integration with key and `COMMANDCODE_API_KEY` environment methods. Both retain the Command Code wire model ID. V2 exposes tiered context pricing; V1 emits its supported `context_over_200k` field and keeps flat pricing for other tiers.
3939
- **Degraded/cache fallbacks** — a `degraded`/`broken` manifest sets the degraded flag with a reason; an unreadable bundled `models.json` falls back to the last-good cache; auth/connect still registers even with an empty catalog.
@@ -46,19 +46,19 @@ OpenCode V2:
4646

4747
```json
4848
{
49-
"plugins": ["@brainervirus/opencode-commandcode@latest"]
49+
"plugins": ["@brainervirus/opencode-commandcode"]
5050
}
5151
```
5252

5353
OpenCode V1:
5454

5555
```json
5656
{
57-
"plugin": ["@brainervirus/opencode-commandcode@latest"]
57+
"plugin": ["@brainervirus/opencode-commandcode"]
5858
}
5959
```
6060

61-
Pin a version instead of `@latest` if you do not want automatic catalog patches.
61+
The bare package name is unpinned and resolves npm's `latest` release when OpenCode installs or updates it; adding `@latest` is unnecessary. OpenCode V2 checks for updates at startup but keeps an existing cached package. Apply a newer package with OpenCode's plugin-update action, then restart to load it. OpenCode V1 can refresh the global package with `opencode plugin @brainervirus/opencode-commandcode --global --force`.
6262

6363
`file://` checkouts are **not** updated by npm; `git pull` after CI commits, or switch to the npm plugin line.
6464

@@ -76,7 +76,7 @@ Set `COMMANDCODE_API_KEY`, or connect interactively. OpenCode V1 provides **Comm
7676
/models
7777
```
7878

79-
Catalog patches arrive as plugin updates: `@latest` refreshes in the background and takes effect on the next OpenCode restart. If a new model is missing after an announced sync, restart opencode once.
79+
Catalog patches ship in new npm releases. OpenCode loads the version in its package cache; an update must be applied through the host before the new catalog appears. If a new model is missing after an announced sync, check the installed package version, apply the update, and restart OpenCode.
8080

8181
## Plugin config file
8282

‎docs/specs/2026-08-28-ci-catalog-automation.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,12 @@ When a subsequent sync succeeds after manual fix:
180180
Published npm name (locked, same account as workit: `brainervirus`):
181181

182182
```json
183-
"plugin": ["@brainervirus/opencode-commandcode@latest"]
183+
{ "plugins": ["@brainervirus/opencode-commandcode"] }
184184
```
185185

186186
`package.json` `name` is `@brainervirus/opencode-commandcode` with `publishConfig.access: "public"` (published as `@brainervirus/commandcode-go-opencode-provider` before the 0.6.0 rename). This stays its own repo; it is not folded into `workflow-toolkit`.
187187

188-
`file://` installs are **not** auto-updated by CI; catalog changes require `git pull` of this repo. npm installs update through `@latest`.
188+
OpenCode V1 uses the singular `plugin` config key with the same bare package name. The package name without a suffix is unpinned and resolves npm's latest dist-tag when installed or updated; `@latest` is redundant. OpenCode V2 checks for unpinned updates at startup but keeps an existing package cache until the host's update action is applied. `file://` installs are not updated by npm; catalog changes require `git pull` of this repo.
189189

190190
## Runtime Plugin Changes
191191

@@ -294,7 +294,7 @@ Same sequence as the identity spec. Phase 1 (A) must refresh `models.json` befor
294294
1. Rename package to `@brainervirus/commandcode-go-opencode-provider` (shipped at 0.5.0; renamed to `@brainervirus/opencode-commandcode` at 0.6.0).
295295
2. Store `NPM_TOKEN` (npm user `brainervirus`) as a GitHub Actions secret; `publishConfig.access: public`.
296296
3. Enable publish + GitHub Release in the workflow.
297-
4. Document `"plugin": ["@brainervirus/opencode-commandcode@latest"]` vs pin.
297+
4. Document the bare unpinned package name and explicit-version pinning.
298298

299299
## Historical Plan Decomposition
300300

‎docs/specs/2026-09-20-catalog-freshness.md‎

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# End-to-End Command Code Catalog Freshness
22

3-
Status: provider steps shipped (0.7.72, 2026-09-20); upstream OpenCode refresh pending
3+
Status: provider steps shipped (0.7.72, 2026-09-20); automatic warm-cache refresh remains an upstream OpenCode limitation
44

55
## Goal
66

@@ -10,7 +10,7 @@ Freshness has three independent boundaries:
1010

1111
1. Command Code must identify which extracted models are currently callable.
1212
2. This provider must publish only that callable catalog with internally consistent metadata.
13-
3. OpenCode must refresh mutable plugin package references without discarding a working cache.
13+
3. OpenCode must make it clear when a mutable plugin package is stale and how to update it without losing a working cache.
1414

1515
## Current Failure
1616

@@ -20,7 +20,7 @@ Separately, OpenCode can retain an older installation of `@brainervirus/opencode
2020

2121
The release pipeline also updated `manifest.json` after npm publication. Consequently, the manifest inside a newly published tarball could report the previous plugin version even though the repository was corrected by a later synchronization PR.
2222

23-
Provider-side resolution (2026-09-20): the availability filter shipped in `fix(catalog): exclude unavailable models` and the packed-manifest alignment shipped in `fix(release): align published manifest version`. The OpenCode-side mutable-package refresh (step 3 below) is still pending upstream.
23+
Provider-side resolution (2026-09-20): the availability filter shipped in `fix(catalog): exclude unavailable models` and the packed-manifest alignment shipped in `fix(release): align published manifest version`. The original request for automatic warm-cache replacement is not implemented by OpenCode V2.0.18; startup loads the cached package and does not replace it. The plugin cannot update the package code that is already running.
2424

2525
## Decisions
2626

@@ -86,19 +86,9 @@ The unavailable list is sorted by ID so repeated synchronization is deterministi
8686

8787
### Mutable OpenCode plugin packages
8888

89-
The upstream OpenCode implementation will rework pull request [anomalyco/opencode#49485](https://github.com/anomalyco/opencode/pull/49485).
89+
OpenCode V2.0.18 loads a warm cached package at startup and does not silently replace it. An isolated local startup with a cached Command Code 0.9.1 package loaded 0.9.1 even though npm `latest` was 0.10.1. A clean cache does install the latest package. OpenCode's current V2 plugin documentation describes startup update checks without changing the installed package; the host's update action must be applied before a restart loads the new code. V1.18.30's `opencode plugin <module> --global --force` explicitly refreshes a global package.
9090

91-
For npm plugin references:
92-
93-
- exact versions are immutable and use the cache without refresh;
94-
- bare package names, dist-tags such as `@latest`, and version ranges are mutable;
95-
- a mutable reference with no cached installation blocks on installation; a failure reports the normal plugin installation error and leaves no partial cache;
96-
- a mutable reference with a cached installation loads that cache immediately and starts at most one background refresh per OpenCode process;
97-
- a successful background refresh becomes active on the next OpenCode restart; plugins are not hot-swapped in a running process;
98-
- a failed background refresh leaves the cached installation untouched and does not block startup; and
99-
- equivalent bare and explicit-latest references share one canonical cache location.
100-
101-
Concurrent refresh attempts for the same canonical package are deduplicated. Installation remains atomic so interruption cannot replace a working cache with a partial package.
91+
The bare package name is the normal unpinned reference; adding `@latest` is redundant. Exact versions remain pinned. The plugin reads only its bundled catalog and cannot refresh its own npm package or hot-swap its running code.
10292

10393
Git, file, and workspace plugin references are outside this behavior change.
10494

@@ -114,7 +104,7 @@ Release verification must inspect the packed artifact and fail before publicatio
114104

115105
The installed plugin reads only its bundled `models.json` and `manifest.json` for normal model registration. It does not call the availability endpoint, npm registry, or GitHub to decide which models to expose.
116106

117-
This preserves deterministic startup and offline use. Freshness is delivered by catalog automation plus OpenCode's mutable-package refresh behavior.
107+
This preserves deterministic startup and offline use. Freshness is delivered by catalog automation plus the host's explicit package-update flow; a warm npm cache can remain stale until that update is applied.
118108

119109
## Non-Goals
120110

@@ -140,13 +130,11 @@ This preserves deterministic startup and offline use. Freshness is delivered by
140130

141131
### OpenCode package refresh
142132

143-
- A warm mutable cache starts OpenCode without waiting for the registry.
144-
- A cold mutable install failure reports an installation error and leaves no cache entry.
145-
- One background refresh is attempted per canonical mutable package per process.
146-
- Successful refresh output is used after restart, not during the current process.
147-
- Offline or failed refresh preserves and continues using the prior cache.
148-
- Exact-version references perform no background refresh.
149-
- Bare and `@latest` references cannot maintain divergent cache roots.
133+
- A clean cache with the bare package entry installs the current npm `latest` release.
134+
- A warm OpenCode V2.0.18 cache loads its installed version at startup without replacing it.
135+
- Applying a host plugin update and restarting loads the new package.
136+
- The V1 force-install command refreshes its configured global package.
137+
- Exact-version references remain pinned; `@latest` is not required for an unpinned package.
150138

151139
### Release metadata
152140

@@ -156,7 +144,7 @@ This preserves deterministic startup and offline use. Freshness is delivered by
156144

157145
### End-to-end
158146

159-
From a clean OpenCode cache, installing the mutable Command Code plugin and restarting after a successful refresh exposes every exact-ID match between extractor candidates and the current API list, including newly added matches, and exposes none of the API-absent retired or unreleased entries.
147+
From a clean OpenCode cache, the bare Command Code package installs the current npm release and exposes the catalog in that tarball. With a warm stale cache, users must apply the host's package update and restart before the newer catalog appears. In both cases the catalog exposes only the exact-ID matches produced by the provider sync.
160148

161149
## Coordination Plan
162150

@@ -178,17 +166,17 @@ From a clean OpenCode cache, installing the mutable Command Code plugin and rest
178166
- Owner: OpenCode contributor.
179167
- Files: the package installation/cache path and tests in the upstream OpenCode repository.
180168
- Dependency: rework pull request `#49485`; independent of provider steps 1 and 2.
181-
- Evidence: cold, warm, offline, exact-version, canonical-cache, deduplication, and next-restart tests in upstream CI.
169+
- Evidence: upstream docs and local warm-cache tests show that V2 checks but does not silently replace a cached package; automatic replacement still requires upstream support.
182170
- Commit: follow OpenCode repository convention.
183-
- Status: pending upstream.
171+
- Status: pending upstream; see [OpenCode plugin update behavior](https://opencode.ai/v2/docs/plugins).
184172
4. **Rollout verification**
185173
- Owner: provider maintainer.
186174
- Dependency: provider release and an OpenCode build containing step 3.
187-
- Evidence: compare the installed tarball manifest to its package version, launch from a clean cache, restart after refresh, and compare displayed provider IDs with the public availability response.
175+
- Evidence: compare the installed tarball manifest to its package version, launch from a clean cache, apply a host update to a warm cache, restart, and compare displayed provider IDs with the public availability response.
188176
- Commit: none unless verification finds a defect.
189177
- Status: pending upstream.
190178

191-
Provider steps 1 and 2 shipped on 2026-09-20 before the upstream change. Until OpenCode releases step 3, users on stale mutable caches may still need one manual cache refresh; that temporary operational workaround is not part of the target behavior.
179+
Provider steps 1 and 2 shipped on 2026-09-20. Until OpenCode adds automatic package installation for warm caches, users may need to apply the host update action manually; do not clear the cache as a workaround because it discards a working install.
192180

193181
## References
194182

0 commit comments

Comments
 (0)