From a0d165bfae01b2fd6c075e845c601baab7a59960 Mon Sep 17 00:00:00 2001 From: raylanlin Date: Tue, 2 Jun 2026 01:01:51 +0800 Subject: [PATCH 1/4] feat(quota): align types with coding-plan API, show boost multiplier - types: add 4 optional fields (interval_status, weekly_status, boost_permille x 2) - render: prepend 'xN' to model name when boost_permille > 1000 - tests: add 2 cases (with boost, without boost) PR #166 missed these fields. status is not separately rendered since percent already conveys the state (1=partial, 3=full). boost informs the user about their 2x subscription multiplier. --- src/output/quota-table.ts | 5 ++- src/types/api.ts | 4 ++ test/output/quota-table.test.ts | 71 +++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/output/quota-table.ts b/src/output/quota-table.ts index 0611318..839c58d 100644 --- a/src/output/quota-table.ts +++ b/src/output/quota-table.ts @@ -128,7 +128,10 @@ export function renderQuotaTable(models: QuotaModelRemain[], config: Config): vo const L = config.region === 'cn' ? LABELS_CN : LABELS_EN; const rows = models.map((m) => { - const displayName = displayModelName(m.model_name, config.region); + const baseName = displayModelName(m.model_name, config.region); + const boost = m.interval_boost_permille; + const boostTag = (boost && boost > 1000) ? ` ×${boost / 1000}` : ''; + const displayName = baseName + boostTag; const current = renderMetric( L.current, m.current_interval_usage_count, diff --git a/src/types/api.ts b/src/types/api.ts index ad59e02..4c81903 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -253,9 +253,13 @@ export interface QuotaModelRemain { current_interval_total_count: number; current_interval_usage_count: number; current_interval_remaining_percent?: number; + current_interval_status?: number; current_weekly_total_count: number; current_weekly_usage_count: number; current_weekly_remaining_percent?: number; + current_weekly_status?: number; + interval_boost_permille?: number; + weekly_boost_permille?: number; weekly_start_time: number; weekly_end_time: number; weekly_remains_time: number; diff --git a/test/output/quota-table.test.ts b/test/output/quota-table.test.ts index 3c0a42c..69d6e2a 100644 --- a/test/output/quota-table.test.ts +++ b/test/output/quota-table.test.ts @@ -47,9 +47,13 @@ function createCodingPlanModels(): QuotaModelRemain[] { current_interval_total_count: 0, current_interval_usage_count: 0, current_interval_remaining_percent: 94, + current_interval_status: 1, current_weekly_total_count: 0, current_weekly_usage_count: 0, current_weekly_remaining_percent: 98, + current_weekly_status: 1, + interval_boost_permille: 2000, + weekly_boost_permille: 2000, weekly_start_time: Date.UTC(2026, 4, 31, 0, 0, 0), weekly_end_time: Date.UTC(2026, 5, 7, 0, 0, 0), weekly_remains_time: 6 * 24 * 60 * 60 * 1000, @@ -129,4 +133,71 @@ describe('renderQuotaTable', () => { expect(output).toContain('21 / 21'); expect(output).not.toContain('0 / 3'); }); + + it('renders boost multiplier when boost_permille > 1000', () => { + const lines: string[] = []; + const originalLog = console.log; + + console.log = (message?: unknown) => { + lines.push(String(message ?? '')); + }; + + try { + renderQuotaTable(createCodingPlanModels(), { + ...createConfig(), + region: 'cn', + noColor: true, + }); + } finally { + console.log = originalLog; + } + + const output = lines.join('\n'); + + // general model has interval_boost_permille=2000 => ×2 prefix + expect(output).toContain('通用 ×2'); + // video model has no boost field => no ×2 on its row + // ensure the video line is still present (so the negative check is meaningful) + expect(output).toContain('视频'); + }); + + it('omits boost multiplier when boost_permille is missing', () => { + const modelsNoBoost: QuotaModelRemain[] = [{ + model_name: 'general', + start_time: Date.UTC(2026, 4, 31, 0, 0, 0), + end_time: Date.UTC(2026, 4, 31, 2, 0, 0), + remains_time: 2 * 60 * 60 * 1000, + current_interval_total_count: 100, + current_interval_usage_count: 50, + current_interval_remaining_percent: 50, + current_weekly_total_count: 1000, + current_weekly_usage_count: 200, + current_weekly_remaining_percent: 80, + weekly_start_time: Date.UTC(2026, 4, 31, 0, 0, 0), + weekly_end_time: Date.UTC(2026, 5, 7, 0, 0, 0), + weekly_remains_time: 6 * 24 * 60 * 60 * 1000, + }]; + + const lines: string[] = []; + const originalLog = console.log; + + console.log = (message?: unknown) => { + lines.push(String(message ?? '')); + }; + + try { + renderQuotaTable(modelsNoBoost, { + ...createConfig(), + region: 'cn', + noColor: true, + }); + } finally { + console.log = originalLog; + } + + const output = lines.join('\n'); + + expect(output).toContain('通用'); + expect(output).not.toContain('×2'); + }); }); From 22c1a6bfc47a6c63fe0360feb857ff8bf00a8fff Mon Sep 17 00:00:00 2001 From: Raylan LIN Date: Tue, 2 Jun 2026 09:54:04 +0800 Subject: [PATCH 2/4] feat(text): default to MiniMax-M3 with 8192 max-tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Default model MiniMax-M2.7 → MiniMax-M3 (frontier reasoning model, launched 2026-06-01) - Default max_tokens 4096 → 8192 (M3 thinking phase consumes tokens; 4096 too tight) - Update help text, examples, and the one test that asserts default model Built on top of #168. Users can still override with --model flag for M2.7/M2.7-highspeed. --- src/commands/text/chat.ts | 8 ++++---- src/commands/text/repl.ts | 8 ++++---- src/sdk/text/index.ts | 4 ++-- test/commands/text/chat.test.ts | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/commands/text/chat.ts b/src/commands/text/chat.ts index 2d5065e..4bd4bde 100644 --- a/src/commands/text/chat.ts +++ b/src/commands/text/chat.ts @@ -159,11 +159,11 @@ export default defineCommand({ apiDocs: '/docs/api-reference/text-post', usage: 'mmx text chat --message [flags]', options: [ - { flag: '--model ', description: 'Model ID (default: MiniMax-M2.7)' }, + { flag: '--model ', description: 'Model ID (default: MiniMax-M3)' }, { flag: '--message ', description: 'Message text (repeatable, prefix role: to set role)', required: true, type: 'array' }, { flag: '--messages-file ', description: 'JSON file with messages array (use - for stdin)' }, { flag: '--system ', description: 'System prompt' }, - { flag: '--max-tokens ', description: 'Maximum tokens to generate (default: 4096)', type: 'number' }, + { flag: '--max-tokens ', description: 'Maximum tokens to generate (default: 8192)', type: 'number' }, { flag: '--temperature ', description: 'Sampling temperature (0.0, 1.0]', type: 'number' }, { flag: '--top-p ', description: 'Nucleus sampling threshold', type: 'number' }, { flag: '--stream', description: 'Stream response tokens (default: on in TTY)' }, @@ -197,7 +197,7 @@ export default defineCommand({ const model = (flags.model as string) || config.defaultTextModel - || 'MiniMax-M2.7'; + || 'MiniMax-M3'; const format = detectOutputFormat(config.output); const shouldStream = flags.stream === true || ( flags.stream === undefined @@ -208,7 +208,7 @@ export default defineCommand({ const body: ChatRequest = { model, messages, - max_tokens: (flags.maxTokens as number) ?? 4096, + max_tokens: (flags.maxTokens as number) ?? 8192, stream: shouldStream, }; diff --git a/src/commands/text/repl.ts b/src/commands/text/repl.ts index 335c9c0..35bb372 100644 --- a/src/commands/text/repl.ts +++ b/src/commands/text/repl.ts @@ -294,9 +294,9 @@ export default defineCommand({ description: 'Start an interactive multi-turn chat session', usage: 'mmx text repl [flags]', options: [ - { flag: '--model ', description: 'Model ID (default: MiniMax-M2.7)' }, + { flag: '--model ', description: 'Model ID (default: MiniMax-M3)' }, { flag: '--system ', description: 'System prompt' }, - { flag: '--max-tokens ', description: 'Maximum tokens per response (default: 4096)', type: 'number' }, + { flag: '--max-tokens ', description: 'Maximum tokens per response (default: 8192)', type: 'number' }, { flag: '--temperature ', description: 'Sampling temperature (0.0, 1.0]', type: 'number' }, { flag: '--top-p ', description: 'Nucleus sampling threshold', type: 'number' }, ], @@ -325,8 +325,8 @@ export default defineCommand({ const state: ReplState = { messages: [], system: flags.system as string | undefined, - model: (flags.model as string) || config.defaultTextModel || 'MiniMax-M2.7', - maxTokens: (flags.maxTokens as number) ?? 4096, + model: (flags.model as string) || config.defaultTextModel || 'MiniMax-M3', + maxTokens: (flags.maxTokens as number) ?? 8192, temperature: flags.temperature !== undefined ? flags.temperature as number : undefined, topP: flags.topP !== undefined ? flags.topP as number : undefined, }; diff --git a/src/sdk/text/index.ts b/src/sdk/text/index.ts index 1242d3e..fc29586 100644 --- a/src/sdk/text/index.ts +++ b/src/sdk/text/index.ts @@ -56,8 +56,8 @@ export class TextSDK extends Client { return { ...params, - model: params.model ?? 'MiniMax-M2.7', - max_tokens: params.max_tokens ?? 4096, + model: params.model ?? 'MiniMax-M3', + max_tokens: params.max_tokens ?? 8192, } as ChatRequest; } } diff --git a/test/commands/text/chat.test.ts b/test/commands/text/chat.test.ts index 37ad015..a0227eb 100644 --- a/test/commands/text/chat.test.ts +++ b/test/commands/text/chat.test.ts @@ -96,7 +96,7 @@ describe('text chat command', () => { }); const parsed = JSON.parse(output); - expect(parsed.request.model).toBe('MiniMax-M2.7'); + expect(parsed.request.model).toBe('MiniMax-M3'); expect(parsed.request.messages).toHaveLength(1); } finally { console.log = originalLog; From 115f462aa3f33c5de592e292de8976b4a5d251f1 Mon Sep 17 00:00:00 2001 From: Raylan LIN Date: Tue, 2 Jun 2026 09:57:09 +0800 Subject: [PATCH 3/4] docs(text): drop MiniMax-M2.7-highspeed from example commands MiniMax-M2.7-highspeed is no longer the recommended fast variant in the M3 era. Reword the chat/repl examples to demonstrate --system without --model, letting the new default (M3) apply. Help descriptions already reference M3 only. Follow-up to the previous commit on this branch; no code or test changes. --- src/commands/text/chat.ts | 2 +- src/commands/text/repl.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/text/chat.ts b/src/commands/text/chat.ts index 4bd4bde..2601f2d 100644 --- a/src/commands/text/chat.ts +++ b/src/commands/text/chat.ts @@ -171,7 +171,7 @@ export default defineCommand({ ], examples: [ 'mmx text chat --message "What is MiniMax?"', - 'mmx text chat --model MiniMax-M2.7-highspeed --system "You are a coding assistant." --message "Write fizzbuzz in Python"', + 'mmx text chat --system "You are a coding assistant." --message "Write fizzbuzz in Python"', 'mmx text chat --message "Hello" --message "assistant:Hi!" --message "How are you?"', 'cat conversation.json | mmx text chat --messages-file - --stream', 'mmx text chat --message "Hello" --output json', diff --git a/src/commands/text/repl.ts b/src/commands/text/repl.ts index 35bb372..065a891 100644 --- a/src/commands/text/repl.ts +++ b/src/commands/text/repl.ts @@ -302,7 +302,7 @@ export default defineCommand({ ], examples: [ 'mmx text repl', - 'mmx text repl --model MiniMax-M2.7-highspeed --system "You are a coding assistant."', + 'mmx text repl --system "You are a coding assistant."', 'mmx text repl --temperature 0.7 --max-tokens 8192', ], async run(config: Config, flags: GlobalFlags) { From 34cb653157cde418ae6b59f97d2cda7cd09c8cb1 Mon Sep 17 00:00:00 2001 From: Raylan LIN Date: Tue, 2 Jun 2026 10:10:37 +0800 Subject: [PATCH 4/4] test: drop MiniMax-M2.7-highspeed fixtures (M2.5/M2.7/M3 only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MiniMax has sunset M2.7-highspeed across all subscription tiers — even legacy users no longer have it. The API still accepts the id (backward compat) but it's no longer part of the official text-model lineup: M2.5 / M2.7 / M3. Update test fixtures to use M2.5 (the only M-tier model in the lineup that wasn't already the hardcoded fallback) so the fixtures reflect real-world model ids users will actually pass. CLI behavior unchanged. Builds on #172. --- test/commands/config/set.test.ts | 4 ++-- test/commands/config/show.test.ts | 4 ++-- test/commands/text/chat.test.ts | 6 +++--- test/utils/model-defaults.test.ts | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test/commands/config/set.test.ts b/test/commands/config/set.test.ts index 53f085e..ea73132 100644 --- a/test/commands/config/set.test.ts +++ b/test/commands/config/set.test.ts @@ -91,7 +91,7 @@ describe('config set command', () => { await expect( setCommand.execute(config, { key: 'default_text_model', - value: 'MiniMax-M2.7-highspeed', + value: 'MiniMax-M2.5', quiet: false, verbose: false, noColor: true, @@ -123,7 +123,7 @@ describe('config set command', () => { await expect( setCommand.execute(config, { key: 'default-text-model', - value: 'MiniMax-M2.7-highspeed', + value: 'MiniMax-M2.5', quiet: false, verbose: false, noColor: true, diff --git a/test/commands/config/show.test.ts b/test/commands/config/show.test.ts index e063b7c..16e9b4c 100644 --- a/test/commands/config/show.test.ts +++ b/test/commands/config/show.test.ts @@ -5,7 +5,7 @@ import { default as showCommand } from '../../../src/commands/config/show'; mock.module('../../../src/config/loader', () => ({ readConfigFile: () => ({ api_key: 'sk-cp-test-key', - default_text_model: 'MiniMax-M2.7-highspeed', + default_text_model: 'MiniMax-M2.5', default_speech_model: 'speech-2.8-hd', default_video_model: 'MiniMax-Hailuo-2.3-6s-768p', default_music_model: 'music-2.6', @@ -89,7 +89,7 @@ describe('config show command', () => { }); const parsed = JSON.parse(output); - expect(parsed.default_text_model).toBe('MiniMax-M2.7-highspeed'); + expect(parsed.default_text_model).toBe('MiniMax-M2.5'); expect(parsed.default_speech_model).toBe('speech-2.8-hd'); expect(parsed.default_video_model).toBe('MiniMax-Hailuo-2.3-6s-768p'); expect(parsed.default_music_model).toBe('music-2.6'); diff --git a/test/commands/text/chat.test.ts b/test/commands/text/chat.test.ts index a0227eb..827bcb0 100644 --- a/test/commands/text/chat.test.ts +++ b/test/commands/text/chat.test.ts @@ -112,7 +112,7 @@ describe('text chat command', () => { baseUrl: 'https://api.mmx.io', output: 'json', timeout: 10, - defaultTextModel: 'MiniMax-M2.7-highspeed', + defaultTextModel: 'MiniMax-M2.5', verbose: false, quiet: false, noColor: true, @@ -140,7 +140,7 @@ describe('text chat command', () => { }); const parsed = JSON.parse(output); - expect(parsed.request.model).toBe('MiniMax-M2.7-highspeed'); + expect(parsed.request.model).toBe('MiniMax-M2.5'); } finally { console.log = originalLog; } @@ -268,7 +268,7 @@ describe('text chat command', () => { baseUrl: 'https://api.mmx.io', output: 'json', timeout: 10, - defaultTextModel: 'MiniMax-M2.7-highspeed', + defaultTextModel: 'MiniMax-M2.5', verbose: false, quiet: false, noColor: true, diff --git a/test/utils/model-defaults.test.ts b/test/utils/model-defaults.test.ts index bb6a908..19ba14f 100644 --- a/test/utils/model-defaults.test.ts +++ b/test/utils/model-defaults.test.ts @@ -33,13 +33,13 @@ function resolveModel( describe('model resolution (flag > config default > fallback)', () => { it('uses flag when provided', () => { - const model = resolveModel('defaultTextModel', 'MiniMax-M2.7', baseConfig, { model: 'MiniMax-M2.7-highspeed' }); - expect(model).toBe('MiniMax-M2.7-highspeed'); + const model = resolveModel('defaultTextModel', 'MiniMax-M2.7', baseConfig, { model: 'MiniMax-M2.5' }); + expect(model).toBe('MiniMax-M2.5'); }); it('falls back to config default when flag is absent', () => { - const model = resolveModel('defaultTextModel', 'MiniMax-M2.7', { ...baseConfig, defaultTextModel: 'MiniMax-M2.7-highspeed' }, {}); - expect(model).toBe('MiniMax-M2.7-highspeed'); + const model = resolveModel('defaultTextModel', 'MiniMax-M2.7', { ...baseConfig, defaultTextModel: 'MiniMax-M2.5' }, {}); + expect(model).toBe('MiniMax-M2.5'); }); it('falls back to hardcoded when neither flag nor config', () => {