use serde::Deserialize;
use std::fmt;
#[derive(Debug, Deserialize)]
pub struct Model {
pub id: String,
}
#[derive(Debug, Deserialize)]
pub struct ModelsResponse {
pub data: Vec<Model>,
}
#[derive(Debug, Deserialize)]
struct MessageContent {
content: Option<String>,
}
#[derive(Debug, Deserialize)]
struct Choice {
message: MessageContent,
}
#[derive(Debug, Deserialize)]
struct ChatResponse {
choices: Vec<Choice>,
}
#[derive(Debug)]
pub enum ApiError {
Unauthorized,
Forbidden,
RateLimited,
HttpError(u16),
NetworkError(String),
ParseError,
EmptyResponse,
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ApiError::Unauthorized => write!(f, "Unauthorized"),
ApiError::Forbidden => write!(f, "Forbidden"),
ApiError::RateLimited => write!(f, "Rate limited"),
ApiError::HttpError(code) => write!(f, "HTTP error {}", code),
ApiError::NetworkError(msg) => write!(f, "Network error: {}", msg),
ApiError::ParseError => write!(f, "Parse error"),
ApiError::EmptyResponse => write!(f, "Empty response"),
}
}
}
pub struct Client {
api_key: String,
}
impl Client {
pub fn new(api_key: String) -> Self {
Self { api_key }
}
pub fn fetch_models(&self) -> Result<Vec<Model>, ApiError> {
let mut resp = ureq::get("https://openrouter.ai/api/v1/models")
.header("Authorization", &format!("Bearer {}", self.api_key))
.call()
.map_err(|e: ureq::Error| match e {
ureq::Error::StatusCode(code) => ApiError::HttpError(code),
other => ApiError::NetworkError(other.to_string()),
})?;
let status_u16: u16 = resp.status().into();
if status_u16 != 200 {
match status_u16 {
401 => return Err(ApiError::Unauthorized),
403 => return Err(ApiError::Forbidden),
429 => return Err(ApiError::RateLimited),
code => return Err(ApiError::HttpError(code)),
}
}
let models_resp: ModelsResponse = resp
.body_mut()
.read_json()
.map_err(|_| ApiError::ParseError)?;
if models_resp.data.is_empty() {
return Err(ApiError::EmptyResponse);
}
Ok(models_resp.data)
}
pub fn generate_commit_message(&self, payload: &serde_json::Value) -> Result<String, ApiError> {
let mut resp = ureq::post("https://openrouter.ai/api/v1/chat/completions")
.header("Authorization", &format!("Bearer {}", self.api_key))
.header("Content-Type", "application/json")
.send_json(payload)
.map_err(|e: ureq::Error| match e {
ureq::Error::StatusCode(code) => ApiError::HttpError(code),
other => ApiError::NetworkError(other.to_string()),
})?;
let status_u16: u16 = resp.status().into();
match status_u16 {
200 => {}
401 => return Err(ApiError::Unauthorized),
403 => return Err(ApiError::Forbidden),
429 => return Err(ApiError::RateLimited),
code => return Err(ApiError::HttpError(code)),
}
let chat_resp: ChatResponse = resp
.body_mut()
.read_json()
.map_err(|_| ApiError::ParseError)?;
chat_resp
.choices
.into_iter()
.next()
.and_then(|c| c.message.content)
.filter(|s| !s.trim().is_empty())
.ok_or(ApiError::EmptyResponse)
}
}