openrouter-rust 0.1.0

A modular, type-safe Rust client for the OpenRouter API
Documentation
use crate::{
    client::OpenRouterClient,
    error::{OpenRouterError, Result},
};
use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Model {
    pub id: String,
    pub canonical_slug: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub hugging_face_id: Option<String>,
    pub name: String,
    pub created: f64,
    pub description: String,
    pub pricing: PublicPricing,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context_length: Option<f64>,
    pub architecture: ModelArchitecture,
    pub top_provider: TopProviderInfo,
    pub per_request_limits: PerRequestLimits,
    pub supported_parameters: Vec<String>,
    pub default_parameters: DefaultParameters,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub expiration_date: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PublicPricing {
    pub prompt: Value,
    pub completion: Value,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub request: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub image: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub image_token: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub image_output: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub audio: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub audio_output: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub input_audio_cache: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub web_search: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub internal_reasoning: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub input_cache_read: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub input_cache_write: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub discount: Option<f64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelArchitecture {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tokenizer: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub instruct_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub modality: Option<String>,
    pub input_modalities: Vec<String>,
    pub output_modalities: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TopProviderInfo {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context_length: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_completion_tokens: Option<f64>,
    pub is_moderated: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerRequestLimits {
    pub prompt_tokens: f64,
    pub completion_tokens: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DefaultParameters {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub temperature: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub top_p: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub frequency_penalty: Option<f64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelsResponse {
    pub data: Vec<Model>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelsCountResponse {
    pub data: ModelsCountData,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelsCountData {
    pub count: f64,
}

#[derive(Debug, Clone, Default)]
pub struct ListModelsParams {
    pub category: Option<String>,
    pub supported_parameters: Option<String>,
    pub use_rss: Option<String>,
    pub use_rss_chat_links: Option<String>,
}



impl OpenRouterClient {
    pub async fn list_models(&self, params: Option<ListModelsParams>) -> Result<ModelsResponse> {
        let mut url = format!("{}/models", self.base_url);
        
        if let Some(p) = params {
            let mut query_parts = Vec::new();
            if let Some(cat) = p.category {
                query_parts.push(format!("category={}", cat));
            }
            if let Some(sup) = p.supported_parameters {
                query_parts.push(format!("supported_parameters={}", sup));
            }
            if let Some(rss) = p.use_rss {
                query_parts.push(format!("use_rss={}", rss));
            }
            if let Some(links) = p.use_rss_chat_links {
                query_parts.push(format!("use_rss_chat_links={}", links));
            }
            if !query_parts.is_empty() {
                url = format!("{}?{}", url, query_parts.join("&"));
            }
        }

        let headers = self.build_headers()?;

        let response = self
            .client
            .get(&url)
            .headers(headers)
            .send()
            .await
            .map_err(OpenRouterError::HttpError)?;

        let status = response.status();
        
        if !status.is_success() {
            let error_text = response.text().await.unwrap_or_default();
            return Err(OpenRouterError::ApiError {
                code: status.as_u16(),
                message: error_text,
            });
        }

        let result = response
            .json::<ModelsResponse>()
            .await
            .map_err(OpenRouterError::HttpError)?;

        Ok(result)
    }

    pub async fn get_models_count(&self) -> Result<ModelsCountResponse> {
        let url = format!("{}/models/count", self.base_url);
        let headers = self.build_headers()?;

        let response = self
            .client
            .get(&url)
            .headers(headers)
            .send()
            .await
            .map_err(OpenRouterError::HttpError)?;

        let status = response.status();
        
        if !status.is_success() {
            let error_text = response.text().await.unwrap_or_default();
            return Err(OpenRouterError::ApiError {
                code: status.as_u16(),
                message: error_text,
            });
        }

        let result = response
            .json::<ModelsCountResponse>()
            .await
            .map_err(OpenRouterError::HttpError)?;

        Ok(result)
    }

    pub async fn list_models_user(&self) -> Result<ModelsResponse> {
        let url = format!("{}/models/user", self.base_url);
        let headers = self.build_headers()?;

        let response = self
            .client
            .get(&url)
            .headers(headers)
            .send()
            .await
            .map_err(OpenRouterError::HttpError)?;

        let status = response.status();
        
        if !status.is_success() {
            let error_text = response.text().await.unwrap_or_default();
            return Err(OpenRouterError::ApiError {
                code: status.as_u16(),
                message: error_text,
            });
        }

        let result = response
            .json::<ModelsResponse>()
            .await
            .map_err(OpenRouterError::HttpError)?;

        Ok(result)
    }
}