dsh-undici-timeout-fix
Fixes DSH cutting off local-model requests after exactly five minutes of silence.
English | 简体中文
What this fixes
When a local model spends more than five minutes on prompt prefill, the request dies with
terminated and the model server logs Client disconnected.
The cause is not a DSH setting. Node's built-in fetch runs on undici, whose connection-level
bodyTimeout defaults to 300,000ms. That timer is an idle timer: every body chunk resets it.
A model that is busy prefilling sends no bytes at all, so undici destroys the socket the moment
the five minutes run out.
streamIdleTimeoutMs does not help here. It configures DSH's own watchdog, which is a separate
timer and — before this plugin — fired at the same 300,000ms mark. Raising it just lets undici
win the race, which is why the error changes from stream idle timeout after 300000ms to the
bare terminated.
How it works
DSH reaches the network through pi-ai calling the openai SDK, and that SDK uses the global
fetch (Shims.getDefaultFetch()). Replacing undici's global dispatcher therefore covers model
requests without patching DSH itself:
import { Agent, setGlobalDispatcher } from 'undici'
setGlobalDispatcher(new Agent({ headersTimeout: 0, bodyTimeout: 0 }))
0 disables undici's timers rather than setting a duration.
Install
From a release tarball (no registry needed)
Download dsh-undici-timeout-fix-1.1.0.tgz from the repository's Releases page, then:
dsh plugin --profile <your-profile> add /path/to/dsh-undici-timeout-fix-1.1.0.tgz
The one dependency (undici) is still resolved from a registry you can reach, so a local npm
mirror or a proxy works fine.
From npm
dsh plugin --profile <your-profile> add dsh-undici-timeout-fix
Or through the plugin manager in the DSH UI: add the package name dsh-undici-timeout-fix and
point the install source at a registry that serves it.
Installing by hand? Add the row to your profile's cordis.patch.yml:
- insert:
- id: undici-timeout-fix
name: 'dsh-undici-timeout-fix'
Restart required
Fully quit DSH and start it again. The dispatcher is installed while the plugin loads, so a
running process keeps whatever it already set. On macOS use Cmd+Q — closing the window is not
enough, because the process survives.
Skipping the restart is the most common reason for "it still fails".
Also configure streamIdleTimeoutMs
This plugin removes the transport ceiling, but it also removes DSH's own backstop. With no upper bound at all, a genuinely wedged connection can hang forever. Keep a large but finite watchdog on the provider that talks to your local model:
- id: llm-pi-ai
name: "@deepseek-ai/dsh-llm-pi-ai"
config:
providers:
lmstudio1:
displayName: lmstudio
apiKeyEnv: LMSTUDIO1_API_KEY
api: openai-completions
baseURL: http://127.0.0.1:1234/v1/
streamIdleTimeoutMs: 1800000 # 30 minutes
models:
- id: qwen3.5-122b-a10b
name: qwen3.5-122b-a10b
Do this for every provider you point at a local model. A provider without the key falls back to the 300,000ms default.
You can also prefer a finite transport timeout over disabling it entirely:
setGlobalDispatcher(new Agent({ headersTimeout: 3_600_000, bodyTimeout: 3_600_000 }))
Verify
Send a request whose prefill exceeds five minutes. If it completes, the fix is live.
You can also confirm which timer fired by reading the error:
| Error | Meaning |
|---|---|
terminated |
undici's transport timer — this plugin's concern |
pi-ai stream idle timeout after xxxms |
DSH's watchdog — raise streamIdleTimeoutMs |
Do not set proxy environment variables
HTTP_PROXY, HTTPS_PROXY, and ALL_PROXY make dsh-http-proxy install its own Agent during
boot, which carries the same five-minute limit and replaces this plugin's. DSH installs the proxy
before plugins load, so this plugin always wins only when no proxy is configured.
Platform status
| Platform | Status |
|---|---|
| macOS (Darwin 24, Node 24.21.0 / undici 7.29.1 built in) | Tested, working |
| Windows | Untested — please open an issue with your result |
The code has no platform-specific paths, file writes, or process spawning, and depends only on
undici, so it should behave identically on Windows. It has not been verified there yet.
Notes
- This bundle declares no
peerDependencies, so DSH's plugin version-compatibility check does not block installation. - pnpm's
minimumReleaseAgepolicy (24 hours by default) blocks freshly published versions. If installation reports an older version, install an exact one:dsh plugin add dsh-undici-timeout-fix@1.1.0.
License
MIT
简体中文
修复 DSH 使用本地大模型时,静默满五分钟后请求被切断的问题。
解决的问题
本地模型做 prompt 预处理(prefill)超过五分钟时,请求会以 terminated 失败,模型服务端日志出现
Client disconnected。
原因不在 DSH 配置。Node 内置 fetch 基于 undici,其连接级 bodyTimeout 默认 300,000ms。
该计时器统计的是空闲时长:每收到一个 body 数据块就重置。正在 prefill 的模型一个字节都不发,
于是五分钟一到 undici 直接销毁 socket。
streamIdleTimeoutMs 解决不了这个问题。它配置的是 DSH 自己的看门狗,是另一个计时器——而且在装本插件
之前,它同样在 300,000ms 处触发。把它调大只是让 undici 抢先触发,所以报错会从
stream idle timeout after 300000ms 变成一个光秃秃的 terminated。
原理
DSH 经由 pi-ai 调用 openai SDK 发起请求,而该 SDK 使用全局 fetch(Shims.getDefaultFetch())。
因此替换 undici 的全局 dispatcher 就能覆盖模型请求,无需改动 DSH 本体:
import { Agent, setGlobalDispatcher } from 'undici'
setGlobalDispatcher(new Agent({ headersTimeout: 0, bodyTimeout: 0 }))
0 表示禁用 undici 的计时器,而不是某个时长。
安装
从 Release 附件安装(无需registry)
从仓库的 Releases 页面下载 dsh-undici-timeout-fix-1.1.0.tgz,然后:
dsh plugin --profile <你的 profile> add /path/to/dsh-undici-timeout-fix-1.1.0.tgz
唯一的依赖(undici)仍会从你能访问到的 registry 解析,所以内网 npm 镜像或代理都可以。
从 npm 安装
dsh plugin --profile <你的 profile> add dsh-undici-timeout-fix
也可以在 DSH 界面里安装:包名填 dsh-undici-timeout-fix,安装源指向提供该包的 registry。
手动安装时,在 profile 的 cordis.patch.yml 里加一行:
- insert:
- id: undici-timeout-fix
name: 'dsh-undici-timeout-fix'
必须重启
完全退出 DSH 后重新启动。 dispatcher 在插件加载时安装,已运行的进程会继续沿用它之前的状态。
macOS 上请用 Cmd+Q——只关窗口不够,进程仍然存活。
漏掉重启是"怎么还是不行"最常见的原因。
同时配置 streamIdleTimeoutMs
本插件移除了传输层的上限,同时也移除了 DSH 自身的兜底。完全没有上限时,真正卡死的连接可能永远挂着。 请为每个指向本地模型的 provider 配一个足够大但有限的看门狗:
- id: llm-pi-ai
name: "@deepseek-ai/dsh-llm-pi-ai"
config:
providers:
lmstudio1:
displayName: lmstudio
apiKeyEnv: LMSTUDIO1_API_KEY
api: openai-completions
baseURL: http://127.0.0.1:1234/v1/
streamIdleTimeoutMs: 1800000 # 30 分钟
models:
- id: qwen3.5-122b-a10b
name: qwen3.5-122b-a10b
每个指向本地模型的 provider 都要配。漏配的会退回默认的 300,000ms。
如果你更想设一个有限值而不是完全禁用:
setGlobalDispatcher(new Agent({ headersTimeout: 3_600_000, bodyTimeout: 3_600_000 }))
验证
发一个 prefill 超过五分钟的请求。正常完成即表示生效。
也可以通过报错文本判断是哪个计时器触发的:
| 报错 | 含义 |
|---|---|
terminated |
undici 传输层计时器——本插件负责的范围 |
pi-ai stream idle timeout after xxxms |
DSH 看门狗——调大 streamIdleTimeoutMs |
不要设置代理环境变量
HTTP_PROXY、HTTPS_PROXY、ALL_PROXY 会让 dsh-http-proxy 在 boot 阶段安装它自己的 Agent,
它同样带五分钟限制,并会替换掉本插件。DSH 先装 proxy、后加载插件,所以只有不设代理时本插件才一定生效。
平台状态
| 平台 | 状态 |
|---|---|
| macOS(Darwin 24,Node 24.21.0,内建 undici 7.29.1) | 已测试,可用 |
| Windows | 未测试——欢迎提 issue 分享你的结果 |
代码不含任何平台相关路径、文件写入或进程调用,唯一依赖是 undici,因此在 Windows 上行为应当一致,
但尚未实测验证。
其他说明
- 本 bundle 未声明
peerDependencies,因此不受 DSH 插件版本兼容性检查拦截。 - pnpm 的
minimumReleaseAge策略(默认 24 小时)会拦截刚发布的版本。若安装提示装了旧版本, 请指定精确版本:dsh plugin add dsh-undici-timeout-fix@1.1.0。
许可
MIT
No comments yet. Be the first to write one.