use super::planner;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Citation {
pub index: usize,
pub source: String,
pub url: Option<String>,
pub snippet: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ResearchReport {
pub markdown: String,
pub citations: Vec<Citation>,
pub subtopics: Vec<String>,
pub rounds_completed: usize,
}
pub(crate) fn parse_gap_queries(
content: &str,
original_gaps: &[String],
) -> Result<Vec<String>, super::ResearchError> {
#[derive(serde::Deserialize)]
struct GapMapping {
gap: String,
queries: Vec<String>,
}
let json_str = planner::extract_json(content);
let mappings: Vec<GapMapping> = serde_json::from_str(&json_str).map_err(|e| {
let preview: String = content.chars().take(200).collect();
super::ResearchError::Llm(format!(
"failed to parse gap→query mapping: {} | raw: {}",
e, preview
))
})?;
let mut all_queries: Vec<String> = Vec::new();
let mut covered_gaps: std::collections::HashSet<usize> = std::collections::HashSet::new();
for mapping in &mappings {
for (i, gap) in original_gaps.iter().enumerate() {
if !covered_gaps.contains(&i)
&& (mapping.gap.contains(gap.as_str()) || gap.contains(mapping.gap.as_str()))
{
covered_gaps.insert(i);
}
}
all_queries.extend(mapping.queries.iter().filter(|q| !q.is_empty()).cloned());
}
for (i, gap) in original_gaps.iter().enumerate() {
if !covered_gaps.contains(&i) {
log::warn!(
"Deep Research: gap '{}' has no follow-up queries, using gap as query",
gap
);
all_queries.push(gap.clone());
}
}
Ok(all_queries)
}