前言¶
DSH 的 agent loop 每一轮都会把整段对话重新交给模型:system instructions、工具定义、历史消息、工具结果,一个不落。对话变长之后,这一轮的输入里大部分是上一轮已经发过的内容——传输体积和 input 计费都重复承担。对于 OpenAI Responses API 这类服务端有状态的接口,上游本来就保存了上一次 response,客户端本可以只发新增 item 和 previous_response_id 完成续传。本文介绍的插件 zhexuany/dsh-stateful-responses 做的就是这件事:在进程内最后一层拦截发往上游的请求,命中条件时改写成增量请求,其余情况原样放行。
这是什么¶
dsh-stateful-responses 是一个 DSH(dsh)bundle 插件,维护者是 zhexuany,分类为模型推理,协议为 MIT(LICENSE 见仓库)。目标运行时是 DSH Harness / dsh 0.2.0-rc.2 及以上,运行环境要求 Node.js >= 22.19.0,无第三方依赖。它解决的问题是:dsh 调用 GPT 模型走 Responses API 时,每一轮都在全量重放会话,插件把这种重放替换为基于 store + previous_response_id 的增量续传。
需要说明的是,这是社区插件,收录在第三方目录 SkillHub 上,与 DeepSeek / 幻方没有官方从属关系。
它解决什么问题¶
Responses API 的 continuation 机制前提是客户端只发送新增 item 和上一轮响应 id。dsh 在这一层存在缺口:
llm-pi-ai的api: openai-responses适配只做协议转换,没有接 continuation;- provider 注册是独占的,插件无法从 provider 层接管;
- 请求扩展只能往请求体里加字段,而「不再重放历史」是会话循环的行为,不是单个字段。
因此插件把改写点放在进程内最后一层:globalThis.fetch。架构上是这样一条链:
agent loop
└── llm-pi-ai (api: openai-responses)
└── OpenAI SDK
└── globalThis.fetch ← dsh-stateful-responses 在这里拦截
└── upstream POST /v1/responses
内部模块分工:index.js 是入口(name + apply(ctx, config),负责安装/卸载拦截器);lib/interceptor.js 负责命中判定、请求改写、响应追踪、回退与降级;lib/chain.js 是有界 LRU,保存会话链;lib/items.js 负责 item 规范化与哈希;lib/sse.js 收集 SSE 终止事件;lib/config.js 做严格配置校验;cordis.patch.yml 是 bundle patch,用 insert 行挂载插件。
选择 fetch 层而不是 adapter 层的原因,README 里给了四点:pi-ai 的 OpenAI SDK 每次请求都用全局 fetch 且 body 是 JSON 字符串,这是稳定的调用契约;不需要了解 pi-ai 内部实现,也不绑定其版本;插件卸载时可以完整恢复原 fetch;不改任何 provider 配置,安装和回滚只是增删一个 bundle。
工作原理¶
下面介绍插件运行时的判定与改写逻辑,这部分来自 README。
命中条件。 只有同时满足才处理,否则原样放行:方法为 POST;URL 以 /responses 结尾且命中配置的 baseURLs;body 是 JSON 字符串且含字符串 model 与数组 input;model 命中配置的 models(支持结尾 * 通配);调用方没有自带 previous_response_id。
会话链。 链的键由请求的稳定要素组成:
chain key = url + model + prompt_cache_key + hash(tools) + hash(instructions)
不同模型、工具集、system instructions 会自然分链;prompt_cache_key(dsh 会把会话 id 传给 provider)让不同会话不串链。链存放在有界 LRU 中(maxChains,默认 32),超出后淘汰最久未使用的链。
item 身份与前缀验证。 每个 input/output item 先规范化再取 sha256,规范化会剔除上游可重新生成的易变字段:id、status、created_at、completed_at、annotations、logprobs、obfuscation。这些字段只影响比较,真正发出的是未经修改的 delta 原文。只有当本次请求的 item 序列是链中序列的严格前缀扩展(前 N 项哈希逐一相同,且至少多出 1 项)才允许续传;否则说明会话分叉、被编辑或换了上下文,插件丢弃该链并按全量发送。
改写。 命中时把 body 改成等价的增量请求:
其余字段(tools、instructions、reasoning 参数、采样参数等)保持不变。
响应追踪与链更新。 流式(SSE)时对 response.body 做 tee(),一份交给 pi-ai,另一份给内部收集器解析终止事件;非流式时通过 response.clone().json() 读取。无论哪种形式,插件都不会修改模型返回给 dsh 的字节内容。只有响应满足 store === true 且带非空 id 时才更新链,链上的 items 由上游返回的 output 追加而成。
失败与降级。 行为如下表:
| 情况 | 行为 |
|---|---|
完成响应 store !== true |
该 baseURL 标记为不支持,清空全部链,之后永远全量(每个 baseURL 只提示一次) |
响应 failed / cancelled |
只丢弃当前链,不标记不支持 |
续传请求非 2xx,且错误文本匹配 previous_response_id / previous response / not found |
丢弃链,用原始全量 body 重试一次 |
| 续传请求网络错误(非用户取消) | 丢弃链,用原始全量 body 重试一次 |
| 解析、规划、追踪等内部异常 | 放行原请求,不打断模型调用 |
安装与启用¶
插件是一个标准 dsh bundle。README 给出的安装方式如下,先做前两步:
- 把仓库放到任意目录,例如
~/dev/dsh-stateful-responses; - 在目标 profile(
~/.dsh/profiles/<name>/package.json)中添加依赖与 bundle:
{
"dependencies": {
"dsh-stateful-responses": "link:/home/you/dev/dsh-stateful-responses"
},
"dsh": {
"profile": {
"bundles": ["…", "dsh-stateful-responses"]
}
}
}
- 在该 profile 目录执行
pnpm install; - 重启 dsh(bundle 列表在启动时读取)。
配置¶
配置项与默认值如下表。插件默认不限定 baseURL 与模型(空数组 = 全部命中),生产使用时建议把 baseURLs / models 收窄到真正支持 Responses 状态的上游与模型,避免向其它端点发送 store: true。
| 键 | 类型 | 默认 | 说明 |
|---|---|---|---|
enabled |
boolean | true |
关闭后不安装拦截器 |
forceStore |
boolean | true |
改写时强制 store: true |
baseURLs |
string[] | [] |
只拦截这些 URL 前缀下的 /responses;空 = 全部 |
models |
string[] | [] |
只拦截这些模型 id;支持结尾 * 通配;空 = 全部 |
maxChains |
positive integer | 32 |
最多保留多少条会话链 |
debug |
boolean | false |
输出每次改写 / 回退的决策日志 |
在 cordis.patch.yml 中的示例:
- id: stateful-responses
name: dsh-stateful-responses
config:
enabled: true
forceStore: true
baseURLs: ["https://api.openai.com/v1"]
models: ["gpt-*"]
maxChains: 32
debug: false
配置是严格的:未知键或类型错误会在激活时输出 warn,插件不会启用,而不是静默忽略。
排错与可观测性¶
debug: true 时会输出四类决策日志:rewriting request(本轮改写为增量)、chain updated(链已推进)、continuation rejected by upstream; retrying once with full history(续传被拒并已回退)、response did not complete; chain dropped(失败响应导致链丢弃)。每个 baseURL 只会出现一次的 info 级提示:
upstream does not retain responses (store is not true); using full history from now on
启用时输出 stateful Responses API interceptor installed;卸载时输出 restoring global fetch 以及本次进程累计统计:requests / continuations / deltaItems / fullItems / fallbacks / unsupportedBases / errors / chains。没有命中任何条件时,插件不产生日志,也不改变请求。
适用场景与注意¶
适合的场景:dsh 通过 OpenAI Responses 协议(POST …/responses)调用支持 store + previous_response_id 的上游,且希望减少每轮全量重放带来的传输与 input 计费。README 明确两点边界:仅覆盖 OpenAI Responses 协议,llm-deepseek 使用的 Messages 协议没有等价的 continuation 字段,不在覆盖范围内;上游必须能接受它自己返回过的 output item(包括 reasoning item 的 encrypted_content)在下一次请求中被带回,否则前缀校验失败,插件退回全量,安全但没有收益。
关于最坏情况,插件保证不会丢上下文:链只在上游返回过 store: true 且有 id 之后才存在;发出的 input 是本次请求比链多出来的原文;任何一环不成立就发全量,行为与未安装插件完全一致。插件不读取、不缓存、不转发任何凭据,鉴权头仍由 OpenAI SDK 生成并原样发送;日志只包含模型名、item 数量、response id 等元数据。
部署时需要留意的权限问题:插件运行在当前 dsh 进程内,以该进程的权限访问网络与文件,安装前应自行检查源码与许可证(MIT)。它只包装 fetch,不引入外部服务,也不改变 provider 配置。
结尾¶
这个插件的价值在于把每轮全量重放换成增量续传,同时把「不确定就回退」作为硬约束,使最坏行为与未安装时一致。目录页在 https://www.skillhub.cn/plugins/zhexuany/dsh-stateful-responses ,源码与 README 在 https://github.com/zhexuany/dsh-stateful-responses 。