rho-coding-agent 1.18.0

A lightweight agent harness inspired by Pi
Documentation
use std::sync::Mutex;

use rho_providers::{
    credentials::{CredentialError, CredentialResult, CredentialStore},
    model::provider_models::set_provider_models_cache_dir_for_tests,
    provider::OPENROUTER_OAUTH_KEY_ACCOUNT,
};

use crate::{cli::Cli, config::Config};

use super::refresh_model_cache;

#[derive(Default)]
struct RecordingStore {
    requested_accounts: Mutex<Vec<String>>,
}

impl CredentialStore for RecordingStore {
    fn get_secret(&self, account: &str) -> CredentialResult<Option<String>> {
        self.requested_accounts.lock().unwrap().push(account.into());
        Err(CredentialError::StoreUnavailable(account.into()))
    }

    fn set_secret(&self, _account: &str, _secret: &str) -> CredentialResult<()> {
        unreachable!("refresh must not write credentials")
    }

    fn delete_secret(&self, _account: &str) -> CredentialResult<bool> {
        unreachable!("refresh must not delete credentials")
    }
}

#[tokio::test]
async fn cold_cache_refresh_uses_the_cli_auth_profile_credentials() {
    let cache = tempfile::tempdir().unwrap();
    set_provider_models_cache_dir_for_tests(Some(cache.path().into()));
    let store = RecordingStore::default();
    let cli = Cli {
        provider: Some("openrouter".into()),
        model: None,
        config: None,
        auth: Some("openrouter-oauth".into()),
        no_system_prompt: false,
        no_tools: false,
        no_subagents: false,
        agent: None,
        reasoning: None,
        resume: None,
        command: None,
    };

    let error = match refresh_model_cache(&cli, &Config::default(), &store).await {
        Ok(_) => panic!("cold-cache refresh unexpectedly succeeded"),
        Err(error) => error,
    };
    set_provider_models_cache_dir_for_tests(None);

    assert!(error.to_string().contains(OPENROUTER_OAUTH_KEY_ACCOUNT));
    assert_eq!(
        *store.requested_accounts.lock().unwrap(),
        vec![OPENROUTER_OAUTH_KEY_ACCOUNT]
    );
}