use std::sync::Arc;
use async_trait::async_trait;
use solo_core::{LlmClient, Message, Result};
use super::openai::OpenAIClient;
pub struct OllamaClient {
inner: Arc<OpenAIClient>,
display_name: String,
}
impl OllamaClient {
pub fn wrap(inner: OpenAIClient) -> Self {
Self::from_arc(Arc::new(inner))
}
pub fn from_arc(inner: Arc<OpenAIClient>) -> Self {
let display_name = format!("ollama:{}", inner.model());
Self {
inner,
display_name,
}
}
pub fn inner(&self) -> &OpenAIClient {
&self.inner
}
}
#[async_trait]
impl LlmClient for OllamaClient {
fn name(&self) -> &str {
&self.display_name
}
async fn complete(&self, messages: &[Message]) -> Result<Message> {
self.inner.complete(messages).await
}
}
pub fn is_ollama_base_url(url: &str) -> bool {
url.contains(":11434")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_default_ollama_url() {
assert!(is_ollama_base_url("http://localhost:11434/v1"));
assert!(is_ollama_base_url("http://127.0.0.1:11434/v1"));
assert!(is_ollama_base_url("http://localhost:11434"));
assert!(is_ollama_base_url(
"http://my-remote-ollama.example.com:11434/v1"
));
}
#[test]
fn rejects_other_endpoints() {
assert!(!is_ollama_base_url("https://api.openai.com/v1"));
assert!(!is_ollama_base_url("http://localhost:1234/v1"));
assert!(!is_ollama_base_url("https://api.together.xyz/v1"));
assert!(!is_ollama_base_url("https://api.groq.com/openai/v1"));
assert!(!is_ollama_base_url("https://api.example.com/v1/11434"));
}
#[test]
fn wrap_produces_ollama_prefixed_display_name() {
let inner =
OpenAIClient::new("dummy-key", "qwen2.5-coder:7b").unwrap();
let wrapped = OllamaClient::wrap(inner);
assert_eq!(wrapped.name(), "ollama:qwen2.5-coder:7b");
}
#[test]
fn wrap_preserves_inner_for_introspection() {
let inner = OpenAIClient::new("dummy-key", "phi4:14b")
.unwrap()
.with_base_url("http://localhost:11434/v1");
let wrapped = OllamaClient::wrap(inner);
assert_eq!(wrapped.inner().model(), "phi4:14b");
assert_eq!(wrapped.inner().base_url(), "http://localhost:11434/v1");
assert_eq!(wrapped.name(), "ollama:phi4:14b");
}
#[test]
fn from_arc_constructor_works_for_shared_inner() {
let inner =
Arc::new(OpenAIClient::new("dummy-key", "llama3.3:8b").unwrap());
let wrapped = OllamaClient::from_arc(inner.clone());
assert_eq!(wrapped.name(), "ollama:llama3.3:8b");
assert_eq!(inner.model(), "llama3.3:8b");
}
}