需要 DSH >= 0.2.0-rc.2 < 0.3.0。在设置页配置任意数量的模型供应商:各自独立设置接口地址、密钥与逐模型参数(思考强度、图片输入、重试策略),支持 OpenAI Chat、OpenAI Responses、Anthropic Messages 与 Google Gemini 四种协议。
安装
# Release 预构建包
dsh plugin --profile web add "https://github.com/EmotionG/dsh-llm-config/releases/download/v0.2.0/dsh-llm-config-0.2.0.tgz"
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:EmotionG/dsh-llm-config
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
DeepSeek Harness 的多供应商模型配置插件。每个供应商独立配置接口地址、密钥与模型参数,支持 OpenAI Chat、OpenAI Responses、Anthropic Messages 与 Google Gemini。
兼容性
本插件适配 DSH 0.2.0-rc.2,即 DeepSeek Harness 桌面端 44.0.0 内置的运行时(@deepseek-ai/dsh-desktop-runtime@0.2.0-rc.2)。
peerDependencies 中的 @deepseek-ai/dsh-* 一律声明为 >=0.2.0-rc.2 <0.3.0,与桌面端市场的宿主兼容性闸门(satisfiesRange(runtimeVersion, range, { includePrerelease: true }))一致。0.1.x 运行时不兼容:本版本使用了 0.2 的消息模型与设置模型,详见下文「0.2 迁移要点」。
npm run check:market # 用桌面端市场自己的闸门校验上面的 peer 范围
该脚本从 scripts/extract-runtime.mjs 抽取出的桌面端运行时清单读取真实版本号,先运行一次抽取:
node scripts/extract-runtime.mjs # 从已安装的 app.asar 抽取运行时包到 .dsh-ref/
安装
从 GitHub 直接安装(源码构建,首次安装会请求一次构建授权):
dsh plugin --profile web add github:EmotionG/dsh-llm-config
或使用 Release 中的预构建 tarball,跳过构建授权:
dsh plugin --profile web add https://github.com/EmotionG/dsh-llm-config/releases/download/v0.2.0/dsh-llm-config-0.2.0.tgz
也可以把本包作为本地依赖挂进 profile:
{
"dsh": { "profile": { "bundles": ["dsh-llm-config"] } },
"dependencies": {
"dsh-llm-config": "file:插件目录"
}
}
安装后重启 dsh / 桌面端。设置页会出现「模型供应商」。
设置模型(0.2)
0.2 把「设置命名空间」的所有权从插件收回到框架:
- 插件的
package.json所在 profile 条目就是命名空间,条目 id 由本包的cordis.patch.yml固定为llm-config; - 插件导出的
Config(schemastery schema)就是条目 schema,设置服务用它校验写入并投影表单; providers字段带.volatile(),因此保存设置会把新快照提交进正在运行的插件(不重启),并通过loader/volatile-update通知本插件重建路由。
因此有三件事必须同时成立,test/channel.mjs 与 test/compat.mjs 会把它们钉住:
cordis.patch.yml的条目 id、宿主常量NS、客户端页面常量NS三者相等;Config至少有一个固定对象路径上的.volatile()字段——否则SettingsForms.volatileForm返回undefined,任何写入都会抛has no volatile fields(页面能渲染但存不下去);- 宿主与客户端都通过
remote.settings的{ok, value}信封读写,并按条目 id(不是settings.llm-config)寻址。
插件自带手写页面,所以宿主调用 ctx.settings.configure({ auto: false }) 关掉框架自动生成的表单。settings.llm-config 只作为文案(locale)命名空间存在。
图片输入(识图)
给模型的「输入」勾上 image 即声明它接受图片。声明后:
- 框架(dsh-llm)在请求组装时不再把图片投影成占位文本,而是原样交给本插件;
- 本插件通过 dsh 的持久附件服务把图片解析成确定性请求版本,再按协议内联进请求体:
- OpenAI Chat / Responses:
{type: "image_url", image_url: {url: "data:<media>;base64,..."}} - Anthropic Messages:
{type: "image", source: {type: "base64", media_type, data}} - Google Gemini:
{inlineData: {mimeType, data}}
- OpenAI Chat / Responses:
- 图片旁会附上模型可见的句柄文本(附件名与请求预览尺寸),与官方适配器的约定一致。
0.2 的附件接口要求完整的 ImageRequestTarget(width / height / maxBytes),因此本插件先用框架的 requestImageDimensions() 按像素预算算出目标尺寸再取版本,缓存键覆盖全部变换输入。
被框架标记为 offloaded 的图片不会作为字节发送:本插件在组装前先用 projectOffloadedImages 把它们投影成占位文本,因此这类请求既不解析请求版本,也不要求挂载附件服务。
未勾 image 的模型仍走框架的纯文本投影(图片替换为占位文本),这是框架行为,不是本插件拒绝。运行时如果部署没有挂附件服务,或模型未声明图片能力而请求带了图片,会以 UNSUPPORTED_CONTENT 显式失败,不会静默丢图。
工具调用与消息角色(0.2)
0.2 把工具结果提升为一等公民:
| 0.1.x | 0.2 | |
|---|---|---|
| 工具结果 | user 消息里的 tool-result 内容块 |
role: 'tool' 消息(toolCallId / isError) |
| 系统提示 | 只有 options.system |
options.system 或 历史里的首条 system 消息 |
| 工具声明变更 | 不存在 | developer 消息 + tool-addition / tool-removal 块 |
本插件的序列化按 0.2 口径重写:首条 system 消息进协议的系统槽(OpenAI 前置消息 / Anthropic 顶层 system / Gemini systemInstruction),更靠后的 system 消息并入 user 文本;连续的工具结果在 Anthropic 上合并进同一条 user 消息(并带 is_error),在 Gemini 上用调用方的函数名(而不是 call id)作为 functionResponse.name。
developer 消息承载的是四种 wire format 都无法表达的工具声明变更,本适配器以 UNSUPPORTED_CONTENT 显式拒绝(与官方适配器一致),而不是把它们当用户文本发出去。
请求失败重试
每个供应商可配置失败重试策略,默认有限次数 5 次(框架默认):
llm-config:
providers:
my-gateway:
retry: 3 # 数字简写:normal 模式,最多重试 3 次
another:
retry:
mode: always # 无限重试直到成功/取消
third: {} # 不写 retry:normal 模式默认 5 次
retry: <n>—mode: normal的简写,maxRetries: n;retry: {mode: always}— 无限重试(退避仍由框架执行);- 完整对象还接受
retryableCodes与backoff(initialDelayMs/maxDelayMs/jitterRatio),校验复用框架的resolveRetryPolicy,错误信息与 dsh 自带供应商插件一致。
重试只作用于瞬时错误(默认码:EMPTY_RESPONSE、RATE_LIMIT、SERVER、TIMEOUT、TRANSPORT);鉴权、配额等确定性错误不重试。执行者是宿主侧的 dsh-llm-retry 插件(agent 循环的请求恢复扩展点),本插件只负责声明策略——策略在路由注册时被框架捕获,因此修改 retry 后本插件会重建该路由,下一次请求即生效,无需重启。
设置页「高级选项」中提供「失败重试」下拉(有限次数 / 一直重试)与重试次数输入框;留空即默认 5。
自建 RPC 频道
models.dev 目录查询走插件自己的宿主 RPC 频道 /llm-config。0.2.0-rc.2 上必须用带 owner 的私有重载:
connection.register(scoped, RPC_CHANNEL, handler)
公开的 ctx.connection.rpc.handle(channel, handler) 取 owner = this.ctx,即连接服务自己的上下文(没有注入 webServer),于是 owner.effect(() => owner.webServer.register(route)) 抛 cannot get property "webServer" without inject;cordis 的服务追踪并不会把 this.ctx 重绑到读取方上下文,所以从注入了 webServer 的作用域读取服务也没用。本插件先尝试公开入口(宿主修正后自动切换),失败再退回上面这条路径,test/rpc.mjs 覆盖两种宿主线。
构建与测试
npm install
npm run build # tsc 编译宿主半 + esbuild 打包浏览器半
npm run typecheck # 宿主半 + 浏览器半
npm test # 全量测试(smoke/client/adapter/mount/sync/channel/rpc/enrich/runtime/compat/responses)
沙箱环境(子进程 stdio 管道被拒绝)下 build:client 会自动回退到 scripts/build-client-sandbox.mjs,直接以继承 stdio 调用 esbuild 二进制。
OpenAI Responses
openai-responses 走真正独立的 wire format(POST <baseURL>/responses),不再复用 Chat Completions 的请求体与事件词汇。形状对齐桌面端运行时自带的 @earendil-works/pi-ai@0.87.1 Responses 适配器。
请求:
| Chat Completions | Responses | |
|---|---|---|
| 消息 | messages[](role 消息) |
input[](item 列表) |
| 系统提示 | 首条 system 消息 |
顶层 instructions 字符串 |
| 助手文本 | message.content |
{type:'message', role:'assistant', content:[{type:'output_text'}]} |
| 工具调用 | tool_calls[] 挂在消息上 |
独立 {type:'function_call', call_id, name, arguments} item |
| 工具结果 | {role:'tool', tool_call_id} |
{type:'function_call_output', call_id, output} item |
| 工具声明 | {type:'function', function:{…}} |
扁平 {type:'function', name, description, parameters} |
| 文本/图片 part | text / image_url({url} 对象) |
input_text / input_image(URL 字符串) |
| 输出上限 | max_tokens |
max_output_tokens(API 最小值 16,低于即上调) |
| 停止串 | stop |
无此字段,配置的 stop 在此协议被丢弃(需要非标写法请用 extraBody) |
| 推理档位 | 顶层 reasoning_effort |
嵌套 reasoning.effort |
工具调用的 item id 被有意省略:它必须是 provider 自己的 fc_* item id 才能回放,而跨协议的历史里可能是 Chat 的 call_* 或 Anthropic 的 toolu_*,带上去会触发配对校验失败;call_id 才是与 function_call_output 配对的键。
流式事件按 output_index 寻址(不是 choices[0].delta),完整性的判据是终态事件 response.completed / response.incomplete / response.failed——该协议没有 [DONE] 哨兵(仍接受,因为部分网关会补发)。reasoning_summary_text.delta / reasoning_text.delta 进 reasoning 块,refusal.delta 作为可见文本呈现而不是丢弃。
终态到 finish reason 的映射,以及各自的可重试性:
| 终态 | finish reason | 默认策略下重试? |
|---|---|---|
completed(无工具调用) |
stop |
— |
completed(有工具调用) |
tool-calls |
— |
incomplete + max_output_tokens |
max-tokens |
— |
incomplete + 其他原因 |
error / INVALID_REQUEST |
否(确定性失败) |
response.failed |
error / SERVER |
是 |
SSE error 事件 |
error / SERVER |
是 |
usage 与 Chat Completions 一样把 input_tokens_details.cached_tokens / cache_write_tokens 从 input_tokens 里减掉,保持三个计数互不重叠;output_tokens_details.reasoning_tokens 单独上报。
协议自身的错误终态优先于适配器的「只推理无可见文本」兜底:后者不会再追加第二个 finish 块覆盖 provider 的判定。
已知缺口
无已知的 wire-format 缺口。openai-responses 之外未实现的 Responses 特性(store / previous_response_id 状态化续写、include: ["reasoning.encrypted_content"] 的推理回放、custom/grammar 工具)都不在本插件的配置面内;需要时可通过模型的 extraBody 注入额外字段。
链接
同类插件
Ebony-Vinyl/dsh-our-free-model★ 55971
为 dsh 接入免费模型车道(含 DeepSeek V4.1 Flash、Kimi K3 等),无需登录、注册或填写 API Key,并提供本地 OpenAI 兼容网关、降级路由与用量看板。
dingminhua/dsh-connect-workbuddy★ 993
将本机登录的 WorkBuddy 模型(国内版与国际版)接入 DSH,每次刷新按上游目录读取每个模型的积分倍率,支持逐模型图片输入开关、带自动换号的账号池、手动每日签到,以及卡片与输入框里的只读积分胶囊读数。
Alice-Marx/dsh-model-router★ 603
为 DeepSeek Harness 规划有依赖的工作包,将其分配给已配置的模型 API 或支持的厂商 CLI,并提供费用预览。
V1ki/dsh-plugin-subscriptions★ 546
把 ChatGPT(Codex)、Claude、Grok 订阅当作 DeepSeek Harness 的 LLM 提供方:设置页登录、模型目录、用量展示,以及 image_generate、video_generate 与 x_search 工具。
Mars-Sea/dsh-commandcode-provider★ 446
非官方 Command Code 模型接入插件:注册 `commandcode` 路由,带实时模型目录与推理强度支持。
FishBottle7/opencode2dsh★ 384
将 OpenCode Zen 免费模型接入 DeepSeek Harness,无需 API Key。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。