fix: parse flat content arrays in Responses API input
When input is [{type: 'input_image', ...}, {type: 'input_text', text: '...'}],
the code was looking for items with role: 'user' which don't exist in flat
content arrays. Now extracts text from input_text items directly first,
falling back to role-based messages only if no flat text found.
Also adds debug header dump for MITM request forwarding.
This commit is contained in:
@@ -80,33 +80,49 @@ fn extract_responses_input(
|
|||||||
text_items
|
text_items
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Normal input extraction (existing logic)
|
// Normal input extraction
|
||||||
items
|
// First try: flat content parts (input_text / input_image)
|
||||||
|
let flat_text: String = items
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.filter(|item| {
|
||||||
.find(|item| item["role"].as_str() == Some("user"))
|
let t = item["type"].as_str().unwrap_or("");
|
||||||
.and_then(|item| {
|
t == "input_text" || t == "text"
|
||||||
// Also scan content array for images
|
|
||||||
if image.is_none() {
|
|
||||||
image = super::util::extract_first_image(&item["content"]);
|
|
||||||
}
|
|
||||||
match &item["content"] {
|
|
||||||
serde_json::Value::String(s) => Some(s.clone()),
|
|
||||||
serde_json::Value::Array(parts) => Some(
|
|
||||||
parts
|
|
||||||
.iter()
|
|
||||||
.filter(|p| {
|
|
||||||
let t = p["type"].as_str().unwrap_or("");
|
|
||||||
t == "input_text" || t == "text"
|
|
||||||
})
|
|
||||||
.filter_map(|p| p["text"].as_str())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(" "),
|
|
||||||
),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.filter_map(|p| p["text"].as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
|
if !flat_text.is_empty() {
|
||||||
|
flat_text
|
||||||
|
} else {
|
||||||
|
// Fallback: conversation-style with role: "user"
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.find(|item| item["role"].as_str() == Some("user"))
|
||||||
|
.and_then(|item| {
|
||||||
|
// Also scan content array for images
|
||||||
|
if image.is_none() {
|
||||||
|
image = super::util::extract_first_image(&item["content"]);
|
||||||
|
}
|
||||||
|
match &item["content"] {
|
||||||
|
serde_json::Value::String(s) => Some(s.clone()),
|
||||||
|
serde_json::Value::Array(parts) => Some(
|
||||||
|
parts
|
||||||
|
.iter()
|
||||||
|
.filter(|p| {
|
||||||
|
let t = p["type"].as_str().unwrap_or("");
|
||||||
|
t == "input_text" || t == "text"
|
||||||
|
})
|
||||||
|
.filter_map(|p| p["text"].as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" "),
|
||||||
|
),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => String::new(),
|
_ => String::new(),
|
||||||
|
|||||||
@@ -633,6 +633,16 @@ async fn handle_http_over_tls(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Forward the request — if write fails, reconnect and retry once
|
// Forward the request — if write fails, reconnect and retry once
|
||||||
|
// DEBUG: dump headers and total size
|
||||||
|
if req_path.contains("streamGenerateContent") {
|
||||||
|
let hdr_end = find_headers_end(&request_buf).unwrap_or(request_buf.len());
|
||||||
|
let hdr_str = String::from_utf8_lossy(&request_buf[..hdr_end.min(request_buf.len())]);
|
||||||
|
info!(
|
||||||
|
total_buf_len = request_buf.len(),
|
||||||
|
body_len = request_buf.len() - hdr_end,
|
||||||
|
"MITM: sending request to upstream\n{hdr_str}"
|
||||||
|
);
|
||||||
|
}
|
||||||
if let Err(e) = conn.write_all(&request_buf).await {
|
if let Err(e) = conn.write_all(&request_buf).await {
|
||||||
debug!(domain, error = %e, "MITM: upstream write failed, reconnecting");
|
debug!(domain, error = %e, "MITM: upstream write failed, reconnecting");
|
||||||
let c = connect_upstream(domain, &upstream_config).await?;
|
let c = connect_upstream(domain, &upstream_config).await?;
|
||||||
|
|||||||
Reference in New Issue
Block a user