fix(opencode-go): send x-opencode-session header for session affinity - #81591
fix(opencode-go): send x-opencode-session header for session affinity#81591chelsealong wants to merge 2 commits into
Conversation
OpenCode Go's chat_completions relay 400s "Model is unavailable" for some backends (e.g. deepseek-v4-flash) unless a stable per-conversation session-affinity header is present. Add x-opencode-session, resolved the same way OpenRouter's x-grok-conv-id already is (ambient conversation context first, explicit session_id fallback), so the value stays stable across turns, rotates with the conversation, and never leaks to other providers. Fixes NousResearch#81584
|
Thanks for this fix! I applied it locally (cherry-picked both files into the provider profile) and can confirm it resolves the issue end-to-end: deepseek-v4-flash via opencode-go now works reliably, where previously every chat/completions request returned HTTP 400 "Model is unavailable" without the x-opencode-session header. Verified with the new session-affinity tests plus the broader opencode-go suites (24 + 51 passed, no regressions), and in live desktop usage. Looking forward to seeing this merged into main! |
|
This was generated by AI during triage. Summary: Problems:
Solution: Checked against |
…_responses too build_api_kwargs_extras (where the header was added) is only consulted by the chat_completions transport, so MiniMax/Qwen models on OpenCode Go - which route through anthropic_messages - and any future codex_responses routing still went out with no session-affinity header. Merge it in build_api_kwargs for both routes the same way Nous Portal's tags/session_id are merged onto the Messages wire.
|
Good catch — pushed 26654cc: |
…esearch#25437) Followup to PR NousResearch#24182 — caught when scanning OpenClaw for recent codex fixes we hadn't considered. OpenClaw learned the hard way (NousResearch#80815) that migrating plugins which codex itself reports as unavailable produces config that fails at activation time. Our /codex-runtime codex_app_server enable path queries codex's plugin/list and migrates everything where installed=true. We were trusting codex's installation state and ignoring its availability field. So a plugin that's installed=true but availability=UNAVAILABLE (broken local install) or REQUIRES_AUTH (OAuth expired or never completed) would get an [plugins."<n>@openai-curated"] entry in ~/.codex/config.toml — and the user's first codex turn after enabling the runtime would fail because codex refuses to activate it. Fix: filter on availability in _query_codex_plugins(). Only emit plugins where availability is empty (older codex versions without the field — preserve backward compat) or explicitly AVAILABLE. Tests: test_plugin_discovery_skips_unavailable_plugins — verifies 4 cases: - good-plugin (installed=True, availability=AVAILABLE) → migrated - broken-plugin (installed=True, availability=UNAVAILABLE) → skipped - auth-pending (installed=True, availability=REQUIRES_AUTH) → skipped - legacy-plugin (installed=True, no availability field) → migrated (older codex versions; preserve backward compat) Docs: Added bullet to 'What's NOT migrated' list in the docs page calling out the availability filter and why. Other OpenClaw codex PRs I reviewed but did NOT apply (with reasoning): - NousResearch#81591 (load Codex for selectable models): we resolve runtime per-call already, no startup-time gating to fix - NousResearch#81510 (cron compatibility): we documented cron as untested; their fix is for OpenClaw-specific cron orchestration shape - NousResearch#81223 (rotate incompatible context-engine threads): we don't have a Lossless context engine equivalent - NousResearch#80688 (constrain sandbox): we don't have an outer-sandbox concept - NousResearch#80616 (release on turn_aborted): we already handle status= interrupted in turn/completed correctly - NousResearch#80278 (expose activeModel in plugin SDK): not our surface - NousResearch#80792 (default destructive_actions on): we don't expose that knob 56 codex-runtime migration tests still green (+1 new).
…esearch#25437) Followup to PR NousResearch#24182 — caught when scanning OpenClaw for recent codex fixes we hadn't considered. OpenClaw learned the hard way (NousResearch#80815) that migrating plugins which codex itself reports as unavailable produces config that fails at activation time. Our /codex-runtime codex_app_server enable path queries codex's plugin/list and migrates everything where installed=true. We were trusting codex's installation state and ignoring its availability field. So a plugin that's installed=true but availability=UNAVAILABLE (broken local install) or REQUIRES_AUTH (OAuth expired or never completed) would get an [plugins."<n>@openai-curated"] entry in ~/.codex/config.toml — and the user's first codex turn after enabling the runtime would fail because codex refuses to activate it. Fix: filter on availability in _query_codex_plugins(). Only emit plugins where availability is empty (older codex versions without the field — preserve backward compat) or explicitly AVAILABLE. Tests: test_plugin_discovery_skips_unavailable_plugins — verifies 4 cases: - good-plugin (installed=True, availability=AVAILABLE) → migrated - broken-plugin (installed=True, availability=UNAVAILABLE) → skipped - auth-pending (installed=True, availability=REQUIRES_AUTH) → skipped - legacy-plugin (installed=True, no availability field) → migrated (older codex versions; preserve backward compat) Docs: Added bullet to 'What's NOT migrated' list in the docs page calling out the availability filter and why. Other OpenClaw codex PRs I reviewed but did NOT apply (with reasoning): - NousResearch#81591 (load Codex for selectable models): we resolve runtime per-call already, no startup-time gating to fix - NousResearch#81510 (cron compatibility): we documented cron as untested; their fix is for OpenClaw-specific cron orchestration shape - NousResearch#81223 (rotate incompatible context-engine threads): we don't have a Lossless context engine equivalent - NousResearch#80688 (constrain sandbox): we don't have an outer-sandbox concept - NousResearch#80616 (release on turn_aborted): we already handle status= interrupted in turn/completed correctly - NousResearch#80278 (expose activeModel in plugin SDK): not our surface - NousResearch#80792 (default destructive_actions on): we don't expose that knob 56 codex-runtime migration tests still green (+1 new).
What does this PR do?
OpenCode Go's
chat_completionsrelay returns HTTP 400Model is unavailableforsome backends (confirmed for
deepseek-v4-flash) unless the request carries astable, opaque
x-opencode-sessionheader. Hermes'opencode-goprovider profilenever sent that header, so the very first request to an affected model fails.
This adds
x-opencode-sessiontoOpenCodeGoProfile.build_api_kwargs_extras,resolved with the same pattern already used for OpenRouter's
x-grok-conv-idsticky-routing header: the ambient conversation context first, falling back to
the explicit
session_id, normalized through the existing_cache_scope_from_session_idhelper. That keeps the value:session-lineage root, same as the OpenRouter/xAI header),
opencode-goprofile'sown hook, so it can't leak into other providers' requests.
This does not send any official-OpenCode-CLI impersonation headers
(
User-Agent: opencode-cli,x-opencode-client: cli) — only the honest,non-impersonating session-affinity header, matching the approach the issue
explicitly asks for.
Related Issue
Fixes #81584
Fixes #81832
Type of Change
Changes Made
plugins/model-providers/opencode-zen/__init__.py:OpenCodeGoProfile.build_api_kwargs_extrasnow attaches
extra_headers["x-opencode-session"]whenever a conversation/sessionid is available, on top of the existing reasoning-control logic (moved unchanged
into a private
_reasoning_api_kwargs_extrashelper).tests/plugins/model_providers/test_opencode_go_profile.py: addedTestOpenCodeGoSessionAffinityHeadercovering header presence with asession_id, cron-timestamp normalization/stability (mirrors the existingOpenRouter grok-header test), header omission with no session context, and
coexistence with the existing reasoning
top_levelkwargs.How to Test
python -m pytest tests/plugins/model_providers/test_opencode_go_profile.py -qgit checkout HEAD~1 -- plugins/model-providers/opencode-zen/__init__.py,rerun the new tests (they fail with
KeyError: 'extra_headers'), thengit checkout HEAD -- plugins/model-providers/opencode-zen/__init__.pyto restore the fix.Actual output
Without the fix (source reverted, tests kept):
With the fix:
Also ran the broader related suites with no regressions:
ruff checkon both changed files: all checks passed.Checklist
fix(opencode-go):)pytest tests/ -q(relevant subset — see above) and all tests passDocumentation & Housekeeping
AI assistance disclosure
This PR was prepared with AI assistance (Claude, via an autonomous coding agent),
reviewed and tested before submission.