use std::{future::Future, pin::Pin, time::Duration};
use futures_core::Stream;
use futures_util::{
StreamExt,
future::{Either, select},
};
use runifold_core::{CancellationToken, Instant, InvocationId, RunContext, RunId};
use crate::{
ModelCapabilities, ModelError, ModelErrorKind, ModelRef, ModelRequest, ModelResponse,
ModelStreamAccumulator, ModelStreamEvent,
};
#[cfg(not(target_arch = "wasm32"))]
pub type ModelFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[cfg(target_arch = "wasm32")]
pub type ModelFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
#[cfg(not(target_arch = "wasm32"))]
pub type ModelEventStream =
Pin<Box<dyn Stream<Item = Result<ModelStreamEvent, ModelError>> + Send + 'static>>;
#[cfg(target_arch = "wasm32")]
pub type ModelEventStream =
Pin<Box<dyn Stream<Item = Result<ModelStreamEvent, ModelError>> + 'static>>;
#[derive(Clone, Debug)]
pub struct ModelCallContext {
invocation_id: InvocationId,
run_id: Option<RunId>,
deadline: Option<Instant>,
cancellation: CancellationToken,
}
impl ModelCallContext {
pub fn new() -> Self {
Self {
invocation_id: InvocationId::new(),
run_id: None,
deadline: None,
cancellation: CancellationToken::new(),
}
}
pub fn for_run(run: &RunContext) -> Self {
Self {
invocation_id: InvocationId::new(),
run_id: Some(run.run_id()),
deadline: run.deadline(),
cancellation: run.cancellation().child_token(),
}
}
pub const fn invocation_id(&self) -> InvocationId {
self.invocation_id
}
pub const fn run_id(&self) -> Option<RunId> {
self.run_id
}
pub const fn deadline(&self) -> Option<Instant> {
self.deadline
}
pub fn remaining(&self) -> Option<Duration> {
self.deadline
.map(|deadline| deadline.saturating_duration_since(Instant::now()))
}
pub const fn cancellation(&self) -> &CancellationToken {
&self.cancellation
}
#[must_use]
pub fn with_deadline(mut self, deadline: Instant) -> Self {
self.deadline = Some(
self.deadline
.map_or(deadline, |current| current.min(deadline)),
);
self
}
#[must_use]
pub fn with_cancellation(mut self, cancellation: &CancellationToken) -> Self {
self.cancellation = cancellation.child_token();
self
}
#[must_use]
pub fn child_attempt(&self) -> Self {
Self {
invocation_id: InvocationId::new(),
run_id: self.run_id,
deadline: self.deadline,
cancellation: self.cancellation.child_token(),
}
}
}
impl Default for ModelCallContext {
fn default() -> Self {
Self::new()
}
}
pub trait Model: Send + Sync {
fn capabilities<'a>(
&'a self,
model: &'a ModelRef,
) -> ModelFuture<'a, Result<ModelCapabilities, ModelError>>;
fn stream(
&self,
request: ModelRequest,
context: ModelCallContext,
) -> ModelFuture<'_, Result<ModelEventStream, ModelError>>;
fn invoke(
&self,
request: ModelRequest,
context: ModelCallContext,
) -> ModelFuture<'_, Result<ModelResponse, ModelError>> {
Box::pin(async move {
let cancellation = context.cancellation().clone();
let stream_future = self.stream(request, context);
let mut stream =
match select(Box::pin(cancellation.cancelled()), Box::pin(stream_future)).await {
Either::Left(_) => return Err(cancelled_error()),
Either::Right((result, _)) => result?,
};
let mut accumulator = ModelStreamAccumulator::new();
loop {
let next = stream.next();
match select(Box::pin(cancellation.cancelled()), Box::pin(next)).await {
Either::Left(_) => return Err(cancelled_error()),
Either::Right((Some(event), _)) => {
if let Some(response) = accumulator.push(event?)? {
return Ok(response);
}
}
Either::Right((None, _)) => {
return Err(ModelError::local(
ModelErrorKind::Protocol,
"model stream ended before a terminal response event",
));
}
}
}
})
}
}
pub trait ProviderModel: Model {
fn provider(&self) -> &str;
fn model_ref(&self, model: impl Into<String>) -> ModelRef
where
Self: Sized,
{
ModelRef::new(self.provider(), model)
}
}
fn cancelled_error() -> ModelError {
ModelError::local(ModelErrorKind::Cancelled, "model invocation was cancelled")
}