use crate::{
error::{RainyError, Result},
search::{DeepResearchResponse, ResearchConfig, SearchExtractResponse, SearchResponse},
RainyClient,
};
use serde_json::json;
impl RainyClient {
pub async fn research(
&self,
topic: impl Into<String>,
config: Option<ResearchConfig>,
) -> Result<DeepResearchResponse> {
let cfg = config.unwrap_or_default();
let topic = topic.into();
let search_depth = Some(cfg.depth);
let max_results = Some(cfg.max_sources.min(20));
let envelope = self.search(topic, search_depth, max_results).await?;
let results_json = envelope
.results
.iter()
.map(|item| {
json!({
"title": item.title,
"url": item.url,
"snippet": item.snippet.as_ref().or(item.content.as_ref()),
})
})
.collect::<Vec<_>>();
let synthesized_content = envelope
.results
.iter()
.enumerate()
.map(|(idx, item)| {
let title = item.title.as_deref().unwrap_or("Untitled");
let url = item.url.as_deref().unwrap_or("");
let snippet = item
.snippet
.as_deref()
.or(item.content.as_deref())
.unwrap_or("");
format!("{}. {}\n{}\n{}", idx + 1, title, url, snippet)
})
.collect::<Vec<_>>()
.join("\n\n");
Ok(DeepResearchResponse {
success: true,
mode: "sync".to_string(),
result: Some(json!({
"content": synthesized_content,
"results": results_json,
})),
task_id: None,
generated_at: None,
provider: Some("tavily".to_string()),
message: None,
})
}
pub async fn search(
&self,
query: impl Into<String>,
depth: Option<crate::models::ResearchDepth>,
max_results: Option<u32>,
) -> Result<SearchResponse> {
#[derive(serde::Deserialize)]
struct SearchEnvelope {
data: SearchResponse,
}
let url = self.api_v1_url("/search");
let search_depth = match depth.unwrap_or(crate::models::ResearchDepth::Basic) {
crate::models::ResearchDepth::Advanced => "advanced",
_ => "basic",
};
let request = json!({
"query": query.into(),
"searchDepth": search_depth,
"maxResults": max_results.unwrap_or(10).clamp(1, 20),
});
let response = self
.http_client()
.post(&url)
.json(&request)
.send()
.await
.map_err(|e| RainyError::Network {
message: e.to_string(),
retryable: true,
source_error: Some(e.to_string()),
})?;
let envelope: SearchEnvelope = self.handle_response(response).await?;
Ok(envelope.data)
}
pub async fn search_extract(&self, urls: Vec<String>) -> Result<SearchExtractResponse> {
#[derive(serde::Deserialize)]
struct ExtractEnvelope {
data: SearchExtractResponse,
}
let url = self.api_v1_url("/search/extract");
let request = json!({
"urls": urls,
});
let response = self
.http_client()
.post(&url)
.json(&request)
.send()
.await
.map_err(|e| RainyError::Network {
message: e.to_string(),
retryable: true,
source_error: Some(e.to_string()),
})?;
let envelope: ExtractEnvelope = self.handle_response(response).await?;
Ok(envelope.data)
}
}