systemprompt_provider_contracts/
tool.rs1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use serde_json::Value as JsonValue;
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
7pub struct ToolDefinition {
8 pub name: String,
9 pub description: Option<String>,
10 pub input_schema: Option<JsonValue>,
11 pub output_schema: Option<JsonValue>,
12 pub service_id: String,
13 #[serde(default)]
14 pub terminal_on_success: bool,
15 #[serde(skip_serializing_if = "Option::is_none")]
16 pub model_config: Option<JsonValue>,
17}
18
19impl ToolDefinition {
20 pub fn new(name: impl Into<String>, service_id: impl Into<String>) -> Self {
21 Self {
22 name: name.into(),
23 description: None,
24 input_schema: None,
25 output_schema: None,
26 service_id: service_id.into(),
27 terminal_on_success: false,
28 model_config: None,
29 }
30 }
31
32 pub fn with_description(mut self, description: impl Into<String>) -> Self {
33 self.description = Some(description.into());
34 self
35 }
36
37 pub fn with_input_schema(mut self, schema: JsonValue) -> Self {
38 self.input_schema = Some(schema);
39 self
40 }
41
42 pub fn with_output_schema(mut self, schema: JsonValue) -> Self {
43 self.output_schema = Some(schema);
44 self
45 }
46
47 pub const fn with_terminal_on_success(mut self, terminal: bool) -> Self {
48 self.terminal_on_success = terminal;
49 self
50 }
51
52 pub fn with_model_config(mut self, config: JsonValue) -> Self {
53 self.model_config = Some(config);
54 self
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ToolCallRequest {
60 pub tool_call_id: String,
61 pub name: String,
62 pub arguments: JsonValue,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ToolCallResult {
67 pub content: Vec<ToolContent>,
68 pub structured_content: Option<JsonValue>,
69 pub is_error: Option<bool>,
70 pub meta: Option<JsonValue>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(tag = "type", rename_all = "lowercase")]
75pub enum ToolContent {
76 Text {
77 text: String,
78 },
79 Image {
80 data: String,
81 mime_type: String,
82 },
83 Resource {
84 uri: String,
85 mime_type: Option<String>,
86 },
87}
88
89impl ToolContent {
90 pub fn text(text: impl Into<String>) -> Self {
91 Self::Text { text: text.into() }
92 }
93}
94
95impl ToolCallResult {
96 pub fn success(text: impl Into<String>) -> Self {
97 Self {
98 content: vec![ToolContent::text(text)],
99 structured_content: None,
100 is_error: Some(false),
101 meta: None,
102 }
103 }
104
105 pub fn error(message: impl Into<String>) -> Self {
106 Self {
107 content: vec![ToolContent::text(message)],
108 structured_content: None,
109 is_error: Some(true),
110 meta: None,
111 }
112 }
113
114 pub fn with_structured_content(mut self, content: JsonValue) -> Self {
115 self.structured_content = Some(content);
116 self
117 }
118}
119
120#[derive(Debug, thiserror::Error)]
121pub enum ToolProviderError {
122 #[error("Tool '{0}' not found")]
123 ToolNotFound(String),
124
125 #[error("Service '{0}' not found")]
126 ServiceNotFound(String),
127
128 #[error("Failed to connect to service '{service}': {message}")]
129 ConnectionFailed { service: String, message: String },
130
131 #[error("Tool execution failed: {0}")]
132 ExecutionFailed(String),
133
134 #[error("Authorization failed: {0}")]
135 AuthorizationFailed(String),
136
137 #[error("Configuration error: {0}")]
138 ConfigurationError(String),
139
140 #[error("Internal error: {0}")]
141 Internal(String),
142}
143
144impl From<anyhow::Error> for ToolProviderError {
145 fn from(err: anyhow::Error) -> Self {
146 Self::Internal(err.to_string())
147 }
148}
149
150#[derive(Debug, Clone)]
151pub struct ToolContext {
152 pub auth_token: String,
153 pub session_id: Option<String>,
154 pub trace_id: Option<String>,
155 pub ai_tool_call_id: Option<String>,
156 pub headers: HashMap<String, String>,
157}
158
159impl ToolContext {
160 pub fn new(auth_token: impl Into<String>) -> Self {
161 Self {
162 auth_token: auth_token.into(),
163 session_id: None,
164 trace_id: None,
165 ai_tool_call_id: None,
166 headers: HashMap::new(),
167 }
168 }
169
170 pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
171 self.session_id = Some(session_id.into());
172 self
173 }
174
175 pub fn with_trace_id(mut self, trace_id: impl Into<String>) -> Self {
176 self.trace_id = Some(trace_id.into());
177 self
178 }
179
180 pub fn with_ai_tool_call_id(mut self, id: impl Into<String>) -> Self {
181 self.ai_tool_call_id = Some(id.into());
182 self
183 }
184
185 pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
186 self.headers.insert(key.into(), value.into());
187 self
188 }
189}
190
191pub type ToolProviderResult<T> = Result<T, ToolProviderError>;
192
193#[async_trait]
194pub trait ToolProvider: Send + Sync {
195 async fn list_tools(
196 &self,
197 agent_name: &str,
198 context: &ToolContext,
199 ) -> ToolProviderResult<Vec<ToolDefinition>>;
200
201 async fn call_tool(
202 &self,
203 request: &ToolCallRequest,
204 service_id: &str,
205 context: &ToolContext,
206 ) -> ToolProviderResult<ToolCallResult>;
207
208 async fn refresh_connections(&self, agent_name: &str) -> ToolProviderResult<()>;
209
210 async fn health_check(&self) -> ToolProviderResult<HashMap<String, bool>>;
211
212 async fn find_tool(
213 &self,
214 agent_name: &str,
215 tool_name: &str,
216 context: &ToolContext,
217 ) -> ToolProviderResult<Option<ToolDefinition>> {
218 let tools = self.list_tools(agent_name, context).await?;
219 Ok(tools.into_iter().find(|t| t.name == tool_name))
220 }
221}