bamboo_engine/session_app/execute/
validation.rs1use bamboo_domain::Session;
4
5pub fn validate_image_fallback_for_session(
10 session: &Session,
11 image_fallback: Option<&crate::ImageFallbackConfig>,
12 _model_name: &str,
13 vision_override: Option<bool>,
14) -> Result<(), String> {
15 use crate::ImageFallbackMode;
16
17 if vision_override.is_some() {
18 return Ok(());
19 }
20 let images_seen = session
21 .messages
22 .iter()
23 .filter_map(|message| message.content_parts.as_ref())
24 .flat_map(|parts| parts.iter())
25 .filter(|part| matches!(part, bamboo_agent_core::MessagePart::ImageUrl { .. }))
26 .count();
27 if matches!(
28 image_fallback,
29 Some(crate::ImageFallbackConfig {
30 mode: ImageFallbackMode::Error,
31 ..
32 })
33 ) && images_seen > 0
34 {
35 return Err(format!(
36 "This server does not currently support image inputs (found {images_seen} image part(s)). \
37 Configure hooks.image_fallback.mode='placeholder' or 'ocr' to degrade gracefully."
38 ));
39 }
40
41 Ok(())
42}
43
44#[cfg(test)]
45mod image_reference_tests {
46 use super::*;
47 use bamboo_domain::{ImageUrlRef, Message, MessagePart};
48
49 #[test]
50 fn explicit_nonvision_new_upload_and_history_followup_pass_without_rewriting_messages() {
51 for followup in [false, true] {
52 let mut session = Session::new("image-reference-preflight", "text-only");
53 session.add_message(Message::user_with_parts(
54 "image",
55 vec![MessagePart::ImageUrl {
56 image_url: ImageUrlRef {
57 url: "bamboo-attachment://image-reference-preflight/image1".into(),
58 detail: None,
59 },
60 }],
61 ));
62 if followup {
63 session.add_message(Message::user("inspect the earlier image"));
64 }
65 let before = serde_json::to_vec(&session).unwrap();
66 validate_image_fallback_for_session(
67 &session,
68 Some(&crate::ImageFallbackConfig {
69 mode: crate::ImageFallbackMode::Error,
70 vision_model: None,
71 }),
72 "text-only",
73 Some(false),
74 )
75 .unwrap();
76 assert_eq!(serde_json::to_vec(&session).unwrap(), before);
77 }
78 }
79}