dsh-session-header
English | 中文
DeepSeek Harness 插件:给 harness 发出的每一个 LLM 请求注入 x-session-id header,值为发起该次调用的 harness session id。
为什么需要它
harness 没有请求级 header 缝——GenerateOptions 没有 headers 字段,每个 adapter 都在自己内部构造线上 header。如果你的模型网关(或中间代理)按 session header 做路由、缓存或审计,harness 自身发不出这个 header。
本插件把两个官方拦截点组合起来补上这个缺口:
llm/streamwaterfall 标记出哪些调用是 LLM 调用,并携带options.sessionId;globalThis.fetch补丁 注入 header,所有基于 fetch 的 adapter(llm-deepseek、llm-pi-ai,以及任何传输层最终落在全局 fetch 上的 SDK)都被覆盖,无需改 adapter 代码。
上下文传播用 AsyncLocalStorage:只有发生在某次 LLM 调用流内部的 fetch 会被触碰,无关 fetch(web RPC、遥测、工具流量)原样通过。别人已设置的 header 绝不覆盖(按 HTTP 语义大小写不敏感)。插件卸载时还原原始 fetch。
取值语义:
- 默认:取当前调用
GenerateOptions.sessionId,并剥掉 harness 的session-品牌前缀(发送纯 UUID)——主会话各轮次、压缩/起标题辅助调用、in-process 子 agent 各自上报自己的 session id(子 agent 拥有独立的 child session id); - 配置
value:所有调用使用固定值(按配置原样发送,不剥前缀); - 两者皆无的调用不发这个 header。
安装
需要 dsh CLI 与 Node ≥ 22。
作为 bundle 安装(推荐)
dsh plugin --profile <name> add github:EmotionTowel/dsh-session-header
本包是纯 JavaScript、无构建脚本,不需要 pnpm ≥ 10 的构建授权。验证层并启动:
dsh --profile <name> --dump-config # 应能看到 "# == dsh-session-header" 层
dsh --profile <name>
本地 checkout 用 --patch 覆盖层加载
# my-overlay.yml —— 此处插件行需要绝对模块路径
- insert:
- id: session-header
name: /absolute/path/to/dsh-session-header/index.js
config:
header: x-session-id
# value: my-fixed-session-id
dsh --patch ./my-overlay.yml
配置
| 字段 | 类型 | 默认 | 含义 |
|---|---|---|---|
header |
string | x-session-id |
注入的 header 名;线上大小写不敏感 |
value |
string | — | 固定值;不设 = 取当前调用的 harness session id |
toolEndpoints |
string[] | [] |
工具执行期匹配的 URL 前缀。非空时,tools/execute 瀑布内的 fetch(例如工具里调用的网关 web-search Messages API)仅当 URL 以某前缀开头才注入 header——第三方工具目标(web_fetch 抓任意网页、GitHub、MCP 服务器等)不受影响。默认空 = 保持原有仅 LLM 注入行为 |
overwriteHeaders |
string[] | [] |
允许本插件覆盖已有值的 header 名列表;未列出的头仍遵守"不覆盖"规则。某些官方 provider 会硬编码占位值(如 dsh-web-search-deepseek 发送 x-opencode-session: dsh-web-search),网关会当作缺失拒绝;把该头列入(overwriteHeaders: [x-opencode-session])即可用真实的会话 id 替换占位值。大小写不敏感。 |
适配 OpenCode Go 网关
OpenCode Go 是 $10/月的订阅网关,其文档要求每个请求带稳定的 session header(x-opencode-session);缺失的请求会被路由层直接拒绝:
400 {"type":"MissingSessionID","message":"... Request is missing x-opencode-session ..."}
该页的 "Known Problematic Clients" 一节也点名了 DeepSeek Harness(session 信息在部分模型路径上缺失)。按下面三步配置即可完整适配,不需要再查 OpenCode Go 文档。
1. 会话请求(LLM)
把注入的 header 名改成网关要求的那个:
# ~/.dsh/profiles/<name>/cordis.patch.yml
- id: session-header
config:
header: x-opencode-session
toolEndpoints:
- https://opencode.ai/zen/go/v1
2. web-search(工具执行期的网关 fetch)
Go 的 web-search 走 Anthropic Messages 端点,那次 fetch 发生在工具执行期、不在 LLM 调用流里,所以只有靠 toolEndpoints 才能把它纳入注入范围;再加 overwriteHeaders,以防某个组件硬编码占位值(网关会把占位值当作缺失而拒绝):
- id: session-header
config:
header: x-opencode-session
toolEndpoints:
- https://opencode.ai/zen/go/v1
overwriteHeaders:
- x-opencode-session
toolEndpoints只匹配 URL 前缀,因此 web_fetch 抓任意网页、GitHub、MCP 服务器等第三方目标不受影响。
3. headless
每个 profile 的插件是独立的:装进 web 不会让 headless 也有;而 dsh --profile headless "..." 缺 header 会被网关以同样的 400 拒绝。要用 Go 网关跑 headless,就得给它也装一遍:
dsh plugin --profile headless add github:EmotionTowel/dsh-session-header
# ~/.dsh/profiles/headless/cordis.patch.yml
- id: session-header
config:
header: x-opencode-session
toolEndpoints:
- https://opencode.ai/zen/go/v1
验证(exit=0 且模型正常回答即为通过):
dsh --profile headless "运行 pwsh 命令 Get-Random -Maximum 1000000,只回复该数字"
不需要本插件的场景
直连 DeepSeek 官方 API(api.deepseek.com)时不需要本节任何配置——llm-deepseek adapter 自身每次请求已带 x-deepseek-harness-session-id。
验证
把某个 provider 的 baseURL 指向会记录请求 header 的网关(或任何回显请求头的端点),开一个会话:
x-session-id: ba104306-a748-4052-a6e3-ab60be2e4c1f
同一会话的所有请求带同一 id;spawn 出的子 agent 的请求带 child session id。
说明
llm-deepseekadapter 自身每次请求已带x-deepseek-harness-session-id;本插件是 provider 中立的,且刻意不覆盖已有 header。- 绝不触碰
attributionHeaders()(harness 的 User-Agent 归因契约)。 - 并发会话正确处理:header 值按调用经由 AsyncLocalStorage 解析,不经过共享可变状态。
No comments yet. Be the first to write one.