1use crate::{AttachmentRef, MediaType, ToolCallRecord};
2
3#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
8pub struct ExecImage {
9 pub mime: MediaType,
10 #[serde(default, skip_serializing_if = "Option::is_none")]
11 pub reference: Option<AttachmentRef>,
12 #[serde(default, skip_serializing_if = "Vec::is_empty")]
13 pub data: Vec<u8>,
14 pub label: String,
15 #[serde(default, skip_serializing_if = "Option::is_none")]
16 pub width: Option<u32>,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub height: Option<u32>,
19}
20
21#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
22pub struct TextProjectionMetadata {
23 pub truncated: bool,
24 pub original_chars: usize,
25 pub projected_chars: usize,
26 pub original_lines: usize,
27 pub projected_lines: usize,
28 pub limit: usize,
29 pub limit_mode: String,
30 pub max_lines: usize,
31}
32
33#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
34pub struct ExecResponse {
35 pub observations: Vec<String>,
36 pub observation_truncation: Vec<TextProjectionMetadata>,
37 pub tool_calls: Vec<ToolCallRecord>,
38 pub images: Vec<ExecImage>,
39 pub printed_images: Vec<AttachmentRef>,
40 pub error: Option<String>,
41 pub duration_ms: u64,
42 pub terminal_finish: Option<serde_json::Value>,
47}
48
49#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
51pub struct PromptUsage {
52 pub prompt_context_tokens: usize,
53 pub input_tokens: usize,
54 pub cache_read_input_tokens: usize,
55 pub cache_write_input_tokens: usize,
56 pub context_budget_tokens: usize,
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn exec_image_media_type_round_trips_as_the_existing_string_wire_shape() {
65 let image = ExecImage {
66 mime: MediaType::parse("image/webp").unwrap(),
67 reference: None,
68 data: vec![1, 2, 3],
69 label: "plot".to_string(),
70 width: Some(320),
71 height: Some(180),
72 };
73
74 let value = serde_json::to_value(&image).unwrap();
75 assert_eq!(value["mime"], "image/webp");
76 assert_eq!(serde_json::from_value::<ExecImage>(value).unwrap(), image);
77 }
78
79 #[test]
80 fn exec_image_rejects_an_invalid_media_type_on_deserialization() {
81 let error = serde_json::from_value::<ExecImage>(serde_json::json!({
82 "mime": "not-a-mime",
83 "data": [],
84 "label": "plot"
85 }))
86 .unwrap_err();
87
88 assert!(error.to_string().contains("invalid media type"));
89 }
90}