#[inline]
pub(crate) fn estimate_tokens(char_len: usize) -> u64 {
char_len.div_ceil(4) as u64
}
pub(crate) fn json_value_size_estimate(value: &serde_json::Value) -> usize {
match value {
serde_json::Value::Null => 4,
serde_json::Value::Bool(b) => if *b { 4 } else { 5 },
serde_json::Value::Number(n) => {
let s = n.to_string();
s.len()
}
serde_json::Value::String(s) => s.len() + 2, serde_json::Value::Array(arr) => {
2 + arr.iter().map(json_value_size_estimate).sum::<usize>() + arr.len().saturating_sub(1) }
serde_json::Value::Object(obj) => {
2 + obj.iter().map(|(k, v)| k.len() + 3 + json_value_size_estimate(v)).sum::<usize>() + obj.len().saturating_sub(1)
}
}
}
pub(crate) fn coerce_json_types(value: &mut serde_json::Value) {
use serde_json::Value;
match value {
Value::Object(map) => {
for v in map.values_mut() {
coerce_json_types(v);
}
}
Value::Array(arr) => {
for v in arr {
coerce_json_types(v);
}
}
Value::String(s) => {
if let Ok(n) = s.parse::<i64>() {
*value = Value::Number(n.into());
} else if let Ok(n) = s.parse::<f64>() {
if n.is_finite() {
if let Some(num) = serde_json::Number::from_f64(n) {
*value = Value::Number(num);
}
}
} else if s == "true" {
*value = Value::Bool(true);
} else if s == "false" {
*value = Value::Bool(false);
} else if s == "null" {
*value = Value::Null;
}
}
_ => {}
}
}
#[inline]
pub(crate) fn redact_for_event(s: &str) -> String {
if s.len() <= 200 {
s.to_string()
} else {
let mut boundary = 200;
while boundary > 0 && !s.is_char_boundary(boundary) {
boundary -= 1;
}
format!("{}... ({} bytes)", &s[..boundary], s.len())
}
}
pub(crate) fn detect_image_media_type(
data: &[u8],
) -> Option<rig::completion::message::ImageMediaType> {
use rig::completion::message::ImageMediaType;
if data.len() < 4 {
return None;
}
if data.starts_with(&[0x89, 0x50, 0x4E, 0x47]) {
Some(ImageMediaType::PNG)
} else if data.starts_with(&[0xFF, 0xD8, 0xFF]) {
Some(ImageMediaType::JPEG)
} else if data.starts_with(b"GIF8") {
Some(ImageMediaType::GIF)
} else if data.len() >= 12 && &data[0..4] == b"RIFF" && &data[8..12] == b"WEBP" {
Some(ImageMediaType::WEBP)
} else {
None
}
}
#[cfg(test)]
mod tests {
#[test]
fn resource_text_non_json_returns_string() {
let text = "Hello, this is plain text from a resource";
let content_text: Option<String> = Some(text.to_string());
let result: serde_json::Value = content_text
.map(|t| serde_json::from_str(&t).unwrap_or(serde_json::Value::String(t)))
.unwrap_or(serde_json::Value::Null);
assert!(
result.is_string(),
"Non-JSON text should be String, not Null"
);
assert_eq!(result.as_str().unwrap(), text);
}
#[test]
fn resource_text_json_returns_parsed() {
let text = r#"{"key": "value"}"#;
let content_text: Option<String> = Some(text.to_string());
let result: serde_json::Value = content_text
.map(|t| serde_json::from_str(&t).unwrap_or(serde_json::Value::String(t)))
.unwrap_or(serde_json::Value::Null);
assert!(result.is_object());
}
#[test]
fn resource_text_none_returns_null() {
let content_text: Option<String> = None;
let result: serde_json::Value = content_text
.map(|t| serde_json::from_str(&t).unwrap_or(serde_json::Value::String(t)))
.unwrap_or(serde_json::Value::Null);
assert!(result.is_null());
}
#[test]
fn wave2_run_agent_response_extraction_loses_json_objects() {
let output_string = serde_json::json!({ "response": "Hello world" });
let extracted_string = output_string
.get("response")
.and_then(|v| v.as_str())
.unwrap_or("");
assert_eq!(extracted_string, "Hello world", "String extraction works");
let output_object = serde_json::json!({
"response": {
"title": "AI Blog Post",
"content": "This is a structured response",
"metadata": { "word_count": 42 }
}
});
let extracted_object = output_object
.get("response")
.and_then(|v| v.as_str()) .unwrap_or("");
assert_eq!(
extracted_object, "",
"BUG PROVEN: JSON object response is silently replaced with empty string. \
The response field exists and contains valid JSON, but as_str() returns None \
for non-string JSON values."
);
let correct_extraction = output_object
.get("response")
.map(|v| match v {
serde_json::Value::String(s) => s.clone(),
other => other.to_string(),
})
.unwrap_or_default();
assert!(
!correct_extraction.is_empty(),
"Correct extraction should preserve the JSON object"
);
assert!(
correct_extraction.contains("AI Blog Post"),
"Correct extraction should contain the title"
);
let output_array = serde_json::json!({
"response": ["item1", "item2", "item3"]
});
let extracted_array = output_array
.get("response")
.and_then(|v| v.as_str())
.unwrap_or("");
assert_eq!(
extracted_array, "",
"BUG PROVEN: Array responses are also silently lost"
);
let output_number = serde_json::json!({ "response": 42 });
let extracted_number = output_number
.get("response")
.and_then(|v| v.as_str())
.unwrap_or("");
assert_eq!(
extracted_number, "",
"BUG PROVEN: Numeric responses are also silently lost"
);
let output_bool = serde_json::json!({ "response": true });
let extracted_bool = output_bool
.get("response")
.and_then(|v| v.as_str())
.unwrap_or("");
assert_eq!(
extracted_bool, "",
"BUG PROVEN: Boolean responses are also silently lost"
);
}
#[test]
fn agent_response_preserves_json_object() {
let mut output = serde_json::Map::new();
let obj = serde_json::json!({"title": "Hello", "score": 42});
output.insert("response".to_string(), obj.clone());
let final_output = serde_json::Value::Object(output);
let response = match final_output.get("response") {
Some(serde_json::Value::String(s)) => s.clone(),
Some(v) => v.to_string(),
None => String::new(),
};
let parsed: serde_json::Value = serde_json::from_str(&response).unwrap();
assert_eq!(parsed, serde_json::json!({"title": "Hello", "score": 42}));
}
#[test]
fn agent_response_preserves_json_array() {
let mut output = serde_json::Map::new();
let arr = serde_json::json!(["item1", "item2", "item3"]);
output.insert("response".to_string(), arr);
let final_output = serde_json::Value::Object(output);
let response = match final_output.get("response") {
Some(serde_json::Value::String(s)) => s.clone(),
Some(v) => v.to_string(),
None => String::new(),
};
assert_eq!(response, r#"["item1","item2","item3"]"#);
}
#[test]
fn agent_response_preserves_string() {
let mut output = serde_json::Map::new();
output.insert(
"response".to_string(),
serde_json::Value::String("Hello world".to_string()),
);
let final_output = serde_json::Value::Object(output);
let response = match final_output.get("response") {
Some(serde_json::Value::String(s)) => s.clone(),
Some(v) => v.to_string(),
None => String::new(),
};
assert_eq!(response, "Hello world");
}
#[test]
fn agent_response_handles_number() {
let mut output = serde_json::Map::new();
output.insert("response".to_string(), serde_json::json!(42));
let final_output = serde_json::Value::Object(output);
let response = match final_output.get("response") {
Some(serde_json::Value::String(s)) => s.clone(),
Some(v) => v.to_string(),
None => String::new(),
};
assert_eq!(response, "42");
}
#[test]
fn agent_response_handles_boolean() {
let mut output = serde_json::Map::new();
output.insert("response".to_string(), serde_json::json!(true));
let final_output = serde_json::Value::Object(output);
let response = match final_output.get("response") {
Some(serde_json::Value::String(s)) => s.clone(),
Some(v) => v.to_string(),
None => String::new(),
};
assert_eq!(response, "true");
}
#[test]
fn agent_response_handles_null() {
let mut output = serde_json::Map::new();
output.insert("response".to_string(), serde_json::Value::Null);
let final_output = serde_json::Value::Object(output);
let response = match final_output.get("response") {
Some(serde_json::Value::String(s)) => s.clone(),
Some(v) => v.to_string(),
None => String::new(),
};
assert_eq!(response, "null");
}
#[test]
fn agent_response_handles_missing_key() {
let output = serde_json::Map::new();
let final_output = serde_json::Value::Object(output);
let response = match final_output.get("response") {
Some(serde_json::Value::String(s)) => s.clone(),
Some(v) => v.to_string(),
None => String::new(),
};
assert_eq!(response, "");
}
#[test]
fn detect_image_media_type_png() {
let data = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
let result = super::detect_image_media_type(&data);
assert_eq!(result, Some(rig::completion::message::ImageMediaType::PNG));
}
#[test]
fn detect_image_media_type_jpeg() {
let data = [0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10];
let result = super::detect_image_media_type(&data);
assert_eq!(result, Some(rig::completion::message::ImageMediaType::JPEG));
}
#[test]
fn detect_image_media_type_gif() {
let data = b"GIF89a\x00\x00";
let result = super::detect_image_media_type(data);
assert_eq!(result, Some(rig::completion::message::ImageMediaType::GIF));
}
#[test]
fn detect_image_media_type_webp() {
let data = b"RIFF\x00\x00\x00\x00WEBP";
let result = super::detect_image_media_type(data);
assert_eq!(result, Some(rig::completion::message::ImageMediaType::WEBP));
}
#[test]
fn detect_image_media_type_unknown() {
let data = [0x00, 0x01, 0x02, 0x03];
let result = super::detect_image_media_type(&data);
assert_eq!(result, None);
}
#[test]
fn detect_image_media_type_too_small() {
let data = [0x89, 0x50];
let result = super::detect_image_media_type(&data);
assert_eq!(result, None);
}
}