litellm-rust 0.5.4

Minimal Rust SDK port of LiteLLM – unified interface for chat, embeddings, images, and video across OpenAI-compatible, Anthropic, Gemini, and xAI providers
Documentation
use crate::config::ProviderConfig;
use crate::error::{LiteLLMError, Result};
use serde_json::{Map, Value};
use std::env;

pub mod anthropic;
pub mod gemini;
pub mod openai_compat;

pub fn resolve_api_key(cfg: &ProviderConfig) -> Result<Option<String>> {
    if cfg.no_auth {
        return Ok(None);
    }
    if let Some(key) = cfg.api_key.clone() {
        return Ok(Some(key));
    }
    if let Some(env_key) = cfg.api_key_env.as_deref() {
        let key =
            env::var(env_key).map_err(|_| LiteLLMError::MissingApiKey(env_key.to_string()))?;
        return Ok(Some(key));
    }
    Err(LiteLLMError::MissingApiKey("<unset>".into()))
}

pub(crate) fn merge_extra_body(
    body: &mut Value,
    extra_body: Option<&Map<String, Value>>,
    protected_fields: &[&str],
) -> Result<()> {
    let Some(extra_body) = extra_body else {
        return Ok(());
    };
    let body_map = body
        .as_object_mut()
        .ok_or_else(|| LiteLLMError::Config("request body must be a JSON object".into()))?;

    for (key, value) in extra_body {
        if protected_fields.iter().any(|field| *field == key) {
            return Err(LiteLLMError::Config(format!(
                "extra_body cannot override protected request field `{key}`"
            )));
        }
        if is_denied_extra_body_key(key) {
            return Err(LiteLLMError::Config(format!(
                "extra_body contains unsupported internal field `{key}`"
            )));
        }
        body_map.insert(key.clone(), value.clone());
    }

    Ok(())
}

fn is_denied_extra_body_key(key: &str) -> bool {
    key.starts_with("litellm_")
        || matches!(
            key,
            "vector_store_id"
                | "vector_store_ids"
                | "extra_body"
                | "extra_headers"
                | "extra_query"
                | "reasoning"
                | "reasoning_content"
                | "reasoning_details"
        )
}