Skip to content

feat: the plugin owns the opencode and opencode-go routes - #113

Open
viztor wants to merge 26 commits into
claude/vigilant-mendel-bctz9n-p-hfrom
claude/vigilant-mendel-bctz9n-p-r1
Open

viztor wants to merge 26 commits into
claude/vigilant-mendel-bctz9n-p-hfrom
claude/vigilant-mendel-bctz9n-p-r1

Conversation

@viztor

@viztor viztor commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Phase P-R1 of docs/improvement-plan.md, stacked on #112.

The plugin now serves the opencode and opencode-go routes itself (the D13 design), using only supported host paths. The hidden-route workaround is gone.

Restacked onto main 1.8.0

This branch now carries main 1.8.0 through its parent (P-H) and the two-channel host: rc 0.2.1-rc.5 and alpha 0.2.2-alpha.2, with pi unpatched. Some sections below describe the branch before the restack; where they disagree, this section wins.

Scope now. The plugin owns the opencode and opencode-go routes: the route claim, PlaneAdapter, the describe graft and the host modules. One characterization table serves every host cell (S0_TODAY_BY_CELL, built per channel by s0PluginFor). Rows differ only where the host decides.

Dropped because main or the host now does it (D26).

  • The Gemini level wrapper (src/host/google-levels.ts) is deleted, with its wiring in plane-provider, plane-profile, host-modules and cordis-context, its tests, its 100% coverage floor and its doc lines. The thinking control is pi's own. The catalog level rule now lives only in test/support/thinking-levels.ts, where it picks test subjects. The "Behaviour changes" bullet about thinkingLevel above no longer holds.
  • pi's catalog entry is no longer a protocol or base source in src/utils/plane-model.ts, including the legacy branch that took pi's own /zen base (D16). The protocol order is catalog SDK, then the route's api, then completions. Bases come only from the endpoint table.
  • plane-profile no longer restates pi-ai's default window and output tokens (D17).
  • main fix: send Gemini 4 thinking levels on older host runtimes #130's test/google-thinking.test.ts imported the deleted bridge. Its intents moved to the mock suite and to the HOST_LIMITATION rows.
  • main feat(test): gate the language pairs, and lift the live-run rules out of the e2e #140's decoratePlaneAdapters case and its stream-hook "cannot be served" case are both obsolete after this PR.
  • P-T1b's per-cell machinery from before this PR is gone: the per-channel floor tables, withMoved, the keyless/keyed listings, saveRejectedUnknown, SAVE_BLOCKED_BY_ANY_UNDESCRIBED, the pi-ai-first override and NEXT_DISPATCH. Every host cell now reaches TODAY_DISPATCH.
  • The patched "pinned" binary cell was not built, because no pi patch remains. The npm and floor cells of both channels ran instead.

Decisions applied.

  • D14': the route claim and the plane adapter are built from the host's own pi on each channel, and nothing is bundled or patched.

  • D22': a channel bump re-measures F8/F12/F14/F20 per channel against that channel's own expectation, and the route claim adopts a moved internal/config contract by detecting the shape the host dispatches, never its version.

  • D24: debug / debugFile follow main's wording in cordis.patch.yml, and the lifecycle test uses apply(ctx, { debugFile }).

  • D25: host limitations are recorded and not fixed:

    • the rc desktop app's pi 1.1.0 sends the level-only Gemini 4 model a thinking budget;
    • alpha 0.2.2-alpha.2 installed by npm cannot save.

    Both are detected from the host's own answers, and both READMEs list them.

Known defect. On cordis 4.0.6-alpha.2 (alpha's npm cell) the route claim does not engage, so pi-ai serves the planes. This is a [KNOWN_DEFECT] row, and the claim-dependent and save rows skip on that cell. The current dsh@alpha (0.2.2-alpha.3) requires that cordis, so its bump must adopt the new contract first.

Live re-measures CI must confirm.

  • protocol-routing.e2e: the Google level case on Go with a key, on the newest pi. It must skip as HOST_LIMITATION on the rc floor.
  • That the gateway still refuses a thinkingBudget for the Go level-only model. The HOST_LIMITATION rows rest on this, and Zen is expected to refuse too but is not yet measured.
  • keyless-matrix.e2e: TODAY_DISPATCH, all four protocols on the modern family on both planes.
  • protocol-routing.e2e: the keyed adapter walks through PlaneAdapter on tier A for both planes.
  • e2e-host for both channels' npm and floor cells. It records configHook per cell and confirms the alpha npm [KNOWN_DEFECT] row and the rc floor HOST_LIMITATION rows.
  • Inherited from P-R2: the x-opencode-request, x-opencode-project and Retry-After rows, and the combined Google User-Agent.

What changed

Item Change
Root plugin Declares inject: [].
src/host/route-claim.ts (new) A prepended internal/config listener gives pi-ai's fiber a fresh copy of its config with providers.opencode and providers["opencode-go"] removed. It runs the nested waterfall on that copy behind a re-entrancy guard. On a commit it schedules reconcile. On a Settings candidate it strictly validates only the plane entries that changed. The served pi-ai row is sticky: it stays the same while it is live and still declares a plane.
src/host/plane-provider.ts (new) reconcile builds one PlaneAdapter, a subclass of the host's PiAiAdapter, and registers only the declared planes through registerAdapter / handle.replace. pi-ai keeps every other route.
src/utils/plane-profile.ts, plane-model.ts, model-metadata.ts, protocols.ts (new) Pure profiles the adapter is built from. The utils/ purity gate covers them.
src/host/describe-graft.ts (new) A read-only settings.describe wrapper, built on patchMethod, that grafts the committed plane entries back (D18).
src/host/host-modules.ts (new) The host-module loaders, moved here. The seam stays in cordis-context.ts. pi-ai's module comes from pi-ai's own loader entry during its commit run, so nothing waits on a timer.
Deleted responses-provider.ts, responses-routes.ts, google-bridge.ts, pi-provider.ts, utils/google-replay.ts (removed after a mock-gateway replay test showed a single provider label); from models-discovery.ts: the listing hide, decoratePlaneAdapters and decorateModelInfo; the stream redirect; the D19 settings; the hidden ids in DEFAULT_PROVIDERS and cordis.patch.yml.
Tests New unit, real-host (host-planes), contract (describe-graft, catalog-change) and mock (plane-dispatch) suites. Characterization rows flipped to new [BASELINE] values: 14 S0_TODAY facets, 14 named host-characterization cases and 5 binary-e2e rows, each named under "Characterization rows this PR flips" below. Every other row stays green unchanged.
Docs README (both languages), AGENTS.md, CONTRIBUTING, credentials, deep-dive, protocol routing and redirect planes now describe the new wiring. In the plan, P-R1 is set to in review.

Some details refine the plan text on purpose, and the plan's P-R1 entry records each one:

  • D20 follows pi-ai's exact rule: it uses the credentials service when one is mounted, and the launch environment only when none is.
  • legacy uses pi's own base only when pi files the model under the same protocol and that base is a /zen root.
  • The fixture-sanity case derives info the way D17 does, so the INFO_NOT_FROM_CATALOG BASELINE guard stays intact.

Behaviour changes

After this phase:

  • Any saved id is accepted and served (D15). An id that no catalog knows dispatches as completions.
  • The protocol comes from the catalog (D16). Model info follows the user's own row first (D17).
  • A model whose reasoning can only be switched on or off offers Off / On (D1). Off is offered only where the protocol can disable reasoning.
  • The default endpoint is modern: the per-protocol inference family. Go Gemini dispatches on /inference/go/google/v1beta.
  • Keys resolve by pi-ai's own rule (D20).
  • A Go entry without apiKeyEnv now sends the Zen key. pi's own lookup reads OPENCODE_API_KEY on both planes, so such an entry authenticates every Go protocol with it. Before, Go's Responses, Messages and Google turns defaulted to OPENCODE_GO_API_KEY (completions already took pi's rule). The meter follows: it prefers the key a Go turn carried, so after the first such turn it sends the Zen key to the Go usage endpoint, is refused, and falls back to the Zen pill or "subscription required". The fix for a user is apiKeyEnv: OPENCODE_GO_API_KEY on the Go entry; README step 3 and "Changed in 1.x" (both languages) say so, and test/host-planes.test.ts pins both cases.
  • A Gemini model the catalog describes by level is sent Google's thinkingLevel, never a thinkingBudget (src/host/google-levels.ts); Go gemini-4-argon refused the budget live.
  • There is no keyless listing filter (D21). A plane lists its declared ids even when no key resolves.

Two changes from the last review round:

  • When several pi-ai rows declare OpenCode planes, the row whose planes the plugin serves stays the same while it is live and still declares a plane. If another row adds a plane later, the plugin does not take it over: that row keeps and serves its own plane, and the plugin reports this once. Before, the plugin could switch rows and leave the old row's planes served by nobody, without a warning.
  • If the host's pi-ai module cannot be read, the plugin now steps aside even when no OpenCode plane is declared yet. A plane added later in Settings then goes to pi-ai's own save check. Before, it was refused for good with "try the save again".

Characterization rows this PR flips

Every row below was [KNOWN_DEFECT] or [INTENDED_CHANGE] on the base and is [BASELINE] here. No [BASELINE] row on the base moved.

S0_TODAY facets (test/support/host.ts; facet order info / callConfig / wire), 14:

Row Facet(s) Base Now
opencode/deepseek-v4-pro wire [INTENDED_CHANGE] /zen/v1/chat/completions /inference/openai/v1/chat/completions
opencode/gpt-5.3-codex-spark info, callConfig [KNOWN_DEFECT] user row ignored (128000), INVALID_CONFIG the user row's 400000 (D17), ok
opencode/not-in-any-catalog info, callConfig, wire [KNOWN_DEFECT] INVALID_CONFIG, nothing sent 262144 / no efforts, ok, sent as completions on /inference/openai/v1 (D15, D16)
opencode-go/deepseek-v4-pro wire [INTENDED_CHANGE] /zen/go/v1/chat/completions /inference/go/openai/v1/chat/completions
opencode-go/minimax-m3 info [KNOWN_DEFECT] toggle-only model offered Minimal…Max Off / On (D1)
opencode-go/gemini-4-argon callConfig [KNOWN_DEFECT] INVALID_CONFIG ok
opencode-go/minimax-m3.1-flash callConfig, wire [KNOWN_DEFECT] INVALID_CONFIG, nothing sent ok, sent on /inference/go/openai/v1
opencode-go/not-in-any-catalog info, callConfig, wire [KNOWN_DEFECT] INVALID_CONFIG, nothing sent 262144 / no efforts, ok, sent on /inference/go/openai/v1

The listing rows keylessListing / keyedListing are replaced by listingBeforeTurn / listingAfterTurn (every declared id, D21), and saveRejectedUnknown is gone (D15).

Named host-characterization cases, 14 (base title → head title):

  1. [INTENDED_CHANGE] registry: both planes are pi-ai's, six hidden routes → [BASELINE] registry: both planes are the plugin's adapter, no hidden route (D13)
  2. [INTENDED_CHANGE] keyless listing hides every paid model → [BASELINE] listing before any turn: every declared id (D21)
  3. [KNOWN_DEFECT] keyed listing: only pi-resolved ids → [BASELINE] listing after a turn: gap ids and the unknown id included (G1)
  4. [KNOWN_DEFECT] toggle-only model offered Minimal/Xhigh/Max → [BASELINE] offered Off / On, an untaken level refused (D1)
  5. [INTENDED_CHANGE] completions dispatch to pi 1.1.0's legacy bases → [BASELINE] completions dispatch to the modern family on both planes
  6. [KNOWN_DEFECT] the adapter decoration never runs in this order → [BASELINE] the plugin patches nothing on its own PlaneAdapter
  7. [KNOWN_DEFECT] Zen gpt-5.3-codex-spark: user row ignored, resolveCallConfig refuses → [BASELINE] user row reaches model info, sent on Responses (D17)
  8. [KNOWN_DEFECT] Go minimax-m3.1-flash INVALID_CONFIG → [BASELINE] validates, sent once as completions on Go's modern base (G1)
  9. [KNOWN_DEFECT] Go gemini-4-argon refused while served through the bridge → [BASELINE] validates, sent on Go's Google base with the Go key (F10)
  10. [KNOWN_DEFECT] a stored gap id blocks every save of its plane → [BASELINE] a stored gap id blocks no save; the unknown id is listed, validates and is sent (D15)
  11. [KNOWN_DEFECT] the probe's save sequence needs the gap ids dropped first → [BASELINE] the first append of the unknown id is accepted
  12. [INTENDED_CHANGE] the adapter decoration runs in pi-ai-first order → [BASELINE] pi-ai held the planes first, so the claim restarted it exactly once
  13. [KNOWN_DEFECT] the apply order decides one facet (Go gemini-4-argon) → [BASELINE] no facet depends on the apply order (todayRowsFor's override dropped)
  14. [KNOWN_DEFECT] the unknown id is what the save check names → [BASELINE] the save check accepts it and the file holds it (D15)

Binary-e2e rows (test/e2e/host-binary.e2e.ts), 5:

  1. [INTENDED_CHANGE] registry: the planes are pi-ai's, hidden routes registered → [BASELINE] both planes are the plugin's PlaneAdapter, no hidden route (D13)
  2. [INTENDED_CHANGE] keyless listing hides every paid model → [BASELINE] listing before any turn: every declared id (D21)
  3. [KNOWN_DEFECT] keyed listing: the gap ids are missing → [BASELINE] listing after a turn on each plane: every declared id (G1)
  4. [KNOWN_DEFECT] a stored gap id blocks saving any new id → [BASELINE] a stored gap id blocks no save (D15)
  5. [KNOWN_DEFECT] a catalog-unknown id cannot be saved → [BASELINE] it saves, is listed, validates and is sent as completions on the modern base (D15, D16)

The binary e2e also gained rows with no base counterpart: the plane provider is built from the host's own pi, the Go Gemini level model is sent a thinking level and no thinkingBudget, and the Settings graft (below).

Records the plan asks for

docs/improvement-plan.md (Test plan, P-R1) asks this PR body to record four things.

  • The measured endpoints. Default (auto = modern) dispatch: Zen https://opencode.ai/inference/{openai/v1, anthropic/v1, google/v1beta}, Go https://opencode.ai/inference/go/{openai/v1, anthropic/v1} and https://opencode.ai/inference/go/google/v1beta (the catalog's family map, else MODERN_FALLBACK in src/utils/protocols.ts). legacy: /zen/v1 and /zen/go/v1 (/zen, /zen/go for Messages), with Go's Google staying on /inference/go/google/v1beta under every family. The keyless matrix (every PR in CI) asserts that no cell the default endpoint dispatches to answers 404; the binary e2e records the URL each characterized model was sent to through the shipped bundle (the wire facets above), including Go gemini-4-argon on /inference/go/google/v1beta with a thinking level.
  • The describe graft. src/host/describe-graft.ts grafts the committed plane entries back into pi-ai's settings.describe value (D18). Held by the binary-e2e row "the Settings graft shows each plane's own entry, and the Models page reads both planes configured under their apiKeyEnv", by the [BASELINE] hasProviderApiKey row in both apply orders, and by test/describe-graft.test.ts.
  • The handback. Disabling the plugin's row hands both planes back to pi-ai (PiAiAdapter owns them again), and pi-ai's own save check is back in force; dispose then apply hands them to the plugin again (test/host-planes.test.ts, tiers B and A). When the host's modules cannot be read the plugin stands down and says so; a failing handback restart is reported, not thrown (test/route-claim.test.ts).
  • The save of a catalog-unknown id. On the first try, with a gap id already stored: accepted, written to the file, listed, validated, and sent as completions on the plane's modern base (D15, D16) — in tier B and on the binary.

Review

Round Found Upheld Fixed
1 31 31 31
2 8 8 8
3 5 5 5
audit 8 8 8

In rounds 2 and 3, reviewers reported some of the same defects (the catalog-listener unsubscribe; the store-opt-in case reading the shell's OPENCODE_API_KEY; the modules-unreadable refusal). Round 3's fixes:

  • the sticky served row;
  • the stand-down when the modules cannot be read;
  • a launch environment that answers nothing, for the store-opt-in case.

Three new route-claim cases fail under the old behaviour and pass with the fix.

The audit round's eight findings repeat four defects. The README.zh-CN.md credential wording, the live adapter walk (a withdrawn model now moves to the protocol's next candidate, a withdrawal is confirmed by a raw probe, and a protocol no candidate reached skips by name) and docs/deep-dive.md's key-isolation claim were already fixed on this branch; a source comment in src/go-discovery.ts still claimed the isolation and is corrected; the description findings are this body's flipped-row and record sections and the D20 Go entry above.

Verification

Node Step Result
26.12.1 pnpm run check 0 errors, 11 warnings (limit 21)
26.12.1 vp test run 95 files, 1467 tests passed
26.12.1 shuffled, seeds 1, 42 95 files, 1467 tests passed each time
26.12.1 mock suite (vitest.mock.config.ts) 4 files, 67 tests passed
26.12.1 vp test run --coverage 97.71 / 94.44 / 97.58 / 97.70 against the ratchet 97.5 / 94 / 97.4 / 97.5
26.12.1 pnpm run build lib/index.mjs 322.89 kB, build complete
26.12.1 scripts/check.ts (bundle gate) every check ok, check passed, exit 0
24.22.1 shuffled, seed 7; mock suite 95 files / 1467 tests; 4 files / 67 tests passed
scratch host binary e2e (DSH_HOST_E2E=1) 11 of 11 passed, probe file written fresh at run time

Before each commit, the full diff and the new files were checked for absolute local paths and anything shaped like a credential. Nothing was found, and the pattern was confirmed to match a known positive.

Note: the catalog freshness check now gates a pull request only on its own generator changes (D23, from the base), and it passes on this branch.


Generated by Claude Code

The plugin now serves the opencode and opencode-go routes itself,
through supported host paths only, and the hidden-route workaround is
gone.

A prepended internal/config listener hands pi-ai a copy of its config
without the two plane entries, so pi-ai keeps every other route. On a
commit the plugin reconciles: it builds one plane adapter, a subclass
of the host's PiAiAdapter, over pure profiles (utils/plane-profile,
plane-model, model-metadata and protocols) and registers only the
planes that are declared. A Settings candidate has the plane entries
it changes validated strictly. A read-only settings.describe graft
puts the committed plane entries back, so the host still sees them.

Deleted: responses-provider, responses-routes, google-bridge,
pi-provider and utils/google-replay; the listing hide and the
discovery decoration; the stream redirect; the D19 settings; and the
hidden ids in DEFAULT_PROVIDERS and cordis.patch.yml.

What users see: any saved id is accepted and served, and an id no
catalog knows dispatches as completions. The protocol comes from the
catalog and model info follows the user's own row first. A model
whose reasoning is only on or off offers Off/On. The default endpoint
is the per-protocol inference family. Keys resolve by pi-ai's own
rule, and a plane lists its declared ids even when no key resolves.

When several pi-ai rows declare planes, the row the plugin serves
stays the same while it is live and still declares one, so no row is
left stripped with nobody serving it. If the host's pi-ai module
cannot be read, the plugin steps aside even before any plane is
declared, so a later Settings save goes to pi-ai's own check.

The characterization rows this changes are flipped to new baselines;
every other row stays green unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
# Conflicts:
#	README.md
#	README.zh-CN.md
#	docs/redirect-planes.md

viztor commented Oct 26, 2026

Copy link
Copy Markdown
Owner Author

catalog freshness is red on bffd3a4 because the vendor changed its catalog, not because of anything in this PR. The Go plane renamed gemini-4-argon-contributor to gemini-4-argon and added iceman-contributor-free, and its limits page changed with them.

The fix already exists. 2ebaffc takes the regenerated tables unchanged, and the follow-ups move the fixtures and counts. #105 through #110 carry it and are green. It is being merged into this PR's base branch (P-H, #112) now. This branch picks it up with its next push: either the fix commit from the review now running on this PR, or the final sync pass.


Generated by Claude Code

viztor commented Oct 26, 2026

Copy link
Copy Markdown
Owner Author

e2e · live gateway on bffd3a4 fails the same way it did on #105, because the vendor's live state changed. The gateway now answers 400 Model is unavailable for the free nemotron-3-ultra-free and 429 for beam-free. Nothing in this PR causes that. The fix is d51a3e6: the live suite now skips withdrawn and throttled free models, and the free-tier routing case only accepts a gated model. It is green on #105–#110 and reaches this branch along the same path as the catalog refresh. When the sync merges it here, it also checks that P-R1's retargeted suite keeps that behaviour.


Generated by Claude Code

The refreshed Go catalog serves its Gemini model as gemini-4-argon, so the
plane-dispatch mock, the plane-model cases and the binary e2e's probe
expectations name the new id; the Go Google base they assert is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The review ran three rounds (found 20, 5 and 8; upheld 19, 4 and 8).
What it found and what changed:

- A completions model whose effort list names none lost its Off.
  utils/model-metadata.ts now offers Off with none as its wire value
  on completions, as pi 1.1.0's own entries do; Responses unchanged.
  Pinned in model-metadata, plane-model and a new mock-gateway case.
- A Go entry with no apiKeyEnv authenticates with OPENCODE_API_KEY
  (pi's lookup, D20), and nothing said so. README "Changed in 1.x" and
  step 3 now name it; host-planes.test.ts pins both cases.
- A Settings save of a plane was refused as "cannot be validated yet"
  while the host modules were unloaded. host/route-claim.ts now judges
  it with the last modules that loaded; route-claim.test.ts pins it.
- Fiber.update(config) with noSave false would persist the stripped
  config and drop both planes. Recorded as a constraint in
  route-claim.ts and the plan; no caller does it today.
- Stale text: cordis-context.ts comments (stream redirect, isolate,
  root inject), deep-dive.md (pi's Go lookup, who answers Fetch),
  CONTRIBUTING (field count, the host-modules floor), and historical
  banners on opencode-parity-plan.md and upstream-findings.md.
- The mock body-shape row asserted only body.model. It now checks each
  protocol's required fields and that the gateway answered 200, or
  the vendor's 404 for the unknown id.
- Route-set rows: removing the last plane now pins replace([]) on the
  live handle, and "booting with none" waits for the plugin's row to
  be active instead of sleeping 50 ms.
- The live protocol-routing suite now sends paid models through the
  plane adapter on a tier-A host, not only raw probes.
- README's credential-isolation claim was false: the Go meter can send
  the Zen key. README.md, README.zh-CN.md, deep-dive.md,
  credentials.md and redirect-planes.md now give the real order and
  the 403 remedies; readme-parity.test.ts keeps both READMEs in step.
- The keySource hint described Automatic backwards. settings-copy.ts
  (en and zh) now gives literal key, captured key, then reference.
- The live adapter blocks dropped a protocol whose model was
  withdrawn. They now walk up to three candidates per protocol
  (support/live-walk.ts), pass a "model is unavailable" turn only when
  a raw probe confirms it (support/live-probe.ts), and skip naming a
  protocol no candidate reached.
- A mock comment claimed a body passed shape checks the mock lacks.

Declined: a toggle beside an effort list still offers no Off, which
moves baseline facets and needs the owner's decision; the Models
store's join stays a documented restatement rather than importing a
client UI package; the PR description is edited outside the tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD

viztor commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

Second review of P-R1

This phase's first reviews ran without the dedicated reviewer, which was unavailable at the time. It has now had that review: three rounds, through the correctness, rules and test-matrix lenses.

Round Found Correctness (upheld / fixed) Rules (upheld / fixed) Test matrix (upheld / fixed)
1 20 5 / 3 8 / 7 6 / 4
2 5 2 / 2 — 2 / 1
3 8 4 / 3 — 4 / 3

In total 31 findings were upheld and 23 fixed. Round 3's two lenses found the same two problems: the Chinese README was not ported, and the live walk dropped a protocol.

Fixed

  • src/utils/model-metadata.ts: a completions model whose effort list names none offers Off again, with none as its wire value, as pi 1.1.0's own entries do. Responses is unchanged.
  • src/host/route-claim.ts: a Settings save of a plane is judged with the last modules that loaded. It is no longer refused after a handover or a partial dispose.
  • README.md: "Changed in 1.x" and step 3 now say that a Go entry without apiKeyEnv sends OPENCODE_API_KEY (D20). test/host-planes.test.ts pins both cases.
  • README.md, README.zh-CN.md: the false "strict credential isolation" claim is replaced with the meter's real key order and the 403 remedies. The Chinese README now carries every change from rounds 1 and 2, and test/readme-parity.test.ts keeps the two in step.
  • docs/deep-dive.md: corrected four points: pi's Go lookup, who answers Fetch, the meter's auto order, and the 403 for a Go entry without apiKeyEnv. The "excludes Zen keys / ring stays hidden" claim is gone.
  • docs/credentials.md, docs/redirect-planes.md: each plane's credential is now given as the entry's apiKeyEnv, else pi's OPENCODE_API_KEY.
  • src/settings-copy.ts: the keySource hint (en and zh) gives Automatic's real order: literal key, then captured key, then the declared reference.
  • src/cordis-context.ts: comments no longer describe the stream redirect, the isolated child mount or an llm root inject.
  • CONTRIBUTING.md: the field count is now "every field of Config.dict", and the per-file floor list includes host/host-modules.ts.
  • docs/opencode-parity-plan.md, docs/upstream-findings.md: marked historical, or superseded by P-R1.
  • test/mock/plane-dispatch.mock.ts: the body-shape row checks each protocol's required fields and the gateway's answer, not only body.model. A comment claimed a shape check the mock does not have; it is corrected.
  • test/route-claim.test.ts: removing the last plane now pins replace([]) on the live handle.
  • test/host-planes.test.ts: "booting with none" waits for the plugin's row to become active instead of sleeping 50 ms.
  • test/e2e/protocol-routing.e2e.ts, test/e2e/dispatch-levels.e2e.ts: paid models go through the plane adapter. Each protocol tries up to three candidates (test/support/live-walk.ts). A withdrawn model is passed only when a raw probe confirms it (test/support/live-probe.ts), and a protocol no candidate reached makes the case skip, naming that protocol. The live blocks could not run here (no vendor access, no keys); the walk is tested offline in test/live-walk.test.ts.

Declined

  • The PR description (raised in all three rounds) still gives counts, not the flipped rows and the four records. It was not rewritten before this push because it would have described work the head commit did not contain. A replacement is prepared and can go in now.
  • Fiber.update(config) with noSave false would persist the stripped config. This is recorded as a host constraint in src/host/route-claim.ts and docs/improvement-plan.md. No caller does this today, so a guard waits for one.
  • A toggle beside an effort list still offers no Off. Changing that moves [BASELINE] facets and the no-level request on Messages and Responses, so it is the owner's decision. It is recorded in docs/improvement-plan.md.
  • The Models store's join stays a restatement, with its source lines documented. Importing a client UI package into a node test for a three-line join is not worth it.

Commit: 0cceb81. Gate before the commit: check, 1387 unit tests, 63 mock tests, build and bundle gate, all passing.


Generated by Claude Code

…levels

The live suite caught the Go plane's Gemini model refusing a thinking
budget: the gateway answered that the model takes a thinking level and
not a budget. Pi chooses a thinking level over a thinking budget only
by matching the model id against its own pattern, so a model the
pattern does not name was sent a budget it cannot accept.

The plugin now makes that decision from the catalog, as D16 asks: a
model whose catalog entry offers discrete effort levels on the Google
protocol gets a thinking level. It builds that request through pi's
public exports, on both the enabled and the disabled path, and hands
every other model back to pi's own path unchanged.

Tests pin it: the pure rule over the catalog, a differential check
that the wrapper builds the same request as pi's own streamSimple for
an id pi already recognises, the lazy loading of the pi pieces, and a
mock-gateway case that the turn completes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD

viztor commented Oct 27, 2026

Copy link
Copy Markdown
Owner Author

Fix pushed: Google's thinking level for Gemini models the catalog describes by level (4dbd22e)

The live failure. The live suite caught a turn on the Go plane's Gemini model failing. The gateway refused a token thinking budget with 400 INVALID_ARGUMENT:

Thinking budget is not supported for this model. Use thinking level instead.

Cause. pi 1.1.0 picks Google's discrete thinkingLevel over a token thinkingBudget only when the model id matches its own pattern, so a level-only model that pattern does not name gets a budget it cannot accept.

Fix. The decision now comes from the catalog, as D16 requires. A model whose catalog entry offers discrete effort levels on the Google protocol (usesCatalogThinkingLevels) is sent a thinking level. src/host/google-levels.ts builds that request from pi's own public exports, loaded lazily through the host-module seam, on both the enabled path and the no-effort path. With no effort chosen, the model gets the level pi itself falls back to, not thinkingBudget: 0. Every other model, including ids pi's pattern already serves by level, goes through pi's own path unchanged. A profile that declares Off for such a model still gets pi's own disable, which the docs record as a profile error.

Tests that pin it.

  • The pure rule over the shipped catalog (test/model-metadata.test.ts).
  • A differential check (test/google-levels.test.ts), 16 cases. pi's own streamSimple on an id its pattern names is the oracle, and the wrapper on the same model under an id the pattern misses must build a deep-equal request. Each case also asserts its own premise and that pi's path would differ for the wrapped id. Planted mutations to the wrapper turn it red.
  • Lazy loading of pi's pieces (test/host-modules.test.ts). The check counts imports, and an eager load turns it red.
  • A mock-gateway case (test/mock/plane-dispatch.mock.ts). The mock refuses a budget with the measured message, and the case requires that the turn completes on both planes and that no request carries a budget. A control requires that a model pi already serves by level keeps pi's own request.

The full gate passed before the push: check, unit suite, mock suite, build and the bundle gate. The fix reaches the stacked PRs above through the sync.

These reviews ran on the general model because the dedicated reviewer was unavailable: review:tests#1, review:correctness#1, verify#1.1, review:tests#2, review:correctness#2, verify#2.1, review:tests#3, review:correctness#3, verify#3.1.


Generated by Claude Code

…h' into claude/vigilant-mendel-bctz9n-p-r1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Three documentation defects the synced base carries into P-R1. None was
caused by the merge (each is identical on the merge-base and on both
parents), and each wants the same change on the lowest branch (#105) and,
for the README date, on P-R2's cherry-pick of d13cbe2.

- PRE-1, docs/redirect-planes.md: "The Go plane serves 12 models on
  shapes other than completions" restores the count its own table gives
  (5 openai + 5 anthropic + 1 google = 11, the Go shim's 30 less its 19
  no-SDK models). The 12 was right when written (6 + 6) and a later
  refresh moved the cells but not the sentence. test/catalog.test.ts now
  holds the sentence to the table's non-completions Go sum, so the next
  refresh cannot strand it again.
- PRE-2, README.md and README.zh-CN.md: the 81-model count is dated
  2026-10-27, the refresh that produced it (d13cbe2); on 2026-10-23 the
  count was 82, so the date no longer matched the number.
- PRE-3, README.md and README.zh-CN.md: both install snippets name
  "^1.4.1" (package.json's version), where they had drifted to ^0.14.0 and
  ^0.12.0, neither of which can resolve to a 1.x release.
  test/readme-parity.test.ts pins the two snippets to one range on
  package.json's major.

Both new assertions were checked against the stale values: each fails on
them and passes on the corrected text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
…-mendel-bctz9n-p-r1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Each finding the third audit round left open, re-verified on this head:

- README.zh-CN.md still says Zen keys never query the Go quota endpoint:
  already fixed (0cceb81, dec00c8). The feature bullet, step 3, the
  EntitlementError row and the credential bullet match README.md.
- The withdrawn-model fix drops a protocol instead of trying its next
  model: already fixed (0cceb81). walkPerProtocol walks up to three
  candidates per protocol and names an unreached protocol in a skip;
  test/live-walk.test.ts pins both.
- N1's claim stands in docs/deep-dive.md and the keySource hint
  describes auto backwards: already fixed (0cceb81) in deep-dive.md and
  both hint strings. The same claim survived in a source comment,
  KEY_SOURCE_ORDER's "tier safety" note in src/go-discovery.ts; this
  commit corrects it: a key is filed by the plane its request went to,
  so a Go entry without apiKeyEnv files the Zen key as a Go key.
- PR #113 lists counts, not the flipped rows and the four records:
  fixed in the PR body.
- The R2 fix passes the live rows without performing a protocol, the
  skip decided by message text: already fixed (0cceb81). A "gone"
  verdict needs a raw probe of the same id to confirm it, and a
  protocol no candidate reached skips by name.
- README.zh-CN.md lacks the N1 fix, the Go apiKeyEnv step and the
  "Changed in 1.x" bullet: already fixed (0cceb81).
- PR #113 omits the D20 Go behaviour change: fixed in the PR body.
- docs/deep-dive.md says resolveGoApiKey excludes Zen keys and a
  Zen-only setup hides the ring: already fixed (0cceb81).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The vendor retired `nemotron-3-ultra-free` from the Zen plane, so
`catalog freshness` gated this pull request (it changes the generator's
inputs). `src/catalog-data.ts` is taken exactly as main regenerated it
(#132), which today's registry still matches apart from that one row,
with no hand edits. What restates the catalog follows it:

- Zen 81 -> 80 served, no-SDK 24 -> 23, free 9 -> 8: catalog.test.ts,
  redirect-planes.md, protocol-routing-and-merge.md and both READMEs
  ("127 listed" stays; a retired model stays listed as deprecated);
- the README model lists, in both languages, now name ids the catalog
  ships: the Zen and Go lists had kept seven ids the vendor has since
  retired. The lists are main's; this branch's own prose around them
  (how the Go Google base is served) is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The live gateway job failed on `gemini-3-flash`: the registry still lists
it, and the gateway now answers it with an error of type
`ModelDeprecated`. The adapter walk moves past a withdrawn model only
when both its pre-filter (`isModelGoneMessage`) and the raw probe that
confirms it (`modelGone`) recognise the answer, and neither knew this
shape, so a vendor retirement read as the plugin's refusal.

Both now recognise `ModelDeprecated` (by type, at any status, as main's
`retiredAtTheGateway` does). `test/live-probe.test.ts` is new and holds
`modelGone` to each shape; `keyless-matrix.test.ts` gains the message
row. Removing either rule fails its test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The vendor published `opencode/1.19.5`, and `cli:version` gates this pull
request because its diff touches that check's inputs. Main made the same
bump in #132 after comparing every cited vendor source between the two
tags: the one that moved (`session/llm/request.ts`) only dropped an
unused type import, so no header the plugin emulates changed. This takes
the same bump in the 17 files that carry a copy here; `cli:version
--copies` reports all 58 copies and mentions at 1.19.5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
viztor pushed a commit that referenced this pull request Nov 3, 2026
…pure half

Outcomes of the review round on P-T1a:

- both-kept-2-rr2-L1 / resolutions-2-rr2-T1 (one finding): fixed.
  test/use-channel.test.ts feeds withoutHostPackages a two-document
  lockfile in pnpm's shape (the package-manager record, then the project
  document with quoted host keys in the importer's three dependency
  sections, packages and snapshots, one peer-suffixed snapshot key, a pi
  entry and a non-host package naming a host peer in its sub-map). It
  asserts every host key is gone from all five sections, the input held one
  in each (so a strip that read nothing fails), pi and the non-host
  entries survive with their sub-maps, the first document is unchanged,
  and the strip is idempotent. Planted four mutations (a section dropped
  from the list, the importer's optional section dropped, the key match
  broken, deletion at the wrong path): each turns it red.
  harness-channel.test.ts also reads every @deepseek-ai version in each
  committed lockfile and fails on one from the other channel's prerelease
  line, which the record fields (dsh, cordis, loader, schemastery) did not
  cover; planting an alpha cordis-plugin-include entry in the rc lockfile
  turns it red.
- both-kept-2-rr2-L2 / rules-docs-2-rules-docs-2-R1 (one finding): fixed.
  docs/testing.md's pure-halves table gains the use-channel.ts ->
  scripts/channel-manifest.ts row, and AGENTS.md's use-channel line names
  the pure half, its four rules and test/use-channel.test.ts.
- both-kept-2-rr2-L3: no tree change, by the finding's preferred route.
  The README policy sentence stays as written in both languages; the plan
  of record's D16 row already says main's #130 Gemini level rewrite leaves
  with the module P-R1 (#113) deletes, and the #109 pull request body
  carries the same sentence, so P-R1 does not have to undo a README clause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
claude added 10 commits November 4, 2026 03:33
…h' into claude/vigilant-mendel-bctz9n-p-r1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The Google level wrapper re-implemented pi 1.3's own rule for which
Google models take a thinking level, for older pi. That is the host's
job (D26): the plugin now hands every Google turn to pi's own protocol
and sends what pi builds. pi 1.3 and later name the Go plane's
level-only model, so every npm install of either channel is unaffected.

The rc desktop app's pi (1.1.0) does not, and sends a budget the gateway
refuses. That is recorded as a HOST_LIMITATION row in the in-process
characterization and the binary e2e, both detected from pi's own
exported usesGoogleThinkingLevel, never a version; the mock and live
cases that need the level skip by the same predicate. The READMEs list
it under "Host limitations", beside the alpha save defect.

The catalog's level rule moves to test/support/thinking-levels.ts, where
the cases that pick a subject by it live.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
A model's protocol was the catalog SDK, then pi's installed entry; its
legacy base took pi's own /zen base when pi filed the model there. pi's
catalog is the host's to reshape, and 1.5.0 did (per-protocol roots, and
refiled models), so a pi bump could move a turn the plugin routes.

The protocol is now the catalog SDK, then the plane entry's route-level
api, then completions (D16), and the base comes from the endpoint
family table alone (D26). pi's entry still fills metadata the user's row
and the catalog leave unstated (D17). A new case builds every catalog
model on both planes and families twice, with pi's entry and with that
entry reshaped, and holds both the protocol and the base still.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The plane profile restated pi-ai's default context window and output
tokens (262_144 / 32_768) as the fallback for an entry pi-ai's Config
had not filled. The alpha bridge has since raised both (1_000_000 /
128_000): a copy that goes stale silently, and would understate a
window wherever it was reached, the overflow trap D17 exists to avoid.

pi-ai's own Config fills both into the entry the plugin reads back, with
the channel's values, so the plugin now restates neither (D17, D26).
Where no Config ran nothing fills them, and a model no source sizes is
an error in pi-ai's words rather than an invented window. The
characterization's catalog-unknown rows take each channel's value from
one record (PI_AI_DEFAULTS), which host-shapes pins against the
installed pi-ai, and the unit and e2e fixtures that fed the copy back in
now pass stand-in values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
…laim

The route claim's note on F12 cited cordis's Fiber.update at the rc
lines only; alpha.2 moved it (764-782). Every other host line the src
cites was checked at both tags (rc.5 and alpha.2) and still holds.

docs/redirect-planes.md said the plane provider puts all the gateway
headers on every protocol. It adds only pi's own session header per
call; the client, project and session-id headers come from the global
fetch patch, which P10a moves into the call.

The plan's P-R1 section records the restack: one row table for every
host cell, and the three compensations for the host's pi removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
The case built a plane profile with no normalize, so its undeclared
model was sized by the plugin's copy of pi-ai's defaults. With that
copy gone the model is an error, as the profile case pins; the case now
hands the entry to pi-ai's own Config, as the route claim does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Deleting the Google level wrapper removed a module held at 100%, and
the global function floor (97.4%) fell to 97.37% with nothing newly
uncovered. Rather than lower the ratchet, this covers the five wire
codecs' create() doors, which no test reached: a runtime that builds a
codec per call takes that door, so a codec whose two doors parse
differently decodes one way in tests and another on the host. A mutated
create() fails the case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Run on the rc floor (pi 1.1.0), the mock suite's per-model dispatch
cases sent the Go plane's level-only model the budget pi builds there,
and the mock gateway refused it, so the nightly floor cell would fail on
a host limitation rather than record it. Those cases now expect the
gateway's budget refusal where pi's own usesGoogleThinkingLevel does not
name the model, and keep asserting the path, the model and the key.
Measured on both floors and both channels' newest installs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Run on the published binaries, both rc cells and the alpha floor serve
the planes through the route claim. On alpha's npm cell, npm resolves
cordis 4.0.6-alpha.2, whose internal/config waterfall hands listeners
the fiber and reads the config from next(), and the claim speaks the
earlier contract, so pi-ai serves both planes there; the binary e2e
failed on that cell.

The probe now records which contract the host dispatched, and the e2e
asserts the measured state as a KNOWN_DEFECT row and skips the rows that
need the claim, by name. dsh 0.2.2-alpha.3 requires that cordis, so the
alpha bump must adopt the contract (feature-detected) and flip the row;
the READMEs, CONTRIBUTING and the plan say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
Close the review of the restacked P-R1 head. One test change, the rest
documentation and comments; no src behaviour moves.

- both-kept-1-F1 / resolutions-1-F1 / rules-docs-1-F1: the "Your row wins"
  bullet in both READMEs now gives the protocol as the catalog's SDK, then
  the plane entry's route-level `api` (only for ids the catalog does not
  classify), then Chat Completions; the plan's P-R1 item says the route
  `api` covers ids the catalog does not classify (D26).
- both-kept-1-F2 / resolutions-1-F2 / rules-docs-1-F2: CONTRIBUTING's
  release step 3 and the D22'' row name the tables that exist: a pi move
  re-measures the cell's column of WHOLE_CATALOG and PI_GAPS_BY_CELL, a
  cluster whose bridge defaults moved updates PI_AI_DEFAULTS (feeding
  s0PluginFor's catalog-unknown rows), no S0 row follows pi; the three
  deleted S0_* names are gone.
- rules-docs-1-F5: AGENTS.md and CONTRIBUTING say each cell takes its
  channel's s0PluginFor table, differing only in PI_AI_DEFAULTS, with
  forHost/forChannel for expectations outside the table.
- both-kept-1-F4 / resolutions-1-F5 / rules-docs-1-F3: redirect-planes,
  CONTRIBUTING's cells bullet and the plan's e2e-host section say the
  claim-dependent binary-e2e rows run where the route claim engages; on a
  host whose internal/config hands listeners the fiber a [KNOWN_DEFECT]
  row asserts pi-ai serves the planes and those rows skip by name, until
  the 0.2.2-alpha.3 bump flips it.
- resolutions-1-F4: the D26 walk now reshapes pi's entry to the served
  protocol on a /zen root of its own, and walks uncatalogued ids filed by
  pi under every non-completions protocol. Checked against 7ed93a7's
  parent: the full old module fails on the legacy base, and the old
  protocol order alone fails on the uncatalogued walk.
- both-kept-1-F3 / resolutions-1-F7: the mock gateway's level-only note,
  the dispatch assertion message and the binary e2e's pi-root comment no
  longer describe the deleted wrapper or the retired floating cell.
- resolutions-1-F6: hostSendsBudgetTo and claimCannotEngage/CLAIM_SKIP sit
  above the doc blocks of expectAnswered and hostCannotSave, so each doc
  is on its own function again.
- resolutions-1-F3: the stream hook's price comment no longer points at a
  dispatch P-R1 deleted.
- rules-docs-1-F7: the protocols and model-metadata comments name no
  model and justify the @ai-sdk/mistral row by the gateway's path; the two other
  "follows pi" sentences are recorded in the plan's P7 row.
- rules-docs-1-F4: the English quota-ring row carries the same apiKeyEnv
  advice as the Chinese one.
- rules-docs-1-F6: engineering-notes' base-URL section is past tense and
  marked superseded by P-R1 (D13, D26): baseUrlFor over the generated
  family table is the only base source.
- rules-docs-1-F8: the plan's Models-store restatement cites the lines
  modelsPageRows cites, at each channel record's tag, not rc.2.
- rules-docs-1-F9: both READMEs give the gatewayUrls default as both
  families and list both in the example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
… restack sweep

Closes the second review round of the P-R1 restack.

- both-kept-2-R2-1: README pair, "Changed in 1.x": one paired bullet says
  the Google thinking control is now the host pi's own, that the level
  rewrite for older runtimes released in 1.4.3 (#130) is removed, and that
  on a pi 1.1.0 host (the rc desktop app) the affected model's thinking
  turns are refused by the gateway on either plane until the host bumps pi;
  it links to the Host limitations list.
- both-kept-2-R2-2: ci.yml's e2e-host npm-cell comment and the binary
  e2e's header now say the rows are asserted where the route claim engages,
  name the [KNOWN_DEFECT] claim row and its CLAIM_SKIP skips, and that the
  save rows skip where the host cannot save.
- both-kept-2-R2-3 / rules-docs-2-F1: protocols.ts no longer states an
  unmeasured gateway path; it grounds the Mistral SDK row in the generated
  family table (the vendor's OpenAI root), says the dialect is unmeasured,
  and points at the plan's P7 row. The P7 row now lists all six places that
  justify the row by pi's data (the README pair's SDK table rows and
  protocol-routing-and-merge's table row added).
- both-kept-2-R2-4 / rules-docs-2-F3: the characterization header says
  every S0 facet and named case is [BASELINE] except the one
  [HOST_LIMITATION] row, and todayRowsFor "answers the cell's rows for
  either order" (header and CONTRIBUTING).
- both-kept-2-R2-5: the README pair's support policy and ci.yml's test-job
  comment say the in-process suites run each channel's cluster with pi as
  npm resolves it and the cordis family at the versions the harness
  vendors (a second exception beside schemastery), and that the e2e-host
  npm cell is the run that lets cordis float.
- resolutions-2-F1: both D15 save rows in the binary e2e also skip with
  CLAIM_SKIP where the route claim cannot engage; CONTRIBUTING and the
  plan's skipped-row list say so. Test-only: the change is the skip in the
  e2e itself, which no offline host can drive red.
- resolutions-2-F2: the Host limitations bullet (both languages),
  redirect-planes, CONTRIBUTING's host cells, the plan's P-R1 record and the
  mock's predicate doc scope the pi 1.1.0 Google thinking-level limitation to the
  model on either plane: measured live on Go, expected on Zen under the
  same pi rule and not yet measured there. The in-process row stays
  Go-only (S0 declares it there); declaring it on Zen would move S0 rows
  and is left out.
- resolutions-2-F3: patch-method's rewrapOnAdaptersUpdated doc keeps the
  two caveats and drops the deleted bridge, hidden-route and KNOWN_DEFECT
  references; host-shapes' F10 doc says the plane adapter routes Go's
  Google models; reload.test's reconfigured doc drops the deleted listing,
  keyless filter and reinstall-case sentences; an architecture tripwire
  comment puts the deleted decoration in the past tense.
- rules-docs-2-F2: the plan's defect table strikes the mode-merging row as
  fixed by P-R1 (one plane adapter answers picker and dispatch in D17's
  order; toggle models offer Off / On, D1), cited at utils/model-metadata.ts
  and utils/plane-model.ts, and re-cites the P3 row by function name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD
- both-kept-3-R3-1: CONTRIBUTING's release step 3 and D22'' now say a moved
  host event is adopted by the route claim's own listener and the tier
  listeners, detected from the dispatch, with every shape a supported channel
  dispatches accepted while the channels' cordis differ; the contract facts
  are re-measured per channel, and dsh@alpha is already 0.2.2-alpha.3.
- resolutions-3-r3-F1: the README pair and redirect-planes say every turn of
  the level-only Gemini 4 model carries a budget on pi 1.1.0, thinkingBudget 0
  when no effort is chosen, not only a turn with thinking.
- resolutions-3-r3-F2, both-kept-3-R3-2: the "Changed in 1.x" bullet no
  longer states the gateway's refusal on both planes; it points to Host
  limitations, which scopes it (measured on Go, expected on Zen).
- resolutions-3-r3-F3, both-kept-3-R3-3: comments no longer describe the
  deleted Google bridge, and the mock's level predicate is scoped to either
  plane.
- rules-docs-3-F1: the support policy and Host limitations, in both
  languages, say the current dsh@alpha (0.2.2-alpha.3) installed with npm
  runs both planes on pi-ai's routing until the plugin adopts its cordis's
  configuration-hook contract.
- rules-docs-3-F2: "Changed in 1.x" names the catalog's one @ai-sdk/mistral
  model as now offered, sent as Chat Completions on the vendor's OpenAI root,
  its dialect unmeasured.
- rules-docs-3-F3: the P7 row lists the two test places that justify the
  Mistral row by pi's data and drops its count; the mock note cites P7.
- rules-docs-3-F4: the manifest case is retitled to what it holds.
- rules-docs-3-F5: the plan's host-support bullets and lesson L1 record what
  P1b and P-R1 already did.
- rules-docs-3-F6: credentials.md's login-reader paragraphs move under a
  v2-record heading, without the bridge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0141fKp5k6hiaJcP5ovhGtSD

viztor commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

Restacked onto main 1.8.0. This branch now carries main 1.8.0 through its parent P-H (ff2db1d), on the two-channel host: rc 0.2.1-rc.5 and alpha 0.2.2-alpha.2, with pi unpatched. Pushed head: 26d1e7479fd497e01778be67ec8123287fb20d9e. The PR body has a new "Restacked onto main 1.8.0" section with the new scope.

Conflicts and how they were resolved

Source

  • src/google-bridge.ts, src/pi-provider.ts and their tests: these were modify/delete conflicts. P-R1's deletions are kept.
  • src/stream-hook.ts: comment only. Took P-H's wording, because P-R1's named a model.
  • src/utils/request-shape.ts: comment only. Took P-R1's, since opencode-google is no longer a route.
  • cordis.patch.yml: P-R1's key list, minus the keys D19 removed, with main's debug/debugFile wording (D24).

CI and config

  • .github/dependabot.yml: P-H's text. The cluster and pi move only as a channel bump.
  • .github/workflows/ci.yml: the e2e-host scope is P-R1's host-seam regex plus scripts/host-drift.ts. patches/ is dropped, and the comment merges both sides.

Host harness and characterization

  • test/support/host.ts: P-H's cell machinery (HostCell, forHost, forChannel, PI_GAPS, the HOST_LIMITATION tag) with P-R1's flipped rows.
    • One table builder per channel (s0PluginFor) feeds S0_TODAY_BY_CELL.
    • todayRowsFor answers the same rows in both apply orders.
  • test/host-characterization.test.ts: started from P-R1's file and ported P-H's cell awareness. The apply-order baseline now runs on every cell.
  • test/e2e/host-binary.e2e.ts: P-R1's assertions on P-H's infrastructure. The rows that depend on the route claim, and the save rows, skip where the host cannot save or the claim cannot engage.
  • test/e2e/host/probe-plugin.mjs: P-R1's plane and graft measurements, plus P-H's thrownBy, editEntry, the control edit and the fiber state.
  • test/host-versions.test.ts, test/mount-protocols.test.ts, test/host-shapes.test.ts: P-H's channel-aware files with P-R1's "plane adapter" wording, and imports from both sides.

Live suites

Other tests

  • test/lifecycle.test.ts: main's D24 rule, apply(ctx, { debugFile }).
  • test/models-discovery.test.ts: P-R1's side.
  • These did not conflict but were wrong after the merge:
    • test/catalog.test.ts: counts now follow main's shim (81 Zen, 9 free, 24 with no SDK).
    • test/host-modules.test.ts: reads HOST_VERSIONS.pi.
    • test/stream-hook.test.ts: the obsolete case is dropped.

Docs

  • AGENTS.md: P-H's host-versions bullet, and P-R1's host.ts bullet updated for S0_TODAY_BY_CELL. The utils table gains retry.ts and loses google-replay.
  • CONTRIBUTING.md: P-H's tags, channels and cells, with P-R1's flip text, trigger list and keyless bullet.
  • README.md and README.zh-CN.md: P-R1's routing prose with main's lists. The count line is 81 as of 2026-11-03. The two READMEs still pair.
  • docs/deep-dive.md: item 3 from P-H, items 4 and 5 from P-R1.
  • docs/protocol-routing-and-merge.md and docs/redirect-planes.md: the counts are re-derived against 81 models.
  • docs/improvement-plan.md: P-H's status table, with the P-R1 row set to merged, plus P-R1's trigger list and keyless bullet.

Dropped because main or the host now does it (D26)

  • src/host/google-levels.ts and everything that hung off it: its wiring, its tests, its 100% coverage floor and its doc lines. The thinking control is pi's own.
  • pi's catalog entry as a protocol or base source in plane-model.ts, including the legacy branch that took pi's /zen base (D16).
  • plane-profile's restated pi-ai default window and output tokens (D17).
  • Obsolete tests from main:
  • P-T1b's per-cell machinery from before P-R1, including NEXT_DISPATCH.
  • The patched "pinned" binary cell. No pi patch remains, so the npm and floor cells of both channels ran instead.

Review rounds

Round Found Upheld Fixed
1 20 20 20
2 11 11 11
3 13 13 12 (1 declined)

The declined finding sets P0's plan row to merged. It belongs at the bottom of the stack (P1a) and will reach this branch through the P-H parent merge. Editing it here first would only create a conflict for that merge.

Live re-measures still owed (CI)

  • protocol-routing.e2e: the Google level case on Go with a key, on the newest pi. It must skip as HOST_LIMITATION on the rc floor.
  • That the gateway still refuses a thinkingBudget for the Go level-only model. Zen is expected to refuse too but is not yet measured.
  • keyless-matrix.e2e: TODAY_DISPATCH, all four protocols on the modern family on both planes.
  • protocol-routing.e2e: the keyed adapter walks through PlaneAdapter on tier A for both planes.
  • e2e-host for both channels' npm and floor cells. It records configHook per cell and confirms the alpha npm [KNOWN_DEFECT] row and the rc floor HOST_LIMITATION rows.
  • Inherited from P-R2: the x-opencode-request, x-opencode-project and Retry-After rows, and the combined Google User-Agent.

Known defect, high priority. On cordis 4.0.6-alpha.2 the route claim does not engage. The current dsh@alpha (0.2.2-alpha.3) requires that cordis, so its bump must adopt that internal/config contract first, detected from what the host dispatches.

All rounds' reviews (both-kept, resolutions, rules-docs) and their verify passes ran on the general model, because the dedicated reviewer was unavailable.


Generated by Claude Code

viztor commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

catalog freshness is red on 26d1e74 because of a vendor-side catalog change, not this pull request. The vendor added a Zen model, crouton-alpha-free (81 → 82), after the last refresh this stack carries. This diff touches the catalog generator's imports, so the check gates on the drift by design. The branch carries the stack's generated src/catalog-data.ts unchanged.

No fix exists yet. The machine doing the restack cannot reach the vendor catalog, so the regenerated file can only come from the scheduled refresh on main. When that lands, it will be ported into the bottom of the stack and merged up through this branch, with the counts that restate the catalog re-derived. A re-run would fail the same way, so none was spent.


Generated by Claude Code

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.

2 participants