fix: port 3 upstream DeepSeek transform fixes#852
Conversation
… providerID
Cherry-picks (manual port — no merge base with upstream-opencode) 3
DeepSeek-specific transform.ts fixes that landed in upstream-opencode but
never made it into altimate-code's main:
- **deepseek assistant messages always carry a reasoning part** (upstream
86715fecc4 / #24180). DeepSeek's OpenAI-compatible protocol rejects
multi-turn assistant messages that lack a `reasoning` part. Inserts
an empty one before the interleaved-field normalization.
- **always emit the interleaved reasoning field, even when empty**
(upstream 923af96d26 / #24146). DeepSeek V4 thinking mode may emit
empty reasoning_content; the field must still be present in
subsequent requests or the API rejects the message. Removes the
`if (reasoningText)` guard around the providerOptions set.
- **deepseek-v4 supports `max` reasoning effort** (upstream f8e939d96f
/ #24163). Adds "max" to the OpenAI-compatible variant cascade when
the model id contains `deepseek-v4`.
A fourth upstream fix (9d6718131e — remove deepseek-chat/deepseek-reasoner
from a variants include list) is NOT ported because our equivalent block
already excludes ALL `deepseek` model ids from variant calculation via
the broader `id.includes("deepseek")` check at transform.ts:380.
Also wraps the altimate-specific `altimate-backend`/
`google-vertex-anthropic` providerID entries in altimate_change markers
(pre-existing oversight from PR #850 that the marker guard now flags).
All marked with `upstream_fix:` so they can be dropped when upstream
finally ships the same shape via a normal merge.
Tests: 387 pass / 0 fail in test/provider/; typecheck + marker guard
clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
📝 WalkthroughWalkthroughA single file receives four enhancements: DeepSeek messages are normalized to always contain a ChangesProvider Transform Updates
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/opencode/src/provider/transform.ts`:
- Around line 305-309: The Anthropic-family provider IDs added to the model
gating (e.g., "google-vertex-anthropic" and "altimate-backend") must also be
included in the caching logic inside applyCaching() so they follow the same
Anthropic message-level handling; update the conditional in applyCaching() that
currently checks providerID === "anthropic" (and the bedrock branch) to OR in
the new IDs and ensure that when messages are an array the cache metadata is
attached via the Anthropic path instead of only on the last content part—search
for the applyCaching function and the providerID === "anthropic" check and
extend that predicate to include "google-vertex-anthropic" and
"altimate-backend".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 3ff4cfac-ed1a-4943-9792-3b6496c1145a
📒 Files selected for processing (1)
packages/opencode/src/provider/transform.ts
| (model.providerID === "anthropic" || | ||
| // altimate_change start — altimate-specific Anthropic provider IDs | ||
| model.providerID === "google-vertex-anthropic" || | ||
| model.providerID === "altimate-backend" || | ||
| // altimate_change end |
There was a problem hiding this comment.
Carry the Anthropic-family expansion into applyCaching().
This widens the outer gate, but applyCaching() still only treats providerID === "anthropic" / bedrock as message-level on Line 235. For google-vertex-anthropic and altimate-backend, array-content messages will still get cache metadata on the last content part instead of following the Anthropic path, so the new support stays partial.
Suggested fix
+ const isAnthropicLikeProvider =
+ model.providerID === "anthropic" ||
+ model.providerID === "google-vertex-anthropic" ||
+ model.providerID === "altimate-backend"
+
for (const msg of unique([...system, ...final])) {
const useMessageLevelOptions =
- model.providerID === "anthropic" ||
+ isAnthropicLikeProvider ||
model.providerID.includes("bedrock") ||
model.api.npm === "`@ai-sdk/amazon-bedrock`"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| (model.providerID === "anthropic" || | |
| // altimate_change start — altimate-specific Anthropic provider IDs | |
| model.providerID === "google-vertex-anthropic" || | |
| model.providerID === "altimate-backend" || | |
| // altimate_change end | |
| const isAnthropicLikeProvider = | |
| model.providerID === "anthropic" || | |
| model.providerID === "google-vertex-anthropic" || | |
| model.providerID === "altimate-backend" | |
| for (const msg of unique([...system, ...final])) { | |
| const useMessageLevelOptions = | |
| isAnthropicLikeProvider || | |
| model.providerID.includes("bedrock") || | |
| model.api.npm === "`@ai-sdk/amazon-bedrock`" |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/opencode/src/provider/transform.ts` around lines 305 - 309, The
Anthropic-family provider IDs added to the model gating (e.g.,
"google-vertex-anthropic" and "altimate-backend") must also be included in the
caching logic inside applyCaching() so they follow the same Anthropic
message-level handling; update the conditional in applyCaching() that currently
checks providerID === "anthropic" (and the bedrock branch) to OR in the new IDs
and ensure that when messages are an array the cache metadata is attached via
the Anthropic path instead of only on the last content part—search for the
applyCaching function and the providerID === "anthropic" check and extend that
predicate to include "google-vertex-anthropic" and "altimate-backend".
| // altimate_change start — upstream_fix: add "max" reasoning effort for deepseek-v4 | ||
| // Ported from upstream-opencode commit f8e939d96f (#24163). DeepSeek-v4 advertises a | ||
| // `max` reasoning effort that the default WIDELY_SUPPORTED_EFFORTS list omits. Drop | ||
| // this marker if upstream lifts the cap or ships the same shape. | ||
| return Object.fromEntries( | ||
| [ | ||
| ...WIDELY_SUPPORTED_EFFORTS, | ||
| ...(model.api.id.includes("deepseek-v4") ? ["max"] : []), | ||
| ].map((effort) => [effort, { reasoningEffort: effort }]), | ||
| ) | ||
| // altimate_change end |
There was a problem hiding this comment.
deepseek-v4 still never reaches this branch.
Line 402 returns {} for every model.id containing deepseek, so this new @ai-sdk/openai-compatible case is dead for the models it is meant to unlock. As written, deepseek-v4 still exposes no reasoning-effort variants.
Suggested fix
+ const isDeepSeekV4 = id.includes("deepseek-v4") || model.api.id.includes("deepseek-v4")
if (
- id.includes("deepseek") ||
+ (id.includes("deepseek") && !isDeepSeekV4) ||
id.includes("minimax") ||
id.includes("glm") ||
id.includes("mistral") ||
id.includes("kimi") ||
id.includes("k2p5") ||
id.includes("qwen")
)
return {}There was a problem hiding this comment.
1 issue found across 1 file
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/opencode/src/provider/transform.ts">
<violation number="1" location="packages/opencode/src/provider/transform.ts:535">
P1: The `model.api.id.includes("deepseek-v4")` check here is dead code for the models it's meant to support. An earlier guard in this function returns `{}` for any model whose `id` includes `"deepseek"`, which means `deepseek-v4` models never reach this `@ai-sdk/openai-compatible` case. You need to exclude `deepseek-v4` from that early return (e.g., `id.includes("deepseek") && !id.includes("deepseek-v4")`) for this branch to be reachable.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| return Object.fromEntries( | ||
| [ | ||
| ...WIDELY_SUPPORTED_EFFORTS, | ||
| ...(model.api.id.includes("deepseek-v4") ? ["max"] : []), |
There was a problem hiding this comment.
P1: The model.api.id.includes("deepseek-v4") check here is dead code for the models it's meant to support. An earlier guard in this function returns {} for any model whose id includes "deepseek", which means deepseek-v4 models never reach this @ai-sdk/openai-compatible case. You need to exclude deepseek-v4 from that early return (e.g., id.includes("deepseek") && !id.includes("deepseek-v4")) for this branch to be reachable.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/provider/transform.ts, line 535:
<comment>The `model.api.id.includes("deepseek-v4")` check here is dead code for the models it's meant to support. An earlier guard in this function returns `{}` for any model whose `id` includes `"deepseek"`, which means `deepseek-v4` models never reach this `@ai-sdk/openai-compatible` case. You need to exclude `deepseek-v4` from that early return (e.g., `id.includes("deepseek") && !id.includes("deepseek-v4")`) for this branch to be reachable.</comment>
<file context>
@@ -503,7 +525,17 @@ export namespace ProviderTransform {
+ return Object.fromEntries(
+ [
+ ...WIDELY_SUPPORTED_EFFORTS,
+ ...(model.api.id.includes("deepseek-v4") ? ["max"] : []),
+ ].map((effort) => [effort, { reasoningEffort: effort }]),
+ )
</file context>
dev-punia-altimate
left a comment
There was a problem hiding this comment.
Multi-Persona Review — Verdict: skipped
Multi-persona review completed.
0/0 agents completed · 2s · 0 findings (0 critical, 0 high, 0 medium)
Multi-Persona Review · vllm:qwen3-next-80b (waves) + vllm-fallback (synth) ·
What does this PR do?
Cherry-picks (manual port — no merge base with upstream-opencode) 3 DeepSeek-specific
transform.tsfixes that landed in upstream-opencode but never made it into altimate-code'smain:DeepSeek assistant messages always carry a reasoning part — upstream
86715fecc4/#24180. DeepSeek's OpenAI-compatible protocol rejects multi-turn assistant messages that lack areasoningpart; the fix inserts an empty one before the interleaved-field normalization.Always emit the interleaved reasoning field, even when empty — upstream
923af96d26/#24146. DeepSeek V4 thinking mode may emit emptyreasoning_content; the field must still be present in subsequent requests or the API rejects the message. Removes theif (reasoningText)guard around theproviderOptionsset so the field is always written.DeepSeek-v4 supports
maxreasoning effort — upstreamf8e939d96f/#24163. Adds"max"to the@ai-sdk/openai-compatiblevariant cascade when the model id containsdeepseek-v4.A fourth upstream fix (
9d6718131e— removedeepseek-chat/deepseek-reasonerfrom a variants include list) is not ported because our equivalent block already excludes alldeepseekmodel ids from variant calculation via the broaderid.includes("deepseek")check attransform.ts:380.Also wraps the altimate-specific
altimate-backend/google-vertex-anthropicproviderID entries inaltimate_changemarkers (pre-existing oversight from PR #850 that the marker guard now flags).All three ported changes are marked with the
upstream_fix:tag so they can be dropped when upstream finally ships the same shape via a normal merge.Type of change
fix:(provider compat with DeepSeek V4 / Reasoner endpoints).Issue for this PR
No tracking issue — these are upstream fixes that fell off the bridge during our diverged history. Investigated as a follow-up to #849 review.
How did you verify your code works?
bun test test/provider/— 387 pass / 0 fail, exercises the variant cascade andnormalizeMessagesbun run typecheck— cleanbun run script/upstream/analyze.ts --markers --base main --strict— clean (new code wrapped inaltimate_changeblocks)~/ade-bench-openrouter/experiments/duckdb_deepseek_v4pro_run1__none/to confirm the model id pattern matches (alibaba-cn/deepseek-v4-proandazure-deepseek/deepseek-v4-flashboth containdeepseek-v4).Checklist
upstream_fix:so they auto-surface during the next upstream merge auditSummary by cubic
Ports three upstream DeepSeek transform fixes to restore API compatibility and prevent 400 errors in multi-turn chats. Ensures reasoning fields are always present and enables
maxreasoning effort fordeepseek-v4.Bug Fixes
reasoningpart to DeepSeek assistant messages."max"to reasoning effort variants fordeepseek-v4under@ai-sdk/openai-compatible.Refactors
altimate-backendandgoogle-vertex-anthropicprovider IDs inaltimate_changeguards.Written for commit dc995c5. Summary will update on new commits.
Summary by CodeRabbit