Skip to content

fix(opencode): route SAP AI Core reasoning variants through modelParams#30482

Merged
rekram1-node merged 16 commits into
anomalyco:devfrom
jerome-benoit:fix/sap-ai-core-reasoning-wire-format
Jun 3, 2026
Merged

fix(opencode): route SAP AI Core reasoning variants through modelParams#30482
rekram1-node merged 16 commits into
anomalyco:devfrom
jerome-benoit:fix/sap-ai-core-reasoning-wire-format

Conversation

@jerome-benoit

@jerome-benoit jerome-benoit commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #30481

Type of change

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

What does this PR do?

The SAP provider's Zod schema strips unknown top-level keys, so opencode's reasoningEffort / thinking / thinkingConfig variants for SAP-hosted models never reached the wire. Only modelParams (Zod catchall) is forwarded verbatim by the SAP SDKs.

Routes reasoning variants through modelParams using upstream-native REST field names where SAP supports granular tiers, with a harmonized reasoning_effort fallback elsewhere:

  • GPT (Azure OpenAI): reasoning_effort via openaiReasoningEfforts (inherits gpt-5.x sub-families and date-gated none / xhigh / minimal).
  • Claude (AWS Bedrock): thinking + output_config.effort adaptive (Opus 4.6+ / Sonnet 4.6+, with display: "summarized" for Opus 4.7+); thinking: { type: "enabled", budget_tokens } non-adaptive. Adaptive shape per AWS Bedrock adaptive thinking docs.
  • Gemini 2.5 (Vertex AI): thinkingConfig: { thinkingBudget, includeThoughts } via a new googleThinkingVariants helper shared with native @ai-sdk/google[/-vertex]. Pro 32k / Flash 24k via googleThinkingBudgetMax.
  • Gemini 3+, Cohere reasoning, Sonar deep-research, future reasoning models: harmonized reasoning_effort: low|medium|high fallback. SAP orchestration translates server-side; FM proxies pass through. Required for Gemini 3+ because the SAP vertexai.py harmonizer reads only thinkingBudget and drops thinkingLevel.
  • Drive-by: @ai-sdk/gateway Google 2.5 max budget unified with googleThinkingBudgetMax (was hardcoded 24576).

Behavioral change: variant overrides under providerOptions['sap-ai-core'] now reach the wire; SAP users previously got no reasoning at all.

How did you verify your code works?

bun test packages/opencode/test/provider/transform.test.ts
bun typecheck

241/241 tests pass (459 expects), typecheck clean. Tests use real SAP fixture IDs (gpt-5, gpt-5-mini, gpt-5-nano, gpt-5.4, gemini-2.5-pro, gemini-2.5-flash, gemini-3.1-flash-lite, anthropic--claude-4.5-opus, cohere--command-a-reasoning, sonar-deep-research) and assert the modelParams envelope on every effort variant. Wire passthrough across both orchestration and foundation-models API modes is validated by @jerome-benoit/sap-ai-provider-v2's parametrized test "should preserve unknown parameters" (sap-ai-language-model.test.ts:4912-4979).

Screenshots / recordings

N/A.

Checklist

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

Top-level reasoningEffort, thinking and thinkingConfig keys were
silently stripped by the SAP provider's Zod schema. Move them under
modelParams (catchall) using each upstream backend's REST field names:
reasoning_effort for Azure OpenAI, thinking + output_config for
Bedrock-routed Claude, thinkingConfig for Vertex-routed Gemini.
Reusing openaiReasoningEfforts lets SAP inherit gpt-5.x sub-family
coverage automatically.
return 24_576
}

// SAP's Zod schema drops unknown top-level keys; reasoning controls survive

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

can we follow style guide here prefer inlining things that arent used multiple times

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks — addressed in ab87638bd. Inlined the last SAP-specific single-use helper (sapAnthropicReasoningParams).

Recap of the multi-use helpers I kept:

  • wrapInSapModelParams — 3 call sites in the SAP case (otherwise Object.fromEntries(Object.entries(...).map(...)) would repeat 3×)
  • googleThinkingVariants — 2 call sites, shared between @ai-sdk/google[/-vertex] and SAP, so SAP automatically inherits future Gemini versions handled by googleThinkingLevelEfforts / googleThinkingBudgetMax.

Other single-use helpers from the original commit (sapGeminiReasoningParams, sapOpenaiReasoningParams) were removed in e97a3d2a4 (folded into the shared helper / inlined respectively).

… SAP

- Extract googleThinkingVariants(model) from @ai-sdk/google[/-vertex] and
  reuse it in the SAP case. SAP now inherits Gemini 3.x thinkingLevel
  tiers via googleThinkingLevelEfforts, matching the forward-compat
  coverage already in place for GPT (openaiReasoningEfforts) and
  Anthropic (anthropicAdaptiveEfforts).
- Drop sapGeminiReasoningParams and inline sapOpenaiReasoningParams
  per AGENTS.md single-use rule.
- Fix @ai-sdk/gateway Google 2.5 max budget hardcoded to 24576 \u2014 now
  uses googleThinkingBudgetMax (32k for 2.5 Pro, 24k for Flash).
- Brace SAP case if-bodies for consistency with the rest of the switch.
- Add Gemini 3 SAP test (gemini-3.1-flash-lite) and missing result.high
  assertion on gemini-2.5-flash.
Addresses review feedback on PR anomalyco#30482: AGENTS.md prefers inlining
single-use helpers. The prior commit (e97a3d2) already removed
sapGeminiReasoningParams (folded into the shared googleThinkingVariants
helper) and sapOpenaiReasoningParams (inlined). This drops the last
SAP-specific single-use helper.

Multi-use helpers are kept: wrapInSapModelParams (3 call sites) and
googleThinkingVariants (2 call sites, shared between native and SAP).
…r SAP models

- Gate Gemini branch on `2.5` so non-2.5 (Gemini 3+) falls through to
  the harmonized fallback. SAP vertexai harmonizer drops unknown
  thinkingLevel; harmonized reasoning_effort is mapped server-side.
- New harmonized fallback at the end of the SAP case using
  `reasoning_effort: low|medium|high` for any reasoning-capable model
  not handled by the major-provider branches (cohere reasoning,
  sonar-deep-research, future providers).
- Update tests: gemini-3.1-flash-lite now asserts harmonized fallback;
  add cohere-command-a-reasoning and sonar-deep-research fallback tests;
  llama opus-substring test asserts harmonized fallback (previously
  asserted empty).
Consolidate Gemini 2.5 pro/flash tests into one loop parametrized on
maxBudget. Consolidate gpt-5/-mini/-nano + gpt-5.4 + o3-mini into one
loop parametrized on apiId+releaseDate+efforts. Consolidate the four
harmonized-fallback tests (gemini-3.1-flash-lite + cohere + sonar +
llama opus-substring) into one loop. Net -16 lines, same coverage.
- Drop redundant .toLowerCase() on the `2.5` check in the Gemini
  guard; matches the case-sensitive substring style used by the
  surrounding anthropic/gpt branches (SAP API ids are already
  lowercase).
- Parametrize the non-adaptive Anthropic test over claude-sonnet-4
  and claude-4.5-opus to lock in that pre-4.6 reasoning models still
  take the budget_tokens path.
Drops the local lowercased id alias. The googleThinkingBudgetMax and
googleThinkingLevelEfforts helpers already lowercase their input, and
the inline "2.5" substring check matches against lowercase Gemini ids
by convention. Single-source naming aligns with the surrounding switch
which uses model.api.id directly.
Aligns the googleThinkingBudgetMax call with the surrounding
`id.includes("2.5")` check (outer-scope `id = model.id.toLowerCase()`)
introduced by the prior commit's drive-by fix.
Preserves the defensive .toLowerCase() the original @ai-sdk/google[/-vertex]
branch had on the outer scope id (= model.id.toLowerCase()). The helper
now declares const id = model.api.id.toLowerCase() locally and reuses it
for every check and every helper call inside googleThinkingVariants.
Replaces model.api.id with the outer-scope id (= model.id.toLowerCase()
declared at L643) in the SAP case, matching the majority pattern used by
other case branches (gateway, anthropic, copilot, etc.). The helpers
GPT5_FAMILY_RE and openaiReasoningEfforts are anchored on (?:^|/) so they
match equally well against the prefixed model.id form.
Mirrors the .serena/ pattern for the .omo/ agent runtime directory.
@rekram1-node

Copy link
Copy Markdown
Collaborator
High: remove the max variant for SAP-routed Claude Sonnet 4.6  
File: packages/opencode/src/provider/transform.ts:981 (https://github.com/anomalyco/opencode/pull/30482/files#diff-59de659bca343c2cc4566be8d80288995f51430778081b42614f91e6707dc455R981)  
anthropicAdaptiveEfforts() includes max for Sonnet 4.6, and this branch now forwards it as modelParams.output_config.effort: "max". SAP routes these Claude models through AWS Bedrock. AWS documents max as Claude Opus 4.6-only and states that using it on other models returns an error. Selecting the new max variant for anthropic--claude-sonnet-4-6 will therefore produce a rejected request. Restrict Sonnet 4.6 to low, medium, and high, while retaining max for Opus 4.6.

Medium: do not expose low and medium for the Cohere fallback without verified SAP translation  
File: packages/opencode/src/provider/transform.ts:1005 (https://github.com/anomalyco/opencode/pull/30482/files#diff-59de659bca343c2cc4566be8d80288995f51430778081b42614f91e6707dc455R1005)  
The generic fallback adds reasoning_effort: low | medium | high to cohere--command-a-reasoning. SAP publicly describes this model as OpenAI-SDK-based, but Cohere’s official OpenAI compatibility API supports only none and high; it explicitly rejects low and medium. The PR description claims SAP translates these values server-side, but the tests only assert object construction and do not verify SAP service acceptance. Either restrict Cohere to documented tiers or add evidence from a real SAP orchestration request that SAP harmonizes all three values successfully.

review bot feedback, acccurate?

@jerome-benoit

jerome-benoit commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

review bot feedback, acccurate?

SAP AI Core do the sanitization needed at routing the request to a given provider.
The same goes for the final fallback for harmonized thinking level: the payload is build given the targeted model/api provider

So I've chosen to expose the largest tunables surface that OpenCode offer for variants and SAP AI Core.

@rekram1-node rekram1-node merged commit 0b796c5 into anomalyco:dev Jun 3, 2026
8 checks passed
ShamirSecret pushed a commit to ShamirSecret/auto-code-machine that referenced this pull request Jun 4, 2026
avion23 pushed a commit to avion23/opencode that referenced this pull request Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SAP AI Core reasoning variants silently dropped before reaching the wire

2 participants