Skip to content

Commit 974a1e4

Browse files
committed
Merge pull request #4354 from octo-patch/octo/20260713-minimax-provider-recvoNohdpT5QV-v2
feat: add MiniMax Messages provider support Resolved web/lib/facts.generated.ts by regenerating from the merged workspace (npm run prebuild); the provider list and tool counts merged cleanly.
2 parents cbfe478 + 47163da commit 974a1e4

24 files changed

Lines changed: 666 additions & 62 deletions

crates/agent/src/lib.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,28 @@ impl Default for ModelRegistry {
776776
supports_tools: true,
777777
supports_reasoning: true,
778778
},
779+
ModelInfo {
780+
id: "MiniMax-M3".to_string(),
781+
provider: ProviderKind::MinimaxAnthropic,
782+
aliases: vec![
783+
"minimax-anthropic".to_string(),
784+
"minimax-anthropic-m3".to_string(),
785+
"minimax-m3".to_string(),
786+
],
787+
supports_tools: true,
788+
supports_reasoning: true,
789+
},
790+
ModelInfo {
791+
id: "MiniMax-M2.7".to_string(),
792+
provider: ProviderKind::MinimaxAnthropic,
793+
aliases: vec![
794+
"minimax-anthropic-m2.7".to_string(),
795+
"minimax-anthropic-m2-7".to_string(),
796+
"minimax-m2.7".to_string(),
797+
],
798+
supports_tools: true,
799+
supports_reasoning: true,
800+
},
779801
ModelInfo {
780802
id: "MiniMax-M2.7-highspeed".to_string(),
781803
provider: ProviderKind::Minimax,
@@ -1561,6 +1583,7 @@ mod tests {
15611583
(ProviderKind::Zai, "GLM-5.2"),
15621584
(ProviderKind::Stepfun, "step-3.7-flash"),
15631585
(ProviderKind::Minimax, "MiniMax-M2.1"),
1586+
(ProviderKind::MinimaxAnthropic, "MiniMax-M3"),
15641587
(ProviderKind::Openmodel, "deepseek-v4-flash"),
15651588
(ProviderKind::Meta, "muse-spark-1.1"),
15661589
(ProviderKind::Xai, "grok-4.5"),
@@ -1661,6 +1684,25 @@ mod tests {
16611684
}
16621685
}
16631686

1687+
#[test]
1688+
fn minimax_anthropic_models_resolve_when_provider_hinted() {
1689+
let registry = ModelRegistry::default();
1690+
1691+
for (alias, expected) in [
1692+
("minimax-anthropic", "MiniMax-M3"),
1693+
("minimax-m3", "MiniMax-M3"),
1694+
("minimax-m2.7", "MiniMax-M2.7"),
1695+
] {
1696+
let resolved = registry.resolve(Some(alias), Some(ProviderKind::MinimaxAnthropic));
1697+
1698+
assert_eq!(resolved.resolved.provider, ProviderKind::MinimaxAnthropic);
1699+
assert_eq!(resolved.resolved.id, expected);
1700+
assert!(!resolved.used_fallback);
1701+
assert!(resolved.resolved.supports_tools);
1702+
assert!(resolved.resolved.supports_reasoning);
1703+
}
1704+
}
1705+
16641706
#[test]
16651707
fn deepseek_v4_flash_alias_resolves_to_openrouter_when_provider_hinted() {
16661708
let registry = ModelRegistry::default();

crates/cli/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ enum ProviderArg {
6161
Zai,
6262
Stepfun,
6363
Minimax,
64+
#[value(
65+
alias = "minimax_anthropic",
66+
alias = "mini-max-anthropic",
67+
alias = "mini_max_anthropic"
68+
)]
69+
MinimaxAnthropic,
6470
#[value(alias = "deep-infra", alias = "deep_infra")]
6571
Deepinfra,
6672
#[value(alias = "fugu", alias = "sakana-ai", alias = "sakana_ai")]
@@ -107,6 +113,7 @@ impl From<ProviderArg> for ProviderKind {
107113
ProviderArg::Zai => ProviderKind::Zai,
108114
ProviderArg::Stepfun => ProviderKind::Stepfun,
109115
ProviderArg::Minimax => ProviderKind::Minimax,
116+
ProviderArg::MinimaxAnthropic => ProviderKind::MinimaxAnthropic,
110117
ProviderArg::Deepinfra => ProviderKind::Deepinfra,
111118
ProviderArg::Sakana => ProviderKind::Sakana,
112119
ProviderArg::LongCat => ProviderKind::LongCat,
@@ -4336,6 +4343,8 @@ mod tests {
43364343
("zai", ProviderArg::Zai),
43374344
("stepfun", ProviderArg::Stepfun),
43384345
("minimax", ProviderArg::Minimax),
4346+
("minimax-anthropic", ProviderArg::MinimaxAnthropic),
4347+
("minimax_anthropic", ProviderArg::MinimaxAnthropic),
43394348
("deepinfra", ProviderArg::Deepinfra),
43404349
("deep-infra", ProviderArg::Deepinfra),
43414350
("siliconflow-cn", ProviderArg::SiliconflowCn),

crates/config/assets/models_dev.bundled.json

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"source": "Compact offline seed of verified in-repo defaults (context/output from crates/tui/src/models.rs; USD pricing from crates/tui/src/pricing.rs) for providers CodeWhale ships with. It is intentionally smaller than a full Models.dev dump; live refresh supersedes these rows on (provider, wire_model_id) identity.",
77
"honesty": "Pricing is intentionally OMITTED where the repo does not publish a trustworthy per-token rate: DeepSeek-native rows (priced via the time-aware DeepSeek table elsewhere, kept UnknownOrStale at the route layer), aggregator-hosted DeepSeek rows (aggregator account terms, not DeepSeek Platform pricing), and Xiaomi MiMo rows (published PAYG rates apply only to sk- pay-as-you-go keys; the catalog cannot distinguish that billing surface from credit/quota Token Plan keys, so MiMo stays unpriced). Absent pricing surfaces as PricingSku::UnknownOrStale, never a fabricated zero.",
88
"default_rows": "Each provider's `default: true` wire id equals that provider's built-in DEFAULT_*_MODEL so RouteResolver::new() and the descriptor stay in agreement when offline.",
9-
"coverage": "14 providers, 42 chat offerings (offline seed only)."
9+
"coverage": "15 providers, 44 chat offerings (offline seed only)."
1010
},
1111
"models": {
1212
"deepseek-v4-pro": {
@@ -144,20 +144,63 @@
144144
"name": "MiniMax M3",
145145
"family": "minimax",
146146
"default": true,
147+
"attachment": true,
147148
"reasoning": true,
149+
"reasoning_options": [
150+
{ "type": "thinking", "values": ["adaptive", "disabled"], "default": "adaptive" }
151+
],
152+
"tool_call": true,
153+
"modalities": { "input": ["text", "image", "video"], "output": ["text"] },
154+
"limit": { "context": 1000000, "output": 524288 }
155+
},
156+
"MiniMax-M2.7": {
157+
"id": "MiniMax-M2.7",
158+
"name": "MiniMax M2.7",
159+
"family": "minimax",
160+
"reasoning": true,
161+
"reasoning_options": [
162+
{ "type": "thinking", "values": ["always_on"], "default": "always_on" }
163+
],
148164
"tool_call": true,
149165
"modalities": { "input": ["text"], "output": ["text"] },
150-
"limit": { "context": 1000000, "output": 524288 },
151-
"cost": { "input": 0.30, "output": 1.20, "cache_read": 0.06 }
166+
"limit": { "context": 204800, "output": 204800 },
167+
"cost": { "input": 0.30, "output": 1.20, "cache_read": 0.06, "cache_write": 0.375 }
168+
}
169+
}
170+
},
171+
"minimax-anthropic": {
172+
"id": "minimax-anthropic",
173+
"name": "MiniMax (Anthropic-compatible)",
174+
"api": "https://api.minimax.io/anthropic",
175+
"npm": "@ai-sdk/anthropic",
176+
"env": ["MINIMAX_API_KEY"],
177+
"models": {
178+
"MiniMax-M3": {
179+
"id": "MiniMax-M3",
180+
"name": "MiniMax M3",
181+
"family": "minimax",
182+
"default": true,
183+
"attachment": true,
184+
"reasoning": true,
185+
"reasoning_options": [
186+
{ "type": "thinking", "values": ["adaptive", "disabled"], "default": "disabled" }
187+
],
188+
"tool_call": true,
189+
"modalities": { "input": ["text", "image", "video"], "output": ["text"] },
190+
"limit": { "context": 1000000, "output": 524288 }
152191
},
153-
"minimax-m2.7": {
154-
"id": "minimax-m2.7",
192+
"MiniMax-M2.7": {
193+
"id": "MiniMax-M2.7",
155194
"name": "MiniMax M2.7",
156195
"family": "minimax",
157196
"reasoning": true,
197+
"reasoning_options": [
198+
{ "type": "thinking", "values": ["always_on"], "default": "always_on" }
199+
],
158200
"tool_call": true,
159201
"modalities": { "input": ["text"], "output": ["text"] },
160-
"limit": { "context": 204800, "output": 204800 }
202+
"limit": { "context": 204800, "output": 204800 },
203+
"cost": { "input": 0.30, "output": 1.20, "cache_read": 0.06, "cache_write": 0.375 }
161204
}
162205
}
163206
},

crates/config/src/catalog/tests.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,39 @@ fn bundled_asset_yields_real_chat_offerings_for_key_models() {
587587
let kimi = find(&rows, "moonshot", "kimi-k2.7-code");
588588
assert_eq!(kimi.limit.as_ref().and_then(|l| l.context), Some(262_144));
589589

590+
let minimax_m3 = find(&rows, "minimax-anthropic", "MiniMax-M3");
591+
assert_eq!(
592+
minimax_m3.limit.as_ref().and_then(|limit| limit.context),
593+
Some(1_000_000)
594+
);
595+
let input_modalities = minimax_m3
596+
.modalities
597+
.as_ref()
598+
.expect("M3 modalities")
599+
.input
600+
.iter()
601+
.map(String::as_str)
602+
.collect::<Vec<_>>();
603+
assert_eq!(input_modalities, ["text", "image", "video"]);
604+
assert_eq!(
605+
minimax_m3.reasoning_options[0]
606+
.get("default")
607+
.and_then(serde_json::Value::as_str),
608+
Some("disabled")
609+
);
610+
611+
let minimax_m2_7 = find(&rows, "minimax-anthropic", "MiniMax-M2.7");
612+
assert_eq!(
613+
minimax_m2_7.limit.as_ref().and_then(|limit| limit.context),
614+
Some(204_800)
615+
);
616+
assert_eq!(
617+
minimax_m2_7.reasoning_options[0]
618+
.get("default")
619+
.and_then(serde_json::Value::as_str),
620+
Some("always_on")
621+
);
622+
590623
// Audio/TTS rows are absent (the asset only ships chat models, but assert
591624
// the filter contract anyway).
592625
assert!(
@@ -629,6 +662,18 @@ fn bundled_asset_pricing_is_honest() {
629662
assert_eq!(cost.input, Some(1.40));
630663
assert_eq!(cost.output, Some(4.40));
631664
assert_eq!(cost.cache_read, Some(0.26));
665+
666+
// M3 has input-length and service tiers that the flat catalog cost shape
667+
// cannot represent, so the bundled route row stays honestly unpriced.
668+
let minimax_m3 = find(&rows, "minimax-anthropic", "MiniMax-M3");
669+
assert!(minimax_m3.cost.is_none());
670+
671+
let minimax_m2_7 = find(&rows, "minimax-anthropic", "MiniMax-M2.7");
672+
let cost = minimax_m2_7.cost.as_ref().expect("M2.7 is priced");
673+
assert_eq!(cost.input, Some(0.30));
674+
assert_eq!(cost.output, Some(1.20));
675+
assert_eq!(cost.cache_read, Some(0.06));
676+
assert_eq!(cost.cache_write, Some(0.375));
632677
}
633678

634679
#[test]

crates/config/src/lib.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,15 @@ pub struct ProvidersToml {
224224
alias = "minimax"
225225
)]
226226
pub minimax: ProviderConfigToml,
227+
#[serde(
228+
default,
229+
skip_serializing_if = "ProviderConfigToml::is_empty",
230+
alias = "minimax-anthropic",
231+
alias = "minimaxAnthropic",
232+
alias = "mini-max-anthropic",
233+
alias = "mini_max_anthropic"
234+
)]
235+
pub minimax_anthropic: ProviderConfigToml,
227236
#[serde(
228237
default,
229238
skip_serializing_if = "ProviderConfigToml::is_empty",
@@ -369,6 +378,7 @@ impl ProvidersToml {
369378
ProviderKind::Zai => &self.zai,
370379
ProviderKind::Stepfun => &self.stepfun,
371380
ProviderKind::Minimax => &self.minimax,
381+
ProviderKind::MinimaxAnthropic => &self.minimax_anthropic,
372382
ProviderKind::Deepinfra => &self.deepinfra,
373383
ProviderKind::Sakana => &self.sakana,
374384
ProviderKind::LongCat => &self.longcat,
@@ -407,6 +417,7 @@ impl ProvidersToml {
407417
ProviderKind::Zai => &mut self.zai,
408418
ProviderKind::Stepfun => &mut self.stepfun,
409419
ProviderKind::Minimax => &mut self.minimax,
420+
ProviderKind::MinimaxAnthropic => &mut self.minimax_anthropic,
410421
ProviderKind::Deepinfra => &mut self.deepinfra,
411422
ProviderKind::Sakana => &mut self.sakana,
412423
ProviderKind::LongCat => &mut self.longcat,
@@ -2260,6 +2271,7 @@ impl ConfigToml {
22602271
ProviderKind::Zai => DEFAULT_ZAI_BASE_URL.to_string(),
22612272
ProviderKind::Stepfun => DEFAULT_STEPFUN_BASE_URL.to_string(),
22622273
ProviderKind::Minimax => DEFAULT_MINIMAX_BASE_URL.to_string(),
2274+
ProviderKind::MinimaxAnthropic => DEFAULT_MINIMAX_ANTHROPIC_BASE_URL.to_string(),
22632275
ProviderKind::Deepinfra => DEFAULT_DEEPINFRA_BASE_URL.to_string(),
22642276
ProviderKind::Sakana => DEFAULT_SAKANA_BASE_URL.to_string(),
22652277
ProviderKind::LongCat => DEFAULT_LONGCAT_BASE_URL.to_string(),
@@ -2495,8 +2507,10 @@ fn normalize_model_for_provider(provider: ProviderKind, model: &str) -> String {
24952507
{
24962508
return canonical.to_string();
24972509
}
2498-
if matches!(provider, ProviderKind::Minimax)
2499-
&& let Some(canonical) = canonical_minimax_model_id(model)
2510+
if matches!(
2511+
provider,
2512+
ProviderKind::Minimax | ProviderKind::MinimaxAnthropic
2513+
) && let Some(canonical) = canonical_minimax_model_id(model)
25002514
{
25012515
return canonical.to_string();
25022516
}
@@ -2515,6 +2529,7 @@ fn normalize_model_for_provider(provider: ProviderKind, model: &str) -> String {
25152529
| ProviderKind::Zai
25162530
| ProviderKind::Stepfun
25172531
| ProviderKind::Minimax
2532+
| ProviderKind::MinimaxAnthropic
25182533
| ProviderKind::Qianfan
25192534
| ProviderKind::Ollama
25202535
| ProviderKind::Meta
@@ -2840,7 +2855,7 @@ fn default_model_for_provider(provider: ProviderKind) -> &'static str {
28402855
ProviderKind::Openmodel => DEFAULT_OPENMODEL_MODEL,
28412856
ProviderKind::Zai => DEFAULT_ZAI_MODEL,
28422857
ProviderKind::Stepfun => DEFAULT_STEPFUN_MODEL,
2843-
ProviderKind::Minimax => DEFAULT_MINIMAX_MODEL,
2858+
ProviderKind::Minimax | ProviderKind::MinimaxAnthropic => DEFAULT_MINIMAX_MODEL,
28442859
ProviderKind::Deepinfra => DEFAULT_DEEPINFRA_MODEL,
28452860
ProviderKind::Sakana => DEFAULT_SAKANA_MODEL,
28462861
ProviderKind::LongCat => DEFAULT_LONGCAT_MODEL,
@@ -2880,6 +2895,7 @@ fn default_base_url_for_provider(provider: ProviderKind) -> &'static str {
28802895
ProviderKind::Zai => DEFAULT_ZAI_BASE_URL,
28812896
ProviderKind::Stepfun => DEFAULT_STEPFUN_BASE_URL,
28822897
ProviderKind::Minimax => DEFAULT_MINIMAX_BASE_URL,
2898+
ProviderKind::MinimaxAnthropic => DEFAULT_MINIMAX_ANTHROPIC_BASE_URL,
28832899
ProviderKind::Deepinfra => DEFAULT_DEEPINFRA_BASE_URL,
28842900
ProviderKind::Sakana => DEFAULT_SAKANA_BASE_URL,
28852901
ProviderKind::LongCat => DEFAULT_LONGCAT_BASE_URL,
@@ -4423,6 +4439,7 @@ struct EnvRuntimeOverrides {
44234439
stepfun_base_url: Option<String>,
44244440
stepfun_model: Option<String>,
44254441
minimax_base_url: Option<String>,
4442+
minimax_anthropic_base_url: Option<String>,
44264443
minimax_model: Option<String>,
44274444
deepinfra_base_url: Option<String>,
44284445
deepinfra_model: Option<String>,
@@ -4657,6 +4674,9 @@ impl EnvRuntimeOverrides {
46574674
minimax_base_url: std::env::var("MINIMAX_BASE_URL")
46584675
.ok()
46594676
.filter(|v| !v.trim().is_empty()),
4677+
minimax_anthropic_base_url: std::env::var("MINIMAX_ANTHROPIC_BASE_URL")
4678+
.ok()
4679+
.filter(|v| !v.trim().is_empty()),
46604680
minimax_model: std::env::var("MINIMAX_MODEL")
46614681
.ok()
46624682
.filter(|v| !v.trim().is_empty()),
@@ -4749,6 +4769,7 @@ impl EnvRuntimeOverrides {
47494769
ProviderKind::Zai => self.zai_base_url.clone(),
47504770
ProviderKind::Stepfun => self.stepfun_base_url.clone(),
47514771
ProviderKind::Minimax => self.minimax_base_url.clone(),
4772+
ProviderKind::MinimaxAnthropic => self.minimax_anthropic_base_url.clone(),
47524773
ProviderKind::Deepinfra => self.deepinfra_base_url.clone(),
47534774
ProviderKind::Sakana => self.sakana_base_url.clone(),
47544775
ProviderKind::LongCat => self.longcat_base_url.clone(),
@@ -4781,7 +4802,7 @@ impl EnvRuntimeOverrides {
47814802
ProviderKind::Openmodel => self.openmodel_model.clone(),
47824803
ProviderKind::Zai => self.zai_model.clone(),
47834804
ProviderKind::Stepfun => self.stepfun_model.clone(),
4784-
ProviderKind::Minimax => self.minimax_model.clone(),
4805+
ProviderKind::Minimax | ProviderKind::MinimaxAnthropic => self.minimax_model.clone(),
47854806
ProviderKind::Deepinfra => self.deepinfra_model.clone(),
47864807
ProviderKind::Sakana => self.sakana_model.clone(),
47874808
ProviderKind::LongCat => self.longcat_model.clone(),

0 commit comments

Comments
 (0)