1use crate::error::CliError;
2use aether_core::agent_spec::{AgentSpec, McpConfigSource};
3use aether_core::core::{AgentBuilder, AgentDeps, AgentHandle, Prompt};
4use aether_core::events::{AgentEvent, Command};
5use aether_core::mcp::McpBuilder;
6use aether_core::mcp::McpSpawnResult;
7use aether_core::mcp::mcp;
8use aether_core::mcp::run_mcp_task::McpCommand;
9use llm::{ChatMessage, LlmModel, ToolDefinition};
10use mcp_servers::McpBuilderExt;
11use mcp_utils::client::{McpClientEvent, McpConnectionDetails, McpServer, OAuthHandlerFactory};
12use std::path::{Path, PathBuf};
13use tokio::sync::mpsc::{Receiver, Sender};
14use tokio::task::JoinHandle;
15use tracing::debug;
16
17pub struct RuntimeBuilder {
18 cwd: PathBuf,
19 spec: AgentSpec,
20 mcp_config_sources: Vec<McpConfigSource>,
21 extra_mcp_servers: Vec<McpServer>,
22 oauth_applicator: Option<Box<dyn FnOnce(McpBuilder) -> McpBuilder + Send>>,
23 agent_deps: AgentDeps,
24}
25
26pub struct Runtime {
27 pub agent_tx: Sender<Command>,
28 pub agent_rx: Receiver<AgentEvent>,
29 pub agent_handle: AgentHandle,
30 pub mcp_tx: Sender<McpCommand>,
31 pub event_rx: Receiver<McpClientEvent>,
32 pub mcp_handle: JoinHandle<()>,
33}
34
35pub struct PromptInfo {
36 pub spec: AgentSpec,
37 pub tool_definitions: Vec<ToolDefinition>,
38}
39
40impl RuntimeBuilder {
41 pub fn new(cwd: &Path, model: &str) -> Result<Self, CliError> {
42 let cwd = cwd.canonicalize().map_err(CliError::IoError)?;
43 let parsed_model: LlmModel = model.parse().map_err(CliError::ModelError)?;
44 let spec = AgentSpec::default_spec(&parsed_model, None, Vec::new());
45
46 Ok(Self {
47 cwd,
48 spec,
49 mcp_config_sources: Vec::new(),
50 extra_mcp_servers: Vec::new(),
51 oauth_applicator: None,
52 agent_deps: AgentDeps::default(),
53 })
54 }
55
56 pub fn from_spec(cwd: PathBuf, spec: AgentSpec) -> Self {
57 Self {
58 cwd,
59 spec,
60 mcp_config_sources: Vec::new(),
61 extra_mcp_servers: Vec::new(),
62 oauth_applicator: None,
63 agent_deps: AgentDeps::default(),
64 }
65 }
66
67 pub fn agent_deps(mut self, deps: AgentDeps) -> Self {
68 self.agent_deps = deps;
69 self
70 }
71
72 pub fn mcp_sources(mut self, sources: Vec<McpConfigSource>) -> Self {
75 self.mcp_config_sources = sources;
76 self
77 }
78
79 pub fn extra_servers(mut self, servers: Vec<McpServer>) -> Self {
80 self.extra_mcp_servers = servers;
81 self
82 }
83
84 pub fn oauth_handler_factory(mut self, factory: OAuthHandlerFactory) -> Self {
85 self.oauth_applicator = Some(Box::new(|builder| builder.with_oauth_handler_factory(factory)));
86 self
87 }
88
89 pub async fn build(
90 self,
91 custom_prompt: Option<Prompt>,
92 messages: Option<Vec<ChatMessage>>,
93 ) -> Result<Runtime, CliError> {
94 let deps = self.agent_deps.clone();
95 let (spec, spawn) = self.spawn_mcp().await?;
96 let McpSpawnResult { command_tx: mcp_tx, event_rx, handle: mcp_handle } = spawn;
97
98 let (agent_tx, agent_rx, agent_handle) =
99 spawn_agent(&spec, &deps, mcp_tx.clone(), Vec::new(), |mut agent_builder| {
100 if let Some(prompt) = custom_prompt {
101 agent_builder = agent_builder.system_prompt(prompt);
102 }
103 if let Some(msgs) = messages {
104 agent_builder = agent_builder.messages(msgs);
105 }
106 agent_builder
107 })
108 .await?;
109
110 Ok(Runtime { agent_tx, agent_rx, agent_handle, mcp_tx, event_rx, mcp_handle })
111 }
112
113 pub async fn build_ready(self, messages: Vec<ChatMessage>) -> Result<(Runtime, McpConnectionDetails), CliError> {
120 let deps = self.agent_deps.clone();
121 let (spec, mut spawn) = self.spawn_mcp().await?;
122 let snapshot = spawn
123 .block_until_ready()
124 .await
125 .ok_or_else(|| CliError::McpError("MCP bootstrap aborted before completion".to_string()))?;
126 let McpSpawnResult { command_tx: mcp_tx, event_rx, handle: mcp_handle } = spawn;
127
128 let filtered_tools = spec.tools.apply(snapshot.tool_definitions.clone());
129 let mut runtime_spec = spec;
130 runtime_spec.prompts.push(Prompt::McpInstructions(snapshot.instructions.clone()));
131
132 let (agent_tx, agent_rx, agent_handle) =
133 spawn_agent(&runtime_spec, &deps, mcp_tx.clone(), filtered_tools, |agent_builder| {
134 agent_builder.messages(messages)
135 })
136 .await?;
137
138 Ok((Runtime { agent_tx, agent_rx, agent_handle, mcp_tx, event_rx, mcp_handle }, snapshot))
139 }
140
141 pub async fn build_prompt_info(self) -> Result<PromptInfo, CliError> {
142 let (spec, mut spawn) = self.spawn_mcp().await?;
143 let details = spawn
144 .block_until_ready()
145 .await
146 .ok_or_else(|| CliError::McpError("MCP bootstrap aborted before completion".to_string()))?;
147 let filtered_tools = spec.tools.apply(details.tool_definitions);
148 Ok(PromptInfo { spec, tool_definitions: filtered_tools })
149 }
150
151 async fn spawn_mcp(self) -> Result<(AgentSpec, McpSpawnResult), CliError> {
152 let mut builder = mcp(&self.cwd).with_agent_deps(self.agent_deps.clone());
153
154 if let Some(apply_oauth) = self.oauth_applicator {
155 builder = apply_oauth(builder);
156 }
157
158 builder = builder.with_builtin_servers();
159
160 if !self.extra_mcp_servers.is_empty() {
161 builder = builder.with_servers(self.extra_mcp_servers);
162 }
163
164 let mcp_config_sources: Vec<McpConfigSource> = if self.mcp_config_sources.is_empty() {
165 self.spec.mcp_config_sources.clone()
166 } else {
167 self.mcp_config_sources
168 };
169
170 if !mcp_config_sources.is_empty() {
171 debug!("Loading MCP configs from: {:?}", mcp_config_sources);
172 builder = builder
173 .from_mcp_config_sources(&mcp_config_sources)
174 .await
175 .map_err(|e| CliError::McpError(e.to_string()))?;
176 }
177
178 let spawn = builder.spawn().await.map_err(|e| CliError::McpError(e.to_string()))?;
179 Ok((self.spec, spawn))
180 }
181}
182
183async fn spawn_agent(
184 spec: &AgentSpec,
185 deps: &AgentDeps,
186 mcp_tx: Sender<McpCommand>,
187 tool_definitions: Vec<ToolDefinition>,
188 configure: impl FnOnce(AgentBuilder) -> AgentBuilder,
189) -> Result<(Sender<Command>, Receiver<AgentEvent>, AgentHandle), CliError> {
190 let builder = AgentBuilder::from_spec(spec, vec![], deps)
191 .await
192 .map_err(|error| CliError::AgentError(error.to_string()))?
193 .tools(mcp_tx, tool_definitions);
194
195 configure(builder).spawn().await.map_err(|error| CliError::AgentError(error.to_string()))
196}