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
9 changes: 9 additions & 0 deletions .changeset/add-minimax-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@open-codesign/shared": minor
"@open-codesign/providers": patch
"@open-codesign/desktop": patch
---

Add MiniMax as a built-in onboarding provider.

MiniMax is now available as a first-class provider option alongside Anthropic, OpenAI, OpenRouter, and Ollama. It uses the OpenAI-compatible wire (`openai-chat`) with a default base URL of `https://api.minimax.io/v1` and ships with a static model hint for `MiniMax-M2.7` and `MiniMax-M2.7-highspeed`. Credentials can be supplied via the `MINIMAX_API_KEY` environment variable or entered during onboarding.
1 change: 1 addition & 0 deletions apps/desktop/src/main/imports/codex-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const ALLOWED_IMPORT_ENV_KEYS: ReadonlySet<string> = new Set([
'GEMINI_API_KEY',
'GOOGLE_API_KEY',
'GROQ_API_KEY',
'MINIMAX_API_KEY',
'MISTRAL_API_KEY',
'OPENAI_API_KEY',
'OPENROUTER_API_KEY',
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/onboarding-ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ function parseValidateKey(raw: unknown): ValidateKeyInput {
}
if (!isSupportedOnboardingProvider(provider)) {
throw new CodesignError(
`Provider "${provider}" is not supported in v0.1. Only anthropic, openai, openrouter.`,
`Provider "${provider}" is not supported in v0.1. Only anthropic, openai, openrouter, minimax.`,
ERROR_CODES.PROVIDER_NOT_SUPPORTED,
);
}
Expand Down
30 changes: 30 additions & 0 deletions packages/providers/src/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,34 @@ describe('pingProvider', () => {
});
await pingProvider('anthropic', 'sk-ant-oat-xyz', 'https://sub2api.example.com');
});

it('validates MiniMax with Bearer auth and returns model count', async () => {
mockFetch(async (url, init) => {
expect(url).toBe('https://api.minimax.io/v1/models');
const headers = (init?.headers ?? {}) as Record<string, string>;
expect(headers['authorization']).toBe('Bearer mm-test-key');
return new Response(
JSON.stringify({ data: [{ id: 'MiniMax-M2.7' }, { id: 'MiniMax-M2.7-highspeed' }] }),
{ status: 200, headers: { 'content-type': 'application/json' } },
);
});
const result = await pingProvider('minimax', 'mm-test-key');
expect(result).toEqual({ ok: true, modelCount: 2 });
});

it('returns 401 code for MiniMax invalid key', async () => {
mockFetch(async () => new Response('unauthorized', { status: 401 }));
const result = await pingProvider('minimax', 'bad-key');
expect(result.ok).toBe(false);
if (!result.ok) expect(result.code).toBe('401');
});

it('respects custom baseUrl for MiniMax', async () => {
mockFetch(async (url) => {
expect(url).toBe('https://api.minimaxi.com/v1/models');
return new Response(JSON.stringify({ data: [{ id: 'MiniMax-M2.7' }] }), { status: 200 });
});
const result = await pingProvider('minimax', 'mm-test-key', 'https://api.minimaxi.com/v1');
expect(result).toEqual({ ok: true, modelCount: 1 });
});
});
9 changes: 8 additions & 1 deletion packages/providers/src/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ function endpoint(provider: SupportedOnboardingProvider, baseUrl?: string): Prov
headers: () => ({}),
};
}
case 'minimax': {
const root = baseUrl ? normalizeValidateBaseUrl(baseUrl) : 'https://api.minimax.io';
return {
url: `${root}/v1/models`,
headers: (apiKey) => ({ authorization: `Bearer ${apiKey}` }),
};
}
}
}

Expand Down Expand Up @@ -97,7 +104,7 @@ export async function pingProvider(
): Promise<ValidateResult> {
if (!isSupportedOnboardingProvider(provider)) {
throw new CodesignError(
`Provider "${provider}" is not supported in v0.1. Supported: anthropic, openai, openrouter, ollama.`,
`Provider "${provider}" is not supported in v0.1. Supported: anthropic, openai, openrouter, ollama, minimax.`,
ERROR_CODES.PROVIDER_NOT_SUPPORTED,
);
}
Expand Down
38 changes: 37 additions & 1 deletion packages/shared/src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe('config v3 schema', () => {
});

describe('migrateLegacyToV3', () => {
it('seeds three builtin providers from an empty v2', () => {
it('seeds all builtin providers from an empty v2', () => {
const legacy = {
version: 2 as const,
provider: 'anthropic' as const,
Expand Down Expand Up @@ -347,6 +347,42 @@ describe('provider capability helpers', () => {
});
});

describe('MiniMax builtin provider', () => {
it('is included in SUPPORTED_ONBOARDING_PROVIDERS', () => {
expect(SUPPORTED_ONBOARDING_PROVIDERS).toContain('minimax');
});

it('has correct wire and baseUrl', () => {
expect(BUILTIN_PROVIDERS.minimax.wire).toBe('openai-chat');
expect(BUILTIN_PROVIDERS.minimax.baseUrl).toBe('https://api.minimax.io/v1');
});

it('uses static-hint model discovery with M2.7 models', () => {
expect(BUILTIN_PROVIDERS.minimax.capabilities?.modelDiscoveryMode).toBe('static-hint');
expect(BUILTIN_PROVIDERS.minimax.modelsHint).toEqual([
'MiniMax-M2.7',
'MiniMax-M2.7-highspeed',
]);
expect(BUILTIN_PROVIDERS.minimax.defaultModel).toBe('MiniMax-M2.7');
});

it('reads MINIMAX_API_KEY env var', () => {
expect(BUILTIN_PROVIDERS.minimax.envKey).toBe('MINIMAX_API_KEY');
});

it('resolves capabilities correctly via resolveProviderCapabilities', () => {
const caps = resolveProviderCapabilities('minimax', {
wire: 'openai-chat',
baseUrl: 'https://api.minimax.io/v1',
});
expect(caps.supportsChatCompletions).toBe(true);
expect(caps.supportsSystemRole).toBe(true);
expect(caps.supportsToolCalling).toBe(true);
expect(caps.requiresClaudeCodeIdentity).toBe(false);
expect(caps.supportsReasoning).toBe(false);
});
});

describe('requiresClaudeCodeIdentity — host-based detection', () => {
it('official api.anthropic.com → requiresClaudeCodeIdentity: false', () => {
const caps = resolveProviderCapabilities('anthropic', {
Expand Down
30 changes: 30 additions & 0 deletions packages/shared/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const SUPPORTED_ONBOARDING_PROVIDERS = [
'openai',
'openrouter',
'ollama',
'minimax',
] as const;
export type SupportedOnboardingProvider = (typeof SUPPORTED_ONBOARDING_PROVIDERS)[number];

Expand Down Expand Up @@ -327,6 +328,28 @@ export const BUILTIN_PROVIDERS: Readonly<Record<SupportedOnboardingProvider, Pro
modelDiscoveryMode: 'models',
},
},
minimax: {
id: 'minimax',
name: 'MiniMax',
builtin: true,
wire: 'openai-chat',
baseUrl: 'https://api.minimax.io/v1',
envKey: 'MINIMAX_API_KEY',
defaultModel: 'MiniMax-M2.7',
modelsHint: ['MiniMax-M2.7', 'MiniMax-M2.7-highspeed'],
capabilities: {
supportsKeyless: false,
supportsModelsEndpoint: false,
supportsChatCompletions: true,
supportsResponsesApi: false,
supportsSystemRole: true,
supportsDeveloperRole: false,
supportsReasoning: false,
supportsToolCalling: true,
requiresClaudeCodeIdentity: false,
modelDiscoveryMode: 'static-hint',
},
},
} as const;

// ── ConfigSchema v3 — canonical on-disk shape ────────────────────────────────
Expand Down Expand Up @@ -520,6 +543,13 @@ export const PROVIDER_SHORTLIST: Record<SupportedOnboardingProvider, ProviderSho
primary: [OLLAMA_DEFAULT_MODEL, 'llama3.1', 'qwen2.5'],
defaultPrimary: OLLAMA_DEFAULT_MODEL,
},
minimax: {
provider: 'minimax',
label: 'MiniMax',
keyHelpUrl: 'https://platform.minimax.io/user-center/basic-information/interface-key',
primary: ['MiniMax-M2.7', 'MiniMax-M2.7-highspeed'],
defaultPrimary: 'MiniMax-M2.7',
},
};

export function isSupportedOnboardingProvider(p: string): p is SupportedOnboardingProvider {
Expand Down
Loading