rho-coding-agent 1.19.1

A lightweight agent harness inspired by Pi
Documentation
use {
    crate::credential_store::AppCredentialStore,
    rho_providers::auth::login_dispatch::{
        AuthenticationMethod, InteractiveLoginCompletion, InteractiveLoginMode,
        InteractiveUserAction, ProviderAuthentication,
    },
    rho_providers::model::catalog,
};

pub(super) async fn run(provider: &str, device_auth: bool) -> anyhow::Result<()> {
    if rho_providers::provider::provider_descriptor(provider)
        .is_some_and(|descriptor| descriptor.is_keyless())
    {
        anyhow::bail!("provider '{provider}' does not require login");
    }
    let Some(target) = catalog::login_target_for_provider(provider) else {
        let options = catalog::login_targets()
            .into_iter()
            .map(|target| target.auth)
            .collect::<Vec<_>>()
            .join(", ");
        anyhow::bail!("unsupported login provider '{provider}'. Use one of: {options}");
    };
    match ProviderAuthentication::method(&target.auth)? {
        AuthenticationMethod::None => {
            anyhow::bail!("provider '{provider}' does not require login")
        }
        AuthenticationMethod::ApiKey { entry_label } => {
            anyhow::bail!(
                "{entry_label} login is only supported in the interactive TUI; run `/login {provider}`"
            );
        }
        AuthenticationMethod::Interactive { .. } => {}
    }

    let mode = if device_auth {
        InteractiveLoginMode::Device
    } else {
        InteractiveLoginMode::Browser
    };
    let login = ProviderAuthentication::start_interactive_login(&target.auth, mode).await?;
    match &login.user_action {
        InteractiveUserAction::BrowserOpened => {
            if ProviderAuthentication::supports_device_login(&target.auth) {
                eprintln!(
                    "Opening browser for {} login. On a remote or headless session, use `rho login {} --device-auth` instead.",
                    login.provider_label, target.auth
                );
            } else {
                eprintln!(
                    "Opening browser for {} login. This provider does not support device login; use an API key on a remote or headless session.",
                    login.provider_label
                );
            }
        }
        InteractiveUserAction::OpenUrl { url, instruction } => {
            eprintln!("{}: {instruction}", login.provider_label);
            eprintln!("{url}");
        }
        InteractiveUserAction::DeviceCode {
            verification_uri,
            user_code,
            verification_uri_complete,
        } => {
            eprintln!(
                "{} login: visit {verification_uri} and enter code {user_code}",
                login.provider_label
            );
            if let Some(uri) = verification_uri_complete {
                eprintln!("Or open this URL to continue: {uri}");
            }
        }
    }

    match login.completion {
        InteractiveLoginCompletion::Confirm(completion) => {
            completion.await?.save(&AppCredentialStore)?;
            eprintln!("Successfully logged in to {}", target.auth);
        }
        InteractiveLoginCompletion::Unconfirmed { instruction } => {
            eprintln!("{instruction}");
        }
    }
    Ok(())
}

#[cfg(test)]
#[path = "login_tests.rs"]
mod tests;