DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Doubao Tts — DSH Plugin for DeepSeek Harness
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins
D

dsh-doubao-tts

Doubao Tts

Integration of 火山引擎豆包「双向流式语音合成 V3」 into DSH: read-aloud button for assistant messages (always visible + action row), settings page section, retrieve message content by messageId/seq, streaming synthesis, and audio caching

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:qin-yu-1314/dsh-doubao-tts#1efc11f15f923bc7a19b592043a26aed26e552da
READMECompatibilityVersions

Compatibility and provenance

Doubao Tts is published as dsh-doubao-tts and currently resolves to version 0.2.2. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
github
Registry updated
10/16/2026

Versions

0.2.2stable
10/16/2026

Related plugins

Loading related plugins…

Latest
0.2.2
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
web
License
MIT
Source
github
GitHub
★ 0
Weekly downloads
0
Last push
10/16/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in integrations-communication.

Remote Web Ui@linxin666/dsh-remote-web-uiScan-to-pair remote access for the dsh web GUI that shares one official interface: a QR beside the settings button pairs phones and PCs into the same Web GUI (a portrait-touch adaptation layer for phones, full desktop on PCs) through one-time tokens and rIm@xmanrui/dsh-im把微信、飞书、企微、钉钉、QQ、Telegram、WhatsApp、Slack、Discord、Matrix接入DSH。 Connect Telegram, WhatsApp, Slack, Discord, Matrix, WeChat, Feishu, WeCom, DingTalk, and QQ to DSH.Ds Botds-botDS Bot: a team of long-lived Bots for DeepSeek Harness, with Main Bots, Bot-to-Bot messages and group chatsFree Searchdsh-free-searchFree web search for DeepSeek Harness: 24 engines (Bing/DuckDuckGo/AnySearch/SearXNG/Exa/Tavily/Keenable/Firecrawl/Parallel keyless; Zhihu global/site via MCP and Perplexity/SerpBase/DeepSeek/You.com/Baidu/Kimi/Aliyun/Doubao with key; OpenAI/Gemini/Claude

README

dsh-doubao-tts

把火山引擎豆包「双向流式语音合成 V3」接进 DSH Web GUI:每条已定稿的助手回答后面多一个朗读按钮,点一下就把这条回答合成成语音播出来。

# 安装(示例用 web profile;桌面版请把 profile 换成 desktop)
dsh plugin --profile web add git+https://github.com/qin-yu-1314/dsh-doubao-tts

# 本机没有 git 时,改用 Release 里的压缩包:
#   dsh plugin --profile web add "https://github.com/qin-yu-1314/dsh-doubao-tts/releases/latest/download/dsh-doubao-tts-0.2.2.tgz"
项目值
适用 profileweb / desktop(桌面版由应用内插件页管理)
最低 DSH 版本>= 0.1.5-rc.2(需要 slot 系统与 settings.section)
许可证MIT
运行时依赖无(自带 WebSocket 实现,安装期不需要 pnpm 拉任何包)
前置条件火山引擎「大模型语音合成」已开通,需要自己的 API Key 或 App ID + Access Key

装好后到**侧栏「设置」→「豆包语音合成」**填密钥与音色,点「测试合成」验证。

浏览器                    DSH 宿主(插件服务端)              火山引擎
┌──────────────┐  GET    ┌──────────────────────────┐  WSS  ┌────────────────┐
│ 朗读按钮      │ ──────► │ /dsh-doubao-tts/speak     │ ────► │ bidirection v3 │
│ <audio> 播放  │ ◄────── │ ① 按 messageId 取正文     │ ◄──── │ 二进制帧 + 音频 │
└──────────────┘ 音频流   │ ② 合成 ③ 落盘缓存         │       └────────────────┘
                          └──────────────────────────┘

它做什么

  • 客户端半边:往 conversation.chat.assistant-actions 这个 slot 注入一个朗读按钮(官方反馈插件用的是同一个 slot)。手写 __ModuleLoader__ bundle,不需要构建步骤,只 require('react'),不挑宿主版本。
  • 服务端半边:注册 /dsh-doubao-tts/* 路由,按火山 v3 二进制帧协议合成音频,边收边以 chunked 流式回给浏览器;同一段文本只付费合成一次(落盘缓存)。
  • 取正文:客户端只传 sessionId + messageId,服务端优先查活会话(ctx.sessions),回落读持久化日志 $DSH_HOME/sessions/<slug>/<sessionId>/session.v4.jsonl.zstd —— 并顺手做朗读清洗(丢掉代码块、去掉 Markdown 记号、URL 换成「链接」)。

安装

插件包 = 一个声明 dsh.bundle.patch 的 npm 包。本地目录直接装:

方式一(推荐,桌面版):侧栏「插件」页 → 添加插件 → 填本目录的绝对路径:

C:\Users\wllj\Documents\deepseek-harness\default-workspace\tts-plugin\dsh-doubao-tts

方式二(CLI,需要先完全退出 DeepSeek Harness Desktop):

F:\deepseekharness\resources\runtime\cli\bin\dsh.cmd plugin --profile desktop add "link:C:\Users\wllj\Documents\deepseek-harness\default-workspace\tts-plugin\dsh-doubao-tts"

方式三(应用可以开着跑,不需要 pnpm):本插件零运行时依赖,所以只差「一个目录联接 + 两处登记」。脚本默认干跑:

node tools/install-local.mjs              # 只打印将要改什么
node tools/install-local.mjs --apply      # 真装(改前备份,失败自动回滚)
node tools/install-local.mjs --uninstall --apply   # 卸载

装完后:

事项说明
生效方式新增 bundle 由 HMR 热加载,不必重启进程;但浏览器页面要刷新一次才会拉取新的客户端 bundle
改代码后替换一个已加载包的 JS 需要重启进程(HMR 只监听 profile 的 manifest / cordis patch,不监听插件自身文件)
页面表现助手消息动作行(复制/分支那一排)最右边出现喇叭图标;未配凭据时点击显示红色提示,而不是静默失败
挂载位置本仓库目录就是插件安装源(link:)。若以后清理工作区,插件会失效——把它复制到固定目录后重装即可

本机已按方式一装好并验证过:profiles/desktop/package.json 里 dependencies 为 link:C:/…/dsh-doubao-tts、dsh.profile.bundles 末尾含 dsh-doubao-tts,node_modules 下是 SymbolicLink;/dsh-doubao-tts/status 从装前的 404 变成 401(宿主信任栅栏拦匿名请求)、/probe 返回插件自己的 405(GET 打到了 POST 路由的分派器)——都证明服务端半边已在宿主进程里注册成功。

自检与排障

插件装载后会写一份不含任何凭据的自检报告(%USERPROFILE%\.dsh\doubao-tts-selfcheck.json),用来回答「到底装上了没有」:

字段含义
phase: "loaded"服务端半边进了宿主、5+1 条路由注册完毕
phase: "status-requested"浏览器里的客户端 bundle 执行了(它装载时就会拉一次 /status),lastStatusRequest.host 是那次请求的 Host
phase: "client-registered"客户端把按钮注册进了 slot;clientReport.status 为 registered / register-failed / no-slots-service,失败时带原因
fence用的是宿主栅栏还是插件自己的回环校验
configured / missing缺哪个凭据字段

按钮不出现时,这份报告能区分三种完全不同的原因:客户端 bundle 没加载(停在 loaded)、slot 没声明/注册被拒(register-failed + 具体错因)、或只是没配凭据(configured: false)。

一键核验(重启或重装后跑一次就有结论):

node tools/verify-install.mjs

它检查 profile 登记(link: 依赖 + bundles + 联接可解析)、自检报告的阶段、以及三条路由的匿名探针,并把每个状态码翻译成人话:

探针返回含义
404路由不存在 → 插件服务端没加载
401路由在,但被宿主信任栅栏拦下(匿名请求本就不该通过,这是正常的)
405路由在,且打到了插件自己的方法分派器(GET 打 POST 路由)
200路由在且放行

早期版本会按宿主 cwd 把自检报告写在 profiles/desktop/ 目录里;以 %USERPROFILE%\.dsh\doubao-tts-selfcheck.json 为准,旧路径那份可以删掉。

配置凭据

复制 volc_tts.env.example 为 %USERPROFILE%\.dsh\volc_tts.env 并填写(或改用环境变量):

字段说明
VOLC_API_KEY新版 API Key(与下面一对二选一)
VOLC_APP_ID + VOLC_ACCESS_TOKEN老版一对密钥;也接受 VOLC_ACCESS_KEY
VOLC_RESOURCE_ID必须与音色所属商品一致,否则报 55000000:合成 2.0 音色用 seed-tts-2.0,1.0 公版用 volc.service_type.10029
VOLC_SPEAKER音色 ID,控制台音色列表里复制

凭据在火山引擎控制台 → 语音技术 → 大模型语音合成 → 开通管理里获取。改完配置不用重启(配置有 5 秒缓存),直接验证:

Invoke-WebRequest http://127.0.0.1:19387/dsh-doubao-tts/probe -Method POST -UseBasicParsing | Select-Object -Expand Content

{"ok":true,"ms":…,"bytes":…} 就说明通道打通了;失败会回可执行的错误说明(错误码已翻译成中文提示)。

路由

路由方法用途
/dsh-doubao-tts/statusGET配置是否齐全、音色、格式、缓存统计、日志根目录、capabilities(服务端能力清单)
/dsh-doubao-tts/settingsGET/POST设置界面用:GET 回显(密钥只回掩码)+ 生效值/来源/缓存;POST 原地改写 env 文件
/dsh-doubao-tts/speakGET/POST?sessionId=&messageId= 或 ?sessionId=&seq=[&turn=] 或 ?text=;返回音频。支持 voice format clean=0 download=1
/dsh-doubao-tts/probePOST用一句测试文本真实合成一次,验证凭据/音色/网络
/dsh-doubao-tts/client-reportGET客户端半边回执注册结果(落进自检报告)
/dsh-doubao-tts/cacheGET缓存条数与占用
/dsh-doubao-tts/cache-clearPOST清空缓存

设置界面

侧栏「设置」→「豆包语音合成」(挂在 settings.section 槽,与「通用」「模型」并列):

分组可改项
鉴权API Key / App ID / Access Key / Access Token(密钥是密码框,只显示掩码占位;留空 = 不修改)
音色Resource ID(下拉:seed-tts-2.0 / volc.service_type.10029)、音色 ID(可手填,下拉给参考值)
音频格式(mp3/pcm/ogg_opus)、采样率、语速、音量
连接WebSocket 端点、是否先发 StartConnection
按钮保存 / 测试合成 / 重新读取 / 清除已保存的密钥 / 清空音频缓存
状态是否已配置、缺哪些字段、缓存条数与占用、配置来源文件、以及会覆盖它的系统环境变量

设计要点:

  • 写回原文件:改的是 %USERPROFILE%\.dsh\volc_tts.env 本身,保留注释、行序与其他键,只动对应的行(先写 .tmp 再 rename,原子写)。
  • 密钥不回明文:GET 只回 {set:true, masked:"••••••••5f53"};输入框永远为空、掩码只当占位符,所以保存时不会把掩码写回文件。
  • 立即生效:保存后服务端立刻清掉配置缓存,不用重启、不用刷新页面。
  • 能力协商:客户端 bundle 刷新页面就更新,服务端代码要重启进程才换。/status 的 capabilities 用来判断当前服务端支持什么,避免「新界面调旧接口」的静默失败。

为什么挂两个 slot

slot位置可见性定位参数
conversation.chat.assistant-actions复制/分支那一排图标里默认 opacity: 0,悬停才淡入(ui-chat 的 [data-actions-reveal=hover])messageId
conversation.chat.turnTail同一节点的兄弟 slot常显seq(该助手消息的事件序号)+ turn

只挂第一个等于把入口藏起来(实测反馈「看不到喇叭图标」);而且当某一轮正在生成时,data-actions-reveal 会退化成 hover(endsWithResponse 要求它是时间线上最后一轮),连最近一轮的动作行也要悬停才出现,甚至当前轮根本不渲染动作行。所以第二个 slot 才是真正的入口——它拿不到 messageId,只能拿 seq,服务端因此也支持按 seq 取正文。

/status 的 capabilities 用于协商:客户端 bundle 随页面刷新立刻更新,而服务端代码要重启进程才换,存在「新客户端 + 旧服务端」的窗口期;此时常显按钮会显示「需要重启一次 DSH」,而不是发一个注定 400 的请求。

错误码:400 MISSING_PARAMS、404 MESSAGE_NOT_FOUND(附已知 messageId 便于排障)、404 SESSION_NOT_FOUND、422 EMPTY_TEXT(只有 reasoning/工具调用、没有正文)、503 NO_CREDENTIALS、502 VOLC_ERROR(带火山原始错误码)、502 AUTH_FAILED(上游拒绝密钥,带服务端原话)、504 ENDPOINT_UNREACHABLE(连不上火山)。

命令行(不开 GUI):

node tools/probe.mjs                                    # 探针:验证凭据/端点
node tools/probe.mjs --api-key bogus                    # 故意用假密钥,看服务端如何拒绝
node tools/speak-message.mjs --out reply.mp3            # 自动挑最近一条助手消息朗读
node tools/speak-message.mjs --session <id> --seq <n>   # 走 seq 通路(常显按钮同一条路)
node tools/speak-message.mjs --text "你好" --out a.mp3

真机验证结果(本机)

验证项证据
装进 profiledependencies 为 link:C:/…/dsh-doubao-tts,dsh.profile.bundles 末尾含包名,node_modules 下是符号链接且 entry.js/client/doubao-tts.js 均可解析
服务端半边装载装前 /dsh-doubao-tts/status 是 404,装后是 401(宿主栅栏拦匿名请求);/probe 返回插件自己的 405(GET 打到 POST 路由的分派器)
客户端半边执行自检报告(宿主 pid 36204)在装载后 0.06 秒就记录了来自 127.0.0.1:19387 的 /status 请求——这个请求只由客户端 bundle 发起
控件注册成功自检报告 clientReport.status = "registered",slot = conversation.chat.assistant-actions,error = null
真实火山端点用假密钥打生产端点:HTTP 401 — {"error":"Invalid X-Api-Key"},125ms 返回 → 端点路径、DNS/TLS、WS 升级、请求头全部正确
真实语音合成真密钥下:握手序列 ConnectionStarted ← SessionStarted ← TTSSentenceStart ← TTSSentenceEnd ← SessionFinished;20 字 → 11 段 / 30765 字节 / 1.5s;77 字 → 36 段 / 114285 字节 / 2.7s;产物为合法 MP3(ID3v2.4 + MPEG 帧同步)
两条取正文通路一致messageId 与 seq 两条通路在同一会话上解析出逐字相同的正文(speak-message.mjs 内置交叉校验)
未影响既有插件/dsh-whale/widget.js → 401、/api/qqbot-settings/read → 200,与安装前一致

一键复现:node tools/verify-install.mjs

没有凭据也能先跑通

tools/demo-server.mjs 起一个真实 HTTP 服务,跑的是生产代码路径,只把火山换成协议级假服务(回 lamejs 现场合成的真实 MP3):

node tools/demo-server.mjs 18081
# 然后把打印出来的 URL 粘进浏览器就能听到声音

它同时会挑一条你真实会话里的助手消息,给出可直接播放的链接——用来确认「按 messageId 取正文 → 合成 → 流式播放」整条链路。

测试

node test/run.mjs

129 项,覆盖:

  • 协议层:客户端/服务端帧的字节级布局、事件与会话字段、长度前缀边界、错误帧;
  • 火山客户端:对着独立的 ws 实现当服务端跑完整会话,断言拿到的音频字节与下行帧 payload 逐字节一致、音频是分帧流式到达、长文本按句切分、错误码翻译、两种鉴权头、跳过 StartConnection 的部署;
  • 取正文:多帧 zstd 会话日志(真实持久化格式)、跳过 reasoning、只有思考的消息、Markdown 清洗,并直接读本机真实会话日志验证;
  • 客户端 bundle:在 Node 里用假 React/Audio/fetch 跑 bundle,断言注册到两个 slot、常显控件带文字标签、点击请求正确 URL(messageId 与 seq 两条)、能力协商、缺服务时不抛异常、注册回执;
  • slot 契约:接宿主真实的 SlotCore(@deepseek-ai/dsh-client-ui-slots)搭出与真机一致的 slot 树(conversation.chat.node#turn-tail 声明的 list/session 子槽),验证我的注册被接受、与 order=10 的官方反馈条目按序共存、inject(sessionId) 约定、disposer 语义、未声明槽会被拒;
  • 插件路由:真实 handler + 假 webServer + 假火山,覆盖取文本、流式输出、缓存命中/清空、错误码映射、自检报告、以及回环/同源/跨站请求策略;
  • 设置存储:原地改 env 文件保留注释/行序/无关键、新键追加、null 删除、原子写(无 .tmp 残留)、掩码只留尾 4 位且不泄露前半段。
  • 设置路由:GET 掩码不泄密、POST 立即生效、密钥留空不写回、clearSecrets 删除、未知字段/空提交 400、落进自检报告。
  • 设置面板:回显 /settings、密钥占位是掩码而非值、保存只提交用户填的字段、清除密钥、测试合成、「完成」接 props.close。
  • 本地安装器:干跑不改文件、真装写对两处登记并能解析联接、幂等、格式不预期时拒绝改动、出错回滚、卸载不误删插件源目录。
  • 一体化:真实客户端 bundle 的 fetch 桥接到真实服务端 handler,跑完整条 装载自检 → 拉 /status → 注册 slot → 回执 /client-report → 点击按钮 → 真合成 → 缓存命中 的链路。
  • 配置:env 文件优先级、按实例隔离的缓存(回归:两个实例不得互相偷配置)、startConnection 开关、凭据服务兜底。
  • 自检报告:里程碑单调、字段累积(回归:高里程碑写入不得抹掉 clientReport)、换装载时重置。
  • 错误分类:握手 401/403 归为 AUTH_FAILED 并带出服务端原话(响应体分两次 write 也不丢),非鉴权失败不误判,端点不可达归为 ENDPOINT_UNREACHABLE。

安全

DSH 的 web server 不做认证与来源策略,文档要求 route owner 自己实现,因此本插件自带:只接受回环 Host(或 DSH_DOUBAO_TTS_TRUSTED_HOSTS 列出的)、拒绝 Sec-Fetch-Site: cross-site、带 Origin 时必须与 Host 同源;写操作(POST)只允许本机(或 DSH_DOUBAO_TTS_ADMIN_HOSTS);可用时一并委托宿主的 connection.requestRejection。

已知限制

  • 文本是整段送进 TTS(按 600 字切包),还没做「LLM 边出字边推 TTS」的增量输入;协议层已经预留(一次会话内多次 TaskRequest)。
  • 合成失败若发生在已经发出部分音频之后,浏览器侧只会表现为播放中断(HTTP 状态码已无法更改);日志里会有 warning。
  • 没有做逐句音频时间轴,因此没有「朗读到哪一句」的高亮。
  • 未做系统朗读回退:本机 SAPI 的 voice token 缺 CLSID 注册(Speak 报 0x80045040/E_ACCESSDENIED),所以离线回退不可用;无凭据时按钮会明确提示缺少凭据而不是静默失败。

与最初那份 doubao_tts.py 的差异

原稿用纯 JSON 文本帧实现,跑不通。正确做法是 v3 二进制帧:

原稿实际协议
{"event":"StartConnection"} 文本帧4 字节头 + event(4B 数字 1) + payload(4B 长度 + JSON)
if isinstance(raw, bytes): yield raw下行全是 bytes;音频藏在 messageType=0b1011 的 TTSResponse(352) 帧 payload 里,不能把整帧当音频
没有 session_idStartSession/TaskRequest/FinishSession 必须带同一 session id
没有 namespace/req_params 结构body = {user,event,namespace:"BidirectionalTTS",req_params:{text,speaker,audio_params,additions}}
字符串事件名等 SessionFinished数字事件:100/150/200/352/102/152,失败走 ErrorInformation(0b1111)
X-Api-Key 一种老版一对密钥 + X-Api-Resource-Id;Resource-Id 与音色不匹配会报 55000000

上架到插件市场

市场(dshmarket)的列表来自精选列表 awesome-dsh-plugin, 只允许安装该列表内的来源;上架方式就是往那个仓库提一个文件:

data/plugins/<owner>__dsh-doubao-tts.yml

本包提供了生成器(查重名 / 生成投稿文件 / 生成自建目录):

node tools/market.mjs check dsh-doubao-tts                        # 查目录里有没有重名、看同类条目
node tools/market.mjs entry --owner <你的 GitHub 用户名>          # 打印可直接提交的 YAML
node tools/market.mjs json  --owner <owner> --out plugins.json    # 自建目录(配合 DSHM_REGISTRY_URL)

提交前确认(后面这条是最常见的被拒原因):

  • 仓库 package.json 必须声明 dsh.bundle(只声明 dsh.client 不算可安装)—— 本包已声明,仓库根也有 cordis.patch.yml;
  • README 第一屏要有:可复制的安装命令、适用 profile、许可证、最低 DSH 版本 —— 本 README 顶部已满足;
  • 不要在 YAML 里手写 npm: 字段,映射由 CI 从 registry 自动采集,手写会被校验拒绝。

许可

MIT。