use std::path::{Path, PathBuf};
use rho_sdk::SystemPrompt;
use {
crate::agent::{PromptPolicy, ToolCapability},
crate::config::Config,
crate::diagnostics::RuntimeDiagnostics,
crate::prompt,
crate::tools::{
advisor::AdvisorSessionStore,
agent::BackgroundSubagents,
sdk_registry::{AppToolSet, DelegationConfig, ToolSetOptions},
},
};
use super::agent_binding::BoundAgent;
pub(crate) struct ToolsAndPromptOptions<'a> {
pub(crate) config: &'a Config,
pub(crate) config_path: PathBuf,
pub(crate) cwd: &'a Path,
pub(crate) no_system_prompt: bool,
pub(crate) no_tools: bool,
pub(crate) no_subagents: bool,
pub(crate) questionnaire_enabled: bool,
pub(crate) background_subagents: BackgroundSubagents,
pub(crate) diagnostics: &'a RuntimeDiagnostics,
pub(crate) agent: &'a BoundAgent,
}
pub(crate) struct StartupInventory {
pub(crate) mcp: crate::tools::mcp::McpSessionReport,
pub(crate) plugins: crate::plugins::PluginLoadReport,
}
pub(crate) struct ToolsAndPrompt {
pub(crate) tools: AppToolSet,
pub(crate) system_prompt: SystemPromptVariants,
pub(crate) inventory: StartupInventory,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct SystemPromptVariants {
without_advisor: SystemPrompt,
with_advisor: SystemPrompt,
}
impl SystemPromptVariants {
pub(crate) fn uniform(prompt: SystemPrompt) -> Self {
Self {
without_advisor: prompt.clone(),
with_advisor: prompt,
}
}
pub(crate) fn for_advisor_mode(&self, enabled: bool) -> SystemPrompt {
if enabled {
self.with_advisor.clone()
} else {
self.without_advisor.clone()
}
}
}
pub(crate) async fn assemble_tools_and_prompt(
options: ToolsAndPromptOptions<'_>,
) -> anyhow::Result<ToolsAndPrompt> {
let native_runtime = options.agent.rho_capabilities().is_some();
let mut capabilities = options
.agent
.rho_capabilities()
.cloned()
.unwrap_or_default();
if options.no_subagents {
capabilities.remove(&ToolCapability::Agent);
capabilities.remove(&ToolCapability::Agents);
}
if !options.questionnaire_enabled {
capabilities.remove(&ToolCapability::Questionnaire);
}
let advisor_capable = capabilities.contains(&ToolCapability::Advisor);
let launch_delegation_enabled = capabilities.contains(&ToolCapability::Agent);
let delegation_enabled =
launch_delegation_enabled || capabilities.contains(&ToolCapability::Agents);
let plugin_discovery =
crate::plugins::discover(options.cwd, crate::paths::home_dir().as_deref());
crate::plugins::log(&plugin_discovery.report);
let crate::plugins::PluginDiscovery {
skills: plugin_skills,
mcp: plugin_mcp,
report: plugins_report,
} = plugin_discovery;
let mut mcp_config = options.config.mcp.clone();
mcp_config.merge(plugin_mcp);
let mcp_plan = if !native_runtime {
crate::tools::mcp::McpSessionPlan::Inventory(
crate::tools::mcp::McpLoadMode::UnsupportedAgent,
)
} else if options.no_tools {
crate::tools::mcp::McpSessionPlan::Inventory(crate::tools::mcp::McpLoadMode::ToolsDisabled)
} else {
crate::tools::mcp::McpSessionPlan::Connect
};
let mcp = crate::tools::mcp::McpConnectOutcome::run(
mcp_plan,
&mcp_config,
options.config.max_output_bytes,
)
.await;
let tools = if options.no_tools {
AppToolSet::disabled().with_mcp(mcp)
} else {
let mut tool_options = ToolSetOptions::new(capabilities);
let workflow_tracker = crate::tools::workflow_tracker::WorkflowRunTracker::new();
tool_options = tool_options.workflow_tracker(workflow_tracker.clone());
if advisor_capable {
tool_options = tool_options.advisor(AdvisorSessionStore::new());
}
if delegation_enabled {
tool_options = tool_options.delegation(DelegationConfig::new(
options.cwd.to_path_buf(),
options.config_path.clone(),
options.background_subagents,
));
}
if options
.agent
.rho_capabilities()
.is_some_and(|capabilities| capabilities.contains(&ToolCapability::Workflow))
{
tool_options = tool_options.workflow(super::workflow_cli::workflow_tool_service(
options.cwd.to_path_buf(),
Some(options.config_path.clone()),
workflow_tracker,
));
}
AppToolSet::new(options.config, options.diagnostics.clone(), tool_options).with_mcp(mcp)
};
let mcp_report = tools.mcp_report().clone();
let specs = tools.specs();
let system_prompt = if options.no_system_prompt {
options.diagnostics.update_prompt_sources(Vec::new());
SystemPromptVariants::uniform(SystemPrompt::None)
} else {
let (mut text, mut advisor_text) = match options.agent.prompt() {
PromptPolicy::Replace(text) => (text.clone(), text.clone()),
PromptPolicy::Extend(extra) => {
let mut built =
prompt::system_prompt_with_plugin_skills(&specs, options.cwd, plugin_skills);
options.diagnostics.update_prompt_sources(built.sources);
if !launch_delegation_enabled {
prompt::append_subagents_disabled_instruction(&mut built.text);
}
let mut advisor_text = built.text.clone();
prompt::append_advisor_instruction(&mut advisor_text);
if !extra.is_empty() {
let instructions = format!("\n\n# Agent instructions\n\n{extra}");
built.text.push_str(&instructions);
advisor_text.push_str(&instructions);
}
(built.text, advisor_text)
}
};
if text.is_empty() {
text = "You are a coding agent.".into();
advisor_text = text.clone();
}
SystemPromptVariants {
without_advisor: SystemPrompt::Custom(text),
with_advisor: SystemPrompt::Custom(advisor_text),
}
};
if let Some(store) = tools.advisor() {
store.bind_system_prompt(match system_prompt.for_advisor_mode(true) {
SystemPrompt::Custom(text) => Some(text),
_ => None,
});
}
options.diagnostics.update_tools(&specs);
Ok(ToolsAndPrompt {
tools,
system_prompt,
inventory: StartupInventory {
mcp: mcp_report,
plugins: plugins_report,
},
})
}
#[cfg(test)]
#[path = "tools_prompt_tests.rs"]
mod tests;