🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

HTTP 接口网关 toddpan/dsh-webapi

DeepSeek Harnessv0.6.0
toddpan6 天前有提交已提供中文文档扫描:含敏感能力

第一次用?先看图文版 使用教程。

37.9/100
综合分
3
GitHub 星标
1,219
npm 周下载
—/5
用户评分

数据截至 2026/10/29 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-web-service
npm 源 · dsh-web-service 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · chat
装得上吗
本站已在 dsh 0.2.1-rc.5 下真实安装成功(非静态推断);本次验证针对插件 v0.6.1,当前版本为 v0.6.0
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 6 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-web-service @ 0.6.0
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/1 12:55:19

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件发起外部网络请求
查看扫描证据(共 117 处,展示前 75 条)
  • 读取本地文件
    lib/files.js:106
    while (existsSync(candidate)) {
  • 读取本地文件
    lib/files.js:172
    if (!existsSync(target)) {
  • 读取本地文件
    lib/files.js:177
    const st = await stat(target);
  • 读取本地文件
    lib/files.js:182
    const dirents = await readdir(target, { withFileTypes: true });
  • 读取本地文件
    lib/files.js:189
    const fst = await stat(full);
  • 读取本地文件
    lib/files.js:235
    if (!existsSync(target)) {
  • 写入本地文件
    lib/files.js:305
    await mkdir(cwd, { recursive: true });
  • 写入本地文件
    lib/files.js:309
    await writeFile(target, f.data);
  • 写入本地文件
    lib/files.js:370
    await mkdir(stageDirOf(cwd), { recursive: true });
  • 写入本地文件
    lib/files.js:372
    await writeFile(paths.part, Buffer.alloc(0));
  • 写入本地文件
    lib/files.js:374
    await writeFile(paths.meta, JSON.stringify(meta));
  • 写入本地文件
    lib/files.js:422
    await appendFile(paths.part, chunk);
  • 删除本地文件
    lib/files.js:455
    await rm(paths.meta, { force: true });
  • 删除本地文件
    lib/files.js:479
    await rm(paths.part, { force: true });
  • 删除本地文件
    lib/files.js:480
    await rm(paths.meta, { force: true });
  • 读取本地文件
    lib/fs.js:42
    const dir = await opendir(target);
  • 读取本地文件
    lib/fs.js:54
    const st = statSync(fullPath);
  • 读取本地文件
    lib/fs.js:68
    const st = await stat(fullPath);
  • 读取本地文件
    lib/fs.js:123
    if (!existsSync(target)) {
  • 读取本地文件
    lib/fs.js:127
    const st = statSync(target);
  • 读取本地文件
    lib/fs.js:155
    if (!existsSync(target)) {
  • 写入本地文件
    lib/fs.js:210
    await mkdir(target, { recursive: true });
  • 删除本地文件递归
    lib/fs.js:236
    await rm(target, { recursive: true, force: true });
  • 写入本地文件
    lib/fs.js:292
    await writeFile(candidate, f.data);
  • 读取本地文件
    lib/skills.js:72
    if (!existsSync(skillPath))
  • 读取本地文件
    lib/skills.js:75
    const raw = await readFile(skillPath, 'utf-8');
  • 读取本地文件
    lib/skills.js:108
    if (!existsSync(skillPath))
  • 读取本地文件
    lib/skills.js:111
    const raw = await readFile(skillPath, 'utf-8');
  • 读取本地文件
    lib/skills.js:134
    const targetDir = existsSync(dir) ? dir : undefined;
  • 读取本地文件
    lib/skills.js:135
    if (!targetDir && !existsSync(flat))
  • 删除本地文件
    lib/skills.js:152
    readStream.on('end', () => { void rm(tmpFile, { force: true }).catch(() => { }); });
  • 写入本地文件
    lib/skills.js:252
    await writeFile(skillPath, newRaw, 'utf-8');
  • 删除本地文件递归
    lib/skills.js:274
    await rm(target, { recursive: true, force: true });
  • 写入本地文件
    lib/skills.js:297
    await mkdir(rootPath, { recursive: true });
  • 删除本地文件递归
    lib/skills.js:304
    await rm(tmpDir, { recursive: true, force: true }).catch(() => { });
  • 删除本地文件递归
    lib/skills.js:317
    await rm(tmpDir, { recursive: true, force: true }).catch(() => { });
  • 删除本地文件递归
    lib/skills.js:323
    await rm(tmpDir, { recursive: true, force: true }).catch(() => { });
  • 删除本地文件递归
    lib/skills.js:360
    await rm(targetDir, { recursive: true, force: true }).catch(() => { });
  • 写入本地文件
    lib/skills.js:361
    await mkdir(targetDir, { recursive: true });
  • 写入本地文件
    lib/skills.js:364
    await writeFile(path.join(targetDir, 'SKILL.md'), await readFile(located.skillPath));
  • 写入本地文件
    lib/skills.js:383
    await writeFile(d, await readFile(s));
  • 写入本地文件
    lib/skills.js:390
    await mkdir(targetDir, { recursive: true });
  • 读取本地文件
    lib/plugins.js:22
    if (existsSync(candidate)) {
  • 读取本地文件
    lib/plugins.js:24
    return { root: dir, pkg: JSON.parse(readFileSync(candidate, 'utf8')) };
  • 读取本地文件
    lib/plugins.js:36
    if (!existsSync(p))
  • 读取本地文件
    lib/plugins.js:39
    const pkg = JSON.parse(readFileSync(p, 'utf8'));
  • 读取本地文件
    lib/openapi.js:19
    body = readFileSync(join(SWAGGER_ASSET_DIR, file));
  • 写入本地文件
    lib/api-keys-store.js:176
    await mkdir(this.dir, { recursive: true, mode: 0o700 });
  • 读取本地文件
    lib/api-keys-store.js:185
    raw = await readFile(this.file, 'utf8');
  • 写入本地文件
    lib/api-keys-store.js:272
    await rename(this.file, `${this.file}.corrupt-${Date.now()}`);
  • 读取本地文件
    lib/api-keys-store.js:280
    return await readFile(`${this.file}.bak`, 'utf8');
  • 读取本地文件
    lib/api-keys-store.js:321
    const raw = (await readFile(this.adminTokenFile, 'utf8')).trim();
  • 写入本地文件
    lib/api-keys-store.js:324
    await chmod(this.adminTokenFile, 0o600).catch(() => undefined);
  • 写入本地文件
    lib/api-keys-store.js:333
    await writeFile(this.adminTokenFile, token, { mode: 0o600 });
  • 写入本地文件
    lib/api-keys-store.js:611
    await mkdir(this.dir, { recursive: true, mode: 0o700 });
  • 写入本地文件
    lib/api-keys-store.js:613
    await writeFile(tmp, data, { mode: 0o600, flag: 'wx' });
  • 读取本地文件
    lib/service-settings.js:51
    const raw = await readFile(this.file, 'utf8');
  • 写入本地文件
    lib/service-settings.js:94
    await mkdir(dir, { recursive: true, mode: 0o700 });
  • 写入本地文件
    lib/service-settings.js:95
    await writeFile(tmp, JSON.stringify(next, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
  • 写入本地文件
    lib/service-settings.js:96
    await rename(tmp, this.file);
  • 写入本地文件
    lib/service-settings.js:97
    await chmod(this.file, 0o600).catch(() => undefined);
  • 读取本地文件
    lib/version.js:10
    const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
  • 发起网络请求
    lib/updates.js:50
    const resp = await fetch(RELEASE_API, {
  • 读取本地文件
    lib/skill-utils.js:44
    return { ok: true, root: { kind: 'project', path: existsSync(agents) ? agents : dsh } };
  • 读取本地文件
    lib/skill-utils.js:59
    if (existsSync(path.join(current, '.git')))
  • 写入本地文件
    lib/skill-utils.js:202
    await mkdir(destDir, { recursive: true });
  • 写入本地文件
    lib/skill-utils.js:240
    await mkdir(path.dirname(target), { recursive: true });
  • 写入本地文件
    lib/skill-utils.js:241
    await writeFile(target, out);
  • 写入本地文件
    lib/skill-utils.js:281
    await writeFile(tmp, buffer);
  • 写入本地文件
    lib/skill-utils.js:282
    await mkdir(destDir, { recursive: true });
  • 删除本地文件
    lib/skill-utils.js:293
    await rm(tmp, { force: true }).catch(() => { });
  • 读取本地文件
    lib/skill-utils.js:299
    if (!existsSync(root))
  • 读取本地文件
    lib/skill-utils.js:303
    entries = await readdir(root);
  • 读取本地文件
    lib/skill-utils.js:312
    st = await stat(full);
  • 读取本地文件
    lib/skill-utils.js:321
    if (!existsSync(skillPath))
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/schemastery@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-tools
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 开放REST与SSE接口
  • 兼容OpenAI端点
  • 供三方客户端集成
展开完整介绍(185 字)↓

DSH 插件:通过 HTTP 驱动 DeepSeek Harness,提供 47 条 REST + SSE 路由(工作区、会话、流式输出、统计、待办、模型、设置、技能、文件),外加 OpenAI 兼容的 /chat/completions。 / DSH 插件:把 DSH 开放成 47 条 REST + SSE 接口与 OpenAI 兼容端点,供三方客户端与智能体集成。

v0.6.12026-10-27
· v0.6.1 · 流式对话断流修复(对齐 pi-web-service 修复) 修复三方集成中「>180s 长对话必然断流」的一组问题(与 pi-web-service 的修复同语义)。 · 什么是坏了 · 同步入口(POST /sessions/:id/prompt、OpenAI 非流式 chat/completions)按 180s 整轮总耗时超时:长文本生成、多轮工具调用的轮次在 180 秒整被静默掐断,调用方只拿到半截内容(且 HTTP 200、ok:true)。 · SSE 通道(prompt-stream / events / OpenAI 流式)无任何心跳:思考或工具执行的长沉默期会被 Nginx / 网关 / 浏览器空闲策略静默掉线。 · prompt-stream 停滞无终点:轮次卡死时客户端永远挂在打开的 SSE 上。 · 事件处理的静默 catch 让断流无声无息,无从排查。 · OpenAI 流式在 prompt 提交失败时因「响应头已发出」走不到错误响应,客户端挂在空流上。 · 修复语义 停滞看门狗取代总时长上限 —— 每收到一个会话事件就重置计时;持续产出(哪怕远超数分钟)绝不打断;只有持续收不到任何会话事件(真停滞)才显式报 turn stalled 收流。 · timeoutMs(同步入口):语义由「总时长上限」改为「停滞阈值」,默认 180s,完全兼容。 · stallTimeoutMs(prompt-stream 新增):停滞收流阈值,默认 300s。 · 所有 SSE 通道每 15s 下发 ': ping' 注释帧心跳(标准 SSE / OpenAI 解析器均忽略),收流时自动停止。 · 流内任意内部错误以 error 事件显式可见并补 done 收流。 · OpenAI 流式提交失败按 OpenAI 错误块 + [DONE] 收流。 · 验证 离线回归 scripts/verify-stream-fix.mjs 四场景全过: 1. prompt-stream 完整流 + 16.5s 全静默期心跳保活; 2. 停滞看门狗显式 error(turn stalled) + done 收流; 3. 同步 prompt 整轮耗时大于阈值时完整返回全部内容(旧语义半途截断); 4. OpenAI 流式提交失败收到错误块 + [DONE],不再空流挂死。 · 安装 / 升级 · GitHub Release tarball dsh plugin --profile web add \ · npm npm i dsh-web-service@0.6.1 Full Changelog: v0.5.0...v0.6.1(含 v0.6.0:GET /plugins 插件清单接口;以及本版断流修复)。
v0.5.02026-10-25
· 独立 API 服务启停重构 设置页原来的「服务端口」只有一个输入框和「保存并立即生效」,这次把它重做成一个完整的服务面板: 首次安装自动分配端口并启动,用户可随时停止 / 启动,API 地址随表单实时变化。 · 交互 · 状态一目了然:● 运行中 127.0.0.1:31800 / 已停止 · 独立端口未启动 · 主端口 XXXXX 仍可访问 · API 地址动态显示:改端口或切换监听范围时地址实时预览,标签在 运行中 / 待应用 / 预览 间切换;运行中显示真实地址,一键复制 · 动作跟随状态:启动服务 → 运行中改动后变 应用并重启,无改动显示 已应用;停止服务 独立可用 · 端口留空 = 沿用当前端口(占位符灰显当前值),非法端口就地报错;自动 按钮向服务端要一个空闲端口 · 监听范围:127.0.0.1(仅本机)/ 0.0.0.0(局域网,自动列出每条局域网地址并给出安全提醒) · 绑定失败(端口占用等)返回 409,原监听回滚、现状不变,错误原因就地展示 · 行为 · 安装即用:首次启动自动分配空闲端口(31800–31899,占满回退系统端口)并自动监听 · 状态持久化:运行中的服务在 DSH 重启后自动恢复同一端口;主动停止后重启仍保持停止,端口保留 · 独立服务与 DSH 主 webserver 解耦,/system/status 与 dsh_web_service_info 都回带稳定端口与 URL · 接口 | 操作 | 接口(管理令牌鉴权) | |---|---| | 读取状态 | GET /api/v1/api-keys/service | | 启动 / 换端口 / 换监听范围 | POST /api/v1/api-keys/service/start | | 停止 | POST /api/v1/api-keys/service/stop | | 建议空闲端口 | GET /api/v1/api-keys/service/free-port | | 旧版兼容 | GET / PUT /api/v1/api-keys/port(fixedPort: 0 停止) | · 其他 · 去掉与 ONENAT / WorkBuddy 的关联文案和冗余说明,插件保持独立;GUI 侧边栏入口改为「API 服务」 · 新增 service-settings 存储,旧版 fixedPort 配置读取时自动迁移 · 新增 43 项服务生命周期冒烟测试;README / INSTALL / 教程 / SKILL 与设置页截图同步更新 · 安装 dsh plugin --profile add dsh-web-service · 或指定 tarball dsh plugin --profile add ./dsh-web-service-0.5.0.tgz npm: npm i dsh-web-service@0.5.0
v0.3.62026-10-08
· v0.3.6 — 会话支持单独调推理强度与模式预设 agentPreset · Sessions API · 建会话 POST /sessions 新增 reasoningEffort、agentPreset 字段 · 改会话 PATCH /sessions/:id - reasoningEffort 可与 provider/model 同传,也可单独传(与当前模型选择合并,无选择记录时返回 NO_MODEL_SELECTION) - agentPreset 宿主支持运行时应用则立即生效;否则仅记录并返回 data.preset.applied=false(上层任务级持久化兜底) · 查会话 GET /sessions/:id 回带 agentPreset(宿主元数据优先,插件运行时记录兜底) · OpenAPI 规范同步更新 · 自 v0.3.2 以来的其他变更 · feat(api-keys): API Key 列表支持「复制」已创建的 Key(v0.3.5) · fix(pages): 修复 GUI 侧边栏内 SWAGGER 等按钮点击无效(v0.3.4) · feat(pages): 三个 SSR 页面统一 HARNESS 主题;Swagger UI 改为自托管(v0.3.3) · fix(dist): 提交 lib/ 构建产物,修复从 git 仓库安装后插件不激活 安装(DSH 插件市场 / GitHub 仓库): dsh plugin add @dsh-external/dsh-web-service@0.3.6
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-webapi (dsh-web-service) 第一次用?先看图文版 使用教程。 把 DeepSeek Harness 通过 HTTP 开放出来:DSH 里的工作区、会话、模型、设置、技能与文件直接变成 REST 接口与 SSE 流,再加一个 OpenAI 兼容的 /chat/completions,现成的 OpenAI 客户端不用改就能接到 DSH。 典型用法:给 DSH 做一个自托管控制台、桌面或移动端客户端、驱动 Agent 的 CI/自动化流程、把 DSH 当成工具调用的其它 Agent,或在 DSH 前面再包一层自己的 API 网关。 - 零新增依赖:插件只 inject webServer 与 tools,路由挂在 DSH 宿主的 webserver 上,并可另开一个可随时启停的独立固定端口服务。 - 80+ 条路由(REST + SSE),内置 OpenAPI 3.0 规范、在线调试页,以及可启停独立 API 服务的设置页。 - 自带 SKILL:skills/dsh-web-service/SKILL.md 教会 DSH 里的 Agent 调用本 API(流式、中止、回答挂起问题、上传文件等)。
展开完整文档(README 剩余 1 节)
dsh-webapi (dsh-web-service)

第一次用?先看图文版 使用教程。

把 DeepSeek Harness 通过 HTTP 开放出来:DSH 里的工作区、会话、模型、设置、技能与文件直接变成 REST 接口与 SSE 流,再加一个 OpenAI 兼容的 /chat/completions,现成的 OpenAI 客户端不用改就能接到 DSH。

典型用法:给 DSH 做一个自托管控制台、桌面或移动端客户端、驱动 Agent 的 CI/自动化流程、把 DSH 当成工具调用的其它 Agent,或在 DSH 前面再包一层自己的 API 网关。

- 零新增依赖:插件只 inject webServer 与 tools,路由挂在 DSH 宿主的 webserver 上,并可另开一个可随时启停的独立固定端口服务。
- 80+ 条路由(REST + SSE),内置 OpenAPI 3.0 规范、在线调试页,以及可启停独立 API 服务的设置页。
- 自带 SKILL:skills/dsh-web-service/SKILL.md 教会 DSH 里的 Agent 调用本 API(流式、中止、回答挂起问题、上传文件等)。

安装

兼容 DSH 0.1.0–0.2.x(peer 范围见 package.json;已在 0.1.7-rc.2 上运行验证,并在 0.2.0-rc.2 上通过类型检查与运行时装配验证)。

方式一:预构建 tarball(推荐,免构建、免 allowBuilds 授权)

dsh plugin --profile web add \
https://github.com/toddpan/dsh-webapi/releases/latest/download/dsh-web-service.tgz

dsh plugin 会把该包写进 profile 的 package.json(依赖 + dsh.profile.bundles);带 HMR 的 profile 会立即装配,否则重启 DSH 生效。探活:

curl -s http://127.0.0.1:3080/api/v1/system/status # {"ok":true,...}
curl -sI http://127.0.0.1:3080/api/v1/docs # HTTP/1.1 200 OK

升级提示。 资产名不带版本号,latest/download/dsh-web-service.tgz 对全新安装永远指向最新构建;但包管理器可能复用该 URL 的旧解析结果。要把已有安装切到确定版本,用带版本号的资产:
dsh plugin --profile web add https://github.com/toddpan/dsh-webapi/releases/download/v0.1.11/dsh-web-service-0.1.11.tgz

方式二:直接从 git 仓库安装(插件管理器的「GitHub 仓库地址」安装方式)

dsh plugin --profile web add github:toddpan/dsh-webapi

lib/ 已入库,所以从 git 装的包可直接运行。lib/ 与 src/ 保持同步——改动 src/ 后,请在同一次改动里重新构建并提交 lib/。

方式三:从源码构建(需要一份 DSH 源码 checkout)

git clone https://github.com/toddpan/dsh-webapi && cd dsh-webapi
DSH_CHECKOUT=/path/to/deepseek-harness bash scripts/build.sh # src/ → lib/
dsh plugin --profile web add "$PWD"

scripts/build.sh 会从 checkout 软链 cordis / schemastery / @deepseek-ai/ 等 peer,无需 npm install。

配置

全部可选,装好后写在 profile patch 的插件行 config 里(默认值见 src/index.ts):

| 字段 | 默认 | 说明 |
| pathPrefix | /api/v1 | 路由前缀 |
| apiKey | '' | 非空则开启鉴权,请求需带该 key |
| standalonePort | 0 | 首次安装时的初始端口;0 = 自动分配(31800-31899)。分配后以设置页为准,配置不再覆盖 |
| standaloneBindIp | 127.0.0.1 | 首次安装时的初始监听范围:127.0.0.1(仅本机)或 0.0.0.0(局域网可达);之后以设置页为准 |
| cors | true | 是否允许跨域 |
| defaultCwd | '' | 默认工作目录,留空取 process.cwd() |
| maxUploadBytes | 2 GiB | 上传大小上限,大文件建议走分片接口 |
| adminRemoteAccess | false | 是否允许非回环地址访问 API Key 管理接口;默认仅本机可管理密钥 |

apiKey 只是「遗留单 Key」,向后兼容用。日常管理请到设置页生成/吊销多条 Key,详见 API Key 管理。

API 服务(独立固定端口)

插件自带一个独立的 HTTP 服务,与 DSH 主 webserver 解耦——主端口每次启动都会变,独立服务端口固定,适合写进三方客户端、脚本或反向代理。

- 首次安装自动分配端口:从 31800 起找空闲端口,分配后固定不变。
- 在设置页启停:GET /api/v1/settings/api-keys → 「API 服务」卡片;端口与监听范围(127.0.0.1 仅本机 / 0.0.0.0 局域网)随时可改,即时生效、无需重启 DSH。
- API 地址实时显示:改端口或监听范围时,地址区随表单实时预览;运行中显示真实地址,可一键复制。
- 状态持久化:运行中的服务在 DSH 重启后自动恢复同一端口;停止后重启 DSH 仍保持停止。

设置文件:<DSH_HOME>/dsh-web-service/service-settings.json(0600)。旧版 fixedPort 格式会在读取时自动迁移。

| 操作 | 接口(管理令牌鉴权) |
| 读取状态 | GET /api/v1/api-keys/service |
| 启动 / 换端口 / 换监听范围 | POST /api/v1/api-keys/service/start { "port": 31800, "bindIp": "127.0.0.1" } |
| 停止 | POST /api/v1/api-keys/service/stop |
| 建议空闲端口 | GET /api/v1/api-keys/service/free-port |
| 旧版兼容 | GET / PUT /api/v1/api-keys/port(fixedPort: 0 表示停止) |

启动绑定失败(端口被占用等)返回 409,原监听回滚,现状不变。

API Key 管理(设置页)

打开 GET /api/v1/settings/api-keys 即可在一个页面里管理独立 API 服务与三方调用鉴权的密钥:查看(脱敏)、新建、轮换、吊销、开启/关闭鉴权。/docs 页头部也有「管理 API Key」入口。

三个入口

| 入口 | 说明 |
| DSH Web GUI 侧边栏「API 服务」 | 插件自带浏览器半边(client half),装好并重启 DSH 后出现在侧边栏,内嵌本页 |
| GET /api/v1/settings/api-keys | 独立页面,可直接收藏 |
| /docs 页头「管理 API Key」按钮 | 从 API 文档一键跳转 |

页面按管理令牌闸门设计:密钥管理与普通 API 调用是两套凭证。首次打开会要求输入管理令牌,页面会直接给出取令牌的命令(在运行 DSH 的机器上执行): cat <DSH 配置根>/dsh-web-service/admin-token,粘贴一次后保存在浏览器 localStorage。
未通过闸门时,页头的「新建 API Key」等管理按钮一律收起——不会出现「点了按钮才报 401」的死胡同。

页面主题与离线可用

/docs、/docs/reference、/settings/api-keys 三个页面统一使用 HARNESS 设计令牌 (--dsw-alias-,取值镜像 packages/client/ui-theme/src/styles/design-platform.css),因此配色、圆角、字号与 DSH Web GUI 一致,并同时支持浅色 / 深色:

- 内嵌在 GUI 侧边栏时(同源 iframe)直接读取父页面的令牌计算值并实时跟随其明暗切换,用户自定义主题同样生效;
- 独立打开时按系统 prefers-color-scheme,也可用 ?theme=light / ?theme=dark 强制。

Swagger UI 资产随包自托管在 vendor/swagger-ui/,由 /api/v1/docs/assets/* 提供, 不依赖任何公共 CDN,内网或离线环境同样可以打开 /docs/reference。

内嵌在 GUI 侧边栏时,页内的「SWAGGER / 查看 OpenAPI JSON / 交互式文档」等按钮会在面板内跳转(目标页给出「← 返回」按钮)。原因:DSH Desktop 主窗口显式拒绝一切新窗口
(webContents.setWindowOpenHandler(() => ({ action: 'deny' }))),target="_blank" 在应用内是
静默失败;外部链接(GitHub / 问题反馈)仍会尝试新窗口,被拒时提示复制链接。

新建时有两种 Key 值来源,对应 POST /api-keys 的 plaintext 字段:

| 方式 | 行为 |
| 随机生成(默认) | 服务端生成 dsk_ + 32 字节 CSPRNG(256 位熵) |
| 使用自定义 Key | 粘贴你已有的 Key(16-256 位可见 ASCII),用于接入既有客户端或迁移 |

自定义 Key 的强度由你负责,因此它连前缀都不展示(列表里标记「自定义」)——否则一个 my-secret-api-key-xxx 露出前 12 位就泄露了大半价值。服务端同样只保存哈希。
同值的有效记录会被拒绝(400);已吊销 / 已过期的值可以重新登记。

两套凭证,互不通用

| 用途 | 凭证 | 头部写法 |
| 调用业务接口(数据面) | API Key | Authorization: Bearer <key> 或 X-API-Key: <key> |
| 管理密钥(管理面) | Admin Token | Authorization: Bearer <admin-token> 或 X-Admin-Token: <admin-token> |

这样持有 API Key 的三方客户端无法给自己增发密钥。Admin Token 由插件首次启动时自动生成,存放在 DSH 配置根下的 dsh-web-service/admin-token(权限 0600):

cat "${DSH_HOME:-$HOME/.dsh}/dsh-web-service/admin-token"

安全约定

- 落盘只存两项:sha256(明文)(鉴权用)和一份可逆密文 sealed(列表页「复制」用)。
sealed 是用管理令牌经 HKDF 派生的密钥做的 AES-256-GCM;因此 api-keys.json 单独泄露(备份、截图、贴到 issue)还原不出 Key,还需要同目录下 0600 的 admin-token。
- 列表页每条 Key 都有「复制」按钮,明文通过管理令牌鉴权的接口现取现用,只写进剪贴板,不进 DOM、不写 localStorage、不打日志。
- 加密存储上线之前创建的 Key 只有哈希,明文不可恢复 —— 点「复制」会直接引导到「轮换」生成新 Key;管理令牌被删除重建后,旧密文的解密封装也随之失效,同样表现为「不可复制,请轮换」(不会静默给错值)。
- 管理接口默认只允许本机(回环)访问;远程管理需显式设置 adminRemoteAccess: true。
浏览器请求一律做同源校验(防 CSRF / DNS rebinding),跨源返回 403 FORBIDDEN_ORIGIN。
- 吊销立即生效且不可恢复;轮换会让旧 Key 立即失效——更稳妥的做法是先「新建」替代 Key 分发,确认切换完成后再吊销旧的。
- 插件配置里存在 apiKey 时鉴权被强制开启,页面上无法关闭(防止误放松保护);没有任何有效 Key 时也不允许开启鉴权,避免把自己锁在外面。
- 密钥存储文件损坏时按 fail-closed 处理:不放松鉴权,/system/status 会报 keysStoreDegraded。
损坏文件会改名为 api-keys.json.corrupt-<时间戳> 保留取证,并自动从 api-keys.json.bak (每次成功写入后更新的「最后已知良好状态」)恢复,不会静默丢弃既有密钥记录。
- 管理接口的回环判定看的是 TCP 对端地址。若 DSH 前面挂了反向代理 / 隧道(例如把端口转发到公网),插件会把代理端当成对端地址,此时回环护栏形同虚设——必须自行在网络层收口,或保持 adminRemoteAccess: false 并只在本机做管理操作。
- 数据面(业务接口)的鉴权是 Bearer / API Key 校验,不含同源校验:它本来就面向跨域三方客户端,因此浏览器页面携带有效 Key 的跨源请求属于预期能力;跨域放行范围由 cors 控制。
- 管理令牌会保存在浏览器 localStorage,公用电脑用完请在页面上「清除本机管理令牌」。

数据文件位于 <DSH_HOME>/dsh-web-service/api-keys.json(0600)。

功能特性

1. 工作区管理 (Workspaces):添加、删除、修改、查询工作区及其关联会话。
2. 会话管理 (Sessions):添加、删除/归档、修改标题/模型、列表查询及消息历史记录分页。
3. 设置与模型管理 (Models & Settings):查询可用模型列表、提供方列表、获取与修改系统全局默认模型配置。
4. 会话流式交互 (Streaming):
- POST /sessions/:id/prompt-stream:基于 Server-Sent Events (SSE) 实时推送 delta、reasoning、tool_call、tool_result 与 turn_end。
- GET /sessions/:id/events:会话底层事件总线实时广播监听通道。
- 流式与超时语义(重要):三个对话入口(prompt / prompt-stream / chat/completions)均采用停滞看门狗而非「整轮总时长」上限 —— 持续产出(长文本生成、多轮工具调用)哪怕远超数分钟也绝不截断,只有持续收不到任何会话事件(真停滞)才显式报错收流;同 timeoutMs(同步入口停滞阈值,默认 180s)、stallTimeoutMs(流式入口停滞收流阈值,默认 300s)。所有 SSE 通道每 15s 下发一遍 ': ping' 注释帧心跳,防中间代理/网关在长沉默期掐流;流内任意内部错误都会以 error 事件显式可见(不再无声消失)。
5. OpenAI 兼容协议:
- POST /chat/completions:支持标准 OpenAI 客户端调用(兼容 stream: true/false)。
6. 交互式 Web UI 文档 & OpenAPI 规范:
- 内置交互式 API 测试面板:GET /api/v1/docs
- OpenAPI 3.0 规范:GET /api/v1/openapi.json
7. 交互式会话:读取挂起的 ask_user_question 问题并经 REST 作答、中止正在跑的轮次、上传/列出/下载工作区文件(含分片续传上传)。
8. API 服务与密钥设置页:GET /api/v1/settings/api-keys 一页管理独立服务的启停/端口/监听范围(地址实时预览)与三方调用密钥——多 Key 并存可命名、脱敏展示、新建/轮换/吊销、列表一键复制、开启关闭鉴权;落盘存哈希 + 管理令牌加密的密文。

API 路由汇总

默认基础前缀:/api/v1

| 模块 | 方法 | 路径 | 说明 |
| System | GET | /system/status | 系统状态、在线模型及端口信息 |
| | GET | /ping | 机器指纹(IP/MAC),判定 127.0.0.1(natpass 隧道)节点是否本机 |
| Workspaces | GET | /workspaces | 查询工作区列表 |
| | POST | /workspaces | 创建/添加工作区 |
| | GET | /workspaces/:id | 获取工作区详情 |
| | PUT | /workspaces/:id | 修改工作区标题 |
| | DELETE | /workspaces/:id | 删除工作区绑定 |
| | GET | /workspaces/:id/sessions | 查询工作区下的会话 |
| Sessions | GET | /sessions | 查询会话列表 (支持搜索和工作区过滤) |
| | POST | /sessions | 创建新会话 |
| | GET | /sessions/:id | 查询单个会话详情与状态 |
| | PUT | /sessions/:id | 修改会话 (标题/模型) |
| | DELETE | /sessions/:id | 删除/归档会话 |
| | GET | /sessions/:id/history | 分页查询会话历史消息 |
| | GET | /sessions/:id/stats | 会话实时统计:轮/步、LLM 与工具调用耗时、首 token 均值、解码吞吐、缓存命中、token 账本(对齐 harness session-stats 投影语义) |
| | GET | /sessions/:id/todos | 会话任务清单 + 运行时长:todos[{content,status}](todo_write 整表投影,turn/start 清空)、counts{completed,inProgress,pending}、running/elapsedMs(当前或最后一轮 turn 墙钟)、turnStartedAt/turnEndedAt/updatedAt(供三方控制台渲染「任务」面板,≥0.1.8) |
| | GET | /sessions/:id/skills | 会话作用域技能目录(按会话 cwd 解析技能根,支持 ?search= 过滤;供输入框 "/" 技能候选,对齐 harness skills/list) |
| | GET | /sessions/:id/questions | 查询会话当前挂起的 ask_user_question 问题批次(REST 集成的宿主侧答复桥,≥0.1.7;含 connection 层抢答绕过;≥0.3.7 起仅按 ALS 归属 REST 会话,GUI/浏览器来源的提问一律透传,修复 GUI 提问 UI 不显示的回归) |
| | POST | /sessions/:id/answers | 提交挂起问题的答复(answers: [{id, selected, custom?}]),resolve 后工具以普通 tool/result 返回、会话继续 |
| | POST | /sessions/:id/cancel | 中止会话轮次 |
| | POST | /sessions/:id/files | 上传文件到会话工作区 (multipart 多文件 或 raw+?filename=);同名自动 -1/-1 去重,AI 可用文件工具直接读取 |
| | GET | /sessions/:id/files | 列出会话工作区目录(?path= 浏览相对子目录,目录优先排序) |
| | GET | /sessions/:id/files/download | 下载工作区文件(?path= 相对路径;?inline=1 浏览器内联预览) |
| Streaming | POST | /sessions/:id/prompt-stream | SSE 流式发送提示词并接收生成 |
| | GET | /sessions/:id/events | SSE 会话全局事件监听订阅 |
| | POST | /sessions/:id/prompt | 同步等待对话结果 |
| OpenAI | POST | /chat/completions | 兼容 OpenAI Chat 协议 (流式/非流式) |
| Models | GET | /models | 查询可用模型清单与默认模型 |
| | GET | /models/default | 获取全局默认模型 |
| | PUT | /models/default | 更新全局默认模型 |
| | GET | /providers | 查询注册的 LLM 提供商 |
| | GET | /presets | 查询可用 Agent Preset 清单 |
| Settings | GET | /settings | 获取系统设置配置命名空间 |
| | PATCH | /settings/:namespace | 更新指定命名空间配置 |
| API Service | GET | /api-keys/service | 独立服务状态(运行中 / 端口 / 监听范围 / 可访问地址) |
| | POST | /api-keys/service/start | 启动或换端口、换监听范围(即时生效,失败回滚) |
| | POST | /api-keys/service/stop | 停止(保留端口,下次 DSH 启动不自动拉起) |
| | GET | /api-keys/service/free-port | 建议一个空闲端口 |
| | GET / PUT | /api-keys/port | 旧版兼容(fixedPort: 0 停止) |
| API Keys | GET | /settings/api-keys | API 服务与密钥设置页(HTML,公开;数据接口需 Admin Token) |
| | GET | /api-keys | 列出 API Key(脱敏,不含明文/哈希) |
| | POST | /api-keys | 新建 API Key(返回明文,之后可随时复制) |
| | PATCH | /api-keys/:id | 修改名称 / 备注 / 过期时间 |
| | POST | /api-keys/:id/rotate | 轮换(旧 Key 立即失效,新 Key 可随时复制) |
| | POST | /api-keys/:id/reveal | 取回明文用于复制(需管理令牌;历史 Key 返回 409) |
| | POST | /api-keys/:id/revoke | 吊销(幂等,不可恢复) |
| | DELETE | /api-keys/:id | 删除记录(仅限已吊销的 Key) |
| | GET | /api-keys/auth | 查询鉴权状态 |
| | PUT | /api-keys/auth | 开启 / 关闭鉴权(关闭需 confirm: "disable-auth") |
| Docs | GET | /docs | 内置交互式 API 测试页面 |
| | GET | /docs/reference | 在线接口文档与调试(Swagger UI,全量接口 Try it out) |
| | GET | /openapi.json | OpenAPI 3.0 接口定义 |

开发与构建

构建插件 bash scripts/build.sh

运行时热注入 (需 dsh-super-injector)
dev_inject_plugin {"dir": "/path/to/dsh-web-service"}

热重载
dev_reload_package {"packageName": "dsh-web-service"}

SKILL:让 AI 智能体学会调用本 API

本仓库自带一份 DSH 原生 SKILL(skills/dsh-web-service/SKILL.md),安装后 AI 智能体会自动发现并掌握全部接口的用法(触发词:HTTP 操作 DSH、三方集成、OpenAI 兼容调用等),会话内也可用 /dsh-web-service 直接调用。

一键在线安装(推荐)

curl -fsSL https://raw.githubusercontent.com/toddpan/dsh-webapi/main/scripts/install-skill.sh | bash

安装到用户级 skill 目录 ~/.dsh/skills/dsh-web-service/,DSH 的 skill-filesystem 提供方会热发现(无需重启),下一个会话即可用。

可选参数

安装到指定目录(项目级 .dsh/skills 或 .agents/skills)
curl -fsSL .../install-skill.sh | bash -s -- --dir /path/to/project/.dsh/skills

指定分支 / 仓库
curl -fsSL .../install-skill.sh | bash -s -- --branch dev
curl -fsSL .../install-skill.sh | bash -s -- --repo other/dsh-webapi

卸载
curl -fsSL .../install-skill.sh | bash -s -- uninstall
或本地:bash install-skill.sh uninstall

脚本行为:下载 SKILL.md → 校验 frontmatter 合法性 → 安装到目标目录 → 探测本机 3080/3000 端口的 DSH Web Service 是否在线并提示。幂等可重复执行。

本地安装(仓库内)

bash scripts/install-skill.sh

License

BSD-3-Clause
预览分享卡片长什么样 ↓toddpan/dsh-webapi
分享 · DeepSeek Harness Hub
toddpan/dsh-webapi

第一次用?先看图文版 使用教程。

这是你的仓库?认领条目

回到顶部