use super::types::OpenRouterChatRequest;
use crate::provider::get_context_window;
use crate::providers::openai_compatible::{build_chat_request, streaming::create_custom_stream_generic};
use crate::{Context, LlmError, LlmResponseStream, ProviderFactory, Result, StreamingModelProvider};
use async_openai::{Client, config::OpenAIConfig};
pub struct OpenRouterProvider {
client: Client<OpenAIConfig>,
model: String,
}
impl OpenRouterProvider {
pub fn new(api_key: String, model: String) -> Result<Self> {
let config = OpenAIConfig::new().with_api_key(api_key).with_api_base("https://openrouter.ai/api/v1");
let client = Client::with_config(config);
Ok(Self { client, model })
}
pub fn default(model: &str) -> Result<Self> {
let api_key = std::env::var("OPENROUTER_API_KEY")
.map_err(|_| LlmError::MissingApiKey("OPENROUTER_API_KEY".to_string()))?;
let config = OpenAIConfig::new().with_api_key(api_key).with_api_base("https://openrouter.ai/api/v1");
let client = Client::with_config(config);
Ok(Self { client, model: model.to_string() })
}
}
impl ProviderFactory for OpenRouterProvider {
async fn from_env() -> Result<Self> {
let api_key = std::env::var("OPENROUTER_API_KEY")
.map_err(|_| LlmError::MissingApiKey("OPENROUTER_API_KEY".to_string()))?;
let config = OpenAIConfig::new().with_api_key(api_key).with_api_base("https://openrouter.ai/api/v1");
let client = Client::with_config(config);
Ok(Self { client, model: String::new() })
}
fn with_model(mut self, model: &str) -> Self {
self.model = model.to_string();
self
}
}
impl StreamingModelProvider for OpenRouterProvider {
fn model(&self) -> Option<crate::LlmModel> {
format!("openrouter:{}", self.model).parse().ok()
}
fn context_window(&self) -> Option<u32> {
get_context_window("openrouter", &self.model)
}
fn stream_response(&self, context: &Context) -> LlmResponseStream {
let mut request: OpenRouterChatRequest = match build_chat_request(&self.model, context) {
Ok(req) => req.into(),
Err(e) => return Box::pin(async_stream::stream! { yield Err(e); }),
};
if let Some(effort) = context.reasoning_effort() {
request.reasoning_effort = Some(effort);
}
create_custom_stream_generic(&self.client, request)
}
fn display_name(&self) -> String {
format!("OpenRouter ({})", self.model)
}
}