use std::sync::LazyLock;
use color_eyre::Result;
use reqwest::Client;
use tokio::sync::mpsc;
use crate::agent::{ContentPart, Message, StreamOutcome, ToolDefinition};
use crate::anthropic::ClaudeClient;
use crate::config::AppConfig;
use crate::openai::OpenAiClient;
static HTTP: LazyLock<Client> = LazyLock::new(Client::new);
pub fn http_client() -> Client {
HTTP.clone()
}
pub enum LlmClient {
Anthropic(ClaudeClient),
OpenAiCompatible(OpenAiClient),
}
impl LlmClient {
pub fn from_config(config: &AppConfig) -> Result<Self> {
let api_key = config.get_api_key()?;
Ok(if config.provider.is_anthropic() {
Self::Anthropic(
ClaudeClient::new(api_key)
.with_model(config.default_model.clone())
.with_max_tokens(config.max_tokens),
)
} else {
Self::OpenAiCompatible(OpenAiClient::new(
api_key,
config.resolve_base_url()?,
config.default_model.clone(),
config.max_tokens,
))
})
}
pub async fn send_message(
&self,
history: &[Message],
tools: Option<&[ToolDefinition]>,
system: Option<&str>,
) -> Result<Vec<ContentPart>> {
match self {
Self::Anthropic(client) => client.send_message(history, tools, system).await,
Self::OpenAiCompatible(client) => client.send_message(history, tools, system).await,
}
}
pub async fn send_message_streaming(
&self,
history: &[Message],
tools: Option<&[ToolDefinition]>,
system: Option<&str>,
update_tx: &mpsc::UnboundedSender<String>,
) -> Result<StreamOutcome> {
match self {
Self::Anthropic(client) => {
client
.send_message_streaming(history, tools, system, update_tx)
.await
}
Self::OpenAiCompatible(client) => {
client
.send_message_streaming(history, tools, system, update_tx)
.await
}
}
}
}