Skip to main content

codex_convert_proxy/providers/
glm.rs

1//! GLM provider implementation.
2
3use crate::providers::trait_::Provider;
4use crate::types::chat_api::{ChatRequest, ChatResponse, ChatStreamChunk};
5
6/// GLM (Zhipu AI) provider.
7///
8/// GLM has some specific requirements:
9/// - Does not support function calling tools
10/// - Messages should be flattened to simple text format
11/// - API path is /chat/completions (not /v1/chat/completions)
12pub struct GLMProvider;
13
14impl Default for GLMProvider {
15    fn default() -> Self {
16        Self
17    }
18}
19
20impl GLMProvider {
21    pub fn new() -> Self {
22        Self
23    }
24}
25
26impl Provider for GLMProvider {
27    fn name(&self) -> &'static str {
28        "glm"
29    }
30
31    fn chat_completions_path(&self) -> String {
32        // GLM uses /chat/completions not /v1/chat/completions
33        "/chat/completions".to_string()
34    }
35
36    fn transform_request(&self, request: &mut ChatRequest) {
37        // GLM doesn't support tools - remove them
38        request.tools = None;
39        request.tool_choice = None;
40
41        // Flatten message content to simple strings
42        for message in &mut request.messages {
43            // GLM doesn't support developer role - convert to user
44            if message.role == crate::types::chat_api::MessageRole::Developer {
45                message.role = crate::types::chat_api::MessageRole::User;
46            }
47            let text = message.content.as_text();
48            message.content = crate::types::chat_api::Content::String(text);
49        }
50    }
51
52    fn transform_response(&self, response: &mut ChatResponse) {
53        // Ensure content is string format
54        for choice in &mut response.choices {
55            let text = choice.message.content.as_text();
56            choice.message.content = crate::types::chat_api::Content::String(text);
57        }
58    }
59
60    fn transform_stream_chunk(&self, chunk: &mut ChatStreamChunk) {
61        // Ensure delta content is string format
62        for choice in &mut chunk.choices {
63            if let Some(delta) = &mut choice.delta
64                && let Some(content) = &delta.content {
65                    let text = content.as_text();
66                    if !text.is_empty() {
67                        delta.content = Some(crate::types::chat_api::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_glm_removes_tools() {
82        let mut request = ChatRequest {
83            model: "glm-4".to_string(),
84            messages: vec![ChatMessage {
85                role: MessageRole::User,
86                content: Content::String("Hello".to_string()),
87                name: None,
88                annotations: None,
89                tool_calls: None,
90                tool_call_id: None,
91                function_call: None,
92                refusal: None,
93            }],
94            tools: Some(vec![]),
95            tool_choice: None,
96            stream: Some(false),
97            temperature: None,
98            max_tokens: None,
99            top_p: None,
100            user: None,
101            stream_options: None,
102            frequency_penalty: None,
103            presence_penalty: None,
104            logit_bias: None,
105            logprobs: None,
106            top_logprobs: None,
107            n: None,
108            stop: None,
109            response_format: None,
110            reasoning_effort: None,
111            parallel_tool_calls: None,
112            seed: None,
113            service_tier: None,
114        };
115
116        let provider = GLMProvider;
117        provider.transform_request(&mut request);
118
119        assert!(request.tools.is_none());
120        assert!(request.tool_choice.is_none());
121    }
122
123    #[test]
124    fn test_glm_flattens_content() {
125        let mut request = ChatRequest {
126            model: "glm-4".to_string(),
127            messages: vec![ChatMessage {
128                role: MessageRole::User,
129                content: Content::Array(vec![crate::types::chat_api::ContentBlock {
130                    block_type: "text".to_string(),
131                    text: Some("Hello".to_string()),
132                    image_url: None,
133                }]),
134                name: None,
135                annotations: None,
136                tool_calls: None,
137                tool_call_id: None,
138                function_call: None,
139                refusal: None,
140            }],
141            tools: None,
142            tool_choice: None,
143            stream: Some(false),
144            temperature: None,
145            max_tokens: None,
146            top_p: None,
147            user: None,
148            stream_options: None,
149            frequency_penalty: None,
150            presence_penalty: None,
151            logit_bias: None,
152            logprobs: None,
153            top_logprobs: None,
154            n: None,
155            stop: None,
156            response_format: None,
157            reasoning_effort: None,
158            parallel_tool_calls: None,
159            seed: None,
160            service_tier: None,
161        };
162
163        let provider = GLMProvider;
164        provider.transform_request(&mut request);
165
166        let msg = request.messages.first().unwrap();
167        assert!(matches!(msg.content, Content::String(_)));
168        assert_eq!(msg.content.as_text(), "Hello");
169    }
170}