use std::collections::BTreeMap;
use std::sync::Arc;
use defect_agent::hooks::HookEngine;
use defect_agent::hooks::builtin::BuiltinRegistry;
use defect_agent::llm::ProviderRegistry;
use defect_agent::policy::SandboxPolicy;
use defect_agent::session::{CompositeRegistry, StaticToolRegistry, ToolRegistry};
use defect_agent::tool::{
CancelBackgroundTaskTool, InspectBackgroundTaskTool, SkillEntry, SkillTool, SpawnAgentTool,
SubagentProfile,
};
use defect_config::{LoadedConfig, ProfileSpec, SkillSpec};
use defect_tools::{BashTool, EditFileTool, FetchTool, ReadFileTool, SearchTool, WriteFileTool};
use crate::hooks::{HookEngineBuildError, HookEngineCtx, build_engine_arc};
pub fn build_process_tools(config: &LoadedConfig) -> Arc<dyn ToolRegistry> {
let mut builder = StaticToolRegistry::builder()
.insert(Arc::new(BashTool::from_config(
&config.effective.tools.bash,
)))
.insert(Arc::new(ReadFileTool::from_config(
&config.effective.tools.fs,
)))
.insert(Arc::new(WriteFileTool::new()))
.insert(Arc::new(EditFileTool::new()));
if config.effective.tools.fetch.enabled {
builder = builder.insert(Arc::new(FetchTool::from_config(
&config.effective.tools.fetch,
)));
}
if config.effective.tools.search.enabled {
builder = builder.insert(Arc::new(SearchTool::from_config(
&config.effective.tools.search,
)));
}
Arc::new(builder.build())
}
pub fn filter_tools_by_allowlist(
base: &Arc<dyn ToolRegistry>,
allow: &[String],
) -> Result<Arc<dyn ToolRegistry>, String> {
let mut builder = StaticToolRegistry::builder();
for name in allow {
if name == "spawn_agent" {
continue;
}
match base.get(name) {
Some(tool) => builder = builder.insert(tool),
None => return Err(name.clone()),
}
}
Ok(Arc::new(builder.build()))
}
fn project_profiles(
specs: &BTreeMap<String, ProfileSpec>,
builtins: &BuiltinRegistry,
hook_rt: &HookEngineCtx<'_>,
) -> Result<BTreeMap<String, SubagentProfile>, ProfileHookBuildError> {
specs
.iter()
.map(|(name, spec)| {
let hooks = if spec.hooks.is_empty() {
None
} else {
let engine = build_engine_arc(&spec.hooks, builtins, hook_rt).map_err(|err| {
ProfileHookBuildError {
profile: name.clone(),
source: err,
}
})?;
Some(engine as Arc<dyn HookEngine>)
};
Ok((
name.clone(),
SubagentProfile {
description: spec.description.clone(),
model: spec.model.clone(),
system_prompt: spec.system_prompt_text.clone(),
tool_allow: spec.tool_allow.clone(),
sampling: spec.sampling.clone(),
hooks,
},
))
})
.collect()
}
#[derive(Debug, thiserror::Error)]
#[error("subagent profile `{profile}` hook engine build failed: {source}")]
pub struct ProfileHookBuildError {
pub profile: String,
#[source]
pub source: HookEngineBuildError,
}
pub fn project_skills(specs: &BTreeMap<String, SkillSpec>) -> BTreeMap<String, SkillEntry> {
specs
.iter()
.map(|(name, spec)| {
(
name.clone(),
SkillEntry {
description: spec.description.clone(),
body: spec.body.clone(),
dir: spec.dir.clone(),
always: spec.always,
triggers: spec.triggers.clone(),
},
)
})
.collect()
}
#[allow(clippy::too_many_arguments)]
pub fn build_process_tools_with_subagents(
config: &LoadedConfig,
profiles: &BTreeMap<String, ProfileSpec>,
skills: &BTreeMap<String, SkillEntry>,
registry: &Arc<ProviderRegistry>,
policy: &Arc<dyn SandboxPolicy>,
base_prompt: Option<String>,
builtins: &BuiltinRegistry,
hook_rt: &HookEngineCtx<'_>,
) -> Result<Arc<dyn ToolRegistry>, ProfileHookBuildError> {
let base = build_process_tools(config);
let projected = project_profiles(profiles, builtins, hook_rt)?;
let has_profiles = SpawnAgentTool::has_profiles(&projected);
let has_skills = SkillTool::has_skills(skills);
if !has_profiles && !has_skills {
return Ok(base);
}
let mut overlay = StaticToolRegistry::builder();
if has_profiles {
let spawn = SpawnAgentTool::new(
Arc::new(projected),
registry.clone(),
policy.clone(),
base.clone(),
base_prompt,
);
overlay = overlay.insert(Arc::new(spawn));
overlay = overlay.insert(Arc::new(InspectBackgroundTaskTool::new()));
overlay = overlay.insert(Arc::new(CancelBackgroundTaskTool::new()));
}
if has_skills {
let skill = SkillTool::new(Arc::new(skills.clone()));
overlay = overlay.insert(Arc::new(skill));
}
let overlay_reg: Arc<dyn ToolRegistry> = Arc::new(overlay.build());
Ok(Arc::new(CompositeRegistry::new(overlay_reg, base)))
}