# dsh-tts English | [中文](README.md) A **text-to-speech** plugin for [dsh web](https://github.com/deepseek-ai/deepseek-harness): it reads the final answer of each assistant reply aloud with **Edge TTS** (Microsoft neural voices), enabling hands-free voice conversation. - **Real Edge TTS**: the host (Node) half synthesizes MP3 through Microsoft Edge's free "Read Aloud" neural-voice endpoint (a hand-rolled RFC 6455 WebSocket — Node's native WebSocket filters the handshake headers Edge requires); the browser plays it. No OpenAI/third-party keys. - **Final result only**: intermediate steps and tool-call narration are skipped; each turn reads only its closing answer, and a burst of completed turns plays just the latest. Long replies are auto-split into sentence segments and read back-to-back. Falls back to the browser's `speechSynthesis` offline. - **Voice & rate**: 20+ Edge neural voices built in (default `zh-CN-XiaoxiaoNeural`), with a 0.5×–2× rate slider. - **Speaker toggle**: a speaker button in the composer tool row (with a hover tooltip) flips the single "read new replies automatically" toggle; it also stops the current utterance. Linked to the setting of the same name. - **Approval alerts**: announces “need approval” when an approval is requested, independently toggleable; a custom alert sound can be picked (preset “Ding / Beep-beep / Dong”, or upload mp3/wav/ogg ≤ 1 MB — playing instantly when selected). - **Fully local, zero runtime dependencies**: the Edge protocol uses Node built-ins only, so the package installs offline. `dsh-plugin` topic: (https://github.com/topics/dsh-plugin) > Default voice `zh-CN-XiaoxiaoNeural`. To add official voices, append an Edge `ShortName` to `src/voices.ts`. ## Settings ![Settings](docs/settings.png) ## Install > Prerequisite: the dsh app closure must include the plugin's `@deepseek-ai/dsh-*` peer packages — any deployment carrying the `@deepseek-ai/dsh-web-app` bundle (every official `dsh web` profile qualifies). ### Method 1: build a local tarball, then install (most common) > This repo is published as **source** — there is no prebuilt release `tgz`. Build `dsh-tts-0.1.0.tgz` in the repo first, then install from it. 1. **Clone/enter the repo and build**: ```sh git clone https://github.com/Vim0x3c/dsh-tts.git dsh-tts cd dsh-tts pnpm install pnpm build # tsdown: produces lib/index.js, lib/client.js, etc. npm pack # produces dsh-tts-0.1.0.tgz ``` > If you only need it on this machine, you can just run `pnpm build` in the repo to get `lib/`; but **installing to dsh is best done with the tarball** (generated by `npm pack`). 2. **Install into the web profile**: ```sh dsh plugin --profile web add -w ./dsh-tts-0.1.0.tgz ``` > The `-w` flag is **required**: every profile ships a `pnpm-workspace.yaml`, so pnpm treats the profile dir as a workspace root and a bare `add` fails with `ERR_PNPM_ADDING_TO_ROOT`. 3. **Restart `dsh web`** (the host half loads on boot): ```sh dsh web # or: dsh --profile web ``` 4. **Verify the install**: ```sh # ① the plugin is in the composed config (expect `- id: dsh-tts`) dsh --profile web --dump-config | grep dsh-tts # ② the host bridge is up (expect 200 {"ok":true}) curl http://127.0.0.1:8765/health ``` ### Method 2: git URL install (not recommended) ```sh dsh plugin --profile web add github:Vim0x3c/dsh-tts ``` > ⚠️ Not recommended: a git install resolves **source**, not the built `lib/`, and the peer packages are not published to npm, so a git install usually cannot build or resolve them. Build the tarball locally and use Method 1 instead. ### Remove / reinstall ```sh dsh plugin --profile web remove dsh-tts # remove dsh plugin --profile web add -w ./dsh-tts-0.1.0.tgz # reinstall (overwrites) ``` ## Usage 1. Open dsh web **Settings → Voice** (see the screenshot above). 2. Turn on **Read new replies automatically**, then click **Test voice** to confirm the Edge bridge is reachable. 3. From then on, each assistant **final answer** is read aloud in the selected voice. - **Speaker toggle**: the speaker button in the composer tool row (hover for a tip). It flips “read new replies automatically” on/off; turning it off also stops the current utterance. - **Read new replies automatically**: the single master toggle. When off, replies are not auto-read, but **Test voice** still works manually. - **Speak approval requests**: announces “need approval” when an approval is requested; independently toggleable, with an optional custom **approval alert sound** (preset or uploaded) that plays instantly when selected. - **Stop**: interrupts the current playback at any time. > **Autoplay policy**: browsers usually require a prior user interaction before playing audio. Because you only receive replies after sending a message, this is generally satisfied; if playback is ever blocked, click **Test voice** once in Settings to unlock audio. > Voice input (mic/STT) is not included: browser `SpeechRecognition` works only in Chrome/Edge (not Safari), and dsh has no built-in STT. For voice input, see [dsh-plugin-voice-input](https://github.com/Hjay1101/dsh-plugin-voice-input) or [dsh-voice](https://github.com/Jesse-njx/dsh-voice). ## Development ```sh pnpm install pnpm build # tsdown: lib/index.js (host), lib/invariant.js, lib/client.js (browser) pnpm test # vitest: unit/regression tests ``` The build is self-contained (tsdown + lightningcss); no monorepo checkout is required. `prepare` runs the same build, so a permitted git install also produces artifacts. > This repo's local `node_modules` reuses the `dsh-skin-appearance` pnpm store for offline type-check/build; release tarballs need not include the peer packages (the dsh app closure resolves them at runtime). ## How it works - **Host half** (`lib/index.js`): a loopback-only `127.0.0.1` HTTP server (probes ports 8765–8780): - `GET /health` — bridge liveness; - `POST /synthesize` — takes `{ text, voice, rate }`, synthesizes MP3 via the native Edge protocol, returns it. - CORS is hardened: it reflects only allow-listed dsh web origins and 403s unknown browser Origins; no-Origin callers (curl) still work. The Edge protocol (`src/edge/`) mirrors Edge's "Read Aloud": `Sec-MS-GEC` (SHA-256 FILETIME) + `Sec-MS-GEC-Version`, browser-fingerprint handshake headers (`Origin`/`User-Agent`/`Cookie: muid`), `speech.config` → SSML messages, binary frames parsed as `[2-byte header length][Path: audio header][MP3]`. WebSocket is a hand-rolled RFC 6455 client. Zero third-party dependencies. - **Browser half** (`lib/client.js`): subscribes via `ctx.sessions`, picks each completed turn's final answer (only the newest of a burst), splits long text into segments, and plays with a generation-token + AbortController so stop/interrupt races are correct (stopping also silences approval sounds). Falls back to the browser Web Speech API on Edge failure. Settings persist to browser localStorage (`dsh-tts.settings`) because the dsh web host only exposes a fixed settings-namespace allowlist to the browser, which a standalone plugin cannot extend (see `WEB_SETTINGS_NAMESPACES` in `api-proxy.ts`). ## Known limitations - **Edge cloud synthesis needs network**: the host must reach `speech.platform.bing.com`. Test with “Test voice” first; on `could not connect` or 403 it automatically falls back to the browser's local Web Speech voices. - **`Sec-MS-GEC` token is aligned** with the current `edge-tts` algorithm (SHA-256 FILETIME + `Sec-MS-GEC-Version`). If Microsoft rotates it again, fix `src/edge/token.ts` (the "Test voice" button surfaces a 403 immediately). - **Active session only**: auto-reading follows the session currently in view; background sessions are not spoken. - **In-process bridge**: the bridge lives with the `dsh web` host process and shuts down when dsh web stops. - **No voice input in this plugin** (see Usage above). ## License MIT