Skip to main content

codex_convert_proxy/providers/
minimax.rs

1//! MiniMax provider implementation.
2
3use crate::providers::trait_::Provider;
4use crate::types::chat_api::{ChatRequest, ChatResponse, ChatStreamChunk, Content, MessageRole};
5use std::any::Any;
6
7#[derive(Clone)]
8/// MiniMax provider.
9///
10/// MiniMax specific handling:
11/// - Content must be string, not array
12/// - Does not support 'developer' role, convert to 'user'
13pub struct MiniMaxProvider;
14
15impl Default for MiniMaxProvider {
16    fn default() -> Self {
17        Self
18    }
19}
20
21impl MiniMaxProvider {
22    pub fn new() -> Self {
23        Self
24    }
25}
26
27impl Provider for MiniMaxProvider {
28    fn name(&self) -> &'static str {
29        "minimax"
30    }
31
32    fn chat_completions_path(&self) -> String {
33        // MiniMax URL already includes /v1, so just /chat/completions
34        "/chat/completions".to_string()
35    }
36
37    fn transform_request(&self, request: &mut ChatRequest) {
38        // MiniMax requires content to be a string, not an array
39        for message in &mut request.messages {
40            if matches!(message.content, Content::Array(_)) {
41                let text = message.content.as_text();
42                message.content = Content::String(text);
43            }
44            // MiniMax doesn't support developer role - convert to user
45            if message.role == MessageRole::Developer {
46                message.role = MessageRole::User;
47            }
48        }
49    }
50
51    fn transform_response(&self, response: &mut ChatResponse) {
52        // Ensure content is string
53        for choice in &mut response.choices {
54            if matches!(choice.message.content, Content::Array(_)) {
55                let text = choice.message.content.as_text();
56                choice.message.content = Content::String(text);
57            }
58        }
59    }
60
61    fn transform_stream_chunk(&self, chunk: &mut ChatStreamChunk) {
62        // Ensure delta content is string
63        for choice in &mut chunk.choices {
64            if let Some(delta) = &mut choice.delta
65                && matches!(delta.content, Some(Content::Array(_)))
66                    && let Some(content) = delta.content.take() {
67                        let text = content.as_text();
68                        if !text.is_empty() {
69                            delta.content = Some(Content::String(text));
70                        }
71                    }
72        }
73    }
74
75    fn as_any(&self) -> &dyn Any {
76        self
77    }
78
79    fn clone_box(&self) -> Box<dyn Provider + Send + Sync> {
80        Box::new(self.clone())
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use crate::types::chat_api::{ChatMessage, Content, MessageRole};
88
89    #[test]
90    fn test_minimax_flattens_content() {
91        let mut request = ChatRequest {
92            model: "ab-01".to_string(),
93            messages: vec![ChatMessage {
94                role: MessageRole::User,
95                content: Content::Array(vec![crate::types::chat_api::ContentBlock {
96                    block_type: "text".to_string(),
97                    text: Some("Hello".to_string()),
98                    image_url: None,
99                }]),
100                name: None,
101                annotations: None,
102                tool_calls: None,
103                tool_call_id: None,
104                function_call: None,
105                refusal: None,
106            }],
107            tools: None,
108            tool_choice: None,
109            stream: Some(false),
110            temperature: None,
111            max_tokens: None,
112            top_p: None,
113            user: None,
114            stream_options: None,
115            frequency_penalty: None,
116            presence_penalty: None,
117            logit_bias: None,
118            logprobs: None,
119            top_logprobs: None,
120            n: None,
121            stop: None,
122            response_format: None,
123            reasoning_effort: None,
124            parallel_tool_calls: None,
125            seed: None,
126            service_tier: None,
127        };
128
129        let provider = MiniMaxProvider;
130        provider.transform_request(&mut request);
131
132        let msg = request.messages.first().unwrap();
133        assert!(matches!(msg.content, Content::String(_)));
134        assert_eq!(msg.content.as_text(), "Hello");
135    }
136}