Skip to content

feat(opencode): inject x-opencode-session header for prompt caching - #12615

Closed
pspranger-throw wants to merge 1 commit into
NousResearch:mainfrom
pspranger-throw:feature/opencode-session-header
Closed

feat(opencode): inject x-opencode-session header for prompt caching#12615
pspranger-throw wants to merge 1 commit into
NousResearch:mainfrom
pspranger-throw:feature/opencode-session-header

Conversation

@pspranger-throw

@pspranger-throw pspranger-throw commented Apr 19, 2026

Copy link
Copy Markdown

Send the x-opencode-session header on every API request to OpenCode Go/Zen providers so the proxy can cache the prompt prefix across requests in the same conversation.

The header is injected per-request in _build_api_kwargs() across all three API modes:

  • chat_completions (opencode-go default, opencode-zen default)
  • anthropic_messages (opencode-go minimax-*, opencode-zen claude-*)
  • codex_responses (opencode-zen gpt-*)

Includes 9 tests covering all three paths, negative cases (no header leakage to other providers), and state mutation (model switch, session rotation after compression).

What does this PR do?

Adds a header to all opencode zen/go api calls to ensure that opencode can track sessions for context caching, otherwise each call pays for the full uncached context!

Solution orients itself after the existing x-grok-conv-id implementation.

Related Issue

Related to #5252. This is a minimal fix for OpenCode Go and Zen while the broader routing table architecture from #5252 is developed.

This PR takes a minimal, targeted approach: direct conditionals in
_build_api_kwargs() for the OpenCode provider only. When the
data-driven routing table from #5252 lands, the OpenCode entries
can be migrated into it.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

**`run_agent.py` -- `_build_api_kwargs()` (3 injection points)**

- **`chat_completions` path** (~line 7168): Inject `x-opencode-session` header into `api_kwargs["extra_headers"]` when `"opencode.ai"` is detected in `self._base_url_lower` and `self.session_id` is set.

- **`anthropic_messages` path** (~line 6860): Capture the return value of `build_anthropic_kwargs()` into a local `kwargs` variable (previously returned directly), then inject the header before returning. This route is used by `opencode-go` with `minimax-*` models and `opencode-zen` with `claude-*` models.

- **`codex_responses` path** (~line 6957): Inject the header alongside the existing `x-grok-conv-id` injection for xAI. This route is used by `opencode-zen` with `gpt-*` models.

The header is injected per-request, reading `self.session_id` and `self._base_url_lower` fresh on every API call. This ensures it automatically follows runtime state changes from model switches (`/model`) and context compression (which rotates `session_id`).

Defensive attribute access: Uses getattr(self, "_base_url_lower", "") rather than
self._base_url_lower because some code paths (notably tests using object.__new__()
to bypass __init__) reach _build_api_kwargs without the setter having run. Returns
"" which safely skips the opencode check — correct behavior for non-opencode agents.

**`tests/run_agent/test_run_agent.py` -- `TestOpencodeSessionHeader` (9 tests)**

- 3 positive tests: header present with correct value for `chat_completions`, `anthropic_messages`, and `codex_responses` API modes
- 3 negative tests: no header when session_id is None, no header for non-OpenCode providers (OpenRouter, Anthropic, OpenAI), no header leakage across API modes
- 2 state mutation tests: header follows `base_url` changes (model switch scenario) and `session_id` changes (context compression scenario)
- 1 edge case: header uses child session_id after compression, not stale parent

How to Test

  1. Used my opencode-go subscription, verified that sessions now cache properly on my dashboard (actual entries in session columns and pricing staying low with growing context)
  2. Went through a compression and verified new session ID after compression as base for new caching
  3. Cannot test zen provider though -> only have opencode go

Checklist

Code

Documentation & Housekeeping

  • N/A - I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • N/A - I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • N/A - I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • N/A - I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • N/A - I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

Send the x-opencode-session header on every API request to OpenCode
Go/Zen providers so the proxy can cache the prompt prefix across
requests in the same conversation.

The header is injected per-request in _build_api_kwargs() across all
three API modes:
  - chat_completions (opencode-go default, opencode-zen default)
  - anthropic_messages (opencode-go minimax-*, opencode-zen claude-*)
  - codex_responses (opencode-zen gpt-*)

Includes 9 tests covering all three paths, negative cases (no header
leakage to other providers), and state mutation (model switch, session
rotation after compression).
@pspranger-throw

Copy link
Copy Markdown
Author

@kovyrin @jbarket @chalkers @leon

Requesting review please or adding of other reviewers please, since I cannot add reviewers myself from outside fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants