OpenAI Responses API streaming: SSE parser requires a space after `data:`/`event:`, silently dropping events from spec-compliant servers
- Dominant language
- Java
- Stars
- 51.8k
- Forks
- 4.4k
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 188
Description
### Background
When using the built-in AI Chat (CE 26.2) with an OpenAI-compatible provider that implements the Responses API (e.g. Alibaba Cloud DashScope `https://dashscope.aliyuncs.com/compatible-mode/v1/`), prompts are sent and the HTTP request succeeds (200, SSE stream flowing), but no response is ever rendered and the token counter stays at 0. No error is shown.
### Root cause
In `org.jkiss.dbeaver.model.ai.engine.openai.OpenAiAPIStreamConsumer`:
```java
public static final String DATA_EVENT = "data: "; // note the trailing space
private static final String EVENT_EVENT = "event: "; // note the trailing space
...
if (event.startsWith(DATA_EVENT)) { ... } else if (event.startsWith(EVENT_EVENT)) { ... } else { log.debug("Unknown OpenAI event: " + event); }
```
The parser only recognizes lines that start with `"data: "` / `"event: "` (colon **plus** a space).
Per the WHATWG SSE spec (https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation), after parsing the field name, "If the field value starts with a single U+0020 SPACE character, remove it from value." — the space is **optional**. `data:{json}` is perfectly valid SSE and is emitted by several major OpenAI-compatible providers (DashScope among them, verified byte-for-byte with `curl | od -c`).
Because such lines match neither prefix, every chunk falls into the `else` branch (logged only at debug level) and is silently discarded. From the user's perspective the chat just hangs with 0 tokens, which looks like a connectivity problem rather than a parsing one.
### Suggested fix
Parse SSE lines per spec instead of prefix-matching with a hardcoded space, e.g.:
```java
if (event.startsWith("data:")) {
String data = event.substring(5).stripLeading();
...
} else if (event.startsWith("event:")) {
String eventType = event.substring(6).stripLeading();
...
}
```
### Related (same streaming path, minor)
1. `AIHttpUtils.resolve(baseUrl, "models")` uses `URI.resolve`, so a Base URL without a trailing slash (`.../compatible-mode/v1`) resolves to `.../compatible-mode/models` (404). Normalizing the base URL (appending `/` when the path ends with a version segment, or documenting the requirement) would save a lot of debugging. The default OpenAI endpoint happens to end with `/v1/`, which hides this.
2. The legacy-chat fallback in `OpenAIClientResponses.mapHttpError` triggers only when the 400 body contains specific hardcoded strings (`"is not supported via Responses API"`, `"model_not_supported"`, ...). Providers that reject the Responses API with a different error wording (DashScope returns `<400> InternalError.Algo.InvalidParameter ...` for `function_call_output` input items) get no fallback and just error out. A configurable "use Chat Completions API instead of Responses API" switch would make the OpenAI engine work with any compatible provider.
### Environment
- DBeaver 26.2.0.202608301738 CE, macOS aarch64
- Provider: Alibaba Cloud DashScope (OpenAI-compatible mode), model qwen-plus
- Reproduced with plain curl: the SSE stream contains `data:{...}` (no space) while `OpenAiAPIStreamConsumer` requires `data: ` (with space)
Thanks!
Contributor guide
Research direction
Start in org.jkiss.dbeaver.model.ai.engine.openai.OpenAiAPIStreamConsumer and inspect how SSE lines are classified by DATA_EVENT and EVENT_EVENT. Verify handling against the WHATWG SSE field-value rule, including data:{json} without a space; done means compliant streaming events are recognized instead of silently dropped.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- ai, backend-api-design
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 78/100