llm 1.3.8

A Rust library unifying multiple LLM backends.
Documentation
use llm::builder::LLMBackend;
use llm::secret_store::SecretStore;

use crate::config::{AppConfig, ProviderConfig};
use crate::provider::backend_env_key;
use crate::provider::error::ProviderBuildError;
use crate::provider::resolve::ProviderSelection;

use super::ProviderOverrides;

pub(super) fn resolve_model(
    selection: &ProviderSelection,
    provider_cfg: Option<&ProviderConfig>,
    config: &AppConfig,
    overrides: &ProviderOverrides,
) -> Option<String> {
    overrides
        .model
        .clone()
        .or_else(|| selection.model.clone())
        .or_else(|| provider_cfg.and_then(|cfg| cfg.model.clone()))
        .or_else(|| config.default_model.clone())
}

pub(super) fn resolve_system(
    provider_cfg: Option<&ProviderConfig>,
    config: &AppConfig,
    override_system: Option<&str>,
) -> Option<String> {
    override_system
        .map(|value| value.to_string())
        .or_else(|| provider_cfg.and_then(|cfg| cfg.system.clone()))
        .or_else(|| config.chat.system_prompt.clone())
}

pub(super) fn resolve_temperature(
    provider_cfg: Option<&ProviderConfig>,
    config: &AppConfig,
    override_temp: Option<f32>,
) -> Option<f32> {
    override_temp
        .or_else(|| provider_cfg.and_then(|cfg| cfg.temperature))
        .or(config.chat.temperature)
}

pub(super) fn resolve_max_tokens(
    provider_cfg: Option<&ProviderConfig>,
    config: &AppConfig,
    override_max: Option<u32>,
) -> Option<u32> {
    override_max
        .or_else(|| provider_cfg.and_then(|cfg| cfg.max_tokens))
        .or(config.chat.max_tokens)
}

pub(super) fn resolve_timeout(
    provider_cfg: Option<&ProviderConfig>,
    config: &AppConfig,
    override_timeout: Option<u64>,
) -> Option<u64> {
    override_timeout
        .or_else(|| provider_cfg.and_then(|cfg| cfg.timeout_seconds))
        .or(config.chat.timeout_seconds)
}

pub(super) fn resolve_base_url(
    provider_cfg: Option<&ProviderConfig>,
    override_url: Option<&str>,
) -> Option<String> {
    override_url
        .map(|value| value.to_string())
        .or_else(|| provider_cfg.and_then(|cfg| cfg.base_url.clone()))
}

pub(super) fn resolve_api_key(
    backend: &LLMBackend,
    provider_cfg: Option<&ProviderConfig>,
    override_key: Option<&str>,
    secrets: Option<&SecretStore>,
) -> Result<Option<String>, ProviderBuildError> {
    if let Some(value) = override_key {
        return Ok(Some(value.to_string()));
    }
    if let Some(value) = api_key_from_config_env(provider_cfg) {
        return Ok(Some(value));
    }
    if let Some(value) = api_key_from_backend(backend, secrets) {
        return Ok(Some(value));
    }
    if let Some(value) = provider_cfg.and_then(|cfg| cfg.api_key.clone()) {
        return Ok(Some(value));
    }
    if backend_env_key(backend).is_some() {
        return Err(ProviderBuildError::MissingApiKey(backend_label(backend)));
    }
    Ok(None)
}

fn api_key_from_config_env(provider_cfg: Option<&ProviderConfig>) -> Option<String> {
    let key = provider_cfg.and_then(|cfg| cfg.api_key_env.as_deref())?;
    std::env::var(key).ok()
}

fn api_key_from_backend(backend: &LLMBackend, secrets: Option<&SecretStore>) -> Option<String> {
    let key = backend_env_key(backend)?;
    std::env::var(key)
        .ok()
        .or_else(|| secrets.and_then(|store| store.get(key).cloned()))
}

fn backend_label(backend: &LLMBackend) -> String {
    match backend {
        LLMBackend::OpenAI => "openai",
        LLMBackend::Anthropic => "anthropic",
        LLMBackend::DeepSeek => "deepseek",
        LLMBackend::XAI => "xai",
        LLMBackend::Google => "google",
        LLMBackend::Groq => "groq",
        LLMBackend::AzureOpenAI => "azure-openai",
        LLMBackend::Cohere => "cohere",
        LLMBackend::Mistral => "mistral",
        LLMBackend::OpenRouter => "openrouter",
        LLMBackend::HuggingFace => "huggingface",
        LLMBackend::Ollama => "ollama",
        LLMBackend::Phind => "phind",
        LLMBackend::ElevenLabs => "elevenlabs",
        LLMBackend::AwsBedrock => "aws-bedrock",
    }
    .to_string()
}