feat(providers): per-model carve-out for reasoning_effort gating - #821
Conversation
Adds modelSupportsReasoningEffort(modelId) helper to model-match.ts returning 'none' | 'binary' | 'levels'. buildDeepSeekRequest now dispatches on this helper instead of unconditionally writing reasoning_effort. Default mode is 'levels' when modelId is omitted, preserving existing DeepSeek behaviour bit-for-bit. Pre-empts the GLM-style regression seen upstream (anomalyco/opencode#32446) where a blanket id.includes() gate silently dropped variants when new model versions arrived. Closes #819 [alexi-bot]
Coverage Report
Coverage Details
|
Security Scan — PR #821Role: Security vertical ( SummaryDiff is tightly scoped to the DeepSeek provider and a new
Overall: no blocking findings introduced by this PR. The only items below are pre-existing repo-level risks worth tracking separately. Critical (block)None. High (warn)These are pre-existing
Production-tree advisories with
Direct dependencies with findings:
Info
Recommendations
Scan basis: |
Architecture Review -- PR #821
SummaryThis PR is structurally contained and architecturally sound. It touches The change adds a new pure helper Layering scan (forbidden upward imports, ESM FindingsF-821.1
|
Review - role-quality
ScopePR adds a per-model carve-out for the
FindingsCorrectness:
Security: no new untrusted-input surface, no eval, no fs, no network. Tests:
Style / typing:
Performance: O(1) string Coverage delta:
Changes madenone VerdictApproved |
Closes #819
Summary
Replaces the unconditional
reasoning_effortwrite inbuildDeepSeekRequestwith a per-model capability dispatch backed by a newmodelSupportsReasoningEffort(modelId)helper insrc/providers/model-match.ts.Changes
src/providers/model-match.ts: Adds exportedReasoningEffortModetype ('none' | 'binary' | 'levels') andmodelSupportsReasoningEffort(modelId)helper. Currently returns'levels'for any id containingdeepseek(case-insensitive) and'none'otherwise. The'binary'branch is wired intobuildDeepSeekRequestso future binary-only thinking models can be carved out with one string match here.src/providers/deepseek.ts:DeepSeekOptionsnow accepts optionalmodelId. Thereasoning_effortblock dispatches on the helper:'levels'->request.reasoning_effort = options.reasoningEffort(existing behaviour).'binary'->request.enable_thinking = true(noreasoning_effort).'none'-> field is dropped.modelIdis omitted, defaults to'levels'to keep existing DeepSeek behaviour bit-for-bit identical.src/providers/__tests__/deepseek.test.tscovers all three branches (the'binary'branch via avi.mockofmodelSupportsReasoningEffort) plus themodelId-omitted default. Newsrc/providers/__tests__/model-match.test.tscovers DeepSeek match, case-insensitivity (DeepSeek-R1->'levels'), and the unknown-id default.Why
buildDeepSeekRequestpreviously forwardedreasoningEffortblindly. The moment routing-config / multi-model work (#813) lets users name arbitrary reasoning-style deployments, that blanket write would either be silently ignored or rejected by the provider. opencode learned this with GLM-4 -> 5.2 (sst/opencode#32446, 2026-06-20) where aid.includes('glm')gate silently dropped High/Max variants. Pre-empting it here in one helper + tests is cheaper than three-file refactor after a regression.Verification
No
@ts-ignore,eslint-disable, or newas anyintroduced.[alexi-bot]