Skip to content

feat(server): detect OpenCode 1.x or 2.x per instance and route by it - #14239

Merged
juliusmarminge merged 4 commits into
t3code/codex-turn-mappingfrom
v2/oc2-1-runtime-probe
Oct 1, 2026
Merged

juliusmarminge merged 4 commits into
t3code/codex-turn-mappingfrom
v2/oc2-1-runtime-probe

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

The OpenCode driver assumed every binary or server was 1.x. OpenCode 2 is out (opencode-ai latest is still 1.18.33, @opencode/cli latest 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 a Cache with an infinite TTL on success and zero on failure, so a failed probe is never remembered.

    • Local binary: opencode --version. This replaces the status check's own --version call, so the binary is still spawned only once. It prints 1.18.32 on 1.x and opencode v2.0.18 on 2.x.
    • External serverUrl: GET /api/info (2.x), then GET /global/health (1.x).
      • The configured password is sent as UTF-8 Basic auth, like the 1.x SDK client. HttpClientRequest.basicAuth uses Latin-1 btoa; checked live, both versions accept only UTF-8.
      • Only a 200 application/json body counts, because each version answers 200 text/html (its web UI) on the other's path. Recorded live: 2.x /global/health and 1.x /api/info are both HTML.
      • /api/info must carry version and pid, as real 2.x does, so other JSON is not taken for 2.x.
    • A 401 from either path is reported as an auth error, not a version. Both versions return 401 for a wrong password on both paths.
    • Server probe errors are fixed text: invalid URL, unreachable, timed out, 401, or not OpenCode. The underlying failure can carry the configured URL or its credentials, so it stays in cause and never reaches the status message.
    • The status check uses 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:

    • the orchestration adapter;
    • workspace inventory (snapshotForCwd);
    • text generation.

    Details:

    • A detected 2.x is refused with "OpenCode 2 is not supported yet…", before any 1.x server is spawned or spoken to. The status check shows the same message as an error.
    • This refusal is temporary. Layer 4 (the OpenCode 2 adapter) replaces it.
    • getCapabilities and planSelectionTransition are 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.
    • A failed probe falls through to the 1.x path. Its existing server checks already report the failure, and the probe retries on the next call.
  • Compatibility policy: unchanged. This branch keeps V2's bundled opencode entry from fix(provider): mark OpenCode v2 incompatible #14198 as is: >=2.0.0 broken, >=1.14.19 <2.0.0 supported, recommended 1.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 keeps status: "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

  • The policy for preview builds is undecided. The maintainer hasn't decided how preview builds will get a policy that marks 2.x supported (and 1.x legacy). This PR doesn't change the manifest.
  • The legacy warning for 1.x ("OpenCode 1.x is legacy. Update to OpenCode 2.", plus the warning that 2.x migrates the shared OpenCode database in place, so 1.x and 2.x must not run side by side). It waits on the policy decision above. The compatibility policy also has no message field, so the custom wording would need a contract change.
  • An upgrade path to 2.x. The update action installs opencode-ai, and 2.x ships as a different npm package, @opencode/cli.
  • User docs (layer 10).

Verification

  • Rebased onto t3code/codex-turn-mapping at cd66adf (v0.0.44). vp test run on opencodeVersionProbe.test.ts, Drivers/OpenCodeDriver.test.ts, Layers/OpenCodeProvider.test.ts, providerCompatibility.test.ts and ModelManifest.test.ts: 57 passed. Before the rebase, the same files plus OpenCodeAdapterV2.test.ts, ProviderInstanceRegistryLive.test.ts, OpenCodeTextGeneration.test.ts and OpenCodeServerOwner.test.ts passed (130).
    • The probe tests replay HTTP responses recorded from live opencode serve 1.18.32 and 2.0.18 (testFixtures/opencodeProbeResponses.ts): the 2.x /api/info JSON, the 1.x /global/health JSON, the SPA HTML each serves on the other's path, and both versions' 401 bodies. They also use the real --version strings of both versions. The replay checks UTF-8 Basic credentials, as the real servers do.
    • Each review fix has a test that fails on the first commit (7a3787a):
      • non-ASCII passwords (pässwörd, pass€word);
      • {"version":"3.4.1"} at /api/info;
      • no URL, userinfo or query secret in any probe error detail;
      • a 2.x instance refusing workspace inventory and text generation without reaching a server;
      • getCapabilities answering while the server never replies;
      • a server URL with a path prefix and a query (/opencode/?route=oc → /opencode/api/info?route=oc).
    • Memoization: a failure is not cached, a success is, and refresh re-probes.
    • Compatibility, against the bundled policy: 1.14.18 is broken, 1.14.19 and 1.18.33 are supported, 2.0.18 is broken, and a ready 1.x snapshot keeps status: "ready".
  • The existing OpenCode replay fixtures, run through the orchestrator (OrchestratorReplayFixtures.integration.test.ts -t opencode): 6 passed (simple, opencode_subagent, opencode_child_approval, opencode_running_child_approval, plan_questions, turn_interrupt).
  • Live, using isolated XDG dirs and servers started and stopped by PID. This used scratch tests that are not committed:
    • probeOpenCodeRuntime against real 2.0.18 and 1.18.32 binaries and serve processes: v2/2.0.18 and v1/1.18.32, from both the binary and the server URL.
    • Non-ASCII passwords: 2.x with pass€word and 1.x with pässwörd both probe correctly. A 1.x server with pass€word, driven through the real OpenCodeDriver, is ready with 8 models, and getCapabilities and planSelectionTransition succeed.
    • A wrong password on either server gives the 401 auth error. A dead port with userinfo in the URL gives "Couldn't reach the OpenCode server.". A URL without a scheme gives the invalid-URL message.
    • The real OpenCodeDriver with the 2.0.18 binary refuses the status check, snapshotForCwd, text generation and getCapabilities. Its fresh data directory holds only the log line for --version: no serve ran and no database was created.
    • The real OpenCodeDriver with the 1.18.32 binary routes getCapabilities to the 1.x adapter.
    • A 1.18.32 server behind a proxy that serves it only under /opencode/ and only with ?route=oc probes as v1/1.18.32. The proxy logged both requests with the prefix and query intact.
  • After the rebase: tsc --noEmit -p apps/server exits 0; vp lint on the changed files and vp run knip:check are clean.
  • Not run: repo-wide checks, and web or mobile UI checks. No client code changed; the picker behavior comes from status staying ready.

Model: Claude Opus 5.5 (Claude Code)

🤖 Generated with Claude Code

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 29, 2026
Comment thread apps/server/src/provider/model-manifest.json Outdated
Comment thread apps/server/src/provider/Drivers/OpenCodeDriver.ts
Comment thread apps/server/src/provider/opencodeVersionProbe.ts Outdated
Comment thread apps/server/src/provider/opencodeVersionProbe.ts Outdated
Comment thread apps/server/src/provider/opencodeVersionProbe.ts Outdated
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire — 4.9 KiB — 6.8 KiB ✅
Codex Thread snapshot wire — 3.7 KiB — 4.9 KiB ✅
Codex Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Codex Live turn WebSocket decoded — 20.4 KiB — 29.3 KiB ✅
Codex Live turn messages — 2 — 8 ✅
Claude Total thread wire — 4.9 KiB — 6.8 KiB ✅
Claude Thread snapshot wire — 3.7 KiB — 4.9 KiB ✅
Claude Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Claude Live turn WebSocket decoded — 20.8 KiB — 29.3 KiB ✅
Claude Live turn messages — 2 — 8 ✅

Baseline: unavailable · PR result: fa8d700 · Source CI: success

Scenario and decoded snapshot size

10 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.

  • Codex decoded thread snapshot: 106.1 KiB
  • Claude decoded thread snapshot: 106.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

macroscopeapp Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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.

Comment thread apps/server/src/provider/opencodeVersionProbe.ts Outdated
Comment thread apps/server/src/provider/opencodeVersionProbe.ts Outdated
juliusmarminge and others added 4 commits September 30, 2026 12:32
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>
@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Sep 30, 2026
@juliusmarminge
juliusmarminge merged commit 14f74e1 into t3code/codex-turn-mapping Oct 1, 2026
23 checks passed
@juliusmarminge
juliusmarminge deleted the v2/oc2-1-runtime-probe branch October 1, 2026 00:05
saphid pushed a commit to saphid/t3code that referenced this pull request Oct 1, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant