dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
deepseek harness 使用第三方提供者的api时的缺失message_stop导致的重复重试
message_stop 重试修复DeepSeek Harness (dsh) 插件:当第三方提供方网关翻译漏发了终止事件
message_stop/done时,dsh 把内容已完整到达的回复误判为TRANSPORT传输错误,并对同一步最多做 5 次指数退避重试。本插件把"内容已到、缺终止信号"的流干净地结束,让回复正常提交、重试不再发生。English: A dsh plugin that heals third-party Anthropic-compatible gateways which close the stream without a terminal
message_stop/doneevent, so the agent loop commits a complete reply instead of burning up to 5 backoff retries.
目录
anthropic-messages 协议。llm-retry 重试 5 次(延迟约 467 → 1090 → 2031 → 3982 → 7282 ms,指数退避)。从 session.jsonl 可见:
{"type":"assistant/chunk","...":"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"Anthropic stream ended before message_stop","code":"TRANSPORT"}}}}
{"type":"llm/retry","...":"data":{"provider":"xjhc","mode":"normal","retry":1,"maxRetries":5,...}}
缺失终止事件由 pi-ai 适配器抛出:@deepseek-ai/dsh-llm-pi-ai/lib/index.js 的 toStreamChunks() 只在收到 done/error 事件时产出 usage+finish 并正常返回;若事件流自然跑完却无终止事件,则在末尾抛
throw new LlmError("pi-ai event stream ended without done/error", "STREAM_CLOSED")
错误码分类:classifyPiAiError() 把含 stream ended (before|without) 的消息归为 TRANSPORT。
触发重试:agent-loop 的 step() 在流式收尾时,若 finish 为 error/aborted,就走 agent/request-error 瀑布;@deepseek-ai/dsh-llm-retry 的 recover() 见到 TRANSPORT 在 retryableCodes 内、且未达 maxRetries,就按 initialDelayMs * 2**n 退避并返回 {kind:'retry'}——于是同一步被重新完整请求一次。
为何"最终内容仍正确":每次重试都重新拿到同样完整的内容;重试耗尽后该步抛错,界面展示的其实是最后那批流式渲染的 assistant/chunk。
为何"干净结束"就够:BlockAssembler 的 finish 取值是
get finish() { return this._finish ?? { kind: 'stop' } }
即流没有 finish chunk 也算正常 stop。所以只要把"缺终止信号"这个错误吞掉、让流干净结束,回复就会被正常提交,request-error/llm-retry 根本不会发生。
监听 dsh host 端公开的 llm/stream waterfall("around every streaming model call"),把下游流包一层:
code === 'STREAM_CLOSED',或失败信息含 stream ended (before|without) / message_stop / done/error),且已收到内容、且所有未闭合的 tool-call 块参数已是合法 JSON 时,才丢弃该错误、干净结束;语义上等价于"把这个网关变得宽容:内容完整即算完"。代码见 heal.mjs(ESM,导出标准 Cordis 插件 { name, apply })。
装进 web profile 后,插件在进程级生效,规则可在 GUI 设置页里调。
dsh plugin --profile web add dsh-missing-stop-heal
missing-stop-heal 一键安装也行。dsh plugin --profile web add link:<本仓库绝对路径>(link 方式 = 改仓库即生效,不用重装)。config,没有设置页)。在某个 agent preset 的 agent.cordis.yml 末尾加一行(name 以 . 开头,loader 按 preset 目录相对路径解析):
- id: missing-stop-heal
name: './heal.mjs'
config:
providers:
- xjhc
把 heal.mjs(或经目录联接指向本仓库)放到该 preset 目录下即可。
注意:
tool-cordis/ 动态 Cordis 插件是进程内临时的,重启即失效;本插件作为 preset / profile 组成行是持久的。
配置可通过行内 config(方式 B / 补丁层)或设置页(方式 A,写 user 层)提供。两种写法,rules 为主:
config:
rules:
- provider: xjhc # 必填:provider 标识(llm/stream options.provider)
models: ['*'] # 选填:缺省/空 = 全部模型;支持 * 通配,如 deepseek-v4*
enabled: true # 选填:缺省 true
config.providers: ['a','b'] 仍兼容=这些 provider 的全部模型;providers 与 rules 可混用,逐条累加。models 语义:留空或填 * = 该 provider 的全部模型;写多个用逗号/数组分隔;可用 * 通配前缀(如 deepseek-v4* 匹配 deepseek-v4 开头)。llm/stream 事件的 options.provider + options.model 逐条匹配;命中才包裹该流。装成 bundle(方式 A)后,dsh web 的设置侧栏会多一个**「流重试修复」**独立入口(settings.section,类似「插件市场」的独立项):
ctx.llm.listProviders),可点选/手填;ctx.llm.listModels),也可手填 + * 通配;provider/模型目录(
catalog)由 host 端写入设置文档,随llm/adapters-updated自动刷新;客户端直接从同一 settingsScope 快照读取,无需手动同步。
xjhc)用 rules 列出;官方 API 不必列,未被规则命中的请求一律放行;heal.mjs)需重启 dsh;改客户端(client/client.js)浏览器 Ctrl+F5 即可。finish chunk、也没有 replayState;usage 是否保留取决于错误是 in-band(有 usage)还是抛错(无 usage),通常 token 计量走估算。engines.dsh 已声明);@deepseek-ai/schemastery 以 peerDependencies 声明,用于设置命名空间(缺失时自动退化为纯行配置,修复功能不受影响)。llm/stream 的 options.provider/options.model 字段(本仓库根因分析里有完整签名)。settings.section,不是藏进「插件」列表。llm/adapters-updated 时写入;首次装完重启后即有,之后可手填。rules 里列出的 provider 一律放行。dsh 会话日志是 JSONL(默认在 $DSH_HOME/sessions/.../session.jsonl)。判断该会话是否发生过此问题:
Select-String -Path <session.jsonl> -Pattern 'llm/retry|TRANSPORT|message_stop|STREAM_CLOSED|stream ended|request-error'
llm/retry、TRANSPORT、Anthropic stream ended before message_stop;assistant/message 的 sourceEventSeqs 是单次连续区间(每步一次尝试即落库),没有 llm/retry 事件。MIT,作者 zzzyaar。欢迎在此基础上扩展、提交 PR。