feat: add scoped multimodal image attachments

This commit is contained in:
2026-08-10 21:49:39 +09:00
parent 64ced7dbad
commit 38b8f26a50
42 changed files with 718 additions and 38 deletions
+29
View File
@@ -153,6 +153,7 @@ impl From<LoggedItem> for Item {
summary,
content,
is_error,
attachments: Vec::new(),
},
LoggedItem::Reasoning {
text,
@@ -213,6 +214,9 @@ impl From<&ContentPart> for LoggedContentPart {
fn from(part: &ContentPart) -> Self {
match part {
ContentPart::Text { text } => Self::Text { text: text.clone() },
ContentPart::Image { .. } => Self::Text {
text: part.as_text().to_string(),
},
ContentPart::Refusal { refusal } => Self::Refusal {
refusal: refusal.clone(),
},
@@ -370,6 +374,31 @@ mod tests {
}
}
#[test]
fn tool_result_persistence_drops_binary_attachments() {
let original = Item::tool_result_item_with_attachments(
"call_image",
"attached",
None,
false,
vec![llm_engine::tool::Attachment::Image(
llm_engine::tool::ImageAttachment::new(
"image/png",
std::sync::Arc::<[u8]>::from(&b"secret-image-body"[..]),
),
)],
);
let logged: LoggedItem = (&original).into();
let json = serde_json::to_string(&logged).unwrap();
assert!(!json.contains("secret-image-body"));
assert!(!json.contains("attachments"));
match Item::from(logged) {
Item::ToolResult { attachments, .. } => assert!(attachments.is_empty()),
other => panic!("unexpected variant: {other:?}"),
}
}
#[test]
fn message_serialization_uses_kind_tag() {
let logged: LoggedItem = (&Item::assistant_message("hi")).into();