Skip to main content

rig_core/providers/
minimax.rs

1//! MiniMax's endpoints and model identifiers.
2//!
3//! Global and China configurations support chat-completions and Messages wires,
4//! using `MINIMAX_API_KEY`. [`from_env`] and [`new`] build a client on the
5//! chat-completions [`MINIMAX`](crate::providers::openai::wire::MINIMAX)
6//! dialect; [`anthropic_from_env`] and [`anthropic_new`] build a Messages-format
7//! client. The chat-completions client also generates images and speech, and
8//! `voice_clone` clones a voice from uploaded audio, and `.responses(model)`
9//! speaks MiniMax's Responses API on either endpoint.
10//!
11//! ```no_run
12//! use rig_core::providers::minimax;
13//!
14//! # fn run() -> Result<(), Box<dyn std::error::Error>> {
15//! let model = minimax::from_env()?.chat(minimax::MINIMAX_M3);
16//! # let _ = model;
17//! # Ok(())
18//! # }
19//! ```
20
21/// Global OpenAI-compatible base URL.
22pub const GLOBAL_API_BASE_URL: &str = "https://api.minimax.io/v1";
23/// China OpenAI-compatible base URL.
24pub const CHINA_API_BASE_URL: &str = "https://api.minimax.cn/v1";
25/// Global Anthropic-compatible base URL.
26pub const GLOBAL_ANTHROPIC_API_BASE_URL: &str = "https://api.minimax.io/anthropic";
27/// China Anthropic-compatible base URL.
28pub const CHINA_ANTHROPIC_API_BASE_URL: &str = "https://api.minimax.cn/anthropic";
29
30/// `MiniMax-M3`
31pub const MINIMAX_M3: &str = "MiniMax-M3";
32/// `MiniMax-M2.7`
33pub const MINIMAX_M2_7: &str = "MiniMax-M2.7";
34/// `MiniMax-M2.7-highspeed`
35pub const MINIMAX_M2_7_HIGHSPEED: &str = "MiniMax-M2.7-highspeed";
36/// `MiniMax-M2.5`
37pub const MINIMAX_M2_5: &str = "MiniMax-M2.5";
38/// `MiniMax-M2.5-highspeed`
39pub const MINIMAX_M2_5_HIGHSPEED: &str = "MiniMax-M2.5-highspeed";
40
41/// The MiniMax model families whose options differ on every wire.
42#[derive(Clone, Copy, PartialEq, Eq)]
43pub(crate) enum Family {
44    M31Flash,
45    M3,
46    Other,
47}
48
49/// `model`'s family, matched in any case; `minimax-m3.1` wins over `minimax-m3`.
50pub(crate) fn family(model: &str) -> Family {
51    let model = model.to_ascii_lowercase();
52    match model.as_str() {
53        id if id.starts_with("minimax-m3.1") => Family::M31Flash,
54        id if id.starts_with("minimax-m3") => Family::M3,
55        _ => Family::Other,
56    }
57}
58
59/// Whether MiniMax `model` reads images: M2 models are text only, and M3
60/// reads images. The name is matched in any case, as gateways lowercase it.
61/// Every wire MiniMax serves applies this rule to an id the catalog does not
62/// list.
63pub(crate) fn reads_images(model: &str) -> bool {
64    !model.to_ascii_lowercase().starts_with("minimax-m2")
65}
66
67#[cfg(feature = "audio")]
68pub mod audio_generation;
69pub mod extension;
70#[cfg(feature = "image")]
71pub mod image_generation;
72#[cfg(feature = "audio")]
73pub mod voice_clone;
74
75#[cfg(feature = "audio")]
76pub use audio_generation::*;
77#[cfg(feature = "image")]
78pub use image_generation::*;
79#[cfg(feature = "audio")]
80pub use voice_clone::voice_cloning;
81
82/// The provider key: the dialect name, a reply's `Origin::provider` and
83/// the typed provider-options key.
84pub const PROVIDER_NAME: &str = "minimax";
85
86crate::client::macros::openai_vendor!(crate::providers::openai::wire::MINIMAX, "MiniMax");
87crate::client::macros::anthropic_vendor!(
88    crate::providers::anthropic::wire::MINIMAX,
89    "MiniMax",
90    anthropic_from_env,
91    anthropic_new
92);
93
94/// Decode a MiniMax media reply once its `base_resp` status reports success.
95/// MiniMax can answer a failure with HTTP 200, so a nonzero `status_code` is
96/// the body verbatim as [`ProviderError::ProviderResponse`].
97#[cfg(any(feature = "image", feature = "audio"))]
98pub(crate) fn decode_reply<T: serde::de::DeserializeOwned>(
99    body: &[u8],
100) -> Result<T, crate::error::ProviderError> {
101    #[derive(serde::Deserialize)]
102    struct Status {
103        #[serde(default)]
104        base_resp: Option<BaseResp>,
105    }
106    #[derive(serde::Deserialize)]
107    struct BaseResp {
108        #[serde(default)]
109        status_code: i64,
110    }
111
112    let status: Status = serde_json::from_slice(body)?;
113    if status
114        .base_resp
115        .is_some_and(|status| status.status_code != 0)
116    {
117        return Err(crate::error::ProviderError::from_provider_body(
118            String::from_utf8_lossy(body),
119        ));
120    }
121    Ok(serde_json::from_slice(body)?)
122}
123
124#[cfg(all(test, any(feature = "image", feature = "audio")))]
125mod tests;