pub use super::shared::{Message, ToolDefinition};
use async_trait::async_trait;
use futures_util::stream::BoxStream;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fmt;
pub mod anthropic;
pub mod openai_compatible;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct LLMRequest {
pub model: String,
pub system: Option<String>,
pub messages: Vec<Message>,
pub tools: Vec<ToolDefinition>,
pub temperature: Option<f32>,
pub max_tokens: Option<u32>,
}
#[derive(Debug, Clone)]
pub enum ProviderStreamChunk {
ContentDelta(String),
ThinkingDelta(String),
ToolCallDelta {
index: usize,
id: Option<String>,
name: Option<String>,
arguments_delta: String,
},
Done {
finish_reason: FinishReason,
usage: Option<TokenUsage>,
},
}
#[derive(Debug, Clone)]
pub enum FinishReason {
Stop,
ToolCalls,
Length,
ContentFilter,
Error,
Unknown(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenUsage {
pub input_tokens: Option<u32>,
pub output_tokens: Option<u32>,
pub total_tokens: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProviderErrorKind {
Transient,
Permanent,
RateLimited { retry_after_secs: Option<u64> },
}
#[derive(Debug, Clone)]
pub struct ProviderError {
pub kind: ProviderErrorKind,
pub message: String,
}
impl ProviderError {
pub fn transient(message: impl Into<String>) -> Self {
Self {
kind: ProviderErrorKind::Transient,
message: message.into(),
}
}
pub fn permanent(message: impl Into<String>) -> Self {
Self {
kind: ProviderErrorKind::Permanent,
message: message.into(),
}
}
pub fn rate_limited(message: impl Into<String>, retry_after_secs: Option<u64>) -> Self {
Self {
kind: ProviderErrorKind::RateLimited { retry_after_secs },
message: message.into(),
}
}
}
impl fmt::Display for ProviderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
ProviderErrorKind::Transient | ProviderErrorKind::Permanent => {
f.write_str(&self.message)
}
ProviderErrorKind::RateLimited { retry_after_secs } => {
if let Some(seconds) = retry_after_secs {
write!(f, "{} (retry_after={}s)", self.message, seconds)
} else {
f.write_str(&self.message)
}
}
}
}
}
impl std::error::Error for ProviderError {}
#[async_trait]
pub trait LlmProvider: Send + Sync {
fn name(&self) -> &str;
fn request_snapshot(&self, req: &LLMRequest) -> Value {
serde_json::to_value(req).unwrap_or(Value::Null)
}
async fn chat_completion(
&self,
req: LLMRequest,
) -> Result<BoxStream<'static, Result<ProviderStreamChunk, ProviderError>>, ProviderError>;
}