返回 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 registration_origin(&self) -> std::borrow::Cow<'_, str> {
26 match &self.spec.namespace {
27 Some(namespace) => format!(
28 "runtime dynamic namespace {}",
29 crate::safe_label::SafeLabel::identifier(namespace)
30 )
31 .into(),
32 None => "runtime dynamic (no namespace)".into(),
33 }
34 }
35
36 fn description(&self) -> &str {
37 &self.spec.description
38 }
39
40 fn input_schema(&self) -> Value {
41 self.spec.input_schema.clone()
42 }
43
44 fn capabilities(&self) -> Vec<ToolCapability> {
45 Vec::new()
46 }
47
48 fn approval_requirement(&self) -> ApprovalRequirement {
49 ApprovalRequirement::Auto
50 }
51
52 fn supports_parallel(&self) -> bool {
53 false
54 }
55
56 fn defer_loading(&self) -> bool {
57 self.spec.defer_loading
58 }
59
60 async fn execute(&self, input: Value, context: &ToolContext) -> Result<ToolResult, ToolError> {
61 let executor = context
62 .runtime
63 .dynamic_tool_executor
64 .as_ref()
65 .ok_or_else(|| {
66 ToolError::not_available(format!(
67 "runtime dynamic tool '{}' has no executor",
68 self.spec.name
69 ))
70 })?;
71 executor
72 .execute_dynamic_tool(
73 context.runtime.active_thread_id.clone(),
74 self.spec.namespace.clone(),
75 self.spec.name.clone(),
76 input,
77 )
78 .await
79 }
80 }
81
82 #[cfg(test)]
83 mod tests {
84 use std::sync::Arc;
85
86 use async_trait::async_trait;
87 use serde_json::{Value, json};
88
89 use super::*;
90 use crate::tools::spec::{DynamicToolExecutor, RuntimeToolServices};
91
92 struct EchoExecutor;
93
94 #[async_trait]
95 impl DynamicToolExecutor for EchoExecutor {
96 async fn execute_dynamic_tool(
97 &self,
98 thread_id: Option<String>,
99 namespace: Option<String>,
100 name: String,
101 input: Value,
102 ) -> Result<ToolResult, ToolError> {
103 Ok(ToolResult::success(
104 json!({
105 "thread_id": thread_id,
106 "namespace": namespace,
107 "name": name,
108 "input": input,
109 })
110 .to_string(),
111 ))
112 }
113 }
114
115 #[tokio::test]
116 async fn runtime_dynamic_tool_delegates_to_runtime_executor() {
117 let tool = RuntimeDynamicTool::new(DynamicToolSpec {
118 namespace: Some("bench".to_string()),
119 name: "lookup".to_string(),
120 description: "Lookup a record".to_string(),
121 input_schema: json!({"type": "object"}),
122 defer_loading: true,
123 });
124 let ctx = ToolContext::new(".").with_runtime_services(RuntimeToolServices {
125 active_thread_id: Some("thr_1".to_string()),
126 dynamic_tool_executor: Some(Arc::new(EchoExecutor)),
127 ..RuntimeToolServices::default()
128 });
129
130 let result = tool.execute(json!({"id": "123"}), &ctx).await.unwrap();
131
132 assert!(result.success);
133 assert!(result.content.contains("\"thread_id\":\"thr_1\""));
134 assert!(result.content.contains("\"namespace\":\"bench\""));
135 assert!(result.content.contains("\"name\":\"lookup\""));
136 }
137 }
138
138 lines RUST