1use std::collections::BTreeMap;
2
3use serde::de::{Error as DeError, MapAccess, Visitor};
4use serde::ser::SerializeMap;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use serde_json::{Map, Number, Value};
7
8use crate::AttachmentRef;
9
10const TAG_KEY: &str = "$lash_tool_value";
11const ATTACHMENT_TAG: &str = "attachment";
12const OBJECT_TAG: &str = "object";
13const REF_KEY: &str = "ref";
14const ENTRIES_KEY: &str = "entries";
15
16#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
17pub struct ToolCallOutput {
18 pub outcome: ToolCallOutcome,
19 #[serde(default, skip_serializing_if = "Option::is_none")]
20 pub control: Option<ToolControl>,
21}
22
23#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
24pub struct ToolCallRecord {
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub call_id: Option<String>,
27 pub tool: String,
28 pub args: Value,
29 pub output: ToolCallOutput,
30 pub duration_ms: u64,
31}
32
33impl ToolCallOutput {
34 pub fn success(value: impl Into<ToolValue>) -> Self {
35 Self {
36 outcome: ToolCallOutcome::Success(value.into()),
37 control: None,
38 }
39 }
40
41 pub fn failure(failure: ToolFailure) -> Self {
42 Self {
43 outcome: ToolCallOutcome::Failure(failure),
44 control: None,
45 }
46 }
47
48 pub fn cancelled(cancellation: ToolCancellation) -> Self {
49 Self {
50 outcome: ToolCallOutcome::Cancelled(cancellation),
51 control: None,
52 }
53 }
54
55 pub fn with_control(mut self, control: ToolControl) -> Self {
56 self.control = Some(control);
57 self
58 }
59
60 pub fn is_success(&self) -> bool {
61 matches!(self.outcome, ToolCallOutcome::Success(_))
62 }
63
64 pub fn status(&self) -> ToolCallStatus {
65 match self.outcome {
66 ToolCallOutcome::Success(_) => ToolCallStatus::Success,
67 ToolCallOutcome::Failure(_) => ToolCallStatus::Failure,
68 ToolCallOutcome::Cancelled(_) => ToolCallStatus::Cancelled,
69 }
70 }
71
72 pub fn value_for_projection(&self) -> Value {
73 match &self.outcome {
74 ToolCallOutcome::Success(value) => value.to_json_value(),
75 ToolCallOutcome::Failure(failure) => failure.to_json_value(),
76 ToolCallOutcome::Cancelled(cancellation) => cancellation.to_json_value(),
77 }
78 }
79
80 pub fn attachments(&self) -> Vec<AttachmentRef> {
81 match &self.outcome {
82 ToolCallOutcome::Success(value) => value.attachments(),
83 ToolCallOutcome::Failure(failure) => failure
84 .raw
85 .as_ref()
86 .map(ToolValue::attachments)
87 .unwrap_or_default(),
88 ToolCallOutcome::Cancelled(cancellation) => cancellation
89 .raw
90 .as_ref()
91 .map(ToolValue::attachments)
92 .unwrap_or_default(),
93 }
94 }
95}
96
97pub fn format_tool_output_content(output: &ToolCallOutput) -> String {
98 match &output.outcome {
99 ToolCallOutcome::Success(value) => {
100 let value = value.to_json_value();
101 match value {
102 Value::String(text) => text,
103 other => serde_json::to_string(&other).unwrap_or_else(|_| "null".to_string()),
104 }
105 }
106 ToolCallOutcome::Failure(failure) => format_failure_message(failure),
107 ToolCallOutcome::Cancelled(cancellation) => format_cancellation_message(cancellation),
108 }
109}
110
111#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
112#[serde(rename_all = "snake_case")]
113pub enum ToolCallStatus {
114 Success,
115 Failure,
116 Cancelled,
117}
118
119#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
120#[serde(tag = "status", content = "payload", rename_all = "snake_case")]
121pub enum ToolCallOutcome {
122 Success(ToolValue),
123 Failure(ToolFailure),
124 Cancelled(ToolCancellation),
125}
126
127#[derive(Clone, Debug, PartialEq)]
128pub enum ToolValue {
129 Null,
130 Bool(bool),
131 Number(Number),
132 String(String),
133 Array(Vec<ToolValue>),
134 Object(BTreeMap<String, ToolValue>),
135 Attachment(AttachmentRef),
136}
137
138impl ToolValue {
139 pub fn to_json_value(&self) -> Value {
140 serde_json::to_value(self).unwrap_or(Value::Null)
141 }
142
143 pub fn from_json_value(value: Value) -> serde_json::Result<Self> {
144 serde_json::from_value(value)
145 }
146
147 pub fn attachments(&self) -> Vec<AttachmentRef> {
148 let mut attachments = Vec::new();
149 self.collect_attachments(&mut attachments);
150 attachments
151 }
152
153 pub fn model_parts(&self) -> Vec<ModelToolReturnPart> {
154 let mut parts = Vec::new();
155 match self {
156 Self::String(text) => push_text_part(&mut parts, text.clone()),
157 Self::Attachment(reference) => {
158 parts.push(ModelToolReturnPart::Attachment(reference.clone()))
159 }
160 Self::Null | Self::Bool(_) | Self::Number(_) | Self::Array(_) | Self::Object(_) => {
161 self.push_compact_model_parts(&mut parts);
162 }
163 }
164 parts
165 }
166
167 fn collect_attachments(&self, attachments: &mut Vec<AttachmentRef>) {
168 match self {
169 Self::Attachment(reference) => attachments.push(reference.clone()),
170 Self::Array(values) => {
171 for value in values {
172 value.collect_attachments(attachments);
173 }
174 }
175 Self::Object(entries) => {
176 for value in entries.values() {
177 value.collect_attachments(attachments);
178 }
179 }
180 Self::Null | Self::Bool(_) | Self::Number(_) | Self::String(_) => {}
181 }
182 }
183
184 fn push_compact_model_parts(&self, parts: &mut Vec<ModelToolReturnPart>) {
185 match self {
186 Self::Null => push_text_part(parts, "null"),
187 Self::Bool(value) => push_text_part(parts, value.to_string()),
188 Self::Number(value) => push_text_part(parts, value.to_string()),
189 Self::String(value) => push_text_part(
190 parts,
191 serde_json::to_string(value).unwrap_or_else(|_| "\"\"".into()),
192 ),
193 Self::Attachment(reference) => {
194 parts.push(ModelToolReturnPart::Attachment(reference.clone()))
195 }
196 Self::Array(values) => {
197 push_text_part(parts, "[");
198 for (index, value) in values.iter().enumerate() {
199 if index > 0 {
200 push_text_part(parts, ",");
201 }
202 value.push_compact_model_parts(parts);
203 }
204 push_text_part(parts, "]");
205 }
206 Self::Object(entries) => {
207 push_text_part(parts, "{");
208 for (index, (key, value)) in entries.iter().enumerate() {
209 if index > 0 {
210 push_text_part(parts, ",");
211 }
212 push_text_part(
213 parts,
214 serde_json::to_string(key).unwrap_or_else(|_| "\"\"".into()),
215 );
216 push_text_part(parts, ":");
217 value.push_compact_model_parts(parts);
218 }
219 push_text_part(parts, "}");
220 }
221 }
222 }
223}
224
225impl From<Value> for ToolValue {
226 fn from(value: Value) -> Self {
227 match value {
228 Value::Null => Self::Null,
229 Value::Bool(value) => Self::Bool(value),
230 Value::Number(value) => Self::Number(value),
231 Value::String(value) => Self::String(value),
232 Value::Array(values) => Self::Array(values.into_iter().map(Self::from).collect()),
233 Value::Object(values) => Self::Object(
234 values
235 .into_iter()
236 .map(|(key, value)| (key, Self::from(value)))
237 .collect(),
238 ),
239 }
240 }
241}
242
243impl From<&str> for ToolValue {
244 fn from(value: &str) -> Self {
245 Self::String(value.to_string())
246 }
247}
248
249impl From<String> for ToolValue {
250 fn from(value: String) -> Self {
251 Self::String(value)
252 }
253}
254
255impl Serialize for ToolValue {
256 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
257 where
258 S: Serializer,
259 {
260 match self {
261 Self::Null => serializer.serialize_none(),
262 Self::Bool(value) => serializer.serialize_bool(*value),
263 Self::Number(value) => value.serialize(serializer),
264 Self::String(value) => serializer.serialize_str(value),
265 Self::Array(values) => values.serialize(serializer),
266 Self::Attachment(reference) => {
267 let mut map = serializer.serialize_map(Some(2))?;
268 map.serialize_entry(TAG_KEY, ATTACHMENT_TAG)?;
269 map.serialize_entry(REF_KEY, reference)?;
270 map.end()
271 }
272 Self::Object(entries) => {
273 if entries.contains_key(TAG_KEY) {
274 let mut map = serializer.serialize_map(Some(2))?;
275 map.serialize_entry(TAG_KEY, OBJECT_TAG)?;
276 map.serialize_entry(ENTRIES_KEY, entries)?;
277 map.end()
278 } else {
279 entries.serialize(serializer)
280 }
281 }
282 }
283 }
284}
285
286impl<'de> Deserialize<'de> for ToolValue {
287 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
288 where
289 D: Deserializer<'de>,
290 {
291 struct ToolValueVisitor;
292
293 impl<'de> Visitor<'de> for ToolValueVisitor {
294 type Value = ToolValue;
295
296 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 formatter.write_str("a Lash tool value")
298 }
299
300 fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
301 Ok(ToolValue::Bool(value))
302 }
303
304 fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> {
305 Ok(ToolValue::Number(Number::from(value)))
306 }
307
308 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> {
309 Ok(ToolValue::Number(Number::from(value)))
310 }
311
312 fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
313 where
314 E: DeError,
315 {
316 Number::from_f64(value)
317 .map(ToolValue::Number)
318 .ok_or_else(|| E::custom("non-finite number is not a valid tool value"))
319 }
320
321 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> {
322 Ok(ToolValue::String(value.to_string()))
323 }
324
325 fn visit_string<E>(self, value: String) -> Result<Self::Value, E> {
326 Ok(ToolValue::String(value))
327 }
328
329 fn visit_none<E>(self) -> Result<Self::Value, E> {
330 Ok(ToolValue::Null)
331 }
332
333 fn visit_unit<E>(self) -> Result<Self::Value, E> {
334 Ok(ToolValue::Null)
335 }
336
337 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
338 where
339 A: serde::de::SeqAccess<'de>,
340 {
341 let mut values = Vec::new();
342 while let Some(value) = seq.next_element()? {
343 values.push(value);
344 }
345 Ok(ToolValue::Array(values))
346 }
347
348 fn visit_map<A>(self, mut access: A) -> Result<Self::Value, A::Error>
349 where
350 A: MapAccess<'de>,
351 {
352 let mut map = Map::new();
353 while let Some((key, value)) = access.next_entry::<String, Value>()? {
354 map.insert(key, value);
355 }
356 decode_object(map).map_err(A::Error::custom)
357 }
358 }
359
360 deserializer.deserialize_any(ToolValueVisitor)
361 }
362}
363
364fn decode_object(mut map: Map<String, Value>) -> serde_json::Result<ToolValue> {
365 let Some(tag) = map.get(TAG_KEY) else {
366 return Ok(ToolValue::Object(
367 map.into_iter()
368 .map(|(key, value)| Ok((key, ToolValue::from_json_value(value)?)))
369 .collect::<serde_json::Result<_>>()?,
370 ));
371 };
372 let tag = tag
373 .as_str()
374 .ok_or_else(|| serde_json::Error::custom("reserved tool value tag must be a string"))?;
375 match tag {
376 ATTACHMENT_TAG => {
377 if map.len() != 2 || !map.contains_key(REF_KEY) {
378 return Err(serde_json::Error::custom("malformed attachment tool value"));
379 }
380 let reference = serde_json::from_value(
381 map.remove(REF_KEY)
382 .ok_or_else(|| serde_json::Error::custom("missing attachment ref"))?,
383 )?;
384 Ok(ToolValue::Attachment(reference))
385 }
386 OBJECT_TAG => {
387 if map.len() != 2 || !map.contains_key(ENTRIES_KEY) {
388 return Err(serde_json::Error::custom(
389 "malformed escaped object tool value",
390 ));
391 }
392 serde_json::from_value(
393 map.remove(ENTRIES_KEY)
394 .ok_or_else(|| serde_json::Error::custom("missing escaped object entries"))?,
395 )
396 .map(ToolValue::Object)
397 }
398 other => Err(serde_json::Error::custom(format!(
399 "unknown reserved tool value tag `{other}`"
400 ))),
401 }
402}
403
404#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
405pub struct ToolFailure {
406 pub class: ToolFailureClass,
407 pub code: String,
408 pub message: String,
409 pub source: ToolFailureSource,
410 pub retry: ToolRetryDisposition,
411 #[serde(default, skip_serializing_if = "Option::is_none")]
412 pub raw: Option<ToolValue>,
413}
414
415impl ToolFailure {
416 pub fn new(
417 class: ToolFailureClass,
418 code: impl Into<String>,
419 message: impl Into<String>,
420 ) -> Self {
421 Self {
422 class,
423 code: code.into(),
424 message: message.into(),
425 source: ToolFailureSource::Runtime,
426 retry: ToolRetryDisposition::Never,
427 raw: None,
428 }
429 }
430
431 pub fn runtime(
432 class: ToolFailureClass,
433 code: impl Into<String>,
434 message: impl Into<String>,
435 ) -> Self {
436 Self::new(class, code, message)
437 }
438
439 pub fn tool(
440 class: ToolFailureClass,
441 code: impl Into<String>,
442 message: impl Into<String>,
443 ) -> Self {
444 Self {
445 source: ToolFailureSource::Tool,
446 ..Self::new(class, code, message)
447 }
448 }
449
450 pub fn safe_retry(
451 class: ToolFailureClass,
452 code: impl Into<String>,
453 message: impl Into<String>,
454 after_ms: Option<u64>,
455 ) -> Self {
456 let mut failure = Self::tool(class, code, message);
457 failure.retry = ToolRetryDisposition::Safe { after_ms };
458 failure
459 }
460
461 pub fn with_retry(mut self, retry: ToolRetryDisposition) -> Self {
462 self.retry = retry;
463 self
464 }
465
466 pub fn to_json_value(&self) -> Value {
467 serde_json::to_value(self).unwrap_or_else(|_| Value::String(self.message.clone()))
468 }
469}
470
471#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
472#[serde(rename_all = "snake_case")]
473pub enum ToolFailureClass {
474 InvalidRequest,
475 Unavailable,
476 PermissionDenied,
477 Timeout,
478 Execution,
479 External,
480 ResourceLimit,
481 Internal,
482}
483
484#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
485#[serde(rename_all = "snake_case")]
486pub enum ToolFailureSource {
487 Runtime,
488 Tool,
489 Plugin,
490 Policy,
491 Cancellation,
492}
493
494#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
495#[serde(tag = "type", rename_all = "snake_case")]
496pub enum ToolRetryDisposition {
497 Never,
498 Safe {
499 #[serde(default, skip_serializing_if = "Option::is_none")]
500 after_ms: Option<u64>,
501 },
502 Exhausted {
503 attempts: u32,
504 },
505}
506
507#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
508pub struct ToolCancellation {
509 pub message: String,
510 pub source: ToolFailureSource,
511 #[serde(default, skip_serializing_if = "Option::is_none")]
512 pub raw: Option<ToolValue>,
513}
514
515impl ToolCancellation {
516 pub fn runtime(message: impl Into<String>) -> Self {
517 Self {
518 message: message.into(),
519 source: ToolFailureSource::Cancellation,
520 raw: None,
521 }
522 }
523
524 pub fn to_json_value(&self) -> Value {
525 serde_json::to_value(self).unwrap_or_else(|_| Value::String(self.message.clone()))
526 }
527}
528
529#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
530#[serde(tag = "type", rename_all = "snake_case")]
531pub enum ToolControl {
532 SwitchAgentFrame {
533 frame_id: String,
534 #[serde(default, skip_serializing_if = "Vec::is_empty")]
535 initial_nodes: Vec<Value>,
536 #[serde(default, skip_serializing_if = "Option::is_none")]
537 task: Option<String>,
538 },
539 Finish {
540 value: ToolValue,
541 },
542 Fail {
543 failure: ToolFailure,
544 },
545}
546
547#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
548pub struct ModelToolReturn {
549 pub call_id: String,
550 pub tool_name: String,
551 pub parts: Vec<ModelToolReturnPart>,
552}
553
554impl ModelToolReturn {
555 pub fn from_output(call_id: String, tool_name: String, output: &ToolCallOutput) -> Self {
556 let parts = model_parts_from_tool_output(output);
557 Self {
558 call_id,
559 tool_name,
560 parts,
561 }
562 }
563
564 pub fn text(call_id: String, tool_name: String, content: impl Into<String>) -> Self {
565 Self {
566 call_id,
567 tool_name,
568 parts: vec![ModelToolReturnPart::text(content)],
569 }
570 }
571}
572
573#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
574#[serde(tag = "type", rename_all = "snake_case")]
575pub enum ModelToolReturnPart {
576 Text { text: String },
577 Attachment(AttachmentRef),
578}
579
580impl ModelToolReturnPart {
581 pub fn text(text: impl Into<String>) -> Self {
582 Self::Text { text: text.into() }
583 }
584}
585
586pub fn model_parts_from_tool_output(output: &ToolCallOutput) -> Vec<ModelToolReturnPart> {
587 match &output.outcome {
588 ToolCallOutcome::Success(value) => value.model_parts(),
589 ToolCallOutcome::Failure(failure) => {
590 let mut parts = vec![ModelToolReturnPart::text(format_failure_message(failure))];
591 if let Some(raw) = &failure.raw {
592 parts.extend(
593 raw.attachments()
594 .into_iter()
595 .map(ModelToolReturnPart::Attachment),
596 );
597 }
598 parts
599 }
600 ToolCallOutcome::Cancelled(cancellation) => {
601 let mut parts = vec![ModelToolReturnPart::text(format_cancellation_message(
602 cancellation,
603 ))];
604 if let Some(raw) = &cancellation.raw {
605 parts.extend(
606 raw.attachments()
607 .into_iter()
608 .map(ModelToolReturnPart::Attachment),
609 );
610 }
611 parts
612 }
613 }
614}
615
616fn push_text_part(parts: &mut Vec<ModelToolReturnPart>, text: impl Into<String>) {
617 let text = text.into();
618 if text.is_empty() {
619 return;
620 }
621 if let Some(ModelToolReturnPart::Text { text: existing }) = parts.last_mut() {
622 existing.push_str(&text);
623 } else {
624 parts.push(ModelToolReturnPart::text(text));
625 }
626}
627
628fn format_failure_message(failure: &ToolFailure) -> String {
629 if failure.message.is_empty() {
630 "[Tool execution failed]".to_string()
631 } else {
632 format!("[Tool execution failed]\n{}", failure.message)
633 }
634}
635
636fn format_cancellation_message(cancellation: &ToolCancellation) -> String {
637 if cancellation.message.is_empty() {
638 "[Tool execution cancelled]".to_string()
639 } else {
640 format!("[Tool execution cancelled]\n{}", cancellation.message)
641 }
642}
643
644#[cfg(test)]
645mod tests {
646 use super::*;
647 use crate::{AttachmentId, AttachmentMeta, ImageMediaType, MediaType};
648
649 fn image_ref(id: &str) -> AttachmentRef {
650 AttachmentMeta::new(
651 AttachmentId::new(id),
652 MediaType::Image(ImageMediaType::Png),
653 3,
654 Some(1),
655 Some(1),
656 Some("tiny".to_string()),
657 )
658 .as_ref()
659 }
660
661 #[test]
662 fn tool_value_serializes_nested_attachments() {
663 let value = ToolValue::Array(vec![ToolValue::Attachment(image_ref("img"))]);
664
665 let json = serde_json::to_value(&value).unwrap();
666
667 assert_eq!(json[0][TAG_KEY], ATTACHMENT_TAG);
668 assert_eq!(json[0][REF_KEY]["id"], "img");
669 assert_eq!(serde_json::from_value::<ToolValue>(json).unwrap(), value);
670 }
671
672 #[test]
673 fn tool_value_escapes_user_reserved_key() {
674 let value = ToolValue::Object(BTreeMap::from([(
675 TAG_KEY.to_string(),
676 ToolValue::String("user".into()),
677 )]));
678
679 let json = serde_json::to_value(&value).unwrap();
680
681 assert_eq!(json[TAG_KEY], OBJECT_TAG);
682 assert!(json[ENTRIES_KEY].is_object());
683 assert_eq!(serde_json::from_value::<ToolValue>(json).unwrap(), value);
684 }
685
686 #[test]
687 fn tool_value_rejects_malformed_reserved_object() {
688 let json = serde_json::json!({ TAG_KEY: ATTACHMENT_TAG, "extra": true });
689
690 assert!(serde_json::from_value::<ToolValue>(json).is_err());
691 }
692
693 #[test]
694 fn tool_value_model_parts_preserve_attachment_position() {
695 let value = ToolValue::Array(vec![
696 ToolValue::String("before".into()),
697 ToolValue::Attachment(image_ref("img")),
698 ToolValue::String("after".into()),
699 ]);
700
701 assert_eq!(
702 value.model_parts(),
703 vec![
704 ModelToolReturnPart::text("[\"before\","),
705 ModelToolReturnPart::Attachment(image_ref("img")),
706 ModelToolReturnPart::text(",\"after\"]"),
707 ]
708 );
709 }
710
711 #[test]
712 fn tool_output_failure_projects_raw_attachments_after_failure_text() {
713 let attachment = image_ref("img");
714 let output = ToolCallOutput::failure(ToolFailure {
715 class: ToolFailureClass::Execution,
716 code: "boom".into(),
717 message: "boom".into(),
718 source: ToolFailureSource::Tool,
719 retry: ToolRetryDisposition::Never,
720 raw: Some(ToolValue::Object(BTreeMap::from([(
721 "image".into(),
722 ToolValue::Attachment(attachment.clone()),
723 )]))),
724 });
725
726 assert_eq!(
727 model_parts_from_tool_output(&output),
728 vec![
729 ModelToolReturnPart::text("[Tool execution failed]\nboom"),
730 ModelToolReturnPart::Attachment(attachment),
731 ]
732 );
733 }
734
735 #[test]
736 fn model_tool_return_text_part_serializes() {
737 let part = ModelToolReturnPart::text("hello");
738
739 let json = serde_json::to_value(&part).unwrap();
740
741 assert_eq!(json, serde_json::json!({ "type": "text", "text": "hello" }));
742 assert_eq!(
743 serde_json::from_value::<ModelToolReturnPart>(json).unwrap(),
744 part
745 );
746 }
747
748 #[test]
749 fn tool_output_status_distinguishes_cancelled_from_failure() {
750 let failure = ToolCallOutput::failure(ToolFailure::tool(
751 ToolFailureClass::Execution,
752 "boom",
753 "boom",
754 ));
755 let cancelled = ToolCallOutput::cancelled(ToolCancellation::runtime("stopped"));
756
757 assert_eq!(failure.status(), ToolCallStatus::Failure);
758 assert_eq!(cancelled.status(), ToolCallStatus::Cancelled);
759 assert!(!cancelled.is_success());
760 }
761}