Introduction¶
The philosophy of DSH (DeepSeek Harness) is “everything is a plugin”; messages, tools, and outputs can all be taken over by plugins. However, getting an agent to speak directly still involves many engineering challenges: TTS interfaces from different vendors are not unified, where to store keys, how to select voices, what tone to use for each segment, and other plugins needing to reuse the same voice service—all of this needs to be pieced together. The dsh-tts-service plugin is designed to solve these problems. Maintained by loveandbeepbrother, it provides multiple TTS vendors and a unified voice service in a single package. Below, I introduce its structure, installation method, and typical usage.
Positioning¶
dsh-tts-service is a multi-vendor Text-to-Speech service for DeepSeek Harness (DSH). It registers Doubao TTS 2.0, Alibaba Cloud Bailian (Model Studio, including CosyVoice, Qwen-Audio-TTS, and Qwen3-TTS), and any OpenAI-compatible endpoint that supports POST /audio/speech, allowing users to select voices themselves and enabling agents to specify tone per-segment.
It is also a service that can be reused by other plugins: other plugins can call the same ctx.ttsService without needing to integrate TTS themselves.
A single package is divided into multiple plugin entries, which can be toggled on or off in the DSH plugin page:
| Entry ID | Function | Enabled by Default |
|---|---|---|
tts-service |
Provides the service ctx.ttsService: vendor registration, selection (vendor, voice, language), synthesis, and voice list. |
Yes |
tts-doubao |
Vendor: Doubao (Volcengine) TTS 2.0. | No |
tts-bailian |
Vendor: Alibaba Cloud Bailian / Model Studio. | No |
tts-openai-compatible |
Vendor: Any endpoint compatible with POST /audio/speech. |
No |
tts-say |
Agent tools tts_service_say, tts_service_voices, tts_service_select, and the output registry ctx.ttsSay. |
Yes |
tts-speaker-windows |
Output method: Play through PC speakers when at the computer (Windows only). | Yes |
A note to make: the plugin has no default vendor or default voice. Until you select one, tts_service_say will fail with VOICE_NOT_SELECTED and inform the agent where to make the selection. Additionally, the current version synthesizes full segments and does not support streaming output.
Installation and Enablement¶
Installation commands are taken from the project README:
dsh plugin add @loveandbeepbrother/dsh-tts-service
It can also be installed from GitHub:
dsh plugin add github:loveandbeepbrother/dsh-tts-service
Runtime environment requirements:
- DSH
>=0.2.1-rc.3 <0.3.0-0(including pre-release versions; the project is tested against 0.2.1-rc.5). - Node.js 22 or higher; the version included with DSH is sufficient.
- At least one vendor’s account and API Key; synthesis fees are billed by the vendor.
- When using speaker output, Windows and Windows PowerShell (
powershell.exe, built into the system by default) are required.
Enabling steps:
- Open a vendor entry (e.g.,
tts-doubao,tts-bailian, ortts-openai-compatible) in the plugin page and fill out its form. - Store the API Key in DSH’s credential store.
- Select a voice: let the agent “list available voices” then “use a specific voice”, or directly fill in
providerandvoicesin the Text-to-Speech Service (tts-service) settings. - Ask the agent to say a sentence.
Key Management¶
The configuration file does not store key values, only key names. Key values come from DSH’s credential service (ctx.credentials, i.e., the key store on the settings page); if the credential service is not loaded, they come from the process environment variables when starting DSH. Keys are read on every request, so changes take effect immediately after rotation. The settings page only indicates whether the key has been configured.
| Entry | Config Field | Default Key Name | Purpose |
|---|---|---|---|
tts-doubao |
apiKeyEnv |
VOLCENGINE_TTS_API_KEY |
Synthesis (the API Key from the “API Key Management” in the Volcengine console). |
tts-doubao |
accessKeyEnv, secretKeyEnv |
VOLCENGINE_ACCESS_KEY, VOLCENGINE_SECRET_KEY |
Optional: Volcengine account AK/SK, used only to list the complete voice directory. |
tts-bailian |
apiKeyEnv |
DASHSCOPE_API_KEY |
Synthesis and listing cloned voices (same name as the official DashScope SDK). |
tts-openai-compatible |
apiKeyEnv |
OPENAI_COMPATIBLE_TTS_API_KEY |
Sent as Authorization: Bearer. You can leave the key field empty for local endpoints that do not require a key. |
These field names can be renamed. For example, if you need to connect to two endpoints of the same type, use different key names to distinguish them.
Selecting Voices¶
Voice selection is stored in the settings of tts-service and persisted to the user profile; no restart is required. The settings include:
| Field | Meaning |
|---|---|
provider |
Default vendor ID, empty means none. |
voices |
Default voice for each vendor: Vendor ID -> Voice ID. Voice IDs only make sense within their respective vendor, so switching from Doubao to Bailian and back will preserve the selection on both sides. |
language |
Default language code, defaults to auto, must be a value declared as supported by the vendor. |
There are three ways to set this, all writing to the same settings and taking effect immediately:
- Settings page: Modify
providerandvoicesin the Text-to-Speech Service settings. - Agent commands: First ask “What voices are available” (
tts_service_voices), then “Switch to…” (tts_service_select). The agent’s reply will indicate the old voice, the new voice, and how to switch back. - Calling from plugin code:
await ctx.ttsService.configure({ provider: 'doubao', voices: { doubao: 'voice-id' } })
tts_service_voices (corresponding to ctx.ttsService.voices(id) in the plugin) lists voices, their supported languages, and whether they follow tone descriptions. If the list is incomplete, it will be clearly marked as such, and voice IDs can also be entered manually.
Error handling is explicit: if the vendor is not registered, the language is not supported, the text exceeds maxChars, or the format cannot be generated, the operation will fail directly. The service will not silently switch to another vendor.
Vendor Details¶
Doubao TTS 2.0 (tts-doubao, Vendor ID doubao)¶
| Field | Default Value | Description |
|---|---|---|
providerId |
doubao |
Can be changed if you need to run two instances. |
resourceId |
seed-tts-2.0 |
Sent as X-Api-Resource-Id. |
apiKeyEnv, accessKeyEnv, secretKeyEnv |
See above | |
sampleRate |
24000 |
16-bit mono PCM. |
maxChars |
300 |
Character limit per request. |
timeoutMs |
60000 |
There are a few details to note. Tone descriptions and “context” (the user’s previous sentence) are merged into a single voice instruction and sent via additions.context_texts. Only Doubao 2.0 system voices (IDs containing uranus) follow this instruction; cloned voices do not. Regarding the voice list: without AK/SK, a static table of 2.0 system voices transcribed from the official voice table is returned, marked as incomplete; with AK/SK, the plugin calls the official ListSpeakers API (HMAC-SHA256 signature, service is speech_saas_prod, region is cn-beijing). Language support includes auto, zh, en, ja, sent via explicit_language.
Alibaba Bailian (tts-bailian, Vendor ID bailian)¶
| Field | Default Value | Description |
|---|---|---|
model |
cosyvoice-v3-flash |
Can also be cosyvoice-v3-plus, cosyvoice-v2, cosyvoice-v3.5-flash, qwen-audio-3.1-tts-flash, qwen3-tts-flash, etc. Voices only match the same model. |
region |
cn-beijing |
Optional: ap-southeast-1, cn-hongkong, us-east-1, eu-central-1, ap-northeast-1. Keys, workspace, and models are isolated by region. |
workspaceId |
— | The first segment of the API host tag (e.g., llm-xxx). Once set, the workspace domain {workspaceId}.{region}.maas.aliyuncs.com is used; otherwise, the old DashScope domain is used. eu-central-1 and ap-northeast-1 must have this set. Alibaba Cloud indicates that the DashScope domain will no longer receive new features starting from 2026-09-30, so it is recommended to set this. |
apiKeyEnv |
DASHSCOPE_API_KEY |
|
sampleRate |
24000 |
CosyVoice / Qwen-Audio-TTS; Qwen3-TTS is fixed at 24000. |
maxChars |
Qwen3-TTS is 600, others are 2000 | |
timeoutMs |
60000 |
Covers synthesis calls and audio downloads. |
At the API level, CosyVoice and Qwen-Audio-TTS use /api/v1/services/audio/tts/SpeechSynthesizer, while Qwen3-TTS uses /api/v1/services/aigc/multimodal-generation/generation; both return an audio URL valid for 24 hours, and the plugin is responsible for downloading it. Whether free-form tone descriptions (instruction) are followed depends on the model:
| Model | Follows Free-form Tone Description |
|---|---|
qwen-audio-* |
Yes, system, cloned, and designed voices are all supported (except for a few English voices). |
cosyvoice-v3.5-* |
Yes (this model only has cloned and designed voices). |
cosyvoice-v3-flash |
Only cloned and designed voices; system voices only accept fixed instruction templates. |
OpenAI Compatible Endpoint (tts-openai-compatible)¶
Any endpoint compatible with POST /audio/speech can be connected directly. Keys are sent as Authorization: Bearer, and the key field can be left empty for local endpoints that do not require a key.
Reusing Service from Plugins¶
This package is an ES Module; main in package.json points to ./src/service.js, and types points to ./index.d.ts. In addition to the default export, the exports field also exports various entries via subpaths: ./doubao, ./bailian, ./openai-compatible, ./say, ./speaker-windows, with corresponding locale files having matching subpaths. To call the voice service directly in your own plugin, you can use ctx.ttsService.configure(...) (see above) or ctx.ttsService.voices(id).
Even if the tts-say entry is not enabled in other plugins, they can still reuse the same ctx.ttsService. This reduces the risk of multiple plugins each integrating TTS, voice selection, and key management strategies inconsistently.
Applicable Scenarios and Notes¶
Suitable for the following types of people:
- Developers who want an agent to speak directly in DSH and control tone per-segment.
- People already using multiple TTS vendors who want unified voice selection and key management.
- People who want to create a plugin that requires broadcasting or voice reminders and want to reuse the same voice service.
Notes when using:
- First open a vendor entry in the plugin page and configure the key, then select a voice; otherwise, synthesis will fail.
- Synthesis is billed by the vendor. Keys are read on every request and take effect immediately after rotation.
- Only use scenarios that require a full segment and do not require streaming playback; the current version does not support streaming synthesis.
- Whether tone descriptions take effect depends on the vendor and the voice model. When listing voices, check if they are marked as “following tone description”.
- The plugin runs with the permissions of the current DSH process. You should check the source code and license (
package.jsondeclares MIT License) yourself before installing. - The community directory site is an independent site with no official affiliation with DeepSeek or Hanhuan.
Summary¶
The value of dsh-tts-service lies in consolidating “which TTS to connect to, which voice to select, tone per-segment, and reusing by other plugins” into a single plugin: one package, multiple entries, vendors can be toggled on demand, voices are saved in the profile, and keys are stored only by name. For those who need to make DSH agents speak, this is currently the most convenient path.
- Directory Page: https://www.skillhub.cn/plugins/loveandbeepbrother/dsh-tts-service
- GitHub: https://github.com/loveandbeepbrother/dsh-tts-service