pub mod anthropic;
pub mod openai;
pub(crate) mod openai_wire;
pub mod openrouter;
pub use anthropic::Anthropic;
pub use openai::OpenAi;
pub use openrouter::OpenRouter;
use futures_util::StreamExt;
use crate::error::{Error, Result};
pub(crate) async fn read_sse<F>(resp: reqwest::Response, mut ingest: F) -> Result<()>
where
F: FnMut(&str) -> bool,
{
let mut stream = resp.bytes_stream();
let mut buf = String::new();
while let Some(chunk) = stream.next().await {
let chunk = chunk.map_err(|e| Error::Provider(e.to_string()))?;
buf.push_str(&String::from_utf8_lossy(&chunk));
while let Some(nl) = buf.find('\n') {
let line = buf[..nl].trim_end_matches('\r').to_string();
buf.drain(..=nl);
let Some(data) = line.strip_prefix("data:") else {
continue;
};
let data = data.trim();
if data.is_empty() {
continue;
}
if ingest(data) {
return Ok(());
}
}
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct ToolSpec {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
}
#[derive(Debug, Clone)]
pub struct CompletionRequest {
pub system: String,
pub user: String,
pub tools: Vec<ToolSpec>,
}
#[derive(Debug, Clone)]
pub struct ToolCall {
pub name: String,
pub arguments: serde_json::Value,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Usage {
pub prompt_tokens: u64,
pub completion_tokens: u64,
pub total_tokens: u64,
}
#[derive(Debug, Clone, Default)]
pub struct CompletionResponse {
pub text: Option<String>,
pub tool_calls: Vec<ToolCall>,
pub usage: Option<Usage>,
}
pub trait Provider {
fn complete(
&self,
request: CompletionRequest,
) -> impl std::future::Future<Output = Result<CompletionResponse>> + Send;
fn name(&self) -> &str {
"provider"
}
}