Repository navigation
design opencode config
Issue #62. The #60
pattern applied to opencode: the runtime-config form is target-aware —
the provider row keeps ONLY the connection (provider → baseURL +
credential), and an opencode-native panel owns everything else, stored on
a typed additive spec.opencode. Ground truth: research §3.3 (opencode
1.18.31 schema, rig-verified) + the official config docs; UI reference:
cc-switch's opencode form.
opencode: {
models?: [{ id, name?, contextWindow?, maxOutputTokens?,
reasoning?, temperatureEnabled?, vision?, options? }], // ≤17
smallModel?: string, // bare id; the writer prefixes harness-nexus/
autoupdateDisabled?: boolean,
}Core mirrors it as OpencodeConfigData on RuntimeConfigSpecData; the
service carries it through the upsert row and the GET view (the #60
lesson — the field-by-field row rebuild drops unknown fields, so both
ends were wired up front).
Three sections — 模型 / 模型详情 / 行为:
- 模型: default model (registry select when the picked provider has chat models, free text otherwise) + the 可选附加模型 allowlist rows — the generic form's rows, moved in (#60's migration).
-
模型详情: one COLLAPSIBLE bordered group per switchable model
(mono-id header + chevron; labeled fields inside at every width — the
#60 mobile lesson applied from the start):
- 显示名 →
name - 上下文窗口 →
limit.context(informational: context-remaining readout + auto-compaction) · 输出上限 →limit.output(a REAL request cap; opencode clamps globally to 32,000 — hinted) - capabilities →
reasoning,temperature(BOOLEAN GATE — only when true does opencode send an agent's temperature value; the value itself is per-agent and out of scope),attachment(vision) - 请求选项 →
optionskey-value rows, the #56 editing idiom (known boolean keys ride a Switch, others raw JSON parsed at the apply boundary); passthrough exists ONLY on the openai-compatible wire — hinted
- 显示名 →
-
行为: 后台小模型 → top-level
small_model(opencode's background model for lightweight tasks like title generation — the analog of claude-code's haiku pin; picked from the managed set, falls back to the main model when unset) · 禁用自动升级 →autoupdate: false(docs caveat: may not apply to package-manager installs incl. npm — our channel; hinted).
Draft state + prefill/serialize/validate live in the panel module
(OcDraft, ocDraftFromSpec, serializeOcDraft, ocIssue); the apply
button stays disabled while ocIssue reports a problem. serializeOcDraft
drops a detail group with no knob set, drops smallModel when its id left
the switchable set (the writer would point opencode at an unlisted
route), and serializes to undefined for an all-default draft (untouched
rows keep their pre-#62 shape).
Per-field precedence in the provider block's models map:
panel value > #51 modelEntries seed > conservative fallback
(8192/128000) — limit.* are REQUIRED by opencode's config schema, so an
unset field FALLS BACK instead of deleting a key (the deliberate contrast
with claude-code's unset-REMOVES-ours semantics; the bridge stays as
fallback, so pre-#62 specs behave identically). Capability keys write
only their true arm; options writes when non-empty.
Top-level small_model (harness-nexus/<id>) and autoupdate: false
are BASE_URL-style owned: configured → write, cleared → remove ours. The
provider block, /v1-normalized baseURL, {file:…} key reference and
the raw 0600 key file are unchanged from W12.
- agent-level
temperature/top_pVALUES — per-agent concept, we manage no opencode agents (the panel only opens the entry-level gate) -
default_agent, provider-levelheaders/extra options - models.dev metadata auto-fill (possible follow-up, could ride #51 query-models)
-
OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAXunclamping, the responses wire
packages/cli/test/runtime-config.test.ts (panel-wins-per-field over the
seed, capabilities/options writes, small_model/autoupdate ownership,
idempotency, knob-less revert with fallback chain) and
packages/shared/test/runtime-config.test.ts (schema bounds).