Skip to content

fix: OpenAI-compatible provider improvements (system messages, image support, stream interruption)#23501

Open
jwcrystal wants to merge 3 commits intoanomalyco:devfrom
jwcrystal:fix/openai-provider-combined
Open

fix: OpenAI-compatible provider improvements (system messages, image support, stream interruption)#23501
jwcrystal wants to merge 3 commits intoanomalyco:devfrom
jwcrystal:fix/openai-provider-combined

Conversation

@jwcrystal
Copy link
Copy Markdown

@jwcrystal jwcrystal commented Apr 20, 2026

Issue for this PR

Closes #20802
Closes #5034
Closes #20466

Supersedes #16981, #21627, #21727

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Three fixes for OpenAI-compatible providers (Ollama, local models, custom endpoints).

1. Image support for custom providers (closes #20802)

Custom OpenAI-compatible providers could not process image attachments. capabilities.input.image defaulted to false for providers without explicit modalities declaration, so transform.ts filtered out image parts before they reached the existing conversion logic in convert-to-openai-compatible-chat-messages.ts.

Fix: one-line change — for @ai-sdk/openai-compatible providers, default image to true instead of false. The existing conversion logic already handles the rest.

2. System message handling for non-Anthropic providers (closes #5034)

Local models (Qwen, Llama, Ollama, DeepSeek via oMLX) reject multiple system messages with "Chat template error: System message must be at the beginning". OpenCode sends multiple system messages (agent prompts, plugins) which breaks these providers.

Fix: added systemMessage capability ('single' | 'multiple') to the provider schema. Anthropic keeps multiple system messages (existing behavior). All other providers merge system messages into a single message joined by \n. Also added reasoning_content field support alongside existing reasoning_text for DeepSeek/oMLX reasoning output.

3. Stream interruption handling (closes #20466)

OpenAI-compatible providers can interrupt SSE streams mid-response. Previously the truncated output was silently accepted as a complete response — no retry, no error.

Fix:

  • retry.ts: Added SSE timeout, connection reset, abort, and "stream ended unexpectedly" patterns to retryable() matching
  • message-v2.ts: Classified SSE read timed out as APIError(isRetryable: true) so retry mechanism picks it up
  • openai-compatible-chat-language-model.ts: When flush() is called without a finish_reason while output is still active (text/reasoning/tool-call), emit an error event instead of silently accepting truncation

How did you verify your code works?

  • Verified image attachments work with custom OpenAI-compatible providers (previously filtered out)
  • Verified local models (Qwen/Llama) no longer throw "Chat template error" with multiple system messages
  • Verified Anthropic still works correctly with multiple system messages (unchanged behavior)
  • Verified reasoning_content is correctly parsed from DeepSeek/oMLX responses
  • Verified stream interruption patterns match in retryable() — tested against SSE timeout, connection reset, and unexpected stream end scenarios
  • All changes based on latest dev branch with typecheck passing (no new errors introduced)

Screenshots / recordings

N/A — backend/provider fixes, no UI changes.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

…iders

Custom OpenAI-compatible providers (Ollama, longent, etc.) were unable to
process image attachments because capabilities.input.image defaulted to false.

Now defaults to true for @ai-sdk/openai-compatible providers.

Closes anomalyco#20802
- Add systemMessage capability schema ('single' | 'multiple')
- Add getSystemMessageMode helper with provider-aware defaults
- Anthropic keeps multiple system messages; all others merge into one
- Add reasoning_content support for DeepSeek/oMLX models
- Merge system messages in convert layer for OpenAI-compatible APIs
- Handle lone system message edge case (convert to user message)

Fixes 'Chat template error' for Qwen, Llama, Ollama, etc.

Closes anomalyco#5034
- Add SSE timeout, connection reset, abort, and stream truncation
  patterns to retryable() matching for automatic retry
- Classify 'SSE read timed out' as APIError(isRetryable: true)
- Emit error event in flush() when stream ends without finish_reason
  while output is still active, instead of silently accepting truncation

Closes anomalyco#20466
@github-actions github-actions bot added the needs:compliance This means the issue will auto-close after 2 hours. label Apr 20, 2026
@github-actions
Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Based on my search, I found the following related PRs that are superseded by PR #23501:

  1. PR fix(provider): enable image support for custom OpenAI-compatible providers #21627 - fix(provider): enable image support for custom OpenAI-compatible providers #21627

    • Title: "fix(provider): enable image support for custom OpenAI-compatible providers"
    • Related to feature feat: compact and other improvements #1 of the current PR (image support for custom providers)
  2. PR fix: handle system messages for non-Anthropic providers #16981 - fix: handle system messages for non-Anthropic providers #16981

    • Title: "fix: handle system messages for non-Anthropic providers"
    • Related to feature Roadmap & Existing Issues #2 of the current PR (system message handling)
  3. PR fix: handle stream interruption for OpenAI-compatible providers #21727 - fix: handle stream interruption for OpenAI-compatible providers #21727

    • Title: "fix: handle stream interruption for OpenAI-compatible providers"
    • Related to feature feat: model config persistence #3 of the current PR (stream interruption handling)

These are not duplicates in the traditional sense—rather, PR #23501 is a combined fix that supersedes these three separate PRs, as explicitly stated in the PR description ("Supersedes: #16981, #21627, #21727"). This is the intended design where the current PR consolidates three related fixes into a single comprehensive change.

@github-actions github-actions bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Apr 20, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

@jwcrystal jwcrystal force-pushed the fix/openai-provider-combined branch from 310c9be to 18b7db5 Compare April 20, 2026 07:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant