feat(server): detect OpenCode 1.x or 2.x per instance and route by it - #14239
Conversation
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: unavailable · PR result: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR introduces production version probing and automatically changes whether OpenCode sessions, inventory loading, and text generation proceed for each instance. The new cache, external-server probing, and runtime gate create a broad behavioral change that requires human review. You can add or adjust custom eligibility rules. Learn more. |
f9d1b90 to
e838bb3
Compare
The OpenCode driver now probes each instance's version once and remembers it: `opencode --version` for a local binary, `/api/info` then `/global/health` for a configured server (JSON only, since each version serves HTML on the other's path; a 401 is reported as an auth error). A failed probe is never remembered, and status checks re-probe so an in-place upgrade re-routes. The orchestration adapter goes through a runtime selector. Only the 1.x runtime exists, so a detected 2.x gets a temporary "not supported yet" error until the OpenCode 2 runtime lands. The bundled compatibility policy gives 1.x limited support; a test pins that a ready 1.x instance stays ready and selectable beside that advisory. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…n probe Review follow-ups for the OpenCode runtime probe: - Send the server password as UTF-8 Basic auth, as the 1.x client does. `HttpClientRequest.basicAuth` uses Latin-1 `btoa`, which threw on `€` and sent the wrong bytes for `ä`, so such passwords broke 1.x servers. - Refuse OpenCode 2 in workspace inventory and text generation too. Both started or connected to a server themselves, so a 2.x instance still ran a 1.x client against it (and migrated its data directory). - Capability and selection reads use the last successful probe instead of waiting on one, so an unreachable server no longer adds 5 s to each. - Require `pid` in `/api/info`, as real 2.x servers send it, so other JSON is not taken for OpenCode 2. - Server probe errors use fixed text; the underlying failure, which can carry the configured URL or credentials, stays in `cause` only. - The probe reads `OpenCodeRuntime` from the environment. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…bing The probe appended `/api/info` to the raw server URL string, so a query put the path inside it (`/?x=1` became `/?x=1/api/info`) and a working server behind a routing proxy read as unavailable. The path now goes onto the parsed URL's pathname, keeping any prefix and the query. Also switch the probe to a namespace import of `opencodeRuntime.ts`, as its other non-test callers do. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…espaces Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
e838bb3 to
fa8d700
Compare
Upstream fast-forwarded t3code/codex-turn-mapping by 36 commits after the previous refresh. Two patches needed changes: - "recover from slow CLI health probes" conflicted with upstream's new OpenCode 1.x/2.x generation probe (pingdotgg#14239). Its server fallback is dropped and only the AI Enablers launcher is kept. - The startup-recovery and orphaned-start patches now use the namespace imports upstream adopted in pingdotgg#14387. The commits live on automation/nightly-v2-20261001-stack. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AHKKXVog6PVQ1HEvdCKgxJ
The OpenCode driver assumed every binary or server was 1.x. OpenCode 2 is out (
opencode-ailatest is still 1.18.33,@opencode/clilatest is 2.0.19). It speaks a different protocol, so T3 would start a 2.x server and talk 1.x to it. This is layer 1 of the OpenCode 2 stack: detect the runtime per instance and route by it, with only the existing 1.x runtime behind the router for now.What changed
Version probe (
provider/opencodeVersionProbe.ts). One probe result per driver instance, stored in aCachewith an infinite TTL on success and zero on failure, so a failed probe is never remembered.opencode --version. This replaces the status check's own--versioncall, so the binary is still spawned only once. It prints1.18.32on 1.x andopencode v2.0.18on 2.x.serverUrl:GET /api/info(2.x), thenGET /global/health(1.x).HttpClientRequest.basicAuthuses Latin-1btoa; checked live, both versions accept only UTF-8.application/jsonbody counts, because each version answers 200text/html(its web UI) on the other's path. Recorded live: 2.x/global/healthand 1.x/api/infoare both HTML./api/infomust carryversionandpid, as real 2.x does, so other JSON is not taken for 2.x.causeand never reaches the status message.refresh, so upgrading OpenCode in place re-routes the instance on the next provider refresh. Settings changes already rebuild the driver.Runtime selector (
OpenCodeDriver.ts). Every path that reaches an OpenCode server goes through the probe, in front of the unchanged 1.x code:snapshotForCwd);Details:
getCapabilitiesandplanSelectionTransitionare hot, so they use the last successful probe (1.x before one lands) and never wait on a server.openSession, workspace inventory and text generation wait for a probe.Compatibility policy: unchanged. This branch keeps V2's bundled
opencodeentry from fix(provider): mark OpenCode v2 incompatible #14198 as is:>=2.0.0broken,>=1.14.19 <2.0.0supported, recommended1.14.19. That is right for stable and nightly, which have no 2.x runtime. A test pins it and checks that a ready 1.x instance keepsstatus: "ready"beside the advisory. Until preview builds get a 2.x-supported policy, a detected 2.x shows as "broken" in settings on V2. Later layers route 2.x through its adapter regardless of the advisory.Not in this PR
opencode-ai, and 2.x ships as a different npm package,@opencode/cli.Verification
t3code/codex-turn-mappingat cd66adf (v0.0.44).vp test runonopencodeVersionProbe.test.ts,Drivers/OpenCodeDriver.test.ts,Layers/OpenCodeProvider.test.ts,providerCompatibility.test.tsandModelManifest.test.ts: 57 passed. Before the rebase, the same files plusOpenCodeAdapterV2.test.ts,ProviderInstanceRegistryLive.test.ts,OpenCodeTextGeneration.test.tsandOpenCodeServerOwner.test.tspassed (130).opencode serve1.18.32 and 2.0.18 (testFixtures/opencodeProbeResponses.ts): the 2.x/api/infoJSON, the 1.x/global/healthJSON, the SPA HTML each serves on the other's path, and both versions' 401 bodies. They also use the real--versionstrings of both versions. The replay checks UTF-8 Basic credentials, as the real servers do.pässwörd,pass€word);{"version":"3.4.1"}at/api/info;getCapabilitiesanswering while the server never replies;/opencode/?route=oc→/opencode/api/info?route=oc).refreshre-probes.status: "ready".OrchestratorReplayFixtures.integration.test.ts -t opencode): 6 passed (simple, opencode_subagent, opencode_child_approval, opencode_running_child_approval, plan_questions, turn_interrupt).probeOpenCodeRuntimeagainst real 2.0.18 and 1.18.32 binaries andserveprocesses: v2/2.0.18 and v1/1.18.32, from both the binary and the server URL.pass€wordand 1.x withpässwördboth probe correctly. A 1.x server withpass€word, driven through the realOpenCodeDriver, isreadywith 8 models, andgetCapabilitiesandplanSelectionTransitionsucceed.OpenCodeDriverwith the 2.0.18 binary refuses the status check,snapshotForCwd, text generation andgetCapabilities. Its fresh data directory holds only the log line for--version: noserveran and no database was created.OpenCodeDriverwith the 1.18.32 binary routesgetCapabilitiesto the 1.x adapter./opencode/and only with?route=ocprobes as v1/1.18.32. The proxy logged both requests with the prefix and query intact.tsc --noEmit -p apps/serverexits 0;vp linton the changed files andvp run knip:checkare clean.statusstayingready.Model: Claude Opus 5.5 (Claude Code)
🤖 Generated with Claude Code