返回 CodeWhale
dynamic.rs
根目录 / crates / tui / src / tools / dynamic.rs
1 use async_trait::async_trait;
2 use codewhale_protocol::runtime::DynamicToolSpec;
3 use serde_json::Value;
4
5 use crate::tools::spec::{
6 ApprovalRequirement, ToolCapability, ToolContext, ToolError, ToolResult, ToolSpec,
7 };
8
9 pub struct RuntimeDynamicTool {
10 spec: DynamicToolSpec,
11 }
12
13 impl RuntimeDynamicTool {
14 pub fn new(spec: DynamicToolSpec) -> Self {
15 Self { spec }
16 }
17 }
18
19 #[async_trait]
20 impl ToolSpec for RuntimeDynamicTool {
21 fn name(&self) -> &str {
22 &self.spec.name
23 }
24
25 fn description(&self) -> &str {
26 &self.spec.description
27 }
28
29 fn input_schema(&self) -> Value {
30 self.spec.input_schema.clone()
31 }
32
33 fn capabilities(&self) -> Vec<ToolCapability> {
34 Vec::new()
35 }
36
37 fn approval_requirement(&self) -> ApprovalRequirement {
38 ApprovalRequirement::Auto
39 }
40
41 fn supports_parallel(&self) -> bool {
42 false
43 }
44
45 fn defer_loading(&self) -> bool {
46 self.spec.defer_loading
47 }
48
49 async fn execute(&self, input: Value, context: &ToolContext) -> Result<ToolResult, ToolError> {
50 let executor = context
51 .runtime
52 .dynamic_tool_executor
53 .as_ref()
54 .ok_or_else(|| {
55 ToolError::not_available(format!(
56 "runtime dynamic tool '{}' has no executor",
57 self.spec.name
58 ))
59 })?;
60 executor
61 .execute_dynamic_tool(
62 context.runtime.active_thread_id.clone(),
63 self.spec.namespace.clone(),
64 self.spec.name.clone(),
65 input,
66 )
67 .await
68 }
69 }
70
71 #[cfg(test)]
72 mod tests {
73 use std::sync::Arc;
74
75 use async_trait::async_trait;
76 use serde_json::{Value, json};
77
78 use super::*;
79 use crate::tools::spec::{DynamicToolExecutor, RuntimeToolServices};
80
81 struct EchoExecutor;
82
83 #[async_trait]
84 impl DynamicToolExecutor for EchoExecutor {
85 async fn execute_dynamic_tool(
86 &self,
87 thread_id: Option<String>,
88 namespace: Option<String>,
89 name: String,
90 input: Value,
91 ) -> Result<ToolResult, ToolError> {
92 Ok(ToolResult::success(
93 json!({
94 "thread_id": thread_id,
95 "namespace": namespace,
96 "name": name,
97 "input": input,
98 })
99 .to_string(),
100 ))
101 }
102 }
103
104 #[tokio::test]
105 async fn runtime_dynamic_tool_delegates_to_runtime_executor() {
106 let tool = RuntimeDynamicTool::new(DynamicToolSpec {
107 namespace: Some("bench".to_string()),
108 name: "lookup".to_string(),
109 description: "Lookup a record".to_string(),
110 input_schema: json!({"type": "object"}),
111 defer_loading: true,
112 });
113 let ctx = ToolContext::new(".").with_runtime_services(RuntimeToolServices {
114 active_thread_id: Some("thr_1".to_string()),
115 dynamic_tool_executor: Some(Arc::new(EchoExecutor)),
116 ..RuntimeToolServices::default()
117 });
118
119 let result = tool.execute(json!({"id": "123"}), &ctx).await.unwrap();
120
121 assert!(result.success);
122 assert!(result.content.contains("\"thread_id\":\"thr_1\""));
123 assert!(result.content.contains("\"namespace\":\"bench\""));
124 assert!(result.content.contains("\"name\":\"lookup\""));
125 }
126 }
127
127 lines RUST