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