use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::tools::ToolSpec;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
pub role: String,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_use_id: Option<String>,
}
impl ChatMessage {
pub fn system(content: impl Into<String>) -> Self {
Self {
role: "system".into(),
content: content.into(),
tool_use_id: None,
}
}
pub fn user(content: impl Into<String>) -> Self {
Self {
role: "user".into(),
content: content.into(),
tool_use_id: None,
}
}
pub fn assistant(content: impl Into<String>) -> Self {
Self {
role: "assistant".into(),
content: content.into(),
tool_use_id: None,
}
}
pub fn tool_result(id: impl Into<String>, content: impl Into<String>) -> Self {
Self {
role: "tool_result".into(),
content: content.into(),
tool_use_id: Some(id.into()),
}
}
pub fn is_tool_result(&self) -> bool {
self.role == "tool_result"
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
pub id: String,
pub name: String,
pub arguments: String,
}
#[derive(Debug, Clone, Default)]
pub struct ChatResponse {
pub text: Option<String>,
pub tool_calls: Vec<ToolCall>,
pub usage: Option<Usage>,
}
#[derive(Debug, Clone, Default)]
pub struct Usage {
pub input_tokens: u32,
pub output_tokens: u32,
}
impl ChatResponse {
pub fn has_tool_calls(&self) -> bool {
!self.tool_calls.is_empty()
}
pub fn text_or_empty(&self) -> &str {
self.text.as_deref().unwrap_or("")
}
}
#[derive(Debug, Clone, Copy)]
pub struct ChatRequest<'a> {
pub messages: &'a [ChatMessage],
pub tools: Option<&'a [ToolSpec]>,
pub model: &'a str,
pub temperature: f64,
pub max_tokens: Option<u32>,
}
#[derive(Debug, Clone, Default)]
pub struct ProviderCapabilities {
pub native_tools: bool,
pub streaming: bool,
pub vision: bool,
pub max_context: u32,
}
#[async_trait]
pub trait Provider: Send + Sync {
fn name(&self) -> &str;
fn capabilities(&self) -> ProviderCapabilities {
ProviderCapabilities::default()
}
async fn chat(&self, request: &ChatRequest<'_>) -> anyhow::Result<ChatResponse>;
async fn simple_chat(&self, message: &str, model: &str) -> anyhow::Result<String> {
let messages = [ChatMessage::user(message)];
let request = ChatRequest {
messages: &messages,
tools: None,
model,
temperature: 0.7,
max_tokens: None,
};
let response = self.chat(&request).await?;
Ok(response.text.unwrap_or_default())
}
}