use eventsource_client::{Client as EsClient, ClientBuilder, ReconnectOptions, SSE};
use futures::stream::{Stream, TryStreamExt};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;
use crate::error::Error;
const MESSAGES_CREATE: &str = "/messages";
#[derive(Debug, Serialize, Deserialize)]
pub struct Usage {
pub input_tokens: Option<u32>,
pub output_tokens: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Content {
pub r#type: String,
pub text: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Message {
pub role: Role,
pub content: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum Role {
Assistant,
User,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct MessageBody {
pub model: String,
pub messages: Vec<Message>,
pub max_tokens: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_sequences: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_k: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
}
impl MessageBody {
#[must_use]
pub fn new(model: &str, messages: Vec<Message>, max_tokens: u32) -> Self {
Self {
model: model.into(),
messages,
max_tokens,
stream: Some(true),
..Default::default()
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MessageResponse {
pub id: String,
pub r#type: String,
pub role: String,
pub content: Vec<Content>,
pub model: String,
pub stop_reason: Option<String>,
pub stop_sequence: Option<String>,
pub usage: Usage,
}
#[derive(Debug, Serialize, Deserialize)]
struct MessageEventResponse {
pub id: String,
pub r#type: String,
pub role: String,
pub content: Vec<Content>,
pub model: String,
pub stop_reason: Option<String>,
pub stop_sequence: Option<String>,
pub usage: Usage,
}
#[derive(Debug, Serialize, Deserialize)]
struct Delta {
pub r#type: Option<String>,
pub text: Option<String>,
pub stop_reason: Option<String>,
pub end_turn: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
enum MessageEventType {
#[default]
Error,
MessageStart,
MessageDelta,
MessageStop,
Ping,
ContentBlockStart,
ContentBlockDelta,
ContentBlockStop,
Comment,
}
#[derive(Debug, Serialize, Deserialize, Default)]
struct MessageEvent {
pub r#type: MessageEventType,
pub message: Option<MessageEventResponse>,
pub index: Option<i32>,
pub content_block: Option<Content>,
pub delta: Option<Delta>,
pub usage: Option<Usage>,
pub comment: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Auth {
pub api_key: String,
pub version: Option<String>,
}
impl Auth {
#[must_use]
pub fn new(api_key: String, version: Option<String>) -> Self {
Self { api_key, version }
}
pub fn from_env() -> Result<Self, Error> {
let api_key = match std::env::var("ANTHROPIC_API_KEY") {
Ok(key) => key,
Err(_) => return Err(Error::AuthError("ANTHROPIC_API_KEY not found".to_string())),
};
let version = std::env::var("ANTHROPIC_API_VERSION").ok();
Ok(Self { api_key, version })
}
}
#[derive(Debug, Clone)]
pub struct Client {
pub auth: Auth,
pub api_url: String,
}
impl Client {
pub fn new(auth: Auth, api_url: impl Into<String>) -> Self {
Self {
auth,
api_url: api_url.into(),
}
}
}
impl Client {
pub fn delta<'a>(
&'a self,
message_body: &'a MessageBody,
) -> Result<impl Stream<Item = Result<String, Error>> + 'a, Error> {
log::debug!("message_body: {:#?}", message_body);
let request_body = match serde_json::to_value(message_body) {
Ok(body) => body,
Err(e) => return Err(Error::Serde(e)),
};
log::debug!("request_body: {:#?}", request_body);
let anthropic_version = self.auth.version.as_deref().unwrap_or("2023-06-01");
let client = ClientBuilder::for_url(&(self.api_url.clone() + MESSAGES_CREATE))?
.header("anthropic-version", anthropic_version)?
.header("content-type", "application/json")?
.header("x-api-key", &self.auth.api_key)?
.method("POST".into())
.body(request_body.to_string())
.reconnect(
ReconnectOptions::reconnect(true)
.retry_initial(false)
.delay(Duration::from_secs(1))
.backoff_factor(2)
.delay_max(Duration::from_secs(60))
.build(),
)
.build();
let stream = Box::pin(client.stream())
.map_err(Error::from)
.map_ok(|event| match event {
SSE::Connected(_) => String::default(),
SSE::Event(ev) => match serde_json::from_str::<MessageEvent>(&ev.data) {
Ok(ev) => {
if matches!(ev.r#type, MessageEventType::ContentBlockDelta) {
if let Some(delta) = ev.delta {
delta.text.map_or_else(String::default, |text| text)
} else {
String::default()
}
} else {
String::default()
}
}
Err(e) => {
log::error!("Error parsing event: {:#?}", ev);
log::error!("Error: {:#?}", e);
String::default()
}
},
SSE::Comment(comment) => {
log::debug!("Comment: {:#?}", comment);
String::default()
}
});
Ok(stream)
}
}