dsh-voice
Multimodalzhuiyueya/dsh-voice
Voice plugin for DeepSeek Harness adding speech-to-text input and read-aloud TTS for text-only DeepSeek, with zero API key for web UI.
- ai-agents
- deepseek
- deepseek-harness
- deepseek-harness-plugin
- dsh-plugin
- multimodal
- speech-to-text
- stt
- text-to-speech
- tts
- voice
- web-speech-api
- whisper
README
DeepSeek's chat API is text-only β it can neither hear audio nor speak. dsh-voice bridges sound at the input/output boundary so the model never sees raw audio, yet gains a full voice loop:
π€ speech β text β DeepSeek (text-only) β text β π speech
Same idea as
dsh-vision-bridgeβ but for audio, the multimodal gap nobody has filled for DeepSeek Harness yet.
β¨ Features
| Layer | What it does | |
|---|---|---|
| π€ | Voice input (STT) β Web UI | A mic button in the composer tool row. Click to speak; the transcript is written straight into the input box via the browser Web Speech API. |
| π | Read-aloud (TTS) β Web UI | A speaker button on every assistant reply. Click to read it aloud via speechSynthesis. |
| π | voice_transcribe tool | Transcribe an attached audio file (wav/mp3/m4a/ogg/webm/flac) through any Whisper-compatible /audio/transcriptions endpoint. |
| π£οΈ | voice_speak tool | Synthesize text into an audio file through any OpenAI-compatible /audio/speech endpoint. |
- β Zero API key for the Web UI β pure browser speech, works out of the box.
- β Zero new model β DeepSeek stays text-only; speech is handled at the edge.
- β Configurable backends β point at local whisper.cpp / Kokoro for a fully free, keyless stack.
π§ How it works
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β dsh Web GUI β
β β
β you speak ββπ€ SpeechRecognitionβββΊ text βββΊ input box β
β β
β reply text ββπ speechSynthesisβββΊ you hear β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β²
β text (STT) β text (TTS)
βΌ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DeepSeek (text-only model) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
attached audio ββ voice_transcribe (Whisper-compatible) βββΊ text βββΊ model
model wants to speak ββ voice_speak (OpenAI-compatible TTS) βββΊ audio file
π¦ Install
# from a local checkout
dsh plugin --profile web add "file:/path/to/dsh-voice"
# or, once published to npm
dsh plugin --profile web add dsh-voice
Activation is automatic: the package ships a bundle patch (cordis.patch.yml) and declares dsh.bundle.patch, so dsh plugin add registers it into the profile's bundles for you.
Then restart dsh web (or wait for HMR). You should see π€ in the composer and π on each reply.
βοΈ Configuration
The π€ mic button needs voice.stt.apiBase (the browser records audio and sends it to the host's Whisper-compatible backend). The π read-aloud needs nothing (browser speechSynthesis). To customize read-aloud language/rate/pitch, edit the constants at the top of lib/client.js (TTS_LANG, TTS_RATE, TTS_PITCH).
settings.yaml:
voice:
stt: # mic button + voice_transcribe tool
enabled: true
apiBase: "" # REQUIRED for the mic. Examples:
# SiliconFlow: https://api.siliconflow.cn/v1
# local whisper.cpp: http://127.0.0.1:8080/v1
apiKeyEnv: VOICE_STT_API_KEY
model: whisper-1
language: "" # zh / en / ... ; empty = auto-detect
tts: # voice_speak tool
enabled: true
apiBase: "" # empty = https://api.openai.com/v1
apiKeyEnv: VOICE_TTS_API_KEY
model: tts-1
voice: alloy # alloy/echo/fable/onyx/nova/shimmer, or a local voice id
format: mp3
Why the mic needs a backend: Chrome's built-in
SpeechRecognitionuploads audio to Google, which is unreachable in some regions (you'd seeθ―ε«εΊιοΌnetwork). dsh-voice records withMediaRecorderand transcribes through your Whisper-compatible backend instead. Two free, keyless options:
- SiliconFlow (China-friendly, free tier) β
apiBase: https://api.siliconflow.cn/v1, modelFunAudioLLM/SenseVoiceSmallorwhisper-1.- Local whisper.cpp β fully offline,
apiBase: http://127.0.0.1:8080/v1(no key).
π§° Agent tools
| Tool | Parameters | Returns |
|---|---|---|
voice_transcribe | path (audio file), language? | { text, language } |
voice_speak | text, outPath?, voice? | { path, bytes } |
π Project layout
dsh-voice/
βββ package.json # dual-half plugin: host (main) + browser (client)
βββ cordis.patch.yml # bundle activation layer
βββ lib/
β βββ index.js # host half: settings + voice_transcribe/voice_speak tools
β βββ client.js # browser half: π€ / π buttons
β βββ types/
β βββ index.d.ts
β βββ client/index.d.ts
βββ README.md # this file
βββ README.zh-CN.md # δΈζη
πΊ Roadmap
- Wire browser-UI language / rate / pitch / auto-read into the
voice:settings page (currently code constants) -
autoRead: auto read-aloud on reply completion - Built-in free
edge-ttsbackend (no OpenAI key) - Local Whisper STT via
@xenova/transformers - Sentence-level reading with streaming interruption
π Credits
Inspired by these established voice solutions for other agents:
- slopus/happy (~23kβ ) β realtime voice interaction UX
- mbailey/voicemode (~1.3kβ ) β Claude Code voice mode
- caiovicentino/claude-call β local Whisper STT + edge-tts, no API key
- edge-tts β free Microsoft Edge neural voices
- ggerganov/whisper.cpp / OpenAI Whisper β speech recognition
- hexgrad/kokoro β local neural TTS