synaptic_tools/
jina_reader.rs1use async_trait::async_trait;
4use serde_json::{json, Value};
5use synaptic_core::{SynapticError, Tool};
6
7pub struct JinaReaderTool {
23 client: reqwest::Client,
24}
25
26impl JinaReaderTool {
27 pub fn new() -> Self {
29 Self {
30 client: reqwest::Client::new(),
31 }
32 }
33}
34
35impl Default for JinaReaderTool {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41#[async_trait]
42impl Tool for JinaReaderTool {
43 fn name(&self) -> &'static str {
44 "jina_reader"
45 }
46
47 fn description(&self) -> &'static str {
48 "Convert any web page URL to clean Markdown for LLM consumption. Removes ads, navigation, and boilerplate. Free to use, no API key required."
49 }
50
51 fn parameters(&self) -> Option<Value> {
52 Some(json!({
53 "type": "object",
54 "properties": {
55 "url": {
56 "type": "string",
57 "description": "The URL to fetch and convert to Markdown"
58 }
59 },
60 "required": ["url"]
61 }))
62 }
63
64 async fn call(&self, args: Value) -> Result<Value, SynapticError> {
65 let url = args["url"]
66 .as_str()
67 .ok_or_else(|| SynapticError::Tool("missing 'url' parameter".to_string()))?;
68
69 let reader_url = format!("https://r.jina.ai/{}", url);
70 let resp = self
71 .client
72 .get(&reader_url)
73 .header("Accept", "text/markdown")
74 .header("X-Return-Format", "markdown")
75 .send()
76 .await
77 .map_err(|e| SynapticError::Tool(format!("Jina Reader request: {e}")))?;
78
79 let status = resp.status().as_u16();
80 let content = resp
81 .text()
82 .await
83 .map_err(|e| SynapticError::Tool(format!("Jina Reader parse: {e}")))?;
84
85 if status != 200 {
86 return Err(SynapticError::Tool(format!(
87 "Jina Reader error ({})",
88 status
89 )));
90 }
91
92 Ok(json!({
93 "url": url,
94 "content": content,
95 }))
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn tool_metadata() {
105 let tool = JinaReaderTool::new();
106 assert_eq!(tool.name(), "jina_reader");
107 assert!(!tool.description().is_empty());
108 }
109
110 #[test]
111 fn tool_schema() {
112 let tool = JinaReaderTool::new();
113 let schema = tool.parameters().unwrap();
114 assert_eq!(schema["type"], "object");
115 assert!(schema["properties"]["url"].is_object());
116 }
117
118 #[test]
119 fn default_impl() {
120 let _tool = JinaReaderTool::default();
121 }
122
123 #[tokio::test]
124 async fn missing_url_returns_error() {
125 let tool = JinaReaderTool::new();
126 let result = tool.call(json!({})).await;
127 assert!(result.is_err());
128 assert!(result.unwrap_err().to_string().contains("url"));
129 }
130}