use std::env;
use std::marker::PhantomData;
use chat_completions::CompletionsBuilder;
pub use chat_completions::{CompletionsClient, ReqwestTransport};
use chat_core::transport::Transport;
use chat_responses::ResponsesBuilder;
pub use chat_responses::ResponsesClient;
pub const DEFAULT_OPENROUTER_BASE_URL: &str = "https://openrouter.ai/api/v1";
const OPENROUTER_API_KEY_ENV: &str = "OPENROUTER_API_KEY";
pub struct WithoutModel;
pub struct WithModel;
pub struct Responses;
pub struct Completions;
pub struct OpenRouterBuilder<M = WithoutModel, W = Responses, T: Transport = ReqwestTransport> {
model: Option<String>,
api_key: Option<String>,
base_url: String,
reasoning_effort: Option<String>,
description: Option<String>,
transport: Option<T>,
_m: PhantomData<M>,
_w: PhantomData<W>,
}
impl Default for OpenRouterBuilder<WithoutModel, Responses, ReqwestTransport> {
fn default() -> Self {
Self::new()
}
}
impl OpenRouterBuilder<WithoutModel, Responses, ReqwestTransport> {
pub fn new() -> Self {
Self {
model: None,
api_key: None,
base_url: DEFAULT_OPENROUTER_BASE_URL.to_string(),
reasoning_effort: None,
description: None,
transport: Some(ReqwestTransport::default()),
_m: PhantomData,
_w: PhantomData,
}
}
}
impl<M, W, T: Transport> OpenRouterBuilder<M, W, T> {
pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
self.api_key = Some(api_key.into());
self
}
pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
self.base_url = url.into();
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn with_transport<T2: Transport>(self, transport: T2) -> OpenRouterBuilder<M, W, T2> {
OpenRouterBuilder {
model: self.model,
api_key: self.api_key,
base_url: self.base_url,
reasoning_effort: self.reasoning_effort,
description: self.description,
transport: Some(transport),
_m: PhantomData,
_w: PhantomData,
}
}
}
impl<W, T: Transport> OpenRouterBuilder<WithoutModel, W, T> {
pub fn with_model(self, model: impl Into<String>) -> OpenRouterBuilder<WithModel, W, T> {
OpenRouterBuilder {
model: Some(model.into()),
api_key: self.api_key,
base_url: self.base_url,
reasoning_effort: self.reasoning_effort,
description: self.description,
transport: self.transport,
_m: PhantomData,
_w: PhantomData,
}
}
}
impl<M, T: Transport> OpenRouterBuilder<M, Responses, T> {
pub fn with_completions(self) -> OpenRouterBuilder<M, Completions, T> {
OpenRouterBuilder {
model: self.model,
api_key: self.api_key,
base_url: self.base_url,
reasoning_effort: self.reasoning_effort,
description: self.description,
transport: self.transport,
_m: PhantomData,
_w: PhantomData,
}
}
pub fn with_reasoning_effort(mut self, effort: impl Into<String>) -> Self {
self.reasoning_effort = Some(effort.into());
self
}
}
impl<M, W, T: Transport> OpenRouterBuilder<M, W, T> {
fn resolve_api_key(&mut self) -> String {
self.api_key
.take()
.or_else(|| env::var(OPENROUTER_API_KEY_ENV).ok())
.expect("No OpenRouter API key. Set OPENROUTER_API_KEY or call .with_api_key().")
}
}
impl<T: Transport> OpenRouterBuilder<WithModel, Responses, T> {
pub fn build(mut self) -> ResponsesClient<T> {
let api_key = self.resolve_api_key();
let mut rb = ResponsesBuilder::new()
.with_base_url(self.base_url)
.with_model(self.model.expect("model set"))
.with_api_key(api_key)
.with_transport(self.transport.expect("transport set"))
.without_previous_response_id();
if let Some(eff) = self.reasoning_effort {
rb = rb.with_reasoning_effort(eff);
}
if let Some(desc) = self.description {
rb = rb.with_description(desc);
}
rb.build()
}
}
impl<T: Transport> OpenRouterBuilder<WithModel, Completions, T> {
pub fn build(mut self) -> CompletionsClient<T> {
let api_key = self.resolve_api_key();
let mut cb = CompletionsBuilder::new()
.with_base_url(self.base_url)
.with_model(self.model.expect("model set"))
.with_api_key(api_key)
.with_transport(self.transport.expect("transport set"));
if let Some(desc) = self.description {
cb = cb.with_description(desc);
}
cb.build()
}
}