openmodex 0.1.1

Official Rust SDK for the OpenModex API
Documentation
use crate::client::OpenModex;
use crate::error::Error;
use crate::types::{Model, ModelCompareResponse, ModelListResponse};

/// Service for model-related operations.
///
/// Obtained via [`OpenModex::models`].
#[derive(Debug)]
pub struct ModelService<'a> {
    client: &'a OpenModex,
}

impl<'a> ModelService<'a> {
    pub(crate) fn new(client: &'a OpenModex) -> Self {
        Self { client }
    }

    /// List all available models.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use openmodex::*;
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Error> {
    /// let client = OpenModex::new("omx_sk_...")?;
    /// let models = client.models().list().await?;
    /// for model in &models.data {
    ///     println!("{}: {}", model.id, model.name);
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub async fn list(&self) -> Result<ModelListResponse, Error> {
        self.client.get("/models").await
    }

    /// Get a single model by ID.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use openmodex::*;
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Error> {
    /// let client = OpenModex::new("omx_sk_...")?;
    /// let model = client.models().get("gpt-4o").await?;
    /// println!("{}: {}", model.id, model.description);
    /// # Ok(())
    /// # }
    /// ```
    pub async fn get(&self, model_id: &str) -> Result<Model, Error> {
        let path = format!("/models/{}", urlencoding_simple(model_id));
        self.client.get(&path).await
    }

    /// Compare multiple models side by side.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use openmodex::*;
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), Error> {
    /// let client = OpenModex::new("omx_sk_...")?;
    /// let comparison = client.models().compare(&["gpt-4o", "claude-3.5-sonnet"]).await?;
    /// if let Some(highlights) = &comparison.highlights {
    ///     println!("Cheapest: {}", highlights.cheapest);
    ///     println!("Fastest: {}", highlights.fastest);
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub async fn compare(&self, models: &[&str]) -> Result<ModelCompareResponse, Error> {
        let models_param = models.join(",");
        let path = format!(
            "/models/compare?models={}",
            urlencoding_simple(&models_param)
        );
        self.client.get(&path).await
    }
}

/// Minimal percent-encoding for path segments (avoids adding a dependency).
fn urlencoding_simple(s: &str) -> String {
    let mut result = String::with_capacity(s.len());
    for b in s.bytes() {
        match b {
            b'A'..=b'Z'
            | b'a'..=b'z'
            | b'0'..=b'9'
            | b'-'
            | b'_'
            | b'.'
            | b'~' => result.push(b as char),
            _ => {
                result.push('%');
                result.push_str(&format!("{b:02X}"));
            }
        }
    }
    result
}