Skip to main content

zai_rs/agent/
request.rs

1//! Agent API request types
2
3use serde::{Deserialize, Serialize};
4use validator::Validate;
5
6/// Request to create a new AI agent
7#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
8pub struct AgentCreateRequest {
9    /// Agent name (required)
10    #[validate(length(min = 1, max = 100))]
11    pub name: String,
12
13    /// Agent description (optional)
14    #[serde(skip_serializing_if = "Option::is_none")]
15    #[validate(length(max = 1000))]
16    pub description: Option<String>,
17
18    /// System prompt for the agent (optional)
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub system_prompt: Option<String>,
21
22    /// Model to use (optional, defaults to glm-4.5)
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub model: Option<String>,
25
26    /// Tools available to the agent (optional)
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub tools: Option<Vec<serde_json::Value>>,
29
30    /// Agent configuration (optional)
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub config: Option<AgentConfig>,
33}
34
35impl AgentCreateRequest {
36    /// Create a builder for AgentCreateRequest
37    pub fn builder() -> AgentCreateRequestBuilder {
38        AgentCreateRequestBuilder::default()
39    }
40}
41
42/// Builder for creating AgentCreateRequest
43#[derive(Default)]
44pub struct AgentCreateRequestBuilder {
45    name: Option<String>,
46    description: Option<String>,
47    system_prompt: Option<String>,
48    model: Option<String>,
49    tools: Option<Vec<serde_json::Value>>,
50    config: Option<AgentConfig>,
51}
52
53impl AgentCreateRequestBuilder {
54    /// Set the agent name
55    pub fn name(mut self, name: impl Into<String>) -> Self {
56        self.name = Some(name.into());
57        self
58    }
59
60    /// Set the agent description
61    pub fn description(mut self, description: impl Into<String>) -> Self {
62        self.description = Some(description.into());
63        self
64    }
65
66    /// Set the system prompt
67    pub fn system_prompt(mut self, system_prompt: impl Into<String>) -> Self {
68        self.system_prompt = Some(system_prompt.into());
69        self
70    }
71
72    /// Set the model
73    pub fn model(mut self, model: impl Into<String>) -> Self {
74        self.model = Some(model.into());
75        self
76    }
77
78    /// Add tools to the agent
79    pub fn tools(mut self, tools: Vec<serde_json::Value>) -> Self {
80        self.tools = Some(tools);
81        self
82    }
83
84    /// Set the agent configuration
85    pub fn config(mut self, config: AgentConfig) -> Self {
86        self.config = Some(config);
87        self
88    }
89
90    /// Build the AgentCreateRequest
91    pub fn build(self) -> Result<AgentCreateRequest, crate::client::error::ZaiError> {
92        let name = self
93            .name
94            .ok_or_else(|| crate::client::error::ZaiError::ApiError {
95                code: 1200,
96                message: "name is required".to_string(),
97            })?;
98
99        let req = AgentCreateRequest {
100            name,
101            description: self.description,
102            system_prompt: self.system_prompt,
103            model: self.model,
104            tools: self.tools,
105            config: self.config,
106        };
107
108        // Run full validation (name length 1-100, description max 1000, etc.)
109        req.validate()
110            .map_err(|e| crate::client::error::ZaiError::ApiError {
111                code: 1200,
112                message: format!("Validation error: {:?}", e),
113            })?;
114
115        Ok(req)
116    }
117}
118
119/// Agent configuration options
120#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
121pub struct AgentConfig {
122    /// Temperature for responses (0.0 to 1.0)
123    #[serde(skip_serializing_if = "Option::is_none")]
124    #[validate(range(min = 0.0, max = 1.0))]
125    pub temperature: Option<f32>,
126
127    /// Maximum tokens in response
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub max_tokens: Option<u32>,
130
131    /// Enable thinking mode
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub thinking_enabled: Option<bool>,
134}
135
136/// Request to update an existing agent
137#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
138pub struct AgentUpdateRequest {
139    /// New agent name (optional)
140    #[serde(skip_serializing_if = "Option::is_none")]
141    #[validate(length(min = 1, max = 100))]
142    pub name: Option<String>,
143
144    /// New description (optional)
145    #[serde(skip_serializing_if = "Option::is_none")]
146    #[validate(length(max = 1000))]
147    pub description: Option<String>,
148
149    /// New system prompt (optional)
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub system_prompt: Option<String>,
152
153    /// New model (optional)
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub model: Option<String>,
156
157    /// New tools (optional)
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub tools: Option<Vec<serde_json::Value>>,
160
161    /// New configuration (optional)
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub config: Option<AgentConfig>,
164}
165
166/// Request to chat with an agent
167#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
168pub struct AgentChatRequest {
169    /// User message
170    #[validate(length(min = 1))]
171    pub message: String,
172
173    /// Conversation ID for multi-turn conversations (optional)
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub conversation_id: Option<String>,
176
177    /// Session ID for tracking (optional)
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub session_id: Option<String>,
180
181    /// Stream response (optional)
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub stream: Option<bool>,
184
185    /// Additional parameters (optional)
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub parameters: Option<AgentChatParameters>,
188}
189
190/// Additional chat parameters
191#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
192pub struct AgentChatParameters {
193    /// Temperature override
194    #[serde(skip_serializing_if = "Option::is_none")]
195    #[validate(range(min = 0.0, max = 1.0))]
196    pub temperature: Option<f32>,
197
198    /// Max tokens override
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub max_tokens: Option<u32>,
201
202    /// Enable thinking for this request
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub thinking_enabled: Option<bool>,
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn test_builder_success() {
213        let req = AgentCreateRequest::builder()
214            .name("TestAgent")
215            .description("A test agent")
216            .model("glm-4.5")
217            .build()
218            .unwrap();
219
220        assert_eq!(req.name, "TestAgent");
221        assert_eq!(req.description, Some("A test agent".to_string()));
222        assert_eq!(req.model, Some("glm-4.5".to_string()));
223    }
224
225    #[test]
226    fn test_builder_missing_name() {
227        let result = AgentCreateRequest::builder().description("No name").build();
228        assert!(result.is_err());
229    }
230
231    #[test]
232    fn test_builder_name_too_long() {
233        let result = AgentCreateRequest::builder().name("a".repeat(101)).build();
234        assert!(result.is_err());
235    }
236
237    #[test]
238    fn test_builder_with_config() {
239        let req = AgentCreateRequest::builder()
240            .name("ConfigAgent")
241            .config(AgentConfig {
242                temperature: Some(0.7),
243                max_tokens: Some(1000),
244                thinking_enabled: Some(false),
245            })
246            .build()
247            .unwrap();
248
249        assert_eq!(req.config.unwrap().temperature, Some(0.7));
250    }
251
252    #[test]
253    fn test_serde_roundtrip() {
254        let req = AgentCreateRequest::builder()
255            .name("SerdeAgent")
256            .description("Serde test")
257            .build()
258            .unwrap();
259
260        let json = serde_json::to_string(&req).unwrap();
261        let parsed: AgentCreateRequest = serde_json::from_str(&json).unwrap();
262        assert_eq!(parsed.name, "SerdeAgent");
263    }
264
265    #[test]
266    fn test_chat_request_validation() {
267        let req = AgentChatRequest {
268            message: "Hello".to_string(),
269            conversation_id: None,
270            session_id: None,
271            stream: None,
272            parameters: None,
273        };
274        assert!(req.validate().is_ok());
275    }
276}