Repository navigation
feat(opencode): every OpenCode request now carries x-opencode-session for backend affinity (#81584) - #101864
Conversation
… backend affinity 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).
૮ >ﻌ< ა ci reviewran on c9e2075 — feat(opencode): send x-opencode-session on every OpenCode re
|
Supersedes #81591's narrower OpenCode Go-only repair: this implementation covers all OpenCode target detection, every main transport, and auxiliary calls. |
|
can there be a point release for this? i've never updated to a random commit before. i got an email i have to send this header by September 6th or I'll experience errors. |
Running hermes update will update to latest commit so it will include this. You can also ask hermes to update itself. |
yeah i went ahead and updated to this commit. it wasnt that hard but i'll go back to a versioned release when possible. |
|
this wont work with docker / container deployments a new image release is needed
|
… backend affinity Port of upstream PR NousResearch#101864 (1393969). OpenCode Go now 400s requests that omit x-opencode-session; the header also pins a conversation to one backend so prompt cache stays warm. - agent/opencode_affinity.py: single owner of target detection and the key - build_api_kwargs: merge once after the per-mode builder so every transport carries it - auxiliary _build_call_kwargs: same key from the runtime-main session; Codex/Anthropic aux adapters now forward extra_headers A full upstream merge is 5514 commits behind; this ports the isolated fix.
The Zen relay's free tier (https://opencode.ai/zen/v1, keyless) answers HTTP 429 FreeUsageLimitError to Hermes' canonical attribution headers (User-Agent: HermesAgent/<v> + the hermes-agent referer/title) while the same model returns 200 to the OpenCode client fingerprint. NousResearch#101864's x-opencode-session fixed the MissingSessionID 400 and stands; only the client default_headers identity was never switched. - hermes_cli/opencode_client_headers.py: stdlib-only leaf module owning the fingerprint (UA opencode/0.20.5, referer https://opencode.ai/, title opencode, opaque X-Session-ID), shared by the runtime header set and the provider profile so the two copies cannot drift. - hermes_cli/models.py: opencode_zen_free_headers() returns the fingerprint plus the empty Authorization that keeps the keyless placeholder off the wire. - plugins/model-providers/opencode-free: the profile's default_headers read that same module instead of duplicating the Hermes values. - agent/agent_runtime_helpers.py: key the header set off the keyless placeholder, not the provider name, so a free slug picked under opencode-zen/go (healed onto the same keyless Zen route) gets it too. Keyed Zen/Go keep the Hermes attribution and never send X-Session-ID; x-opencode-session still rides on every OpenCode request, so both header policies coexist. Tests: wire-level mock guard tests/run_agent/test_opencode_zen_free_wire_headers.py (red on main, green after the fix, bites under sabotage) plus the updated free-tier header assertions.
…t of upstream PR NousResearch#101864) opencode-go returns HTTP 400 MissingSessionID on real multi-message conversations without the header. Ported: - agent/opencode_affinity.py (new): header derivation per conversation - chat_completion_helpers.build_api_kwargs: wrap mode builder, merge header - auxiliary_client: forward extra_headers on Codex + Anthropic aux adapters E2E: 4-msg probe without header -> 400; with header -> 200.
…ackport of upstream PR NousResearch#101864; upstream main already contains its own evolved copy)
Summary
Every request Hermes sends to OpenCode (Zen / Go / Free, and custom providers pointing at opencode.ai) now carries an opaque, per-conversation
x-opencode-sessionheader, on every transport and on auxiliary calls too. OpenCode uses it to pin a conversation to one backend so its prompt cache stays warm; without it, cache ratios on Hermes traffic were poor (reported directly by the OpenCode team) and some Go backends 400'd outright (#81584, #81832).Changes
agent/opencode_affinity.py(new, ~80 LOC): single owner of target detection (opencode_provider_family+ opencode.ai host) and the key. Key resolution matches the OpenRouter stickysession_id/ xAIx-grok-conv-idhints: declared affinity scope → conversation root → physicalsession_id, cron per-fire timestamp stripped.agent/chat_completion_helpers.py:build_api_kwargsmerges the header once after the per-mode builder →chat_completions,codex_responses,anthropic_messagesall covered with one site.agent/auxiliary_client.py:_build_call_kwargsadds the same key from the runtime-main session (compression / titles / vision / MoA stay on the conversation's backend); the aux Codex and Anthropic adapters now forwardextra_headers(previously dropped on the floor).website/docs/integrations/providers.md: one paragraph documenting the header.tests/agent/test_opencode_session_affinity.py).Validation
glm-5)x-opencode-session: <session>gpt-5.6-luna)minimax-m2.7)_build_call_kwargs(compression etc.)POST https://opencode.ai/zen/v1/chat/completionscarriedx-opencode-session: sess-live-77, 200 OKTargeted suites: 242 + 305 passed (opencode profile/free/headers, aux client, codex/anthropic transports, prompt caching, attribution headers). ruff clean.
Supersedes #81591 (@chelsealong) — that PR covered opencode-go only and re-derived the key in three places; this generalizes to all OpenCode providers + aux path with one owner. Credit to chelsealong for the original diagnosis.
Closes #81584
Closes #81832
Infographic