Skip to content

feat(tts): add MiniMax TTS provider support#49894

Closed
ViccRondo wants to merge 2 commits into
openclaw:mainfrom
ViccRondo:feature/minimax-tts-provider
Closed

feat(tts): add MiniMax TTS provider support#49894
ViccRondo wants to merge 2 commits into
openclaw:mainfrom
ViccRondo:feature/minimax-tts-provider

Conversation

@ViccRondo

Copy link
Copy Markdown

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

  • 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

messages:
  tts:
    provider: minimax
    minimax:
      apiKey: your-api-key  # or set MINIMAX_API_KEY env var
      model: speech-01-turbo
      voiceId: female-shaonv
      speed: 1.0
      volume: 1.0
      pitch: 0

Available Voices

Voice ID Description
female-shaonv Young female voice
female-tianmei Female voice
female-emma Female voice
male-baijia Male voice
male-yunyang Male voice
male-john Male voice

Files Changed

  • src/tts/providers/minimax.ts - New MiniMax TTS provider implementation
  • src/config/types.tts.ts - MiniMax type definitions
  • src/config/zod-schema.core.ts - MiniMax Zod schema
  • src/tts/provider-registry.ts - Provider registration
  • src/tts/tts.ts - Configuration resolution

Generated by 花火 (Hanabi) 🎭"

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-apps

greptile-apps Bot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This 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:

  • Auto-detection gap in getTtsProvider (src/tts/tts.ts): MiniMax was added to TTS_PROVIDERS and resolveTtsApiKey, but getTtsProvider — the function that picks a provider automatically when none is configured — was not updated. Users who rely on MINIMAX_API_KEY without an explicit provider: minimax in their config will silently fall back to microsoft instead of MiniMax being auto-selected, unlike OpenAI and ElevenLabs which are checked in that function.
  • Unused parameters in listMiniMaxVoices (src/tts/providers/minimax.ts): The function accepts a required apiKey parameter but never uses it (the voice list is static/hardcoded). This causes listVoices to unnecessarily throw "MiniMax API key missing" when listing voices for an unconfigured provider, even though no network call is made.

Confidence Score: 3/5

  • Mostly safe but has a silent provider auto-detection regression and a misleading API contract on voice listing.
  • The config schema, type definitions, and provider registration are all correct. The fetch-based synthesis implementation is reasonable. The two issues — missing MiniMax from getTtsProvider's auto-detection chain and the unused-but-required apiKey in listMiniMaxVoices — are real functional bugs that will confuse users but won't crash the system. The auto-detection gap in particular is the kind of silent regression that's easy to miss in testing if only the explicit-config path is exercised.
  • src/tts/tts.ts (auto-detection logic) and src/tts/providers/minimax.ts (listMiniMaxVoices signature)

Comments Outside Diff (1)

  1. src/tts/tts.ts, line 476-482 (link)

    P1 MiniMax missing from auto-detection chain

    getTtsProvider checks for OpenAI and ElevenLabs API keys and auto-selects those providers, but MiniMax was not added to this logic. A user who only sets MINIMAX_API_KEY (with no explicit provider: minimax in config) will silently fall through to microsoft instead of MiniMax being picked up automatically — even though resolveTtsApiKey now correctly handles "minimax".

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/tts/tts.ts
    Line: 476-482
    
    Comment:
    **MiniMax missing from auto-detection chain**
    
    `getTtsProvider` checks for OpenAI and ElevenLabs API keys and auto-selects those providers, but MiniMax was not added to this logic. A user who only sets `MINIMAX_API_KEY` (with no explicit `provider: minimax` in config) will silently fall through to `microsoft` instead of MiniMax being picked up automatically — even though `resolveTtsApiKey` now correctly handles `"minimax"`.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/tts/tts.ts
Line: 476-482

Comment:
**MiniMax missing from auto-detection chain**

`getTtsProvider` checks for OpenAI and ElevenLabs API keys and auto-selects those providers, but MiniMax was not added to this logic. A user who only sets `MINIMAX_API_KEY` (with no explicit `provider: minimax` in config) will silently fall through to `microsoft` instead of MiniMax being picked up automatically — even though `resolveTtsApiKey` now correctly handles `"minimax"`.

```suggestion
  if (resolveTtsApiKey(config, "openai")) {
    return "openai";
  }
  if (resolveTtsApiKey(config, "elevenlabs")) {
    return "elevenlabs";
  }
  if (resolveTtsApiKey(config, "minimax")) {
    return "minimax";
  }
  return "microsoft";
```

How can I resolve this? If you propose a fix, please make it concise.

---

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.

Last reviewed commit: "feat(tts): add MiniM..."

Comment thread src/tts/providers/minimax.ts Outdated
Comment on lines +87 to +97
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()),
}));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

openclaw/src/tts/tts.ts

Lines 479 to 482 in 5041507

if (resolveTtsApiKey(config, "elevenlabs")) {
return "elevenlabs";
}
return "microsoft";

P2 Badge Auto-select MiniMax when it is the only configured TTS key

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());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +163 to +166
return {
audioBuffer,
outputFormat: "mp3",
sampleRate: 24000, // MiniMax default sample rate

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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
@ViccRondo

Copy link
Copy Markdown
Author

Both issues fixed in def1611:

  • Auto-detection: Added MiniMax to getTtsProvider() after ElevenLabs, so users with only MINIMAX_API_KEY set will get auto-detected without needing explicit provider: minimax
  • listVoices: Removed unused apiKey and baseUrl params from listMiniMaxVoices() — no API call is needed for the static voice list, so the key check was unnecessary

Thanks for the thorough review! 🎭

@ViccRondo ViccRondo left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in def1611 — added MiniMax to the auto-detection chain in getTtsProvider(). Users who set only MINIMAX_API_KEY will now have MiniMax auto-selected without needing explicit provider config. Thanks for the review! 🎭

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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}`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +125 to +127
outputFormat: "mp3",
fileExtension: ".mp3",
voiceCompatible: req.target === "voice-note",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

@steipete

steipete commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

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:

  • extensions/minimax/speech-provider.ts
  • extensions/minimax/tts.ts
  • extensions/minimax/index.ts
  • docs/tools/tts.md

That landed on the extension-owned/provider-owned surface rather than the older core src/tts/* path proposed here, so this PR no longer makes sense to merge in parallel.

Thanks for putting this together.

@steipete steipete closed this Apr 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants