Skip to content

fix(openai): preserve reasoning for local OpenAI-compatible models via R1 toggle - #1119

Open
ch405canova-sudo wants to merge 3 commits into
Zoo-Code-Org:mainfrom
ch405canova-sudo:fix/preserve-reasoning-openai-compatible
Open

fix(openai): preserve reasoning for local OpenAI-compatible models via R1 toggle#1119
ch405canova-sudo wants to merge 3 commits into
Zoo-Code-Org:mainfrom
ch405canova-sudo:fix/preserve-reasoning-openai-compatible

Conversation

@ch405canova-sudo

@ch405canova-sudo ch405canova-sudo commented Aug 4, 2026

Copy link
Copy Markdown

Summary

Local OpenAI-compatible reasoning models (llama.cpp llama-server, LM Studio, Ollama's OpenAI endpoint, vLLM, etc.) stream a reasoning_content field, but Zoo Code strips it from the follow-up context for these providers — so the model can never see its own reasoning chain on the next turn.

Root cause

openAiR1FormatEnabled (exposed as a UI checkbox via R1FormatSetting and declared in provider-settings.ts) only forces the R1 request format in openai.ts. It never propagates to info.preserveReasoning, which is what gates reasoning retention in Task.ts:

// src/core/task/Task.ts
const shouldPreserveForApi = this.api.getModel().info.preserveReasoning === true

OpenAiHandler.getModel() builds its ModelInfo from openAiCustomModelInfo ?? openAiModelInfoSaneDefaults — neither sets preserveReasoning. For the built-in OpenAI-compatible provider the value is therefore always undefined, and reasoning is stripped from messages sent back to the API.

Fix

When the user enables the R1 format toggle, getModel() now sets preserveReasoning: true on the returned model info:

return {
    id,
    info: this.options.openAiR1FormatEnabled ? { ...info, preserveReasoning: true } : info,
    ...params,
}

This preserves reasoning_content in the assistant history sent to the model on follow-up turns. Default behaviour (toggle off) is unchanged.

Tests

Added two getModel cases to src/api/providers/__tests__/openai.spec.ts:

  • preserveReasoning is true when openAiR1FormatEnabled is on
  • preserveReasoning stays undefined by default

Related

Fixes #1118

…a R1 toggle

The openAiR1FormatEnabled toggle (UI + provider-settings) only forced the
R1 request format, but getModel() never set info.preserveReasoning. As a
result Task.ts (shouldPreserveForApi = info.preserveReasoning === true)
stripped reasoning_content from follow-up context for every local
OpenAI-compatible reasoning model (llama.cpp, LM Studio, Ollama) — there
was no way to feed the chain back.

Enable the toggle and getModel() now sets preserveReasoning: true so the
reasoning chain is preserved in the next-turn context. Default behaviour
unchanged.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 47647a28-5868-4e9a-a101-5d375d0e186a

📥 Commits

Reviewing files that changed from the base of the PR and between f4a69cc and 60b1b45.

📒 Files selected for processing (2)
  • src/api/providers/__tests__/openai.spec.ts
  • src/api/providers/openai.ts

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.

📜 Recent review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: e2e-mock
  • GitHub Check: compile
  • GitHub Check: platform-unit-test (ubuntu-latest)
  • GitHub Check: platform-unit-test (windows-latest)
🧰 Additional context used
📓 Path-based instructions (5)
Treat model, provider, MCP, path, command, and tool data as untrusted.

⚙️ CodeRabbit configuration file

Files:

  • src/api/providers/openai.ts
  • src/api/providers/__tests__/openai.spec.ts
Require regression coverage at the lowest valid harness with behavior-focused assertions, including relevant negative, error, false/unset, and boundary cases.

⚙️ CodeRabbit configuration file

Files:

  • src/api/providers/__tests__/openai.spec.ts
Check strict typing and exhaustive behavior across normal, boundary, error, cancellation, retry, and compatibility paths.

⚙️ CodeRabbit configuration file

Files:

  • src/api/providers/openai.ts
  • src/api/providers/__tests__/openai.spec.ts
Verify extension/webview contracts, cancellation and error propagation, VS Code lifecycle correctness, and behavior under retries and partial failure.

⚙️ CodeRabbit configuration file

Files:

  • src/api/providers/openai.ts
  • src/api/providers/__tests__/openai.spec.ts
Act as an adversarial second-opinion reviewer.

⚙️ CodeRabbit configuration file

Files:

  • src/api/providers/openai.ts
  • src/api/providers/__tests__/openai.spec.ts
🔇 Additional comments (1)
src/api/providers/openai.ts (1)

309-309: Preserve reasoning for model-ID R1 detection.

createMessage() enables R1 conversion when modelId.includes("deepseek-reasoner"), but line 309 enables preserveReasoning only for openAiR1FormatEnabled. A DeepSeek Reasoner model with the toggle disabled still has its streamed reasoning removed before a follow-up request. Use the same effective R1 condition in getModel(), and add coverage for the model-ID path.


📝 Summary

Summary by CodeRabbit

  • New Features

    • OpenAI models can now preserve reasoning information when the R1 format option is enabled, allowing supported responses to retain additional reasoning details.
  • Bug Fixes

    • Model configuration remains unchanged when the option is disabled, maintaining the existing default behavior and avoiding unintended changes to other model settings.

Walkthrough

getModel now returns copied model information with preserveReasoning: true when openAiR1FormatEnabled is enabled. Tests cover enabled and default behavior.

Changes

OpenAI reasoning configuration

Layer / File(s) Summary
Conditional model configuration
src/api/providers/openai.ts, src/api/providers/__tests__/openai.spec.ts
getModel copies model information and adds preserveReasoning: true when the R1 format setting is enabled. Tests verify enabled and default behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 60b1b

The R1 toggle now preserves streamed reasoning for follow-up requests while default behavior remains unchanged. The focused implementation and tests are ready to merge.

🚥 Pre-merge checks | ✅ 8
✅ Passed checks (8 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address issue #1118 by connecting the existing R1-format toggle to preserve reasoning for local OpenAI-compatible models. Tests verify enabled and default behavior.
Out of Scope Changes check ✅ Passed The implementation and tests are limited to reasoning preservation in the OpenAI-compatible provider and directly support issue #1118.
Regression Evidence ✅ Passed PASS. The PR changes only OpenAiHandler.getModel() and its unit tests. The new test enables openAiR1FormatEnabled and asserts info.preserveReasoning is true. A second test covers the default u…
Security Boundaries ✅ Passed No changed path matches the security failure conditions. The only production change is OpenAiHandler.getModel() setting info.preserveReasoning after the typed boolean openAiR1FormatEnabled is en…
Persistence Integrity ✅ Passed No changed persistence path exists. The PR only changes OpenAiHandler.getModel() to return an in-memory ModelInfo copy with preserveReasoning: true when the existing openAiR1FormatEnabled sett…
Lifecycle Resource Cleanup ✅ Passed PASS. The only production change is in OpenAiHandler.getModel(). It synchronously derives model metadata and conditionally creates a plain object with preserveReasoning: true. It does not add list…
Title check ✅ Passed The title clearly identifies the OpenAI provider fix and the R1 toggle behavior. It is concise and directly related to the main change.
Description check ✅ Passed The description explains the issue, root cause, implementation, linked issue, and added tests. It is mostly complete, although it does not reproduce the template checklist or provide detailed test exe…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ch405canova-sudo

Copy link
Copy Markdown
Author

This work is a joint effort by chaos (@ch405canova-sudo) and opencode — the bug was found and the fix developed together on a local llama.cpp stack (August 2026).

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@ch405canova-sudo

Copy link
Copy Markdown
Author

This PR addresses the same root cause as #1096 (local OpenAI-compatible reasoning is discarded because preserveReasoning is never enabled for the built-in OpenAI-compatible provider).

Note on approach: #1096 proposes a separate preserveReasoning checkbox, while this PR couples it to the existing R1-format toggle. Both solve the underlying issue — happy to align with whichever approach the maintainers prefer.

@edelauna edelauna left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution, but could we align with #1096 and include this a a checkbox in the ui, I don't feel great about reusing an unrelated flag.

// toggle, treat the model as preserving reasoning so the chain is fed back.
return {
id,
info: this.options.openAiR1FormatEnabled ? { ...info, preserveReasoning: true } : info,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createMessage() treats a model as R1-format via modelId.includes("deepseek-reasoner") || enabledR1Format (line 90), but this gate only checks the toggle. If someone points openAiModelId at a custom endpoint whose id contains deepseek-reasoner without flipping the toggle, wouldn't createMessage still use R1 conversion while preserveReasoning never gets set here — leaving the original bug open for that case?

// toggle, treat the model as preserving reasoning so the chain is fed back.
return {
id,
info: this.options.openAiR1FormatEnabled ? { ...info, preserveReasoning: true } : info,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This forces preserveReasoning: true whenever the toggle is on, even if openAiCustomModelInfo.preserveReasoning was explicitly set to false. Should an explicit value win here, e.g. info.preserveReasoning ?? true?

Comment thread src/api/providers/__tests__/openai.spec.ts Outdated
@github-actions github-actions Bot added awaiting-author PR is waiting for the author to address requested changes and removed awaiting-review PR changes are ready and waiting for maintainer re-review labels Aug 6, 2026
Co-authored-by: edelauna <54631123+edelauna@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

This PR has been awaiting author changes for 14 days and will be automatically closed in 7 days. Please address the review comments or leave a comment if you need more time.

@github-actions github-actions Bot added the stale-awaiting-author PR is stale while waiting for requested author changes label Aug 24, 2026
@github-actions

github-actions Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Review status

Thanks for contributing. This comment tracks the review sequence and the next action.

Current step: Awaiting fresh human maintainer or CODEOWNER approval.

Automated review is complete for the latest commit but does not replace human approval.

Review-state labels are managed by this workflow; do not edit them manually.

@github-actions github-actions Bot added coderabbit-review-active Required CI passed; CodeRabbit review is active awaiting-coderabbit Waiting for CodeRabbit to approve the latest commit awaiting-author PR is waiting for the author to address requested changes and removed awaiting-author PR is waiting for the author to address requested changes stale-awaiting-author PR is stale while waiting for requested author changes labels Aug 29, 2026
@github-actions github-actions Bot removed coderabbit-review-active Required CI passed; CodeRabbit review is active awaiting-coderabbit Waiting for CodeRabbit to approve the latest commit labels Sep 10, 2026
@github-actions github-actions Bot added the awaiting-maintainer CodeRabbit approved; waiting for a human maintainer label Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-maintainer CodeRabbit approved; waiting for a human maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Reasoning from local OpenAI-compatible models (llama.cpp/Ollama/LM Studio) is discarded — no official toggle

2 participants