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
88 changes: 88 additions & 0 deletions src/__tests__/unit/minimax-provider.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { generateText } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
import { ClaudeCodeCompatModel } from '../../lib/claude-code-compat';

const MODEL_ID = 'MiniMax-M3';
const API_KEY = 'test-key-not-real';

function requestUrl(input: RequestInfo | URL): string {
return typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
}

function anthropicResponse(): Response {
return new Response(JSON.stringify({
id: 'msg_test',
type: 'message',
role: 'assistant',
model: MODEL_ID,
content: [{ type: 'text', text: 'ok' }],
stop_reason: 'end_turn',
usage: { input_tokens: 1, output_tokens: 1 },
}), { status: 200, headers: { 'content-type': 'application/json' } });
}

function openAiResponse(): Response {
return new Response(JSON.stringify({
id: 'chatcmpl_test',
object: 'chat.completion',
created: 0,
model: MODEL_ID,
choices: [{ index: 0, message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}), { status: 200, headers: { 'content-type': 'application/json' } });
}

describe('MiniMax endpoint request capture', () => {
it('routes regional Anthropic bases to /anthropic/v1/messages', async () => {
const originalFetch = globalThis.fetch;
const urls: string[] = [];
globalThis.fetch = (async (input: RequestInfo | URL) => {
urls.push(requestUrl(input));
return anthropicResponse();
}) as typeof fetch;

try {
for (const baseUrl of [
'https://api.minimaxi.com/anthropic',
'https://api.minimax.io/anthropic',
]) {
const model = new ClaudeCodeCompatModel({
baseUrl,
modelId: MODEL_ID,
authToken: API_KEY,
});
await generateText({ model, prompt: 'ping' });
}
} finally {
globalThis.fetch = originalFetch;
}

assert.deepEqual(urls, [
'https://api.minimaxi.com/anthropic/v1/messages',
'https://api.minimax.io/anthropic/v1/messages',
]);
});

it('routes regional OpenAI bases to /v1/chat/completions', async () => {
const urls: string[] = [];
const captureFetch = (async (input: RequestInfo | URL) => {
urls.push(requestUrl(input));
return openAiResponse();
}) as typeof fetch;

for (const baseURL of [
'https://api.minimaxi.com/v1',
'https://api.minimax.io/v1',
]) {
const openai = createOpenAI({ apiKey: API_KEY, baseURL, fetch: captureFetch });
await generateText({ model: openai.chat(MODEL_ID), prompt: 'ping' });
}

assert.deepEqual(urls, [
'https://api.minimaxi.com/v1/chat/completions',
'https://api.minimax.io/v1/chat/completions',
]);
});
});
42 changes: 42 additions & 0 deletions src/__tests__/unit/provider-preset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,48 @@ describe('Preset Schema Validation', () => {
assert.equal(p.authStyle, 'auth_token');
});

it('MiniMax presets expose M3 and M2.7 capabilities in both regions', () => {
const expectedBaseUrls = new Map([
['minimax-cn', 'https://api.minimaxi.com/anthropic'],
['minimax-global', 'https://api.minimax.io/anthropic'],
]);

for (const [key, baseUrl] of expectedBaseUrls) {
const preset = VENDOR_PRESETS.find(v => v.key === key);
assert.ok(preset, `${key} preset must exist`);
assert.equal(preset.baseUrl, baseUrl);
assert.equal(preset.protocol, 'anthropic');

const m3 = preset.defaultModels.find(model => model.modelId === 'MiniMax-M3');
assert.deepEqual(m3, {
modelId: 'MiniMax-M3',
upstreamModelId: 'MiniMax-M3',
displayName: 'MiniMax-M3',
capabilities: {
contextWindow: 1000000,
reasoning: true,
toolUse: true,
vision: true,
supportsAdaptiveThinking: true,
},
});

const m27 = preset.defaultModels.find(model => model.upstreamModelId === 'MiniMax-M2.7');
assert.deepEqual(m27, {
modelId: 'sonnet',
upstreamModelId: 'MiniMax-M2.7',
displayName: 'MiniMax-M2.7',
role: 'default',
capabilities: {
contextWindow: 204800,
reasoning: true,
toolUse: true,
},
});
assert.equal(preset.defaultRoleModels?.default, 'MiniMax-M2.7');
}
});

it('openai-compatible preset exists: openai-compatible protocol, api_key auth, no fixed URL, no fabricated catalog', () => {
const p = VENDOR_PRESETS.find(v => v.key === 'openai-compatible');
assert.ok(p, 'generic openai-compatible preset must exist');
Expand Down
6 changes: 4 additions & 2 deletions src/lib/provider-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,8 @@ export const VENDOR_PRESETS: VendorPreset[] = [
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
},
defaultModels: [
{ modelId: 'sonnet', upstreamModelId: 'MiniMax-M2.7', displayName: 'MiniMax-M2.7', role: 'default' },
{ modelId: 'sonnet', upstreamModelId: 'MiniMax-M2.7', displayName: 'MiniMax-M2.7', role: 'default', capabilities: { contextWindow: 204800, reasoning: true, toolUse: true } },
{ modelId: 'MiniMax-M3', upstreamModelId: 'MiniMax-M3', displayName: 'MiniMax-M3', capabilities: { contextWindow: 1000000, reasoning: true, toolUse: true, vision: true, supportsAdaptiveThinking: true } },
],
defaultRoleModels: {
default: 'MiniMax-M2.7',
Expand Down Expand Up @@ -706,7 +707,8 @@ export const VENDOR_PRESETS: VendorPreset[] = [
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
},
defaultModels: [
{ modelId: 'sonnet', upstreamModelId: 'MiniMax-M2.7', displayName: 'MiniMax-M2.7', role: 'default' },
{ modelId: 'sonnet', upstreamModelId: 'MiniMax-M2.7', displayName: 'MiniMax-M2.7', role: 'default', capabilities: { contextWindow: 204800, reasoning: true, toolUse: true } },
{ modelId: 'MiniMax-M3', upstreamModelId: 'MiniMax-M3', displayName: 'MiniMax-M3', capabilities: { contextWindow: 1000000, reasoning: true, toolUse: true, vision: true, supportsAdaptiveThinking: true } },
],
defaultRoleModels: {
default: 'MiniMax-M2.7',
Expand Down
Loading