use std::fmt::Debug;
use std::pin::Pin;
use futures_core::Stream;
use tea_control::CancellationScope;
use tea_protocol::ProtocolMetadata;
use thiserror::Error;
use crate::{ToolExecutionFailure, ToolResult, ValidatedToolInvocation};
use tea_protocol::ToolPresentation;
#[derive(Debug, Clone, PartialEq)]
pub struct ToolProgress {
message: String,
completed_units: u64,
total_units: Option<u64>,
details: ProtocolMetadata,
}
impl ToolProgress {
pub fn new(
message: impl Into<String>,
completed_units: u64,
total_units: Option<u64>,
) -> Result<Self, ToolStreamViolation> {
let message = message.into();
if message.is_empty()
|| message.len() > 4096
|| message.contains('\0')
|| total_units.is_some_and(|total| completed_units > total)
{
return Err(ToolStreamViolation::InvalidProgress);
}
Ok(Self {
message,
completed_units,
total_units,
details: ProtocolMetadata::default(),
})
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
#[must_use]
pub fn with_details(mut self, details: ProtocolMetadata) -> Self {
self.details = details;
self
}
#[must_use]
pub const fn completed_units(&self) -> u64 {
self.completed_units
}
#[must_use]
pub const fn total_units(&self) -> Option<u64> {
self.total_units
}
#[must_use]
pub const fn details(&self) -> &ProtocolMetadata {
&self.details
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ToolExecutionEvent {
Progress(ToolProgress),
Finished(ToolResult),
Failed(ToolExecutionFailure),
}
pub trait ToolExecutionStream: Stream<Item = ToolExecutionEvent> + Send {}
impl<T> ToolExecutionStream for T where T: Stream<Item = ToolExecutionEvent> + Send {}
pub type BoxToolExecutionStream = Pin<Box<dyn ToolExecutionStream + 'static>>;
pub trait ToolExecutor: Debug + Send + Sync {
fn preview(&self, _invocation: &ValidatedToolInvocation) -> Option<ToolPresentation> {
None
}
fn execute(
&self,
invocation: ValidatedToolInvocation,
cancellation: CancellationScope,
) -> BoxToolExecutionStream;
}
#[derive(Debug, Default)]
pub struct ToolStreamValidator {
terminal: bool,
events: usize,
}
impl ToolStreamValidator {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn observe(&mut self, event: &ToolExecutionEvent) -> Result<(), ToolStreamViolation> {
if self.terminal {
return Err(ToolStreamViolation::EventAfterTerminal);
}
if matches!(
event,
ToolExecutionEvent::Finished(_) | ToolExecutionEvent::Failed(_)
) {
self.terminal = true;
}
self.events += 1;
Ok(())
}
pub fn finish(self) -> Result<usize, ToolStreamViolation> {
if self.terminal {
Ok(self.events)
} else {
Err(ToolStreamViolation::MissingTerminal)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
pub enum ToolStreamViolation {
#[error("tool progress is invalid")]
InvalidProgress,
#[error("tool event appeared after terminal")]
EventAfterTerminal,
#[error("tool stream ended without terminal result")]
MissingTerminal,
}