use reqwest::Client;
use serde::{Deserialize, Serialize};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Monster Gaming API error: {status} — {message}")]
Api {
status: u16,
message: String,
body: Option<serde_json::Value>,
},
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
pub role: String,
pub content: String,
}
impl ChatMessage {
pub fn system(content: impl Into<String>) -> Self {
Self { role: "system".into(), content: content.into() }
}
pub fn user(content: impl Into<String>) -> Self {
Self { role: "user".into(), content: content.into() }
}
pub fn assistant(content: impl Into<String>) -> Self {
Self { role: "assistant".into(), content: content.into() }
}
}
#[derive(Debug, Serialize)]
pub struct ChatCompletionRequest {
pub model: String,
pub messages: Vec<ChatMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<Vec<String>>,
}
#[derive(Debug, Deserialize)]
pub struct Choice {
pub index: u32,
pub message: ChatMessage,
pub finish_reason: String,
}
#[derive(Debug, Deserialize)]
pub struct Usage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
}
#[derive(Debug, Deserialize)]
pub struct ChatCompletionResponse {
pub id: String,
pub object: String,
pub created: u64,
pub model: String,
pub choices: Vec<Choice>,
pub usage: Usage,
}
#[derive(Debug, Deserialize)]
pub struct Model {
pub id: String,
pub object: String,
pub created: u64,
pub owned_by: String,
}
#[derive(Debug, Deserialize)]
pub struct ModelList {
pub object: String,
pub data: Vec<Model>,
}
pub struct MonsterGaming {
api_key: String,
base_url: String,
client: Client,
}
impl MonsterGaming {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
base_url: "https://api.monstergaming.ai".into(),
client: Client::new(),
}
}
pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
self.base_url = url.into().trim_end_matches('/').to_string();
self
}
pub async fn chat_completion(
&self,
model: impl Into<String>,
messages: Vec<ChatMessage>,
) -> Result<ChatCompletionResponse, Error> {
let req = ChatCompletionRequest {
model: model.into(),
messages,
temperature: None,
max_tokens: None,
top_p: None,
stop: None,
};
self.chat_completion_full(req).await
}
pub async fn chat_completion_full(
&self,
request: ChatCompletionRequest,
) -> Result<ChatCompletionResponse, Error> {
let resp = self
.client
.post(format!("{}/v1/chat/completions", self.base_url))
.header("Authorization", format!("Bearer {}", self.api_key))
.json(&request)
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body: Option<serde_json::Value> = resp.json().await.ok();
return Err(Error::Api {
status,
message: body
.as_ref()
.and_then(|b| b["error"]["message"].as_str())
.unwrap_or("Unknown error")
.to_string(),
body,
});
}
Ok(resp.json().await?)
}
pub async fn list_models(&self) -> Result<ModelList, Error> {
let resp = self
.client
.get(format!("{}/v1/models", self.base_url))
.header("Authorization", format!("Bearer {}", self.api_key))
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status().as_u16();
let body: Option<serde_json::Value> = resp.json().await.ok();
return Err(Error::Api {
status,
message: "Failed to list models".into(),
body,
});
}
Ok(resp.json().await?)
}
}