codetether_agent/search/
engine.rs1use 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
14pub 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(®istry, 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}