From 8b69a2b3610cd8829c4a0242260e970e42875f2a Mon Sep 17 00:00:00 2001 From: octo-patch <266937838+octo-patch@users.noreply.github.com> Date: Thu, 30 Jul 2026 05:04:46 +0000 Subject: [PATCH] feat(ask-code): add MiniMax-M3 to the inline code Q&A catalog while retaining M2.7 Introduce a MiniMax model and endpoint catalog so the inline code Q&A provider is no longer pinned to a single model or a single endpoint. M3 is represented with its 1M-token context window, image and video input and adaptive/disabled thinking, while M2.7 is retained as a 204K text-only always-on model. Both the global and China regional endpoints and their protocol base URLs are exposed. The provider now derives its request URL and default model from the catalog. --- README.md | 2 +- electron/ipc/ask-code-minimax.test.ts | 2 +- electron/ipc/ask-code-minimax.ts | 9 ++- electron/ipc/minimax-catalog.test.ts | 85 ++++++++++++++++++++ electron/ipc/minimax-catalog.ts | 110 ++++++++++++++++++++++++++ src/components/SettingsDialog.tsx | 4 +- 6 files changed, 206 insertions(+), 6 deletions(-) create mode 100644 electron/ipc/minimax-catalog.test.ts create mode 100644 electron/ipc/minimax-catalog.ts diff --git a/README.md b/README.md index be457922..7346525b 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ - **See every session in one place** — switch context without losing momentum. - **Control everything keyboard-first** — every action has a shortcut, mouse optional. - **Monitor progress from your phone** — scan a QR code, watch agents work over Wi-Fi or Tailscale. -- **Ask about code with any LLM** — the inline code Q&A feature supports [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (default) or [MiniMax](https://www.minimax.io/) M2.7 (204K context) — configurable in Settings. +- **Ask about code with any LLM** — the inline code Q&A feature supports [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (default) or [MiniMax](https://www.minimax.io/) M3 (1M context, image and video input) with M2.7 also supported — configurable in Settings.
How does it compare? diff --git a/electron/ipc/ask-code-minimax.test.ts b/electron/ipc/ask-code-minimax.test.ts index 8a73d51a..4232b9f0 100644 --- a/electron/ipc/ask-code-minimax.test.ts +++ b/electron/ipc/ask-code-minimax.test.ts @@ -164,7 +164,7 @@ describe('askAboutCodeMinimax', () => { ); }); - it('uses MiniMax-M2.7 model', async () => { + it('uses the default MiniMax model from the catalog', async () => { const { win, messages } = makeMockWin(); mockFetch.mockResolvedValueOnce(makeStreamResponse('data: [DONE]\n\n')); diff --git a/electron/ipc/ask-code-minimax.ts b/electron/ipc/ask-code-minimax.ts index 0f262296..0d382928 100644 --- a/electron/ipc/ask-code-minimax.ts +++ b/electron/ipc/ask-code-minimax.ts @@ -8,6 +8,11 @@ import { assertCanStart, assertPromptWithinLimit, } from './request-registry.js'; +import { + DEFAULT_MINIMAX_MODEL_ID, + DEFAULT_MINIMAX_REGION, + minimaxChatCompletionsUrl, +} from './minimax-catalog.js'; interface MinimaxAskCodeRequest { requestId: string; @@ -15,8 +20,8 @@ interface MinimaxAskCodeRequest { prompt: string; } -const MINIMAX_API_URL = 'https://api.minimax.io/v1/chat/completions'; -export const MINIMAX_MODEL = 'MiniMax-M2.7'; +const MINIMAX_API_URL = minimaxChatCompletionsUrl(DEFAULT_MINIMAX_REGION); +export const MINIMAX_MODEL = DEFAULT_MINIMAX_MODEL_ID; const activeRequests = new RequestRegistry({ maxConcurrent: ASK_CODE_MAX_CONCURRENT, diff --git a/electron/ipc/minimax-catalog.test.ts b/electron/ipc/minimax-catalog.test.ts new file mode 100644 index 00000000..532183d2 --- /dev/null +++ b/electron/ipc/minimax-catalog.test.ts @@ -0,0 +1,85 @@ +import { describe, it, expect } from 'vitest'; +import { + DEFAULT_MINIMAX_MODEL_ID, + DEFAULT_MINIMAX_REGION, + MINIMAX_ENDPOINTS, + MINIMAX_MODELS, + getMinimaxEndpoint, + getMinimaxModel, + minimaxBaseUrl, + minimaxChatCompletionsUrl, + minimaxModelIds, +} from './minimax-catalog.js'; + +describe('minimax catalog models', () => { + it('retains M2.7 alongside M3', () => { + expect(minimaxModelIds()).toEqual(['MiniMax-M3', 'MiniMax-M2.7']); + }); + + it('defaults to M3', () => { + expect(DEFAULT_MINIMAX_MODEL_ID).toBe('MiniMax-M3'); + expect(getMinimaxModel(DEFAULT_MINIMAX_MODEL_ID)).toBeDefined(); + }); + + it('represents M3 with a 1M context window, image/video input and adaptive/disabled thinking', () => { + const m3 = getMinimaxModel('MiniMax-M3'); + expect(m3).toBeDefined(); + expect(m3?.contextWindow).toBe(1_000_000); + expect(m3?.inputModalities).toEqual(['text', 'image', 'video']); + expect(m3?.thinking).toEqual(['adaptive', 'disabled']); + }); + + it('represents M2.7 as a 204K text-only always-on model', () => { + const m27 = getMinimaxModel('MiniMax-M2.7'); + expect(m27).toBeDefined(); + expect(m27?.contextWindow).toBe(204_800); + expect(m27?.inputModalities).toEqual(['text']); + expect(m27?.thinking).toEqual(['always_on']); + }); + + it('does not clone the M2.7 profile onto M3', () => { + const m3 = getMinimaxModel('MiniMax-M3'); + const m27 = getMinimaxModel('MiniMax-M2.7'); + expect(m3?.contextWindow).not.toBe(m27?.contextWindow); + expect(m3?.inputModalities).not.toEqual(m27?.inputModalities); + }); + + it('returns undefined for unknown models', () => { + expect(getMinimaxModel('nope')).toBeUndefined(); + }); + + it('exposes exactly the catalogued models', () => { + expect(MINIMAX_MODELS).toHaveLength(2); + }); +}); + +describe('minimax catalog endpoints', () => { + it('exposes the global and China regions', () => { + expect(MINIMAX_ENDPOINTS.map((e) => e.region)).toEqual(['global_en', 'cn_zh']); + }); + + it('exposes both protocol base URLs for the global region', () => { + expect(minimaxBaseUrl('global_en', 'openai')).toBe('https://api.minimax.io/v1'); + expect(minimaxBaseUrl('global_en', 'anthropic')).toBe('https://api.minimax.io/anthropic'); + }); + + it('exposes both protocol base URLs for the China region', () => { + expect(minimaxBaseUrl('cn_zh', 'openai')).toBe('https://api.minimaxi.com/v1'); + expect(minimaxBaseUrl('cn_zh', 'anthropic')).toBe('https://api.minimaxi.com/anthropic'); + }); + + it('defaults to the global region', () => { + expect(DEFAULT_MINIMAX_REGION).toBe('global_en'); + }); + + it('builds the chat completions URL from the region base URL', () => { + expect(minimaxChatCompletionsUrl('global_en')).toBe( + 'https://api.minimax.io/v1/chat/completions', + ); + expect(minimaxChatCompletionsUrl('cn_zh')).toBe('https://api.minimaxi.com/v1/chat/completions'); + }); + + it('falls back to the global endpoint for the default region', () => { + expect(getMinimaxEndpoint(DEFAULT_MINIMAX_REGION).region).toBe('global_en'); + }); +}); diff --git a/electron/ipc/minimax-catalog.ts b/electron/ipc/minimax-catalog.ts new file mode 100644 index 00000000..6d59315a --- /dev/null +++ b/electron/ipc/minimax-catalog.ts @@ -0,0 +1,110 @@ +/** + * Catalog of the MiniMax models and regional API endpoints used by the inline + * "Ask about Code" provider. + * + * Centralizing this metadata keeps the provider from being pinned to a single + * model or a single endpoint: each model carries its own context window, input + * modalities and thinking modes, and each region exposes both of its + * supported protocol base URLs. + */ + +/** Regions where the MiniMax platform is reachable. */ +export type MinimaxRegion = 'global_en' | 'cn_zh'; + +/** Wire protocols each MiniMax endpoint speaks. */ +export type MinimaxProtocol = 'openai' | 'anthropic'; + +/** Input modalities a MiniMax model can accept. */ +export type MinimaxInputModality = 'text' | 'image' | 'video'; + +/** Thinking modes a MiniMax model supports. */ +export type MinimaxThinkingMode = 'adaptive' | 'disabled' | 'always_on'; + +export interface MinimaxModel { + /** Model identifier sent as the `model` field on the wire. */ + id: string; + /** Maximum number of tokens the model accepts in a single request. */ + contextWindow: number; + /** Input modalities the model understands. */ + inputModalities: MinimaxInputModality[]; + /** Thinking modes the model exposes. */ + thinking: MinimaxThinkingMode[]; +} + +export interface MinimaxEndpoint { + region: MinimaxRegion; + /** Base URL for the `openai` protocol; chat completions live under `${openaiBaseUrl}/chat/completions`. */ + openaiBaseUrl: string; + /** Base URL for the `anthropic` protocol. */ + anthropicBaseUrl: string; + /** Root of the platform documentation for this region. */ + docsRoot: string; +} + +/** + * Supported models, newest first. M3 carries a 1M-token context window and + * accepts image and video input with adaptive or disabled thinking; M2.7 is + * retained as a 204K-token text-only model with always-on thinking. + */ +export const MINIMAX_MODELS: MinimaxModel[] = [ + { + id: 'MiniMax-M3', + contextWindow: 1_000_000, + inputModalities: ['text', 'image', 'video'], + thinking: ['adaptive', 'disabled'], + }, + { + id: 'MiniMax-M2.7', + contextWindow: 204_800, + inputModalities: ['text'], + thinking: ['always_on'], + }, +]; + +/** Regional endpoints and the protocol base URLs each one exposes. */ +export const MINIMAX_ENDPOINTS: MinimaxEndpoint[] = [ + { + region: 'global_en', + openaiBaseUrl: 'https://api.minimax.io/v1', + anthropicBaseUrl: 'https://api.minimax.io/anthropic', + docsRoot: 'https://platform.minimax.io/docs', + }, + { + region: 'cn_zh', + openaiBaseUrl: 'https://api.minimaxi.com/v1', + anthropicBaseUrl: 'https://api.minimaxi.com/anthropic', + docsRoot: 'https://platform.minimaxi.com/docs', + }, +]; + +/** Default model used by the inline code Q&A provider. */ +export const DEFAULT_MINIMAX_MODEL_ID = 'MiniMax-M3'; + +/** Default region used by the inline code Q&A provider. */ +export const DEFAULT_MINIMAX_REGION: MinimaxRegion = 'global_en'; + +/** Model ids in the catalog, newest first. */ +export function minimaxModelIds(): string[] { + return MINIMAX_MODELS.map((model) => model.id); +} + +/** Looks up a model by id, or `undefined` when it is not in the catalog. */ +export function getMinimaxModel(id: string): MinimaxModel | undefined { + return MINIMAX_MODELS.find((model) => model.id === id); +} + +/** Returns the endpoint for a region, falling back to the first (global) one. */ +export function getMinimaxEndpoint(region: MinimaxRegion): MinimaxEndpoint { + return MINIMAX_ENDPOINTS.find((endpoint) => endpoint.region === region) ?? MINIMAX_ENDPOINTS[0]; +} + +/** Returns the base URL a region exposes for the given protocol. */ +export function minimaxBaseUrl(region: MinimaxRegion, protocol: MinimaxProtocol): string { + const endpoint = getMinimaxEndpoint(region); + return protocol === 'anthropic' ? endpoint.anthropicBaseUrl : endpoint.openaiBaseUrl; +} + +/** Builds the chat completions URL for a region. */ +export function minimaxChatCompletionsUrl(region: MinimaxRegion): string { + return `${minimaxBaseUrl(region, 'openai')}/chat/completions`; +} diff --git a/src/components/SettingsDialog.tsx b/src/components/SettingsDialog.tsx index 7b2f246f..bce4caf6 100644 --- a/src/components/SettingsDialog.tsx +++ b/src/components/SettingsDialog.tsx @@ -590,7 +590,7 @@ export function SettingsDialog(props: SettingsDialogProps) { }} > - + @@ -625,7 +625,7 @@ export function SettingsDialog(props: SettingsDialogProps) { {store.askCodeProvider === 'minimax' - ? 'Uses MiniMax M2.7 (204K context) via the OpenAI-compatible API — no Claude Code CLI required.' + ? 'Uses MiniMax M3 (1M context; text, image and video input), with M2.7 also supported — no local CLI required.' : 'Uses the claude CLI to answer questions about selected code. Requires Claude Code to be installed.'}