OpenCode Go strictly requires an x-opencode-session header on all requests to route requests efficiently and avoid HTTP 400 MissingSessionID. In turn chats and parented auxiliary calls, the header is derived from the conversation context or session ID. For stateless one-shot requests (commit messages, summaries, unparented auxiliary tasks), opencode_session_headers now falls back to generating an ephemeral session ID so one-shot requests to OpenCode succeed.
Closes#105841
OpenCode's free tier now returns HTTP 403 for anonymous traffic outside
the OpenCode client, so the built-in keyless provider is dead weight:
- drop the opencode-free provider row, aliases (free/opencode_free), model
catalog, keyless runtime ladder rung, header wiring, and cached slugs
- delete the model-providers/opencode-free plugin
- update tests and the compat manifest for the removed symbols
- keep a migration hint in auth.py so users who had it configured see a
clear error naming the removal
Existing opencode-free configs can move to opencode-zen (pay-as-you-go)
or opencode-go (flat subscription).
Review follow-up on #113189. `_main_runtime_from_agent` (the Desktop/TUI-gateway
`llm.oneshot` session-bound path, task defaults to title_generation) built its explicit
main_runtime without `session_id`, so on an OpenCode route the title request still sent
no `x-opencode-session` (MissingSessionID -> paid main-model fallback) — the exact symptom
this PR fixes on /btw, the turn_context titler and `_current_main_runtime`. Add the field;
it is str-valued so the existing strip() branch keeps it.
Also fixes the red CI run 35127963318: `test_feasibility_check_passes_live_main_runtime`
asserts the exact `_current_main_runtime` dict, which this PR extended with `session_id`.
Follow-up on the salvaged #112721 commits (@fangliquanflq):
- agent/turn_context.py, tui_gateway/methods_prompt.py: add ``session_id`` to the explicit
snapshot dicts instead of switching them to ``agent._current_main_runtime()``. The titling
prologue is duck-typed (its tests drive a minimal stub) and the snapshot values feed the
``runtime_validator`` equality checks, where ``_current_main_runtime()``'s ``"" `` for a
missing attribute would no longer match the live ``None``. Same outcome — the background
request inherits the conversation's ``x-opencode-session`` — without changing what the
callers read.
- tests/agent/test_opencode_session_affinity.py: the salvaged title test passed on an
unfixed tree because the titler thread republishes the conversation contextvar
(``set_conversation_context``) and the affinity header falls back to it. Replace it with
two invariant tests (sync + async ``call_llm(main_runtime=...)`` with EVERY ambient source
unset via the ``out_of_turn`` fixture): red on origin/main, green here; both also pin
that the explicit binding does not leak past the call.
- website/docs/integrations/providers.md: name the background/out-of-turn auxiliary calls
the header now covers.
Fixes#112717
The OpenCode Zen relay no longer serves hy3-free (since ~2026-08-31) or
laguna-s-2.1-free (new, verified 2026-09-09): both are gone from the live
GET /zen/v1/models catalog and anonymous chat completions return
401 {"type":"ModelError","message":"Model <id> is not supported"}
(2 probes >=60s apart, x-opencode-session header present).
- hermes_cli/models_catalog_static.py: remove both slugs from the
opencode-free offline floor and the opencode-zen discovery floor;
document the delist dates in the catalog comment.
- plugins/model-providers/opencode-free: default_aux_model moves from the
dead laguna-s-2.1-free to nemotron-3.5-lightning-free (fastest surviving
anonymous model).
- tests: swap fixtures off the dead slugs; extend the floor-exclusion
invariant to cover both.
The live revalidation path already hides them when the relay is reachable;
this fixes the OFFLINE floor and the aux default, which would otherwise
offer/route to models that 401.
OpenCode pins requests sharing an x-opencode-session value to one upstream
backend, which is what keeps its prompt cache warm across a conversation.
Hermes never sent it, so cache ratios on OpenCode traffic were poor.
- agent/opencode_affinity.py: single owner of the header — target detection
(built-in zen/go/free, custom opencode-* providers, any opencode.ai URL)
and the key (affinity scope → conversation root → session id, cron
timestamp stripped), same resolution as OpenRouter/xAI affinity hints.
- build_api_kwargs: merged once after the per-mode builder, so
chat_completions, codex_responses and anthropic_messages all carry it.
- auxiliary _build_call_kwargs: same key from the runtime-main session so
compression/title/vision calls stay on the conversation's backend; the
aux Codex and Anthropic adapters now forward extra_headers.
Closes#81584, #81832 (deepseek-v4-flash 400 without the header).