fix(opencode): send ephemeral x-opencode-session header on one-shot requests

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
This commit is contained in:
ericmaddox
2026-09-09 21:24:27 -04:00
committed by Teknium
parent f7f000de99
commit 6ca41cbe8b
2 changed files with 22 additions and 1 deletions

View File

@@ -18,6 +18,7 @@ matching per-model wire-format decision for the auxiliary client.
from __future__ import annotations
import uuid
from typing import Any, Optional
OPENCODE_SESSION_HEADER = "x-opencode-session"
@@ -100,7 +101,13 @@ def opencode_session_headers(
)
except Exception:
key = str(session_id or "")
return {OPENCODE_SESSION_HEADER: key} if key else {}
if not key:
# Stateless one-shot requests (commit messages, summaries, standalone prompts outside
# a session) lack an ambient conversation or session id. OpenCode Go strictly requires
# x-opencode-session on every request (HTTP 400 MissingSessionID if absent, #105841)
# so generate an ephemeral session id fallback.
key = f"oneshot-{uuid.uuid4().hex[:16]}"
return {OPENCODE_SESSION_HEADER: key}
def merge_opencode_session_headers(

View File

@@ -147,3 +147,17 @@ def test_tui_gateway_oneshot_runtime_snapshot_carries_the_session(monkeypatch, o
aux.call_llm(task="title_generation", main_runtime=_main_runtime_from_agent(agent), messages=_MSGS)
assert captured["extra_headers"]["x-opencode-session"] == "sess-desktop-1"
def test_stateless_oneshot_still_sends_an_opencode_session_header(out_of_turn):
"""A one-shot with no live session (Desktop commit-message generation from the review panel with
no active chat, standalone aux calls) has no conversation identity at all, yet the relay rejects
header-less requests with 400 MissingSessionID (#105841). It must carry an ephemeral key instead
of nothing; non-OpenCode targets stay untouched."""
from agent.opencode_affinity import opencode_session_headers
kwargs = aux._build_call_kwargs("opencode-go", "glm-5", _MSGS, base_url="https://opencode.ai/zen/go/v1")
assert kwargs["extra_headers"]["x-opencode-session"]
assert opencode_session_headers("opencode-go", None, session_id=None).get("x-opencode-session")
assert opencode_session_headers("openrouter", "https://openrouter.ai/api/v1", session_id=None) == {}