Skip to content
dsh.fish
Bundle

@gw/dsh-mimotts

MiMo TTS speech synthesis for DeepSeek Harness: a configuration card on the Plugins page, a speak button under every finalized assistant message, and an inline player with a draggable progress bar

Source
1497105876
License
MIT
Updated
Updated 9 days ago

Readme

# @gw/dsh-mimotts — MiMo TTS 语音合成插件(DeepSeek Harness 0.2.0-rc.2)

把助手回复"读出来"的 DSH 插件:**"插件"页里的配置卡片** + **每条已定稿助手回复动作行里的音量按钮** + **带可拖动进度条的内联播放器** + **设置页里的"语音历史"**。语音由小米 MiMo TTS(`mimo-v2.5-tts`)合成,API Key 只存在于宿主侧,浏览器只发送文本、收到 WAV。

面向 **deepseek-harness `0.2.0-rc.2`**(`engines.dsh: ">=0.2.0-rc.2 <0.3.0"`),完全按照官方插件规范实现:

| 官方规范条目 | 本插件的落点 |
|---|---|
| [打包与安装插件](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/docs/user/develop/basic/publish.zh.md)(bundle 组合包) | `package.json` 声明 `dsh.bundle.patch` + `cordis.patch.yml` 插入 `id: mimotts` 行;同包携带 `dsh.client` 浏览器半侧(裸包名行同时挂载 Host/Client 两侧) |
| [插件配置](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/docs/user/develop/basic/config.zh.md)(无硬编码可调参数) | 全部参数走 Schemastery Config + `.volatile()`(接口地址/模型/音色/克隆样本/风格/字数/超时均可配);密钥 `role('secret')` |
| [即时配置表单](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/docs/cookbook/adding-a-settings-card.zh.md) | `ctx.configForms`(`mimotts` 命名空间)+ `SettingsFormModel` 标准表单;`ctx.settings.configure({ auto: false })` 关闭自动页策略,配置卡片自己注册进官方 `plugins.bundle.config`(key = npm 包名) |
| [Web Client Slots](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/docs/subsystems/slots.zh.md) | `plugins.bundle.config`(插件页配置卡)+ `conversation.chat.assistant-actions`(消息动作行)+ `settings.section`(语音历史页)三个席位,`ctx.slots.inject` 贡献、effect 生命周期托管;跨插件只 `import type` 取 slot 声明 |
| [Web Server 子系统](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/docs/subsystems/web-server.zh.md) | `ctx.webServer.register` 挂 `/api/mimotts/status`、`/api/mimotts/synthesize` 两条路由,入口先过 `ctx.connection.requestRejection` 信任栅栏(Host/Origin + 浏览器鉴权),LAN 裸请求无法白嫖 API 额度 |
| [客户端模块系统](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/packages/client/modules/README.zh.md) | `lib/client.js` 为 lazy-CJS factory(`window.__ModuleLoader__.load({ id, factory })`),外部仅平台模块(react / cordis / client-store / ui-primitives),跨插件只经 Cordis 服务与 `import type`(构建期 purity gate 强制) |
| [extension-cookbook · UI 插件](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.0-rc.2/docs/cookbook/extension-cookbook.zh.md) | 业务数据不进组件 props 面:文本经 `useChat` 从 Conversation binding 派生,配置经 `configForms`,合成经宿主路由,文案经 `ctx.locale`(zh/en 双语) |

## 功能

1. **「插件」页 → @gw/dsh-mimotts 详情页的配置卡片**(官方 `plugins.bundle.config` 席位,key = 包名)
   - API Key(只写输入框:粘贴即经官方 `credentials/set` 通道存入宿主凭据存储,页面不回显;显示"已配置/未配置",清空保存不改动已存密钥)
   - 接口地址(默认 `https://api.xiaomimimo.com/v1`)、模型(默认 `mimo-v2.5-tts`)
   - 预置音色(**官方 9 音色下拉**:mimo_default、冰糖、茉莉、苏打、白桦、Mia、Chloe、Milo、Dean)/ 音色克隆样本(宿主本地 WAV 路径,24kHz/16bit/单声道,5–15s)
   - 默认风格(如"开心""语速慢""东北话";留空由模型自行判断,推荐)、单次最大字符数、请求超时
   - **试听**:用卡片当前(含未保存修改)的风格/音色合成一句示例,内联播放
   - 标准表单行为:脏值预览、"已覆盖/恢复默认"徽标、保存/放弃、只读部署提示
2. **每条已定稿助手回复底部的音量按钮**(位于该消息动作行,复制/分支之间)
   - 点击 → 宿主合成(Markdown 归一化:不朗读代码块/URL/标记符号)→ 自动播放
   - 播放器浮层:**播放/暂停 + 可拖动进度条(pointer 拖拽、单击定位、键盘 ←→/Home/End)** + 时间读数
   - 同时只有一条在播;播放中动作行不再悬停淡出
   - 失败就地提示(未配置/超时/上游拒绝/无文本等稳定错误码 → 本地化文案)
3. **设置 → 语音历史**
   - 每次朗读自动保存;按文本分组、多版本并列
   - 逐版本回放、「重新合成」(用当前配置重读并保留旧版本对比)、「删除」
4. **安全边界**:密钥只在宿主;`/api/mimotts/*` 路由先过 Connection 信任栅栏;请求体 1MiB 上限;文本超预算在句末截断朗读开头。

## 目录

```
dsh-mimotts/
├── package.json            # dsh.bundle + dsh.client manifest,engines.dsh 锁 0.2.0
├── cordis.patch.yml        # bundle 层:插入 id: mimotts 行(两侧共用)
├── tsconfig.json           # tsc → lib/node(Node 半侧 + 类型)
├── tsdown.config.ts        # 官方 clientBundle 的树外复刻 → lib/client.js
├── icon.svg  locale/{zh,en}.json
├── src/
│   ├── index.ts            # Host 插件:Config schema + 两条 Web 路由
│   ├── tts.ts              # MiMo TTS 客户端(/chat/completions + audio 输出块)
│   ├── wav.ts  normalize.ts# PCM→WAV 封装;Markdown→可朗读文本
│   ├── settings.ts         # 两侧共享:命名空间/路由/默认值/线格式类型
│   └── client/
│       ├── index.ts        # 浏览器插件:三个 slot 席位注册
│       ├── SpeakAction.tsx # 消息动作行的音量按钮 + 浮层播放器
│       ├── PlayerBar.tsx   # 可拖动进度条(role=slider,指针+键盘)
│       ├── BundleConfigCard.tsx + settings-controller.ts  # 插件页配置卡片
│       ├── RecordingsPage.tsx + RecordingsList.tsx        # 语音历史设置页
│       ├── chat-text.ts    # 从 Chat snapshot 取该消息的可朗读文本
│       ├── use-audio-player.ts  # 播放引擎(单活播放器)
│       ├── api.ts  status.ts  slots.ts  locales.ts  styles.ts  SpeakerIcon.tsx
├── tests/                  # 19 项单测(node --test)
└── scripts/
    ├── stub-e2e.mjs        # 本地桩服务端到端(无需真实密钥)
    └── live-e2e.mjs        # 真实 MiMo API 端到端(需 MIMO_API_KEY)
```

## 构建与安装

```sh
npm install          # 装依赖(无 prepare 钩子,需手动构建)
npm test             # 19 项单测
node scripts/stub-e2e.mjs
```

安装进 DSH profile(源码 checkout):

```sh
dsh plugin --profile demo add ./dsh-mimotts
# pnpm≥10 首次 git/本地源码安装需要为构建授权:把 dsh-mimotts 写入
# 该 profile 的 pnpm-workspace.yaml → allowBuilds: { dsh-mimotts: true },再重试
dsh --profile demo --dump-config   # 应能看到 "# == dsh-mimotts" 层
dsh --profile demo
```

打开 `http://127.0.0.1:3080`:**「插件」页 → @gw/dsh-mimotts 卡片**里填入 API Key(试听可即时验证),随后每条已定稿回复下都会出现音量按钮;**设置 → 语音历史** 管理已保存的朗读。

不想把密钥写进配置文件时,在 profile 的 `cordis.patch.yml` 里覆写该行:

```yaml
- id: mimotts
  config:
    apiKey: !!js process.env.MIMO_API_KEY
```

## 适配版本与「按版本选适配」

**分发不会按宿主版本自动下载对应适配版本。** 本插件打包时把 `@deepseek-ai/dsh-*` 依赖锁死在某一个 release(当前 0.2.0-rc.2),别人拉到的就是同一份 `package.json`;npm 不会"看对方 dsh 版本 → 选不同版本的 dsh-* 依赖"。真正的版本闸门是 dsh 的两处兼容性声明:

- `dsh.compatibility.dshReleases`:官方字段,精确列出本包适配的 release(如 `{ "0.2.0-rc.2": "compatible" }`)。安装器/市场在对方 dsh 版本不在白名单时**拒绝或警告**,而不是自动换一个适配版本。
- `engines.dsh`:npm 风格的范围闸门(当前 `>=0.2.0-rc.2 <0.3.0`),语义与上面互补。

运行时实际用的 `@deepseek-ai/dsh-*` 实例由**宿主 dsh 自带**(客户端半从平台模块表取 cordis/store/slots/primitives,Host 半由 Node 按宿主 `node_modules` 解析),插件代码只是复用宿主环境里的版本——只要代码用的 API 在该 dsh 版本里存在即可。想让 0.1.7 的 dsh 也能用,得单独发一份 pin 回 0.1.7-rc.2 的包(下方脚本一键生成),即"多版本并行发布",用户按自己的 dsh 版本装对应那份。

本插件跟随 deepseek-harness 的 release,但 dsh 的包版本规则有两点容易踩坑:

1. **`@deepseek-ai/dsh-*` 整条线共用同一个 release 版本号**(0.2.0-rc.2 的所有 dsh-* 包都标 0.2.0-rc.2)。
2. **`@deepseek-ai/cordis` 与 `@deepseek-ai/schemastery` 是独立版本号,不跟随 release**(0.2.0-rc.2 时代仍是 cordis `~4.0.4`、schemastery `~3.18.4`),无脑把全部依赖改成同一个 release 会 404。
3. **`0.2.0-rc.2` 起 `dsh-scope` 成为 peer 依赖**:`dsh-session` / `dsh-api-remotes` 把 `@deepseek-ai/dsh-scope` 列为 peer,需与 cordis 一样同时写在 `peerDependencies` 与 `devDependencies` 里,否则 `npm install` 报 ERESOLVE。

要把插件重新对准另一个 dsh release,**不要手改版本号**,用脚本:

```sh
node scripts/pin-dsh-version.mjs <release>            # 改 package.json(dsh-* + engines.dsh)并打印后续步骤
node scripts/pin-dsh-version.mjs <release> --install  # 改完顺手跑 npm install
node scripts/pin-dsh-version.mjs <release> --self     # 顺便把插件自身 version 也改成 release
node scripts/pin-dsh-version.mjs <release> --dry-run  # 只预览不写文件
```

脚本会自动:把 `@deepseek-ai/dsh-*`(含 dsh-scope)在 `peer` / `dev` 两处改到目标 release;把 `engines.dsh` 推进到 `>=<release> <次版本+1>.0`;把 `dsh.compatibility.dshReleases` 同步写成 `{ "<release>": "compatible" }`;并去 npm 查该 release 配套的 `cordis` / `schemastery` 区间(也可用 `--cordis=...` / `--schemastery=...` 手动指定,跳过网络)。改完 `package.json` 后跑 `npm install` 让 `node_modules` / `package-lock.json` 跟上,再 `npm run build && npm test`。

## MiMo TTS 线格式(与 `mimo-tts-wav` skill 完全一致)

`POST {baseUrl}/chat/completions`,头 `api-key: <key>`,体:

```json
{
  "model": "mimo-v2.5-tts",
  "audio": { "format": "wav", "voice": "mimo_default" },
  "messages": [{ "role": "assistant", "content": "<style>开心</style>要说的话" }]
}
```

音色克隆时 `audio` 换成 `{ "format": "wav", "voice_audio": { "format": "wav", "data": "<base64 wav>" } }`;响应 `choices[0].message.audio.data` 为 base64 音频(RIFF 或裸 PCM,裸 PCM 由插件包成 24kHz/16bit/mono WAV)。

## 已知边界

- 按**消息**朗读:动作行属于该条已定稿回复,朗读其正文(不读思考块与工具调用);代码块以"代码略"代替。
- 文本超过"单次最大字符数"时朗读开头并在句末截断(动作行旁有提示)。
- Desktop(Electron)走 IPC 桥接 fetch,路由路径不变。
- 语音输入(麦克风)是官方 `voice-input` 组合包的职责,与本插件互补不冲突。

## 验证记录

- `tsc` 全量类型检查通过(对着 0.2.0-rc.2 的真实 .d.ts)
- `node --test`:19/19 通过(请求形状/PCM 封装/Markdown 归一化/文本提取/错误码映射)
- `scripts/stub-e2e.mjs`:真实 HTTP 桩端到端通过(96,044 字节 WAV)
- MiMo 实机协议探测通过(与官方 `mimo-tts-wav` skill 同一契约,115,244 字节 WAV);`scripts/live-e2e.mjs` 可对真实 API 跑完整引擎链路

Install

dsh plugin --profile web add github:1497105876/dsh-mimotts

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source