前言¶
DSH Desktop 本身是交互式客户端,”让别的程序也能驱动它”通常要么改客户端源码,要么直接在宿主环境里塞自写脚本,维护成本都不低。WilShi/dsh-external-api 走另一条路:在 DSH 进程内挂一个可选的 HTTP 服务,默认关闭、默认只监听 127.0.0.1,用 Bearer Token 鉴权,对外暴露会话、Run、SSE 事件这些能力。维护者是 WilShi。下面是介绍。
这是什么¶
一句话定位:DSH Desktop 的可选外部 HTTP API,面向本机与可信局域网的集成场景。
插件包名为 dsh-external-api,当前版本 0.2.4,MIT 许可证,Node 要求 ^22.19.0 || ^24.0.0。它兼容 DSH 0.2.1-rc.5(Host protocol v4)与 Cordis 4.0.5,插件形态是 Bundle(dsh.bundle.patch)加 Client inject(settings.section 槽位),也就是安装后要在 DSH 设置页里点启用,而不是改配置文件。
它不新开一套宿主环境。所有 @deepseek-ai/* 宿主包都声明为 peerDependencies,由 DSH 运行时提供;插件自身不安装、也不会带出第二套 cordis 单例。必需的宿主服务(inject)有 sessionController、workspaceController、agentPresets、sessionSkillCatalog、webServer。运行时依赖只有 @deepseek-ai/dsh-atomic-write(原子写)和 @deepseek-ai/schemastery(配置 schema)。
核心功能¶
1、默认关闭。服务没有启用前不监听任何端口。
2、默认只监听本机。启用后 host 默认是 127.0.0.1,只有本机能访问;改成 0.0.0.0 或某个局域网地址才对网络开放,此时启动日志会打印一条网络暴露告警。
3、Bearer Token 鉴权。除健康检查和 CORS 预检外,所有接口都要求 Authorization: Bearer <token>。
4、资源模型是 Session / Run / 事件流:列会话、建会话、发起 Run、查 Run 状态、订阅 SSE、取消 Run。
5、Token 级隔离。每个 Token 只能访问自己创建的 Session、Run 和 SSE 流。
6、可订阅的事件类型固定:run.queued、run.started、run.cancelling、message.delta、message.completed、tool.started、tool.completed、run.completed、run.cancelled、run.failed,SSE 支持 Last-Event-ID 断线续传。
7、能力探测接口。/api/v1/capabilities 返回当前 Token 能看到什么,调用方可以先读它再决定后续请求。
8、撤销即失效。Token 撤销会立即断开它的 SSE 连接、尝试取消活动 Run,并删除其会话归属记录。
安装与启用¶
安装命令只有一条,来自 README:
dsh plugin --profile web add dsh-external-api
安装后重启 DSH,然后在「设置 → API 服务」里启用。之后按页面提示填端口(默认 47831),为调用方创建独立 Token,并立即保存只显示一次的明文。
开头先验证一下服务¶
端口按默认 47831,Token 已写入环境变量 DSH_API_TOKEN。这一步的作用是确认 Listener 已起来、Token 有效,并看清这个 Token 具备哪些权限。
curl.exe http://127.0.0.1:47831/api/v1/capabilities `
-H "Authorization: Bearer $env:DSH_API_TOKEN"
创建一个会话并发起一次 Run¶
最省事的路径是 POST /api/v1/responses:一次请求同时建会话、发 Run。下面用 Windows PowerShell 演示,局域网设备把 127.0.0.1 换成 DSH 主机的局域网 IPv4。
$body = @{ input = '请概括当前项目的结构' } | ConvertTo-Json -Compress
Invoke-RestMethod `
-Method Post `
-Uri 'http://127.0.0.1:47831/api/v1/responses' `
-Headers @{ Authorization = "Bearer $env:DSH_API_TOKEN" } `
-ContentType 'application/json' `
-Body $body
响应状态是 202,里面包含 session、run 和 eventsUrl。下一步拿这个 Run ID 去读实时事件:
curl.exe -N http://127.0.0.1:47831/api/v1/runs/RUN_ID/events `
-H "Authorization: Bearer $env:DSH_API_TOKEN"
接口与 Scope¶
完整接口表(README 原文,Scope 名称照抄):
| 方法 | 路径 | 作用 | 所需 Scope |
|---|---|---|---|
GET / HEAD |
/api/v1/health |
Listener 健康检查 | 无 |
GET |
/api/v1/capabilities |
能力、限制和当前 Token 权限 | capabilities:read |
GET |
/api/v1/workspaces |
Token 可使用的工作区 | capabilities:read |
GET |
/api/v1/agents |
Token 可使用的 Agent Preset | capabilities:read |
GET |
/api/v1/models?sessionId=... |
会话可用模型 | capabilities:read |
GET |
/api/v1/skills?sessionId=... |
会话可用技能 | capabilities:read |
GET |
/api/v1/sessions |
列出当前 Token 创建的会话 | sessions:read |
POST |
/api/v1/sessions |
创建会话 | sessions:write |
GET |
/api/v1/sessions/:id/messages |
获取脱敏后的消息历史 | sessions:read |
POST |
/api/v1/sessions/:id/messages |
发起 Run | runs:write |
POST |
/api/v1/sessions/:id/runs |
发起 Run(语义等同上一项) | runs:write |
POST |
/api/v1/responses |
创建会话并发起 Run | sessions:write、runs:write |
GET |
/api/v1/runs/:id |
查询 Run 状态 | runs:read |
GET |
/api/v1/runs/:id/events |
订阅 Run 的 SSE 事件 | runs:read |
POST |
/api/v1/runs/:id/cancel |
取消 Run | runs:write |
创建会话时可以指定白名单里的 workspaceId 和 agentPreset:
{
"workspaceId": "workspace-id",
"agentPreset": "agent-preset-id"
}
发起 Run 的请求体:
{
"input": "分析这个问题",
"clientTimeZone": "Asia/Shanghai"
}
浏览器端订阅 SSE¶
浏览器原生的 EventSource 不能设置 Bearer Header,需要改用 fetch 流。注意:浏览器还必须位于服务允许的 Origin 白名单中,而当前设置页尚未开放这项配置,普通网页默认会被拒绝;下面代码只适用于已经由发行版或受控配置启用对应 Origin 的环境。
const response = await fetch(`${baseUrl}/api/v1/runs/${runId}/events`, {
headers: { Authorization: `Bearer ${token}` },
})
if (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`)
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()
for (;;) {
const { value, done } = await reader.read()
if (done) break
console.log(value) // 正式客户端应按 SSE 帧解析,而不是假定一次读取就是一个事件
}
错误格式¶
外部接口使用稳定的 JSON 错误结构,不回传内部异常或宿主错误详情。客户端应以 HTTP 状态码和 error.code 作为程序判断依据。
{
"error": {
"code": "AUTH_INVALID",
"message": "The bearer token is invalid.",
"requestId": "..."
}
}
安全边界¶
这一节建议先读完再决定要不要改监听地址。
1、默认只绑定 127.0.0.1。显式把 host 设为 0.0.0.0(或某个局域网地址)才会对网络开放。它不复用、也不转发 DSH 主站的 /api 路由。
2、Token 明文只在创建时返回一次,持久化配置里只保存 SHA-256 摘要。
3、工作区与 Agent Preset 默认不允许调用方自行指定,需要显式加入 Token 白名单;白名单非空时,创建 Session 必须显式指定被允许的 workspaceId / agentPreset,省略即 403。
4、对外开放范围有明确上限:Slash Command、任意内部 RPC、工具参数、工具结果正文、系统提示和推理内容都不会对外开放。
5、Host 必须与客户端实际访问的 IPv4 和端口一致;Origin、Fetch Metadata、请求体大小、请求速率、并发 Run 和 SSE 连接数也都有限制。活动 Run 默认上限是每 Token 10 个、全局 20 个;旧版精确的 2/4 默认组合会在启动时自动升级,其他自定义组合不变。
6、管理接口只挂在 DSH 自身的随机回环端口,并校验同源浏览器请求,用于防止网页跨站调用;它不把同一系统账户下的恶意本机进程当作可隔离的安全边界。
7、管理接口写入的配置保存在 $DSH_HOME/external-api/config-v1.json(目录权限 0700、文件 0600),与 Token 归属记录 ownership-v1.json 分开存放;加载器通过 cordis.patch.yml 下发的 config 作为基础层,管理页的编辑覆盖其上。
8、外部 API 是明文 HTTP。只在受信任的专用网络中启用,用系统防火墙把入站来源限制到所需私网;不要把端口直接映射或暴露到公网。跨不受信任网络接入时,应使用 VPN 或受信任的 TLS 反向代理并限制来源网络。
适用场景与注意¶
适合的场景:想在本机给 DSH Desktop 接一个外部编排脚本、Web 前端或局域网内的另一台设备,并且希望按 Token 粒度控制谁能建会话、谁能跑 Run。
需要留意的地方:
1、插件以当前 DSH 进程的权限运行,能访问会话、工作区和工具执行结果,等于拥有该进程的同等能力。安装前先读源码,确认 MIT 许可证与你的分发方式兼容,再看 cordis.patch.yml 下发了哪些配置。
2、它不是官方产品,目录页是社区站点,与 DeepSeek / 幻方没有官方从属关系。
3、从 0.2.1-rc.5 起的 DSH Host protocol v4 才在这个兼容范围内,用在其他版本上需要先确认。
4、Token 明文只显示一次,创建时丢失只能重新生成。
结尾¶
这套接口的价值在于把”驱动 DSH”变成一次普通的 HTTP 调用,同时用默认本机绑定、Token 级隔离和明确的对外暴露上限把风险压在可控范围里。目录页在 https://www.skillhub.cn/plugins/WilShi/dsh-external-api,仓库在 https://github.com/WilShi/dsh-external-api。