dsh-opencode-session-header
Per-conversation x-opencode-session header injection for OpenCode Go in DeepSeek Harness (DSH). Fixes 400 MissingSessionID, keeps prompt-cache routing optimal, scoped strictly to opencode.ai, runtime-switchable without restart.
一句话定位
Per-conversation x-opencode-session header injection for OpenCode Go in DeepSeek Harness (DSH). Fixes 400 MissingSessionID, keeps prompt-cache routing optimal, scoped strictly to opencode.ai, runtime-switchable without restart.
安装方式
在 DSH 的 web profile 中执行下面的命令;装完刷新页面即可。
$ dsh plugin --profile web add dsh-opencode-session-header@0.1.0
兼容的 DSH 版本
未声明
作者与热度
beihzb · ★ 16 · 更新于 2026-09-07
关于这个插件
dsh-opencode-session-header
Per-conversation x-opencode-session header injection for DeepSeek Harness (DSH) → OpenCode Go.
It fixes the 400 MissingSessionID rejection and keeps OpenCode's per-conversation routing / prompt-cache optimization working — scoped strictly to opencode.ai, runtime-switchable without restart, zero dependencies.
[中文文档](./README.zh-CN.md)
The problem
Since 2026-09-05, [OpenCode Go](https://opencode.ai/docs/go/) requires a stable x-opencode-session header on every inference request (used for routing and prompt caching). DeepSeek Harness ≤ 0.1.2-rc.1 sends no such header on any adapter path, so every opencode-go model call fails:
400: {"type":"MissingSessionID","message":"Error from provider (Console Go): Request is missing x-opencode-session ..."}
Upstream fix is tracked in [deepseek-harness discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495) but has not shipped yet. This plugin fills the gap locally.
Why not the common workarounds?
| | Static headers in settings.yaml | Global opencode_zen profile (dsh-custom-header) | This plugin |
|---|---|---|---|
| Fixes the 400 | ✅ | ✅ | ✅ |
| Per-conversation id (cache / routing optimal) | ❌ one shared id → cache misses, slower & pricier | ✅ | ✅ real DSH session id |
| Other providers untouched | ✅ | ❌ rewrites UA + x-opencode-* on every host | ✅ opencode.ai only |
| Toggle without restart | — | — | ✅ JSON file flip |
| Extra dependencies | — | third-party plugin, client bundle | none |
Install
dsh plugin --profile web add dsh-opencode-session-header
or from a local clone / checkout:
dsh plugin --profile web add "file:C:\path\to\dsh-opencode-session-header"
Then restart dsh web once — plugins load at boot. The startup log should show:
[dsh-opencode-session-header] loaded: header=x-opencode-session hosts=opencode.ai fallback=dsh-default
[dsh-opencode-session-header] runtime switch: \plugins\dsh-opencode-session-header.json ({"enabled":false} disables; missing file = enabled)
Runtime switch (no restart needed)
State file: /plugins/dsh-opencode-session-header.json (defaults to ~/.dsh):
{ "enabled": false }
false→ injection off (all requests pass through untouched);trueor file missing → injection on;- the file is re-read on every LLM request, so toggling takes effect immediately.
How it works
Two seams, both proven inside DSH 0.1.2-rc.1:
1. llm/stream waterfall observer — wraps every adapter stream iteration in an AsyncLocalStorage carrying GenerateOptions.sessionId (stable per DSH conversation across turns, compaction and retries; fresh per conversation / fork / subagent).
2. Fetch transport middleware — stamps x-opencode-session only when the request host matches the allowlist (default: opencode.ai + subdomains). Value = the current session id, or the fallback id (dsh-default) outside any LLM context (e.g. model discovery). Every other host passes through byte-for-byte untouched.
The fetch pipeline runs under this plugin's own Symbol.for key (mechanics vendored from [@aizigao/pi-fetch-pipeline](https://github.com/aizigao/pi-fetch-pipeline), MIT), so it coexists with other fetch-patching plugins instead of clobbering them. Header merging follows the fetch spec: whichever of init.headers / Request.headers would reach the wire is used as the merge base.
Testing
npm test # 13 assertions: injection, fallback, allowlist, runtime switch,
# concurrent-session isolation, llm/stream propagation, subdomains
Compatibility & retirement
- Built and verified against DSH
0.1.2-rc.1(npm latest at release time). - Depends on DSH outbound LLM traffic using the process-global
fetch. If a future DSH build switches its network stack, the plugin silently stops injecting — the symptom is simply the 400 returning; uninstall then. - Once upstream ships native per-conversation session headers ([discussion #5495](https://github.com/deepseek-ai/deepseek-harness/discussions/5495)), retire this plugin:
dsh plugin --profile web remove dsh-opencode-session-header
then restart dsh web (optionally delete the switch file).
Credits
- Fetch-pipeline mechanics vendored from [
@aizigao/pi-fetch-pipeline](https://github.com/aizigao/pi-fetch-pipeline) (MIT); the fetch-layer approach on DSH was proven by [dsh-custom-header](https://github.com/Asaiuta/dsh-custom-header) (MIT) by [Asaiuta](https://github.com/Asaiuta).
License
[MIT](./LICENSE)
同分类对比
同一分类下 Star 最高的 5 个,便于横向比较。
| 插件 | 作者 | Star | 版本 | 更新 |
|---|---|---|---|---|
| dsh-purge | kingcharleslzy-ai | 6.3K | 1.6.86 | 2026-11-08 |
| dsh-unrestricted | yexi-by | 1.1K | 0.2.8 | 2026-11-05 |
| dsh-univer-office | dream-num | 707 | 0.4.0 | 2026-11-03 |
| dsh-synapse | liangmianya | 697 | 0.5.7 | 2026-11-04 |
| dsh-qqbot | tencent-connect | 570 | 0.5.5 | 2026-11-08 |