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:
@@ -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]" {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user