wilshi/dsh-external-api:给 DSH Desktop 加一个可选的对外 HTTP API

前言

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。

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜