1use std::fmt::Debug;
2use std::pin::Pin;
3
4use futures_core::Stream;
5use tea_control::CancellationScope;
6use tea_protocol::ProtocolMetadata;
7use thiserror::Error;
8
9use crate::{ToolExecutionFailure, ToolResult, ValidatedToolInvocation};
10use tea_protocol::ToolPresentation;
11
12#[derive(Debug, Clone, PartialEq)]
14pub struct ToolProgress {
15 message: String,
16 completed_units: u64,
17 total_units: Option<u64>,
18 details: ProtocolMetadata,
19}
20
21impl ToolProgress {
22 pub fn new(
28 message: impl Into<String>,
29 completed_units: u64,
30 total_units: Option<u64>,
31 ) -> Result<Self, ToolStreamViolation> {
32 let message = message.into();
33 if message.is_empty()
34 || message.len() > 4096
35 || message.contains('\0')
36 || total_units.is_some_and(|total| completed_units > total)
37 {
38 return Err(ToolStreamViolation::InvalidProgress);
39 }
40 Ok(Self {
41 message,
42 completed_units,
43 total_units,
44 details: ProtocolMetadata::default(),
45 })
46 }
47 #[must_use]
49 pub fn message(&self) -> &str {
50 &self.message
51 }
52 #[must_use]
54 pub fn with_details(mut self, details: ProtocolMetadata) -> Self {
55 self.details = details;
56 self
57 }
58 #[must_use]
60 pub const fn completed_units(&self) -> u64 {
61 self.completed_units
62 }
63 #[must_use]
65 pub const fn total_units(&self) -> Option<u64> {
66 self.total_units
67 }
68 #[must_use]
70 pub const fn details(&self) -> &ProtocolMetadata {
71 &self.details
72 }
73}
74
75#[derive(Debug, Clone, PartialEq)]
77pub enum ToolExecutionEvent {
78 Progress(ToolProgress),
80 Finished(ToolResult),
82 Failed(ToolExecutionFailure),
84}
85
86pub trait ToolExecutionStream: Stream<Item = ToolExecutionEvent> + Send {}
88impl<T> ToolExecutionStream for T where T: Stream<Item = ToolExecutionEvent> + Send {}
89pub type BoxToolExecutionStream = Pin<Box<dyn ToolExecutionStream + 'static>>;
91
92pub trait ToolExecutor: Debug + Send + Sync {
94 fn preview(&self, _invocation: &ValidatedToolInvocation) -> Option<ToolPresentation> {
100 None
101 }
102
103 fn execute(
105 &self,
106 invocation: ValidatedToolInvocation,
107 cancellation: CancellationScope,
108 ) -> BoxToolExecutionStream;
109}
110
111#[derive(Debug, Default)]
113pub struct ToolStreamValidator {
114 terminal: bool,
115 events: usize,
116}
117impl ToolStreamValidator {
118 #[must_use]
120 pub fn new() -> Self {
121 Self::default()
122 }
123 pub fn observe(&mut self, event: &ToolExecutionEvent) -> Result<(), ToolStreamViolation> {
129 if self.terminal {
130 return Err(ToolStreamViolation::EventAfterTerminal);
131 }
132 if matches!(
133 event,
134 ToolExecutionEvent::Finished(_) | ToolExecutionEvent::Failed(_)
135 ) {
136 self.terminal = true;
137 }
138 self.events += 1;
139 Ok(())
140 }
141 pub fn finish(self) -> Result<usize, ToolStreamViolation> {
147 if self.terminal {
148 Ok(self.events)
149 } else {
150 Err(ToolStreamViolation::MissingTerminal)
151 }
152 }
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
157pub enum ToolStreamViolation {
158 #[error("tool progress is invalid")]
160 InvalidProgress,
161 #[error("tool event appeared after terminal")]
163 EventAfterTerminal,
164 #[error("tool stream ended without terminal result")]
166 MissingTerminal,
167}