Back to home@agent-mobile

dsh-speech

Speech plugin for the DeepSeek Harness (dsh) web host: ASR, TTS and realtime transcription over pluggable providers

Stars
1
Language
TypeScript
Created
Sep 8, 2026
Updated
Sep 9, 2026
GitHub repo

Introduction

dsh-speech

DeepSeek Harness(DSH)语音能力插件:装一个插件,主机就拥有 token 门控的 /s/api 语音转写(ASR)与语音合成(TTS)服务面、/s/ws 实时转录会话通道, 手机 App / 任何局域网客户端直接调用。不改 dsh 源码、不需要重新下载编译—— dsh plugin 一条命令安装。

配套手机端(dsh_mobile_app / dsh_dart_sdk)走 mobile-gateway 的 /m/api 聊天, 语音请求走本插件的 /s/api/s/ws,两者共用同一个 token。 语音模式 / 实时转写的用法与语音链路、组网(蒲公英 · WireGuard)配置,见 VOICE-AND-NETWORK.md

它解决什么问题

dsh 官方宿主没有任何语音能力位(LLM seam 只认 chat 模态,附件只收图片)。 本插件以官方扩展点(webServer 路由注册)旁挂独立的语音 HTTP/WS 面:

动作机制
多提供商 ASR/TTS插件内 SpeechService 注册表,dashscope / openai-compatible / local-relay / streaming-ws 四类 adapter
阿里云百炼(原生)dashscope 走百炼原生 HTTP 协议(multimodal-generation 端点):批量识别/合成 + 句级实时转录(VAD 复用批量 ASR)。内置平台预设,模型列表可「拉取」
云端厂商通吃openai-compatible 说标准 /v1/audio/transcriptions + /v1/audio/speech(OpenAI、Groq、SiliconFlow、Fireworks、自建兼容网关……换厂商只改配置)
本地服务直连local-relay 说自建 SenseVoice(POST {asr}/transcribe,base64 WAV→{text})与 CosyVoice2 的私有契约:批量 POST {tts}/synthesize{text,voice,stream:false}→WAV)、流式 WS {tts}/ws/tts(增量 PCM16 帧→连续 WAV 流,边合成边播),自动归一化采样率/声道
实时转录(本地/百炼)local-relay / dashscope 复用同一批量 ASR:主机 VAD 分段 + 伪流式逐字预览(句级出稿)。说话人分离仅 local-relay 提供include_embedding 声纹聚类);百炼实时转录无 speaker 字段(见 dashscope 段能力边界)
实时转录(云端流式)streaming-ws 直连流式 ASR WebSocket:Deepgram / FunASR wss-server / sherpa-onnx / 讯飞 iat v2(单人听写,自动轮转 60s 上限)/ 讯飞 rtasr v1(长音频多人实时转写,roleType=2 说话人分离,静音保活防 15s 断连)
云端合成(讯飞)streaming-ws + dialect: xfyun-tts 直连在线语音合成 v2/tts WebSocket:mp3/wav、语速/音高/音量、川粤多方言音色;与 iat 共用同一应用三件套凭证,单次超 8000 字节文本自动分段多次调用
混搭ASR、TTS、实时转录三个选择器互相独立:可百炼批量识别 + 本地合成 + Deepgram 实时
模型改名自愈编辑器内置「拉取平台最新模型」按钮(OpenAI/Groq/硅基流动/百炼通吃 /models 接口)+ 静态预设标注已下线模型 + 自由手填三层兜底
密钥安全两种方式任选:页面直接填写apiKey 内联,保存热生效,持久化在 ~/.dsh/settings.yaml,接口与页面只回显掩码 sk-***xxxx,编辑时留空即沿用、可显式清除);或 环境变量名引用apiKeyEnv 等,值放 ~/.dsh/.env)。内联值优先于环境引用

快速开始

# 1. 安装官方 dsh(已装可跳过)
npm install -g @deepseek-ai/dsh

# 2. 安装本插件(从 GitHub,一条命令)
dsh plugin --profile web add github:agent-mobile/dsh-speech
#   首次安装 pnpm 会询问是否允许构建本包(allowBuilds),按 dsh 的提示放行即可;
#   开发调试:  dsh plugin --profile web add link:<本仓库路径>

# 3. 配置(见下),随 dsh web 启动自动生效
dsh web

桌面入口:插件带一个浏览器半区,安装后 dsh web 的 「设置 → 插件」里会出现「语音服务」标签页(与「插件配置」并列), 原生渲染提供商配置界面。配置项 uiEntry: false 可隐藏该标签页。 /s/ 独立页面仍可直接访问(手机 App 入口依赖它)。

两个管理界面都内置平台预设src/presets.ts,单一数据源): 添加 openai-compatible 提供商时选择「阿里云百炼 / OpenAI / Groq / 硅基流动」即可一键填入 Base URL、密钥环境变量名、推荐模型与音色 (模型/音色仍可自由输入,未列出的网关走「自定义」);streaming-ws 切换方言时自动补全云端地址与鉴权环境变量名。密钥值本身永远不进配置 与页面——只填环境变量名,值放在 ~/.dsh/.env 或启动环境,重启生效。

dsh-mobile-gateway 一起装时,端口绑定由 mobile-gateway 负责(0.0.0.0),本插件的路由自动对局域网可达。

配置

token 必填,其余有默认值。配置写在 profile patch (~/.dsh/profiles/web/cordis.patch.yml)的插件行:

- id: speech
  config:
    token: 换成一个长随机串
    transcriptionProvider: ''        # 空 = 自动(恰好一个可用时选中)
    synthesisProvider: ''
    sessionTranscriptionProvider: '' # 实时转录选择器,语义同上
    maxAudioUploadBytes: 26214400    # 单次音频上传上限(字节)
    maxSynthesisChars: 4000          # 单次合成文本上限(字符)
    providers:
      # 云端:任何 OpenAI 兼容端点
      groq-asr:
        type: openai-compatible
        baseUrl: https://api.groq.com/openai/v1
        apiKeyEnv: GROQ_API_KEY
        asrModel: whisper-large-v3-turbo
      siliconflow-tts:
        type: openai-compatible
        baseUrl: https://api.siliconflow.cn/v1
        apiKeyEnv: SILICONFLOW_API_KEY
        ttsModel: FunAudioLLM/CosyVoice2-0.5B
        ttsVoice: FunAudioLLM/CosyVoice2-0.5B:alex
      # 本地:自建 SenseVoice + CosyVoice2(契约已在 OpenClaw relay 验证)
      local:
        type: local-relay
        asrEndpoint: http://192.0.2.10:9001
        ttsEndpoint: http://192.0.2.10:9002
        ttsVoice: 中文女
        diarization: true            # 开启实时转录的说话人分离(include_embedding)
      # 云端流式实时转录:无本地服务的用户
      deepgram:
        type: streaming-ws
        dialect: deepgram
        url: wss://api.deepgram.com/v1/listen
        apiKeyEnv: DEEPGRAM_API_KEY
        diarization: true
      # 国内云流式(单人听写,60s 连接上限自动轮转)
      xfyun:
        type: streaming-ws
        dialect: xfyun-iat
        url: wss://iat-api.xfyun.cn/v2/iat
        appIdEnv: XF_APP_ID
        apiKeyEnv: XF_API_KEY
        apiSecretEnv: XF_API_SECRET
      # 国内云流式(长音频/多人,说话人分离;与 iat 共用应用凭证但需在控制台单独开通)
      xfyun-rtasr:
        type: streaming-ws
        dialect: xfyun-rtasr
        url: wss://rtasr.xfyun.cn/v1/ws
        appIdEnv: XF_APP_ID
        apiKeyEnv: XF_API_KEY
        diarization: true            # 开启 roleType=2 角色分离(结果 rl 字段 → spk)
      # 国内云合成(TTS;与 iat 共用三件套凭证但需在控制台单独开通,方言音色需先添加发音人)
      xfyun-tts:
        type: streaming-ws
        dialect: xfyun-tts
        url: wss://tts-api.xfyun.cn/v2/tts
        appIdEnv: XF_APP_ID
        apiKeyEnv: XF_API_KEY
        apiSecretEnv: XF_API_SECRET
        ttsVoice: xiaoyan            # 发音人 vcn,必填;方言音色以控制台显示为准
        ttsFormat: mp3               # mp3(默认)/ wav
      # 本地流式(想要逐字 partial 时)
      funasr:
        type: streaming-ws
        dialect: funasr
        url: ws://192.0.2.10:10095

字段说明

字段默认说明
token(必填)客户端以 Authorization: Bearer <token> 呈现;留空插件拒绝启动
transcriptionProvider / synthesisProvider / sessionTranscriptionProvider''分别钉选批量识别 / 合成 / 实时转录的 entry id;空时恰好一个可用者自动选中,多个可用报 SPEECH_PROVIDER_AMBIGUOUS
maxAudioUploadBytes26214400/s/api/speech.transcribe 请求体上限
maxSynthesisChars4000/s/api/speech.synthesize 文本长度上限

provider entry

openai-compatible(云端与兼容网关):

字段说明
baseUrl兼容 API 根(含 /v1
apiKey页面直接填写的密钥值(内联优先);任何接口响应不回显,只报掩码
apiKeyEnv环境变量名;缺省 = 无鉴权(内网网关)。变量为空时该 provider 视为不可用
asrModel/audio/transcriptions 的 model;不配则该 entry 不提供转写
asrLanguage默认语言提示(请求头可覆盖)
ttsModel / ttsVoice / ttsFormat / ttsSpeed/audio/speech 参数;不配 ttsModel 则不提供合成
timeoutMs上游超时,默认 60000

dashscope(阿里云百炼原生协议 · 批量识别/合成 + 句级实时转录):

字段说明
apiKey / apiKeyEnvopenai-compatible(页面直填或环境变量名,内联优先)
asrModel百炼批量识别模型(如 qwen-audio-3.0-asr-flash);不配则不提供识别
asrLanguage语言提示(zh/en/…),映射到 language_hints
ttsModel百炼非实时合成模型(如 qwen3-tts-flash);不配则不提供合成
ttsVoice必填(配了 ttsModel 时):音色名(如 Cherry
baseUrlAPI 根,默认 https://dashscope.aliyuncs.com
vadSilenceMs / vadMaxSpeechMs / vadMinSpeechMs / partialFlushMs实时转录 VAD 调参(句级 + 伪流式逐字预览)
timeoutMs上游超时,默认 60000

模型改名?编辑器内置「拉取平台最新模型」按钮(GET /compatible-mode/v1/models),或手动填入控制台模型广场的最新名称——无需改代码。

能力边界(写文档必读):百炼的实时转录不支持说话人分离。其实时/批量 ASR 的 sentence 结果只含 begin_time/end_time/text/sentence_id/words[],没有任何 speaker 字段 (官方文档 fun-asr-server-events 已确认)。因此 dashscope 适配器的 sessionTranscription.diarization 恒为 false,所有语句统一归 spk=0。这是百炼服务本身 的能力边界,不是插件适配器的限制——真正的声纹说话人聚类只有 local-relay (SenseVoice include_embedding)提供。

local-relay(自建 SenseVoice / CosyVoice2):

字段说明
asrEndpointASR 服务根(POST {endpoint}/transcribe);不配则不提供转写。配了即同时提供实时转录(VAD 句级模式)
ttsEndpointTTS 服务根:批量 POST {endpoint}/synthesize;流式 WS {endpoint}/ws/tts(增量出声,需 TTS 服务带该 WebSocket 端点,连接失败自动回退批量);不配则不提供合成
ttsVoice默认音色(请求体可覆盖)
asrTargetSampleRateHzASR 目标采样率,默认 16000(自动重采样/降混)
diarization实时转录开启说话人分离:冲刷时带 include_embedding,服务端返回 segments[{spk,text,embedding}] 时做会话级聚类
vadSilenceMs / vadMaxSpeechMs / vadMinSpeechMsVAD 调参;默认 700/15000/300(开 diarization 时静音与上限自动变为 900/8000,长句自动分段)
spkMergeThreshold说话人聚类余弦阈值,默认 0.5
partialFlushMs伪流式逐字预览:说话中每隔该毫秒把已积累音频重新识别并作为 partial 预览下发(灰字),句末仍用完整音频定稿;0 关闭。默认 1500
timeoutMs上游超时,默认 60000:批量是整个请求的硬上限,流式是「连接 + 相邻两帧」的空闲上限(健康的长合成不会被掐断)

streaming-ws(流式实时转录上游 / 讯飞在线合成):

字段说明
dialect信令方言:deepgram / funasr / sherpa / xfyun-iat / xfyun-rtasr(实时转录)/ xfyun-tts(合成,不提供转录)
url上游 WebSocket 地址(ws:// / wss://
apiKey / appId / apiSecret页面直接填写的凭证(内联优先;密钥值不回显)。讯飞 iat / tts 需三件套;rtasr 只需 APP ID + API Key(HmacSHA1 签名,无 Secret)
apiKeyEnvDeepgram Token 鉴权 / 讯飞 API Key 的环境变量名
appIdEnv / apiSecretEnv / rotateAfterSec讯飞 iat 三件套与轮转秒数(默认 55,避开 60s 连接上限,自动无缝续接);rtasr 用不到后两项,tts 用不到 rotateAfterSec
language默认语言提示。rtasr 原样透传为 lang 参数(cn / en / cn_cantonese …),缺省普通话;粤语等方言需先在控制台「实时语音转写-方言/语种」为该应用开通
diarization请求说话人分离(Deepgram diarize / 讯飞 rtasr roleType=2rl 角色编号映射为会话内 spk;其余方言忽略)
ttsVoicexfyun-tts 发音人(上游 vcn),该方言下必填——API 拒绝无发音人请求;方言音色(粤语等)需先在控制台添加发音人,名字以控制台显示为准
ttsFormat / ttsSpeed / ttsPitch / ttsVolumexfyun-tts:容器 mp3(默认)/ wav(raw PCM 套 RIFF 头);语速/音高/音量 0–100,缺省均 50

xfyun-rtasr 协议要点:端点 wss://rtasr.xfyun.cn/v1/ws,查询参数 appid / ts / signasigna = base64(HmacSHA1(MD5(appid+ts), apiKey))),连接后直接发 16kHz PCM16 二进制帧(无起始帧),结束发二进制 {"end": true}。上游在 15 秒无音频时 主动断连(错误码 37005),适配器每 10s 静音间隙注入 40ms 静音保活,并把句子时间戳 按已注入量回拨到客户端时间轴。与 iat 相比:单连接无 60s 上限、逐句 draft/final、 支持角色分离——需在讯飞控制台为同一应用单独开通「实时语音转写」服务

xfyun-tts 协议要点:端点 wss://tts-api.xfyun.cn/v2/tts,鉴权与 iat 同一套 HMAC-SHA256 签名(host / date / authorization 查询参数,APISecret 参与签名)。 每次调用一个连接:发送单帧 JSON(common.app_id + business.vcn/aue/sfl/... + data.text base64),上游以 data.audio base64 片段流式回传,data.status === 2 为结束;单次文本上限约 8000 utf8 字节(~2000 汉字),超限由适配器按字符边界分段、 顺序多次调用并拼接音频。aue=lame(mp3,配 sfl=1)或 raw(PCM16 16kHz 单声道, 适配器套 WAV 头)。需在讯飞控制台为同一应用单独开通「在线语音合成」服务

讯飞凭证获取(xfyun-iat / xfyun-tts 三件套):在 讯飞开放平台控制台 获取——

  1. 注册并登录讯飞开放平台,完成实名认证(个人认证即可)
  2. 左侧菜单「我的应用」→「创建应用」,填写应用名称,能力勾选语音听写(IAT);用 rtasr / tts 的话在应用详情页再分别开通实时语音转写 / 在线语音合成(同一应用内各服务独立开通、独立计费)
  3. 创建后进入该应用详情页,直接显示 APP IDAPIKeyAPISecret(APISecret 默认隐藏,点「显示/复制」查看;丢失可在该页重置)

新用户有免费体验额度(控制台「资源/用量」可查剩余量);环境变量模式下三个值分别写入 ~/.dsh/.envXF_APP_ID / XF_API_KEY / XF_API_SECRET

HTTP API(/s/api

鉴权规则(与 mobile-gateway 的 /m/ 管理页同款)

  • 本机访问127.0.0.1 / localhost 打开 http://127.0.0.1:3080/s/免 token——桌面浏览器直接打开即用
  • 局域网访问(手机等)需 Authorization: Bearer <token> 头或 ?token=<secret> 查询参数
方法路径请求响应
POST/s/api/speech.transcribe音频原始字节做 body,Content-Type 标明容器(audio/wav 等),可选 X-Speech-Language{"text":"...","provider":"local"}
POST/s/api/speech.synthesize{"text":"...","voice?":"...","format?":"mp3"|"wav"}音频字节(Content-Type: audio/wav / audio/mpegX-Speech-Provider 标明提供商)
GET/s/api/speech.providers选择快照:候选、钉选、接受的格式、音色、实时转录能力(mode/句级或逐字/diarization,不含任何密钥)
GET/s/api/health{"status":"ok"}

错误响应统一为 {"error":{"code":"...","message":"..."}},状态码: 401 未授权 / 400 请求形状错误 / 413 超限 / 415 格式不支持 / 502 上游失败 / 503 无可用或多个可用未钉选的 provider。

错误码全集:SPEECH_BAD_REQUEST SPEECH_UNAUTHORIZED SPEECH_AUDIO_TOO_LARGE SPEECH_TEXT_TOO_LONG SPEECH_UNSUPPORTED_FORMAT SPEECH_PROVIDER_UNAVAILABLE SPEECH_PROVIDER_AMBIGUOUS SPEECH_PROVIDER_CONFIGURED_MISSING SPEECH_PROVIDER_CONFIGURED_UNAVAILABLE SPEECH_UPSTREAM_FAILURE SPEECH_INTERNAL

/s/ws 实时转录通道(WebSocket)

鉴权与 /s/api 同款:本机(loopback Host)免 token,局域网用 ?token=<secret> 查询参数或 Authorization 头。JSON 文本帧为控制消息, 二进制帧为音频(PCM16 LE,按 session.create 声明的采样率):

C→S  {"type":"session.create","provider":null,"sampleRateHz":16000,
      "encoding":"pcm16","diarization":true}
S→C  {"type":"session.ready","provider":"local","mode":"vad","partial":false,
      "diarization":true}                ← 能力协商:句级/逐字/说话人分离
C→S  <二进制 PCM16 帧,任意分块>
S→C  {"type":"partial","text":"…"}                       ← 仅 streaming 模式
S→C  {"type":"transcript","text":"…","segments":[{"spk":0,"text":"…",
      "startMs":0,"endMs":1200}]}                        ← spk 会话级稳定
C→S  {"type":"session.close"}
S→C  {"type":"closed"}                                   ← 随后连接关闭
S→C  {"type":"error","code":"…","message":"…"}           ← 随后连接关闭

provider 可为空(走服务端 sessionTranscriptionProvider 选择器)或按连接 临时钉选。/s/ 配置页的「实测 6 秒」按钮就是这条通道的浏览器端演练 (getUserMedia → 时间线事件回放)。

Dart SDK 侧:DshSpeechClient.openSession() 返回 DshSpeechSessionevents 广播流 + sendAudio/close),配套 App 的 TranscriptionController 状态机(开始/暂停/恢复/停止/失败重试)。

curl 示例

# 识别一段 WAV
curl -s http://192.168.1.5:3080/s/api/speech.transcribe \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: audio/wav" \
  --data-binary @speech.wav

# 合成并播放
curl -s http://192.168.1.5:3080/s/api/speech.synthesize \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"text":"你好,世界"}' -o out.mp3

安全

  • 全路由常数时间 Bearer 比较;缺/错 token 统一 401
  • token 留空时插件 fail-loud 拒绝启动
  • 密钥两种存放方式:内联(页面填写,保存在 ~/.dsh/settings.yaml)或环境变量; 任何 GET 响应与页面永不回显密钥值——内联只报 sk-***xxxx 掩码与"已保存"状态; 编辑提供商时密钥框留空即沿用已存密钥(*Keep 语义),勾选"清除"才删除
  • 音频字节与文本长度双上限;local-relay 端点应只在内网使用(不要把 SenseVoice/CosyVoice2 的地址暴露到公网)

设计说明

  • HTTP 路由 + /s/ws WebSocket 升级路由都挂在 webServer 扩展点;不碰 /api/m/api,卸载即消失
  • 实时转录的 vad 模式把 OpenClaw SenseVoice provider 的分段状态机 (RMS 静音判停 + 说话人质心聚类)移植到主机侧,PCM16 直入、不再经过 G.711 μ-law 折返;streaming 模式逐字 partial、说话人分离由上游承担 (Deepgram diarize)或自动轮转续接(讯飞 60s 上限)
  • 语音产物不进 session log:文字仍走官方 session.prompt,天然满足 harness "模型可见 ⟺ 已记录" 的约定
  • 采样率/声道归一化(48k 立体声 → 16k 单声道线性插值)在 local-relay adapter 内完成,App 端录制 16kHz 单声道即零转码直通

测试

pnpm test        # 58 项:gate / wav / providers / selection / vad / session-channel / integration
pnpm typecheck
pnpm run build

集成测试以真实 WebServer + 真实本机 HTTP/WS mock 上游(模拟 SenseVoice diarized 响应、Deepgram 事件流、OpenAI 兼容端点)覆盖全链路,包括鉴权、 上限、错误码、provider 选择策略与实时会话协商/转录/关闭。