use derive_builder::Builder;
use reqwest::Client as HttpClient;
use serde::{Deserialize, Serialize};
use crate::{
error::OpenRouterError,
transport::{request as transport_request, response as transport_response},
types::ApiResponse,
};
#[derive(Serialize, Deserialize, Debug)]
pub struct AuthRequest {
code: String,
code_verifier: Option<String>,
code_challenge_method: Option<CodeChallengeMethod>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum CodeChallengeMethod {
#[serde(rename = "S256")]
S256,
Plain,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct AuthResponse {
pub key: String,
pub user_id: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum UsageLimitType {
Daily,
Weekly,
Monthly,
}
#[derive(Serialize, Deserialize, Debug, Clone, Builder)]
#[builder(build_fn(error = "OpenRouterError"))]
pub struct CreateAuthCodeRequest {
#[builder(setter(into))]
callback_url: String,
#[builder(setter(into, strip_option), default)]
#[serde(skip_serializing_if = "Option::is_none")]
code_challenge: Option<String>,
#[builder(setter(strip_option), default)]
#[serde(skip_serializing_if = "Option::is_none")]
code_challenge_method: Option<CodeChallengeMethod>,
#[builder(setter(strip_option), default)]
#[serde(skip_serializing_if = "Option::is_none")]
limit: Option<f64>,
#[builder(setter(into, strip_option), default)]
#[serde(skip_serializing_if = "Option::is_none")]
expires_at: Option<String>,
#[builder(setter(into, strip_option), default)]
#[serde(skip_serializing_if = "Option::is_none")]
key_label: Option<String>,
#[builder(setter(strip_option), default)]
#[serde(skip_serializing_if = "Option::is_none")]
usage_limit_type: Option<UsageLimitType>,
#[builder(setter(into, strip_option), default)]
#[serde(skip_serializing_if = "Option::is_none")]
spawn_agent: Option<String>,
#[builder(setter(into, strip_option), default)]
#[serde(skip_serializing_if = "Option::is_none")]
spawn_cloud: Option<String>,
}
impl CreateAuthCodeRequest {
pub fn builder() -> CreateAuthCodeRequestBuilder {
CreateAuthCodeRequestBuilder::default()
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AuthCodeData {
pub id: String,
pub app_id: f64,
pub created_at: String,
}
pub async fn exchange_code_for_api_key(
base_url: &str,
code: &str,
code_verifier: Option<&str>,
code_challenge_method: Option<CodeChallengeMethod>,
) -> Result<AuthResponse, OpenRouterError> {
let http_client = crate::transport::new_client()?;
exchange_code_for_api_key_with_client(
&http_client,
base_url,
code,
code_verifier,
code_challenge_method,
)
.await
}
pub(crate) async fn exchange_code_for_api_key_with_client(
http_client: &HttpClient,
base_url: &str,
code: &str,
code_verifier: Option<&str>,
code_challenge_method: Option<CodeChallengeMethod>,
) -> Result<AuthResponse, OpenRouterError> {
let url = format!("{base_url}/auth/keys");
let request = AuthRequest {
code: code.to_string(),
code_verifier: code_verifier.map(|s| s.to_string()),
code_challenge_method,
};
let response = transport_request::post(http_client, &url)
.json(&request)
.send()
.await?;
if response.status().is_success() {
let auth_response: AuthResponse =
transport_response::parse_json_response(response, "auth key exchange").await?;
Ok(auth_response)
} else {
transport_response::handle_error(response).await?;
unreachable!()
}
}
pub async fn create_auth_code(
base_url: &str,
api_key: &str,
request: &CreateAuthCodeRequest,
) -> Result<AuthCodeData, OpenRouterError> {
let http_client = crate::transport::new_client()?;
create_auth_code_with_client(&http_client, base_url, api_key, request).await
}
pub(crate) async fn create_auth_code_with_client(
http_client: &HttpClient,
base_url: &str,
api_key: &str,
request: &CreateAuthCodeRequest,
) -> Result<AuthCodeData, OpenRouterError> {
let url = format!("{base_url}/auth/keys/code");
let response =
transport_request::with_bearer_auth(transport_request::post(http_client, &url), api_key)
.json(request)
.send()
.await?;
if response.status().is_success() {
let payload: ApiResponse<AuthCodeData> =
transport_response::parse_json_response(response, "auth code creation").await?;
Ok(payload.data)
} else {
transport_response::handle_error(response).await?;
unreachable!()
}
}