-
Notifications
You must be signed in to change notification settings - Fork 3.4k
feat: add MiniMax provider support with M2.7 as default #3581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2783,6 +2783,21 @@ export const GroqIcon = (props: SVGProps<SVGSVGElement>) => ( | |
| </svg> | ||
| ) | ||
|
|
||
| export const MiniMaxIcon = (props: SVGProps<SVGSVGElement>) => ( | ||
| <svg {...props} height='1em' viewBox='0 0 120 120' width='1em' xmlns='http://www.w3.org/2000/svg'> | ||
| <title>MiniMax</title> | ||
| <rect width='120' height='120' rx='24' fill='#1A1A2E' /> | ||
| <path | ||
| d='M30 80V40l15 20 15-20v40M70 40v40M80 40l10 20 10-20v40' | ||
| stroke='#E94560' | ||
| strokeWidth='6' | ||
| strokeLinecap='round' | ||
| strokeLinejoin='round' | ||
| fill='none' | ||
| /> | ||
| </svg> | ||
| ) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MiniMax icon is a hand-drawn placeholder, not officialLow Severity The |
||
|
|
||
| export const DeepseekIcon = (props: SVGProps<SVGSVGElement>) => ( | ||
| <svg {...props} height='1em' viewBox='0 0 24 24' width='1em' xmlns='http://www.w3.org/2000/svg'> | ||
| <title>DeepSeek</title> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| /** | ||
| * @vitest-environment node | ||
| */ | ||
| import { beforeEach, describe, expect, it, vi } from 'vitest' | ||
|
|
||
| const { mockCreate, MockOpenAI } = vi.hoisted(() => { | ||
| const mockCreate = vi.fn() | ||
| const MockOpenAI = vi.fn().mockImplementation(() => ({ | ||
| chat: { | ||
| completions: { | ||
| create: mockCreate, | ||
| }, | ||
| }, | ||
| })) | ||
| return { mockCreate, MockOpenAI } | ||
| }) | ||
|
|
||
| vi.mock('openai', () => ({ | ||
| default: MockOpenAI, | ||
| })) | ||
|
|
||
| vi.mock('@/tools', () => ({ | ||
| executeTool: vi.fn(), | ||
| })) | ||
|
|
||
| import { minimaxProvider } from '@/providers/minimax' | ||
|
|
||
| describe('MiniMax Provider', () => { | ||
| beforeEach(() => { | ||
| vi.clearAllMocks() | ||
| }) | ||
|
|
||
| describe('provider metadata', () => { | ||
| it('has correct id and name', () => { | ||
| expect(minimaxProvider.id).toBe('minimax') | ||
| expect(minimaxProvider.name).toBe('MiniMax') | ||
| }) | ||
|
|
||
| it('has correct version', () => { | ||
| expect(minimaxProvider.version).toBe('1.0.0') | ||
| }) | ||
|
|
||
| it('has models defined', () => { | ||
| expect(minimaxProvider.models).toBeDefined() | ||
| expect(minimaxProvider.models.length).toBeGreaterThan(0) | ||
| }) | ||
|
|
||
| it('has default model set', () => { | ||
| expect(minimaxProvider.defaultModel).toBe('MiniMax-M2.7') | ||
| }) | ||
| }) | ||
|
|
||
| describe('executeRequest', () => { | ||
| it('throws when API key is missing', async () => { | ||
| await expect( | ||
| minimaxProvider.executeRequest({ | ||
| model: 'MiniMax-M2.5', | ||
| messages: [{ role: 'user', content: 'Hello' }], | ||
| }) | ||
| ).rejects.toThrow('API key is required for MiniMax') | ||
| }) | ||
|
|
||
| it('creates OpenAI client with correct base URL', async () => { | ||
| mockCreate.mockResolvedValue({ | ||
| choices: [{ message: { content: 'Hello!', tool_calls: undefined } }], | ||
| usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, | ||
| }) | ||
|
|
||
| await minimaxProvider.executeRequest({ | ||
| model: 'MiniMax-M2.5', | ||
| apiKey: 'test-key', | ||
| messages: [{ role: 'user', content: 'Hello' }], | ||
| }) | ||
|
|
||
| expect(MockOpenAI).toHaveBeenCalledWith({ | ||
| apiKey: 'test-key', | ||
| baseURL: 'https://api.minimax.io/v1', | ||
| }) | ||
| }) | ||
|
|
||
| it('returns content from response', async () => { | ||
| mockCreate.mockResolvedValue({ | ||
| choices: [{ message: { content: 'Hello from MiniMax!', tool_calls: undefined } }], | ||
| usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, | ||
| }) | ||
|
|
||
| const result = await minimaxProvider.executeRequest({ | ||
| model: 'MiniMax-M2.5', | ||
| apiKey: 'test-key', | ||
| messages: [{ role: 'user', content: 'Hello' }], | ||
| }) | ||
|
|
||
| expect(result).toHaveProperty('content', 'Hello from MiniMax!') | ||
| }) | ||
|
|
||
| it('clamps temperature to valid range', async () => { | ||
| mockCreate.mockResolvedValue({ | ||
| choices: [{ message: { content: 'ok', tool_calls: undefined } }], | ||
| usage: { prompt_tokens: 5, completion_tokens: 2, total_tokens: 7 }, | ||
| }) | ||
|
|
||
| await minimaxProvider.executeRequest({ | ||
| model: 'MiniMax-M2.5', | ||
| apiKey: 'test-key', | ||
| temperature: 0, | ||
| messages: [{ role: 'user', content: 'Hi' }], | ||
| }) | ||
|
|
||
| const callArgs = mockCreate.mock.calls[0][0] | ||
| expect(callArgs.temperature).toBeGreaterThan(0) | ||
| expect(callArgs.temperature).toBeLessThanOrEqual(1) | ||
| }) | ||
|
|
||
| it('includes system prompt when provided', async () => { | ||
| mockCreate.mockResolvedValue({ | ||
| choices: [{ message: { content: 'ok', tool_calls: undefined } }], | ||
| usage: { prompt_tokens: 10, completion_tokens: 2, total_tokens: 12 }, | ||
| }) | ||
|
|
||
| await minimaxProvider.executeRequest({ | ||
| model: 'MiniMax-M2.5', | ||
| apiKey: 'test-key', | ||
| systemPrompt: 'You are a helpful assistant', | ||
| messages: [{ role: 'user', content: 'Hi' }], | ||
| }) | ||
|
|
||
| const callArgs = mockCreate.mock.calls[0][0] | ||
| expect(callArgs.messages[0]).toEqual({ | ||
| role: 'system', | ||
| content: 'You are a helpful assistant', | ||
| }) | ||
| }) | ||
|
|
||
| it('returns token usage information', async () => { | ||
| mockCreate.mockResolvedValue({ | ||
| choices: [{ message: { content: 'Hello!', tool_calls: undefined } }], | ||
| usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }, | ||
| }) | ||
|
|
||
| const result = await minimaxProvider.executeRequest({ | ||
| model: 'MiniMax-M2.5', | ||
| apiKey: 'test-key', | ||
| messages: [{ role: 'user', content: 'Hello' }], | ||
| }) | ||
|
|
||
| expect(result).toHaveProperty('tokens') | ||
| const response = result as any | ||
| expect(response.tokens.input).toBe(10) | ||
| expect(response.tokens.output).toBe(5) | ||
| expect(response.tokens.total).toBe(15) | ||
| }) | ||
| }) | ||
| }) |


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SVG icon path draws incomplete second "M" character
Low Severity
The third sub-path
M80 40l10 20 10-20v40in theMiniMaxIconSVG is missing its left vertical stroke. It traces (80,40)→(90,60)→(100,40)→(100,80), rendering as a V-shape with a trailing line — not an "M". Compare with the first sub-path which correctly starts from the bottomM30 80V40l15 20 15-20v40, drawing the upward left stroke before the V-shape. The third sub-path needs to start at the bottom (e.g.M80 80V40l10 20 10-20v40) to draw a matching "M".