use std::sync::Arc;
use agent_base::{AgentResult, AgentRuntime, ReasoningEffort, RunOutcome, RuntimeEvent, SafetyConfig, SessionId};
use agent_works::AgentBuilder;
use crate::agent::builder::base_agent_builder;
#[derive(Clone, Default)]
pub struct PhiAgentConfig {
pub model: String,
pub enable_thinking: bool,
pub thinking_budget: Option<u64>,
pub thinking_effort: ReasoningEffort,
pub safety: SafetyConfig,
pub max_turns: Option<u32>,
}
#[derive(Clone)]
pub struct PhiAgent {
runtime: AgentRuntime,
pub config: PhiAgentConfig,
#[cfg(feature = "mcp")]
mcp_hub: Arc<tokio::sync::Mutex<Option<Arc<agent_works::mcp::EnhancedMcpHub>>>>,
}
impl PhiAgent {
pub fn builder(llm_client: Arc<dyn agent_base::LlmClient>, system_prompt: String) -> AgentBuilder {
base_agent_builder(llm_client).system_prompt(system_prompt)
}
pub fn build(builder: AgentBuilder, config: PhiAgentConfig) -> AgentResult<Self> {
let runtime = builder.build()?;
Ok(Self {
runtime,
config,
#[cfg(feature = "mcp")]
mcp_hub: Arc::new(tokio::sync::Mutex::new(None)),
})
}
pub async fn create_session(&self) -> SessionId {
self.runtime.create_session().await
}
pub async fn run_turn<F>(&self, session_id: SessionId, query: &str, on_event: F) -> AgentResult<RunOutcome>
where
F: FnMut(RuntimeEvent) -> AgentResult<()> + Send,
{
self.runtime.run_turn(session_id, query, on_event).await
}
pub fn cancel(&self) {
self.runtime.cancel();
}
pub fn is_cancelled(&self) -> bool {
self.runtime.is_cancelled()
}
pub async fn set_reasoning_effort(&self, effort: ReasoningEffort) {
self.runtime.set_reasoning_effort(effort).await;
}
pub fn runtime(&self) -> &AgentRuntime {
&self.runtime
}
pub async fn list_tools(&self) -> Vec<agent_base::ToolMetadata> {
let tools = self.runtime.tools_mut();
let registry = tools.read().await;
registry.metadatas()
}
}
#[cfg(feature = "mcp")]
impl PhiAgent {
async fn get_or_init_hub(&self) -> Arc<agent_works::mcp::EnhancedMcpHub> {
let mut guard = self.mcp_hub.lock().await;
if let Some(ref hub) = *guard {
return hub.clone();
}
let hub = Arc::new(agent_works::mcp::EnhancedMcpHub::new());
*guard = Some(hub.clone());
hub
}
pub async fn attach_mcp(&self, config: agent_works::mcp::McpServerConfig) -> AgentResult<()> {
let name = config.name.clone();
let hub = self.get_or_init_hub().await;
hub.add_server(config);
if let Err(e) = hub.connect_one(&name).await {
hub.remove_server(&name).await;
return Err(e);
}
let discovered = match hub.discover_all().await {
Ok(d) => d,
Err(e) => {
hub.remove_server(&name).await;
return Err(e);
},
};
let tools = self.runtime.tools_mut();
let mut registry = tools.write().await;
hub.register_server(&mut registry, &name).await;
self.runtime.inject_framework_deps(®istry);
let count: usize = discovered.iter().filter(|(n, _)| n == &name).map(|(_, t)| t.len()).sum();
if count == 0 {
tracing::warn!(
server_name = %name,
"attached MCP server but discovered zero tools — server may be misconfigured"
);
}
tracing::info!(server_name = %name, tool_count = count, "attached MCP server at runtime");
Ok(())
}
pub async fn detach_mcp(&self, name: &str) {
let hub = {
let guard = self.mcp_hub.lock().await;
match *guard {
Some(ref hub) => hub.clone(),
None => return,
}
};
let mcp_prefix = format!("mcp.{}.", name);
let tool_names: Vec<String> = {
let tools = self.runtime.tools_mut();
let registry = tools.read().await;
registry.metadatas().iter().filter(|m| m.name.starts_with(&mcp_prefix)).map(|m| m.name.clone()).collect()
};
if !tool_names.is_empty() {
let tools = self.runtime.tools_mut();
let mut registry = tools.write().await;
for tool_name in &tool_names {
registry.remove(tool_name);
}
}
hub.remove_server(name).await;
tracing::info!(
server_name = %name,
tool_count = tool_names.len(),
"detached MCP server at runtime"
);
}
#[cfg(feature = "mcp")]
pub fn into_mcp_server(&self, config: agent_works::mcp::McpServeConfig) -> agent_works::mcp::McpServer {
agent_works::mcp::McpServer::new(self.runtime.clone(), config)
}
}