1#![allow(dead_code)]
9#![allow(unused_variables)]
10use crate::chat::{ChatMessage, ChatSession};
11use crate::config::Config;
12use crate::error::{HeliosError, Result};
13use crate::llm::{LLMClient, LLMProviderType};
14use crate::tools::{ToolRegistry, ToolResult};
15use serde_json::Value;
16
17const AGENT_MEMORY_PREFIX: &str = "agent:";
19
20pub struct Agent {
22 name: String,
24 llm_client: LLMClient,
26 tool_registry: ToolRegistry,
28 chat_session: ChatSession,
30 max_iterations: usize,
32 react_mode: bool,
34 react_prompt: Option<String>,
36}
37
38impl Agent {
39 async fn new(name: impl Into<String>, config: Config) -> Result<Self> {
50 #[cfg(feature = "candle")]
53 let provider_type = if let Some(candle_config) = config.candle {
54 LLMProviderType::Candle(candle_config)
55 } else {
56 #[cfg(feature = "local")]
57 {
58 if let Some(local_config) = config.local {
59 LLMProviderType::Local(local_config)
60 } else {
61 LLMProviderType::Remote(config.llm)
62 }
63 }
64 #[cfg(not(feature = "local"))]
65 {
66 LLMProviderType::Remote(config.llm)
67 }
68 };
69
70 #[cfg(all(feature = "local", not(feature = "candle")))]
71 let provider_type = if let Some(local_config) = config.local {
72 LLMProviderType::Local(local_config)
73 } else {
74 LLMProviderType::Remote(config.llm)
75 };
76
77 #[cfg(not(any(feature = "local", feature = "candle")))]
78 let provider_type = LLMProviderType::Remote(config.llm);
79
80 let llm_client = LLMClient::new(provider_type).await?;
81
82 Ok(Self {
83 name: name.into(),
84 llm_client,
85 tool_registry: ToolRegistry::new(),
86 chat_session: ChatSession::new(),
87 max_iterations: 10,
88 react_mode: false,
89 react_prompt: None,
90 })
91 }
92
93 pub fn builder(name: impl Into<String>) -> AgentBuilder {
99 AgentBuilder::new(name)
100 }
101
102 pub async fn quick(name: impl Into<String>) -> Result<Self> {
123 let config = Config::load_or_default("config.toml");
124 Agent::builder(name).config(config).build().await
125 }
126
127 pub fn name(&self) -> &str {
129 &self.name
130 }
131
132 pub fn set_system_prompt(&mut self, prompt: impl Into<String>) {
138 self.chat_session = self.chat_session.clone().with_system_prompt(prompt);
139 }
140
141 pub fn register_tool(&mut self, tool: Box<dyn crate::tools::Tool>) {
147 self.tool_registry.register(tool);
148 }
149
150 pub fn tool_registry(&self) -> &ToolRegistry {
152 &self.tool_registry
153 }
154
155 pub fn tool_registry_mut(&mut self) -> &mut ToolRegistry {
157 &mut self.tool_registry
158 }
159
160 pub fn chat_session(&self) -> &ChatSession {
162 &self.chat_session
163 }
164
165 pub fn chat_session_mut(&mut self) -> &mut ChatSession {
167 &mut self.chat_session
168 }
169
170 pub fn clear_history(&mut self) {
172 self.chat_session.clear();
173 }
174
175 pub async fn send_message(&mut self, message: impl Into<String>) -> Result<String> {
185 let user_message = message.into();
186 self.chat_session.add_user_message(user_message.clone());
187
188 let response = self.execute_with_tools().await?;
190
191 Ok(response)
192 }
193
194 const DEFAULT_REASONING_PROMPT: &'static str = r#"Before taking any action, think through this step by step:
196
1971. What is the user asking for?
1982. What information or tools do I need to answer this?
1993. What is my plan to solve this problem?
200
201Provide your reasoning in a clear, structured way."#;
202
203 async fn generate_reasoning(&self) -> Result<String> {
209 let reasoning_prompt = self
210 .react_prompt
211 .as_deref()
212 .unwrap_or(Self::DEFAULT_REASONING_PROMPT);
213
214 let mut reasoning_messages = self.chat_session.get_messages();
216 reasoning_messages.push(ChatMessage::user(reasoning_prompt));
217
218 let response = self
220 .llm_client
221 .chat(reasoning_messages, None, None, None, None)
222 .await?;
223
224 Ok(response.content)
225 }
226
227 async fn handle_react_reasoning(&mut self) -> Result<()> {
233 if self.react_mode && !self.tool_registry.get_definitions().is_empty() {
235 let reasoning = self.generate_reasoning().await?;
236
237 println!("\n💠ReAct Reasoning:\n{}\n", reasoning);
239
240 self.chat_session
243 .add_message(ChatMessage::assistant(format!(
244 "[Reasoning]: {}",
245 reasoning
246 )));
247 }
248 Ok(())
249 }
250
251 async fn execute_with_tools(&mut self) -> Result<String> {
253 self.execute_with_tools_streaming().await
254 }
255
256 async fn execute_with_tools_streaming(&mut self) -> Result<String> {
258 self.execute_with_tools_streaming_with_params(None, None, None)
259 .await
260 }
261
262 async fn execute_with_tools_with_params(
264 &mut self,
265 temperature: Option<f32>,
266 max_tokens: Option<u32>,
267 stop: Option<Vec<String>>,
268 ) -> Result<String> {
269 self.handle_react_reasoning().await?;
271
272 let mut iterations = 0;
273 let tool_definitions = self.tool_registry.get_definitions();
274
275 loop {
276 if iterations >= self.max_iterations {
277 return Err(HeliosError::AgentError(
278 "Maximum iterations reached".to_string(),
279 ));
280 }
281
282 let messages = self.chat_session.get_messages();
283 let tools_option = if tool_definitions.is_empty() {
284 None
285 } else {
286 Some(tool_definitions.clone())
287 };
288
289 let response = self
290 .llm_client
291 .chat(
292 messages,
293 tools_option,
294 temperature,
295 max_tokens,
296 stop.clone(),
297 )
298 .await?;
299
300 if let Some(ref tool_calls) = response.tool_calls {
302 self.chat_session.add_message(response.clone());
304
305 for tool_call in tool_calls {
307 let tool_name = &tool_call.function.name;
308 let tool_args: Value = serde_json::from_str(&tool_call.function.arguments)
309 .unwrap_or(Value::Object(serde_json::Map::new()));
310
311 let tool_result = self
312 .tool_registry
313 .execute(tool_name, tool_args)
314 .await
315 .unwrap_or_else(|e| {
316 ToolResult::error(format!("Tool execution failed: {}", e))
317 });
318
319 let tool_message = ChatMessage::tool(tool_result.output, tool_call.id.clone());
321 self.chat_session.add_message(tool_message);
322 }
323
324 iterations += 1;
325 continue;
326 }
327
328 self.chat_session.add_message(response.clone());
330 return Ok(response.content);
331 }
332 }
333
334 async fn execute_with_tools_streaming_with_params(
336 &mut self,
337 temperature: Option<f32>,
338 max_tokens: Option<u32>,
339 stop: Option<Vec<String>>,
340 ) -> Result<String> {
341 self.handle_react_reasoning().await?;
343
344 let mut iterations = 0;
345 let tool_definitions = self.tool_registry.get_definitions();
346
347 loop {
348 if iterations >= self.max_iterations {
349 return Err(HeliosError::AgentError(
350 "Maximum iterations reached".to_string(),
351 ));
352 }
353
354 let messages = self.chat_session.get_messages();
355 let tools_option = if tool_definitions.is_empty() {
356 None
357 } else {
358 Some(tool_definitions.clone())
359 };
360
361 let mut streamed_content = String::new();
362
363 let stream_result = self
364 .llm_client
365 .chat_stream(
366 messages,
367 tools_option, temperature,
369 max_tokens,
370 stop.clone(),
371 |chunk| {
372 print!("{}", chunk);
374 let _ = std::io::Write::flush(&mut std::io::stdout());
375 streamed_content.push_str(chunk);
376 },
377 )
378 .await;
379
380 let response = stream_result?;
381
382 println!();
384
385 if let Some(ref tool_calls) = response.tool_calls {
387 let mut msg_with_content = response.clone();
389 msg_with_content.content = streamed_content.clone();
390 self.chat_session.add_message(msg_with_content);
391
392 for tool_call in tool_calls {
394 let tool_name = &tool_call.function.name;
395 let tool_args: Value = serde_json::from_str(&tool_call.function.arguments)
396 .unwrap_or(Value::Object(serde_json::Map::new()));
397
398 let tool_result = self
399 .tool_registry
400 .execute(tool_name, tool_args)
401 .await
402 .unwrap_or_else(|e| {
403 ToolResult::error(format!("Tool execution failed: {}", e))
404 });
405
406 let tool_message = ChatMessage::tool(tool_result.output, tool_call.id.clone());
408 self.chat_session.add_message(tool_message);
409 }
410
411 iterations += 1;
412 continue;
413 }
414
415 let mut final_msg = response;
417 final_msg.content = streamed_content.clone();
418 self.chat_session.add_message(final_msg);
419 return Ok(streamed_content);
420 }
421 }
422
423 pub async fn chat(&mut self, message: impl Into<String>) -> Result<String> {
425 self.send_message(message).await
426 }
427
428 pub async fn ask(&mut self, question: impl Into<String>) -> Result<String> {
430 self.chat(question).await
431 }
432
433 pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
435 self.set_system_prompt(prompt);
436 self
437 }
438
439 pub fn with_tool(mut self, tool: Box<dyn crate::tools::Tool>) -> Self {
441 self.register_tool(tool);
442 self
443 }
444
445 pub fn with_tools(mut self, tools: Vec<Box<dyn crate::tools::Tool>>) -> Self {
447 for tool in tools {
448 self.register_tool(tool);
449 }
450 self
451 }
452
453 pub fn set_max_iterations(&mut self, max: usize) {
459 self.max_iterations = max;
460 }
461
462 pub fn get_session_summary(&self) -> String {
464 self.chat_session.get_summary()
465 }
466
467 pub fn clear_memory(&mut self) {
469 self.chat_session
471 .metadata
472 .retain(|k, _| !k.starts_with(AGENT_MEMORY_PREFIX));
473 }
474
475 #[inline]
477 fn prefixed_key(key: &str) -> String {
478 format!("{}{}", AGENT_MEMORY_PREFIX, key)
479 }
480
481 pub fn set_memory(&mut self, key: impl Into<String>, value: impl Into<String>) {
484 let key = key.into();
485 self.chat_session
486 .set_metadata(Self::prefixed_key(&key), value);
487 }
488
489 pub fn get_memory(&self, key: &str) -> Option<&String> {
491 self.chat_session.get_metadata(&Self::prefixed_key(key))
492 }
493
494 pub fn remove_memory(&mut self, key: &str) -> Option<String> {
496 self.chat_session.remove_metadata(&Self::prefixed_key(key))
497 }
498
499 pub fn increment_counter(&mut self, key: &str) -> u32 {
502 let current = self
503 .get_memory(key)
504 .and_then(|v| v.parse::<u32>().ok())
505 .unwrap_or(0);
506 let next = current + 1;
507 self.set_memory(key, next.to_string());
508 next
509 }
510
511 pub fn increment_tasks_completed(&mut self) -> u32 {
513 self.increment_counter("tasks_completed")
514 }
515
516 pub async fn chat_with_history(
534 &mut self,
535 messages: Vec<ChatMessage>,
536 temperature: Option<f32>,
537 max_tokens: Option<u32>,
538 stop: Option<Vec<String>>,
539 ) -> Result<String> {
540 let mut temp_session = ChatSession::new();
542
543 for message in messages {
545 temp_session.add_message(message);
546 }
547
548 self.execute_with_tools_temp_session(temp_session, temperature, max_tokens, stop)
550 .await
551 }
552
553 async fn execute_with_tools_temp_session(
555 &mut self,
556 mut temp_session: ChatSession,
557 temperature: Option<f32>,
558 max_tokens: Option<u32>,
559 stop: Option<Vec<String>>,
560 ) -> Result<String> {
561 let mut iterations = 0;
562 let tool_definitions = self.tool_registry.get_definitions();
563
564 loop {
565 if iterations >= self.max_iterations {
566 return Err(HeliosError::AgentError(
567 "Maximum iterations reached".to_string(),
568 ));
569 }
570
571 let messages = temp_session.get_messages();
572 let tools_option = if tool_definitions.is_empty() {
573 None
574 } else {
575 Some(tool_definitions.clone())
576 };
577
578 let response = self
579 .llm_client
580 .chat(
581 messages,
582 tools_option,
583 temperature,
584 max_tokens,
585 stop.clone(),
586 )
587 .await?;
588
589 if let Some(ref tool_calls) = response.tool_calls {
591 temp_session.add_message(response.clone());
593
594 for tool_call in tool_calls {
596 let tool_name = &tool_call.function.name;
597 let tool_args: Value = serde_json::from_str(&tool_call.function.arguments)
598 .unwrap_or(Value::Object(serde_json::Map::new()));
599
600 let tool_result = self
601 .tool_registry
602 .execute(tool_name, tool_args)
603 .await
604 .unwrap_or_else(|e| {
605 ToolResult::error(format!("Tool execution failed: {}", e))
606 });
607
608 let tool_message = ChatMessage::tool(tool_result.output, tool_call.id.clone());
610 temp_session.add_message(tool_message);
611 }
612
613 iterations += 1;
614 continue;
615 }
616
617 return Ok(response.content);
619 }
620 }
621
622 pub async fn chat_stream_with_history<F>(
641 &mut self,
642 messages: Vec<ChatMessage>,
643 temperature: Option<f32>,
644 max_tokens: Option<u32>,
645 stop: Option<Vec<String>>,
646 on_chunk: F,
647 ) -> Result<ChatMessage>
648 where
649 F: FnMut(&str) + Send,
650 {
651 let mut temp_session = ChatSession::new();
653
654 for message in messages {
656 temp_session.add_message(message);
657 }
658
659 self.execute_streaming_with_tools_temp_session(
662 temp_session,
663 temperature,
664 max_tokens,
665 stop,
666 on_chunk,
667 )
668 .await
669 }
670
671 async fn execute_streaming_with_tools_temp_session<F>(
673 &mut self,
674 mut temp_session: ChatSession,
675 temperature: Option<f32>,
676 max_tokens: Option<u32>,
677 stop: Option<Vec<String>>,
678 mut on_chunk: F,
679 ) -> Result<ChatMessage>
680 where
681 F: FnMut(&str) + Send,
682 {
683 let mut iterations = 0;
684 let tool_definitions = self.tool_registry.get_definitions();
685
686 loop {
687 if iterations >= self.max_iterations {
688 return Err(HeliosError::AgentError(
689 "Maximum iterations reached".to_string(),
690 ));
691 }
692
693 let messages = temp_session.get_messages();
694 let tools_option = if tool_definitions.is_empty() {
695 None
696 } else {
697 Some(tool_definitions.clone())
698 };
699
700 let mut streamed_content = String::new();
702
703 let stream_result = self
704 .llm_client
705 .chat_stream(
706 messages,
707 tools_option,
708 temperature,
709 max_tokens,
710 stop.clone(),
711 |chunk| {
712 on_chunk(chunk);
713 streamed_content.push_str(chunk);
714 },
715 )
716 .await;
717
718 match stream_result {
719 Ok(response) => {
720 if let Some(ref tool_calls) = response.tool_calls {
722 let mut msg_with_content = response.clone();
724 msg_with_content.content = streamed_content.clone();
725 temp_session.add_message(msg_with_content);
726
727 for tool_call in tool_calls {
729 let tool_name = &tool_call.function.name;
730 let tool_args: Value =
731 serde_json::from_str(&tool_call.function.arguments)
732 .unwrap_or(Value::Object(serde_json::Map::new()));
733
734 let tool_result = self
735 .tool_registry
736 .execute(tool_name, tool_args)
737 .await
738 .unwrap_or_else(|e| {
739 ToolResult::error(format!("Tool execution failed: {}", e))
740 });
741
742 let tool_message =
744 ChatMessage::tool(tool_result.output, tool_call.id.clone());
745 temp_session.add_message(tool_message);
746 }
747
748 iterations += 1;
749 continue; } else {
751 let mut final_msg = response;
753 final_msg.content = streamed_content;
754 return Ok(final_msg);
755 }
756 }
757 Err(e) => return Err(e),
758 }
759 }
760 }
761}
762
763pub struct AgentBuilder {
764 name: String,
765 config: Option<Config>,
766 system_prompt: Option<String>,
767 tools: Vec<Box<dyn crate::tools::Tool>>,
768 max_iterations: usize,
769 react_mode: bool,
770 react_prompt: Option<String>,
771}
772
773impl AgentBuilder {
774 pub fn new(name: impl Into<String>) -> Self {
775 Self {
776 name: name.into(),
777 config: None,
778 system_prompt: None,
779 tools: Vec::new(),
780 max_iterations: 10,
781 react_mode: false,
782 react_prompt: None,
783 }
784 }
785
786 pub fn config(mut self, config: Config) -> Self {
787 self.config = Some(config);
788 self
789 }
790
791 pub fn auto_config(mut self) -> Self {
793 self.config = Some(Config::load_or_default("config.toml"));
794 self
795 }
796
797 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
798 self.system_prompt = Some(prompt.into());
799 self
800 }
801
802 pub fn prompt(self, prompt: impl Into<String>) -> Self {
804 self.system_prompt(prompt)
805 }
806
807 pub fn tool(mut self, tool: Box<dyn crate::tools::Tool>) -> Self {
809 self.tools.push(tool);
810 self
811 }
812
813 pub fn with_tool(mut self, tool: Box<dyn crate::tools::Tool>) -> Self {
815 self.tools.push(tool);
816 self
817 }
818
819 pub fn tools(mut self, tools: Vec<Box<dyn crate::tools::Tool>>) -> Self {
839 self.tools.extend(tools);
840 self
841 }
842
843 pub fn with_tools(mut self, tools: Vec<Box<dyn crate::tools::Tool>>) -> Self {
845 self.tools.extend(tools);
846 self
847 }
848
849 pub fn max_iterations(mut self, max: usize) -> Self {
850 self.max_iterations = max;
851 self
852 }
853
854 pub fn react(mut self) -> Self {
875 self.react_mode = true;
876 self
877 }
878
879 pub fn react_with_prompt(mut self, prompt: impl Into<String>) -> Self {
911 self.react_mode = true;
912 self.react_prompt = Some(prompt.into());
913 self
914 }
915
916 pub async fn build(self) -> Result<Agent> {
917 let config = self
918 .config
919 .ok_or_else(|| HeliosError::AgentError("Config is required".to_string()))?;
920
921 let mut agent = Agent::new(self.name, config).await?;
922
923 if let Some(prompt) = self.system_prompt {
924 agent.set_system_prompt(prompt);
925 }
926
927 for tool in self.tools {
928 agent.register_tool(tool);
929 }
930
931 agent.set_max_iterations(self.max_iterations);
932 agent.react_mode = self.react_mode;
933 agent.react_prompt = self.react_prompt;
934
935 Ok(agent)
936 }
937}
938
939#[cfg(test)]
940mod tests {
941 use super::*;
942 use crate::config::Config;
943 use crate::tools::{CalculatorTool, Tool, ToolParameter, ToolResult};
944 use serde_json::Value;
945 use std::collections::HashMap;
946
947 #[tokio::test]
949 async fn test_agent_creation_via_builder() {
950 let config = Config::new_default();
951 let agent = Agent::builder("test_agent").config(config).build().await;
952 assert!(agent.is_ok());
953 }
954
955 #[tokio::test]
957 async fn test_agent_memory_namespacing_set_get_remove() {
958 let config = Config::new_default();
959 let mut agent = Agent::builder("test_agent")
960 .config(config)
961 .build()
962 .await
963 .unwrap();
964
965 agent.set_memory("working_directory", "/tmp");
967 assert_eq!(
968 agent.get_memory("working_directory"),
969 Some(&"/tmp".to_string())
970 );
971
972 assert_eq!(
974 agent.chat_session().get_metadata("agent:working_directory"),
975 Some(&"/tmp".to_string())
976 );
977 assert!(agent
979 .chat_session()
980 .get_metadata("working_directory")
981 .is_none());
982
983 let removed = agent.remove_memory("working_directory");
985 assert_eq!(removed.as_deref(), Some("/tmp"));
986 assert!(agent.get_memory("working_directory").is_none());
987 }
988
989 #[tokio::test]
991 async fn test_agent_clear_memory_scoped() {
992 let config = Config::new_default();
993 let mut agent = Agent::builder("test_agent")
994 .config(config)
995 .build()
996 .await
997 .unwrap();
998
999 agent.set_memory("tasks_completed", "3");
1001 agent
1002 .chat_session_mut()
1003 .set_metadata("session_start", "now");
1004
1005 agent.clear_memory();
1007
1008 assert!(agent.get_memory("tasks_completed").is_none());
1010 assert_eq!(
1012 agent.chat_session().get_metadata("session_start"),
1013 Some(&"now".to_string())
1014 );
1015 }
1016
1017 #[tokio::test]
1019 async fn test_agent_increment_helpers() {
1020 let config = Config::new_default();
1021 let mut agent = Agent::builder("test_agent")
1022 .config(config)
1023 .build()
1024 .await
1025 .unwrap();
1026
1027 let n1 = agent.increment_tasks_completed();
1029 assert_eq!(n1, 1);
1030 assert_eq!(agent.get_memory("tasks_completed"), Some(&"1".to_string()));
1031
1032 let n2 = agent.increment_tasks_completed();
1033 assert_eq!(n2, 2);
1034 assert_eq!(agent.get_memory("tasks_completed"), Some(&"2".to_string()));
1035
1036 let f1 = agent.increment_counter("files_accessed");
1038 assert_eq!(f1, 1);
1039 let f2 = agent.increment_counter("files_accessed");
1040 assert_eq!(f2, 2);
1041 assert_eq!(agent.get_memory("files_accessed"), Some(&"2".to_string()));
1042 }
1043
1044 #[tokio::test]
1046 async fn test_agent_builder() {
1047 let config = Config::new_default();
1048 let agent = Agent::builder("test_agent")
1049 .config(config)
1050 .system_prompt("You are a helpful assistant")
1051 .max_iterations(5)
1052 .tool(Box::new(CalculatorTool))
1053 .build()
1054 .await
1055 .unwrap();
1056
1057 assert_eq!(agent.name(), "test_agent");
1058 assert_eq!(agent.max_iterations, 5);
1059 assert_eq!(
1060 agent.tool_registry().list_tools(),
1061 vec!["calculator".to_string()]
1062 );
1063 }
1064
1065 #[tokio::test]
1067 async fn test_agent_system_prompt() {
1068 let config = Config::new_default();
1069 let mut agent = Agent::builder("test_agent")
1070 .config(config)
1071 .build()
1072 .await
1073 .unwrap();
1074 agent.set_system_prompt("You are a test agent");
1075
1076 let session = agent.chat_session();
1078 assert_eq!(
1079 session.system_prompt,
1080 Some("You are a test agent".to_string())
1081 );
1082 }
1083
1084 #[tokio::test]
1086 async fn test_agent_tool_registry() {
1087 let config = Config::new_default();
1088 let mut agent = Agent::builder("test_agent")
1089 .config(config)
1090 .build()
1091 .await
1092 .unwrap();
1093
1094 assert!(agent.tool_registry().list_tools().is_empty());
1096
1097 agent.register_tool(Box::new(CalculatorTool));
1099 assert_eq!(
1100 agent.tool_registry().list_tools(),
1101 vec!["calculator".to_string()]
1102 );
1103 }
1104
1105 #[tokio::test]
1107 async fn test_agent_clear_history() {
1108 let config = Config::new_default();
1109 let mut agent = Agent::builder("test_agent")
1110 .config(config)
1111 .build()
1112 .await
1113 .unwrap();
1114
1115 agent.chat_session_mut().add_user_message("Hello");
1117 assert!(!agent.chat_session().messages.is_empty());
1118
1119 agent.clear_history();
1121 assert!(agent.chat_session().messages.is_empty());
1122 }
1123
1124 struct MockTool;
1126
1127 #[async_trait::async_trait]
1128 impl Tool for MockTool {
1129 fn name(&self) -> &str {
1130 "mock_tool"
1131 }
1132
1133 fn description(&self) -> &str {
1134 "A mock tool for testing"
1135 }
1136
1137 fn parameters(&self) -> HashMap<String, ToolParameter> {
1138 let mut params = HashMap::new();
1139 params.insert(
1140 "input".to_string(),
1141 ToolParameter {
1142 param_type: "string".to_string(),
1143 description: "Input parameter".to_string(),
1144 required: Some(true),
1145 },
1146 );
1147 params
1148 }
1149
1150 async fn execute(&self, args: Value) -> crate::Result<ToolResult> {
1151 let input = args
1152 .get("input")
1153 .and_then(|v| v.as_str())
1154 .unwrap_or("default");
1155 Ok(ToolResult::success(format!("Mock tool output: {}", input)))
1156 }
1157 }
1158}