use std::io::{self, BufRead, BufReader};
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};
use serde_json::Value;
use super::reply::{self, Delta, Reply};
use super::{Key, ProviderError, error};
const CONNECT: Duration = Duration::from_secs(30);
const FIRST_BYTE: Duration = Duration::from_secs(300);
const BODY_LIMIT: Duration = Duration::from_secs(60 * 60);
const API_VERSION: &str = "2023-06-01";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Endpoint {
pub address: String,
pub model: String,
pub key_header: String,
pub key: Option<Key>,
pub silence: Duration,
}
impl Endpoint {
pub const SILENCE: Duration = Duration::from_secs(120);
#[must_use]
pub fn url(&self) -> String {
format!("{}/v1/messages", self.address.trim().trim_end_matches('/'))
}
pub fn stream(
&self,
body: &Value,
on_delta: &mut dyn FnMut(Delta),
cancelled: &dyn Fn() -> bool,
) -> Result<Reply, ProviderError> {
let url = self.url();
let mut body = body.clone();
if let Some(object) = body.as_object_mut() {
object.insert("model".to_owned(), Value::String(self.model.clone()));
object.insert("stream".to_owned(), Value::Bool(true));
}
let config = ureq::Agent::config_builder()
.timeout_connect(Some(CONNECT))
.timeout_recv_response(Some(FIRST_BYTE))
.timeout_recv_body(Some(BODY_LIMIT))
.http_status_as_error(false)
.build();
let agent = ureq::Agent::new_with_config(config);
let header = self.key_header.trim();
let key = self.key.as_ref().map_or("", Key::expose);
let secret =
if header.eq_ignore_ascii_case("authorization") { format!("Bearer {key}") } else { key.to_owned() };
let unreachable =
|error: ureq::Error| ProviderError::Unreachable { url: url.clone(), reason: error.to_string() };
let mut request =
agent.post(&url).header("anthropic-version", API_VERSION).header("accept", "text/event-stream");
if self.key.is_some() {
request = request.header(header, &secret);
}
let mut answer =
request.content_type("application/json").send(body.to_string().as_str()).map_err(unreachable)?;
let status = answer.status().as_u16();
if !(200..300).contains(&status) {
let said = answer.body_mut().read_to_string().unwrap_or_default();
let refused = ProviderError::Refused { url, status, said: error::complaint(&said) };
return Err(refused.without(key));
}
let lines = Watched::start(BufReader::new(answer.into_body().into_reader()), self.silence, cancelled);
reply::read(lines, on_delta, cancelled).map_err(|error| error.without(key))
}
}
const POLL: Duration = Duration::from_millis(100);
struct Watched<'a> {
lines: mpsc::Receiver<io::Result<String>>,
silence: Duration,
cancelled: &'a dyn Fn() -> bool,
}
impl<'a> Watched<'a> {
fn start(reader: impl BufRead + Send + 'static, silence: Duration, cancelled: &'a dyn Fn() -> bool) -> Self {
let (sender, lines) = mpsc::sync_channel(64);
thread::spawn(move || {
for line in reader.lines() {
if sender.send(line).is_err() {
return;
}
}
});
Self { lines, silence, cancelled }
}
}
impl Iterator for Watched<'_> {
type Item = io::Result<String>;
fn next(&mut self) -> Option<Self::Item> {
let waiting = Instant::now();
loop {
if (self.cancelled)() {
return None;
}
let left = self.silence.saturating_sub(waiting.elapsed());
if left.is_zero() {
let said =
format!("the provider sent nothing for {} seconds and was given up on", self.silence.as_secs());
return Some(Err(io::Error::new(io::ErrorKind::TimedOut, said)));
}
match self.lines.recv_timeout(left.min(POLL)) {
Ok(line) => return Some(line),
Err(mpsc::RecvTimeoutError::Timeout) => {}
Err(mpsc::RecvTimeoutError::Disconnected) => return None,
}
}
}
}