Skip to main content

codetether_agent/search/
engine.rs

1//! End-to-end: resolve model → ask router → run chosen backends → collect.
2
3use anyhow::{Context, Result};
4use std::sync::Arc;
5
6use crate::provider::ProviderRegistry;
7
8use super::dispatch::run_choice;
9use super::model::resolve_router_model;
10use super::parse::parse_router_response;
11use super::request::build_router_request;
12use super::result::{BackendRun, RouterResult};
13
14/// Execute the full search pipeline.
15///
16/// Picks up to `top_n` backends from the router's plan (1 when the caller
17/// wants a single best hit; >1 for fan-out).
18pub async fn run_router_search(
19    registry: Arc<ProviderRegistry>,
20    router_model: &str,
21    query: &str,
22    top_n: usize,
23) -> Result<RouterResult> {
24    let (provider, model_id) = resolve_router_model(&registry, router_model)?;
25    let request = build_router_request(&model_id, query, top_n.max(1));
26    let response = provider
27        .complete(request)
28        .await
29        .context("router model call failed")?;
30    let raw = collect_text(&response);
31    let plan = parse_router_response(&raw)?;
32    let mut runs = Vec::with_capacity(plan.choices.len().min(top_n.max(1)));
33    for choice in plan.choices.into_iter().take(top_n.max(1)) {
34        let tool_result = run_choice(&choice, query).await?;
35        runs.push(BackendRun {
36            backend: choice.backend,
37            success: tool_result.success,
38            output: tool_result.output,
39            metadata: serde_json::to_value(tool_result.metadata).unwrap_or_default(),
40        });
41    }
42    Ok(RouterResult {
43        query: query.to_string(),
44        router_model: format!("{}/{}", provider.name(), model_id),
45        runs,
46    })
47}
48
49fn collect_text(response: &crate::provider::CompletionResponse) -> String {
50    response
51        .message
52        .content
53        .iter()
54        .filter_map(|part| match part {
55            crate::provider::ContentPart::Text { text }
56            | crate::provider::ContentPart::Thinking { text } => Some(text.as_str()),
57            _ => None,
58        })
59        .collect::<Vec<_>>()
60        .join("\n")
61}