feat: upgrade MiniMax default model to M3#6468
Conversation
- Add ChatMiniMax node with FlowiseChatMiniMax integration - Add MiniMaxApi credential definition - Update models.json with MiniMax model list (M3, M2.7, M2.7-highspeed) - Set MiniMax-M3 as the default model - Add MiniMax TTS support in textToSpeech.ts
There was a problem hiding this comment.
Code Review
This pull request introduces support for MiniMax services, including a new ChatMiniMax model node, MiniMax API credentials, and MiniMax Text-to-Speech (TTS) integration. Feedback on the changes highlights a critical bug in the TTS stream processing where the loop does not fully terminate and the stream reader is not cancelled on error or abort, leading to potential resource leaks. Additionally, the falsy checks for temperature and topP in the ChatMiniMax node should be updated to safe nullish checks to allow valid values of 0.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const processMinimaxStream = async () => { | ||
| for (;;) { | ||
| if (abortController.signal.aborted) { | ||
| reader.cancel() | ||
| streamDestroyed = true | ||
| reject(new Error('TTS generation aborted')) | ||
| return | ||
| } | ||
|
|
||
| const { done, value } = await reader.read() | ||
| if (done) break | ||
|
|
||
| sseBuffer += decoder.decode(value, { stream: true }) | ||
| const lines = sseBuffer.split('\n') | ||
| sseBuffer = lines.pop() || '' | ||
|
|
||
| for (const line of lines) { | ||
| const trimmedLine = line.trim() | ||
| if (!trimmedLine || trimmedLine.startsWith(':')) { | ||
| continue | ||
| } | ||
|
|
||
| if (trimmedLine.startsWith('data:')) { | ||
| const jsonStr = trimmedLine.slice(5).trim() | ||
| if (!jsonStr) continue | ||
|
|
||
| try { | ||
| const eventData = JSON.parse(jsonStr) | ||
|
|
||
| if (eventData.base_resp?.status_code !== 0) { | ||
| const errorMsg = eventData.base_resp?.status_msg || 'Unknown error' | ||
| reject(new Error(`MiniMax TTS error: ${errorMsg}`)) | ||
| return | ||
| } | ||
|
|
||
| if (eventData.data?.audio) { | ||
| const audioChunk = Buffer.from(eventData.data.audio, 'hex') | ||
| onChunk(audioChunk) | ||
| } | ||
|
|
||
| if (eventData.data?.status === 2) { | ||
| break | ||
| } | ||
| } catch { | ||
| // Skip malformed JSON | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| onEnd() | ||
| resolve() | ||
| } |
There was a problem hiding this comment.
There are two critical issues in processMinimaxStream:
- The
breakstatement on line 200 only exits the innerfor (const line of lines)loop. The outerfor (;;)loop continues to run and callreader.read(), causing unnecessary processing and potential hangs. - If an error occurs (
status_code !== 0) or the stream is aborted, the stream reader is not properly cancelled, leading to resource/connection leaks.
Using a labeled loop (outerLoop) and ensuring reader.cancel() is called on all exit paths resolves these issues. Additionally, throwing an error when the API returns an invalid status code promotes fail-fast behavior.
const processMinimaxStream = async () => {
try {
outerLoop: for (;;) {
if (abortController.signal.aborted) {
await reader.cancel();
streamDestroyed = true;
reject(new Error("TTS generation aborted"));
return;
}
const { done, value } = await reader.read();
if (done) break;
sseBuffer += decoder.decode(value, { stream: true });
const lines = sseBuffer.split("\n");
sseBuffer = lines.pop() || "";
for (const line of lines) {
const trimmedLine = line.trim();
if (!trimmedLine || trimmedLine.startsWith(":")) {
continue;
}
if (trimmedLine.startsWith("data:")) {
const jsonStr = trimmedLine.slice(5).trim();
if (!jsonStr) continue;
try {
const eventData = JSON.parse(jsonStr);
if (eventData.base_resp?.status_code !== 0) {
const errorMsg = eventData.base_resp?.status_msg || "Unknown error";
await reader.cancel();
reject(new Error("MiniMax TTS error: " + errorMsg));
return;
}
if (eventData.data?.audio) {
const audioChunk = Buffer.from(eventData.data.audio, "hex");
onChunk(audioChunk);
}
if (eventData.data?.status === 2) {
await reader.cancel();
break outerLoop;
}
} catch {
// Skip malformed JSON
}
}
}
}
onEnd();
resolve();
} catch (err) {
await reader.cancel().catch(() => {});
reject(err);
}
}References
- When handling potentially invalid data from external sources (like an API response), prefer throwing an error for invalid input types rather than silently returning a default or empty value. This promotes fail-fast behavior.
| if (temperature) obj.temperature = parseFloat(temperature) | ||
| if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) | ||
| if (topP) obj.topP = parseFloat(topP) | ||
| if (cache) obj.cache = cache |
There was a problem hiding this comment.
Checking if (temperature) can fail when temperature is resolved as the number 0 (which is falsy in JavaScript), preventing users from setting a deterministic temperature of 0. It is safer to check temperature != null && temperature !== '' to allow 0 as a valid value. Similarly, topP should be checked safely. Use loose equality (!= null) as a standard idiom for nullish checks.
| if (temperature) obj.temperature = parseFloat(temperature) | |
| if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) | |
| if (topP) obj.topP = parseFloat(topP) | |
| if (cache) obj.cache = cache | |
| if (temperature != null && temperature !== '') obj.temperature = parseFloat(temperature) | |
| if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10) | |
| if (topP != null && topP !== '') obj.topP = parseFloat(topP) | |
| if (cache) obj.cache = cache |
References
- In JavaScript/TypeScript, use loose equality (
== null) as a standard idiom for a 'nullish' check that covers bothnullandundefined.
Summary
Add MiniMax as a chat model provider in Flowise, with the latest M3 model as default.
Changes
ChatMiniMaxnode wrappingChatAnthropicwith MiniMax Anthropic-compatible base URLMiniMaxApicredential type for API key managementmodels.json: M3, M2.7, M2.7-highspeedMiniMax-M3as the default modeltextToSpeech.ts(TTS model unchanged, no chat-model impact)Why
MiniMax-M3 is the latest flagship model with a 512K context window, up to 128K output, and image input support. Upgrading from M2.5 to M3 gives Flowise users access to the new generation model.
Testing
models.jsonis valid JSON with correct model orderingMiniMax-M3as both the form default and the configured model fallback