rho-coding-agent 0.11.0

A lightweight agent harness inspired by Pi
use crate::model::{AuthMode, ModelError};

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ProviderId {
    OpenAi,
    OpenAiCodex,
    Anthropic,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ProviderRuntime {
    OpenAi { auth_mode: AuthMode },
    Anthropic,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ProviderModelSource {
    StaticCatalog,
    CachedProviderModels,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ProviderModelRefreshKind {
    OpenAi,
    Anthropic,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ProviderAuthKind {
    ApiKey {
        env_var: &'static str,
        account: &'static str,
        entry_label: &'static str,
        missing: MissingCredential,
    },
    CodexOAuth {
        env_var: &'static str,
        account: &'static str,
    },
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MissingCredential {
    OpenAiApiKey,
    AnthropicApiKey,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ProviderDescriptor {
    pub id: ProviderId,
    pub name: &'static str,
    pub display_name: &'static str,
    pub auth: &'static str,
    pub login_label: &'static str,
    pub auth_kind: ProviderAuthKind,
    pub model_source: ProviderModelSource,
    pub model_refresh: Option<ProviderModelRefreshKind>,
    pub metadata_upstream: &'static str,
    pub runtime: ProviderRuntime,
}

const PROVIDER_IDS: &[&str] = &["openai", "openai-codex", "anthropic"];

pub const PROVIDERS: &[ProviderDescriptor] = &[
    ProviderDescriptor {
        id: ProviderId::OpenAi,
        name: "openai",
        display_name: "OpenAI",
        auth: "api-key",
        login_label: "OpenAI API key",
        auth_kind: ProviderAuthKind::ApiKey {
            env_var: "OPENAI_API_KEY",
            account: "provider:openai:api-key",
            entry_label: "OpenAI API key",
            missing: MissingCredential::OpenAiApiKey,
        },
        model_source: ProviderModelSource::CachedProviderModels,
        model_refresh: Some(ProviderModelRefreshKind::OpenAi),
        metadata_upstream: "openai",
        runtime: ProviderRuntime::OpenAi {
            auth_mode: AuthMode::ApiKey,
        },
    },
    ProviderDescriptor {
        id: ProviderId::OpenAiCodex,
        name: "openai-codex",
        display_name: "OpenAI Codex",
        auth: "codex",
        login_label: "Codex OAuth",
        auth_kind: ProviderAuthKind::CodexOAuth {
            env_var: "CODEX_ACCESS_TOKEN",
            account: "provider:openai-codex:tokens",
        },
        model_source: ProviderModelSource::StaticCatalog,
        model_refresh: None,
        metadata_upstream: "openai",
        runtime: ProviderRuntime::OpenAi {
            auth_mode: AuthMode::Codex,
        },
    },
    ProviderDescriptor {
        id: ProviderId::Anthropic,
        name: "anthropic",
        display_name: "Anthropic",
        auth: "anthropic-api-key",
        login_label: "Anthropic API key",
        auth_kind: ProviderAuthKind::ApiKey {
            env_var: "ANTHROPIC_API_KEY",
            account: "provider:anthropic:api-key",
            entry_label: "Anthropic API key",
            missing: MissingCredential::AnthropicApiKey,
        },
        model_source: ProviderModelSource::CachedProviderModels,
        model_refresh: Some(ProviderModelRefreshKind::Anthropic),
        metadata_upstream: "anthropic",
        runtime: ProviderRuntime::Anthropic,
    },
];

pub fn provider_ids() -> &'static [&'static str] {
    PROVIDER_IDS
}

pub fn providers() -> &'static [ProviderDescriptor] {
    PROVIDERS
}

pub fn provider_descriptor(provider: &str) -> Option<&'static ProviderDescriptor> {
    providers()
        .iter()
        .find(|descriptor| descriptor.name == provider)
}

pub fn missing_credential_error(missing: MissingCredential) -> ModelError {
    match missing {
        MissingCredential::OpenAiApiKey => ModelError::MissingApiKey,
        MissingCredential::AnthropicApiKey => ModelError::MissingAnthropicApiKey,
    }
}

pub fn missing_credentials_error(provider: &str) -> ModelError {
    match provider_descriptor(provider).map(|descriptor| descriptor.auth_kind) {
        Some(ProviderAuthKind::ApiKey { missing, .. }) => missing_credential_error(missing),
        Some(ProviderAuthKind::CodexOAuth { .. }) => ModelError::MissingCodexAuth,
        None => ModelError::UnsupportedProvider(provider.to_string()),
    }
}