rig_core/providers/
minimax.rs1use crate::client::{
25 self, BearerAuth, Capabilities, Capable, DebugExt, Nothing, Provider, ProviderBuilder,
26 ProviderClient,
27};
28use crate::http_client::{self, HttpClientExt};
29use crate::providers::anthropic::client::{
30 AnthropicBuilder as AnthropicCompatBuilder, AnthropicKey, finish_anthropic_builder,
31};
32
33pub const GLOBAL_API_BASE_URL: &str = "https://api.minimax.io/v1";
35pub const CHINA_API_BASE_URL: &str = "https://api.minimaxi.com/v1";
37pub const GLOBAL_ANTHROPIC_API_BASE_URL: &str = "https://api.minimax.io/anthropic";
39pub const CHINA_ANTHROPIC_API_BASE_URL: &str = "https://api.minimaxi.com/anthropic";
41
42pub const MINIMAX_M2_7: &str = "MiniMax-M2.7";
44pub const MINIMAX_M2_7_HIGHSPEED: &str = "MiniMax-M2.7-highspeed";
46pub const MINIMAX_M2_5: &str = "MiniMax-M2.5";
48pub const MINIMAX_M2_5_HIGHSPEED: &str = "MiniMax-M2.5-highspeed";
50pub const MINIMAX_M2_1: &str = "MiniMax-M2.1";
52pub const MINIMAX_M2_1_HIGHSPEED: &str = "MiniMax-M2.1-highspeed";
54pub const MINIMAX_M2: &str = "MiniMax-M2";
56
57#[derive(Debug, Default, Clone, Copy)]
58pub struct MiniMaxExt;
59
60#[derive(Debug, Default, Clone, Copy)]
61pub struct MiniMaxBuilder;
62
63#[derive(Debug, Default, Clone)]
64pub struct MiniMaxAnthropicBuilder {
65 anthropic: AnthropicCompatBuilder,
66}
67
68#[derive(Debug, Default, Clone, Copy)]
69pub struct MiniMaxAnthropicExt;
70
71type MiniMaxApiKey = BearerAuth;
72
73pub type Client<H = reqwest::Client> = client::Client<MiniMaxExt, H>;
74pub type ClientBuilder<H = crate::markers::Missing> =
75 client::ClientBuilder<MiniMaxBuilder, MiniMaxApiKey, H>;
76
77pub type AnthropicClient<H = reqwest::Client> = client::Client<MiniMaxAnthropicExt, H>;
78pub type AnthropicClientBuilder<H = crate::markers::Missing> =
79 client::ClientBuilder<MiniMaxAnthropicBuilder, AnthropicKey, H>;
80
81impl Provider for MiniMaxExt {
82 type Builder = MiniMaxBuilder;
83
84 const VERIFY_PATH: &'static str = "/models";
85}
86
87impl Provider for MiniMaxAnthropicExt {
88 type Builder = MiniMaxAnthropicBuilder;
89
90 const VERIFY_PATH: &'static str = "/v1/models";
91}
92
93impl<H> Capabilities<H> for MiniMaxExt {
94 type Completion = Capable<super::openai::completion::GenericCompletionModel<MiniMaxExt, H>>;
95 type Embeddings = Nothing;
96 type Transcription = Nothing;
97 type ModelListing = Nothing;
98 #[cfg(feature = "image")]
99 type ImageGeneration = Nothing;
100 #[cfg(feature = "audio")]
101 type AudioGeneration = Nothing;
102 type Rerank = Nothing;
103}
104
105impl<H> Capabilities<H> for MiniMaxAnthropicExt {
106 type Completion =
107 Capable<super::anthropic::completion::GenericCompletionModel<MiniMaxAnthropicExt, H>>;
108 type Embeddings = Nothing;
109 type Transcription = Nothing;
110 type ModelListing = Nothing;
111 #[cfg(feature = "image")]
112 type ImageGeneration = Nothing;
113 #[cfg(feature = "audio")]
114 type AudioGeneration = Nothing;
115 type Rerank = Nothing;
116}
117
118impl DebugExt for MiniMaxExt {}
119impl DebugExt for MiniMaxAnthropicExt {}
120
121impl ProviderBuilder for MiniMaxBuilder {
122 type Extension<H>
123 = MiniMaxExt
124 where
125 H: HttpClientExt;
126 type ApiKey = MiniMaxApiKey;
127
128 const BASE_URL: &'static str = GLOBAL_API_BASE_URL;
129
130 fn build<H>(
131 _builder: &client::ClientBuilder<Self, Self::ApiKey, H>,
132 ) -> http_client::Result<Self::Extension<H>>
133 where
134 H: HttpClientExt,
135 {
136 Ok(MiniMaxExt)
137 }
138}
139
140impl ProviderBuilder for MiniMaxAnthropicBuilder {
141 type Extension<H>
142 = MiniMaxAnthropicExt
143 where
144 H: HttpClientExt;
145 type ApiKey = AnthropicKey;
146
147 const BASE_URL: &'static str = GLOBAL_ANTHROPIC_API_BASE_URL;
148
149 fn build<H>(
150 _builder: &client::ClientBuilder<Self, Self::ApiKey, H>,
151 ) -> http_client::Result<Self::Extension<H>>
152 where
153 H: HttpClientExt,
154 {
155 Ok(MiniMaxAnthropicExt)
156 }
157
158 fn finish<H>(
159 &self,
160 builder: client::ClientBuilder<Self, AnthropicKey, H>,
161 ) -> http_client::Result<client::ClientBuilder<Self, AnthropicKey, H>> {
162 finish_anthropic_builder(&self.anthropic, builder)
163 }
164}
165
166impl super::anthropic::completion::AnthropicCompatibleProvider for MiniMaxAnthropicExt {
167 const PROVIDER_NAME: &'static str = "minimax";
168
169 fn default_max_tokens(_model: &str) -> Option<u64> {
170 Some(4096)
171 }
172}
173
174impl ProviderClient for Client {
175 type Input = MiniMaxApiKey;
176 type Error = crate::client::ProviderClientError;
177
178 fn from_env() -> Result<Self, Self::Error> {
179 let api_key = crate::client::required_env_var("MINIMAX_API_KEY")?;
180 let mut builder = Self::builder().api_key(api_key);
181
182 if let Some(base_url) = crate::client::optional_env_var("MINIMAX_API_BASE")? {
183 builder = builder.base_url(base_url);
184 }
185
186 builder.build().map_err(Into::into)
187 }
188
189 fn from_val(input: Self::Input) -> Result<Self, Self::Error> {
190 Self::new(input).map_err(Into::into)
191 }
192}
193
194impl ProviderClient for AnthropicClient {
195 type Input = String;
196 type Error = crate::client::ProviderClientError;
197
198 fn from_env() -> Result<Self, Self::Error> {
199 let api_key = crate::client::required_env_var("MINIMAX_API_KEY")?;
200 let mut builder = Self::builder().api_key(api_key);
201
202 if let Some(base_url) =
203 anthropic_base_override("MINIMAX_ANTHROPIC_API_BASE", "MINIMAX_API_BASE")?
204 {
205 builder = builder.base_url(base_url);
206 }
207
208 builder.build().map_err(Into::into)
209 }
210
211 fn from_val(input: Self::Input) -> Result<Self, Self::Error> {
212 Self::builder().api_key(input).build().map_err(Into::into)
213 }
214}
215
216fn anthropic_base_override(
217 primary_env: &'static str,
218 fallback_env: &'static str,
219) -> crate::client::ProviderClientResult<Option<String>> {
220 let primary = crate::client::optional_env_var(primary_env)?;
221 let fallback = crate::client::optional_env_var(fallback_env)?;
222
223 Ok(resolve_anthropic_base_override(
224 primary.as_deref(),
225 fallback.as_deref(),
226 ))
227}
228
229fn resolve_anthropic_base_override(
230 primary: Option<&str>,
231 fallback: Option<&str>,
232) -> Option<String> {
233 primary
234 .map(str::to_owned)
235 .or_else(|| fallback.and_then(normalize_anthropic_base_url))
236}
237
238fn normalize_anthropic_base_url(base_url: &str) -> Option<String> {
239 if base_url.contains("/anthropic") {
240 return Some(base_url.to_owned());
241 }
242
243 match base_url.trim_end_matches('/') {
244 GLOBAL_API_BASE_URL => Some(GLOBAL_ANTHROPIC_API_BASE_URL.to_owned()),
245 CHINA_API_BASE_URL => Some(CHINA_ANTHROPIC_API_BASE_URL.to_owned()),
246 _ => {
247 let mut url = url::Url::parse(base_url).ok()?;
248 if !matches!(url.path(), "/v1" | "/v1/") {
249 return None;
250 }
251 url.set_path("/anthropic");
252 Some(url.to_string())
253 }
254 }
255}
256
257impl<H> ClientBuilder<H> {
258 pub fn global(self) -> Self {
259 self.base_url(GLOBAL_API_BASE_URL)
260 }
261
262 pub fn china(self) -> Self {
263 self.base_url(CHINA_API_BASE_URL)
264 }
265}
266
267impl<H> AnthropicClientBuilder<H> {
268 pub fn global(self) -> Self {
269 self.base_url(GLOBAL_ANTHROPIC_API_BASE_URL)
270 }
271
272 pub fn china(self) -> Self {
273 self.base_url(CHINA_ANTHROPIC_API_BASE_URL)
274 }
275
276 pub fn anthropic_version(self, anthropic_version: &str) -> Self {
277 self.over_ext(|mut ext| {
278 ext.anthropic.anthropic_version = anthropic_version.into();
279 ext
280 })
281 }
282
283 pub fn anthropic_betas(self, anthropic_betas: &[&str]) -> Self {
284 self.over_ext(|mut ext| {
285 ext.anthropic
286 .anthropic_betas
287 .extend(anthropic_betas.iter().copied().map(String::from));
288 ext
289 })
290 }
291
292 pub fn anthropic_beta(self, anthropic_beta: &str) -> Self {
293 self.over_ext(|mut ext| {
294 ext.anthropic.anthropic_betas.push(anthropic_beta.into());
295 ext
296 })
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::{
303 CHINA_ANTHROPIC_API_BASE_URL, CHINA_API_BASE_URL, GLOBAL_ANTHROPIC_API_BASE_URL,
304 GLOBAL_API_BASE_URL, normalize_anthropic_base_url, resolve_anthropic_base_override,
305 };
306
307 #[test]
308 fn test_client_initialization() {
309 let _client = crate::providers::minimax::Client::new("dummy-key").expect("Client::new()");
310 let _client_from_builder = crate::providers::minimax::Client::builder()
311 .api_key("dummy-key")
312 .build()
313 .expect("Client::builder()");
314 let _anthropic_client = crate::providers::minimax::AnthropicClient::new("dummy-key")
315 .expect("AnthropicClient::new()");
316 let _anthropic_client_from_builder = crate::providers::minimax::AnthropicClient::builder()
317 .api_key("dummy-key")
318 .build()
319 .expect("AnthropicClient::builder()");
320 }
321
322 #[test]
323 fn normalize_openai_bases_to_anthropic_bases() {
324 assert_eq!(
325 normalize_anthropic_base_url(GLOBAL_API_BASE_URL).as_deref(),
326 Some(GLOBAL_ANTHROPIC_API_BASE_URL)
327 );
328 assert_eq!(
329 normalize_anthropic_base_url(CHINA_API_BASE_URL).as_deref(),
330 Some(CHINA_ANTHROPIC_API_BASE_URL)
331 );
332 assert_eq!(
333 normalize_anthropic_base_url("https://proxy.example.com/v1").as_deref(),
334 Some("https://proxy.example.com/anthropic")
335 );
336 }
337
338 #[test]
339 fn normalize_preserves_existing_anthropic_base() {
340 assert_eq!(
341 normalize_anthropic_base_url(CHINA_ANTHROPIC_API_BASE_URL).as_deref(),
342 Some(CHINA_ANTHROPIC_API_BASE_URL)
343 );
344 }
345
346 #[test]
347 fn anthropic_primary_override_wins() {
348 let override_url = resolve_anthropic_base_override(
349 Some("https://primary.example.com/anthropic"),
350 Some(CHINA_API_BASE_URL),
351 );
352
353 assert_eq!(
354 override_url.as_deref(),
355 Some("https://primary.example.com/anthropic")
356 );
357 }
358}