Skip to main content

ai_agents_tools/
provider.rs

1use 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    /// Policy bindings supplied by provider descriptors when available.
20    #[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    /// Attach policy bindings advertised by the provider.
53    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            if let Some(ref aliases) = self.aliases {
61                if let Some(name) = aliases.get_name(lang) {
62                    return name;
63                }
64            }
65        }
66        &self.name
67    }
68
69    pub fn get_description(&self, lang: Option<&str>) -> &str {
70        if let Some(lang) = lang {
71            if let Some(ref aliases) = self.aliases {
72                if let Some(desc) = aliases.get_description(lang) {
73                    return desc;
74                }
75            }
76        }
77        &self.description
78    }
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, Default)]
82#[serde(tag = "status", rename_all = "snake_case")]
83pub enum ProviderHealth {
84    #[default]
85    Healthy,
86    Degraded {
87        message: String,
88    },
89    Unavailable {
90        message: String,
91    },
92}
93
94impl ProviderHealth {
95    pub fn is_healthy(&self) -> bool {
96        matches!(self, ProviderHealth::Healthy)
97    }
98
99    pub fn is_available(&self) -> bool {
100        !matches!(self, ProviderHealth::Unavailable { .. })
101    }
102
103    pub fn degraded(message: impl Into<String>) -> Self {
104        ProviderHealth::Degraded {
105            message: message.into(),
106        }
107    }
108
109    pub fn unavailable(message: impl Into<String>) -> Self {
110        ProviderHealth::Unavailable {
111            message: message.into(),
112        }
113    }
114}
115
116#[derive(Debug, thiserror::Error)]
117pub enum ToolProviderError {
118    #[error("Tool not found: {0}")]
119    ToolNotFound(String),
120
121    #[error("Execution failed: {0}")]
122    ExecutionFailed(String),
123
124    #[error("Provider unavailable: {0}")]
125    Unavailable(String),
126
127    #[error("Connection error: {0}")]
128    ConnectionError(String),
129
130    #[error("Configuration error: {0}")]
131    ConfigError(String),
132
133    #[error("Timeout after {0}ms")]
134    Timeout(u64),
135
136    #[error("{0}")]
137    Other(String),
138}
139
140#[async_trait]
141pub trait ToolProvider: Send + Sync {
142    fn id(&self) -> &str;
143
144    fn name(&self) -> &str;
145
146    fn provider_type(&self) -> ToolProviderType;
147
148    async fn list_tools(&self) -> Vec<ToolDescriptor>;
149
150    async fn get_tool(&self, tool_id: &str) -> Option<Arc<dyn Tool>>;
151
152    async fn execute(
153        &self,
154        tool_id: &str,
155        args: Value,
156        ctx: ToolExecutionContext,
157    ) -> Result<ToolResult, ToolProviderError> {
158        if let Some(tool) = self.get_tool(tool_id).await {
159            Ok(tool.execute(args, ctx).await)
160        } else {
161            Err(ToolProviderError::ToolNotFound(tool_id.to_string()))
162        }
163    }
164
165    fn supports_refresh(&self) -> bool {
166        false
167    }
168
169    async fn refresh(&self) -> Result<(), ToolProviderError> {
170        Ok(())
171    }
172
173    async fn health_check(&self) -> ProviderHealth {
174        ProviderHealth::Healthy
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_tool_descriptor() {
184        let desc = ToolDescriptor::new(
185            "search",
186            "Web Search",
187            "Search the web",
188            serde_json::json!({"type": "object"}),
189        );
190
191        assert_eq!(desc.id, "search");
192        assert_eq!(desc.get_name(None), "Web Search");
193        assert_eq!(desc.get_description(None), "Search the web");
194    }
195
196    #[test]
197    fn test_tool_descriptor_with_aliases() {
198        let aliases = ToolAliases::new()
199            .with_name("ko", "검색")
200            .with_description("ko", "웹 검색");
201
202        let desc = ToolDescriptor::new(
203            "search",
204            "Web Search",
205            "Search the web",
206            serde_json::json!({}),
207        )
208        .with_aliases(aliases);
209
210        assert_eq!(desc.get_name(Some("ko")), "검색");
211        assert_eq!(desc.get_name(Some("en")), "Web Search");
212        assert_eq!(desc.get_description(Some("ko")), "웹 검색");
213    }
214
215    #[test]
216    fn test_provider_health() {
217        let healthy = ProviderHealth::Healthy;
218        assert!(healthy.is_healthy());
219        assert!(healthy.is_available());
220
221        let degraded = ProviderHealth::degraded("Some tools failing");
222        assert!(!degraded.is_healthy());
223        assert!(degraded.is_available());
224
225        let unavailable = ProviderHealth::unavailable("Connection lost");
226        assert!(!unavailable.is_healthy());
227        assert!(!unavailable.is_available());
228    }
229}