☰
DSH 桌面版用 DeepSeek 报 REQUEST_EXTENSION 的根因定位与修复
2026/10/14 17:02:49 网站建设 项目流程

DSH 桌面版用 DeepSeek 报 REQUEST_EXTENSION 的根因定位与修复

DSH 桌面版(DeepSeek Harness Desktop)里把模型切到 DeepSeek 后,每次发消息都失败:本轮运行失败 DeepSeek request extension preparation failed,Tag 是REQUEST_EXTENSION。请求根本没发出去。本文从错误字符串一路翻到运行时源码,定位到一个 profile 清单缺字段引发的连锁报错,并给出零升级的本地修复方案和可复现的 A/B 验证。

一、现象

症状很干净,也很诡异:

  • 桌面版默认模型是 MiniMax / 其他 provider 时,一切正常;
  • 一旦在模型选择器里切到DeepSeek(本文实测是deepseek-account/deepseek-flash),发送任意消息(哪怕只是「你好」)都是:
本轮运行失败 DeepSeek request extension preparation failed REQUEST_EXTENSION
  • 关键点:会话assistant/attempt事件里,从request/header到报错只隔了~20ms。这说明不是网络超时、不是鉴权失败——它是在本地、在任何 HTTP 之前就炸了。
{"type":"request/header","seq":182,"time":1791868045205, "data":{"header":{"config":{"provider":"deepseek-account","model":"deepseek-flash","reasoningEffort":"high"}}}} {"type":"assistant/attempt","seq":184,"time":1791868045225, "data":{"turn":5,"step":1,"stream":[{"chunk":{"type":"finish", "reason":{"kind":"error","failure":{"message":"DeepSeek request extension preparation failed","code":"REQUEST_EXTENSION"}}}}]}}

二、先搞清楚这个错误是谁抛的

DSH 的运行时是打进app.asar的。把dsh-llm-deepseek/lib/index.js抠出来,REQUEST_EXTENSION只有一处:

async function prepareRequestExtensions(body, options, prepare, onOmitted) { let extensions; try { extensions = await prepare({ body, ...options }); // ← 贡献者在这里跑 } catch (error) { throw new LlmError("DeepSeek request extension preparation failed", "REQUEST_EXTENSION", { cause: error }); // ← UI 只显示这层的 message } // ...再合并字段、序列化、派发 }

也就是说:UI 上那句错误只是外壳,真正的cause被吞掉了。DeepSeek 官方适配器在发请求前会跑一个「请求扩展准备」阶段,把所有插件想往请求里注入的顶层字段准备好;任何一个贡献者抛异常,都会被包装成REQUEST_EXTENSION。

那谁在往 DeepSeek 请求里塞字段?在运行时里搜deepseekLlmApiExtensions.register,一共两个:

插件注入字段干什么
dsh-session-log-deepseekdsh_session_log增量上传会话日志
dsh-plugin-package-inventory-deepseekdsh_plugin_packages上报当前激活插件的包清单

而且这俩是@deepseek-ai/dsh-base这一层 bundle 的默认开启插件,所以每个 profile 都有。

# <runtime>/node_modules/@deepseek-ai/dsh-base/cordis.patch.yml - id: deepseek-llm-api-extensions name: '@deepseek-ai/dsh-deepseek-llm-api-extensions' - id: session-log-deepseek name: '@deepseek-ai/dsh-session-log-deepseek' - id: plugin-package-inventory-deepseek name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'

三、根因:一个「相对路径插件」撞上「缺 version 的 profile 清单」

看dsh-plugin-package-inventory-deepseek的prepare,它会遍历所有激活的 loader 条目,解析每个条目的归属包:

ctx.deepseekLlmApiExtensions.register("dsh_plugin_packages", { prepare: async (request) => ({ value: { version: 1, packages: await collectActivePluginPackages(ctx, resolver, hostBaseUrl, request.sessionId) } }) });

解析逻辑里,对「裸包名」和「相对/绝对路径模块」是两条路:相对路径的模块会就近向上找一个package.json,再用identityFromManifest()校验它:

function identityFromManifest(path, allowAnonymous) { const manifest = JSON.parse(readFileSync(path, "utf8")); if (allowAnonymous && manifest.name === void 0) return void 0; if (typeof manifest.name !== "string" || manifest.name.length === 0 || typeof manifest.version !== "string" || manifest.version.length === 0) { throw new Error(`plugin-package-inventory-deepseek: ${path} must declare non-empty name and version`); } return { name: manifest.name, version: manifest.version }; }

现在看这台机器上的 desktop profile。它里面恰好有一个相对路径插件——就是之前为了修「Open In VS Code 打开失败」加的那个本地副本:

# ~/.dsh/profiles/desktop/cordis.patch.yml - id: open-in-app disabled: true - insert: - id: open-in-app-patched name: "./plugins/open-in-app.mjs" # ← 相对路径!

于是plugin-package-inventory-deepseek解析这个条目时:

  1. ./plugins/open-in-app.mjs不是裸包名 → 走就近 manifest 逻辑;
  2. 从plugins/往上一层,找到最近的package.json=profile 自己的~/.dsh/profiles/desktop/package.json;
  3. 而DSH 桌面版的 profile 初始化器生成的清单里没有version字段:
// 桌面版 app.asar 内的 lib/main.js,initProfile() const manifest = { name: `dsh-profile-${basename(dir)}`, private: true, dependencies: {}, dsh: { profile: { bundles: [...bundles] } } // ← 没有 version };
  1. identityFromManifest走到typeof manifest.version !== "string"→抛错;
  2. 异常从prepare冒泡 → 被适配器包成REQUEST_EXTENSION。

于是链条闭合:只要 desktop profile 里挂着那个相对路径插件,任何一次 DeepSeek 请求都会在本地准备阶段必炸,跟模型、额度、网络、Cookie 统统无关。

两个很自然的问题也顺带解答了:

  • 为什么换模型就好了?因为「请求扩展准备」只存在于 DeepSeek 适配器里,MiniMax / GLM 等 provider 根本不跑这段代码。
  • 为什么之前改 web profile 没用?桌面版启动的是desktopprofile(~/.dsh/profiles/desktop),不是web;改错对象了。

四、复现与验证(隔离 DSH_HOME + 桌面自带运行时)

桌面版自带一个 CLI 包装器,用应用内那份不可变运行时:

:: <install>\resources\runtime\cli\bin\dsh.cmd set "ELECTRON_RUN_AS_NODE=1" "%~dp0..\..\..\..\DeepSeek Harness.exe" --expose-internals ^ "%~dp0..\..\..\app.asar\dsh\node_modules\@deepseek-ai\dsh-desktop-host\lib\cli.js" %*

为了不污染真实环境,复制一份独立DSH_HOME,建一个最小 profile(dsh-base+dsh-headless两个 bundle),默认模型指到deepseek-official/deepseek-flash,然后只改一个变量:有没有那个相对路径插件、profile 清单有没有version。

组相对插件./plugins/dummy.mjsprofilepackage.json结果
A✅无version❌REQUEST_EXTENSION: DeepSeek request extension preparation failed
B❌无version✅ 正常返回「你好」
C✅加"version": "0.0.0"✅ 正常返回「你好」

三组用同一条命令,只有 A 复现了用户看到的报错;B 证明「跟缺 version 无关,是相对插件触发的」;C 证明「补上 version 就没事了」。

$env:DSH_HOME = "<隔离目录>" & "<install>\resources\runtime\cli\bin\dsh.cmd" --profile td "只回复两个字:你好" # A 组输出: dsh: REQUEST_EXTENSION: DeepSeek request extension preparation failed

随后用真实 desktop profile 的补丁副本(含open-in-app-patched)重跑,也稳定拿到「你好」——修复在真实配置上成立。

五、修复:给 profile 清单补一个version

根因不在业务代码,而在 profile 清单少了一个字段。最小改动:

// ~/.dsh/profiles/desktop/package.json { "name": "dsh-profile-desktop", "version": "0.0.0", "private": true, "dependencies": {}, "dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"] } } }

只加了"version": "0.0.0"一行,其余不动;open-in-app的本地补丁继续保留(那是修另一个问题的)。改完重启 DSH 桌面版(profile 在启动时读取),再发一次 DeepSeek 消息即可,旧会话里的失败记录不影响新请求。

为什么不改插件、也不禁功能?禁用session-log-deepseek/plugin-package-inventory-deepseek也能绕过去,但那是关掉两个默认功能换来的「不报错」,属于治标;补version才是治本,且不损失任何能力。

六、给上游的建议

这其实是 DSH 运行时里的一处不一致:

  • initProfile()生成的是name有、version无的清单;
  • identityFromManifest(..., allowAnonymous=true)只放宽了「没有 name」的情况,对「有 name 没 version」是硬抛。

合理修法二选一:初始化器给 profile 清单补version,或让 inventory 插件把「无 version 的 profile 自身清单」也按匿名模块跳过。在那之前,profile 里挂了相对路径插件的用户都会中招,~/.dsh/profiles/desktop/package.json加version就是最省事的兜底。

小结

  • REQUEST_EXTENSION只是外壳,真正原因藏在被吞掉的cause里:DeepSeek 发请求前的「请求扩展准备」阶段有插件抛异常。
  • 触发链:desktopprofile 挂了相对路径插件./plugins/open-in-app.mjs→ 被解析到 profile 自身package.json→ 该清单缺version→plugin-package-inventory-deepseek抛错。
  • 只有 DeepSeek 适配器跑这段准备逻辑,所以「换模型就好、用 DeepSeek 必炸」。
  • 修复:给~/.dsh/profiles/desktop/package.json补"version": "0.0.0",重启桌面版。
  • 排查心法:报错只隔 ~20ms、且发生在 HTTP 之前 → 别怀疑网络和凭据,去翻「请求构造/准备」阶段的本地插件代码。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询