use futures_util::FutureExt;
use rho_sdk::SystemPrompt;
use super::InteractiveRuntime;
use crate::{model_identity::PromptModel, prompt, tools::mcp::McpConnectOutcome};
impl InteractiveRuntime {
pub(crate) fn mcp_connect_pending(&self) -> bool {
self.pending_mcp.is_some()
}
pub(crate) fn startup_hydrate_pending(&self) -> bool {
self.pending_mcp.is_some() || self.pending_catalog_names.is_some()
}
pub(crate) fn cancel_startup_hydrates(&mut self) {
if let Some(handle) = self.pending_mcp.take() {
handle.abort();
}
if let Some(handle) = self.pending_catalog_names.take() {
handle.abort();
}
}
pub(crate) async fn poll_startup_hydrates(&mut self) -> anyhow::Result<bool> {
let mut changed = false;
if let Some(handle) = self.pending_mcp.as_mut() {
if handle.is_finished() {
if let Some(handle) = self.pending_mcp.take() {
match handle.now_or_never() {
Some(Ok(outcome)) => {
self.apply_mcp_connect(outcome).await?;
changed = true;
}
Some(Err(error)) => {
self.mcp_report
.fail_connecting(&format!("MCP connect did not finish: {error}"));
changed = true;
}
None => {}
}
}
}
}
if let Some(handle) = self.pending_catalog_names.as_mut() {
if handle.is_finished() {
if let Some(handle) = self.pending_catalog_names.take() {
let _ = handle.now_or_never();
if self.pending_mcp.is_none() {
let prompt_changed = self.refresh_startup_system_prompt()?;
if prompt_changed {
self.replace_history_system_prompt()?;
}
changed |= prompt_changed;
}
}
}
}
Ok(changed)
}
async fn apply_mcp_connect(&mut self, outcome: McpConnectOutcome) -> anyhow::Result<()> {
let instructions = outcome
.report
.servers
.iter()
.filter_map(|server| Some((server.identity.as_str(), server.instructions()?)))
.map(|(identity, text)| (identity.to_string(), text.to_string()))
.collect::<Vec<_>>();
self.tools.attach_mcp(outcome);
self.mcp_report = self.tools.mcp_report().clone();
if let Some(template) = self.prompt_template.as_mut() {
let mut retained = String::new();
prompt::append_mcp_instructions(
&mut retained,
instructions
.iter()
.map(|(identity, text)| (identity.as_str(), text.as_str())),
);
template.append_retained(&retained);
}
let prompt_changed = self.refresh_startup_system_prompt()?;
self.rebind_current_session().await?;
self.remember_tool_list();
if prompt_changed {
self.replace_history_system_prompt()?;
} else if !instructions.is_empty() && !self.may_rewrite_startup_prompt {
let mut notice = String::new();
prompt::append_mcp_instructions(
&mut notice,
instructions
.iter()
.map(|(identity, text)| (identity.as_str(), text.as_str())),
);
if !notice.is_empty() {
let _ = self.append_user_context_with_display(notice.clone(), notice);
}
}
Ok(())
}
fn refresh_startup_system_prompt(&mut self) -> anyhow::Result<bool> {
if !self.may_rewrite_startup_prompt || self.live_context_warm {
return Ok(false);
}
let Some(template) = self.prompt_template.as_ref() else {
return Ok(false);
};
let running = PromptModel::from_sdk_identity(&self.provider.provider().identity());
let built = template.render(&running, self.sessions.prompt.loaded.as_ref());
if self.sessions.prompt.system == SystemPrompt::Custom(built.text.clone()) {
return Ok(false);
}
self.sessions.prompt.adopt(
crate::app::active_prompt::ActivePrompt::from_prepared(built),
&self.diagnostics,
self.tools.advisor(),
);
Ok(true)
}
fn replace_history_system_prompt(&mut self) -> anyhow::Result<()> {
let SystemPrompt::Custom(prompt) = &self.sessions.prompt.system else {
return Ok(());
};
crate::app::conversation_switch::replace_system_prompt(self.sessions.session(), prompt)?;
Ok(())
}
}