use std::sync::Arc;
use bitrouter_core::errors::{BitrouterError, Result};
use bitrouter_core::tools::provider::ToolProvider;
use bitrouter_core::tools::result::{ToolCallResult, ToolContent};
pub struct RestToolProvider {
name: String,
api_base: String,
auth_header: Option<(String, String)>,
client: Arc<reqwest::Client>,
}
impl RestToolProvider {
pub fn new(
name: String,
api_base: String,
auth_header: Option<(String, String)>,
client: Arc<reqwest::Client>,
) -> Self {
let api_base = api_base.trim_end_matches('/').to_owned();
Self {
name,
api_base,
auth_header,
client,
}
}
}
impl ToolProvider for RestToolProvider {
fn provider_name(&self) -> &str {
&self.name
}
async fn call_tool(
&self,
tool_id: &str,
arguments: serde_json::Value,
) -> Result<ToolCallResult> {
let url = format!("{}/{}", self.api_base, tool_id);
let mut request = self.client.post(&url).json(&arguments);
if let Some((ref header_name, ref header_value)) = self.auth_header {
request = request.header(header_name.as_str(), header_value.as_str());
}
let response = request
.send()
.await
.map_err(|e| BitrouterError::transport(Some(&self.name), e.to_string()))?;
let status = response.status();
let body = response
.text()
.await
.map_err(|e| BitrouterError::transport(Some(&self.name), e.to_string()))?;
if !status.is_success() {
return Ok(ToolCallResult {
content: vec![ToolContent::Text {
text: format!("{} error ({}): {}", tool_id, status.as_u16(), body),
}],
is_error: true,
metadata: Some(serde_json::json!({ "status": status.as_u16() })),
});
}
let content = match serde_json::from_str::<serde_json::Value>(&body) {
Ok(data) => vec![ToolContent::Json { data }],
Err(_) => vec![ToolContent::Text { text: body }],
};
Ok(ToolCallResult {
content,
is_error: false,
metadata: None,
})
}
}