dsh-auto-rerun

modelnpmMIT

Automatically retries model failures (drops, timeouts, rate limits, 5xx) in-turn. Retry count, delays, backoff, fallback and more are configurable. Rate-limit backoff is capped and jittered, server retry hints are honored, and optional retry-until-success keeps going within a time ceiling. Every opt

下载量 / Downloads
626

Install

dsh plugin --profile web add dsh-auto-rerun

Sandbox test

This plugin has not been tested yet. Submit a test →

README

dsh-auto-rerun — 模型断连自动重跑

![npm](https://www.npmjs.com/package/dsh-auto-rerun)

![license](LICENSE)

DeepSeek Harness 宿主插件。当模型请求因断连类故障(网络断开、代理/VPN 重启、

限流、5xx 服务错误、流中断、超时)失败时自动恢复,不让任务死在一条红字上。

它做什么

  • 同回合自动重试:失败不抛给你,插件接管重试,任务从断点继续往下跑。
  • 限流长退避:429 / tpm exhausted 这类分钟级配额,用 30s→60s→120s→180s 的

静默长窗口跨过去,而不是撞几次就放弃。

  • 听服务端的话:provider 回 Retry-After 或失败文本里的 retry in ~3s /

retry_after: 5 / 请 3 秒后重试,就按它给的时刻等,不用自己的窗口。

  • 一直试到成功(retryUntilSuccess,默认开):同一个模型、同一个会话,

只要不是死路就一直重连,靠退避封顶而不是次数限制防打爆端点;配 2 小时天花板,

路线彻底坏掉时会把错误如实交还,不会假装还在努力。

  • 选模型重试(modelFallback,默认关):一条路线持续跑不通时,换备选路线

再试;死路(401/403、订阅过期、key 失效、欠费)立刻换并拉黑 30 分钟。

  • 看得见也改得动:右侧栏面板显示当前路线、重试次数、事故计数,开关和数字

当场改、立刻生效、重启仍保留(默认仅内存,不写盘)。

能调的选项(34 个,完整说明见[配置](#配置cordispatchyml-的-entry-config-或插件行配置)):

重试次数、初始/最大延迟、退避系数与抖动、限流专用延迟窗口、文本提示上下限、

一直重试的天花板、换路线的触发条件(次数/时长)与次数上限、候选顺序(钉死/

同模型换 provider/自动目录)、死路黑名单时长、事故间隔判定、续跑次数与延迟、

哪些失败码不发可见消息、HTTP 状态集合、语言、调试日志、日志文件、设置文件路径。


与内置重试的关系(互补,不冲突)

DSH 自带 @deepseek-ai/dsh-llm-retry,在每个供应商的 retryPolicy.normal 模式

下已经覆盖短窗口抖动(默认 5 次、退避 0.5–10s、jitter 10%)。本插件只做它不想

做的事:

层 · 触发 · 行为 · 边界

Layer A 请求级接管 · agent/request-error 瀑布下游让出终态(内置重试预算耗尽 / 无 provider policy),且失败属断连类 · 追加自己的长退避重试(默认 3 次、5s→60s 指数退避 + 20% jitter;限流类单独用 30s→180s 长窗口;provider 自带的 Retry-After 或失败文本里的 retry in ~3s 提示优先于类窗口),返回 {kind:'retry'} · 默认 retryUntilSuccess:不是死路信号就一直重连(退避有封顶,不会打爆端点),但最长 retryUntilSuccessMaxMs(2 小时)没跑通就把错误交还宿主——否则路线彻底坏掉时会永远静默重试、把错误藏起来;关掉 retryUntilSuccess 才回到计数/时间预算制

Layer B 回合级续跑 · 请求仍终态失败,agent/error 触发,且失败不属于 rerunExcludeCodes · 延迟 3s 后自动 agent.followup(继续任务),让 Agent 新开回合从中断处续跑 · 连续最多 1 次;回合成功 / 用户发新消息 / Agent 销毁即清零

Layer C 选模型重试 · 1.6.0 起默认关(modelFallback.enabled: false,需要就打开)。开之后触发条件:① 普通模式下同一条 provider/model 路线把自己那份计数预算用完(且连续失败 ≥ fallbackAfterFailures);② retryUntilSuccess 模式下同一条路线连续失败超过 fallbackAfterMs(计数预算在该模式下永不耗尽,时间才是唯一信号);③ 该路线给出死路信号(401/403、订阅过期、key 无效、没额度)——死路不等预算,立刻换 · 换一条路线再试,并重置计数预算与新路线自己的时间窗口:优先 fallbackRoutes 里钉的,否则从 ctx.llm 目录里挑——优先「同 model id、另一 provider」(行为不变、配额独立,真能躲限流);死掉的路线进 30 分钟黑名单,不再当候选 · 单次事故最多换 fallbackMaxSwitches 次;每候选都过 resolveModelInfo 校验;同一路线只试一次

Layer B 会往会话里发一条消息(用户可见),所以限流类默认被排除:RATE_LIMIT

(含 HTTP 429)只走 Layer A 的静默重试,不打扰用户。限流(如免费线 tpm exhausted)

是分钟级配额,短退避必死,Layer A 对它使用 30s→60s→120s 长窗口,足以跨过;

而让它发续跑消息无意义——消息出去也是撞墙,还可能连环触发。

Layer C 怎么换路线:优先走宿主的模型选择通道

ctx.get("sessionController").agents.selectForNextRequest(agent, {provider, model})

—— 这会落一条 model/selection 会话事件,于是会话里出现宿主自己的

[model changed: …] 通知、界面上的模型选择器跟着变、重载后仍然生效,**没有任何

偷偷骗人的地方**。拿不到该服务时(非 Web 宿主 / 端口改名)才退化为「只改下一次

请求」:插件在 agent/request 瀑布最外层改写 provider/model(顺带丢掉从原模型

继承的 reasoningEffort,避免新模型不认),并在这条事故结束前持续生效。两种情况

都写一行 model fallback: a/x → b/y (…) 日志;用户在界面上自己换过模型时,

尚未落地的临时改写会被丢弃并记日志(不跟用户抢方向盘)。

候选顺序:钉死的 fallbackRoutes → 同 model id 的另一 provider →

本进程已见过的路线(宿主自己装配过,在这台机器上确定能跑)→ 目录里其余模型

→ 目录完全拿不到时,只剩「见过的路线」这一级

(同 provider 换个 model 名字通常还在同一个限流桶里,换 provider 才换桶)。

死路信号(Provider 402/401/403、subscription … expired、invalid api key、

no credits、余额/额度不足/订阅过期)与拥塞信号(429、tpm exhausted、

retry in ~3s)严格区分:前者重试无意义、立刻换路线;后者只是等一会儿,

绝不能被当成死路把会话拖走。

永不重试的失败:认证(401/403)、无效请求(400)、上下文超长、协议/代码错误

——这些重试无用,插件会把它们如实交还宿主。只有打开 Layer C

(modelFallback: { enabled: true })时,「认证/订阅过期/额度耗尽」才会先尝试

换一条路线,换不了才把错误交还。默认(1.6.0)不换路线:这类"一定的报错"

直接停,别的断连类一直重试。

断连类判定

同时支持三种输入(任一命中即算断连类):

1. loop 失败码(默认):EMPTY_RESPONSE RATE_LIMIT SERVER TIMEOUT TRANSPORT

2. POSIX errno:ECONNRESET ECONNREFUSED ETIMEDOUT EPIPE EHOSTUNREACH

ENETUNREACH EAI_AGAIN 等

3. HTTP 状态:408 / 425 / 429 / 500 / 502 / 503 / 504;以及消息文本正则

(socket hang up、fetch failed、network、disconnected、timed out、

rate limit、overloaded、service unavailable、bad gateway、gateway timeout 等)

安装

已发布到 npm,也可以直接从 GitHub 发布包装:

# 推荐一:从 npm 安装(任意 DSH 会话里)
plugin_manager install_bundle "dsh-auto-rerun"

# 推荐二:从 GitHub 发布包安装(不依赖 npm)
plugin_manager install_bundle "https://github.com/good-saltfish/dsh-auto-rerun/releases/download/v1.8.5/dsh-auto-rerun-1.8.5.tgz"
# 或本地目录(开发调试用):
plugin_manager install_bundle "<你本机的仓库路径>"

或 Plugin Manager 页面手动安装本地目录。安装后新会话即生效;

正在运行的会话建议重启 DSH 让宿主插件重新挂载。

配置(cordis.patch.yml 的 entry config 或插件行配置)

键 · 默认 · 说明

enabled · true · 总开关

request.enabled · true · Layer A 开关

rerun.enabled · true · Layer B 开关

retries · 3 · Layer A 每次事故最大接管重试次数

initialDelayMs / maxDelayMs / backoffFactor / jitterRatio · 5000 / 60000 / 2 / 0.2 · 普通断连退避参数

rateLimitInitialDelayMs / rateLimitMaxDelayMs · 30000 / 180000 · 限流类(429/RATE_LIMIT)单独用的静默长退避;provider 给了 Retry-After 就照它等

honorMessageRetryHint · true · 读失败文本里的退避指令(retry in ~3s / try again in 2.5s / retry_after: 5 / 请 3 秒后重试)并按它等,优先于上面的类窗口

messageHintMinMs / messageHintMaxMs · 1000 / 60000 · 文本提示的取用窗口:太短的值会被抬到下限,过长的会被截断

busyRouteBudgetMs · 600000 · 仅 retryUntilSuccess: false 时生效:拥塞类失败在计数预算之外按"静默时长"给时间预算——只要每隔不超过这么久就还能发出一次重连,就一直连;路线真正连续 10 分钟毫无响应才收工

retryUntilSuccess · true · 核心场景(免费模型容易堵):同一个模型、同一个会话内,只要失败不是"一定的报错"(死路:401/403、订阅过期、key 失效、永久没额度),就一直重连直到跑通,计数预算和时间预算都不拦(退避仍封顶:限流最长 180s、文本提示最长 60s 一次,不会打爆端点)。日志里预算栏显示 until-success #N。设 false 回到 1.5.x 的预算制

retryUntilSuccessMaxMs · 7200000 · 「一直重试」的天花板:连续这么久都没跑通过任何一条请求,就把失败交还宿主(日志写 until-success window spent (…s >= …s))。没有它,路线彻底坏掉时会永远静默重连、把错误藏起来,用户只觉得卡住。0 = 不设上限

modelFallback.enabled · false · 选模型重试开关(1.6.0 起默认关:老大的要求是「就守着一个模型重试」)。开 = true 时,同一条路线连续失败后可换 provider/model 再试

fallbackRoutes · [] · 钉死的候选路线,按优先级写 "provider/model"(如 `["other-provider/model-i

…(truncated)

Rendered from the README published on npm. Formatted only, not edited.

Versions

Published on npm::3,最后更新 2026-10-31

Links

GitHub · Homepage · npm

Same category