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