cersei_tools/
config_tool.rs1use super::*;
4use serde::Deserialize;
5
6static 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}