feat(tts): add MiniMax TTS provider support#49894
Conversation
Add MiniMax as a new TTS provider option for OpenClaw voice synthesis. Features: - Support for MiniMax Speech API (speech-01-turbo, speech-01-hd, speech-02, speech-02-hd) - Configurable voice_id, speed, volume, and pitch parameters - Support for both audio-file and voice-note synthesis targets - Telephony synthesis support (for Discord voice channel integration) - Environment variable support via MINIMAX_API_KEY Configuration example: This enables Discord voice channel users to use MiniMax's high-quality Chinese TTS voices for text-to-speech playback.
Greptile SummaryThis PR adds MiniMax as a new TTS provider, covering config types, Zod schema validation, provider implementation, and integration into the resolution/registry pipeline. The implementation is largely sound, but two issues are worth addressing before merging:
Confidence Score: 3/5
|
| export async function listMiniMaxVoices(params: { | ||
| apiKey: string; | ||
| baseUrl?: string; | ||
| }): Promise<SpeechVoiceOption[]> { | ||
| // MiniMax doesn't have a public list voices API, so we return common voices | ||
| // Users can use custom voice IDs from their MiniMax dashboard | ||
| return MINIMAX_VOICE_IDS.map((voiceId) => ({ | ||
| id: voiceId, | ||
| name: voiceId.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()), | ||
| })); | ||
| } |
There was a problem hiding this comment.
Unused parameters create a false API constraint
listMiniMaxVoices declares apiKey: string (required) and baseUrl?: string as parameters, but neither is used — the function always returns a hardcoded static list. As a consequence, the listVoices callback (line 104–116) throws "MiniMax API key missing" when no key is configured, even though no network call is made and no key is actually needed. This blocks voice listing for unconfigured users unnecessarily.
Consider dropping the unused params (or making them optional) to reflect the real behaviour:
export async function listMiniMaxVoices(): Promise<SpeechVoiceOption[]> {
return MINIMAX_VOICE_IDS.map((voiceId) => ({
id: voiceId,
name: voiceId.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
}));
}And relax the key requirement in listVoices accordingly.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/tts/providers/minimax.ts
Line: 87-97
Comment:
**Unused parameters create a false API constraint**
`listMiniMaxVoices` declares `apiKey: string` (required) and `baseUrl?: string` as parameters, but neither is used — the function always returns a hardcoded static list. As a consequence, the `listVoices` callback (line 104–116) throws `"MiniMax API key missing"` when no key is configured, even though no network call is made and no key is actually needed. This blocks voice listing for unconfigured users unnecessarily.
Consider dropping the unused params (or making them optional) to reflect the real behaviour:
```typescript
export async function listMiniMaxVoices(): Promise<SpeechVoiceOption[]> {
return MINIMAX_VOICE_IDS.map((voiceId) => ({
id: voiceId,
name: voiceId.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
}));
}
```
And relax the key requirement in `listVoices` accordingly.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Lines 479 to 482 in 5041507
getTtsProvider() still auto-selects only OpenAI and ElevenLabs before falling back to Microsoft, even though this commit adds MiniMax key resolution elsewhere in the same file. If a user sets only messages.tts.minimax.apiKey/MINIMAX_API_KEY and leaves messages.tts.provider unset, OpenClaw will still pick microsoft; textToSpeech() tries that provider first, and because Microsoft is always configured, MiniMax is never attempted. That makes the advertised env-var support ineffective unless users also know to set the provider explicitly.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| throw new Error(`MiniMax TTS API error (${response.status}): ${error}`); | ||
| } | ||
|
|
||
| return Buffer.from(await response.arrayBuffer()); |
There was a problem hiding this comment.
Decode MiniMax's JSON TTS response before saving audio
MiniMax's official T2A HTTP API returns a JSON envelope whose data.audio field contains hex-encoded audio (or a URL if requested), not a raw audio response body. Reading response.arrayBuffer() here therefore gives you the bytes of the JSON payload itself, and textToSpeech() will write that JSON into a .mp3 file. In practice, every successful MiniMax synthesis call will produce an unreadable audio file until this response is parsed and decoded.
Useful? React with 👍 / 👎.
| return { | ||
| audioBuffer, | ||
| outputFormat: "mp3", | ||
| sampleRate: 24000, // MiniMax default sample rate |
There was a problem hiding this comment.
Return PCM for telephony synthesis instead of MP3 bytes
The telephony path expects raw 16-bit PCM: extensions/voice-call/src/telephony-tts.ts:33-43 passes the buffer straight into convertPcmToMulaw8k, and extensions/voice-call/src/telephony-audio.ts:54-56 resamples it as PCM samples. This provider advertises telephony support but returns MP3 bytes with sampleRate: 24000, so MiniMax-backed voice calls will be converted from compressed frames as if they were PCM and come out as noise. Either request PCM from MiniMax or decode the MP3 before exposing synthesizeTelephony.
Useful? React with 👍 / 👎.
- Add MiniMax to getTtsProvider() auto-detection, after elevenlabs - Make listMiniMaxVoices params optional (no API call needed for static voice list) - Remove unnecessary API key check from listVoices callback Fixes PR feedback from greptile-apps and Codex reviewers
|
Both issues fixed in def1611:
Thanks for the thorough review! 🎭 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: def1611966
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const response = await fetch(`${normalizeMiniMaxBaseUrl(baseUrl)}/v1/t2a_v2`, { | ||
| method: "POST", | ||
| headers: { | ||
| Authorization: `Bearer ${apiKey}`, |
There was a problem hiding this comment.
Normalize MiniMax keys before populating Authorization
MiniMax credentials already need copy/paste normalization elsewhere in this repo (src/agents/minimax-vlm.ts:54, plus src/agents/minimax-vlm.normalizes-api-key.test.ts) because portal/CLI tokens can contain CR/LF or non-Latin1 garbage. Here the raw apiKey is interpolated straight into Authorization, and Node's fetch rejects those values before any request is sent (invalid header value / ByteString errors). In practice, any user who pastes one of those common malformed MINIMAX_API_KEY values into messages.tts.minimax.apiKey will see every MiniMax TTS call fail immediately.
Useful? React with 👍 / 👎.
| outputFormat: "mp3", | ||
| fileExtension: ".mp3", | ||
| voiceCompatible: req.target === "voice-note", |
There was a problem hiding this comment.
Emit a voice-note codec for bubble channels
src/tts/tts.ts:525 routes Feishu and WhatsApp through the voice-note target, but this provider still returns .mp3 for that path and marks it voice-compatible. That breaks channel-specific delivery: for example, Feishu only treats Opus/Ogg uploads as audio messages (extensions/feishu/src/media.ts:491-496), so MiniMax-backed auto-TTS on Feishu will fall back to a generic file upload instead of a playable voice bubble. The provider needs to transcode/request a bubble-compatible codec before claiming voice-note support.
Useful? React with 👍 / 👎.
|
Closing as superseded by the landed MiniMax speech-provider implementation on current main. MiniMax TTS is already in-tree through the bundled MiniMax plugin path:
That landed on the extension-owned/provider-owned surface rather than the older core Thanks for putting this together. |
Summary
Add MiniMax as a new TTS provider option for OpenClaw voice synthesis.
Motivation
Support Discord voice channel users who want to use MiniMax's high-quality Chinese TTS voices for text-to-speech playback.
Features
MINIMAX_API_KEYConfiguration
Available Voices
Files Changed
src/tts/providers/minimax.ts- New MiniMax TTS provider implementationsrc/config/types.tts.ts- MiniMax type definitionssrc/config/zod-schema.core.ts- MiniMax Zod schemasrc/tts/provider-registry.ts- Provider registrationsrc/tts/tts.ts- Configuration resolutionGenerated by 花火 (Hanabi) 🎭"