use crate::{AttachmentRef, MediaType, ToolCallRecord};
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct ExecImage {
pub mime: MediaType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reference: Option<AttachmentRef>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub data: Vec<u8>,
pub label: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub width: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub height: Option<u32>,
}
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct TextProjectionMetadata {
pub truncated: bool,
pub original_chars: usize,
pub projected_chars: usize,
pub original_lines: usize,
pub projected_lines: usize,
pub limit: usize,
pub limit_mode: String,
pub max_lines: usize,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct ExecResponse {
pub observations: Vec<String>,
pub observation_truncation: Vec<TextProjectionMetadata>,
pub tool_calls: Vec<ToolCallRecord>,
pub images: Vec<ExecImage>,
pub printed_images: Vec<AttachmentRef>,
pub error: Option<String>,
pub duration_ms: u64,
pub terminal_finish: Option<serde_json::Value>,
}
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct PromptUsage {
pub prompt_context_tokens: usize,
pub input_tokens: usize,
pub cache_read_input_tokens: usize,
pub cache_write_input_tokens: usize,
pub context_budget_tokens: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exec_image_media_type_round_trips_as_the_existing_string_wire_shape() {
let image = ExecImage {
mime: MediaType::parse("image/webp").unwrap(),
reference: None,
data: vec![1, 2, 3],
label: "plot".to_string(),
width: Some(320),
height: Some(180),
};
let value = serde_json::to_value(&image).unwrap();
assert_eq!(value["mime"], "image/webp");
assert_eq!(serde_json::from_value::<ExecImage>(value).unwrap(), image);
}
#[test]
fn exec_image_rejects_an_invalid_media_type_on_deserialization() {
let error = serde_json::from_value::<ExecImage>(serde_json::json!({
"mime": "not-a-mime",
"data": [],
"label": "plot"
}))
.unwrap_err();
assert!(error.to_string().contains("invalid media type"));
}
}