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

Add MiniMax as a built-in provider with static M3 and M2.7 model choices, regional endpoint guidance, and Codex environment-key import support.
43 changes: 43 additions & 0 deletions MINIMAX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# MiniMax Provider

Open CoDesign includes MiniMax as a built-in provider. The default entry uses the global English OpenAI-compatible endpoint and exposes `MiniMax-M3` and `MiniMax-M2.7` as static model choices.

## Configuration

- Provider ID: `minimax`
- API key environment variable: `MINIMAX_API_KEY`
- Default model: `MiniMax-M3`
- Model choices: `MiniMax-M3`, `MiniMax-M2.7`
- Official documentation: `https://platform.minimax.io/docs/api-reference/api-overview`
- China documentation: `https://platform.minimaxi.com/docs/api-reference/api-overview`

The built-in provider uses `https://api.minimax.io/v1`. To use another region or wire format, add a custom provider in Settings and use one of these public bases:

| Region | Wire | Base URL | Documentation |
| --- | --- | --- | --- |
| `global_en` | OpenAI-compatible | `https://api.minimax.io/v1` | `https://platform.minimax.io/docs` |
| `global_en` | Anthropic-compatible | `https://api.minimax.io/anthropic` | `https://platform.minimax.io/docs` |
| `cn_zh` | OpenAI-compatible | `https://api.minimaxi.com/v1` | `https://platform.minimaxi.com/docs` |
| `cn_zh` | Anthropic-compatible | `https://api.minimaxi.com/anthropic` | `https://platform.minimaxi.com/docs` |

For an Anthropic-compatible entry, enter the base directly without adding `/v1`. Open CoDesign derives the versioned `/v1/messages` request path internally. For an OpenAI-compatible entry, keep `/v1` in the public base; the client appends the chat-completions path.

## Model catalog

Prices are USD per million tokens. The model metadata below mirrors the shared provider registry.

| Model | Context | Input | Output | Cache read | Cache write | Input modalities | Thinking |
| --- | ---: | ---: | ---: | ---: | ---: | --- | --- |
| `MiniMax-M3` | 1,000,000 | 0.30 | 1.20 | 0.06 | null | Text, image, video | Adaptive, disabled |
| `MiniMax-M2.7` | 204,800 | 0.30 | 1.20 | 0.06 | 0.375 | Text | Always on |

`MiniMax-M3` has tiered pricing based on input length and service tier:

| Service tier | Input length | Input | Output | Cache read |
| --- | --- | ---: | ---: | ---: |
| Standard | Up to 512,000 | 0.30 | 1.20 | 0.06 |
| Standard | Above 512,000 | 0.60 | 2.40 | 0.12 |
| Priority | Up to 512,000 | 0.45 | 1.80 | 0.09 |
| Priority | Above 512,000 | 0.90 | 3.60 | 0.18 |

The desktop provider adapter keeps MiniMax OpenAI-compatible models on the chat-completions path and does not enable the OpenAI developer-role reasoning flag for these model IDs.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ Add a `SKILL.md` to any project to teach the model your own taste.
- **Unified provider model** — Anthropic, OpenAI, Gemini, DeepSeek, OpenRouter, SiliconFlow, local Ollama, or any OpenAI-compatible relay; keyless (IP-allowlisted) proxies supported
- **One-click import and sign-in** — bring Claude Code / Codex API-key provider configs across, or sign in with ChatGPT subscription for Codex models
- **Dynamic model picker** — every provider exposes its real model catalogue, not a hardcoded shortlist
- **MiniMax support** - built-in MiniMax access with regional OpenAI-compatible and Anthropic-compatible endpoint guidance ([provider guide](./MINIMAX.md))

### Generation and editing
- **Prompt → HTML or JSX/React component** prototype, rendered in a sandboxed iframe (vendored React 18 + Babel on-device)
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/main/imports/codex-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ env_key = "${envKey}"
'DEEPSEEK_API_KEY',
'XAI_API_KEY',
'GROQ_API_KEY',
'MINIMAX_API_KEY',
])('accepts known provider env_key=%s', async (envKey) => {
const toml = `
[model_providers.p]
Expand Down
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 @@ -49,6 +49,7 @@ export const ALLOWED_IMPORT_ENV_KEYS: ReadonlySet<string> = new Set([
'GOOGLE_API_KEY',
'GROQ_API_KEY',
'MISTRAL_API_KEY',
'MINIMAX_API_KEY',
'OPENAI_API_KEY',
'OPENROUTER_API_KEY',
'PERPLEXITY_API_KEY',
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/onboarding/provider-parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export function parseValidateKey(raw: unknown): ValidateKeyInput {
const providerId = provider.trim();
if (!isSupportedOnboardingProvider(providerId)) {
throw new CodesignError(
`Provider "${providerId}" is not supported. Only anthropic, openai, openrouter, ollama.`,
`Provider "${providerId}" is not supported. Only anthropic, openai, openrouter, minimax, ollama.`,
ERROR_CODES.PROVIDER_NOT_SUPPORTED,
);
}
Expand Down
19 changes: 18 additions & 1 deletion packages/providers/src/validate.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CodesignError } from '@open-codesign/shared';
import { CodesignError, MINIMAX_ENDPOINTS } from '@open-codesign/shared';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { pingProvider } from './validate';

Expand Down Expand Up @@ -127,6 +127,23 @@ describe('pingProvider', () => {
expect(result).toEqual({ ok: true, modelCount: 0 });
});

it('uses the configured global MiniMax OpenAI endpoint and Bearer auth', async () => {
mockFetch(async (url, init) => {
expect(url).toBe(`${MINIMAX_ENDPOINTS.global_en.openaiBaseUrl}/models`);
const headers = (init?.headers ?? {}) as Record<string, string>;
expect(headers['authorization']).toBe('Bearer minimax-test-key');
return new Response(
JSON.stringify({ data: [{ id: 'MiniMax-M3' }, { id: 'MiniMax-M2.7' }] }),
{
status: 200,
headers: { 'content-type': 'application/json' },
},
);
});
const result = await pingProvider('minimax', 'minimax-test-key');
expect(result).toEqual({ ok: true, modelCount: 2 });
});

it('respects custom baseUrl without /v1 suffix', async () => {
mockFetch(async (url) => {
expect(url).toBe('https://proxy.example/v1/models');
Expand Down
12 changes: 11 additions & 1 deletion packages/providers/src/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
CodesignError,
ERROR_CODES,
isSupportedOnboardingProvider,
MINIMAX_ENDPOINTS,
type SupportedOnboardingProvider,
stripInferenceEndpointSuffix,
} from '@open-codesign/shared';
Expand Down Expand Up @@ -57,6 +58,15 @@ function endpoint(provider: SupportedOnboardingProvider, baseUrl?: string): Prov
headers: (apiKey) => ({ authorization: `Bearer ${apiKey}` }),
};
}
case 'minimax': {
const root = baseUrl
? normalizeValidateBaseUrl(baseUrl)
: normalizeValidateBaseUrl(MINIMAX_ENDPOINTS.global_en.openaiBaseUrl);
return {
url: `${root}/v1/models`,
headers: (apiKey) => ({ authorization: `Bearer ${apiKey}` }),
};
}
case 'ollama': {
// Local Ollama — OpenAI-compat endpoint at /v1. No auth header; the
// caller (renderer) may still pass a non-empty apiKey as a sentinel
Expand Down Expand Up @@ -97,7 +107,7 @@ export async function pingProvider(
): Promise<ValidateResult> {
if (!isSupportedOnboardingProvider(provider)) {
throw new CodesignError(
`Provider "${provider}" is not supported by the first-run provider shortcut. Supported: anthropic, openai, openrouter, ollama. Add custom providers in Settings, or use ChatGPT subscription sign-in for chatgpt-codex.`,
`Provider "${provider}" is not supported by the first-run provider shortcut. Supported: anthropic, openai, openrouter, minimax, ollama. Add custom providers in Settings, or use ChatGPT subscription sign-in for chatgpt-codex.`,
ERROR_CODES.PROVIDER_NOT_SUPPORTED,
);
}
Expand Down
49 changes: 48 additions & 1 deletion packages/shared/src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
defaultProviderCapabilities,
detectWireFromBaseUrl,
hydrateConfig,
MINIMAX_ENDPOINTS,
MINIMAX_MODEL_CATALOG,
MINIMAX_MODEL_IDS,
migrateLegacyToV3,
parseConfigFlexible,
resolveProviderCapabilities,
Expand All @@ -13,6 +16,50 @@ import {
} from './config';

describe('config v3 schema', () => {
it('registers MiniMax models and all configured endpoint variants', () => {
expect(MINIMAX_MODEL_IDS).toEqual(['MiniMax-M3', 'MiniMax-M2.7']);
expect(BUILTIN_PROVIDERS.minimax).toMatchObject({
id: 'minimax',
wire: 'openai-chat',
baseUrl: MINIMAX_ENDPOINTS.global_en.openaiBaseUrl,
envKey: 'MINIMAX_API_KEY',
defaultModel: 'MiniMax-M3',
modelsHint: ['MiniMax-M3', 'MiniMax-M2.7'],
});
expect(
Object.values(MINIMAX_ENDPOINTS).flatMap((endpoint) => [
endpoint.openaiBaseUrl,
endpoint.anthropicBaseUrl,
]),
).toEqual([
'https://api.minimax.io/v1',
'https://api.minimax.io/anthropic',
'https://api.minimaxi.com/v1',
'https://api.minimaxi.com/anthropic',
]);
expect(
Object.values(MINIMAX_ENDPOINTS).every((endpoint) =>
endpoint.anthropicBaseUrl.endsWith('/anthropic'),
),
).toBe(true);
expect(MINIMAX_MODEL_CATALOG).toMatchObject([
{
modelId: 'MiniMax-M3',
contextWindow: 1_000_000,
inputModalities: ['text', 'image', 'video'],
thinking: ['adaptive', 'disabled'],
},
{
modelId: 'MiniMax-M2.7',
contextWindow: 204_800,
inputModalities: ['text'],
thinking: ['always_on'],
},
]);
expect(MINIMAX_MODEL_CATALOG[0]?.pricingTiersUsdPerMillionTokens).toHaveLength(4);
expect(MINIMAX_MODEL_CATALOG[1]?.pricingUsdPerMillionTokens.cacheWrite).toBe(0.375);
});

it('parses a minimal v3 config', () => {
const raw = {
version: 3,
Expand Down Expand Up @@ -302,7 +349,7 @@ describe('config v3 schema', () => {
});

describe('migrateLegacyToV3', () => {
it('seeds three builtin providers from an empty v2', () => {
it('seeds all supported builtin providers from an empty v2', () => {
const legacy = {
version: 2 as const,
provider: 'anthropic' as const,
Expand Down
106 changes: 105 additions & 1 deletion packages/shared/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const ProviderIdEnum = z.enum([
'openai',
'google',
'openrouter',
'minimax',
'groq',
'cerebras',
'xai',
Expand All @@ -20,6 +21,7 @@ export const SUPPORTED_ONBOARDING_PROVIDERS = [
'anthropic',
'openai',
'openrouter',
'minimax',
'ollama',
] as const;
export type SupportedOnboardingProvider = (typeof SUPPORTED_ONBOARDING_PROVIDERS)[number];
Expand All @@ -29,6 +31,84 @@ export type SupportedOnboardingProvider = (typeof SUPPORTED_ONBOARDING_PROVIDERS
export const OLLAMA_DEFAULT_BASE_URL = 'http://localhost:11434/v1';
export const OLLAMA_DEFAULT_MODEL = 'llama3.2';

export const MINIMAX_ENDPOINTS = {
global_en: {
region: 'global_en',
openaiBaseUrl: 'https://api.minimax.io/v1',
anthropicBaseUrl: 'https://api.minimax.io/anthropic',
docsRoot: 'https://platform.minimax.io/docs',
},
cn_zh: {
region: 'cn_zh',
openaiBaseUrl: 'https://api.minimaxi.com/v1',
anthropicBaseUrl: 'https://api.minimaxi.com/anthropic',
docsRoot: 'https://platform.minimaxi.com/docs',
},
} as const;

export const MINIMAX_MODEL_CATALOG = [
{
modelId: 'MiniMax-M3',
contextWindow: 1_000_000,
pricingUsdPerMillionTokens: {
input: 0.3,
output: 1.2,
cacheRead: 0.06,
cacheWrite: null,
},
pricingTiersUsdPerMillionTokens: [
{
serviceTier: 'standard',
inputTokensLte: 512_000,
input: 0.3,
output: 1.2,
cacheRead: 0.06,
cacheWrite: null,
},
{
serviceTier: 'standard',
inputTokensGt: 512_000,
input: 0.6,
output: 2.4,
cacheRead: 0.12,
cacheWrite: null,
},
{
serviceTier: 'priority',
inputTokensLte: 512_000,
input: 0.45,
output: 1.8,
cacheRead: 0.09,
cacheWrite: null,
},
{
serviceTier: 'priority',
inputTokensGt: 512_000,
input: 0.9,
output: 3.6,
cacheRead: 0.18,
cacheWrite: null,
},
],
inputModalities: ['text', 'image', 'video'],
thinking: ['adaptive', 'disabled'],
},
{
modelId: 'MiniMax-M2.7',
contextWindow: 204_800,
pricingUsdPerMillionTokens: {
input: 0.3,
output: 1.2,
cacheRead: 0.06,
cacheWrite: 0.375,
},
inputModalities: ['text'],
thinking: ['always_on'],
},
] as const;

export const MINIMAX_MODEL_IDS = MINIMAX_MODEL_CATALOG.map((model) => model.modelId);

// ── Wire types (v3) ──────────────────────────────────────────────────────────

export const WireApiSchema = z.enum([
Expand Down Expand Up @@ -278,6 +358,23 @@ export const BUILTIN_PROVIDERS: Readonly<Record<SupportedOnboardingProvider, Pro
modelDiscoveryMode: 'models',
},
},
minimax: {
id: 'minimax',
name: 'MiniMax',
builtin: true,
wire: 'openai-chat',
baseUrl: MINIMAX_ENDPOINTS.global_en.openaiBaseUrl,
envKey: 'MINIMAX_API_KEY',
defaultModel: MINIMAX_MODEL_CATALOG[0].modelId,
modelsHint: MINIMAX_MODEL_IDS,
capabilities: {
supportsKeyless: false,
supportsModelsEndpoint: false,
supportsReasoning: false,
requiresClaudeCodeIdentity: false,
modelDiscoveryMode: 'static-hint',
},
},
ollama: {
id: 'ollama',
name: 'Ollama (local)',
Expand Down Expand Up @@ -384,7 +481,7 @@ function cloneBuiltin(id: SupportedOnboardingProvider): ProviderEntry {
}

/**
* Pure: migrate a validated v1/v2 config to v3. Seeds the three builtin
* Pure: migrate a validated v1/v2 config to v3. Seeds the supported builtin
* providers and overlays any stored baseUrls onto them.
*/
export function migrateLegacyToV3(legacy: LegacyConfig): ConfigV3 {
Expand Down Expand Up @@ -507,6 +604,13 @@ export const PROVIDER_SHORTLIST: Record<SupportedOnboardingProvider, ProviderSho
primary: ['anthropic/claude-sonnet-4.6', 'openai/gpt-4o'],
defaultPrimary: 'anthropic/claude-sonnet-4.6',
},
minimax: {
provider: 'minimax',
label: 'MiniMax',
keyHelpUrl: 'https://platform.minimax.io/docs/api-reference/api-overview',
primary: MINIMAX_MODEL_IDS,
defaultPrimary: MINIMAX_MODEL_CATALOG[0].modelId,
},
ollama: {
provider: 'ollama',
label: 'Ollama (local)',
Expand Down
4 changes: 4 additions & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const ProviderId = z.enum([
'openai',
'google',
'openrouter',
'minimax',
'groq',
'cerebras',
'xai',
Expand Down Expand Up @@ -319,6 +320,9 @@ export {
ImageGenerationSettingsSchema,
ImageGenerationSizeSchema,
isSupportedOnboardingProvider,
MINIMAX_ENDPOINTS,
MINIMAX_MODEL_CATALOG,
MINIMAX_MODEL_IDS,
migrateLegacyToV3,
PROVIDER_SHORTLIST,
ProviderCapabilitiesSchema,
Expand Down
Loading