use std::{
env,
fmt::Debug,
io::{Error as IoError, Result as IoResult},
mem,
pin::Pin,
task::{Context, Poll},
time::Duration,
};
use futures::{Stream, TryStreamExt};
use reqwest::{
Body, Client,
multipart::{Form, Part},
};
use tokio_util::{
bytes::Bytes,
codec::{FramedRead, LinesCodec},
io::StreamReader,
};
use crate::_prelude::*;
pub(crate) type EventStream<T> = _Stream<Result<T>>;
type _Stream<T> = Pin<Box<dyn Send + Stream<Item = T>>>;
type ByteStream = _Stream<IoResult<Bytes>>;
pub trait ApiBase
where
Self: Send + Sync,
{
fn base_uri(&self) -> &str;
fn get(&self, endpoint: &str) -> impl Send + Future<Output = Result<String>>;
fn post_multipart(
&self,
endpoint: &str,
multipart: Multipart,
) -> impl Send + Future<Output = Result<String>>;
fn post_json<S>(&self, endpoint: &str, body: S) -> impl Send + Future<Output = Result<String>>
where
S: Send + Serialize;
fn sse<S, H>(
&self,
endpoint: &str,
body: S,
options: SseOptions<H>,
) -> impl Send + Future<Output = Result<EventStream<H::Event>>>
where
S: Send + Serialize,
H: 'static + EventHandler;
fn sse_with_resume<S, H>(
&self,
endpoint: &str,
body: S,
options: SseOptions<H>,
last_event_id: Option<&str>,
) -> impl Send + Future<Output = Result<EventStream<H::Event>>>
where
S: Send + Serialize,
H: 'static + EventHandler;
}
pub trait EventHandler
where
Self: Send,
{
type Event;
fn handle_event(&self, #[allow(unused)] event: &str) -> Result<()> {
Ok(())
}
fn handle_data(&self, data: String) -> Result<Self::Event>;
fn handle_unexpected(&self, #[allow(unused)] unexpected: String) -> Result<()> {
Ok(())
}
}
impl EventHandler for () {
type Event = String;
fn handle_data(&self, data: String) -> Result<Self::Event> {
Ok(data)
}
}
#[derive(Debug)]
pub struct SseOptions<H> {
pub drop_event: bool,
pub event_handler: H,
pub reconnect: Reconnect,
}
impl<H> SseOptions<H> {
pub fn new(event_handler: H) -> Self {
Self { drop_event: false, event_handler, reconnect: Reconnect::default() }
}
pub fn drop_event(mut self, drop: bool) -> Self {
self.drop_event = drop;
self
}
pub fn event_handler(mut self, event_handler: H) -> Self {
self.event_handler = event_handler;
self
}
pub fn reconnect(mut self, reconnect: Reconnect) -> Self {
self.reconnect = reconnect;
self
}
}
#[derive(Debug)]
pub struct Reconnect {
pub support: bool,
pub max_retries: usize,
pub retry_interval: Duration,
}
impl Default for Reconnect {
fn default() -> Self {
Self { support: false, max_retries: 3, retry_interval: Duration::from_millis(200) }
}
}
#[derive(Clone, Debug)]
pub struct Api {
http: Client,
auth: Auth,
}
impl Api {
pub fn new(auth: Auth) -> Self {
let http =
Client::builder().user_agent("openagent").build().expect("build must succeed; qed");
Self { http, auth }
}
}
impl ApiBase for Api {
fn base_uri(&self) -> &str {
&self.auth.uri
}
async fn get(&self, endpoint: &str) -> Result<String> {
Ok(self
.http
.get(format!("{}{endpoint}", self.base_uri()))
.bearer_auth(&self.auth.key)
.send()
.await?
.text()
.await?)
}
async fn post_multipart(&self, endpoint: &str, multipart: Multipart) -> Result<String> {
Ok(self
.http
.post(format!("{}{endpoint}", self.base_uri()))
.bearer_auth(&self.auth.key)
.multipart(multipart.into())
.send()
.await?
.text()
.await?)
}
async fn post_json<S>(&self, endpoint: &str, body: S) -> Result<String>
where
S: Send + Serialize,
{
Ok(self
.http
.post(format!("{}{endpoint}", self.base_uri()))
.bearer_auth(&self.auth.key)
.json(&body)
.send()
.await?
.text()
.await?)
}
async fn sse<S, H>(
&self,
endpoint: &str,
body: S,
options: SseOptions<H>,
) -> Result<Pin<Box<dyn Send + Stream<Item = Result<H::Event>>>>>
where
S: Send + Serialize,
H: 'static + EventHandler,
{
let stream = self
.http
.post(format!("{}{endpoint}", self.base_uri()))
.bearer_auth(&self.auth.key)
.header("Accept", "text/event-stream")
.header("Cache-Control", "no-cache")
.json(&body)
.send()
.await?
.bytes_stream()
.map_err(IoError::other);
let reader = StreamReader::new(Box::pin(stream) as _);
let stream = FramedRead::new(reader, LinesCodec::new());
Ok(Box::pin(Sse {
stream,
options,
last_event: Default::default(),
data: Default::default(),
unexpected: Default::default(),
}))
}
async fn sse_with_resume<S, H>(
&self,
endpoint: &str,
body: S,
options: SseOptions<H>,
last_event_id: Option<&str>,
) -> Result<Pin<Box<dyn Send + Stream<Item = Result<H::Event>>>>>
where
S: Send + Serialize,
H: 'static + EventHandler,
{
let mut req = self
.http
.post(format!("{}{endpoint}", self.base_uri()))
.bearer_auth(&self.auth.key)
.header("Accept", "text/event-stream")
.header("Cache-Control", "no-cache")
.json(&body);
if let Some(event_id) = last_event_id {
req = req.header("Last-Event-ID", event_id);
}
let stream = req.send().await?.bytes_stream().map_err(IoError::other);
let reader = StreamReader::new(Box::pin(stream) as _);
let stream = FramedRead::new(reader, LinesCodec::new());
Ok(Box::pin(Sse {
stream,
options,
last_event: (None, last_event_id.map(Into::into)),
data: Default::default(),
unexpected: Default::default(),
}))
}
}
#[derive(Clone, Debug)]
pub struct Auth {
pub uri: String,
pub key: String,
}
impl Auth {
pub fn from_env() -> Self {
Auth {
uri: env::var("OPENAI_BASE_URL").expect("OPENAI_BASE_URL must be set; qed"),
key: env::var("OPENAI_API_KEY").expect("OPENAI_API_KEY must be set; qed"),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Multipart {
#[allow(clippy::type_complexity)]
pub binary: Vec<(Cow<'static, str>, Cow<'static, [u8]>, Option<String>)>,
pub text: Vec<(Cow<'static, str>, Cow<'static, str>)>,
}
impl From<Multipart> for Form {
fn from(val: Multipart) -> Form {
val.binary.into_iter().fold(
val.text.into_iter().fold(Form::new(), |form, (k, v)| form.text(k, v)),
|form, (k, v, filename)| {
let len = v.len() as _;
form.part(
k,
match v {
Cow::Borrowed(v) => build_stream_part(v, len, filename),
Cow::Owned(v) => build_stream_part(v, len, filename),
},
)
},
)
}
}
#[pin_project::pin_project]
pub struct Sse<T> {
#[pin]
pub stream: FramedRead<StreamReader<ByteStream, Bytes>, LinesCodec>,
pub options: SseOptions<T>,
pub last_event: (Option<String>, Option<String>),
pub data: String,
pub unexpected: String,
}
impl<T> Stream for Sse<T>
where
T: EventHandler,
{
type Item = Result<T::Event>;
fn poll_next(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Option<Self::Item>> {
let mut this = self.project();
loop {
match Pin::new(&mut this.stream).poll_next(ctx) {
Poll::Ready(Some(Ok(line))) => {
let line = line.trim();
if line.is_empty() {
if !this.data.is_empty() {
let data = mem::take(this.data);
this.data.shrink_to_fit();
let res = this.options.event_handler.handle_data(data);
this.last_event.0 = None;
return Poll::Ready(Some(res));
}
continue;
}
tracing::debug!("{line}");
if let Some(data_chunk) = line.strip_prefix("data: ") {
if data_chunk == "[DONE]" {
return Poll::Ready(None);
}
if !this.data.is_empty() {
this.data.push('\n');
}
this.data.push_str(data_chunk);
} else if let Some(event) = line.strip_prefix("event: ") {
if !this.options.drop_event {
this.last_event.0 = Some(event.into());
if let Err(e) = this.options.event_handler.handle_event(event) {
return Poll::Ready(Some(Err(e)));
}
}
} else if let Some(event_id) = line.strip_prefix("id: ") {
this.last_event.1 = Some(event_id.into());
} else if let Some(retry_ms) = line.strip_prefix("retry: ") {
if let Ok(_ms) = retry_ms.parse::<u64>() {
}
} else if line.starts_with(':') {
continue;
} else {
if !this.unexpected.is_empty() {
this.unexpected.push('\n');
}
this.unexpected.push_str(line);
}
},
Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e.into()))),
Poll::Ready(None) => {
if !this.unexpected.is_empty() {
let unexpected = mem::take(this.unexpected);
this.unexpected.shrink_to_fit();
if let Err(e) = this.options.event_handler.handle_unexpected(unexpected) {
return Poll::Ready(Some(Err(e)));
}
}
return Poll::Ready(None);
},
Poll::Pending => return Poll::Pending,
}
}
}
}
fn build_stream_part<T>(data: T, data_len: u64, filename: Option<String>) -> Part
where
T: Into<Body>,
{
let part = Part::stream_with_length(data, data_len);
if let Some(filename) = filename { part.file_name(filename) } else { part }
}