rho-coding-agent 1.19.2

A lightweight agent harness inspired by Pi
Documentation
use super::*;
use rho_providers::auth::login_dispatch::ProviderAuthentication;

pub(super) struct ProviderActivation {
    pub(super) provider: String,
    pub(super) model: String,
    pub(super) reasoning: reasoning_metadata::ModelSwitchReasoningResolution,
    pub(super) auth: String,
    pub(super) replacement: std::sync::Arc<dyn rho_sdk::provider::ModelProvider>,
}

pub(super) enum ProviderActivationOutcome {
    Saved,
    ConfigSaveFailed(anyhow::Error),
}

impl App {
    /// Builds a provider selection on top of persisted application config so
    /// transport settings (notably custom endpoints) survive live rebuilds.
    pub(super) fn build_provider_for_selection(
        &self,
        provider: &str,
        model: &str,
        reasoning: rho_providers::reasoning::ReasoningLevel,
        auth: &str,
    ) -> anyhow::Result<std::sync::Arc<dyn rho_sdk::provider::ModelProvider>> {
        let mut config = self.info.services.config_repository.load()?;
        config.provider = provider.into();
        config.model = model.into();
        config.reasoning = reasoning;
        config.auth = auth.into();
        Ok(crate::credential_store::build_provider_from_config(
            &config,
            std::sync::Arc::clone(&self.credential_store),
        )?)
    }

    pub(super) fn activate_provider(
        &mut self,
        activation: ProviderActivation,
        agent: &mut InteractiveRuntime,
    ) -> anyhow::Result<ProviderActivationOutcome> {
        agent.replace_provider(
            activation.replacement,
            activation.reasoning.effective,
            &activation.auth,
        )?;
        self.info.runtime.provider = activation.provider;
        self.info.runtime.model = activation.model;
        self.info
            .set_reasoning(activation.reasoning.effective, activation.reasoning.source);
        self.info.runtime.auth = activation.auth;
        self.info.services.auth_unavailable = None;
        self.using_unavailable_provider = false;
        self.start_model_metadata_fetch(agent);
        Ok(match self.save_current_config() {
            Ok(()) => ProviderActivationOutcome::Saved,
            Err(error) => ProviderActivationOutcome::ConfigSaveFailed(error),
        })
    }

    pub(super) fn switch_active_auth_mode(
        &mut self,
        auth: &str,
        agent: &mut InteractiveRuntime,
    ) -> anyhow::Result<()> {
        let provider_name = self.info.runtime.provider.clone();
        let Some(descriptor) = rho_providers::provider::provider_descriptor(&provider_name) else {
            self.insert_entry(&Entry::Error(format!(
                "unsupported provider '{provider_name}'"
            )));
            self.status = "auth switch failed".into();
            return Ok(());
        };
        let Some(mode) = descriptor.auth_mode(auth) else {
            self.insert_entry(&Entry::Error(format!(
                "auth mode '{auth}' does not belong to {provider_name}"
            )));
            self.status = "auth switch failed".into();
            return Ok(());
        };
        if !ProviderAuthentication::has_credentials(self.credential_store.as_ref(), mode.id)? {
            self.insert_entry(&Entry::Error(format!(
                "credentials for {} are unavailable. Run /login {} to sign in again.",
                mode.login_label, mode.id
            )));
            self.status = "auth switch failed".into();
            return Ok(());
        }
        if self.info.runtime.auth == mode.id {
            self.status = format!("active auth: {}", mode.login_label);
            return Ok(());
        }

        let model = self.info.runtime.model.clone();
        let reasoning = self.info.runtime.reasoning;
        let new_provider =
            match self.build_provider_for_selection(&provider_name, &model, reasoning, mode.id) {
                Ok(provider) => provider,
                Err(err) => {
                    self.insert_entry(&Entry::Error(format!(
                        "could not switch to {}: {err}. Run /login {} to sign in again.",
                        mode.login_label, mode.id
                    )));
                    self.status = "auth switch failed".into();
                    return Ok(());
                }
            };

        let activation = ProviderActivation {
            provider: provider_name,
            model,
            reasoning: reasoning_metadata::ModelSwitchReasoningResolution {
                effective: reasoning,
                source: self.info.runtime.reasoning_source,
            },
            auth: mode.id.into(),
            replacement: new_provider,
        };
        let outcome = self.activate_provider(activation, agent)?;
        self.refresh_available_auths();
        match outcome {
            ProviderActivationOutcome::Saved => {
                self.insert_entry(&Entry::Notice(format!(
                    "switched {} to {}",
                    descriptor.display_name, mode.login_label
                )));
                self.status = format!("active auth: {}", mode.login_label);
            }
            ProviderActivationOutcome::ConfigSaveFailed(err) => {
                self.insert_entry(&Entry::Error(format!(
                    "auth mode switched, but saving config failed: {err}"
                )));
                self.status = "config save failed".into();
            }
        }
        Ok(())
    }
}