soul_core/executor/
direct.rs1use std::sync::Arc;
4
5use async_trait::async_trait;
6use tokio::sync::mpsc;
7
8use crate::error::{SoulError, SoulResult};
9use crate::tool::{ToolOutput, ToolRegistry};
10use crate::types::ToolDefinition;
11
12use super::ToolExecutor;
13
14pub struct DirectExecutor {
19 tools: Arc<ToolRegistry>,
20}
21
22impl DirectExecutor {
23 pub fn new(tools: Arc<ToolRegistry>) -> Self {
24 Self { tools }
25 }
26}
27
28#[async_trait]
29impl ToolExecutor for DirectExecutor {
30 async fn execute(
31 &self,
32 definition: &ToolDefinition,
33 call_id: &str,
34 arguments: serde_json::Value,
35 partial_tx: Option<mpsc::UnboundedSender<String>>,
36 ) -> SoulResult<ToolOutput> {
37 let tool = self
38 .tools
39 .get(&definition.name)
40 .ok_or_else(|| SoulError::ToolExecution {
41 tool_name: definition.name.clone(),
42 message: format!("Unknown tool: {}", definition.name),
43 })?;
44
45 tool.execute(call_id, arguments, partial_tx).await
46 }
47
48 fn executor_name(&self) -> &str {
49 "direct"
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use crate::tool::Tool;
57 use serde_json::json;
58
59 struct MockTool;
60
61 #[async_trait]
62 impl Tool for MockTool {
63 fn name(&self) -> &str {
64 "mock"
65 }
66
67 fn definition(&self) -> ToolDefinition {
68 ToolDefinition {
69 name: "mock".into(),
70 description: "Mock tool".into(),
71 input_schema: json!({"type": "object"}),
72 }
73 }
74
75 async fn execute(
76 &self,
77 _call_id: &str,
78 _arguments: serde_json::Value,
79 _partial_tx: Option<mpsc::UnboundedSender<String>>,
80 ) -> SoulResult<ToolOutput> {
81 Ok(ToolOutput::success("mock result"))
82 }
83 }
84
85 #[tokio::test]
86 async fn delegates_to_tool_registry() {
87 let mut registry = ToolRegistry::new();
88 registry.register(Box::new(MockTool));
89 let executor = DirectExecutor::new(Arc::new(registry));
90
91 let def = ToolDefinition {
92 name: "mock".into(),
93 description: "".into(),
94 input_schema: json!({}),
95 };
96
97 let result = executor.execute(&def, "c1", json!({}), None).await.unwrap();
98 assert_eq!(result.content, "mock result");
99 }
100
101 #[tokio::test]
102 async fn unknown_tool_errors() {
103 let registry = ToolRegistry::new();
104 let executor = DirectExecutor::new(Arc::new(registry));
105
106 let def = ToolDefinition {
107 name: "nonexistent".into(),
108 description: "".into(),
109 input_schema: json!({}),
110 };
111
112 let result = executor.execute(&def, "c1", json!({}), None).await;
113 assert!(result.is_err());
114 }
115
116 #[test]
117 fn executor_name_is_direct() {
118 let registry = ToolRegistry::new();
119 let executor = DirectExecutor::new(Arc::new(registry));
120 assert_eq!(executor.executor_name(), "direct");
121 }
122
123 #[test]
124 fn is_send_sync() {
125 fn assert_send_sync<T: Send + Sync>() {}
126 assert_send_sync::<DirectExecutor>();
127 }
128}