llmshim 0.6.0

Blazing fast LLM API translation layer in pure Rust
Documentation
pub mod breaker;
pub mod cache;
pub mod client;
pub mod schema;
pub mod shim;
/// Offline-first model catalog, also available as the standalone `llmshim-catalog` crate.
pub use llmshim_catalog as catalog;
pub mod config;
pub mod cost;
pub mod env;
pub mod error;
pub mod fallback;
pub mod log;
pub mod models;
pub mod provider;
pub mod providers;
pub mod reasoning;
pub mod router;
pub mod streaming;
pub mod toolcall;
pub mod usage;
pub mod vision;

#[cfg(feature = "proxy")]
pub mod proxy;

#[cfg(feature = "gateway")]
pub mod gateway;

use client::ShimClient;
use error::Result;
pub use fallback::{completion_with_fallback, FallbackConfig};
use log::{LogEntry, Logger, RequestTimer};
use router::Router;
use serde_json::Value;

use futures::Stream;
use std::pin::Pin;
use std::sync::LazyLock;

/// Shared HTTP client — reuses connection pool across all requests.
/// Shared HTTP client with connection pooling.
pub static SHARED_CLIENT: LazyLock<ShimClient> = LazyLock::new(ShimClient::new);

/// Pre-establish TCP+TLS connections to all configured provider endpoints.
/// Call once after creating the Router to eliminate cold-start latency on first request.
pub async fn warmup(router: &Router) {
    let urls: Vec<&str> = router
        .provider_keys()
        .iter()
        .filter_map(|name| match *name {
            "openai" => Some("https://api.openai.com"),
            "anthropic" => Some("https://api.anthropic.com"),
            "gemini" => Some("https://generativelanguage.googleapis.com"),
            "xai" => Some("https://api.x.ai"),
            _ => None,
        })
        .collect();
    SHARED_CLIENT.warmup(&urls).await;
}

/// Top-level entry point. Resolves the provider from the model string and fires the request.
pub async fn completion(router: &Router, request: &Value) -> Result<Value> {
    completion_with_logger(router, request, None).await
}

/// Completion with optional logging.
pub async fn completion_with_logger(
    router: &Router,
    request: &Value,
    logger: Option<&Logger>,
) -> Result<Value> {
    // A named route resolves to its model and settings before dispatch.
    let request = router.expand_route(request)?;
    let request = request.as_ref();
    let model_str = request
        .get("model")
        .and_then(|m| m.as_str())
        .ok_or(error::ShimError::MissingModel)?;

    let (provider, model) = router.resolve(model_str)?;
    let client = &*SHARED_CLIENT;
    let timer = RequestTimer::start();

    // Ordinary traffic feeds provider health too, so a chain's first fallback
    // decision is not the first thing that ever noticed a provider is down.
    let result = client.completion(provider, &model, request).await;
    router
        .breaker()
        .observe(provider.name(), result.as_ref().map(|_| ()))
        .await;

    match result {
        Ok(resp) => {
            if let Some(logger) = logger {
                logger.log(&LogEntry::from_response(
                    provider.name(),
                    model_str,
                    &resp,
                    timer.elapsed(),
                ));
            }
            Ok(resp)
        }
        Err(e) => {
            if let Some(logger) = logger {
                logger.log(&LogEntry::from_error(
                    provider.name(),
                    model_str,
                    &e.to_string(),
                    timer.elapsed(),
                ));
            }
            Err(e)
        }
    }
}

/// Streaming entry point. Returns an SSE stream of OpenAI-format chunks.
pub async fn stream(
    router: &Router,
    request: &Value,
) -> Result<Pin<Box<dyn Stream<Item = Result<String>> + Send>>> {
    let request = router.expand_route(request)?;
    let request = request.as_ref();
    let model_str = request
        .get("model")
        .and_then(|m| m.as_str())
        .ok_or(error::ShimError::MissingModel)?;

    let (provider, model) = router.resolve_owned(model_str)?;
    // Observed but not gated: a single-target call has no alternative, so
    // refusing here would only convert an upstream failure into a local one.
    // The breaker refuses where there is somewhere else to go — `fallback.rs`.
    let client = &*SHARED_CLIENT;
    let opened = client.stream_owned(provider.clone(), &model, request).await;
    // A stream's health verdict is whether it opened; per-chunk failures are
    // the transport's business, not the breaker's.
    router
        .breaker()
        .observe(provider.name(), opened.as_ref().map(|_| ()))
        .await;
    opened
}