ic-rig 0.2.0

A lean, modular library for building LLM applications. Bring your own HTTP client.
Documentation
//! HTTP client abstraction.
//!
//! This crate ships **no** HTTP implementation. You provide one by implementing
//! [`HttpClient`]. This lets you plug in whatever transport fits your platform:
//! - `reqwest` for native and browser-WASM
//! - `ic_cdk::api::management_canister::http_request` for ICP canisters
//! - A mock client for tests
//!
//! # Example (ICP)
//!
//! ```rust,ignore
//! use irig::http::{HttpClient, HttpRequest, HttpResponse};
//!
//! pub struct IcpHttpClient;
//!
//! impl HttpClient for IcpHttpClient {
//!     type Error = String;
//!
//!     async fn post(&self, req: HttpRequest) -> Result<HttpResponse, Self::Error> {
//!         // Build ic_cdk http_request args from `req`, call the management
//!         // canister, then map the response back to HttpResponse.
//!         todo!()
//!     }
//! }
//! ```

use std::collections::HashMap;
use thiserror::Error;

// ── Request / Response ────────────────────────────────────────────────────────

/// A minimal HTTP POST request.
#[derive(Debug, Clone)]
pub struct HttpRequest {
    /// Absolute URL.
    pub url: String,
    /// HTTP headers (e.g. `Authorization`, `Content-Type`).
    pub headers: HashMap<String, String>,
    /// Raw request body (typically JSON bytes).
    pub body: Vec<u8>,
}

impl HttpRequest {
    pub fn new(url: impl Into<String>) -> Self {
        Self {
            url: url.into(),
            headers: HashMap::new(),
            body: Vec::new(),
        }
    }

    pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.headers.insert(key.into(), value.into());
        self
    }

    pub fn json_body(mut self, body: Vec<u8>) -> Self {
        self.headers
            .insert("Content-Type".to_owned(), "application/json".to_owned());
        self.body = body;
        self
    }
}

/// A minimal HTTP response.
#[derive(Debug, Clone)]
pub struct HttpResponse {
    /// HTTP status code.
    pub status: u16,
    /// Raw response body.
    pub body: Vec<u8>,
}

impl HttpResponse {
    /// Deserialise the body as JSON.
    pub fn json<T: serde::de::DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
        serde_json::from_slice(&self.body)
    }

    /// Check whether the status indicates success (2xx).
    pub fn is_success(&self) -> bool {
        self.status >= 200 && self.status < 300
    }
}

// ── Trait ─────────────────────────────────────────────────────────────────────

/// Implement this trait to provide an HTTP backend.
///
/// Only `POST` is required because every LLM provider API uses POST for
/// inference. If you need other methods (e.g. GET for model listing), extend
/// this trait in your own provider code.
pub trait HttpClient {
    type Error: std::error::Error + 'static;

    /// Send an HTTP POST request and return the response.
    fn post(
        &self,
        req: HttpRequest,
    ) -> impl std::future::Future<Output = Result<HttpResponse, Self::Error>>;
}

// ── Error ─────────────────────────────────────────────────────────────────────

/// Error wrapping an [`HttpClient`] transport failure.
#[derive(Debug, Error)]
#[error("HTTP transport error: {0}")]
pub struct HttpError(pub String);