From 1cea6fd19bde223a42a8281b1637317791f14232 Mon Sep 17 00:00:00 2001 From: for1337 Date: Mon, 27 Apr 2026 00:13:04 +0200 Subject: [PATCH] feat: add MiniMax provider integration and configuration support --- src/index.ts | 19 ++++++++++++++++++ src/providers/index.ts | 1 + src/providers/minimax.ts | 37 ++++++++++++++++++++++++++++++++++++ src/providers/registry.ts | 4 ++++ src/utils/config.ts | 11 ++++++++++- src/utils/provider-models.ts | 18 +++++++++++++++++- 6 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 src/providers/minimax.ts diff --git a/src/index.ts b/src/index.ts index 8de69a95..c12399d6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -109,6 +109,7 @@ const PROVIDER_OPTIONS: Array<{ key: ProviderName; label: string }> = [ { key: 'grok', label: 'Grok (xAI)' }, { key: 'ollamaCloud', label: 'Ollama Cloud' }, { key: 'ollamaLocal', label: 'Ollama Local' }, + { key: 'minimax', label: 'MiniMax' }, ]; function getConfiguredProviderNames(config: MercuryConfig): ProviderName[] { @@ -666,6 +667,24 @@ async function configure(existingConfig?: MercuryConfig): Promise { config.providers.ollamaLocal.model = result.model; config.providers.ollamaLocal.enabled = true; } + continue; + } + + if (provider === 'minimax') { + const mask = isReconfig && config.providers.minimax.apiKey ? ` [${maskKey(config.providers.minimax.apiKey)}]` : ''; + const result = await promptApiKeyWithModelSelection( + config, + 'minimax', + 'MiniMax', + chalk.white(` MiniMax API key${mask}${isReconfig ? '' : ' (Enter to skip)'}: `), + isReconfig, + ); + if (!result.skipped && result.apiKey && result.model) { + config.providers.minimax.apiKey = result.apiKey; + config.providers.minimax.model = result.model; + config.providers.minimax.enabled = true; + } + continue; } } diff --git a/src/providers/index.ts b/src/providers/index.ts index d69645be..52c44a9e 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -2,6 +2,7 @@ export { BaseProvider } from './base.js'; export { OpenAICompatProvider } from './openai-compat.js'; export { AnthropicProvider } from './anthropic.js'; export { DeepSeekProvider } from './deepseek.js'; +export { MiniMaxProvider } from './minimax.js'; export { OllamaProvider } from './ollama.js'; export { ProviderRegistry } from './registry.js'; export type { LLMResponse, LLMStreamChunk } from './base.js'; diff --git a/src/providers/minimax.ts b/src/providers/minimax.ts new file mode 100644 index 00000000..7f77b0db --- /dev/null +++ b/src/providers/minimax.ts @@ -0,0 +1,37 @@ +import { createOpenAI } from '@ai-sdk/openai'; +import { BaseProvider } from './base.js'; +import type { ProviderConfig } from '../utils/config.js'; + +export class MiniMaxProvider extends BaseProvider { + readonly name: string; + readonly model: string; + private modelInstance: any; + + constructor(config: ProviderConfig) { + super(config); + this.name = config.name; + this.model = config.model; + + const client = createOpenAI({ + apiKey: config.apiKey, + baseURL: config.baseUrl, + }); + this.modelInstance = client.chat(config.model); + } + + async generateText(_prompt: string, _systemPrompt: string): Promise { + throw new Error('Use getModelInstance() with the AI SDK agent loop'); + } + + async *streamText(_prompt: string, _systemPrompt: string): AsyncIterable { + throw new Error('Use getModelInstance() with the AI SDK agent loop'); + } + + isAvailable(): boolean { + return this.config.apiKey.length > 0; + } + + getModelInstance() { + return this.modelInstance; + } +} \ No newline at end of file diff --git a/src/providers/registry.ts b/src/providers/registry.ts index 0f254d92..b04412d1 100644 --- a/src/providers/registry.ts +++ b/src/providers/registry.ts @@ -4,6 +4,7 @@ import type { BaseProvider } from './base.js'; import { OpenAICompatProvider } from './openai-compat.js'; import { AnthropicProvider } from './anthropic.js'; import { DeepSeekProvider } from './deepseek.js'; +import { MiniMaxProvider } from './minimax.js'; import { OllamaProvider } from './ollama.js'; import { logger } from '../utils/logger.js'; @@ -22,6 +23,7 @@ export class ProviderRegistry { config.providers.grok, config.providers.ollamaCloud, config.providers.ollamaLocal, + config.providers.minimax, ]; for (const pc of entries) { @@ -34,6 +36,8 @@ export class ProviderRegistry { provider = new DeepSeekProvider(pc); } else if (pc.name === 'ollamaCloud' || pc.name === 'ollamaLocal') { provider = new OllamaProvider(pc); + } else if (pc.name === 'minimax') { + provider = new MiniMaxProvider(pc); } else { provider = new OpenAICompatProvider(pc); } diff --git a/src/utils/config.ts b/src/utils/config.ts index 8cf99e32..af9233f5 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -52,7 +52,8 @@ export type ProviderName = | 'deepseek' | 'grok' | 'ollamaCloud' - | 'ollamaLocal'; + | 'ollamaLocal' + | 'minimax'; export interface MercuryConfig { identity: { @@ -68,6 +69,7 @@ export interface MercuryConfig { grok: ProviderConfig; ollamaCloud: ProviderConfig; ollamaLocal: ProviderConfig; + minimax: ProviderConfig; }; channels: { telegram: { @@ -173,6 +175,13 @@ export function getDefaultConfig(): MercuryConfig { model: getEnv('OLLAMA_LOCAL_MODEL', 'gpt-oss:20b'), enabled: getEnvBool('OLLAMA_LOCAL_ENABLED', false), }, + minimax: { + name: 'minimax', + apiKey: getEnv('MINIMAX_API_KEY', ''), + baseUrl: getEnv('MINIMAX_BASE_URL', 'https://api.minimax.io/v1'), + model: getEnv('MINIMAX_MODEL', 'MiniMax-M2.7'), + enabled: getEnvBool('MINIMAX_ENABLED', true), + }, }, channels: { telegram: { diff --git a/src/utils/provider-models.ts b/src/utils/provider-models.ts index 30b3eb0b..2c45cdb7 100644 --- a/src/utils/provider-models.ts +++ b/src/utils/provider-models.ts @@ -51,6 +51,13 @@ const OLLAMA_LOCAL_PREFERRED_MODELS = [ 'gpt-oss:120b', ] as const; +const MINIMAX_PREFERRED_MODELS = [ + 'MiniMax-M2.7', + 'MiniMax-M2.7-highspeed', + 'MiniMax-M2.5', + 'MiniMax-M2.1', +] as const; + export class ProviderModelFetchError extends Error { constructor(message: string) { super(message); @@ -154,6 +161,7 @@ function chooseRecommendedModel( grok: GROK_PREFERRED_MODELS, ollamaCloud: OLLAMA_CLOUD_PREFERRED_MODELS, ollamaLocal: OLLAMA_LOCAL_PREFERRED_MODELS, + minimax: MINIMAX_PREFERRED_MODELS, }; for (const candidate of preferredByProvider[provider]) { @@ -187,6 +195,7 @@ export function buildModelCatalog( grok: GROK_PREFERRED_MODELS, ollamaCloud: OLLAMA_CLOUD_PREFERRED_MODELS, ollamaLocal: OLLAMA_LOCAL_PREFERRED_MODELS, + minimax: MINIMAX_PREFERRED_MODELS, }; const withoutRecommended = filtered.filter((model) => model !== recommendedModel); @@ -206,7 +215,7 @@ async function fetchOpenAICompatModels(provider: ProviderName, config: ProviderC Authorization: `Bearer ${config.apiKey}`, }, }, - `Mercury could not fetch models for this ${provider === 'grok' ? 'Grok' : provider === 'deepseek' ? 'DeepSeek' : 'OpenAI'} key. Please re-enter it.`, + `Mercury could not fetch models for this ${provider === 'grok' ? 'Grok' : provider === 'deepseek' ? 'DeepSeek' : provider === 'minimax' ? 'MiniMax' : 'OpenAI'} key. Please re-enter it.`, ); const ids = (data.data ?? []) @@ -215,6 +224,9 @@ async function fetchOpenAICompatModels(provider: ProviderName, config: ProviderC if (provider === 'deepseek') { return id.startsWith('deepseek-'); } + if (provider === 'minimax') { + return id.startsWith('MiniMax-'); + } return isOpenAIChatModel(id); }); @@ -297,5 +309,9 @@ export async function fetchProviderModelCatalog( return fetchOllamaModels(provider, config); } + if (provider === 'minimax') { + return buildModelCatalog(provider, [...MINIMAX_PREFERRED_MODELS], config.model); + } + return fetchOpenAICompatModels(provider, config); }