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
5 changes: 5 additions & 0 deletions app/api/chat/[project_id]/act/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { initializeNextJsProject as initializeCodexProject, applyChanges as appl
import { initializeNextJsProject as initializeCursorProject, applyChanges as applyCursorChanges } from '@/lib/services/cli/cursor';
import { initializeNextJsProject as initializeQwenProject, applyChanges as applyQwenChanges } from '@/lib/services/cli/qwen';
import { initializeNextJsProject as initializeGLMProject, applyChanges as applyGLMChanges } from '@/lib/services/cli/glm';
import { initializeNextJsProject as initializeMiniMaxProject, applyChanges as applyMiniMaxChanges } from '@/lib/services/cli/minimax';
import { getDefaultModelForCli, normalizeModelId } from '@/lib/constants/cliModels';
import { streamManager } from '@/lib/services/stream';
import type { ChatActRequest } from '@/types/backend';
Expand Down Expand Up @@ -407,6 +408,8 @@ export async function POST(request: NextRequest, { params }: RouteContext) {
? initializeQwenProject
: cliPreference === 'glm'
? initializeGLMProject
: cliPreference === 'minimax'
? initializeMiniMaxProject
: initializeClaudeProject;

executor(
Expand All @@ -428,6 +431,8 @@ export async function POST(request: NextRequest, { params }: RouteContext) {
? applyQwenChanges
: cliPreference === 'glm'
? applyGLMChanges
: cliPreference === 'minimax'
? applyMiniMaxChanges
: applyClaudeChanges;

const sessionId =
Expand Down
15 changes: 15 additions & 0 deletions app/api/settings/cli-status/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { CLIStatus } from '@/types/backend';
import { CODEX_MODEL_DEFINITIONS } from '@/lib/constants/codexModels';
import { QWEN_MODEL_DEFINITIONS } from '@/lib/constants/qwenModels';
import { GLM_MODEL_DEFINITIONS } from '@/lib/constants/glmModels';
import { MINIMAX_MODEL_DEFINITIONS } from '@/lib/constants/minimaxModels';
import { CURSOR_MODEL_DEFINITIONS } from '@/lib/constants/cursorModels';

const execAsync = promisify(exec);
Expand Down Expand Up @@ -132,6 +133,10 @@ export async function GET() {
installed: false,
checking: false,
},
minimax: {
installed: false,
checking: false,
},
};

// Check Claude Code CLI installation
Expand Down Expand Up @@ -179,6 +184,16 @@ export async function GET() {
models: GLM_MODEL_DEFINITIONS.map((model) => model.id),
};

// MiniMax reuses the Claude Code runtime (Anthropic-compatible endpoint)
const minimaxStatus = claudeStatus;
status.minimax = {
installed: minimaxStatus.installed,
version: minimaxStatus.version,
checking: false,
error: minimaxStatus.error,
models: MINIMAX_MODEL_DEFINITIONS.map((model) => model.id),
};

return NextResponse.json(status);
} catch (error) {
console.error('[API] Failed to check CLI status:', error);
Expand Down
16 changes: 16 additions & 0 deletions components/modals/CreateProjectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ const CLI_OPTIONS: CLIOption[] = [
})),
features: ['Claude-compatible runtime', 'GLM 4.6 reasoning', 'Text-only mode'],
},
{
id: 'minimax',
name: 'MiniMax CLI',
icon: '🟣',
description: 'MiniMax agent running via Claude Code runtime',
color: 'from-purple-500 to-fuchsia-600',
downloadUrl: 'https://platform.minimax.io/docs',
installCommand: 'npm install -g @anthropic-ai/claude-code',
models: getModelDefinitionsForCli('minimax').map(({ id, name, description, supportsImages }) => ({
id,
name,
description,
supportsImages,
})),
features: ['Claude-compatible runtime', 'MiniMax M3 reasoning', 'Multimodal input'],
},
];

function generateUUID() {
Expand Down
54 changes: 54 additions & 0 deletions components/settings/GlobalSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ const CLI_OPTIONS: CLIOption[] = [
enabled: true,
models: getModelDefinitionsForCli('glm').map(({ id, name }) => ({ id, name })),
},
{
id: 'minimax',
name: 'MiniMax CLI',
icon: '',
description: 'MiniMax agent running through Claude Code runtime',
color: 'from-purple-500 to-fuchsia-600',
brandColor: '#FF2E5F',
downloadUrl: 'https://platform.minimax.io/docs',
installCommand: 'npm install -g @anthropic-ai/claude-code',
enabled: true,
models: getModelDefinitionsForCli('minimax').map(({ id, name }) => ({ id, name })),
},
];

// Global settings are provided by context
Expand Down Expand Up @@ -544,6 +556,11 @@ export default function GlobalSettings({ isOpen, onClose, initialTab = 'general'
{cli.id === 'glm' && (
<Image src="/glm.svg" alt="GLM" width={32} height={32} className="w-8 h-8" />
)}
{cli.id === 'minimax' && (
<div className="w-8 h-8 rounded-md flex items-center justify-center text-white text-xs font-bold" style={{ backgroundColor: '#FF2E5F' }}>
MM
</div>
)}
{cli.id === 'gemini' && (
<Image src="/gemini.png" alt="Gemini" width={32} height={32} className="w-8 h-8" />
)}
Expand Down Expand Up @@ -641,6 +658,38 @@ export default function GlobalSettings({ isOpen, onClose, initialTab = 'general'
</p>
</div>
)}
{cli.id === 'minimax' && (
<div className="space-y-1.5">
<label className="text-xs font-medium text-gray-600 ">
API Key
</label>
<div className="flex items-center gap-2">
<input
type={apiKeyVisibility[cli.id] ? 'text' : 'password'}
value={settings.apiKey ?? ''}
onChange={(e) => setCliApiKey(cli.id, e.target.value)}
placeholder="Enter MiniMax API key"
className="flex-1 px-3 py-1.5 rounded-lg border border-gray-200 bg-white text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-200"
/>
<button
type="button"
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
toggleApiKeyVisibility(cli.id);
}}
className="px-3 py-1.5 text-xs font-medium text-gray-600 hover:text-gray-900 border border-gray-200 rounded-lg bg-white transition-colors"
>
{apiKeyVisibility[cli.id] ? 'Hide' : 'Show'}
</button>
</div>
<p className="text-[11px] text-gray-500 leading-snug">
Stored locally and injected as <code className="font-mono">MINIMAX_API_KEY</code> when running MiniMax.
Set <code className="font-mono">MINIMAX_REGION</code> to <code className="font-mono">cn_zh</code> to use the China endpoint.
Leave blank to rely on server environment variables instead.
</p>
</div>
)}
</div>
) : (
<div onClick={(e) => e.stopPropagation()}>
Expand Down Expand Up @@ -918,6 +967,7 @@ export default function GlobalSettings({ isOpen, onClose, initialTab = 'general'
</span>
{selectedCLI.id === 'gemini' && 'Authenticate (OAuth or API Key)'}
{selectedCLI.id === 'glm' && 'Authenticate (Z.ai DevPack login)'}
{selectedCLI.id === 'minimax' && 'Authenticate (MiniMax API key)'}
{selectedCLI.id === 'qwen' && 'Authenticate (Qwen OAuth or API Key)'}
{selectedCLI.id === 'codex' && 'Start Codex and sign in'}
{selectedCLI.id === 'claude' && 'Start Claude and sign in'}
Expand All @@ -930,6 +980,7 @@ export default function GlobalSettings({ isOpen, onClose, initialTab = 'general'
selectedCLI.id === 'codex' ? 'codex' :
selectedCLI.id === 'qwen' ? 'qwen' :
selectedCLI.id === 'glm' ? 'zai' :
selectedCLI.id === 'minimax' ? 'claude' :
selectedCLI.id === 'gemini' ? 'gemini' : ''}
</code>
<button
Expand All @@ -942,6 +993,7 @@ export default function GlobalSettings({ isOpen, onClose, initialTab = 'general'
selectedCLI.id === 'codex' ? 'codex' :
selectedCLI.id === 'qwen' ? 'qwen' :
selectedCLI.id === 'glm' ? 'zai' :
selectedCLI.id === 'minimax' ? 'claude' :
selectedCLI.id === 'gemini' ? 'gemini' : '';
if (authCmd) navigator.clipboard.writeText(authCmd);
showToast('Command copied to clipboard', 'success');
Expand Down Expand Up @@ -971,6 +1023,7 @@ export default function GlobalSettings({ isOpen, onClose, initialTab = 'general'
selectedCLI.id === 'codex' ? 'codex --version' :
selectedCLI.id === 'qwen' ? 'qwen --version' :
selectedCLI.id === 'glm' ? 'zai --version' :
selectedCLI.id === 'minimax' ? 'claude --version' :
selectedCLI.id === 'gemini' ? 'gemini --version' : ''}
</code>
<button
Expand All @@ -983,6 +1036,7 @@ export default function GlobalSettings({ isOpen, onClose, initialTab = 'general'
selectedCLI.id === 'codex' ? 'codex --version' :
selectedCLI.id === 'qwen' ? 'qwen --version' :
selectedCLI.id === 'glm' ? 'zai --version' :
selectedCLI.id === 'minimax' ? 'claude --version' :
selectedCLI.id === 'gemini' ? 'gemini --version' : '';
if (versionCmd) navigator.clipboard.writeText(versionCmd);
showToast('Command copied to clipboard', 'success');
Expand Down
1 change: 1 addition & 0 deletions contexts/GlobalSettingsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const defaultSettings: GlobalAISettings = {
codex: { model: getDefaultModelForCli('codex') },
qwen: { model: getDefaultModelForCli('qwen') },
glm: { model: getDefaultModelForCli('glm') },
minimax: { model: getDefaultModelForCli('minimax') },
},
};

Expand Down
9 changes: 8 additions & 1 deletion lib/constants/cliModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { CODEX_DEFAULT_MODEL, CODEX_MODEL_DEFINITIONS, getCodexModelDisplayName,
import { CURSOR_DEFAULT_MODEL, CURSOR_MODEL_DEFINITIONS, getCursorModelDisplayName, normalizeCursorModelId } from './cursorModels';
import { QWEN_DEFAULT_MODEL, QWEN_MODEL_DEFINITIONS, getQwenModelDisplayName, normalizeQwenModelId } from './qwenModels';
import { GLM_DEFAULT_MODEL, GLM_MODEL_DEFINITIONS, getGLMModelDisplayName, normalizeGLMModelId } from './glmModels';
import { MINIMAX_DEFAULT_MODEL, MINIMAX_MODEL_DEFINITIONS, getMiniMaxModelDisplayName, normalizeMiniMaxModelId } from './minimaxModels';
import type { CLAUDE_MODEL_DEFINITIONS as _Guard } from './claudeModels'; // Ensure module side effects preserved

type CLIKey = 'claude' | 'codex' | 'cursor' | 'gemini' | 'qwen' | 'glm';
type CLIKey = 'claude' | 'codex' | 'cursor' | 'gemini' | 'qwen' | 'glm' | 'minimax';

type ModelDefinition = {
id: string;
Expand All @@ -21,6 +22,7 @@ const DEFAULT_MODELS: Record<CLIKey, string> = {
gemini: 'gemini-2.5-pro',
qwen: QWEN_DEFAULT_MODEL,
glm: GLM_DEFAULT_MODEL,
minimax: MINIMAX_DEFAULT_MODEL,
};

const MODEL_DEFINITIONS: Record<CLIKey, ModelDefinition[]> = {
Expand All @@ -33,6 +35,7 @@ const MODEL_DEFINITIONS: Record<CLIKey, ModelDefinition[]> = {
],
qwen: QWEN_MODEL_DEFINITIONS,
glm: GLM_MODEL_DEFINITIONS,
minimax: MINIMAX_MODEL_DEFINITIONS,
};

export function getDefaultModelForCli(cli: string | null | undefined): string {
Expand All @@ -56,6 +59,8 @@ export function normalizeModelId(cli: string | null | undefined, model?: string
return normalizeQwenModelId(model);
case 'glm':
return normalizeGLMModelId(model);
case 'minimax':
return normalizeMiniMaxModelId(model);
case 'claude':
default:
return normalizeClaudeModelId(model);
Expand All @@ -76,6 +81,8 @@ export function getModelDisplayName(cli: string | null | undefined, modelId?: st
return getQwenModelDisplayName(modelId);
case 'glm':
return getGLMModelDisplayName(modelId);
case 'minimax':
return getMiniMaxModelDisplayName(modelId);
case 'claude':
default:
return getClaudeModelDisplayName(normalizeClaudeModelId(modelId));
Expand Down
87 changes: 87 additions & 0 deletions lib/constants/minimaxModels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
export type MiniMaxModelId = 'MiniMax-M3' | 'MiniMax-M2.7';

export interface MiniMaxModelDefinition {
id: MiniMaxModelId;
/** User facing display name */
name: string;
/** Longer description shown in pickers */
description?: string;
/** Whether the model accepts image input */
supportsImages?: boolean;
/** Alias strings that should resolve to this model */
aliases: string[];
}

export const MINIMAX_MODEL_DEFINITIONS: MiniMaxModelDefinition[] = [
{
id: 'MiniMax-M3',
name: 'MiniMax M3',
description: 'MiniMax M3 with 1M context, multimodal input and Claude Code compatible agent runtime',
supportsImages: true,
aliases: [
'minimax-m3',
'minimaxm3',
'minimax m3',
'minimax_m3',
'm3',
'minimax-latest',
'minimax',
],
},
{
id: 'MiniMax-M2.7',
name: 'MiniMax M2.7',
description: 'MiniMax M2.7 text model with 204k context and always-on thinking',
supportsImages: false,
aliases: [
'minimax-m2.7',
'minimaxm2.7',
'minimax m2.7',
'minimax_m2_7',
'm2.7',
'm27',
],
},
];

export const MINIMAX_DEFAULT_MODEL: MiniMaxModelId = 'MiniMax-M3';

const MINIMAX_MODEL_ALIAS_MAP: Record<string, MiniMaxModelId> = MINIMAX_MODEL_DEFINITIONS.reduce(
(map, definition) => {
definition.aliases.forEach((alias) => {
const key = alias.trim().toLowerCase();
map[key] = definition.id;
});
map[definition.id.toLowerCase()] = definition.id;
return map;
},
{} as Record<string, MiniMaxModelId>,
);

export function normalizeMiniMaxModelId(model?: string | null): MiniMaxModelId {
if (!model) {
return MINIMAX_DEFAULT_MODEL;
}
const normalized = model.trim().toLowerCase();
if (!normalized) {
return MINIMAX_DEFAULT_MODEL;
}
return MINIMAX_MODEL_ALIAS_MAP[normalized] ?? MINIMAX_DEFAULT_MODEL;
}

export function getMiniMaxModelDefinition(id: string): MiniMaxModelDefinition | undefined {
return (
MINIMAX_MODEL_DEFINITIONS.find((definition) => definition.id === id) ??
MINIMAX_MODEL_DEFINITIONS.find((definition) =>
definition.aliases.some((alias) => alias.toLowerCase() === id.toLowerCase()),
)
);
}

export function getMiniMaxModelDisplayName(id?: string | null): string {
if (!id) {
return getMiniMaxModelDefinition(MINIMAX_DEFAULT_MODEL)?.name ?? MINIMAX_DEFAULT_MODEL;
}
const normalized = normalizeMiniMaxModelId(id);
return getMiniMaxModelDefinition(normalized)?.name ?? normalized;
}
Loading