Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
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 Down
4 changes: 2 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,15 +249,15 @@ opencode-responses:
| **模型与免费额度** | 用 OpenCode 官方目录补全模型信息(默认开启) | `on` | 把目录的上下文上限、模态与思考档位补进模型信息,并让网关 `/models` 列表带上完整目录。**不会**往选择器里添加模型——选择器只显示已声明的;要添加请用卡片的**检查目录** |
| **模型与免费额度** | 自动补全核心工具(默认开启) | `on` | 补上免费层 `/responses` 请求体所需的 `read` + `bash` schema |
| **配额计量** | 开启输入区用量计量(默认开启) | `on` | 在输入框停靠栏挂载实时计量表——Go 额度环,或 Zen 会话消耗 |
| **账户与端点** | API 端点 | `auto` | 请求发往哪个端点家族:**自动**(跟随目录) · **旧端点**(`zen/v1`) · **新端点**(`inference/…`) |
| **账户与端点** | API 端点 | `auto` | 请求发往哪个端点家族:**自动**(同新端点) · **旧端点**(`zen/v1`、`zen/go/v1`) · **新端点**(按协议的 `inference/{openai,anthropic,google}/…`,Go 为 `inference/go/…`) |
| **账户与端点** | 凭据来源 | `auto` | 多个密钥同时可用时以哪个为准:**自动** · **优先实时请求** · **优先已声明密钥** |
| **账户与端点** | 读取 OpenCode 安装版的密钥(默认关闭) | `off` | 允许本插件读取 OpenCode 自己保存的凭据,且仅作最后手段——默认关闭 |

### 选择端点与凭据

一个请求发往哪里由两个问题决定,答案就在**账户与端点**分区里。

**发往哪个端点家族。** 网关在两套端点上都应答——较早的 `/zen/v1` 与较新的 `inference` 家族(其 base 按协议区分:`…/inference/openai/v1`、`…/inference/anthropic/v1`、`…/inference/google/v1beta`)——而两者的授权是**彼此独立**的:在一边可用的账户不保证在另一边可用。`auto` 会跟随网关自己的目录,目前它指向较新的一套,因此目录迁移会带着你一起走;`legacy` 与 `modern` 则固定其一,适用于账户只在较早一套上可用,或你想确认较新的一套是否可用的情况。
**发往哪个端点家族。** 网关在两套端点上都应答——较早的 `/zen/v1`(Go 为 `/zen/go/v1`),所有协议共用一个基址;以及较新的按协议划分的 `inference` 家族(`/inference/openai/v1`、`/inference/anthropic/v1`、`/inference/google/v1beta`,Go 为 `/inference/go/…`)——而两者的授权是**彼此独立**的:在一边可用的账户不保证在另一边可用。`modern` 将每个协议发往各自的 inference 路径;`auto`(默认值)是 `modern` 的别名;`legacy` 固定使用较早的一套,适用于账户只在那里可用的情况。

**以哪个凭据为准。** `keySource` 决定插件可见的多个凭据源的优先级——`auto` 优先使用你声明过的密钥,其次是从实时请求中捕获的密钥,最后是声明的凭据引用;`request` 把捕获到的密钥提到最前;`configured` 则相反。其下的 `readOpencodeStore` 允许插件读取 OpenCode 自己保存的凭据,且**仅作最后手段**。两者默认分别为 `auto` 与关闭,因此在你说要改之前一切照旧。

Expand Down
24 changes: 11 additions & 13 deletions docs/improvement-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Phases land as **stacked pull requests**. Each one is based on the previous phas
| Phase | PR | Title | State | Restack onto 1.8.0 |
| :-- | :-- | :-- | :-- | :-- |
| P0 | #105 | suite hardening, 10 defect fixes, this plan | in review | **merged `main` 1.8.0**; catalog refreshes and the pricing fix dropped (on `main`) |
| P1a | #106 | `fix:` shipped breakage | in review | pending: merges P0; shrinks hard (title changes) |
| P1a | #106 | `fix:` the purge row's refusal and the endpoint copy | in review | **merged P0**; shrunk to the purge row's `RemoteError.message`, the `auto` = `modern` copy and the `opencode-google` regression tests; the warning colour drops its token fallback; the catalog's refusal tests gain three cases `main` lacks; the rest is on `main`, and the debug gate follows D24 |
| P1b | #107 | `chore:` hygiene | in review | pending: merges P1a; drops its tool-fallback variant (D25) |
| P2 | #108 | `feat:` a smaller public API | in review | pending: merges P1b; re-retires the wrapper (D9) |
| P-T1a | #109 | `build:` pin the host | in review | pending: merges P2; redone on the alpha cluster and pi 1.3.0 (D14', D22') |
Expand Down Expand Up @@ -112,11 +112,11 @@ Phases land as **stacked pull requests**. Each one is based on the previous phas
| Settings shows pi's snapshot, not the catalog | `models-discovery.ts:142-198,532-539` |
| Mode merging is done only for Go→Gemini. For the other modes the picker shows catalog levels while dispatch validates against a different profile: toggle models show Minimal…Max with no Off, and Go `minimax-m3` + Xhigh/Max is refused on the internal Anthropic route. | `models-discovery.ts:607-616,735-760,1063-1203`; `pi-provider.ts:351-386`; `responses-provider.ts:111-150` |
| ~~`isZenProvider("opencode-google")` is false~~ Fixed on `main`: only `opencode-go` and `opencode-go-*` are excluded. P1a adds the regression test. | `utils/request-shape.ts:273-282` |
| ~~`enrichModelsResponse` turns a 401/403/5xx into a 200 catalog. It must still fill the Go plane's 404.~~ Fixed on `main` (#133): a refusal other than 404 passes through. | `models-catalog.ts:731` |
| ~~`enrichModelsResponse` turns a 401/403/5xx into a 200 catalog. It must still fill the Go plane's 404.~~ Fixed on `main` (#133): a refusal other than 404 passes through. P1a adds the Zen-plane 404 fill, the non-JSON 200 answer and the retry-after passthrough cases. | `models-catalog.ts:731` |
| ~~The warning token doesn't exist, so warnings render as caption text.~~ Fixed on `main` (#133): both use `--dsw-alias-state-warn-label`. | `settings-card.tsx:159`, `settings-store-inspect.tsx:250` |
| ~~The Settings spinner animates only if the meter is mounted~~ Fixed on `main` (#133): the card carries its own keyframes. | `settings-usage.tsx:45` |
| ~~The stream hook writes `debugFile` with `debug` off~~ Not a defect under D24: naming `debugFile` writes both halves. | `stream-hook.ts:327-340` vs `lifecycle.ts:361` |
| `settings-purge`: the `{ ok: false }` branch and the parser are on `main` (#133). Still open: a refused purge whose `error` is the host's `RemoteError` object, not a string, shows the generic message; P1a reads its `message`. | `settings-purge.ts:56-69` |
| `settings-purge`: the `{ ok: false }` branch and the parser are on `main` (#133). ~~Still open: a refused purge whose `error` is the host's `RemoteError` object, not a string, shows the generic message.~~ Fixed by P1a: `settings-purge.ts` reads the `RemoteError`'s `message`. | `settings-purge.ts:56-69` |
| The stream hook's `prepared` handling is dead. The host never reads `options.prepared`. | `stream-hook.ts:44,200-203,258` |
| The partition test misses `debugFile` | `test/settings-page.test.tsx:1223` |
| `cordis.patch.yml`: phantom `showUsagePrice`, wrong `freeModelMarker: ''` text, incomplete field lists | `cordis.patch.yml:23,36,61` |
Expand Down Expand Up @@ -337,15 +337,13 @@ What stays private is pinned by contract tests against the real host packages:
- completions models move from `/zen(/go)/v1` to `/inference(/go)/openai/v1`;
- `decoratePlaneAdapters`' baseUrl repair never reaches the wire.

1. **P1a `fix:` shipped breakage.**
- The wrapper's broken `export { default }`.
- `isZenProvider("opencode-google")` answers false.
- `enrichModelsResponse` passes a 401/403/5xx through, and still fills the Go plane's 404.
- The warning colour uses `--dsw-alias-state-warn-label`.
- The spinner keyframes are in the settings styles.
- The debug gate follows D24.
- `settings-purge` uses `parsePurgeResult`.
- The endpoint copy (`config.ts`, `config-values.ts`, README) names the per-protocol inference family for `modern`, with `auto` as its alias.
1. **P1a `fix:` the purge row's refusal and the endpoint copy.** The rest of what shipped broken in 1.4.1 was fixed on `main` (#133, #135) before this phase landed.
- `settings-purge` shows the `message` of a refusal whose `error` is the host's `RemoteError` object.
- The endpoint copy (`config.ts`, `config-values.ts`, the card, both READMEs) names the per-protocol inference family for `modern`, with `auto` as its alias (D19), pinned by `endpoint-copy.test.ts`.
- Regression tests for `isZenProvider("opencode-google")`: the predicate, its agreement with `catalogPlaneForRoute`, and the usage service's Zen fallback on that route.
- The warning colour is the bare `--dsw-alias-state-warn-label` (no token fallback), and every token the card names is checked against the host's vocabulary.
- The stream hook's debug test asserts D24 rather than the gate this phase first proposed.
- The catalog's refusal tests gain three cases `main` lacks: the Zen-plane 404 fill, the non-JSON 200 answer, and a `retry-after` header that survives the passthrough. The passthrough itself is on `main` (#133).
2. **P1b `chore:` hygiene.**
- Delete `adapter-replacement.ts` and its test, with a note in the engineering notes on why the approach is unsupported.
- Delete the dead `prepared` handling and `.freebuff/`.
Expand Down Expand Up @@ -452,7 +450,7 @@ How each phase changes now that it sits on `main` 1.8.0 and the alpha host. Each
| Phase | Adjustment |
| :-- | :-- |
| P0 | Shrinks: the three catalog refreshes and the per-plane pricing fix are on `main`. Keeps the suite hardening, the lifecycle, mount and credential fixes, D23's scoping with `--copies`, and this plan. |
| P1a | Shrinks hard: the purge row shows the host's `RemoteError.message`, the endpoint copy says `auto` is `modern`, and `isZenProvider("opencode-google")` gains a regression test. The wrapper fix, refusal passthrough, warning token, spinner and `parsePurgeResult` are on `main` or superseded; the debug gate follows D24. |
| P1a | Shrinks hard: the purge row shows the host's `RemoteError.message`, the endpoint copy says `auto` is `modern`, and `isZenProvider("opencode-google")` gains a regression test. The wrapper fix, refusal passthrough, warning token, spinner and `parsePurgeResult` are on `main` or superseded; P1a only drops the warning token's fallback, checks every token the card names against the host's vocabulary, and adds three refusal-passthrough test cases `main` lacks (the Zen-plane 404 fill, the non-JSON 200 answer, the `retry-after` passthrough). The debug gate follows D24. |
| P1b | Takes `main`'s `coreToolDecision` (D25) and ports only the dead Anthropic branch's deletion; its catalogue fixture merges with `main`'s `test/fixtures/catalogs.ts` behind the one door. |
| P2 | Re-retires the wrapper `main` revived (D9); the door list grows by every `src` name `main`'s tests import. |
| P-T1a | Redone on D14' and D22': `main`'s pins, then the pi 1.3.0 override and patch; the 1.1.0 patch is deleted. |
Expand Down
2 changes: 1 addition & 1 deletion docs/redirect-planes.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ A **plane** is one gateway base URL with its own catalog and its own credential.

Counts as of 2026-11-03 (the shipped shim, regenerated in #132 and refreshed for `pulse-alpha-free`); the document's 2026-10-09 stamp is for its measurements.

**There is no longer one base URL per plane to quote.** The gateway answers on two endpoint families and the `endpoint` setting picks one: `auto` follows the catalog, which today names `…/inference/openai/v1` and `…/inference/go/openai/v1`, and `legacy` pins the older `…/zen/v1` and `…/zen/go/v1`. Both are still served and they are entitled separately, which is why the choice is the user's rather than a migration. The dated measurement below was taken on the legacy family.
**There is no longer one base URL per plane to quote.** The gateway answers on two endpoint families and the `endpoint` setting picks one: `auto`, the default, is an alias of `modern`, the per-protocol inference family (`…/inference/openai/v1` and `…/inference/go/openai/v1` for completions), and `legacy` pins the older `…/zen/v1` and `…/zen/go/v1`. Both are still served and they are entitled separately, which is why the choice is the user's rather than a migration. The dated measurement below was taken on the legacy family.

A plane is a property of the **account and the base URL**, never of a model. The same model id can exist on both planes and be served on a different shape in each.

Expand Down
12 changes: 4 additions & 8 deletions src/config-values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,10 @@ export const isKeySourcePolicy = (value: unknown): value is KeySourcePolicy =>
* The gateway answers on two families and the user, not the catalog, is the one
* who knows which their account is entitled to:
*
* - `auto` follows the catalog — the `api` the vendor's own entry names, which
* today is the `inference` family.
* - `legacy` forces the older pair, which really is one base per plane
* (`…/zen/v1`, `…/zen/go/v1`).
* - `modern` forces the newer `inference` family, whose base is PER PROTOCOL
* (`…/inference/openai/v1`, `…/inference/anthropic/v1`,
* `…/inference/google/v1beta`, and the `go/` variants) — so "inference/v1" is
* not an address the plugin ever sends, and the copy says `inference/…`.
* - `auto` — an alias of `modern` (the default).
* - `legacy` — the older single base per plane, `/zen/v1` and `/zen/go/v1`.
* - `modern` — the per-protocol `inference` family,
* `/inference/{openai,anthropic,google}/…` and `/inference/go/…` on Go.
*
* Declared here rather than in `config.ts` so the client card can read the same
* list without importing schemastery.
Expand Down
19 changes: 11 additions & 8 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ export interface ResolvedPluginConfig {
* Which endpoint family the gateway's requests go to.
*
* The two families are independently entitled and both are still served, so
* the choice is the user's: `auto` follows the catalog, and `legacy` /
* `modern` pin one. See {@link ENDPOINT_FAMILIES}.
* the choice is the user's: `auto` is an alias of `modern` (the default), and
* `legacy` pins the older single base per plane. See {@link ENDPOINT_FAMILIES}.
*/
endpoint: EndpointFamily;
/** `*` when the core-tool fallback should run for every model. */
Expand Down Expand Up @@ -276,8 +276,8 @@ const resolvedKeySource = (raw: unknown): KeySourcePolicy => {
* A row's endpoint family, or the default when it names something else.
*
* Lenient for the same reason {@link resolvedKeySource} is: a typo in a
* hand-edited row degrades to `auto`, which follows the catalog and is the
* answer that cannot be wrong about which family is current.
* hand-edited row degrades to `auto`, the default, so a typo cannot select a
* family the user never named.
*/
const resolvedEndpoint = (raw: unknown): EndpointFamily => {
const value = readString(raw);
Expand Down Expand Up @@ -563,10 +563,13 @@ export const Config = z.object({
.string()
.default(CONFIG_DEFAULTS.endpoint)
.description(
"Which endpoint family the gateway's requests go to. auto: follow the " +
"catalog's own address (today the inference family). legacy: force " +
"https://opencode.ai/zen/v1. modern: force " +
"https://opencode.ai/inference/<protocol>. The two families are separately " +
"Which endpoint family the gateway's requests go to. modern: the " +
"per-protocol inference family, " +
"https://opencode.ai/inference/{openai,anthropic,google}/... on Zen " +
"and https://opencode.ai/inference/go/{openai,anthropic,google}/... " +
"on Go. auto: an alias of modern, and the default. legacy: the older " +
"single base per plane, https://opencode.ai/zen/v1 and " +
"https://opencode.ai/zen/go/v1. The two families are separately " +
"entitled and both still answer, so this is a choice rather than a " +
"migration; an unrecognised value falls back to auto."
)
Expand Down
Loading
Loading