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 super::openai::completion::OpenAICompatibleProvider for MiniMaxExt {
122 const PROVIDER_NAME: &'static str = "minimax";
123
124 type StreamingUsage = super::openai::Usage;
125
126 type Response = super::openai::CompletionResponse;
127}
128
129impl ProviderBuilder for MiniMaxBuilder {
130 type Extension<H>
131 = MiniMaxExt
132 where
133 H: HttpClientExt;
134 type ApiKey = MiniMaxApiKey;
135
136 const BASE_URL: &'static str = GLOBAL_API_BASE_URL;
137
138 fn build<H>(
139 _builder: &client::ClientBuilder<Self, Self::ApiKey, H>,
140 ) -> http_client::Result<Self::Extension<H>>
141 where
142 H: HttpClientExt,
143 {
144 Ok(MiniMaxExt)
145 }
146}
147
148impl ProviderBuilder for MiniMaxAnthropicBuilder {
149 type Extension<H>
150 = MiniMaxAnthropicExt
151 where
152 H: HttpClientExt;
153 type ApiKey = AnthropicKey;
154
155 const BASE_URL: &'static str = GLOBAL_ANTHROPIC_API_BASE_URL;
156
157 fn build<H>(
158 _builder: &client::ClientBuilder<Self, Self::ApiKey, H>,
159 ) -> http_client::Result<Self::Extension<H>>
160 where
161 H: HttpClientExt,
162 {
163 Ok(MiniMaxAnthropicExt)
164 }
165
166 fn finish<H>(
167 &self,
168 builder: client::ClientBuilder<Self, AnthropicKey, H>,
169 ) -> http_client::Result<client::ClientBuilder<Self, AnthropicKey, H>> {
170 finish_anthropic_builder(&self.anthropic, builder)
171 }
172}
173
174impl super::anthropic::completion::AnthropicCompatibleProvider for MiniMaxAnthropicExt {
175 const PROVIDER_NAME: &'static str = "minimax";
176
177 fn default_max_tokens(_model: &str) -> Option<u64> {
178 Some(4096)
179 }
180}
181
182impl ProviderClient for Client {
183 type Input = MiniMaxApiKey;
184 type Error = crate::client::ProviderClientError;
185
186 fn from_env() -> Result<Self, Self::Error> {
187 let api_key = crate::client::required_env_var("MINIMAX_API_KEY")?;
188 let mut builder = Self::builder().api_key(api_key);
189
190 if let Some(base_url) = crate::client::optional_env_var("MINIMAX_API_BASE")? {
191 builder = builder.base_url(base_url);
192 }
193
194 builder.build().map_err(Into::into)
195 }
196
197 fn from_val(input: Self::Input) -> Result<Self, Self::Error> {
198 Self::new(input).map_err(Into::into)
199 }
200}
201
202impl ProviderClient for AnthropicClient {
203 type Input = String;
204 type Error = crate::client::ProviderClientError;
205
206 fn from_env() -> Result<Self, Self::Error> {
207 let api_key = crate::client::required_env_var("MINIMAX_API_KEY")?;
208 let mut builder = Self::builder().api_key(api_key);
209
210 if let Some(base_url) =
211 anthropic_base_override("MINIMAX_ANTHROPIC_API_BASE", "MINIMAX_API_BASE")?
212 {
213 builder = builder.base_url(base_url);
214 }
215
216 builder.build().map_err(Into::into)
217 }
218
219 fn from_val(input: Self::Input) -> Result<Self, Self::Error> {
220 Self::builder().api_key(input).build().map_err(Into::into)
221 }
222}
223
224fn anthropic_base_override(
225 primary_env: &'static str,
226 fallback_env: &'static str,
227) -> crate::client::ProviderClientResult<Option<String>> {
228 let primary = crate::client::optional_env_var(primary_env)?;
229 let fallback = crate::client::optional_env_var(fallback_env)?;
230
231 Ok(resolve_anthropic_base_override(
232 primary.as_deref(),
233 fallback.as_deref(),
234 ))
235}
236
237fn resolve_anthropic_base_override(
238 primary: Option<&str>,
239 fallback: Option<&str>,
240) -> Option<String> {
241 primary
242 .map(str::to_owned)
243 .or_else(|| fallback.and_then(normalize_anthropic_base_url))
244}
245
246fn normalize_anthropic_base_url(base_url: &str) -> Option<String> {
247 if base_url.contains("/anthropic") {
248 return Some(base_url.to_owned());
249 }
250
251 match base_url.trim_end_matches('/') {
252 GLOBAL_API_BASE_URL => Some(GLOBAL_ANTHROPIC_API_BASE_URL.to_owned()),
253 CHINA_API_BASE_URL => Some(CHINA_ANTHROPIC_API_BASE_URL.to_owned()),
254 _ => {
255 let mut url = url::Url::parse(base_url).ok()?;
256 if !matches!(url.path(), "/v1" | "/v1/") {
257 return None;
258 }
259 url.set_path("/anthropic");
260 Some(url.to_string())
261 }
262 }
263}
264
265impl<H> ClientBuilder<H> {
266 pub fn global(self) -> Self {
267 self.base_url(GLOBAL_API_BASE_URL)
268 }
269
270 pub fn china(self) -> Self {
271 self.base_url(CHINA_API_BASE_URL)
272 }
273}
274
275impl<H> AnthropicClientBuilder<H> {
276 pub fn global(self) -> Self {
277 self.base_url(GLOBAL_ANTHROPIC_API_BASE_URL)
278 }
279
280 pub fn china(self) -> Self {
281 self.base_url(CHINA_ANTHROPIC_API_BASE_URL)
282 }
283
284 pub fn anthropic_version(self, anthropic_version: &str) -> Self {
285 self.over_ext(|mut ext| {
286 ext.anthropic.anthropic_version = anthropic_version.into();
287 ext
288 })
289 }
290
291 pub fn anthropic_betas(self, anthropic_betas: &[&str]) -> Self {
292 self.over_ext(|mut ext| {
293 ext.anthropic
294 .anthropic_betas
295 .extend(anthropic_betas.iter().copied().map(String::from));
296 ext
297 })
298 }
299
300 pub fn anthropic_beta(self, anthropic_beta: &str) -> Self {
301 self.over_ext(|mut ext| {
302 ext.anthropic.anthropic_betas.push(anthropic_beta.into());
303 ext
304 })
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::{
311 CHINA_ANTHROPIC_API_BASE_URL, CHINA_API_BASE_URL, GLOBAL_ANTHROPIC_API_BASE_URL,
312 GLOBAL_API_BASE_URL, normalize_anthropic_base_url, resolve_anthropic_base_override,
313 };
314
315 #[test]
316 fn test_client_initialization() {
317 let _client = crate::providers::minimax::Client::new("dummy-key").expect("Client::new()");
318 let _client_from_builder = crate::providers::minimax::Client::builder()
319 .api_key("dummy-key")
320 .build()
321 .expect("Client::builder()");
322 let _anthropic_client = crate::providers::minimax::AnthropicClient::new("dummy-key")
323 .expect("AnthropicClient::new()");
324 let _anthropic_client_from_builder = crate::providers::minimax::AnthropicClient::builder()
325 .api_key("dummy-key")
326 .build()
327 .expect("AnthropicClient::builder()");
328 }
329
330 #[test]
331 fn normalize_openai_bases_to_anthropic_bases() {
332 assert_eq!(
333 normalize_anthropic_base_url(GLOBAL_API_BASE_URL).as_deref(),
334 Some(GLOBAL_ANTHROPIC_API_BASE_URL)
335 );
336 assert_eq!(
337 normalize_anthropic_base_url(CHINA_API_BASE_URL).as_deref(),
338 Some(CHINA_ANTHROPIC_API_BASE_URL)
339 );
340 assert_eq!(
341 normalize_anthropic_base_url("https://proxy.example.com/v1").as_deref(),
342 Some("https://proxy.example.com/anthropic")
343 );
344 }
345
346 #[test]
347 fn normalize_preserves_existing_anthropic_base() {
348 assert_eq!(
349 normalize_anthropic_base_url(CHINA_ANTHROPIC_API_BASE_URL).as_deref(),
350 Some(CHINA_ANTHROPIC_API_BASE_URL)
351 );
352 }
353
354 #[test]
355 fn anthropic_primary_override_wins() {
356 let override_url = resolve_anthropic_base_override(
357 Some("https://primary.example.com/anthropic"),
358 Some(CHINA_API_BASE_URL),
359 );
360
361 assert_eq!(
362 override_url.as_deref(),
363 Some("https://primary.example.com/anthropic")
364 );
365 }
366}