1use crate::types::{Tool, ToolCall, ToolResult};
2
3pub struct BuiltinToolRegistry {
10 tools: Vec<Tool>,
11 version: &'static str,
12}
13
14impl BuiltinToolRegistry {
15 #[must_use]
16 pub fn new(version: &'static str) -> Self {
17 Self {
18 tools: vec![
19 Tool {
20 name: "llm_version".into(),
21 description: "Returns the current LLM CLI version".into(),
22 input_schema: serde_json::json!({
23 "type": "object",
24 "properties": {},
25 }),
26 },
27 Tool {
28 name: "llm_time".into(),
29 description: "Returns the current date and time".into(),
30 input_schema: serde_json::json!({
31 "type": "object",
32 "properties": {},
33 }),
34 },
35 ],
36 version,
37 }
38 }
39
40 #[must_use]
41 pub fn list(&self) -> &[Tool] {
42 &self.tools
43 }
44
45 #[must_use]
46 pub fn get(&self, name: &str) -> Option<&Tool> {
47 self.tools.iter().find(|t| t.name == name)
48 }
49
50 #[must_use]
52 pub fn contains(&self, name: &str) -> bool {
53 self.get(name).is_some()
54 }
55
56 #[must_use]
58 pub fn execute_tool(&self, call: &ToolCall) -> ToolResult {
59 let output = match call.name.as_str() {
60 "llm_version" => self.version.to_string(),
61 "llm_time" => {
62 let utc = chrono::Utc::now();
63 let local = chrono::Local::now();
64 let tz = local.format("%Z").to_string();
65 serde_json::json!({
66 "utc_time": utc.to_rfc3339(),
67 "local_time": local.to_rfc3339(),
68 "timezone": tz,
69 })
70 .to_string()
71 }
72 _ => {
73 return ToolResult {
74 name: call.name.clone(),
75 output: String::new(),
76 tool_call_id: call.tool_call_id.clone(),
77 error: Some(format!("unknown tool: {}", call.name)),
78 };
79 }
80 };
81
82 ToolResult {
83 name: call.name.clone(),
84 output,
85 tool_call_id: call.tool_call_id.clone(),
86 error: None,
87 }
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 fn registry() -> BuiltinToolRegistry {
96 BuiltinToolRegistry::new("9.9.9-test")
97 }
98
99 #[test]
100 fn registry_has_two_builtin_tools() {
101 assert_eq!(registry().list().len(), 2);
102 }
103
104 #[test]
105 fn llm_version_returns_constructor_version() {
106 let call = ToolCall {
107 name: "llm_version".into(),
108 arguments: serde_json::json!({}),
109 tool_call_id: Some("tc_1".into()),
110 };
111 let result = registry().execute_tool(&call);
112 assert!(result.error.is_none());
113 assert_eq!(result.output, "9.9.9-test");
114 }
115
116 #[test]
117 fn llm_time_returns_time_info() {
118 let call = ToolCall {
119 name: "llm_time".into(),
120 arguments: serde_json::json!({}),
121 tool_call_id: None,
122 };
123 let result = registry().execute_tool(&call);
124 assert!(result.error.is_none());
125 let parsed: serde_json::Value = serde_json::from_str(&result.output).unwrap();
126 assert!(parsed.get("utc_time").is_some());
127 assert!(parsed.get("local_time").is_some());
128 assert!(parsed.get("timezone").is_some());
129 }
130
131 #[test]
132 fn unknown_tool_returns_error_result() {
133 let call = ToolCall {
134 name: "nonexistent".into(),
135 arguments: serde_json::json!({}),
136 tool_call_id: None,
137 };
138 let result = registry().execute_tool(&call);
139 assert!(result.error.is_some());
140 assert!(result.error.unwrap().contains("unknown tool"));
141 }
142
143 #[test]
144 fn registry_get_finds_tool() {
145 let r = registry();
146 assert!(r.get("llm_version").is_some());
147 assert!(r.get("llm_time").is_some());
148 assert!(r.get("nonexistent").is_none());
149 assert!(r.contains("llm_time"));
150 }
151}