ai_agents_tools/
provider.rs1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::sync::Arc;
5
6use super::types::{ToolAliases, ToolMetadata, ToolProviderType};
7use super::{Tool, ToolExecutionContext, ToolPolicyBindings, ToolResult};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ToolDescriptor {
11 pub id: String,
12 pub name: String,
13 pub description: String,
14 pub input_schema: Value,
15 #[serde(default, skip_serializing_if = "Option::is_none")]
16 pub aliases: Option<ToolAliases>,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub metadata: Option<ToolMetadata>,
19 #[serde(default)]
21 pub policy_bindings: ToolPolicyBindings,
22}
23
24impl ToolDescriptor {
25 pub fn new(
26 id: impl Into<String>,
27 name: impl Into<String>,
28 description: impl Into<String>,
29 input_schema: Value,
30 ) -> Self {
31 Self {
32 id: id.into(),
33 name: name.into(),
34 description: description.into(),
35 input_schema,
36 aliases: None,
37 metadata: None,
38 policy_bindings: ToolPolicyBindings::default(),
39 }
40 }
41
42 pub fn with_aliases(mut self, aliases: ToolAliases) -> Self {
43 self.aliases = Some(aliases);
44 self
45 }
46
47 pub fn with_metadata(mut self, metadata: ToolMetadata) -> Self {
48 self.metadata = Some(metadata);
49 self
50 }
51
52 pub fn with_policy_bindings(mut self, bindings: ToolPolicyBindings) -> Self {
54 self.policy_bindings = bindings;
55 self
56 }
57
58 pub fn get_name(&self, lang: Option<&str>) -> &str {
59 if let Some(lang) = lang
60 && let Some(ref aliases) = self.aliases
61 && let Some(name) = aliases.get_name(lang)
62 {
63 return name;
64 }
65 &self.name
66 }
67
68 pub fn get_description(&self, lang: Option<&str>) -> &str {
69 if let Some(lang) = lang
70 && let Some(ref aliases) = self.aliases
71 && let Some(desc) = aliases.get_description(lang)
72 {
73 return desc;
74 }
75 &self.description
76 }
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize, Default)]
80#[serde(tag = "status", rename_all = "snake_case")]
81pub enum ProviderHealth {
82 #[default]
83 Healthy,
84 Degraded {
85 message: String,
86 },
87 Unavailable {
88 message: String,
89 },
90}
91
92impl ProviderHealth {
93 pub fn is_healthy(&self) -> bool {
94 matches!(self, ProviderHealth::Healthy)
95 }
96
97 pub fn is_available(&self) -> bool {
98 !matches!(self, ProviderHealth::Unavailable { .. })
99 }
100
101 pub fn degraded(message: impl Into<String>) -> Self {
102 ProviderHealth::Degraded {
103 message: message.into(),
104 }
105 }
106
107 pub fn unavailable(message: impl Into<String>) -> Self {
108 ProviderHealth::Unavailable {
109 message: message.into(),
110 }
111 }
112}
113
114#[derive(Debug, thiserror::Error)]
115pub enum ToolProviderError {
116 #[error("Tool not found: {0}")]
117 ToolNotFound(String),
118
119 #[error("Execution failed: {0}")]
120 ExecutionFailed(String),
121
122 #[error("Provider unavailable: {0}")]
123 Unavailable(String),
124
125 #[error("Connection error: {0}")]
126 ConnectionError(String),
127
128 #[error("Configuration error: {0}")]
129 ConfigError(String),
130
131 #[error("Timeout after {0}ms")]
132 Timeout(u64),
133
134 #[error("{0}")]
135 Other(String),
136}
137
138#[async_trait]
139pub trait ToolProvider: Send + Sync {
140 fn id(&self) -> &str;
141
142 fn name(&self) -> &str;
143
144 fn provider_type(&self) -> ToolProviderType;
145
146 async fn list_tools(&self) -> Vec<ToolDescriptor>;
147
148 async fn get_tool(&self, tool_id: &str) -> Option<Arc<dyn Tool>>;
149
150 async fn execute(
151 &self,
152 tool_id: &str,
153 args: Value,
154 ctx: ToolExecutionContext,
155 ) -> Result<ToolResult, ToolProviderError> {
156 if let Some(tool) = self.get_tool(tool_id).await {
157 Ok(tool.execute(args, ctx).await)
158 } else {
159 Err(ToolProviderError::ToolNotFound(tool_id.to_string()))
160 }
161 }
162
163 fn supports_refresh(&self) -> bool {
164 false
165 }
166
167 async fn refresh(&self) -> Result<(), ToolProviderError> {
168 Ok(())
169 }
170
171 async fn health_check(&self) -> ProviderHealth {
172 ProviderHealth::Healthy
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn test_tool_descriptor() {
182 let desc = ToolDescriptor::new(
183 "search",
184 "Web Search",
185 "Search the web",
186 serde_json::json!({"type": "object"}),
187 );
188
189 assert_eq!(desc.id, "search");
190 assert_eq!(desc.get_name(None), "Web Search");
191 assert_eq!(desc.get_description(None), "Search the web");
192 }
193
194 #[test]
195 fn test_tool_descriptor_with_aliases() {
196 let aliases = ToolAliases::new()
197 .with_name("ko", "검색")
198 .with_description("ko", "웹 검색");
199
200 let desc = ToolDescriptor::new(
201 "search",
202 "Web Search",
203 "Search the web",
204 serde_json::json!({}),
205 )
206 .with_aliases(aliases);
207
208 assert_eq!(desc.get_name(Some("ko")), "검색");
209 assert_eq!(desc.get_name(Some("en")), "Web Search");
210 assert_eq!(desc.get_description(Some("ko")), "웹 검색");
211 }
212
213 #[test]
214 fn test_provider_health() {
215 let healthy = ProviderHealth::Healthy;
216 assert!(healthy.is_healthy());
217 assert!(healthy.is_available());
218
219 let degraded = ProviderHealth::degraded("Some tools failing");
220 assert!(!degraded.is_healthy());
221 assert!(degraded.is_available());
222
223 let unavailable = ProviderHealth::unavailable("Connection lost");
224 assert!(!unavailable.is_healthy());
225 assert!(!unavailable.is_available());
226 }
227}