Skip to content
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
16 changes: 16 additions & 0 deletions src/data/models.json
Original file line number Diff line number Diff line change
Expand Up @@ -15425,6 +15425,22 @@
"id": "openai"
},
"name": "Text Embedding Ada 002"
},
{
"id": "MiniMax-M2.7",
"object": "model",
"provider": {
"id": "minimax"
},
"name": "MiniMax M2.7"
},
{
"id": "MiniMax-M2.7-highspeed",
"object": "model",
"provider": {
"id": "minimax"
},
"name": "MiniMax M2.7 Highspeed"
}
]
}
7 changes: 7 additions & 0 deletions src/data/providers.json
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,13 @@
"object": "provider",
"description": "Dashscope provides intelligent analytics solutions designed to help businesses visualize and interpret complex data effectively. Their platform integrates advanced machine learning algorithms to generate real-time insights from diverse datasets, enabling organizations to make informed decisions quickly. Dashscope focuses on enhancing data accessibility and usability, empowering teams to leverage analytics for strategic planning and operational efficiency.",
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1"
},
{
"id": "minimax",
"name": "MiniMax",
"object": "provider",
"description": "MiniMax is a leading AI company that develops advanced large language models including MiniMax-M2.7 and MiniMax-M2.7-highspeed. Their models support 204,800 tokens context window and are accessible via an OpenAI-compatible API, enabling seamless integration for text generation, chat, and tool-calling applications.",
"base_url": "https://api.minimax.io/v1"
}
]
}
2 changes: 2 additions & 0 deletions src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export const ORACLE: string = 'oracle';
export const IO_INTELLIGENCE: string = 'iointelligence';
export const AIBADGR: string = 'aibadgr';
export const OVHCLOUD: string = 'ovhcloud';
export const MINIMAX: string = 'minimax';

export const VALID_PROVIDERS = [
ANTHROPIC,
Expand Down Expand Up @@ -189,6 +190,7 @@ export const VALID_PROVIDERS = [
IO_INTELLIGENCE,
AIBADGR,
OVHCLOUD,
MINIMAX,
];

export const CONTENT_TYPES = {
Expand Down
2 changes: 2 additions & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import OracleConfig from './oracle';
import IOIntelligenceConfig from './iointelligence';
import AIBadgrConfig from './aibadgr';
import OVHcloudConfig from './ovhcloud';
import MiniMaxConfig from './minimax';

const Providers: { [key: string]: ProviderConfigs } = {
openai: OpenAIConfig,
Expand Down Expand Up @@ -148,6 +149,7 @@ const Providers: { [key: string]: ProviderConfigs } = {
iointelligence: IOIntelligenceConfig,
aibadgr: AIBadgrConfig,
ovhcloud: OVHcloudConfig,
minimax: MiniMaxConfig,
};

export default Providers;
20 changes: 20 additions & 0 deletions src/providers/minimax/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ProviderAPIConfig } from '../types';

const MiniMaxAPIConfig: ProviderAPIConfig = {
getBaseURL: () => 'https://api.minimax.io/v1',
headers: ({ providerOptions }) => {
return {
Authorization: `Bearer ${providerOptions.apiKey}`,
};
},
getEndpoint: ({ fn }) => {
switch (fn) {
case 'chatComplete':
return '/chat/completions';
default:
return '';
}
},
};

export default MiniMaxAPIConfig;
115 changes: 115 additions & 0 deletions src/providers/minimax/chatComplete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { MINIMAX } from '../../globals';
import { ChatCompletionResponse, ErrorResponse } from '../types';
import {
generateErrorResponse,
generateInvalidProviderResponseError,
} from '../utils';

export interface MiniMaxChatCompleteResponse extends ChatCompletionResponse {}

export interface MiniMaxErrorResponse extends ErrorResponse {}

export interface MiniMaxStreamChunk {
id: string;
object: string;
created: number;
model: string;
choices: {
delta: {
role?: string | null;
content?: string;
tool_calls?: object[];
};
index: number;
finish_reason: string | null;
}[];
usage?: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
}

export const MiniMaxChatCompleteResponseTransform: (
response: MiniMaxChatCompleteResponse | MiniMaxErrorResponse,
responseStatus: number
) => ChatCompletionResponse | ErrorResponse = (response, responseStatus) => {
if ('error' in response && responseStatus !== 200) {
return generateErrorResponse(
{
message: response.error.message,
type: response.error.type,
param: null,
code: response.error.code?.toString() || null,
},
MINIMAX
);
}

if ('choices' in response) {
return {
id: response.id,
object: response.object,
created: response.created,
model: response.model,
provider: MINIMAX,
choices: response.choices.map((c) => ({
index: c.index,
message: c.message,
logprobs: c.logprobs,
finish_reason: c.finish_reason,
})),
usage: {
prompt_tokens: response.usage?.prompt_tokens || 0,
completion_tokens: response.usage?.completion_tokens || 0,
total_tokens: response.usage?.total_tokens || 0,
},
};
}

return generateInvalidProviderResponseError(response, MINIMAX);
};

export const MiniMaxChatCompleteStreamChunkTransform: (
response: string
) => string = (responseChunk) => {
let chunk = responseChunk.trim();
chunk = chunk.replace(/^data: /, '');
chunk = chunk.trim();
if (chunk === '[DONE]') {
return `data: ${chunk}\n\n`;
}

const parsedChunk: MiniMaxStreamChunk = JSON.parse(chunk);
return (
`data: ${JSON.stringify({
id: parsedChunk.id,
object: parsedChunk.object,
created: parsedChunk.created,
model: parsedChunk.model,
provider: MINIMAX,
choices:
parsedChunk.choices && parsedChunk.choices.length > 0
? [
{
index: parsedChunk.choices[0].index || 0,
delta: {
role: parsedChunk.choices[0].delta?.role || undefined,
content: parsedChunk.choices[0].delta?.content || '',
tool_calls:
parsedChunk.choices[0].delta?.tool_calls || undefined,
},
finish_reason: parsedChunk.choices[0].finish_reason || null,
},
]
: [],
usage: parsedChunk.usage
? {
prompt_tokens: parsedChunk.usage.prompt_tokens || 0,
completion_tokens: parsedChunk.usage.completion_tokens || 0,
total_tokens: parsedChunk.usage.total_tokens || 0,
}
: undefined,
})}` + '\n\n'
);
};
27 changes: 27 additions & 0 deletions src/providers/minimax/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { MINIMAX } from '../../globals';
import { chatCompleteParams, responseTransformers } from '../open-ai-base';
import { ProviderConfigs } from '../types';
import MiniMaxAPIConfig from './api';
import { MiniMaxChatCompleteStreamChunkTransform } from './chatComplete';

const MiniMaxConfig: ProviderConfigs = {
chatComplete: chatCompleteParams(
['logit_bias', 'logprobs', 'top_logprobs', 'parallel_tool_calls'],
{ temperature: 1 },
{
response_format: {
param: 'response_format',
default: null,
},
}
),
api: MiniMaxAPIConfig,
responseTransforms: {
...responseTransformers(MINIMAX, {
chatComplete: true,
}),
'stream-chatComplete': MiniMaxChatCompleteStreamChunkTransform,
},
};

export default MiniMaxConfig;
104 changes: 104 additions & 0 deletions src/tests/minimax.integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Integration tests for MiniMax provider.
* These tests call the real MiniMax API and require MINIMAX_API_KEY env var.
* Run with: npx jest src/tests/minimax.integration.test.ts
*/

const MINIMAX_API_KEY = process.env.MINIMAX_API_KEY;
const BASE_URL = 'https://api.minimax.io/v1';

const skipIfNoKey = MINIMAX_API_KEY ? describe : describe.skip;

skipIfNoKey('MiniMax integration tests', () => {
test('non-streaming chat completion with MiniMax-M2.7', async () => {
const response = await fetch(`${BASE_URL}/chat/completions`, {
method: 'POST',
headers: {
Authorization: `Bearer ${MINIMAX_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'MiniMax-M2.7',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Say hello in one word.' },
],
max_tokens: 30,
temperature: 1.0,
stream: false,
}),
});

expect(response.status).toBe(200);
const data = await response.json();
expect(data).toHaveProperty('id');
expect(data).toHaveProperty('model');
expect(data).toHaveProperty('choices');
expect(data.choices.length).toBeGreaterThan(0);
expect(data.choices[0]).toHaveProperty('message');
expect(data.choices[0].message).toHaveProperty('content');
expect(data).toHaveProperty('usage');
expect(data.usage).toHaveProperty('prompt_tokens');
expect(data.usage).toHaveProperty('completion_tokens');
expect(data.usage).toHaveProperty('total_tokens');
});

test('streaming chat completion with MiniMax-M2.7-highspeed', async () => {
const response = await fetch(`${BASE_URL}/chat/completions`, {
method: 'POST',
headers: {
Authorization: `Bearer ${MINIMAX_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'MiniMax-M2.7-highspeed',
messages: [{ role: 'user', content: 'Say hi' }],
max_tokens: 20,
temperature: 1.0,
stream: true,
}),
});

expect(response.status).toBe(200);
const text = await response.text();
const lines = text
.split('\n')
.filter((line: string) => line.startsWith('data: '));
expect(lines.length).toBeGreaterThan(0);

// Check that the last meaningful chunk or a data chunk has proper structure
const firstLine = lines[0];
const firstData = JSON.parse(firstLine.replace('data: ', ''));
expect(firstData).toHaveProperty('id');
expect(firstData).toHaveProperty('model');
expect(firstData).toHaveProperty('choices');

// Verify at least one chunk has finish_reason
const hasFinishReason = lines.some((line: string) => {
try {
const parsed = JSON.parse(line.replace('data: ', ''));
return parsed.choices?.[0]?.finish_reason !== null;
} catch {
return false;
}
});
expect(hasFinishReason).toBe(true);
});

test('error handling with invalid API key', async () => {
const response = await fetch(`${BASE_URL}/chat/completions`, {
method: 'POST',
headers: {
Authorization: 'Bearer invalid-key',
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'MiniMax-M2.7',
messages: [{ role: 'user', content: 'Hello' }],
max_tokens: 10,
}),
});

expect(response.status).not.toBe(200);
});
});
Loading
Loading