Skip to content
This repository was archived by the owner on Aug 13, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions packages/components/credentials/MiniMaxApi.credential.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { INodeParams, INodeCredential } from '../src/Interface'

class MiniMaxApi implements INodeCredential {
label: string
name: string
version: number
inputs: INodeParams[]

constructor() {
this.label = 'MiniMax API'
this.name = 'miniMaxApi'
this.version = 1.0
this.inputs = [
{
label: 'MiniMax API Key',
name: 'miniMaxApiKey',
type: 'password'
},
{
label: 'TTS API Region',
name: 'miniMaxTTSRegion',
type: 'options',
options: [
{
label: 'Global',
name: 'global_en'
},
{
label: 'China',
name: 'cn_zh'
}
],
default: 'global_en'
}
]
}
}

module.exports = { credClass: MiniMaxApi }
37 changes: 37 additions & 0 deletions packages/components/models.json
Original file line number Diff line number Diff line change
Expand Up @@ -2193,6 +2193,43 @@
}
]
},
{
"name": "chatMiniMax",
"models": [
{
"label": "MiniMax-M3",
"name": "MiniMax-M3",
"description": "MiniMax M3 - 1,000,000-token context; text, image, and video input; adaptive or disabled thinking",
"context_window": 1000000,
"pricing_usd_per_million_tokens": {
"input": 0.6,
"output": 2.4,
"cache_read": 0.12,
"cache_write": null
},
"input_modalities": ["text", "image", "video"],
"thinking": ["adaptive", "disabled"],
"input_cost": 6e-7,
"output_cost": 2.4e-6
},
{
"label": "MiniMax-M2.7",
"name": "MiniMax-M2.7",
"description": "MiniMax M2.7 - 204,800-token context; text input; always-on thinking",
"context_window": 204800,
"pricing_usd_per_million_tokens": {
"input": 0.3,
"output": 1.2,
"cache_read": 0.06,
"cache_write": 0.375
},
"input_modalities": ["text"],
"thinking": ["always_on"],
"input_cost": 3e-7,
"output_cost": 1.2e-6
}
]
},
{
"name": "chatMistralAI",
"models": [
Expand Down
118 changes: 118 additions & 0 deletions packages/components/nodes/chatmodels/ChatMiniMax/ChatMiniMax.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { ChatAnthropic as LangchainChatAnthropic } from '@langchain/anthropic'
import { BaseCache } from '@langchain/core/caches'
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { ChatMiniMax, ChatMiniMaxInput } from './FlowiseChatMiniMax'
import { getModels, MODEL_TYPE } from '../../../src/modelLoader'

class ChatMiniMax_ChatModels implements INode {
label: string
name: string
version: number
type: string
icon: string
category: string
description: string
baseClasses: string[]
credential: INodeParams
inputs: INodeParams[]

constructor() {
this.label = 'ChatMiniMax'
this.name = 'chatMiniMax'
this.version = 1.0
this.type = 'ChatMiniMax'
this.icon = 'minimax.svg'
this.category = 'Chat Models'
this.description = 'Wrapper around MiniMax large language models using Anthropic-compatible API'
this.baseClasses = [this.type, ...getBaseClasses(LangchainChatAnthropic)]
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['miniMaxApi']
}
this.inputs = [
{
label: 'Cache',
name: 'cache',
type: 'BaseCache',
optional: true
},
{
label: 'Model Name',
name: 'modelName',
type: 'asyncOptions',
loadMethod: 'listModels',
default: 'MiniMax-M3'
},
{
label: 'Temperature',
name: 'temperature',
type: 'number',
step: 0.1,
default: 1.0,
optional: true
},
{
label: 'Streaming',
name: 'streaming',
type: 'boolean',
default: true,
optional: true,
additionalParams: true
},
{
label: 'Max Tokens',
name: 'maxTokens',
type: 'number',
step: 1,
optional: true,
additionalParams: true
},
{
label: 'Top P',
name: 'topP',
type: 'number',
step: 0.1,
optional: true,
additionalParams: true
}
]
}

//@ts-ignore
loadMethods = {
async listModels(): Promise<INodeOptionsValue[]> {
return await getModels(MODEL_TYPE.CHAT, 'chatMiniMax')
}
}

async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const temperature = nodeData.inputs?.temperature as string
const modelName = nodeData.inputs?.modelName as string
const maxTokens = nodeData.inputs?.maxTokens as string
const topP = nodeData.inputs?.topP as string
const streaming = nodeData.inputs?.streaming as boolean
const cache = nodeData.inputs?.cache as BaseCache

const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const miniMaxApiKey = getCredentialParam('miniMaxApiKey', credentialData, nodeData)

const obj: ChatMiniMaxInput = {
modelName,
miniMaxApiKey,
streaming: streaming ?? true
}

if (temperature) obj.temperature = parseFloat(temperature)
if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10)
if (topP) obj.topP = parseFloat(topP)
if (cache) obj.cache = cache
Comment on lines +108 to +111

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.

medium

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.

Suggested change
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
  1. In JavaScript/TypeScript, use loose equality (== null) as a standard idiom for a 'nullish' check that covers both null and undefined.


const model = new ChatMiniMax(nodeData.id, obj)
return model
}
}

module.exports = { nodeClass: ChatMiniMax_ChatModels }
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ChatAnthropic as LangchainChatAnthropic, AnthropicInput } from '@langchain/anthropic'
import { type BaseChatModelParams } from '@langchain/core/language_models/chat_models'

export interface ChatMiniMaxInput extends Partial<AnthropicInput>, BaseChatModelParams {
miniMaxApiKey?: string
}

export class ChatMiniMax extends LangchainChatAnthropic {
configuredModel: string
configuredMaxToken: number
id: string

constructor(id: string, fields?: ChatMiniMaxInput) {
const miniMaxApiKey = fields?.miniMaxApiKey || fields?.anthropicApiKey

super({
...fields,
anthropicApiKey: miniMaxApiKey,
clientOptions: {
baseURL: 'https://api.minimax.io/anthropic'
}
})

this.id = id
this.configuredModel = fields?.modelName || 'MiniMax-M3'
this.configuredMaxToken = this.maxTokens

// @langchain/anthropic defaults topP and topK to -1 as an "unset" sentinel and
// always serialises them into the request body. The real Anthropic API accepts
// -1 silently, but MiniMax's Anthropic-compatible endpoint requires top_p/top_k
// to be in (0, 1]. Setting them to undefined causes JSON.stringify to omit the
// fields entirely so MiniMax applies its own defaults.
if (fields?.topP === undefined) this.topP = undefined as unknown as number
if (fields?.topK === undefined) this.topK = undefined as unknown as number
}

revertToOriginalModel(): void {
this.modelName = this.configuredModel
this.maxTokens = this.configuredMaxToken
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions packages/components/src/minimaxParameters.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as fs from 'fs'
import * as path from 'path'
import { getMiniMaxTTSEndpoint } from './textToSpeech'

describe('MiniMax parameter refresh', () => {
const modelsPath = path.join(__dirname, '..', 'models.json')
const models = JSON.parse(fs.readFileSync(modelsPath, 'utf8'))
const catalog = models.chat.find((entry: any) => entry.name === 'chatMiniMax')

it('records the current model parameters', () => {
expect(catalog.models).toEqual([
expect.objectContaining({
name: 'MiniMax-M3',
context_window: 1000000,
pricing_usd_per_million_tokens: { input: 0.6, output: 2.4, cache_read: 0.12, cache_write: null },
input_modalities: ['text', 'image', 'video'],
thinking: ['adaptive', 'disabled']
}),
expect.objectContaining({
name: 'MiniMax-M2.7',
context_window: 204800,
pricing_usd_per_million_tokens: { input: 0.3, output: 1.2, cache_read: 0.06, cache_write: 0.375 },
input_modalities: ['text'],
thinking: ['always_on']
})
])
})

it('resolves both TTS regions', () => {
expect(getMiniMaxTTSEndpoint()).toBe('https://api.minimax.io/v1/t2a_v2')
expect(getMiniMaxTTSEndpoint('cn_zh')).toBe('https://api.minimaxi.com/v1/t2a_v2')
})
})
Loading