Skip to main content

cersei_tools/
config_tool.rs

1//! ConfigTool: read and modify agent configuration.
2
3use super::*;
4use serde::Deserialize;
5
6/// In-memory config store (session-scoped).
7static CONFIG_STORE: once_cell::sync::Lazy<dashmap::DashMap<String, serde_json::Value>> =
8    once_cell::sync::Lazy::new(dashmap::DashMap::new);
9
10pub fn get_config(key: &str) -> Option<serde_json::Value> {
11    CONFIG_STORE.get(key).map(|v| v.clone())
12}
13
14pub fn set_config(key: &str, value: serde_json::Value) {
15    CONFIG_STORE.insert(key.to_string(), value);
16}
17
18pub struct ConfigTool;
19
20#[async_trait]
21impl Tool for ConfigTool {
22    fn name(&self) -> &str {
23        "Config"
24    }
25    fn description(&self) -> &str {
26        "Read or modify configuration values."
27    }
28    fn permission_level(&self) -> PermissionLevel {
29        PermissionLevel::None
30    }
31    fn category(&self) -> ToolCategory {
32        ToolCategory::Custom
33    }
34
35    fn input_schema(&self) -> Value {
36        serde_json::json!({
37            "type": "object",
38            "properties": {
39                "action": { "type": "string", "enum": ["get", "set", "list"], "description": "Action to perform" },
40                "key": { "type": "string", "description": "Config key (for get/set)" },
41                "value": { "description": "Value to set (for set action)" }
42            },
43            "required": ["action"]
44        })
45    }
46
47    async fn execute(&self, input: Value, _ctx: &ToolContext) -> ToolResult {
48        #[derive(Deserialize)]
49        struct Input {
50            action: String,
51            key: Option<String>,
52            value: Option<Value>,
53        }
54
55        let input: Input = match serde_json::from_value(input) {
56            Ok(i) => i,
57            Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
58        };
59
60        match input.action.as_str() {
61            "get" => {
62                let key = input.key.unwrap_or_default();
63                match get_config(&key) {
64                    Some(v) => ToolResult::success(format!("{} = {}", key, v)),
65                    None => ToolResult::success(format!("{} is not set", key)),
66                }
67            }
68            "set" => {
69                let key = input.key.unwrap_or_default();
70                let value = input.value.unwrap_or(Value::Null);
71                set_config(&key, value.clone());
72                ToolResult::success(format!("{} = {}", key, value))
73            }
74            "list" => {
75                let entries: Vec<String> = CONFIG_STORE
76                    .iter()
77                    .map(|e| format!("  {} = {}", e.key(), e.value()))
78                    .collect();
79                if entries.is_empty() {
80                    ToolResult::success("No configuration values set.")
81                } else {
82                    ToolResult::success(entries.join("\n"))
83                }
84            }
85            other => {
86                ToolResult::error(format!("Unknown action: {}. Use get, set, or list.", other))
87            }
88        }
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::permissions::AllowAll;
96    use std::sync::Arc;
97
98    fn test_ctx() -> ToolContext {
99        ToolContext {
100            working_dir: std::env::temp_dir(),
101            session_id: "cfg-test".into(),
102            permissions: Arc::new(AllowAll),
103            cost_tracker: Arc::new(CostTracker::new()),
104            mcp_manager: None,
105            extensions: Extensions::default(),
106        }
107    }
108
109    #[tokio::test]
110    async fn test_config_set_get() {
111        let tool = ConfigTool;
112        tool.execute(
113            serde_json::json!({"action": "set", "key": "theme", "value": "dark"}),
114            &test_ctx(),
115        )
116        .await;
117        let result = tool
118            .execute(
119                serde_json::json!({"action": "get", "key": "theme"}),
120                &test_ctx(),
121            )
122            .await;
123        assert!(result.content.contains("dark"));
124    }
125}