use futures_util::FutureExt;
use rho_providers::model::models_dev::{
custom_model_id_catalog_miss, fetch_model_metadata, CatalogLookupMiss,
};
use super::{reasoning_metadata, App, ComposerMode, Entry, InteractiveRuntime, StatusSource};
#[cfg(test)]
#[path = "background_polls_tests.rs"]
mod tests;
impl App {
pub(super) async fn poll_startup_hydrates(
&mut self,
agent: &mut InteractiveRuntime,
) -> anyhow::Result<bool> {
let pending = agent.mcp_connect_pending();
let changed = agent.poll_startup_hydrates().await?;
if !changed {
return Ok(false);
}
self.mcp_report = agent.mcp_report().clone();
self.mcp_catalog = agent.mcp_catalog().clone();
if pending && !agent.mcp_connect_pending() {
if self.status_source == StatusSource::McpConnecting {
self.set_status_quiet("");
}
self.clear_mcp_connecting_activity();
}
if matches!(
self.input_ui.composer(),
ComposerMode::Picker(picker) if picker.is_mcp_inventory()
) {
let _ = self.execute_mcp_command();
}
Ok(true)
}
pub(super) fn poll_custom_provider_models(&mut self) {
let Some(handle) = self.pending_custom_models.as_mut() else {
return;
};
if !handle.is_finished() {
return;
}
self.pending_custom_models = None;
}
pub(super) fn poll_syntax_warmup(&mut self) -> bool {
let Some(handle) = self.pending_syntax_warmup.as_mut() else {
return false;
};
if !handle.is_finished() {
return false;
}
self.pending_syntax_warmup = None;
self.history.invalidate_from(0);
true
}
pub(super) fn poll_herdr_graphics(&mut self) {
let Some(handle) = self.pending_herdr_graphics.as_mut() else {
return;
};
let Some(result) = handle.now_or_never() else {
return;
};
self.pending_herdr_graphics = None;
if let Ok(capability) = result {
self.image_picker = super::feed_image::picker_from_environment(capability);
}
}
pub(super) fn poll_update_notice(&mut self) {
let Some(handle) = self.pending_update_notice.as_mut() else {
return;
};
let Some(result) = handle.now_or_never() else {
return;
};
self.pending_update_notice = None;
if let Ok(Some(notice)) = result {
self.info.services.update_notice = Some(notice);
}
}
fn apply_context_window(
&mut self,
agent: &mut InteractiveRuntime,
context_window: Option<u64>,
) -> bool {
if let Err(err) = agent.set_context_window(context_window) {
self.insert_entry(&Entry::Error(format!(
"could not apply the model context window: {err}"
)));
return false;
}
true
}
pub(super) fn start_model_metadata_fetch(&mut self, agent: &mut InteractiveRuntime) {
if let Some(handle) = self.pending_model_metadata.take() {
handle.abort();
}
self.pending_model_metadata_reasoning = None;
let provider = self.info.runtime.provider.clone();
let model = rho_providers::providers::fast_mode::request_model(
&provider,
&self.info.runtime.model,
&self.info.runtime.auth,
self.info.runtime.fast_mode_active(),
)
.to_string();
if let Some((metadata, metadata_is_current)) =
reasoning_metadata::cached_metadata(&provider, &model)
{
if self.apply_context_window(agent, metadata.display_context_window()) {
let reasoning_metadata_complete = metadata.reasoning_metadata_complete;
self.model_metadata = Some(metadata);
if reasoning_metadata_complete && metadata_is_current {
return;
}
}
} else {
let _ = self.apply_context_window(agent, None);
self.model_metadata = None;
}
self.pending_model_metadata_reasoning = Some((
self.info.runtime.reasoning,
self.info.runtime.reasoning_source,
));
self.pending_model_metadata = Some(tokio::spawn(async move {
fetch_model_metadata(&provider, &model).await
}));
}
pub(super) async fn poll_model_metadata_fetch(&mut self, agent: &mut InteractiveRuntime) {
if agent.is_session_busy() {
return;
}
let Some(handle) = self.pending_model_metadata.as_mut() else {
return;
};
if !handle.is_finished() {
return;
}
if let Some(handle) = self.pending_model_metadata.take() {
let reasoning_at_fetch_start = self.pending_model_metadata_reasoning.take();
if let Some(Ok(Some(metadata))) = handle.now_or_never() {
if !self.apply_context_window(agent, metadata.display_context_window()) {
return;
}
self.apply_fetched_reasoning(
agent,
&metadata.reasoning_capabilities(),
reasoning_at_fetch_start,
)
.await;
self.model_metadata = Some(metadata);
} else {
self.warn_custom_model_id_catalog_miss();
}
}
}
fn warn_custom_model_id_catalog_miss(&mut self) {
let provider = self.info.runtime.provider.as_str();
let model = self.info.runtime.model.as_str();
let Some(miss) = custom_model_id_catalog_miss(provider, model) else {
return;
};
let message = match miss {
CatalogLookupMiss::BareModelId => format!(
"{provider}/{model} has no models.dev metadata: catalog_mode = \"model-id\" needs a provider/model id"
),
CatalogLookupMiss::MissingRow {
source_provider,
source_model,
} => format!(
"{provider}/{model} has no models.dev metadata for {source_provider}/{source_model}"
),
};
self.insert_entry(&Entry::Notice(message));
self.set_status("models.dev catalog miss");
}
}