Skip to content

Commit f5c75bc

Browse files
authored
feat: complete OpenCode V2 parity and endpoint routing (#144)
* feat: complete model metadata and pricing parity * feat: add OpenCode V2 auth integration * docs: document OpenCode V2 parity * feat: route models through advertised endpoints * fix: avoid timestamp-only catalog sync changes * chore: remove redundant provider config * docs: record Claude plan-gated probe
1 parent 3b5faab commit f5c75bc

26 files changed

Lines changed: 1952 additions & 231 deletions

‎.github/workflows/catalog-sync.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
workflow_dispatch:
77
inputs:
88
force:
9-
description: Re-extract even if command-code version is unchanged
9+
description: Force a catalog refresh, including for an unpublished plugin version
1010
type: boolean
1111
default: false
1212

‎AGENTS.md‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,5 @@ Any branch that changes shipped behavior — `plugin.ts`, `index.ts`, `src/**`,
4242

4343
- `src/schemas.ts` is the single source of truth for `ModelEntry` / `CatalogManifest` shapes. `src/catalog.ts` and `src/manifest.ts` derive them via `z.infer` — never redeclare the shape. Verify: `grep -rn "interface ModelEntry\|type CatalogManifest = {" src` must be empty.
4444
- zod lives only at validation boundaries (bundled `models.json` / `manifest.json` reads in `plugin.ts`, provider availability payloads, user config files). Hot paths (`src/convert.ts`, `src/stream.ts`, `src/model.ts`, the `generate*` model loops) stay zod-free. Verify: `grep -rn 'from "zod"' src/convert.ts src/stream.ts src/model.ts` must be empty.
45-
- `dist/plugin.js` budget: ~708KB with zod bundled (was ~45KB type-only). A second runtime dependency requires either `--external` in `scripts/build-plugin.ts` or updating this budget line. Verify: `bun run build && du -h dist/plugin.js`.
4645
- V1 (`generateOpencodeModels` in `src/catalog.ts`) and V2 (`toV2Model` in `src/v2models.ts`) cost/limit/modality mappings must stay in parity — change one, update the other plus `tests/unit/v2models.test.ts`. UI/map keys use `toConfigKey` in `src/catalog.ts` (do not duplicate it); the Command Code wire id is always catalog `entry.id` (V1 model `id`, V2 `modelID`) — bare short names 400 as unsupported_model.
4746
- Entry points: `plugin.ts` owns all config-hook logic; `index.ts` re-exports + SDK factory; `src/entry.ts` is bundle glue for `scripts/build-plugin.ts` only. Do not add a fourth entry or duplicate the catalog-load path.

‎README.md‎

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
[Command Code](https://commandcode.ai) API provider for [opencode](https://opencode.ai). Use Claude, GPT, Gemini, DeepSeek, Qwen, Kimi, GLM, MiniMax, Step, and other models through a single API key.
88

9+
This plugin is for Command Code accounts with Provider API access. GOAT is the live-tested baseline; other API-enabled plans can use models their account is entitled to. The $1 Go plan has no Provider API access. The catalog follows the provider-wide model list, so a model appearing in OpenCode does not imply that every account can call it. See [GOAT plan details](https://commandcode.ai/docs/plans/goat) and [Provider API docs](https://commandcode.ai/docs/provider).
10+
911
This package keeps a **bundled** model catalog current via CI. You do **not** need a local `command-code` CLI. Catalog patches publish automatically after a green PR merges to `main`.
1012

1113
Previously published as `@brainervirus/commandcode-go-opencode-provider`. Use this name instead.
@@ -17,44 +19,56 @@ This package is based on **[FanFan4204/opencode-commandcode-provider](https://gi
1719
### What this package adds
1820

1921
- Bundled `models.json` is the default runtime catalog (no local CLI scrape).
20-
- Dual OpenCode entry: V2 `setup` injects the provider plus models via transforms; V1 `server` fills `provider.commandcode` defaults plus models and registers API-key auth (auth stays V1-only).
22+
- V1 and V2 use their native plugin and provider surfaces. V1 `server()` fills `provider.commandcode` and registers API-key auth; V2 `setup()` registers provider/model transforms and a key/env integration for `/connect` and `opencode auth login`.
2123
- Validation at the boundaries via `src/schemas.ts` (zod): bundled `models.json` / `manifest.json` reads, provider availability payloads, and the plugin config file.
2224
- CLI cost extraction can fail without dropping models; official docs fill missing costs, remaining paid gaps use [models.dev](https://models.dev) as a reference price at sync time. Command Code free SKUs stay `$0`.
2325
- 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.
2426
- Reasoning effort **variants** on models that declare `reasoningEfforts`.
27+
- 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.
2529
- Quiet OpenCode startup (diagnostics go to `startup.json`, not stdout).
2630

2731
## How it works
2832

29-
Each CI sync extracts the model catalog from the latest `command-code` npm bundle, merges costs, and commits versioned artifacts; at startup the plugin loads those artifacts and registers them with OpenCode — never the other way around.
33+
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.
3034

3135
- **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.
32-
- **Costs merge** — per model, first hit wins: CLI bundle costs → 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 prices.
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.
3337
- **Artifacts** — `models.json` (the catalog), `_version.txt` (upstream version), `manifest.json` (counts, per-source cost stats, `healthy`/`degraded`/`broken` status).
34-
- **V1 injection + V2 transform** — V1 `server()` fills `provider.commandcode` defaults and the models map; V2 `setup()` adds/updates the provider inventory (baseURL, API key binding) and sets models through `ctx.provider.transform`.
38+
- **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.
3539
- **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.
3640

3741
## Quick Start
3842

3943
### 1. Install the plugin
4044

45+
OpenCode V2:
46+
47+
```json
48+
{
49+
"plugins": ["@brainervirus/opencode-commandcode@latest"]
50+
}
51+
```
52+
53+
OpenCode V1:
54+
4155
```json
4256
{
4357
"plugin": ["@brainervirus/opencode-commandcode@latest"]
4458
}
4559
```
4660

47-
Pin a version instead of `@latest` if you do not want automatic catalog patches. The config key stays `plugin` in OpenCode V2 — there is no `plugins` key.
61+
Pin a version instead of `@latest` if you do not want automatic catalog patches.
4862

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

5165
### 2. No provider block needed
5266

53-
On OpenCode V2 the plugin registers the `commandcode` provider itself (Provider API base URL plus `COMMANDCODE_API_KEY` binding) and its models. On V1 the `server` hook fills the same `provider.commandcode` defaults — `npm: "@ai-sdk/openai-compatible"` plus the Provider API `baseURL`; the plugin package itself is never the SDK `npm` field. Only add a manual `provider.commandcode` entry if you need non-default transport options.
67+
On OpenCode V2 the plugin registers the `commandcode` provider, its models, and its API base URL through the V2 provider API. On V1 the `server` hook fills `provider.commandcode` defaults — `npm: "@ai-sdk/openai-compatible"` plus the Provider API `baseURL`; the plugin package itself is never the SDK `npm` field. Only add a manual provider entry if you need non-default transport options.
5468

5569
### 3. Connect
5670

57-
Set `COMMANDCODE_API_KEY`, or on OpenCode V1 run `/connect`, search for **Command Code**, and enter your API key. V2 registers the provider with the env binding; the `/connect` API-key method is V1-only.
71+
Set `COMMANDCODE_API_KEY`, or connect interactively. OpenCode V1 provides **Command Code** through `/connect`; OpenCode V2 registers key and environment methods for `/connect` and `opencode auth login commandcode`. V2 uses OpenCode's automatic provider activation and preserves an explicit activation setting. Connect before running a model; an explicit run while disconnected still returns an authorization error from the API.
5872

5973
### 4. Select a model
6074

@@ -97,9 +111,9 @@ bun run generate-readme # reports catalog counts only; README is hand-edited
97111
bun run catalog:ci # entry used by the catalog-sync workflow
98112
```
99113

100-
Entry points: `plugin.ts` owns all config-hook logic (dual default `{ id, setup }` plus `server`); `index.ts` re-exports the plugin plus the `createCommandCode` SDK factory; `src/entry.ts` is bundle glue for `scripts/build-plugin.ts` only — it produces `dist/plugin.js`.
114+
Entry points: `plugin.ts` owns both config surfaces (V2 `id`/`setup` plus V1 `server`); `index.ts` re-exports the plugin plus the `createCommandCode` SDK factory; `src/entry.ts` is bundle glue for `scripts/build-plugin.ts` only — it produces `dist/plugin.js`.
101115

102-
CI (`.github/workflows/catalog-sync.yml`) opens a `fix(catalog)` PR every 6 hours when Command Code ships a new catalog; if extraction fails it opens a `catalog-break` issue instead. The PR auto-merges after **check (test)**, **check (typecheck)**, **check (lint)**, **check (format)**, and **check (pack)** are green. `.github/workflows/release.yml` then runs **semantic-release** (build + verified npm publish + GitHub Release + tag). Do not push to `main`.
116+
CI (`.github/workflows/catalog-sync.yml`) checks every 6 hours for CLI catalog changes and provider availability/endpoint metadata changes; it opens a `fix(catalog)` PR only when generated files change. If extraction fails or the model-count safety floor is breached, it opens a `catalog-break` issue and leaves the last-good files intact. The PR auto-merges after **check (test)**, **check (typecheck)**, **check (lint)**, **check (format)**, and **check (pack)** are green. `.github/workflows/release.yml` then runs **semantic-release** (build + verified npm publish + GitHub Release + tag). Do not push to `main`.
103117

104118
The GitHub Actions secret name is `NPMJS`. It is mapped to both `NPM_TOKEN` and `NODE_AUTH_TOKEN`. Use an npm **Automation** token (bypasses 2FA). A login token from `~/.npmrc` fails CI with `EOTP`. Catalog PRs get a real CI run when `RELEASE_SYNC_TOKEN` is a PAT; `GITHUB_TOKEN` can open the PR but GitHub will not start workflows from that event.
105119

‎docs/2026-08-28-ci-catalog/spec.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Watch `command-code` on npm every 6 hours, refresh the bundled catalog, publish
1717
- Human merges to `main` run semantic-release; releases are path-gated (`scripts/analyze-release-scope.ts`), so CI/docs/tests-only merges do not publish.
1818
- Cost-only CLI failure still ships (`degraded` only if unmatched placeholder costs remain). Model extract failure → no publish, `catalog-break` issue.
1919
- Runtime catalog stays bundled `models.json`. No GitHub fetch at OpenCode startup.
20-
- Hybrid OpenCode transport stays `@ai-sdk/openai-compatible` + Provider API; this package is the **plugin**, not the SDK `npm` field.
20+
- Chat Completions keep `@ai-sdk/openai-compatible`; provider `supported_endpoints` selects per-model Responses (`@ai-sdk/openai`) or Anthropic Messages where advertised. This package remains the **plugin**, not the SDK `npm` field.
2121

2222
## First publish
2323

‎docs/specs/2026-08-19-stable-model-identity.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Command Code OpenCode Provider — Runtime Identity and Resilience
22

3-
Status: shipped (updated 2026-09-26)
3+
Status: shipped baseline (updated 2026-09-26; current V2 additions are in the [2026-09-28 parity spec](./2026-09-28-v2-parity.md))
44

55
## Goal
66

@@ -35,7 +35,7 @@ Every load with models rewrites the cache; every load writes `startup.json`. Wri
3535
| cost data missing after the CI waterfall | continue; unmatched costs keep the placeholder and the manifest is `degraded` |
3636
| provider availability call fails in sync | no artifact writes and a `catalog-break` issue; the previous catalog stays |
3737
| opt-in local extract fails | ignore override; use bundled |
38-
| auth/connect | registers on V1 regardless of catalog state |
38+
| auth/connect | V1 auth and V2 integration register regardless of catalog state |
3939

4040
Degraded reporting is internal: `startup.json` carries `degraded` and `degradedReason`, and V1/V2 registration is unchanged. There is no separate degraded UI.
4141

@@ -57,7 +57,7 @@ Degraded reporting is internal: `startup.json` carries `degraded` and `degradedR
5757
- Default is quiet: no `console.log`/`console.warn` in the plugin load path.
5858
- `~/.local/state/opencode/commandcode-provider/startup.json` records `catalogSource` (`bundled`/`cache`/`opt-in-local`), `commandCodeVersion`, `modelCount`, `reasoningModelCount`, `degraded`, and `degradedReason`.
5959
- `debugStartupLogs: true` mirrors the summary to stderr once.
60-
- V1 `server()` registers provider defaults and the API-key auth method; V2 `setup()` adds/updates the provider inventory and models through transforms. Auth stays V1-only.
60+
- At this spec's 0.9.1 baseline, V1 `server()` registered provider defaults and the API-key auth method; V2 `setup()` only added/updated provider inventory and models. V2 key/env integration support was added later; see the [parity spec](./2026-09-28-v2-parity.md).
6161

6262
## Config
6363

@@ -81,7 +81,7 @@ If favorites migration is ever needed, reopen it as a new spec against the curre
8181

8282
## Test coverage
8383

84-
Unit tests exercise the shipped contract (`tests/unit/plugin.test.ts`, `startup.test.ts`, `schemas.test.ts`, `catalog.test.ts`, `v2models.test.ts`, `auth.test.ts`):
84+
Unit tests exercise the baseline contract (`tests/unit/plugin.test.ts`, `startup.test.ts`, `schemas.test.ts`, `catalog.test.ts`, `v2models.test.ts`, `auth.test.ts`); V2 integration behavior is covered by `tests/unit/plugin-v2.test.ts`:
8585

8686
- bundled load, cache fallback, and dropped-entry degraded reasons
8787
- V1 map key vs wire id; V2 `id` vs `modelID`
@@ -92,3 +92,4 @@ Unit tests exercise the shipped contract (`tests/unit/plugin.test.ts`, `startup.
9292

9393
- [CI catalog automation](./2026-08-28-ci-catalog-automation.md)
9494
- [Catalog freshness](./2026-09-20-catalog-freshness.md)
95+
- [OpenCode V1 and V2 parity completion](./2026-09-28-v2-parity.md)

0 commit comments

Comments
 (0)