use async_trait::async_trait;
use futures::stream::Stream;
use std::pin::Pin;
use crate::error::Result;
use crate::providers::{GenerationConfig, GenerationResponse, InferenceClient, StreamChunk};
use crate::types::Provider;
use crate::utils::ConversationMessage;
use super::openai::OAIClient;
#[derive(Debug, Clone)]
pub struct OpenRouterClient {
inner: OAIClient,
}
impl OpenRouterClient {
pub fn new(
api_key: impl Into<String>,
model: impl Into<String>,
referer: Option<String>,
title: Option<String>,
) -> Self {
let inner = OAIClient::new(api_key, model)
.with_api_url("https://openrouter.ai/api/v1/chat/completions")
.with_ranking_headers(referer, title)
.with_provider(Provider::OpenRouter);
Self { inner }
}
pub fn with_reasoning(mut self, reasoning: impl Into<String>) -> Self {
self.inner = self.inner.with_reasoning(reasoning);
self
}
#[allow(dead_code)]
async fn populate_cost(&self, _response: &mut GenerationResponse) -> Result<()> {
Ok(())
}
}
#[async_trait]
impl InferenceClient for OpenRouterClient {
async fn get_generation(
&self,
messages: &[ConversationMessage],
config: &GenerationConfig,
) -> Result<GenerationResponse> {
let mut response = self.inner.get_generation(messages, config).await?;
self.populate_cost(&mut response).await?;
Ok(response)
}
async fn connect_and_listen(
&self,
messages: &[ConversationMessage],
config: &GenerationConfig,
) -> Result<Pin<Box<dyn Stream<Item = Result<StreamChunk>> + Send>>> {
self.inner.connect_and_listen(messages, config).await
}
fn provider(&self) -> Provider {
Provider::OpenRouter
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_client() {
let client = OpenRouterClient::new(
"test-key",
"anthropic/claude-3-5-sonnet",
Some("https://example.com".to_string()),
Some("Test App".to_string()),
);
assert_eq!(client.provider(), Provider::OpenRouter);
}
#[test]
fn test_new_client_without_headers() {
let client = OpenRouterClient::new("test-key", "anthropic/claude-3-5-sonnet", None, None);
assert_eq!(client.provider(), Provider::OpenRouter);
}
#[test]
fn test_with_reasoning() {
let client = OpenRouterClient::new("test-key", "anthropic/claude-3-5-sonnet", None, None)
.with_reasoning("high");
assert_eq!(client.provider(), Provider::OpenRouter);
}
#[tokio::test]
async fn test_populate_cost_placeholder() {
let client = OpenRouterClient::new("test-key", "test-model", None, None);
let mut response = GenerationResponse::text("test");
let result = client.populate_cost(&mut response).await;
assert!(result.is_ok());
}
}