#[cfg(feature = "tower")]
mod tower_impl {
use crate::{ToolMeta, ToolError};
use futures::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use tower::Service;
#[derive(Debug, Clone)]
pub struct ToolRequest {
pub name: String,
pub params: serde_json::Value,
pub ctx: Arc<crate::ToolContext>,
}
#[derive(Debug, Clone)]
pub struct ToolResponse {
pub output: serde_json::Value,
}
pub struct ToolService {
_meta: Option<&'static ToolMeta>,
}
impl Clone for ToolService {
fn clone(&self) -> Self {
Self { _meta: self._meta }
}
}
impl ToolService {
pub fn new() -> Self {
Self { _meta: None }
}
pub fn for_tool(meta: &'static ToolMeta) -> Self {
Self {
_meta: Some(meta),
}
}
}
impl Default for ToolService {
fn default() -> Self {
Self::new()
}
}
impl Service<ToolRequest> for ToolService {
type Response = ToolResponse;
type Error = ToolError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: ToolRequest) -> Self::Future {
Box::pin(async move {
let output = crate::call_tool(&req.name, req.params, req.ctx).await?;
Ok(ToolResponse { output })
})
}
}
pub fn tool_service() -> ToolService {
ToolService::new()
}
pub fn tool_service_for(name: &str) -> Option<ToolService> {
crate::find_tool(name).map(ToolService::for_tool)
}
}
#[cfg(feature = "tower")]
pub use tower_impl::*;
#[cfg(test)]
#[cfg(feature = "tower")]
mod tests {
use super::*;
use crate::{Tool, ToolContext};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tower::Service;
#[derive(Deserialize, JsonSchema)]
struct TestParams {
value: i32,
}
#[derive(Serialize, JsonSchema)]
struct TestOutput {
result: i32,
}
struct TestTool;
impl Tool for TestTool {
type Params = TestParams;
type Output = TestOutput;
const NAME: &'static str = "test_tower";
const DESCRIPTION: &'static str = "Test tool for Tower";
async fn call(
_ctx: Arc<ToolContext>,
params: Self::Params,
) -> Result<Self::Output, crate::ToolError> {
Ok(TestOutput {
result: params.value * 2,
})
}
}
inventory::submit! {
crate::ToolMeta {
name: TestTool::NAME,
description: TestTool::DESCRIPTION,
call: |ctx, params| {
Box::pin(async move {
let params: TestParams = serde_json::from_value(params)?;
let result = <TestTool as Tool>::call(ctx, params).await?;
Ok(serde_json::to_value(result)?)
})
},
schema: || <TestTool as Tool>::schema(),
examples: || <TestTool as Tool>::EXAMPLES,
}
}
#[tokio::test]
async fn test_tool_service_basic() {
let mut service = ToolService::new();
let req = ToolRequest {
name: "test_tower".to_string(),
params: serde_json::json!({"value": 5}),
ctx: Arc::new(ToolContext::new()),
};
let response = service.call(req).await.unwrap();
assert_eq!(response.output["result"], 10);
}
#[tokio::test]
async fn test_tool_service_not_found() {
let mut service = ToolService::new();
let req = ToolRequest {
name: "nonexistent".to_string(),
params: serde_json::json!({}),
ctx: Arc::new(ToolContext::new()),
};
let result = service.call(req).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_tool_service_for_tool() {
let meta = crate::find_tool("test_tower").unwrap();
let mut service = ToolService::for_tool(meta);
let req = ToolRequest {
name: "test_tower".to_string(),
params: serde_json::json!({"value": 7}),
ctx: Arc::new(ToolContext::new()),
};
let response = service.call(req).await.unwrap();
assert_eq!(response.output["result"], 14);
}
#[tokio::test]
async fn test_convenience_functions() {
let service = tool_service();
let _ = service;
let service = tool_service_for("test_tower");
assert!(service.is_some());
let service = tool_service_for("nonexistent");
assert!(service.is_none());
}
}