use std::sync::Arc;
use async_trait::async_trait;
use edgecrab_tools::registry::{DelegationEvent, SubAgentRunRequest, ToolRegistry};
use edgecrab_tools::{SubAgentResult, SubAgentRunner};
use edgecrab_types::Platform;
use edgequake_llm::LLMProvider;
pub struct CoreSubAgentRunner {
provider: Arc<dyn LLMProvider>,
tool_registry: Arc<ToolRegistry>,
platform: Platform,
model: String,
}
impl CoreSubAgentRunner {
pub fn new(
provider: Arc<dyn LLMProvider>,
tool_registry: Arc<ToolRegistry>,
platform: Platform,
model: String,
) -> Self {
Self {
provider,
tool_registry,
platform,
model,
}
}
}
#[async_trait]
impl SubAgentRunner for CoreSubAgentRunner {
async fn run_task(&self, request: SubAgentRunRequest) -> Result<SubAgentResult, String> {
let SubAgentRunRequest {
goal,
system_prompt,
enabled_toolsets,
max_iterations,
model_override,
parent_cancel,
progress_tx,
task_index,
task_count,
} = request;
let (child_provider, child_model) =
self.resolve_child_provider_and_model(model_override.as_deref())?;
let child = Arc::new(
crate::AgentBuilder::new(&child_model)
.provider(child_provider)
.tools(self.tool_registry.clone())
.max_iterations(max_iterations)
.platform(self.platform)
.quiet_mode(true)
.build()
.map_err(|e| format!("Failed to build child agent: {e}"))?,
);
{
let mut config = child.config.write().await;
config.enabled_toolsets = enabled_toolsets;
config.disabled_toolsets.push("delegation".to_string());
config.skip_memory = true;
config.skip_context_files = true;
}
let child_for_cancel = child.clone();
let cancel_watch = tokio::spawn(async move {
parent_cancel.cancelled().await;
child_for_cancel.interrupt();
});
let (child_event_tx, mut child_event_rx) = tokio::sync::mpsc::unbounded_channel();
let progress_forwarder = if let Some(progress_tx) = progress_tx {
Some(tokio::spawn(async move {
while let Some(event) = child_event_rx.recv().await {
match event {
crate::StreamEvent::Reasoning(text) => {
if !text.trim().is_empty() {
let _ = progress_tx.send(DelegationEvent::Thinking {
task_index,
task_count,
text,
});
}
}
crate::StreamEvent::ToolExec {
name, args_json, ..
} => {
let _ = progress_tx.send(DelegationEvent::ToolCalled {
task_index,
task_count,
tool_name: name,
args_json,
});
}
_ => {}
}
}
}))
} else {
drop(child_event_rx);
None
};
let result = child
.execute_loop(
&goal,
Some(&system_prompt),
None,
Some(&child_event_tx),
None,
)
.await;
cancel_watch.abort();
drop(child_event_tx);
if let Some(forwarder) = progress_forwarder {
let _ = forwarder.await;
}
let result = result.map_err(|e| format!("Child agent execution failed: {e}"))?;
Ok(SubAgentResult {
summary: result.final_response,
api_calls: result.api_calls,
input_tokens: result.usage.input_tokens,
output_tokens: result.usage.output_tokens,
cache_read_tokens: result.usage.cache_read_tokens,
cache_write_tokens: result.usage.cache_write_tokens,
reasoning_tokens: result.usage.reasoning_tokens,
model: Some(result.model),
interrupted: result.interrupted,
budget_exhausted: result.budget_exhausted,
messages: result.messages,
})
}
}
impl CoreSubAgentRunner {
fn resolve_child_provider_and_model(
&self,
model_override: Option<&str>,
) -> Result<(Arc<dyn LLMProvider>, String), String> {
let Some(raw_model) = model_override.map(str::trim).filter(|m| !m.is_empty()) else {
return Ok((self.provider.clone(), self.model.clone()));
};
if let Some((provider_name, model_name)) = raw_model.split_once('/') {
let canonical = edgecrab_tools::vision_models::normalize_provider_name(provider_name);
let provider = edgecrab_tools::create_provider_for_model(&canonical, model_name)
.map_err(|e| {
format!(
"Failed to create delegation provider '{}' for model '{}': {}",
canonical, model_name, e
)
})?;
return Ok((provider, raw_model.to_string()));
}
Ok((self.provider.clone(), raw_model.to_string()))
}
}