1use serde_json::Value;
2use tea_protocol::{ContentBlock, ProtocolMetadata, ToolPresentation, Usage};
3use thiserror::Error;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ToolExecutionFailureCode {
8 ExecutionFailed,
10 Cancelled,
12 InvalidOutput,
14 Internal,
16}
17
18#[derive(Debug, Clone, PartialEq)]
20pub struct ToolExecutionFailure {
21 code: ToolExecutionFailureCode,
22 message: String,
23 details: ProtocolMetadata,
24}
25
26impl ToolExecutionFailure {
27 pub fn execution(message: impl Into<String>) -> Result<Self, ToolResultError> {
33 Self::new(ToolExecutionFailureCode::ExecutionFailed, message)
34 }
35 #[must_use]
37 pub fn cancelled() -> Self {
38 Self {
39 code: ToolExecutionFailureCode::Cancelled,
40 message: "tool execution was cancelled".to_owned(),
41 details: ProtocolMetadata::default(),
42 }
43 }
44 #[must_use]
46 pub fn invalid_output() -> Self {
47 Self {
48 code: ToolExecutionFailureCode::InvalidOutput,
49 message: "tool executor returned invalid output".to_owned(),
50 details: ProtocolMetadata::default(),
51 }
52 }
53 #[must_use]
55 pub fn internal_contract() -> Self {
56 Self {
57 code: ToolExecutionFailureCode::Internal,
58 message: "tool executor stream ended without a terminal result".to_owned(),
59 details: ProtocolMetadata::default(),
60 }
61 }
62 fn new(
63 code: ToolExecutionFailureCode,
64 message: impl Into<String>,
65 ) -> Result<Self, ToolResultError> {
66 let message = message.into();
67 if message.is_empty() || message.len() > 4096 || message.contains('\0') {
68 return Err(ToolResultError::InvalidFailureMessage);
69 }
70 Ok(Self {
71 code,
72 message,
73 details: ProtocolMetadata::default(),
74 })
75 }
76 #[must_use]
78 pub fn with_details(mut self, details: ProtocolMetadata) -> Self {
79 self.details = details;
80 self
81 }
82 #[must_use]
84 pub const fn code(&self) -> ToolExecutionFailureCode {
85 self.code
86 }
87 #[must_use]
89 pub fn message(&self) -> &str {
90 &self.message
91 }
92 #[must_use]
94 pub const fn details(&self) -> &ProtocolMetadata {
95 &self.details
96 }
97}
98
99#[derive(Debug, Clone, PartialEq)]
101pub struct ToolResult {
102 content: Vec<ContentBlock>,
103 output: Value,
104 details: ProtocolMetadata,
105 presentation: Option<ToolPresentation>,
106 usage: Option<Usage>,
107}
108
109impl ToolResult {
110 pub fn new(content: Vec<ContentBlock>, output: Value) -> Result<Self, ToolResultError> {
116 if content.is_empty()
117 || content.len() > 256
118 || !output.is_object()
119 || serde_json::to_vec(&output)
120 .map_err(|_| ToolResultError::InvalidResult)?
121 .len()
122 > 256 * 1024
123 || json_depth(&output) > 32
124 {
125 return Err(ToolResultError::InvalidResult);
126 }
127 if content.iter().any(|block| {
128 !matches!(
129 block,
130 ContentBlock::Text { .. } | ContentBlock::Image { .. }
131 )
132 }) || content
133 .iter()
134 .any(|block| serde_json::to_value(block).is_err())
135 {
136 return Err(ToolResultError::InvalidResult);
137 }
138 Ok(Self {
139 content,
140 output,
141 details: ProtocolMetadata::default(),
142 presentation: None,
143 usage: None,
144 })
145 }
146 #[must_use]
148 pub fn with_details(mut self, details: ProtocolMetadata) -> Self {
149 self.details = details;
150 self
151 }
152 #[must_use]
154 pub fn with_presentation(mut self, presentation: ToolPresentation) -> Self {
155 self.presentation = Some(presentation);
156 self
157 }
158 #[must_use]
160 pub fn with_usage(mut self, usage: Usage) -> Self {
161 self.usage = Some(usage);
162 self
163 }
164 #[must_use]
166 pub fn content(&self) -> &[ContentBlock] {
167 &self.content
168 }
169 #[must_use]
171 pub const fn output(&self) -> &Value {
172 &self.output
173 }
174 #[must_use]
176 pub const fn details(&self) -> &ProtocolMetadata {
177 &self.details
178 }
179 #[must_use]
181 pub const fn presentation(&self) -> Option<&ToolPresentation> {
182 self.presentation.as_ref()
183 }
184 #[must_use]
186 pub const fn usage(&self) -> Option<&Usage> {
187 self.usage.as_ref()
188 }
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
193pub enum ToolResultError {
194 #[error("tool result is invalid")]
196 InvalidResult,
197 #[error("tool failure message is invalid")]
199 InvalidFailureMessage,
200}
201
202fn json_depth(value: &Value) -> usize {
203 match value {
204 Value::Array(values) => 1 + values.iter().map(json_depth).max().unwrap_or(0),
205 Value::Object(values) => 1 + values.values().map(json_depth).max().unwrap_or(0),
206 _ => 1,
207 }
208}