bitrouter_api/router/
tools.rs1use std::sync::Arc;
12
13use bitrouter_core::routers::registry::ToolRegistry;
14use serde::Serialize;
15use warp::Filter;
16
17#[derive(Debug, Default)]
19struct ToolQuery {
20 provider: Option<String>,
22 id: Option<String>,
24}
25
26pub fn tools_filter<T>(
31 registry: Option<Arc<T>>,
32) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone
33where
34 T: ToolRegistry + 'static,
35{
36 warp::path!("v1" / "tools")
37 .and(warp::get())
38 .and(optional_raw_query())
39 .and(warp::any().map(move || registry.clone()))
40 .and_then(handle_list_tools)
41}
42
43fn optional_raw_query()
46-> impl Filter<Extract = (Option<String>,), Error = std::convert::Infallible> + Clone {
47 warp::query::raw()
48 .map(Some)
49 .or(warp::any().map(|| None))
50 .unify()
51}
52
53#[derive(Serialize)]
54struct ToolResponse {
55 id: String,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 name: Option<String>,
58 provider: String,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 description: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 input_schema: Option<serde_json::Value>,
63}
64
65fn parse_query(raw: &str) -> ToolQuery {
66 let mut query = ToolQuery::default();
67 for pair in raw.split('&') {
68 if let Some((key, value)) = pair.split_once('=') {
69 match key {
70 "provider" => query.provider = Some(value.to_owned()),
71 "id" => query.id = Some(value.to_owned()),
72 _ => {}
73 }
74 }
75 }
76 query
77}
78
79async fn handle_list_tools<T: ToolRegistry>(
80 raw_query: Option<String>,
81 registry: Option<Arc<T>>,
82) -> Result<impl warp::Reply, warp::Rejection> {
83 let Some(registry) = registry else {
84 return Err(warp::reject::not_found());
85 };
86 let query = raw_query.as_deref().map(parse_query).unwrap_or_default();
87 let entries = registry.list_tools().await;
88 let id_lower = query.id.as_deref().map(str::to_lowercase);
89
90 let tools: Vec<ToolResponse> = entries
91 .into_iter()
92 .filter(|e| {
93 if query.provider.as_deref().is_some_and(|p| e.provider != p) {
94 return false;
95 }
96 if id_lower
97 .as_deref()
98 .is_some_and(|s| !e.id.to_lowercase().contains(s))
99 {
100 return false;
101 }
102 true
103 })
104 .map(|e| {
105 let input_schema = e
106 .definition
107 .input_schema
108 .and_then(|s| serde_json::to_value(s).ok());
109 ToolResponse {
110 id: e.id,
111 name: Some(e.definition.name),
112 provider: e.provider,
113 description: e.definition.description,
114 input_schema,
115 }
116 })
117 .collect();
118 Ok(warp::reply::json(&serde_json::json!({ "tools": tools })))
119}