前言¶
DeepSeek Harness(DSH)目前没有针对单请求头扩展的接口。当需要给不同模型路由附加不同格式的会话 ID 头(例如 Claude Code 需要的 x-claude-code-session-id,OpenCode Go 需要 x-opencode-session)时,适配器内部自己拼接 wire headers,无法由插件统一管理。本插件通过组合 DSH 两个官方拦截点,在 llm/stream 流水线中按模型匹配规则注入头,解决这一层缺失。
插件定位¶
dsh-model-session-headers 是一个按模型粒度注入会话 ID 相关 HTTP 头的 DSH 插件,由 jiangwennn 维护,属于模型推理类插件。它不修改 DSH 的核心逻辑,仅补全出站请求头这一层,适用于需要与 Claude Code 兼容网关、OpenCode Go 或通用中转(如 LiteLLM、one-api)对接的场景。
核心功能¶
按模型匹配规则注入头¶
插件在 llm/stream 流水线中拦截调用,通过 options.provider 和 options.model 匹配规则,为命中规则的请求附加会话 ID 头。同一个 provider 路由下的不同模型可以走两套头:claude-* 发 Claude Code 头,deepseek-* 发 OpenCode 头,互不干扰。
内置三种生态头¶
| 生态 | 请求头 | 用途 |
|---|---|---|
| Claude Code | x-claude-code-session-id |
与 Claude Code 兼容网关对接,按会话聚合流量、计费、粘性路由 |
| OpenCode Go | x-opencode-session |
与 OpenCode / OpenCode Go 对接,缺头会返回 400 MissingSessionID,同值请求粘在同一后端以保 prompt cache |
| 通用 | x-session-id |
适用于 LiteLLM / one-api 等自建中转 |
优先级与覆盖¶
规则自上而下求值,首条命中生效;未命中任何规则的调用原样透传。默认不覆盖已存在的同名头(HTTP 语义上大小写不敏感),适配器自己设置的值优先。开启全局或单条规则的 overwrite,或通过规则中的 headers 覆盖预设。
安装与启用¶
从本地目录安装:
dsh plugin --profile web add /abs/path/to/dsh-model-session-headers
从 GitHub 安装:
dsh plugin --profile web add github:jiangwennn/dsh-model-session-headers
安装后重启 profile(bundle 层在启动时读取)。确认层已生效:
dsh --profile web --dump-config | grep model-session-headers
配置¶
在 profile 的 cordis.patch.yml 中配置(或通过设置界面编辑,写入同一文件):
- insert:
- id: model-session-headers
name: dsh-model-session-headers
config:
mode: session-id # session-id | uuid | fixed
keepPrefix: false
overwrite: false
debug: false
rules:
# Claude Code 兼容:所有 claude 系模型
- models: ["claude-*", "anthropic/claude-*"]
flavor: claude-code
# OpenCode Go:只对走 opencode-go 路由的 deepseek 模型
- models: ["deepseek-v4*"]
providers: [opencode-go]
flavor: opencode-go
# 完全自定义:一个模型挂多套头
- models: ["my-gateway-model"]
headers:
x-session-id: "{sessionId}"
x-opencode-session: "{sessionId}"
x-tenant: acme
x-routing-key: "{provider}/{model}"
默认规则¶
未配置 rules 时内置两条规则:
| 模型 | flavor | 示例 |
|---|---|---|
opencode/*、*/opencode/* |
opencode-go |
opencode/deepseek-v4.1-flash、aihub/opencode/deepseek-v4.1-flash |
claude-* |
claude-code |
claude-sonnet-4-6、anthropic/claude-sonnet-4-6 |
显式写 rules: [] 表示停用全部注入;profile 里已有的 rules 会整体覆盖默认规则。
模板变量¶
| 变量 | 含义 |
|---|---|
{sessionId} |
本次调用的 DSH 会话 id |
{model} |
模型 id |
{provider} |
provider 路由名称 |
mode 取值语义¶
mode |
头的值 |
|---|---|
session-id(默认) |
会话 id 去掉 session- 前缀的裸 UUID(keepPrefix: true 可保留) |
uuid |
会话 id 的 SHA-256 派生 UUIDv5 形态,跨进程稳定、对上游不可读 |
fixed |
配置里的 value,所有调用相同(value 为空则该规则不注入) |
会话 id 缺失的调用(部分内部辅助请求)不注入任何头,不会把空值发出去。
调试¶
开启调试模式:
debug: true # 控制台打印每次注入
debugFile: /tmp/session-headers.jsonl # 每次注入追加一行 JSON
JSONL 每行格式:{"ts","provider","model","session","headers"}。
状态显示¶
每个对话头部会显示一个状态标记:圆点 + 最近一次请求的头名称。
| 圆点 | 含义 |
|---|---|
| 绿 | 已发送:DSH 已带上这个头发出请求 |
| 黄 | 跳过:请求里已有同名头且 overwrite 关闭,保留了原值 |
| 灰 | 未命中规则,或还没有请求记录 |
点开后列出会话 id 和这个对话最近 10 次请求:时间、provider / 模型、状态、实际发出的头和值(跳过时显示已存在的值)。
- 默认开启。在设置卡片里取消「在对话右上角显示发送状态」即可关闭(YAML:
showStatus: false),保存后无需重启。 - 记录只在内存中,每个对话最多 10 条、最多保留 100 个对话,DSH 重启后清空。
- 没有会话 id 的内部辅助请求无法归属到某个对话,不会出现在列表里。
- 只能证明 DSH 这一跳已发出。中间网关(如 magpie)是否把头转发给上游,这里看不到——需要看网关自己的日志。
工作原理¶
插件通过组合两个官方拦截点实现:
llm/streamwaterfall —— 拿到options.provider/options.model/options.sessionId,通过AsyncLocalStorage限定作用域。- 补丁的
globalThis.fetch—— 真正把头写进出站请求。
匹配流程:llm/stream(options, next) → selectRule() 匹配规则 → buildHeaders() 构建头 → 在 als.run(scope) 内执行适配器内部的 fetch,补丁后的 fetch 从 store 读取并合并 headers。
限制¶
- 只覆盖
llm/stream内的对话/流式请求。GET /models这类模型发现请求是独立流程,不会带头;如果网关连列表接口都拒绝,需要另做适配。 - 依赖 DSH 出站 LLM 请求走 Node 全局
fetch。未来若 DSH 换网络栈,注入会失效(症状是原来的缺头报错回归)。 - 不伪造 TLS 指纹、
user-agent、anthropic-beta等其他头;需要时用规则的headers显式添加。 - 不把内部会话 id 泄露给第三方:只有命中规则的出站请求会被改写,其他 fetch 原样通过。
适用场景¶
适合需要与 Claude Code 兼容网关、OpenCode Go 或通用中转对接的 DSH 用户,特别是同一 provider 下需要不同模型走不同会话头格式、或需要按模型粒度控制头注入的场景。使用前应检查源码与许可证,确认符合自身项目需求。
总结¶
dsh-model-session-headers 补全了 DSH 在请求头注入层面的缺失,按模型匹配规则注入 Claude Code / OpenCode Go / 通用会话 ID 头,支持通配符匹配、模板变量、调试日志和状态显示。它不改变 DSH 核心逻辑,仅通过组合官方拦截点实现,适合需要精细控制出站请求头的场景。