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