Responses API SSE stream starts with a synthetic `chatcmpl-dummy` frame causing `TypeValidationError`
已打开 09:34AM - 05 Jun 26 UTC
### Description
## Summary
When using the OpenAI Responses API (`sdk.responses…()`) with models served through certain OpenAI-compatible gateways, the SSE stream begins with a synthetic `chatcmpl-dummy` frame in Chat Completions format before the real Responses API events. This frame is not a valid Responses API event and causes a `TypeValidationError` that terminates the agent turn.
## Environment
- OpenCode version: 1.16.0 (dev), 1.15.13 (release)
- Provider: OpenAI (`@ai-sdk/openai`)
- Model: `gpt-5.5` (via OpenAI-compatible gateway)
- Runtime: `ai-sdk`
## Reproducing
### 1. Verification command
Set up environment variables for your OpenAI-compatible provider:
```bash
export OPENAI_BASE_URL="<your-endpoint>/v1"
export OPENAI_API_KEY="<your-api-key>"
```
Run opencode with the Responses API (default) and Chat Completions API:
```bash
# Test with gpt-5.5 (Responses API — fails)
opencode run "say hi" --format json --model openai/gpt-5.5 --dangerously-skip-permissions
```
### 2. gpt-5.5 fails with TypeValidationError
The stream errors immediately:
```json
{
"type": "error",
"error": {
"name": "UnknownError",
"data": {
"message": "Type validation failed: Value: {\"id\":\"chatcmpl-dummy\",\"object\":\"chat.completion.chunk\",\"created\":1780651633,\"model\":\"gpt-5.5\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\"}}]}.\nError message: [\n {\n \"code\": \"invalid_union\",\n \"errors\": [\n [\n {\n \"code\": \"invalid_value\",\n \"values\": [\"response.output_text.delta\"],\n \"path\": [\"type\"],\n \"message\": \"Invalid input: expected \\\"response.output_text.delta\\\"\"\n },\n ...\n ],\n ...\n ],\n \"message\": \"Invalid input\"\n }\n]"
}
}
}
```
The error shows `chatcmpl-dummy` — a Chat Completions format frame with `"content":""` — being passed to the Responses API event validator, which rejects it because it does not match any expected event type (`response.output_text.delta`, `response.created`, etc.).
### 3. deepseek-v4-pro works fine
The same setup works correctly with DeepSeek's native API (which does not go through the same gateway):
```bash
opencode run "say hi" --format json --model deepseek/deepseek-v4-pro --dangerously-skip-permissions
```
Expected output:
```json
{"type":"step_start","part":{"type":"step-start"}}
{"type":"text","part":{"type":"text","text":"hi"}}
{"type":"step_finish","part":{"type":"step-finish"}}
```
### 4. Root cause confirmed via curl
Probing the Responses API endpoint directly reveals the synthetic frame:
```bash
curl -N -s "${OPENAI_BASE_URL}/responses" \
-H "Authorization: Bearer ${OPENAI_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-5.5","input":"hello","stream":true}'
```
The very first SSE event is:
```
data: {"id":"chatcmpl-dummy","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":""}}]}
```
Note the `content: ""` — this frame carries no actual content. It is a bare `data:` frame (no `event:` prefix) with `object: "chat.completion.chunk"` — it is **not** a valid Responses API event. The subsequent frames are proper Responses API events (`event: response.created`, `event: response.output_text.delta`, etc.).
The same test against DeepSeek's API shows no such frame:
```bash
curl -N -s "${DEEPSEEK_BASE_URL}/responses" \
-H "Authorization: Bearer ${DEEPSEEK_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-v4-pro","input":"hello","stream":true}'
```
Output starts directly with a proper Responses API event — no `chatcmpl-dummy` prefix.
### 5. Suggested fix
The `chatcmpl-dummy` frame has a hardcoded ID (`"chatcmpl-dummy"`). The fix is to filter it out at the HTTP response level in the AI SDK's custom fetch wrapper, before the stream reaches the event validator.
**Location:** `packages/opencode/src/provider/provider.ts` — in the custom `fetch` wrapper inside `resolveSDK()`.
**Approach:** When the response is an SSE stream from a `/responses` endpoint, pipe the body through a `TransformStream` that parses each SSE event's `data:` line as JSON and drops any event whose `id` equals `"chatcmpl-dummy"`.
This approach:
- Precisely targets only the synthetic `chatcmpl-dummy` frame
- Does not affect normal Chat Completions or Responses API streams
- Requires no user configuration changes
- Is transparent to all other providers
### Plugins
_No response_
### OpenCode version
_No response_
### Steps to reproduce
_No response_
### Screenshot and/or share link
_No response_
### Operating System
_No response_
### Terminal
_No response_