Fix SSE parsers to accept optional space after data: prefix (#7929)

Signed-off-by: Nilton Volpato <nilton@volpa.to>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nilton Volpato
2026-03-17 10:24:21 -07:00
committed by GitHub
parent cfc6c55a2e
commit e075861bdf
4 changed files with 24 additions and 14 deletions
@@ -573,11 +573,12 @@ where
let line = line_result?;
// Skip empty lines and non-data lines
if line.trim().is_empty() || !line.starts_with("data: ") {
// Note: SSE spec allows both "data: value" and "data:value" (space is optional)
if line.trim().is_empty() || !line.starts_with("data:") {
continue;
}
let data_part = line.strip_prefix("data: ").unwrap_or(&line);
let data_part = line.strip_prefix("data: ").or_else(|| line.strip_prefix("data:")).unwrap_or(&line);
// Handle end of stream
if data_part.trim() == "[DONE]" {
+11 -7
View File
@@ -520,7 +520,10 @@ fn ensure_valid_json_schema(schema: &mut Value) {
}
fn strip_data_prefix(line: &str) -> Option<&str> {
line.strip_prefix("data: ").map(|s| s.trim())
// SSE spec allows both "data: value" and "data:value" (space after colon is optional)
line.strip_prefix("data: ")
.or_else(|| line.strip_prefix("data:"))
.map(|s| s.trim())
}
pub fn response_to_streaming_message<S>(
@@ -536,12 +539,13 @@ where
let mut accumulated_reasoning_content = String::new();
'outer: while let Some(response) = stream.next().await {
if response.as_ref().is_ok_and(|s| s == "data: [DONE]") {
break 'outer;
}
let response_str = response?;
let line = strip_data_prefix(&response_str);
if line.is_some_and(|l| l == "[DONE]") {
break 'outer;
}
if line.is_none() || line.is_some_and(|l| l.is_empty()) {
continue
}
@@ -580,11 +584,11 @@ where
let mut done = false;
while !done {
if let Some(response_chunk) = stream.next().await {
if response_chunk.as_ref().is_ok_and(|s| s == "data: [DONE]") {
break 'outer;
}
let response_str = response_chunk?;
if let Some(line) = strip_data_prefix(&response_str) {
if line == "[DONE]" {
break 'outer;
}
let tool_chunk: StreamingChunk = serde_json::from_str(line)
.map_err(|e| anyhow!("Failed to parse streaming chunk: {}: {:?}", e, &line))?;
@@ -684,9 +684,12 @@ where
// Parse SSE format: "event: <type>\ndata: <json>"
// For now, we only care about the data line
// SSE spec allows both "data: value" and "data:value" (space after colon is optional)
let data_line = if response_str.starts_with("data: ") {
response_str.strip_prefix("data: ").unwrap()
} else if response_str.starts_with("event: ") {
} else if response_str.starts_with("data:") {
response_str.strip_prefix("data:").unwrap()
} else if response_str.starts_with("event: ") || response_str.starts_with("event:") {
// Skip event type lines
continue;
} else {
@@ -128,13 +128,15 @@ pub fn parse_streaming_response(sse_data: &str) -> Result<Message> {
// Parse each SSE event
for line in sse_data.lines() {
if !line.starts_with("data: ") {
// SSE spec allows both "data: value" and "data:value" (space after colon is optional)
if !line.starts_with("data:") {
continue;
}
let Some(json_str) = line.get(6..) else {
continue;
}; // Remove "data: " prefix
let json_str = line
.strip_prefix("data: ")
.or_else(|| line.strip_prefix("data:"))
.unwrap(); // Remove "data:" prefix
if json_str.trim().is_empty() || json_str.trim() == "[DONE]" {
continue;
}