Skip to content

fix(discovery): read the plane from the path, in both endpoint families - #100

Merged
viztor merged 2 commits into
mainfrom
fix/plane-from-the-path
Oct 22, 2026
Merged

viztor merged 2 commits into
mainfrom
fix/plane-from-the-path

Conversation

@viztor

@viztor viztor commented Oct 22, 2026

Copy link
Copy Markdown
Owner

The Go plane's model list was being answered with the Zen catalog. Three places ask which plane a URL belongs to, and they had three answers — because the newer inference endpoint family was added to each of them separately, and each addition was partial.

path the old substring checks said is
/inference/go/openai/v1 zen (two of the three knew only /zen/go) go
/inference/google/v1beta go (/inference/go is a prefix of it) zen
/inference/openai/v1 unknown (/inference/v1 is not inside it) zen

The first row is the reported symptom

The Go plane's base is the inference family now, and the adapter lists its models at ${base}/models — a path the gateway does not serve, so the answer comes from this plugin's enrichment. That enrichment asks which plane the listing is on, read by substring, and it read the modern Go path as the Zen plane.

So the Go plane was answered with the Zen catalog: every Zen model, and none of the Go plane's own — the Gemini contributor and the Muse contributor, both of which the deployment declares.

Measured against the real gateway, through the patched fetch:

listing before after
…/zen/go/v1/models 36 Go models 36 Go models
…/inference/go/openai/v1/models 82 Zen models 28 Go models

The other two rows are credentials filed under the wrong account

  • The Zen google family's captured key was filed as a Go key, and used as one. A key filed under the wrong account is worse than one filed under none: it is used, and it is the wrong one.
  • The Zen openai family's captured key was filed under no account at all, which is the "my key is invalid" report on a key that was fine — the same report this plugin has already been asked about once.

The fix

One predicate answers it now, in src/utils/request-shape.ts, and the three call sites use it: isGoModelsListingUrl, resolveDiscoveryProvider and tierForRequest.

The segment boundary is load-bearing — /inference/go must end there, or the Zen google family is read as the Go plane. A relative path answers too (/zen/go/v1/models), because a caller with no origin passes a path and the plane is a property of the path; a string that is neither a URL nor a path is still not guessed at.

The Go plane's other traffic is unaffected — and measured

endpoint answer
POST …/inference/go/openai/v1/chat/completions 200, a real completion
POST …/inference/go/google/v1beta/…:generateContent 200, a real completion
GET …/zen/go/v1/usage 200, the quota panel's endpoint

The usage endpoint stays on the older family on purpose: the vendor's inference proxy has no /inference/…/usage counterpart, which is why toGoBaseURL rewrites an inference-family base to https://opencode.ai/zen/go/v1.

Verification

gate result
vp check 0 errors, 23 pre-existing warnings
suite 1155 passed / 73 files (+5 tests)
coverage (95.9 / 91.3 / 95.9 / 95.9) 96.32 / 91.92 / 96.33 / 96.31
scripts/check.ts passes

Each of the three old behaviours was planted back and watched fail the new tests — the listing predicate (expected false to be true: the Go-only Gemini model missing), the tier classifier, and the path predicate.

Also in this branch

AGENTS.md records the merge policy: squash, so one pull request is one commit on main, one line in the changelog, and the pull request's title is the release signal.

release-please resolves a MERGE COMMIT to the pull request's title, so under a
merge-commit history every pull request appears in `CHANGELOG.md` once for its
title and once for each commit inside it. The 1.4.0 notes listed nine entries
for four changes, and a reader cannot tell a real second change from a repeated
one.

A squashed commit carries the pull request's title and nothing else, so each
change is exactly one line — and the title is then the release signal, which is
why it must be a Conventional Commit.
**The Go plane's model list was answered with the Zen catalog.** Three places
ask which plane a URL belongs to, and they had three answers, because the newer
`inference` family was added to each of them separately and each addition was
partial:

| path | the old substring checks said | is |
| :-- | :-- | :-- |
| `/inference/go/openai/v1` | zen (two of the three knew only `/zen/go`) | go |
| `/inference/google/v1beta` | **go** (`/inference/go` is a PREFIX of it) | **zen** |
| `/inference/openai/v1` | unknown (`/inference/v1` is not inside it) | zen |

The first row is the reported symptom. The Go plane's base is the `inference`
family now, and the adapter lists its models at `${base}/models` — a path the
gateway does not serve, so the answer comes from this plugin's enrichment. That
enrichment asks which plane the listing is on, read by substring, and it read
the modern Go path as the Zen plane. The Go plane was therefore answered with
the Zen catalog: every Zen model, and none of the Go plane's own — the Gemini
contributor and the Muse contributor, both of which the deployment declares.

Measured against the real gateway, through the patched fetch:

| listing | before | after |
| :-- | :-- | :-- |
| `…/zen/go/v1/models` | 36 Go models | 36 Go models |
| `…/inference/go/openai/v1/models` | **82 Zen models** | **28 Go models** |

The second row is a credential filed under the wrong account: the Zen google
family's captured key was filed as a Go key, and used as one. The third is a
credential filed under no account at all, which is the "my key is invalid"
report on a key that was fine — the same report this plugin has already been
asked about once.

**One predicate answers it now**, in `src/utils/request-shape.ts`, and the
three call sites use it: `isGoModelsListingUrl`, `resolveDiscoveryProvider`
and `tierForRequest`. The segment boundary is load-bearing — `/inference/go`
must END there, or the Zen google family is read as the Go plane.

A relative path answers too (`/zen/go/v1/models`), because a caller with no
origin passes a path and the plane is a property of the path; a string that is
neither a URL nor a path is still not guessed at.

**The Go plane's other traffic is unaffected**, and measured: the gateway
serves `/inference/go/openai/v1/chat/completions` (200, a real completion),
`/inference/go/google/v1beta/…:generateContent` (200, a real completion) and
`/zen/go/v1/usage` (200, the quota panel's endpoint, which stays on the older
family because there is no `/inference/…/usage` counterpart). The three
planted violations of the old behaviour each fail the new tests.
@viztor
viztor merged commit 36a6489 into main Oct 22, 2026
3 checks passed
@viztor
viztor deleted the fix/plane-from-the-path branch October 22, 2026 17:57
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.

1 participant