Skip to main content

tea_tools/
executor.rs

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/// Non-durable tool execution progress.
13#[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    /// Creates bounded monotonic progress metadata.
23    ///
24    /// # Errors
25    ///
26    /// Returns an error for invalid messages or completed units above total.
27    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    /// Returns technical progress message.
48    #[must_use]
49    pub fn message(&self) -> &str {
50        &self.message
51    }
52    /// Adds bounded safe progress details.
53    #[must_use]
54    pub fn with_details(mut self, details: ProtocolMetadata) -> Self {
55        self.details = details;
56        self
57    }
58    /// Returns completed units.
59    #[must_use]
60    pub const fn completed_units(&self) -> u64 {
61        self.completed_units
62    }
63    /// Returns total units.
64    #[must_use]
65    pub const fn total_units(&self) -> Option<u64> {
66        self.total_units
67    }
68    /// Returns bounded safe progress details.
69    #[must_use]
70    pub const fn details(&self) -> &ProtocolMetadata {
71        &self.details
72    }
73}
74
75/// One normalized tool execution stream event.
76#[derive(Debug, Clone, PartialEq)]
77pub enum ToolExecutionEvent {
78    /// Non-durable progress.
79    Progress(ToolProgress),
80    /// Successful terminal result.
81    Finished(ToolResult),
82    /// Failed or cancelled terminal result.
83    Failed(ToolExecutionFailure),
84}
85
86/// Provider-neutral asynchronous tool event stream.
87pub trait ToolExecutionStream: Stream<Item = ToolExecutionEvent> + Send {}
88impl<T> ToolExecutionStream for T where T: Stream<Item = ToolExecutionEvent> + Send {}
89/// Object-safe boxed tool execution stream.
90pub type BoxToolExecutionStream = Pin<Box<dyn ToolExecutionStream + 'static>>;
91
92/// Object-safe executor receiving only registry-validated invocations.
93pub trait ToolExecutor: Debug + Send + Sync {
94    /// Produces an optional, non-durable preview for one validated invocation.
95    ///
96    /// Implementations must not mutate external state. A missing preview is
97    /// intentionally indistinguishable from an unavailable preview so callers
98    /// can preserve the normal approval and execution flow.
99    fn preview(&self, _invocation: &ValidatedToolInvocation) -> Option<ToolPresentation> {
100        None
101    }
102
103    /// Creates a lazy execution stream owned by the caller.
104    fn execute(
105        &self,
106        invocation: ValidatedToolInvocation,
107        cancellation: CancellationScope,
108    ) -> BoxToolExecutionStream;
109}
110
111/// Deterministic tool stream grammar validator.
112#[derive(Debug, Default)]
113pub struct ToolStreamValidator {
114    terminal: bool,
115    events: usize,
116}
117impl ToolStreamValidator {
118    /// Creates an empty validator.
119    #[must_use]
120    pub fn new() -> Self {
121        Self::default()
122    }
123    /// Observes one event.
124    ///
125    /// # Errors
126    ///
127    /// Returns an error when an event follows a terminal event.
128    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    /// Finishes after stream end.
142    ///
143    /// # Errors
144    ///
145    /// Returns an error when no terminal event was observed.
146    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/// Tool execution stream grammar violation.
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
157pub enum ToolStreamViolation {
158    /// Progress is invalid.
159    #[error("tool progress is invalid")]
160    InvalidProgress,
161    /// Event followed terminal result.
162    #[error("tool event appeared after terminal")]
163    EventAfterTerminal,
164    /// Stream ended without terminal result.
165    #[error("tool stream ended without terminal result")]
166    MissingTerminal,
167}