Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
a03423e
test: make the suite engine-independent and guard the client boundary
claude Oct 22, 2026
6ac740f
fix(lifecycle): contain the internal-route mount and follow reloads i…
claude Oct 22, 2026
aa3c01b
fix: price, list and key requests on the plane they belong to; isolat…
claude Oct 22, 2026
54a5b19
fix(store): mount the inspection remote, and read legacy keys per int…
claude Oct 22, 2026
8a5e95f
docs: describe the tree that exists, and the guarantees the suite now…
claude Oct 22, 2026
52f175f
docs: add the improvement plan from the architecture review
claude Oct 22, 2026
603d378
docs(plan): the approved refactor plan, with the host's own contracts
claude Oct 22, 2026
0a0dbb0
docs(plan): route every mode through the user's own provider
claude Oct 23, 2026
d4e3067
docs(plan): the plugin owns the plane routes, measured on pi 1.1.0
claude Oct 23, 2026
a38259c
docs(plan): fold the reviewed design and the build order into the plan
claude Oct 23, 2026
cd3107e
fix: repair what shipped broken in 1.4.1
claude Oct 23, 2026
73d60e4
docs(plan): P1a is in review
claude Oct 23, 2026
2bbb673
chore: remove dead code and tighten the repository's gates
claude Oct 23, 2026
e8944cc
feat: a smaller public API
claude Oct 23, 2026
f276902
build: pin the host the plugin runs on
claude Oct 23, 2026
813e6f3
chore: refresh the generated catalog and limits tables
claude Oct 24, 2026
a3d096b
Merge branch 'claude/vigilant-mendel-bctz9n' into p1a
claude Oct 24, 2026
2339c48
Merge branch 'claude/vigilant-mendel-bctz9n-p1a' into p1b
claude Oct 24, 2026
8b6cde6
Merge branch 'claude/vigilant-mendel-bctz9n-p1b' into p2
claude Oct 24, 2026
1a568f9
Merge branch 'claude/vigilant-mendel-bctz9n-p2' into p-t1a
claude Oct 24, 2026
2ebaffc
chore: refresh the generated catalog and limits tables
claude Oct 26, 2026
d51a3e6
test: skip free models the gateway has withdrawn in the live suite
claude Oct 26, 2026
0dd1b9c
Merge branch 'claude/vigilant-mendel-bctz9n' into p1a
claude Oct 26, 2026
1f5ed90
Merge branch 'claude/vigilant-mendel-bctz9n-p1a' into p1b
claude Oct 26, 2026
05faa93
Merge branch 'claude/vigilant-mendel-bctz9n-p1b' into p2
claude Oct 26, 2026
73db37b
Merge branch 'claude/vigilant-mendel-bctz9n-p2' into p-t1a
claude Oct 26, 2026
059236a
ci: gate freshness only on a pull request's own generator changes
claude Oct 27, 2026
d13cbe2
chore: refresh the generated catalog after Zen retired deepseek-v4-flash
claude Oct 27, 2026
32f2d71
Merge branch 'claude/vigilant-mendel-bctz9n' into claude/vigilant-men…
claude Oct 27, 2026
64c8acc
Merge branch 'claude/vigilant-mendel-bctz9n-p1a' into claude/vigilant…
claude Oct 27, 2026
6988e7a
Merge branch 'claude/vigilant-mendel-bctz9n-p1b' into claude/vigilant…
claude Oct 27, 2026
aba2c4a
Merge branch 'claude/vigilant-mendel-bctz9n-p2' into claude/vigilant-…
claude Oct 27, 2026
3d7cb38
test: give the freshness walk's dynamic import a script-local target
claude Oct 27, 2026
e4aa0c9
Merge branch 'claude/vigilant-mendel-bctz9n' into claude/vigilant-men…
claude Oct 27, 2026
94c3a22
Merge branch 'claude/vigilant-mendel-bctz9n-p1a' into claude/vigilant…
claude Oct 27, 2026
14c1544
Merge branch 'claude/vigilant-mendel-bctz9n-p1b' into claude/vigilant…
claude Oct 27, 2026
8ee4245
Merge branch 'claude/vigilant-mendel-bctz9n-p2' into claude/vigilant-…
claude Oct 27, 2026
d16a600
docs: date the refreshed model count and correct three stale figures
claude Oct 27, 2026
f8a4af3
Merge branch 'claude/vigilant-mendel-bctz9n' into claude/vigilant-men…
claude Oct 27, 2026
cb3cdda
Merge branch 'claude/vigilant-mendel-bctz9n-p1a' into claude/vigilant…
claude Oct 27, 2026
3d461c2
Merge branch 'claude/vigilant-mendel-bctz9n-p1b' into claude/vigilant…
claude Oct 27, 2026
659b0f3
Merge branch 'claude/vigilant-mendel-bctz9n-p2' into claude/vigilant-…
claude Oct 27, 2026
1c91312
Merge remote-tracking branch 'origin/main' into claude/vigilant-mende…
claude Nov 2, 2026
15e6236
test: install the plane-lookup world before each test
claude Nov 2, 2026
2454609
test(scripts): move the generated-table skip into the CLI-version rules
claude Nov 2, 2026
beae0ca
test(e2e): read an upstream's 400 "Model is unavailable" as a retirement
claude Nov 2, 2026
1d4aaab
docs(testing): describe the suite this branch keeps
claude Nov 2, 2026
4b0ebea
test(e2e): keep the free-tier case's narrowing in the subject picker
claude Nov 2, 2026
28fee8d
docs(plan): restack the plan onto 1.8.0 and the alpha host
claude Nov 2, 2026
8b9da84
build: raise the client bundle ceiling to 136 KB
claude Nov 2, 2026
281fc9f
docs: complete the restack of P0
claude Nov 2, 2026
6812723
docs: close the restack review of P0
claude Nov 2, 2026
7cd12d3
docs: hold the meter's allowance figures to the generated table
claude Nov 3, 2026
e3216d1
Merge remote-tracking branch 'origin/claude/vigilant-mendel-bctz9n' i…
claude Nov 3, 2026
73d5465
style: lint and format the merged purge row and token test
claude Nov 3, 2026
2ecf689
fix: complete the restack of P1a
claude Nov 3, 2026
252d0f8
docs: state the measured meter-scan reasons and P1a's refusal test cases
claude Nov 3, 2026
bf5717b
Merge remote-tracking branch 'origin/claude/vigilant-mendel-bctz9n-p1…
claude Nov 3, 2026
5e60c1b
test: one catalogue fixture, behind the one door
claude Nov 3, 2026
f3697a5
docs: describe debugFile by the gate main chose (D24)
claude Nov 3, 2026
074c834
style: satisfy main's promise lint in the retry test
claude Nov 3, 2026
19f05e6
docs(plan): record P1b's restack
claude Nov 3, 2026
8e9f36e
test: keep the fallback case's comment free of a model name
claude Nov 3, 2026
7136ca0
fix: complete the restack of P1b
claude Nov 3, 2026
786de90
docs: name the setup file in the door rule and count the fixture's en…
claude Nov 3, 2026
239d635
docs(plan): record the both-channel host question for P-T1a
claude Nov 3, 2026
bab4fc8
Merge remote-tracking branch 'origin/claude/vigilant-mendel-bctz9n-p1…
claude Nov 3, 2026
521b98f
test: route main's new tests through the one door
claude Nov 3, 2026
737a49d
docs(testing): drop the wrapper forwarder P2 retires again
claude Nov 3, 2026
c15aca0
docs: stop the count sweep record asserting a superseded catalog
claude Nov 3, 2026
798ba2e
chore: refresh the generated catalog for pulse-alpha-free
claude Nov 3, 2026
749669c
Merge branch 'claude/vigilant-mendel-bctz9n' into claude/vigilant-men…
claude Nov 3, 2026
835c793
Merge branch 'claude/vigilant-mendel-bctz9n-p1a' into claude/vigilant…
claude Nov 3, 2026
0563db6
chore: merge the parent's catalog refresh into P2
claude Nov 3, 2026
394e892
Merge remote-tracking branch 'origin/claude/vigilant-mendel-bctz9n-p2…
claude Nov 3, 2026
d65ed21
build: switch the dev install between the harness channels
claude Nov 3, 2026
f595d2d
test: fail if src reads what the desktop's patched pi changes
claude Nov 3, 2026
ea228a6
ci: run the unit and mock suites on both harness channels
claude Nov 3, 2026
90048d7
docs: record the both-channel, no-patch host policy
claude Nov 3, 2026
bcae61f
docs: keep the plan's D14'' row one table cell
claude Nov 3, 2026
44a6d5b
docs: complete the restack of P-T1a
claude Nov 3, 2026
bce1863
test: hold the relock's lockfile strip and name the channel switch's …
claude Nov 3, 2026
4679c1d
fix: report harness channel drift nightly and complete the restack of…
claude Nov 3, 2026
6310663
fix: keep Dependabot and the rc lockfile inside the channel-bump rule…
claude Nov 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .freebuff/project-id

This file was deleted.

29 changes: 19 additions & 10 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ updates:

# The plugin's own dependencies. Grouped so a routine bump does not arrive as
# a dozen PRs; the lockfile is the unit that is actually tested.
#
# Every pull request this opens also needs `channels/rc/pnpm-lock.yaml`
# relocked, which Dependabot cannot do: that lockfile is the root one with the
# host packages re-resolved, so it carries every specifier in `package.json`
# and CI's rc cell installs it frozen. Push one commit running
# `node --experimental-strip-types scripts/use-channel.ts rc --relock` onto
# the pull request; `test/harness-channel.test.ts` names that command when
# the two lockfiles disagree.
- package-ecosystem: npm
directory: /
schedule:
Expand Down Expand Up @@ -56,17 +64,18 @@ updates:
- vitest
- "@vitest/*"
ignore:
# pi-ai follows the line the main dsh installs, not the registry. The bridge
# loads pi from the wrapper's copy, so our pin has to satisfy the range the
# wrapper declares - now `^1.1.0`, where minor releases stay inside the range
# and a major does not. A second pi beside the wrapper's is the failure this
# prevents: two module instances drift apart the moment either side moves, and
# `test/google-bridge.test.ts` asserts the pin satisfies the range, so a major
# here would land red rather than break something quietly. Minors are
# welcome - they stay inside the wrapper's range - so only majors are ignored.
# The harness cluster and pi move only as a channel bump (D22''), never
# as a dependency update: a bump moves a channel record in
# `test/support/host-versions.ts`, that channel's lockfile and, for alpha,
# `package.json`'s exact `@deepseek-ai/*` pins together (CONTRIBUTING's
# release step 3), and pi is the bridge's, at the newest 1.x its range
# resolves, pinned per channel. A pull request from here would move one of
# them, land red on `test/harness-channel.test.ts`, and take its whole
# group with it. The nightly `host-drift` job reports when either channel
# moves, and P-T3 (a later phase) turns that report into the bump. Every
# update type is ignored; `test/harness-channel.test.ts` holds both rules.
- dependency-name: "@deepseek-ai/*"
- dependency-name: "@earendil-works/pi-ai"
update-types:
- version-update:semver-major
# React stays at 18: the bundles this plugin ships are host-bound, so a
# major here is a coordinated move with the host rather than a bump - and
# it is not hypothetical: under react-dom 19 the settings suite fails
Expand Down
265 changes: 220 additions & 45 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,15 @@ jobs:
export NODE_AUTH_TOKEN=${{ secrets.GITHUB_TOKEN }}
PUBLISH_REGISTRY=https://npm.pkg.github.com node --experimental-strip-types scripts/publish-scoped.ts
# The release claim, checked instead of assumed. Without this the job
# reported success while `@viztor/dsh-opencode*` sat four versions behind
# reported success while `@viztor/dsh-opencode-patch` sat four versions behind
# the canonical package, because a publisher that was never registered
# fails with the same E404 as a transient fault.
- name: verify every published target
run: |
VER=$(node -p "require('./package.json').version")
pending="dsh-opencode-patch@$VER @viztor/dsh-opencode-patch@$VER @viztor/dsh-opencode@$VER"
# One line: scripts/check.ts reads it to prove this list equals what
# publish-scoped.ts publishes (scripts/scoped-targets.ts).
pending="dsh-opencode-patch@$VER @viztor/dsh-opencode-patch@$VER"

# Polls, because a successful `npm publish` does not mean the version
# is readable yet: npm answers "Your package is being processed and
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ coverage/

# Agent workspace: session notes and scratch data, not source.
.workbuddy-ai/
.freebuff/
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
26
32 changes: 19 additions & 13 deletions AGENTS.md

Large diffs are not rendered by default.

45 changes: 38 additions & 7 deletions CONTRIBUTING.md

Large diffs are not rendered by default.

42 changes: 25 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ cd ~/.dsh/profiles/web
npm install dsh-opencode-patch
```

The package is `dsh-opencode-patch` under every name it is used by: the dependency you install, the bundle entry in your profile, and the row the host resolves.
The package is `dsh-opencode-patch` under every name it is used by: the dependency you install, the bundle entry in your profile, and the row the host resolves. It is also published as [`@viztor/dsh-opencode-patch`](https://www.npmjs.com/package/@viztor/dsh-opencode-patch); the retired name `@viztor/dsh-opencode` is no longer published, so an install still on it should move to either of those.

**2. Enable the bundle** — add the package to the profile's `dsh.profile.bundles` array:

```jsonc
// ~/.dsh/profiles/web/package.json
{
"dependencies": {
"dsh-opencode-patch": "^0.14.0",
"dsh-opencode-patch": "^1.8.0",
},
"dsh": {
"profile": {
Expand Down Expand Up @@ -161,13 +161,13 @@ Zen's provider-level SDK is `@ai-sdk/openai-compatible`. models.dev names a **di

| models.dev `provider.npm` | models | protocol | served from |
| :-- | --: | :-- | :-- |
| _(absent)_ | 23 | OpenAI Chat Completions | the route you configured |
| _(absent)_ | 24 | OpenAI Chat Completions | the route you configured |
| `@ai-sdk/openai` | 31 | OpenAI Responses | `opencode-responses` |
| `@ai-sdk/anthropic` | 17 | Anthropic Messages | `opencode-anthropic` |
| `@ai-sdk/google` | 8 | Google Generative AI | `opencode-google` |
| `@ai-sdk/mistral` | 1 | _(a dialect this plugin does not serve)_ | **not offered** |

Counts are the **80** active `opencode` models in `models.opencode.ai` as of 2026-10-23 — everything else the vendor lists is deprecated or retired, and the patch ships only what the gateway still serves. The same 80 are bundled offline in `src/catalog-data.ts`, so every one of them routes correctly before the first catalog refresh; regenerate that file with `pnpm run catalog:shim`.
Counts are the **81** active `opencode` models in `models.opencode.ai` as of 2026-11-03 — everything else the vendor lists is deprecated or retired, and the patch ships only what the gateway still serves. The same 81 are bundled offline in `src/catalog-data.ts`, so every one of them routes correctly before the first catalog refresh; regenerate that file with `pnpm run catalog:shim -- --write` (without `--write` it only checks).

The patch **keeps those two routes out of both the model picker and _Settings → Models_**. Three properties hold today, and one does not yet:

Expand Down Expand Up @@ -223,7 +223,7 @@ Merging is **additive**: adapter rows are kept, canonical rows are appended when

Two things follow that you will notice in use. **Cold start is never empty** — the bundled shim answers before the first refresh, so the picker has models immediately. And **a model the catalog has no row for still appears**, with `—` where its rate would be: never a guess, and never the previous model's name.

Both generated files are checked in CI (`catalog:shim`, `limits:shim`); they exit non-zero when the vendor's data moved, which is the only way to notice the vendor's registry changing under you.
Both generated files are checked in CI (`catalog:shim`, `limits:shim`); they exit non-zero when the vendor's data moved, which is the only way to notice the vendor's registry changing under you. A pull request fails on that only when it changes the code that generates the file; otherwise CI warns, and a nightly run proposes the refresh as a pull request of its own.

→ Protocol routing, the merge and the UI in technical detail: [`docs/protocol-routing-and-merge.md`](./docs/protocol-routing-and-merge.md) · [中文](./docs/protocol-routing-and-merge.md)

Expand Down Expand Up @@ -251,15 +251,15 @@ Nine controls in four sections — the decisions a user actually makes. Everythi
| **Models & Free Tier** | Enrich model metadata from the OpenCode catalog | `on` | Fills catalog context limits, modalities and thinking levels into model info, and lets the gateway `/models` listing carry the full catalog. Does **not** add models to the picker — the picker shows declared models only; use the card's **Check catalog** row to declare more |
| **Models & Free Tier** | Inject Core Tools | `on` | Adds the `read` + `bash` schemas free-tier `/responses` bodies require |
| **Quota Meter** | Enable Composer Usage Meter | `on` | Mounts the live meter in the composer dock — the Go quota ring, or the Zen session spend |
| **Account & Endpoint** | API Endpoint | `auto` | Which endpoint family requests go to: **Automatic** (follow the catalog) · **Legacy** (`zen/v1`) · **Modern** (`inference/…`) |
| **Account & Endpoint** | API Endpoint | `auto` | Which endpoint family requests go to: **Automatic** (same as Modern) · **Legacy** (`zen/v1`, `zen/go/v1`) · **Modern** (per-protocol `inference/{openai,anthropic,google}/…`, `inference/go/…` on Go) |
| **Account & Endpoint** | Credential Source | `auto` | Which key wins when several are known: **Automatic** · **Live request first** · **Declared key first** |
| **Account & Endpoint** | Read the OpenCode installation's key | `off` | Lets the plugin read the credential OpenCode itself stored, as the LAST resort — off by default |

### Choosing the endpoint and the credential

Two questions decide where a request goes, and the **Account & Endpoint** section is where you answer them.

**Which endpoint family.** The gateway answers on two families — the older `/zen/v1` and the newer `inference` family, whose base is per protocol (`…/inference/openai/v1`, `…/inference/anthropic/v1`, `…/inference/google/v1beta`) — and they are entitled **separately**: an account that works on one is not guaranteed to work on the other. `auto` follows the gateway's own catalog, which today names the newer family, so a catalog move carries you along; `legacy` and `modern` pin one, which is what you want when your account only works on the older one, or when you are checking that the newer one does.
**Which endpoint family.** The gateway answers on two families — the older `/zen/v1` (`/zen/go/v1` on Go), one base for every protocol, and the newer per-protocol `inference` family (`/inference/openai/v1`, `/inference/anthropic/v1`, `/inference/google/v1beta`, with `/inference/go/…` on Go) — and they are entitled **separately**: an account that works on one is not guaranteed to work on the other. `modern` sends each protocol to its own inference path; `auto`, the default, is an alias of `modern`; `legacy` pins the older family, which is what you want when your account only works there.

**Which credential wins.** `keySource` orders the sources the plugin can see — `auto` prefers a key you declared, then one captured from a live request, then the declared reference; `request` promotes a captured key first; `configured` does the reverse. Below it, `readOpencodeStore` lets the plugin read the credential OpenCode itself stored, as the **last** resort only. Both are `off`/`auto` by default, so nothing changes until you say so.

Expand All @@ -274,7 +274,7 @@ Go usage Healthy
● 5 hours 0% used · resets in 4h 58m 100% left
● Weekly 28% used · resets in 3d 2h 72% left
● Monthly 14% used · resets Nov 7, 8:55 AM 86% left
Monthly allowance mimo-v2.6-pro · Go $60 · Plus $120 ⓘ
Monthly allowance mimo-v2.6-flash · Go $60 · Plus $120 ⓘ
```

**This build carries the API key path only.** There is no sign-in row and no device flow: a credential comes from the deployment's configuration, from a key captured on a live request, or — when `readOpencodeStore` is on — from the OpenCode installation's own store. The Console login (device flow, session store, credential pool) lives on the `v2` line and is deliberately not in `main`, so nothing here depends on a service the host may not mount.
Expand Down Expand Up @@ -371,7 +371,7 @@ These exist in the schema but render no control — each is a literal, a marker,
| `userAgent` | empty (= canonical CLI UA) | Literal override; the default is what the gateway expects |
| `originClient` | `cli` | Literal `x-opencode-client` value |
| `sessionIdEnv` | `OPENCODE_SESSION_ID` | Names an env var only a caller-supplied session id uses |
| `freeModelMarker` | `free` | Model-id substring; `*` forces the fallback, `''` disables it |
| `freeModelMarker` | `free` | Model-id substring; `*` forces the fallback; a blank value inherits the default; catalog-free models always qualify |
| `usageBaseURL` | `https://opencode.ai/zen/go/v1` | Endpoint override; auto-discovered from the composition |
| `credentialOrg` | — | The org id an account credential belongs to, when the deployment supplies one out of band |
| `debug` / `debugFile` | `false` / — | Diagnostic JSONL logging, not a behaviour anyone tunes in the UI |
Expand All @@ -389,7 +389,6 @@ These exist in the schema but render no control — each is a literal, a marker,
- opencode-anthropic
gatewayUrls:
- opencode.ai/zen
hiddenModels: [] # ids withheld from every listing
sessionIdEnv: "OPENCODE_SESSION_ID"
freeModelMarker: "free"
usageBaseURL: "https://opencode.ai/zen/go/v1"
Expand Down Expand Up @@ -425,28 +424,37 @@ These exist in the schema but render no control — each is a literal, a marker,

---

## Changed in 1.x

- **A smaller public API.** The package's entry exports `apply`, `Config`, `inject` and `name` — the names the host reads — and the config types (`PluginConfig`, `ResolvedPluginConfig`, `ModelOverrideConfig`, `ModelRouteConfig`, `EndpointFamily`, `KeySourcePolicy`). Everything else it used to re-export is internal now.
- **`./src/*` is no longer an export.** `src/` was never in the published files, so the subpath resolved in the repository and nowhere else.
- **`internalRoutes` was removed.** The per-protocol routes always mount. A profile row that still carries `internalRoutes` validates and the key is ignored.
- **`@viztor/dsh-opencode` is no longer published.** Its last version's "Renamed to @viztor/dsh-opencode-patch" deprecation stands as the final message; install `dsh-opencode-patch` or `@viztor/dsh-opencode-patch` instead.

## 📜 Compatibility & Verification

**Verified on DeepSeek Harness `0.2.1` — both the `alpha` and `rc` channels (Node 24+)**
**Supported: the latest release of both DeepSeek Harness channels, `rc` and `alpha`, installed with npm or shipped in the desktop app (Node 24+)**

**Support policy.** The plugin supports the **latest** release of **both** channels, as each one actually ships: `rc` is `dsh@latest` (today `0.2.1-rc.5`, whose bridge declares pi `^1.1.0`) and `alpha` is `dsh@alpha` (today `0.2.2-alpha.2`, pi `^1.3.0`). An npm install runs pi **unpatched**, at the newest `1.x` the bridge's range admits; the desktop app ships the pi the harness's own workspace pins (`1.1.0` on `rc`, `1.3.0` on `alpha`). CI runs the unit and offline mock suites on both channels' npm installs on every pull request, and on each channel's floor pi nightly. The plugin neither patches nor re-implements what the host's pi does: where a host's pi lacks something the gateway needs, that is a host limitation, recorded and reported upstream rather than worked around here.

The harness publishes two **channels**, `alpha` and `rc`, and they are parallel tracks rather than a version ordering: semver sorts `0.2.1-rc.2` _above_ `0.2.1-alpha.5`. Both channels' latest is **`0.2.1`**, and that line is this plugin's target — it is tested against every published `0.2.1` build in both channels, and the `@deepseek-ai/dsh-typert-protocol` peer range says so exactly:
The harness publishes two **channels**, `alpha` and `rc`, and they are parallel tracks rather than a version ordering: semver sorts `0.2.1-rc.2` _above_ `0.2.1-alpha.5`. The plugin targets two lines on both channels: **`0.2.1`**, the `rc` channel's latest, and the **`0.2.2`** prereleases the `alpha` channel now carries (the test suite's default install runs `0.2.2-alpha.2`; its `rc` install runs `0.2.1-rc.5`). The `@deepseek-ai/dsh-typert-protocol` peer range says so exactly:

```
>=0.2.1-alpha.1 <0.2.2
>=0.2.1-alpha.1 <0.3.0 || >=0.2.2-0 <0.3.0
```

One range covers the whole line on both channels: its lower bound (`alpha.1`) sorts below every `rc` of the same line, so rc needs no separate entry. A plain `<0.2.2` would **not** work — semver excludes prereleases unless a comparator in the same `major.minor.patch` tuple carries one, which is how a range written against one channel silently excludes the other.
The first clause covers the `0.2.1` line on both channels: its lower bound (`alpha.1`) sorts below every `rc` of the same line, so rc needs no separate entry. The second is **not** redundant — semver excludes prereleases unless a comparator in the same `major.minor.patch` tuple carries one, so `<0.3.0` alone would silently drop every `0.2.2-*` build, alpha and rc alike. Both clauses stop at `0.3.0`, which leaves the next line a separate decision rather than a silent widening.

| Surface | Target |
| :-- | :-- |
| **Harness line** | **`0.2.1`**, `alpha` and `rc` channels alike (peer: `>=0.2.1-alpha.1 <0.2.2`) |
| **Harness line** | **`0.2.1`** and the **`0.2.2`** prereleases, `alpha` and `rc` channels alike (peer: the range above) |
| **Plugin package** | [`dsh-opencode-patch`](https://www.npmjs.com/package/dsh-opencode-patch) |
| **Host profile** | DSH Web profile (`patchReload: live`) |
| **Routes claimed** | `opencode`, `opencode-go`, `opencode-responses`, `opencode-anthropic` |
| **Gateways** | `opencode.ai/zen/v1` (`/responses`, `/chat/completions`, `/messages`, `:streamGenerateContent`), `zen/go/v1` (`/chat/completions`) |
| **Catalog** | Generated from `models.opencode.ai/api.json`; retirement follows the registry's own `status: "deprecated"` flag, with no hand-maintained id list |
| **Upstream pi** | **Not bundled.** The bridge loads whatever `@earendil-works/pi-ai` the harness installed, so a harness bump never skews a pinned copy |
| **Verification gate** | `vp check` clean, **961** deterministic tests green (unit + offline gateway mock), full schema validation, consumer install + load ([`scripts/check.ts`](./scripts/check.ts)) |
| **Upstream pi** | **Not bundled, not patched.** The bridge loads whatever `@earendil-works/pi-ai` the harness installed, so a harness bump never skews a pinned copy; the suite runs each channel's pi as npm resolves it, and its floor |
| **Verification gate** | `vp check` clean, the deterministic unit and offline gateway-mock suites green, full schema validation, consumer install + load ([`scripts/check.ts`](./scripts/check.ts)) |

---

Expand Down
Loading
Loading