Skip to main content

atomr_agents_deep_research_harness/
dispatch.rs

1//! Object-safe dispatch trait + the public type-erased handle.
2
3use std::sync::Arc;
4
5use async_trait::async_trait;
6use atomr_agents_callable::Callable;
7use atomr_agents_core::{AgentError, CallCtx, HarnessId, Result as CoreResult, Value};
8use atomr_agents_deep_research_core::ResearchRequest;
9
10/// Object-safe trait every deep-research harness implements.
11#[async_trait]
12pub trait DeepResearchHarnessDispatch: Send + Sync + 'static {
13    async fn dispatch(&self, request: ResearchRequest) -> CoreResult<Value>;
14}
15
16/// Public type-erased handle.
17#[derive(Clone)]
18pub struct DeepResearchHarnessRef {
19    pub id: HarnessId,
20    inner: Arc<dyn DeepResearchHarnessDispatch>,
21}
22
23impl DeepResearchHarnessRef {
24    pub fn new(id: HarnessId, inner: Arc<dyn DeepResearchHarnessDispatch>) -> Self {
25        Self { id, inner }
26    }
27
28    /// Run the harness and return the serialized [`ResearchResult`].
29    pub async fn run(&self, request: ResearchRequest) -> CoreResult<Value> {
30        self.inner.dispatch(request).await
31    }
32}
33
34#[async_trait]
35impl Callable for DeepResearchHarnessRef {
36    async fn call(&self, input: Value, _ctx: CallCtx) -> CoreResult<Value> {
37        let request: ResearchRequest = parse_request(input)?;
38        self.run(request).await
39    }
40
41    fn label(&self) -> &str {
42        self.id.as_str()
43    }
44}
45
46/// Parse a JSON `Value` into a [`ResearchRequest`]. Accepts the full
47/// request as a JSON object, or a bare string as shorthand for
48/// `{"query": "..."}`.
49pub fn parse_request(input: Value) -> CoreResult<ResearchRequest> {
50    if let Some(s) = input.as_str() {
51        return Ok(ResearchRequest::new(s));
52    }
53    serde_json::from_value(input)
54        .map_err(|e| AgentError::Harness(format!("deep-research: invalid request: {e}")))
55}