Skip to content

Commit 594d6b7

Browse files
committed
feat: add per-modality default model configuration
Users can now set default models in config.json for each modality, avoiding the need to pass --model every time. Config keys (hyphen aliases accepted): - default_text_model (default-text-model) - default_speech_model (default-speech-model) - default_video_model (default-video-model) - default_music_model (default-music-model) Resolution priority: 1. Explicit --model flag 2. Config file default 3. Hardcoded fallback Music cover is special: defaultMusicModel only applies if it's a valid cover model name (music-cover or music-cover-free), otherwise the key-type-based default is preserved. Tests added for config set/show, text chat, speech synthesize, video generate, music generate, and model resolution logic. 97 tests pass.
1 parent aff1e03 commit 594d6b7

21 files changed

Lines changed: 649 additions & 17 deletions

speech_2026-04-11-17-52-51.mp3

16.4 KB
Binary file not shown.

speech_2026-04-11-17-53-11.mp3

14.7 KB
Binary file not shown.

speech_2026-04-11-17-53-17.mp3

13.5 KB
Binary file not shown.

speech_2026-04-11-17-55-45.mp3

28.8 KB
Binary file not shown.

src/commands/config/set.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,22 @@ import { readConfigFile, writeConfigFile } from '../../config/loader';
66
import type { Config } from '../../config/schema';
77
import type { GlobalFlags } from '../../types/flags';
88

9-
const VALID_KEYS = ['region', 'base_url', 'output', 'timeout', 'api_key'];
9+
const VALID_KEYS = ['region', 'base_url', 'output', 'timeout', 'api_key', 'default_text_model', 'default_speech_model', 'default_video_model', 'default_music_model'];
10+
11+
// Allow hyphen-style keys (e.g. default-text-model → default_text_model)
12+
const KEY_ALIASES: Record<string, string> = {
13+
'default-text-model': 'default_text_model',
14+
'default-speech-model': 'default_speech_model',
15+
'default-video-model': 'default_video_model',
16+
'default-music-model': 'default_music_model',
17+
};
1018

1119
export default defineCommand({
1220
name: 'config set',
1321
description: 'Set a config value',
1422
usage: 'mmx config set --key <key> --value <value>',
1523
options: [
16-
{ flag: '--key <key>', description: 'Config key (region, base_url, output, timeout, api_key)' },
24+
{ flag: '--key <key>', description: 'Config key (region, base_url, output, timeout, api_key, default_text_model, default_speech_model, default_video_model, default_music_model)' },
1725
{ flag: '--value <value>', description: 'Value to set' },
1826
],
1927
examples: [
@@ -25,6 +33,9 @@ export default defineCommand({
2533
const key = flags.key as string | undefined;
2634
const value = flags.value as string | undefined;
2735

36+
// Resolve hyphen aliases to underscore keys
37+
const resolvedKey = KEY_ALIASES[key] || key;
38+
2839
if (!key || value === undefined) {
2940
throw new CLIError(
3041
'--key and --value are required.',
@@ -33,29 +44,29 @@ export default defineCommand({
3344
);
3445
}
3546

36-
if (!VALID_KEYS.includes(key)) {
47+
if (!VALID_KEYS.includes(resolvedKey)) {
3748
throw new CLIError(
3849
`Invalid config key "${key}". Valid keys: ${VALID_KEYS.join(', ')}`,
3950
ExitCode.USAGE,
4051
);
4152
}
4253

4354
// Validate specific values
44-
if (key === 'region' && !['global', 'cn'].includes(value)) {
55+
if (resolvedKey === 'region' && !['global', 'cn'].includes(value)) {
4556
throw new CLIError(
4657
`Invalid region "${value}". Valid values: global, cn`,
4758
ExitCode.USAGE,
4859
);
4960
}
5061

51-
if (key === 'output' && !['text', 'json'].includes(value)) {
62+
if (resolvedKey === 'output' && !['text', 'json'].includes(value)) {
5263
throw new CLIError(
5364
`Invalid output format "${value}". Valid values: text, json`,
5465
ExitCode.USAGE,
5566
);
5667
}
5768

58-
if (key === 'timeout') {
69+
if (resolvedKey === 'timeout') {
5970
const num = Number(value);
6071
if (isNaN(num) || num <= 0) {
6172
throw new CLIError(
@@ -68,16 +79,16 @@ export default defineCommand({
6879
const format = detectOutputFormat(config.output);
6980

7081
if (config.dryRun) {
71-
console.log(formatOutput({ would_set: { [key]: value } }, format));
82+
console.log(formatOutput({ would_set: { [resolvedKey]: value } }, format));
7283
return;
7384
}
7485

7586
const existing = readConfigFile() as Record<string, unknown>;
76-
existing[key] = key === 'timeout' ? Number(value) : value;
87+
existing[resolvedKey] = resolvedKey === 'timeout' ? Number(value) : value;
7788
await writeConfigFile(existing);
7889

7990
if (!config.quiet) {
80-
console.log(formatOutput({ [key]: existing[key] }, format));
91+
console.log(formatOutput({ [resolvedKey]: existing[resolvedKey] }, format));
8192
}
8293
},
8394
});

src/commands/config/show.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ export default defineCommand({
3131
result.api_key = maskToken(file.api_key);
3232
}
3333

34+
// Default models
35+
if (file.default_text_model) result.default_text_model = file.default_text_model;
36+
if (file.default_speech_model) result.default_speech_model = file.default_speech_model;
37+
if (file.default_video_model) result.default_video_model = file.default_video_model;
38+
if (file.default_music_model) result.default_music_model = file.default_music_model;
39+
3440
console.log(formatOutput(result, format));
3541
},
3642
});

src/commands/music/models.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,17 @@ export function isCodingPlan(config: Config): boolean {
1010
}
1111

1212
export function musicGenerateModel(config: Config): string {
13+
// Config default > key-type-based default
14+
if (config.defaultMusicModel) return config.defaultMusicModel;
1315
return isCodingPlan(config) ? 'music-2.6' : 'music-2.6-free';
1416
}
1517

18+
const VALID_COVER_MODELS = new Set(['music-cover', 'music-cover-free']);
19+
1620
export function musicCoverModel(config: Config): string {
21+
// Config default (only if it's a valid cover model) > key-type-based default
22+
if (config.defaultMusicModel && VALID_COVER_MODELS.has(config.defaultMusicModel)) {
23+
return config.defaultMusicModel;
24+
}
1725
return isCodingPlan(config) ? 'music-cover' : 'music-cover-free';
1826
}

src/commands/speech/synthesize.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ export default defineCommand({
5555
);
5656
}
5757

58-
const model = (flags.model as string) || 'speech-2.8-hd';
58+
const model = (flags.model as string)
59+
|| config.defaultSpeechModel
60+
|| 'speech-2.8-hd';
5961
const voice = (flags.voice as string) || 'English_expressive_narrator';
6062
const ts = new Date().toISOString().slice(0, 19).replace(/[T:]/g, '-');
6163
const ext = (flags.format as string) || 'mp3';

src/commands/text/chat.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ export default defineCommand({
117117
}
118118
}
119119

120-
const model = (flags.model as string) || 'MiniMax-M2.7';
120+
const model = (flags.model as string)
121+
|| config.defaultTextModel
122+
|| 'MiniMax-M2.7';
121123
const shouldStream = flags.stream === true || (flags.stream === undefined && process.stdout.isTTY);
122124
const format = detectOutputFormat(config.output);
123125

src/commands/video/generate.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,17 @@ export default defineCommand({
7171
);
7272
}
7373

74-
// Determine model: explicit --model overrides auto-switch
74+
// Determine model: explicit --model > auto-switch > config default > hardcoded
7575
const explicitModel = flags.model as string | undefined;
76-
let model = explicitModel || 'MiniMax-Hailuo-2.3';
77-
if (!explicitModel && flags.lastFrame) {
76+
let model: string;
77+
if (explicitModel) {
78+
model = explicitModel;
79+
} else if (flags.lastFrame) {
7880
model = 'MiniMax-Hailuo-02';
79-
} else if (!explicitModel && flags.subjectImage) {
81+
} else if (flags.subjectImage) {
8082
model = 'S2V-01';
83+
} else {
84+
model = config.defaultVideoModel || 'MiniMax-Hailuo-2.3';
8185
}
8286
const format = detectOutputFormat(config.output);
8387

0 commit comments

Comments
 (0)