Repository navigation
fix(opencode): restore variant controls with custom values and v2 argv - #32
Draft
andromarces wants to merge 6 commits into
Draft
andromarces wants to merge 6 commits into
andromarces wants to merge 6 commits into
Conversation
opencode --variant names are per-model presets, not a fixed effort
scale. Models declare variants:{} or sets without medium, and unknown
values are accepted with exit 0, so low/medium/high misleads. Declare
thinking:false and reject any thinking value loudly until per-model
variants are exposed. See rjx18#30.
Intended behavior change for harn:assume harness-declares-supported-thinking-levels: codor stops offering opencode a fixed effort set the selected model may not declare. Harn hashes not edited per CONTRIBUTING.
Probe result anchored to opencode 0.0.0-dev-202609102034 on 2026-09-12, separate from the pinned 1.17.14 July evidence. Documents the chosen upgrade contract for rjx18#30: persisted opencode thinking fails loudly and clears on manual Configure save.
Applying an opencode preset with stored thinking is refused with a named message, the preset editor blocks save, and a stale default roster fails channel creation. Document the switch-harness-back recovery and edit/remove path instead of implying a normal save clears it.
reconcileConfig clears model alongside thinking, so the documented preset recovery costs a model reselect.
Replacing a stale preset goes through the API without thinking; deletion requires removing it from the default roster first.
Restores the fixed thinking set (low/medium/high) with a bounded custom entry for exact per-model variant keys, detects the installed OpenCode line per adapter instance (v1 --variant, v2 --model provider/model#variant), and resolves the v2 default model per turn. Widens the protocol thinking value to enum | bounded custom string with normalization; strict adapters keep exact-list enforcement at the registry gate and at spawn/turn build. Refs rjx18#30
Contributor
Author
|
Scope-change notice: since the withdrawal commits, this PR changed direction from withdrawing OpenCode thinking to restoring it with v2 support. It now restores |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #30 (per-model validation from that issue is deliberately out of scope here).
Problem and result
This PR previously withdrew OpenCode thinking:
--variantnames areper-model presets, not a fixed effort scale, so a fixed low/medium/high
slider could not tell the operator whether the value would be used. This PR
now restores variant controls with custom values and v2 argv support: the
fixed set
low/medium/high, a bounded custom entry for exact per-modelvariant keys, and per-line argv. v1 keeps
--variant; v2 has no--variant,so the value rides the model as
provider/model#variant(observed againstopencode2 v0.0.0-dev-19272). Default still omits the variant. OpenCodecontrols the effective behavior, including its native configuration.
Scope note: this PR is three things, in this order — (1) a protocol
widening of the thinking value, (2) the OpenCode feature, (3) turn-path
revalidation in claude-code, codex, and grok as a consequence of (1).
Protocol change and where strictness lives now
ThinkingLevelwas a closed enum; it is now enum | bounded custom string.Three consequences a reviewer will notice in the diff:
starting agent, wire
ThinkingLevelSchema, and preset parsing — soCreateRoomRequestSchemanow parses{ harness: 'claude-code', thinking: 'ludicrous' }. Strictness moved fromthe wire schema to the registry gate plus the per-adapter spawn/turn
asserts, which is where the harness is known.
ThinkingLevelresolves tostring, soas const satisfies readonly ThinkingLevel[]in claude-code and codex nolonger proves anything at compile time, and
claudeThinkingOptionsneededan
as unknown ascast. Enforcement moved toKnownThinkingLevelSchemaat adapter registration: a declared
thinking_levelsentry outside thefixed set is refused.
at spawn and at turn build, so gate and adapters agree (covered by
registry and adapter tests for all four thinking adapters).
Normalization and validation
The custom UI field itself does not trim — its native pattern rejects all
whitespace before submit. Normalization below applies at the protocol
schema, the registry gate, the adapters, and the UI helpers
(
supportedThinking/reconcileConfig), though not in the input fielditself:
Protocol, registry, and spawn boundaries reject an explicit blank value.
recognized level (
" HIGH "becomeshigh).exact object key.
-,#, andnames over 64 characters.
Line detection (once per resolution key per adapter instance, LRU-bounded to 16)
Probe
<command> --version(fixed argv, no shell, 5 s timeout, cappedoutput, async;
listModelsanddiscoverSessionskeep their pre-existingsynchronous probes, neither on the turn path) in the session cwd with the
merged session environment:
opencode v2.0.3/opencode2 v0.0.0-dev-19272shape: v2.1.18.30/0.0.0-dev-202609102034bare-version shape: v1.opencode v3.0.0,falls back to
run --help:--variantwithout#variantis v1,#variantwithout--variantis v2, both or neither is unknown. (Anonzero
--versionresult returns unknown immediately.)Unknown fails the turn with
opencode line not recognizedplus a versiontoken or
(probe failed)— never raw probe output. Detection runs beforethe thinking value is read, so an unrecognized line fails even Default
turns with no thinking value: a broken probe breaks every turn on that
install, not only thinking turns. Fail-loud is deliberate — silently
running a future line's turns on guessed argv is worse — and unknown
results are never cached, so each unrecognized turn retries (up to two
5 s probes). The success cache keys on PATH (last case-insensitive match
on win32) plus cwd exactly when cwd can change resolution; Windows bare
commands always carry cwd (the launcher searches it first), so a Windows
daemon with more than 16 active directories re-probes past the LRU bound.
On the daemon path today, sessions carry only the five
CODOR_*environment variables (never
PATH), so PATH-based re-probing is acontract guarantee exercised by tests rather than by live daemon traffic.
v2 Default-model rule
v2 with the Default model and a thinking value resolves the default per
turn via
<command> api GET /api/model/defaultand sends--model <providerID>/<id>#<value>usingdata.id(the selectable catalogreference, not
data.modelID). Never persisted;session.modelstaysundefined; never cached across turns.
data: null, a failed call, orunparsable/invalid output fails the turn with a fixed explicit-model
message; the stored thinking value is preserved. v1 ignores an unknown
variant silently (exit 0); v2 fails the turn with
Variant unavailable for <model>through the existing failed-turn path.Codor does not validate a value against the selected model.
Malformed custom values, precisely
pattern+maxLengthblock submit natively (proven bye2e
patternMismatch).explicit named error before spawn; nothing is partially applied.
Well-formed but harness-unsupported values save, then fail: unsupported
presets fail during application, and direct spawn and configure requests
fail at the registry gate.
buildSpawnSpec) omits an unsupportedvalue, and the configure dialog serializes it as
null(clear). No pathsends a rejected value to a harness.
Harn intent (not edited —
.harn/untouched)harness-declares-supported-thinking-levels: the registry now enforcesthe declared list plus a
thinking_customflag, and the controls renderdeclared levels plus a custom entry. Strict adapters keep exact-list
rejections at the gate and at spawn/turn build.
individual-agent-presets-are-bounded-catalog-validated-configurations:the persisted thinking value is now enum | bounded custom string;
"bounded" is enforced by
ThinkingLevelSchemaafter normalization.Tests
pnpm -r testdoes not go green on macOS for pre-existing,unrelated reasons: copilot/gemini/grok assert
/var/...against/private/var/..., and 19 switchboard worktree/artifact/server tests failthe same way (plus load-sensitive 5 s timeouts under parallel runs — counts
vary run to run; use per-package totals). Web-next unit shows 734 pass /
17 fail across three localStorage-related files plus one brand-assets
timeout, also untouched by this diff. Room10's single axe
color-contrastfailure is intermittent and no branch-specific cause was found (it
alternates across dialogs and passes in isolation and on reruns).
The opencode spec fakes are POSIX-only (shebang scripts plus
chmod).This prevents package-test validation on Windows. Production Windows
behavior remains untested. The cross-cwd identity test is a regression
guard for the cache key, not a field fix.
CLI compatibility evidence (2026-09-13 probes, macOS)
Direct CLI invocations covering argv shapes only (no end-to-end Codor
adapter turns against real CLIs). v1, re-verified live on this machine via
pnpm dlx opencode-ai@1.18.30(banner1.18.30;run --helpdocuments--variantas "model variant (provider-specific reasoning effort, e.g.,high, max, minimal)" alongside
--model provider/model,--session,--format json,--auto; unknown--variantignored, exit 0).v2 (
@opencode/cli2.0.3, devopencode2 v0.0.0-dev-19272, re-verifiedlive via the installed CLI):
runhas no--variant;--model provider/model#variantaccepted;--variantrejected with usageVariant unavailable for <model>;GET /api/model/defaultreturns{location, data: {id, modelID, providerID, variants}}with nullabledata. These rows prove CLI shapes, not that a model applies a value.Known v2 gaps (planned follow-ups)
dbsubcommand;discoverSessionsreturns[]via its error path. Fix:
session list --format jsonper line with a cwd.step_finishobserved on v2, sorun.completedcarries nousage. Confirm on a paid model first.
modelsis cwd-scoped on v2;listModelsruns daemon-cwdand can return empty.
runis a client of an auto-started service; process-group
SIGINTinterrupt on the client is unverified.Not tested
End-to-end Codor adapter turns against real CLIs; a v2 turn with a
supported variant; a v2 turn with the resolved default plus a variant; v2
tool events; v2 interrupt; Windows; Linux.
Incidental notes
realpathSync(cwd)in the opencodeargv test for the same macOS
/varsymlink gemini/copilot/grok still carry.final_textfailure path on grok andopencode vs
erroron codex — inherited convention per adapter.