fix(tui): accept compact SSE data fields

Accept SSE data frames with either `data: {...}` or `data:{...}` in Chat Completions, Responses, and Anthropic stream readers.

Harvested from PR #3152.

Co-authored-by: wgeeker <169752135+wgeeker@users.noreply.github.com>
This commit is contained in:
Hunter B
2026-06-12 02:31:31 -07:00
parent c1a48492b6
commit 2f717d3345
6 changed files with 40 additions and 3 deletions
+4
View File
@@ -49,6 +49,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- **SSE data lines without spaces (#3152).** Chat Completions, Responses, and
Anthropic stream readers now accept both `data: {...}` and `data:{...}` SSE
frames, matching the spec and preventing providers that omit the optional
space from streaming empty output. Thanks @wgeeker for the PR.
- **SiliconFlow China provider config (#2893/#2895).** `siliconflow-CN`
now reads its own `[providers.siliconflow_cn]` / `[providers.siliconflow-CN]`
table and falls back to `[providers.siliconflow]` only for unset
+4
View File
@@ -49,6 +49,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- **SSE data lines without spaces (#3152).** Chat Completions, Responses, and
Anthropic stream readers now accept both `data: {...}` and `data:{...}` SSE
frames, matching the spec and preventing providers that omit the optional
space from streaming empty output. Thanks @wgeeker for the PR.
- **SiliconFlow China provider config (#2893/#2895).** `siliconflow-CN`
now reads its own `[providers.siliconflow_cn]` / `[providers.siliconflow-CN]`
table and falls back to `[providers.siliconflow]` only for unset
+29
View File
@@ -1543,6 +1543,11 @@ mod anthropic;
mod chat;
mod responses;
fn extract_sse_data_value(line: &str) -> Option<&str> {
line.strip_prefix("data:")
.map(|value| value.strip_prefix(' ').unwrap_or(value))
}
pub(crate) use chat::{CacheWarmupKey, PromptInspection};
pub(crate) fn inspect_prompt_for_request(request: &MessageRequest) -> PromptInspection {
@@ -3942,4 +3947,28 @@ mod tests {
"https://api.deepseek.com/v1/chat/completions"
);
}
#[test]
fn extract_sse_data_value_accepts_optional_space() {
assert_eq!(
extract_sse_data_value("data: {\"ok\":true}"),
Some("{\"ok\":true}")
);
assert_eq!(
extract_sse_data_value("data:{\"ok\":true}"),
Some("{\"ok\":true}")
);
}
#[test]
fn extract_sse_data_value_handles_done_marker() {
assert_eq!(extract_sse_data_value("data: [DONE]"), Some("[DONE]"));
assert_eq!(extract_sse_data_value("data:[DONE]"), Some("[DONE]"));
}
#[test]
fn extract_sse_data_value_rejects_non_data_lines() {
assert_eq!(extract_sse_data_value("event: message"), None);
assert_eq!(extract_sse_data_value(": heartbeat"), None);
}
}
+1 -1
View File
@@ -205,7 +205,7 @@ impl DeepSeekClient {
// `event:` lines are redundant (the data payload carries
// `type`) and comment/heartbeat lines are ignorable.
let Some(data) = line.strip_prefix("data: ") else {
let Some(data) = super::extract_sse_data_value(&line) else {
continue;
};
+1 -1
View File
@@ -437,7 +437,7 @@ impl DeepSeekClient {
continue;
}
if let Some(data) = line.strip_prefix("data: ") {
if let Some(data) = super::extract_sse_data_value(&line) {
line_buf.push_str(data);
}
// Ignore other SSE fields (event:, id:, retry:)
+1 -1
View File
@@ -165,7 +165,7 @@ impl DeepSeekClient {
continue;
}
if let Some(data) = line.strip_prefix("data: ") {
if let Some(data) = super::extract_sse_data_value(&line) {
if data == "[DONE]" {
done = true;
break;