openrouter-rs 0.9.0

A type-safe OpenRouter Rust SDK
Documentation
use reqwest::Client as HttpClient;
use serde::{Deserialize, Serialize};
use urlencoding::encode;

use crate::{
    error::OpenRouterError,
    transport::{request as transport_request, response as transport_response},
    types::{ApiResponse, ModelCategory, SupportedParameters},
};

#[derive(Serialize, Deserialize, Debug)]
pub struct Model {
    pub id: String,
    pub name: String,
    pub created: f64,
    pub description: String,
    pub context_length: f64,
    pub architecture: Architecture,
    pub top_provider: TopProvider,
    pub pricing: Pricing,
    pub per_request_limits: Option<std::collections::HashMap<String, String>>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Architecture {
    pub modality: String,
    pub tokenizer: String,
    pub instruct_type: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct TopProvider {
    pub context_length: Option<f64>,
    pub max_completion_tokens: Option<f64>,
    pub is_moderated: bool,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Pricing {
    pub prompt: String,
    pub completion: String,
    pub image: Option<String>,
    pub request: Option<String>,
    pub input_cache_read: Option<String>,
    pub input_cache_write: Option<String>,
    pub web_search: Option<String>,
    pub internal_reasoning: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Endpoint {
    pub name: String,
    pub context_length: f64,
    pub pricing: EndpointPricing,
    pub provider_name: String,
    pub supported_parameters: Vec<String>,
    pub quantization: Option<String>,
    pub max_completion_tokens: Option<f64>,
    pub max_prompt_tokens: Option<f64>,
    pub status: Option<serde_json::Value>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct EndpointPricing {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub request: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub image: Option<String>,
    pub prompt: String,
    pub completion: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct EndpointData {
    pub id: String,
    pub name: String,
    pub created: f64,
    pub description: String,
    pub architecture: EndpointArchitecture,
    pub endpoints: Vec<Endpoint>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct EndpointArchitecture {
    pub tokenizer: Option<String>,
    pub instruct_type: Option<String>,
    pub modality: Option<String>,
}

/// Returns a list of models available through the API
///
/// # Arguments
///
/// * `base_url` - The base URL of the OpenRouter API.
/// * `api_key` - The API key for authentication.
/// * `category` - Optional category filter for the models.
/// * `supported_parameters` - Optional supported-parameter filter for the models.
///
/// # Returns
///
/// * `Result<Vec<Model>, OpenRouterError>` - A list of models or an error.
pub async fn list_models(
    base_url: &str,
    api_key: &str,
    category: Option<ModelCategory>,
    supported_parameters: Option<SupportedParameters>,
) -> Result<Vec<Model>, OpenRouterError> {
    let http_client = crate::transport::new_client()?;
    list_models_with_client(
        &http_client,
        base_url,
        api_key,
        category,
        supported_parameters,
    )
    .await
}

pub(crate) async fn list_models_with_client(
    http_client: &HttpClient,
    base_url: &str,
    api_key: &str,
    category: Option<ModelCategory>,
    supported_parameters: Option<SupportedParameters>,
) -> Result<Vec<Model>, OpenRouterError> {
    #[derive(Serialize)]
    struct ListModelsQuery {
        #[serde(skip_serializing_if = "Option::is_none")]
        category: Option<ModelCategory>,
        #[serde(skip_serializing_if = "Option::is_none")]
        supported_parameters: Option<SupportedParameters>,
    }

    let url = format!("{base_url}/models");
    let query = ListModelsQuery {
        category,
        supported_parameters,
    };

    let req =
        transport_request::with_bearer_auth(transport_request::get(http_client, &url), api_key);
    let response = if query.category.is_none() && query.supported_parameters.is_none() {
        req.send().await?
    } else {
        req.query(&query).send().await?
    };

    if response.status().is_success() {
        let model_list_response: ApiResponse<_> =
            transport_response::parse_json_response(response, "model list").await?;
        Ok(model_list_response.data)
    } else {
        transport_response::handle_error(response).await?;
        unreachable!()
    }
}

/// Returns details about the endpoints for a specific model
///
/// # Arguments
///
/// * `base_url` - The base URL of the OpenRouter API.
/// * `api_key` - The API key for authentication.
/// * `author` - The author of the model.
/// * `slug` - The slug identifier for the model.
///
/// # Returns
///
/// * `Result<EndpointData, OpenRouterError>` - The endpoint data or an error.
pub async fn list_model_endpoints(
    base_url: &str,
    api_key: &str,
    author: &str,
    slug: &str,
) -> Result<EndpointData, OpenRouterError> {
    let http_client = crate::transport::new_client()?;
    list_model_endpoints_with_client(&http_client, base_url, api_key, author, slug).await
}

pub(crate) async fn list_model_endpoints_with_client(
    http_client: &HttpClient,
    base_url: &str,
    api_key: &str,
    author: &str,
    slug: &str,
) -> Result<EndpointData, OpenRouterError> {
    let encoded_author = encode(author);
    let encoded_slug = encode(slug);
    let url = format!("{base_url}/models/{encoded_author}/{encoded_slug}/endpoints");

    let response =
        transport_request::with_bearer_auth(transport_request::get(http_client, &url), api_key)
            .send()
            .await?;

    if response.status().is_success() {
        let endpoint_list_response: ApiResponse<_> =
            transport_response::parse_json_response(response, "model endpoint list").await?;
        Ok(endpoint_list_response.data)
    } else {
        transport_response::handle_error(response).await?;
        unreachable!()
    }
}