Skip to main content

qwencode_rs/types/
config.rs

1use derive_builder::Builder;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6use crate::types::mcp::McpServerConfig;
7use crate::types::permission::PermissionMode;
8
9/// Authentication type
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
11#[serde(rename_all = "snake_case")]
12pub enum AuthType {
13    #[default]
14    Openai,
15    QwenOauth,
16}
17
18/// System prompt configuration
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(tag = "type", rename_all = "snake_case")]
21pub enum SystemPromptConfig {
22    /// Custom system prompt string
23    Custom(String),
24    /// Preset with optional append text
25    Preset {
26        preset: String,
27        append: Option<String>,
28    },
29}
30
31/// Timeout configuration for various SDK operations
32#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
33#[builder(default)]
34pub struct TimeoutConfig {
35    /// Timeout for can_use_tool callback (milliseconds)
36    #[builder(default = "60000")]
37    pub can_use_tool: u64,
38
39    /// Timeout for MCP tool requests (milliseconds)
40    #[builder(default = "60000")]
41    pub mcp_request: u64,
42
43    /// Timeout for control requests (milliseconds)
44    #[builder(default = "60000")]
45    pub control_request: u64,
46
47    /// Timeout for stream close (milliseconds)
48    #[builder(default = "15000")]
49    pub stream_close: u64,
50}
51
52impl Default for TimeoutConfig {
53    fn default() -> Self {
54        TimeoutConfig {
55            can_use_tool: 60000,
56            mcp_request: 60000,
57            control_request: 60000,
58            stream_close: 15000,
59        }
60    }
61}
62
63/// Subagent configuration
64#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
65#[builder(default)]
66#[derive(Default)]
67pub struct SubagentConfig {
68    #[builder(default)]
69    pub name: String,
70    #[builder(default)]
71    pub description: String,
72    #[builder(default)]
73    pub tools: Option<Vec<String>>,
74}
75
76/// Query options for configuring SDK behavior
77#[derive(Debug, Clone, Builder, Serialize, Deserialize)]
78#[builder(default)]
79pub struct QueryOptions {
80    /// Working directory for the session
81    #[builder(default, setter(into))]
82    pub cwd: Option<PathBuf>,
83
84    /// AI model to use (e.g., "qwen-max", "qwen-plus")
85    #[builder(default, setter(into))]
86    pub model: Option<String>,
87
88    /// Path to qwen executable (auto-detected if None)
89    #[builder(default, setter(into))]
90    pub path_to_qwen_executable: Option<String>,
91
92    /// Permission mode for tool approval
93    #[builder(default)]
94    pub permission_mode: PermissionMode,
95
96    /// Environment variables merged with current process
97    #[builder(default)]
98    pub env: Option<HashMap<String, String>>,
99
100    /// System prompt configuration
101    #[builder(default)]
102    pub system_prompt: Option<SystemPromptConfig>,
103
104    /// MCP servers configuration
105    #[builder(default)]
106    pub mcp_servers: Option<HashMap<String, McpServerConfig>>,
107
108    /// Enable debug logging
109    #[builder(default)]
110    pub debug: bool,
111
112    /// Maximum session turns (-1 for unlimited)
113    #[builder(default = "-1")]
114    pub max_session_turns: i32,
115
116    /// Allowlist of tools
117    #[builder(default)]
118    pub core_tools: Option<Vec<String>>,
119
120    /// Denylist of tools
121    #[builder(default)]
122    pub exclude_tools: Option<Vec<String>>,
123
124    /// Tools that bypass can_use_tool and auto-execute
125    #[builder(default)]
126    pub allowed_tools: Option<Vec<String>>,
127
128    /// Authentication type
129    #[builder(default)]
130    pub auth_type: AuthType,
131
132    /// Subagent configuration
133    #[builder(default)]
134    pub agents: Option<Vec<SubagentConfig>>,
135
136    /// Include partial messages during generation
137    #[builder(default)]
138    pub include_partial_messages: bool,
139
140    /// Session ID to resume history
141    #[builder(default, setter(into))]
142    pub resume: Option<String>,
143
144    /// Session ID without resuming history
145    #[builder(default, setter(into))]
146    pub session_id: Option<String>,
147
148    /// Timeout configuration
149    #[builder(default)]
150    pub timeouts: Option<TimeoutConfig>,
151}
152
153impl Default for QueryOptions {
154    fn default() -> Self {
155        QueryOptions {
156            cwd: None,
157            model: None,
158            path_to_qwen_executable: None,
159            permission_mode: PermissionMode::default(),
160            env: None,
161            system_prompt: None,
162            mcp_servers: None,
163            debug: false,
164            max_session_turns: -1,
165            core_tools: None,
166            exclude_tools: None,
167            allowed_tools: None,
168            auth_type: AuthType::default(),
169            agents: None,
170            include_partial_messages: false,
171            resume: None,
172            session_id: None,
173            timeouts: None,
174        }
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_auth_type_default() {
184        let auth = AuthType::default();
185        assert_eq!(auth, AuthType::Openai);
186    }
187
188    #[test]
189    fn test_auth_type_qwen_oauth() {
190        let auth = AuthType::QwenOauth;
191        assert_eq!(auth, AuthType::QwenOauth);
192    }
193
194    #[test]
195    fn test_auth_type_serialization() {
196        let auth = AuthType::Openai;
197        let serialized = serde_json::to_string(&auth).unwrap();
198        assert_eq!(serialized, "\"openai\"");
199
200        let deserialized: AuthType = serde_json::from_str(&serialized).unwrap();
201        assert_eq!(deserialized, AuthType::Openai);
202    }
203
204    #[test]
205    fn test_system_prompt_config_custom() {
206        let config = SystemPromptConfig::Custom("Custom prompt".to_string());
207
208        match &config {
209            SystemPromptConfig::Custom(prompt) => {
210                assert_eq!(prompt, "Custom prompt");
211            }
212            _ => panic!("Expected Custom variant"),
213        }
214    }
215
216    #[test]
217    fn test_system_prompt_config_preset_without_append() {
218        let config = SystemPromptConfig::Preset {
219            preset: "qwen_code".to_string(),
220            append: None,
221        };
222
223        match &config {
224            SystemPromptConfig::Preset { preset, append } => {
225                assert_eq!(preset, "qwen_code");
226                assert!(append.is_none());
227            }
228            _ => panic!("Expected Preset variant"),
229        }
230    }
231
232    #[test]
233    fn test_system_prompt_config_preset_with_append() {
234        let config = SystemPromptConfig::Preset {
235            preset: "qwen_code".to_string(),
236            append: Some("Additional instructions".to_string()),
237        };
238
239        match &config {
240            SystemPromptConfig::Preset { preset, append } => {
241                assert_eq!(preset, "qwen_code");
242                assert_eq!(append, &Some("Additional instructions".to_string()));
243            }
244            _ => panic!("Expected Preset variant"),
245        }
246    }
247
248    #[test]
249    fn test_timeout_config_default() {
250        let config = TimeoutConfig::default();
251
252        assert_eq!(config.can_use_tool, 60000);
253        assert_eq!(config.mcp_request, 60000);
254        assert_eq!(config.control_request, 60000);
255        assert_eq!(config.stream_close, 15000);
256    }
257
258    #[test]
259    fn test_timeout_config_builder() {
260        let config = TimeoutConfigBuilder::default()
261            .can_use_tool(30000)
262            .mcp_request(120000)
263            .build()
264            .unwrap();
265
266        assert_eq!(config.can_use_tool, 30000);
267        assert_eq!(config.mcp_request, 120000);
268        assert_eq!(config.control_request, 60000); // default
269        assert_eq!(config.stream_close, 15000); // default
270    }
271
272    #[test]
273    fn test_subagent_config_default() {
274        let config = SubagentConfig::default();
275
276        assert_eq!(config.name, "");
277        assert_eq!(config.description, "");
278        assert!(config.tools.is_none());
279    }
280
281    #[test]
282    fn test_subagent_config_builder() {
283        let config = SubagentConfigBuilder::default()
284            .name("test-agent".to_string())
285            .description("Test agent".to_string())
286            .tools(Some(vec!["tool1".to_string(), "tool2".to_string()]))
287            .build()
288            .unwrap();
289
290        assert_eq!(config.name, "test-agent");
291        assert_eq!(config.description, "Test agent");
292        assert_eq!(
293            config.tools,
294            Some(vec!["tool1".to_string(), "tool2".to_string()])
295        );
296    }
297
298    #[test]
299    fn test_query_options_default() {
300        let options = QueryOptions::default();
301
302        assert!(options.cwd.is_none());
303        assert!(options.model.is_none());
304        assert!(options.path_to_qwen_executable.is_none());
305        assert_eq!(options.permission_mode, PermissionMode::default());
306        assert!(options.env.is_none());
307        assert!(options.system_prompt.is_none());
308        assert!(options.mcp_servers.is_none());
309        assert!(!options.debug);
310        assert_eq!(options.max_session_turns, -1);
311        assert!(options.core_tools.is_none());
312        assert!(options.exclude_tools.is_none());
313        assert!(options.allowed_tools.is_none());
314        assert_eq!(options.auth_type, AuthType::default());
315        assert!(options.agents.is_none());
316        assert!(!options.include_partial_messages);
317        assert!(options.resume.is_none());
318        assert!(options.session_id.is_none());
319        assert!(options.timeouts.is_none());
320    }
321
322    #[test]
323    fn test_query_options_builder() {
324        let options = QueryOptionsBuilder::default()
325            .model("qwen-max".to_string())
326            .debug(true)
327            .max_session_turns(10)
328            .include_partial_messages(true)
329            .build()
330            .unwrap();
331
332        assert_eq!(options.model, Some("qwen-max".to_string()));
333        assert!(options.debug);
334        assert_eq!(options.max_session_turns, 10);
335        assert!(options.include_partial_messages);
336        // Check defaults for unset fields
337        assert!(options.cwd.is_none());
338        assert_eq!(options.permission_mode, PermissionMode::default());
339    }
340
341    #[test]
342    fn test_query_options_with_cwd_pathbuf() {
343        let path = PathBuf::from("/tmp/test");
344        let options = QueryOptionsBuilder::default()
345            .cwd(path.clone())
346            .build()
347            .unwrap();
348
349        assert_eq!(options.cwd, Some(path));
350    }
351
352    #[test]
353    fn test_query_options_serialization() {
354        let options = QueryOptions {
355            model: Some("qwen-plus".to_string()),
356            debug: true,
357            max_session_turns: 5,
358            ..Default::default()
359        };
360
361        let serialized = serde_json::to_string(&options).unwrap();
362        assert!(serialized.contains("\"model\":\"qwen-plus\""));
363        assert!(serialized.contains("\"debug\":true"));
364        assert!(serialized.contains("\"max_session_turns\":5"));
365    }
366
367    #[test]
368    fn test_timeout_config_all_custom() {
369        let config = TimeoutConfigBuilder::default()
370            .can_use_tool(10000)
371            .mcp_request(20000)
372            .control_request(30000)
373            .stream_close(5000)
374            .build()
375            .unwrap();
376
377        assert_eq!(config.can_use_tool, 10000);
378        assert_eq!(config.mcp_request, 20000);
379        assert_eq!(config.control_request, 30000);
380        assert_eq!(config.stream_close, 5000);
381    }
382}