[PR #5531] Feature/OpenAI compatible reasoning #11457

Closed
opened 2026-02-16 18:16:17 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/5531

State: closed
Merged: No


Add support for reasoning models with reasoning_content field - Kimi K2, Qwen3, DeepSeekV3.2 hosted wth OpenAI compatible APIs

Overview

This PR adds support for OpenAI-compatible reasoning models that return their thinking process via the reasoning_content field, including DeepSeek, Qwen Thinking, and Kimi K2 Thinking models. Please note, I need this feature for myself. This is primarily why I wrote it, but I'm pushing it upstream in hope it will be useful.

Problem

Several popular reasoning models (DeepSeek-V3, DeepSeek-R1, Qwen3-235B-Thinking, Kimi-K2-Thinking) are served with OpenAI-compatible APIs by some providers but return their reasoning/thinking content in a separate reasoning_content field rather than using OpenAI's native reasoning API format.

Currently, OpenCode either:

  • Lost this reasoning content entirely
  • Mixed it into the regular response text
  • Failed to display it in the proper collapsible UI format

Impact

Especially when the thinking is not displayed it breaks the flow of work by making user sit there and wait a long time for the final response. I found this inconvenient enough as to be unusable with my favourite models.

Solution

This PR introduces a custom AI SDK wrapper (@ai-sdk/openai-compatible-reasoning) that:

  1. Intercepts streaming chunks from OpenAI-compatible APIs
  2. Detects delta.reasoning_content fields in the response
  3. Transforms them into proper reasoning events (reasoning-start, reasoning-delta, reasoning-end)
  4. Enables OpenCode to display reasoning in collapsible UI blocks, just like Claude Extended Thinking or OpenAI o1 models
Screenshot from 2025-12-14 18-26-46

Implementation Details

New Provider Type

Created a new bundled provider @ai-sdk/openai-compatible-reasoning that extends the standard OpenAI-compatible provider with reasoning detection capabilities.

File: packages/opencode/src/provider/sdk/openai-compatible/src/openai-compatible-chat-reasoning-model.ts

The chat model wrapper:

  • Extends OpenAICompatibleChatLanguageModel from @ai-sdk/openai-compatible
  • Adds a TransformStream to intercept raw chunks
  • Parses choices[0].delta.reasoning_content from streaming responses
  • Emits synthetic reasoning events that OpenCode's processor already handles
  • Maintains state to properly emit start/delta/end events
  • Fully delegates request handling to base model (preserves multimodal support)

File: packages/opencode/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts

The provider factory:

  • Chat models: Uses custom OpenAICompatibleChatWithReasoningLanguageModel wrapper
  • Embeddings: Uses official OpenAICompatibleEmbeddingModel directly
  • Image generation: Uses official OpenAICompatibleImageModel directly
  • Maintains full feature parity with the standard provider

Provider Options Support

Added support for reasoning.enabled provider option to enable reasoning output for models that require explicit request parameters (like DeepSeek).

File: packages/opencode/src/provider/transform.ts

When configured, the option is passed through to the API request:

{
  "model": "deepseek-ai/DeepSeek-V3.2",
  "messages": [...],
  "reasoning": {
    "enabled": true
  }
}

Architecture

Raw API Response (SSE chunks)
  ↓
OpenAICompatibleChatWithReasoningLanguageModel.doStream()
  ↓
TransformStream intercepts "raw" chunks
  ↓
Parses delta.reasoning_content field
  ↓
Emits: reasoning-start → reasoning-delta(s) → reasoning-end
  ↓
SessionProcessor receives events (existing code)
  ↓
Creates MessageV2.ReasoningPart (existing code)
  ↓
UI displays in collapsible thinking blocks (existing code)

Configuration

Users can now configure reasoning models using the new provider:

Example: DeepSeek V3 via DeepInfra

{
  "provider": {
    "deepinfra-thinking": {
      "npm": "@ai-sdk/openai-compatible-reasoning",
      "options": {
        "baseURL": "https://api.deepinfra.com/v1/openai",
        "reasoning": {
          "enabled": true
        }
      },
      "models": {
        "deepseek-ai/DeepSeek-V3.2": {
          "name": "DeepSeek V3.2"
        },
        "Qwen/Qwen3-235B-A22B-Thinking-2507": {
          "name": "Qwen3 235B Thinking"
        }
      }
    }
  }
}

Example: Direct DeepSeek API

{
  "provider": {
    "deepseek": {
      "npm": "@ai-sdk/openai-compatible-reasoning",
      "options": {
        "baseURL": "https://api.deepseek.com/v1",
        "reasoning": {
          "enabled": true
        }
      },
      "models": {
        "deepseek-chat": {
          "name": "DeepSeek Chat"
        },
        "deepseek-reasoner": {
          "name": "DeepSeek Reasoner"
        }
      }
    }
  }
}

Supported Models

This implementation works with any OpenAI-compatible model that returns reasoning_content in the response, including:

  • DeepSeek: DeepSeek-V3, DeepSeek-R1, deepseek-chat, deepseek-reasoner
  • Qwen: Qwen3-235B-A22B-Thinking-2507 and other Qwen thinking models
  • Kimi: Kimi-K2-Thinking (moonshotai/Kimi-K2-Thinking)

Feature Completeness

The custom provider maintains full feature parity with the official @ai-sdk/openai-compatible provider:

  • Chat models: Custom wrapper with reasoning support + full multimodal input (images, files)
  • Embedding models: Delegated to official OpenAICompatibleEmbeddingModel
  • Image generation: Delegated to official OpenAICompatibleImageModel
  • Multimodal chat: Images can be dragged into terminal and sent in messages (unchanged behavior)

How It Works

The reasoning wrapper is a thin response-only layer:

Requests (unchanged):

  • Multimodal messages (text + images) pass through directly to the base model
  • The base model handles image encoding, URL resolution, and API formatting
  • All request functionality works identically to the official provider

Responses (enhanced):

  • Intercepts streaming chunks to detect reasoning_content fields
  • Transforms reasoning into proper UI events
  • Passes through all other content unchanged

This means:

  • Users can send images in chat messages (drag & drop in terminal)
  • Models can use text embeddings via textEmbeddingModel()
  • Models can generate images via imageModel()
  • Reasoning is properly displayed in collapsible UI blocks

Testing

Tested with:

  • DeepSeek-V3 via DeepInfra (requires reasoning.enabled: true)
  • Qwen3-235B-A22B-Thinking-2507 via DeepInfra (works without explicit enablement)

Notes

  • The reasoning.enabled option is only required for some models (DeepSeek). Other models (Qwen) return reasoning by default.
  • The implementation uses OpenCode's existing reasoning display logic.
  • Full feature parity: The provider supports all capabilities of the official OpenAI-compatible provider (chat, embeddings, image generation, multimodal input).
  • Non-breaking: Only the response stream is intercepted; all request handling is identical to the official provider.
  • Documentation is updated as well.
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/5531 **State:** closed **Merged:** No --- # Add support for reasoning models with `reasoning_content` field - Kimi K2, Qwen3, DeepSeekV3.2 hosted wth OpenAI compatible APIs ## Overview This PR adds support for OpenAI-compatible reasoning models that return their thinking process via the `reasoning_content` field, including DeepSeek, Qwen Thinking, and Kimi K2 Thinking models. Please note, I need this feature for myself. This is primarily why I wrote it, but I'm pushing it upstream in hope it will be useful. ## Problem Several popular reasoning models (DeepSeek-V3, DeepSeek-R1, Qwen3-235B-Thinking, Kimi-K2-Thinking) are served with OpenAI-compatible APIs by some providers but return their reasoning/thinking content in a separate `reasoning_content` field rather than using OpenAI's native reasoning API format. Currently, OpenCode either: - Lost this reasoning content entirely - Mixed it into the regular response text - Failed to display it in the proper collapsible UI format ## Impact Especially when the thinking is not displayed it breaks the flow of work by making user sit there and wait a long time for the final response. I found this inconvenient enough as to be unusable with my favourite models. ## Solution This PR introduces a custom AI SDK wrapper (`@ai-sdk/openai-compatible-reasoning`) that: 1. Intercepts streaming chunks from OpenAI-compatible APIs 2. Detects `delta.reasoning_content` fields in the response 3. Transforms them into proper reasoning events (`reasoning-start`, `reasoning-delta`, `reasoning-end`) 4. Enables OpenCode to display reasoning in collapsible UI blocks, just like Claude Extended Thinking or OpenAI o1 models <img width="3783" height="2004" alt="Screenshot from 2025-12-14 18-26-46" src="https://github.com/user-attachments/assets/bad7b4d2-de94-4c85-81ab-eed97b21037a" /> ## Implementation Details ### New Provider Type Created a new bundled provider `@ai-sdk/openai-compatible-reasoning` that extends the standard OpenAI-compatible provider with reasoning detection capabilities. **File:** `packages/opencode/src/provider/sdk/openai-compatible/src/openai-compatible-chat-reasoning-model.ts` The chat model wrapper: - Extends `OpenAICompatibleChatLanguageModel` from `@ai-sdk/openai-compatible` - Adds a TransformStream to intercept raw chunks - Parses `choices[0].delta.reasoning_content` from streaming responses - Emits synthetic reasoning events that OpenCode's processor already handles - Maintains state to properly emit start/delta/end events - **Fully delegates request handling** to base model (preserves multimodal support) **File:** `packages/opencode/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts` The provider factory: - **Chat models**: Uses custom `OpenAICompatibleChatWithReasoningLanguageModel` wrapper - **Embeddings**: Uses official `OpenAICompatibleEmbeddingModel` directly - **Image generation**: Uses official `OpenAICompatibleImageModel` directly - Maintains full feature parity with the standard provider ### Provider Options Support Added support for `reasoning.enabled` provider option to enable reasoning output for models that require explicit request parameters (like DeepSeek). **File:** `packages/opencode/src/provider/transform.ts` When configured, the option is passed through to the API request: ```json { "model": "deepseek-ai/DeepSeek-V3.2", "messages": [...], "reasoning": { "enabled": true } } ``` ### Architecture ``` Raw API Response (SSE chunks) ↓ OpenAICompatibleChatWithReasoningLanguageModel.doStream() ↓ TransformStream intercepts "raw" chunks ↓ Parses delta.reasoning_content field ↓ Emits: reasoning-start → reasoning-delta(s) → reasoning-end ↓ SessionProcessor receives events (existing code) ↓ Creates MessageV2.ReasoningPart (existing code) ↓ UI displays in collapsible thinking blocks (existing code) ``` ## Configuration Users can now configure reasoning models using the new provider: ### Example: DeepSeek V3 via DeepInfra ```json { "provider": { "deepinfra-thinking": { "npm": "@ai-sdk/openai-compatible-reasoning", "options": { "baseURL": "https://api.deepinfra.com/v1/openai", "reasoning": { "enabled": true } }, "models": { "deepseek-ai/DeepSeek-V3.2": { "name": "DeepSeek V3.2" }, "Qwen/Qwen3-235B-A22B-Thinking-2507": { "name": "Qwen3 235B Thinking" } } } } } ``` ### Example: Direct DeepSeek API ```json { "provider": { "deepseek": { "npm": "@ai-sdk/openai-compatible-reasoning", "options": { "baseURL": "https://api.deepseek.com/v1", "reasoning": { "enabled": true } }, "models": { "deepseek-chat": { "name": "DeepSeek Chat" }, "deepseek-reasoner": { "name": "DeepSeek Reasoner" } } } } } ``` ## Supported Models This implementation works with any OpenAI-compatible model that returns `reasoning_content` in the response, including: - **DeepSeek**: DeepSeek-V3, DeepSeek-R1, deepseek-chat, deepseek-reasoner - **Qwen**: Qwen3-235B-A22B-Thinking-2507 and other Qwen thinking models - **Kimi**: Kimi-K2-Thinking (moonshotai/Kimi-K2-Thinking) ## Feature Completeness The custom provider maintains **full feature parity** with the official `@ai-sdk/openai-compatible` provider: - **Chat models**: Custom wrapper with reasoning support + full multimodal input (images, files) - **Embedding models**: Delegated to official `OpenAICompatibleEmbeddingModel` - **Image generation**: Delegated to official `OpenAICompatibleImageModel` - **Multimodal chat**: Images can be dragged into terminal and sent in messages (unchanged behavior) ### How It Works The reasoning wrapper is a **thin response-only layer**: **Requests (unchanged):** - Multimodal messages (text + images) pass through directly to the base model - The base model handles image encoding, URL resolution, and API formatting - All request functionality works identically to the official provider **Responses (enhanced):** - Intercepts streaming chunks to detect `reasoning_content` fields - Transforms reasoning into proper UI events - Passes through all other content unchanged This means: - Users can send images in chat messages (drag & drop in terminal) - Models can use text embeddings via `textEmbeddingModel()` - Models can generate images via `imageModel()` - Reasoning is properly displayed in collapsible UI blocks ## Testing Tested with: - DeepSeek-V3 via DeepInfra (requires `reasoning.enabled: true`) - Qwen3-235B-A22B-Thinking-2507 via DeepInfra (works without explicit enablement) ## Notes - The `reasoning.enabled` option is only required for some models (DeepSeek). Other models (Qwen) return reasoning by default. - The implementation uses OpenCode's existing reasoning display logic. - **Full feature parity**: The provider supports all capabilities of the official OpenAI-compatible provider (chat, embeddings, image generation, multimodal input). - **Non-breaking**: Only the response stream is intercepted; all request handling is identical to the official provider. - Documentation is updated as well.
yindo added the pull-request label 2026-02-16 18:16:17 -05:00
yindo closed this issue 2026-02-16 18:16:17 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#11457