Repository navigation
[AISAGE-706] fix(ai-gateway): replace OpenRouter reasoning object for direct BYOK providers - #7754
Conversation
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryRe-reviewed the incremental commit The helper's default path is behavior-preserving ( Files Reviewed (5 files)
Previous Review Summary (commit 5d1a596)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 5d1a596)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryThe reasoning-object replacement is implemented correctly, ordered correctly against Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (17 files)
Reviewed by deepseek-v4.1-flash · Input: 72.2K · Output: 19K · Cached: 887.7K Review guidance: REVIEW.md from base branch |
E2E verification with live provider keysI tested four providers with live keys: chutes-byok, inceptron-byok, nvidia-byok and edenai. Setup: I ran ai-gateway locally with a seeded user whose BYOK keys were stored for those four providers. I sent requests through
For the baseline, I restored the provider files and
Upstream behavior, checked directly against each provider rather than through the gateway:
None of these four rejects the Separate finding, not changed here: inceptron returns reasoning in the legacy |
Summary
Since #7586, direct BYOK providers use the OpenRouter client by default. As a result, clients send OpenRouter's top-level
reasoningobject ({ effort, enabled, ... }) instead ofreasoning_effort. Strict OpenAI-compatible upstreams rejectreasoningas an unknown parameter. #7751 fixes this for OpenCode Go, which returned[unknown_parameter] invalid request body: json: unknown field "reasoning".This PR applies the same fix to the other direct BYOK providers that still forward the object. A new shared helper,
replaceReasoningObjectWithReasoningEffortinopenrouter/request-helpers.ts, copiesreasoning.effortintoreasoning_effort, unless the request already setsreasoning_effort, and then deletesreasoning. It only touches Chat Completions. A Responses APIreasoningobject is valid and left alone.morph-byok,xiaomi-token-plan-ams,xiaomi-token-plan-sgp,alibaba-token-plan,neuralwatttransformRequest:reasoningsent as is, effort never reachedreasoning_effortchutes-byok,inceptron-byok,ollama-cloud,syntheticreasoning_effortbut still sentreasoningzai-coding,byteplus-coding,kimi-codingthinkingfromreasoning, then still sentreasoningthinkingis still derived fromreasoningfirst, then the helper runs. In Kimi it runs before the existingreasoning_effort: 'none'removal, so a copiednoneis still removednvidia-byok,edenaireasoning; nvidia lostreasoning: {enabled: false}because it only copiedeffort{ disableWithNoneEffort: true }, which mapsenabled: falsetoreasoning_effort: 'none'. Only these two opt in: both were verified live to honornoneopencode-goUnchanged:
orcarouterkeepsreasoning, because it is a router and likely understands OpenRouter's parameter.The
nonemapping is opt-in because upstreams differ: nvidia and edenai honorreasoning_effort: 'none', but Chutes rejects it with a 400literal_error.Copying the effort into
reasoning_effortmatches what clients sent before #7586, when these providers used theopenai-compatibleclient.Upstream error logging
The Axiom investigation for this PR was slowed down by the upstream error log: direct BYOK failures appear as
direct-byok returned error <status>with only the upstream model ID (for exampleglm-5.3-flash). The provider had to be guessed from the error format, and the rejected field from the response text.captureProxyErrornow also logs:gatewayModel: the Kilo model ID after auto-routing (for exampleopencode-go/glm-5.3-flash), only when it differs frommodelbyokProviderIds: the user BYOK provider IDs serving the request (for exampleopencode-go)requestFields/messageFields: top-level and per-message field names (never values) of the body as sent upstream, after provider transformsThe message prefix is unchanged, so existing
startswith 'direct-byok returned error'queries keep working. Example from a local run of this branch (OpenCode Go omen-alpha, which still rejectsreasoninguntil #7751 merges):Evidence
direct-byok400s withunknown field "reasoning"came from OpenCode Goglm-5.3-flash.reasoningrejections were logged for the providers in this PR. This is a preventive fix based on the same request shape. Only an OpenCode Go key was available for live testing.reasoning_effortand scale reasoning with it (omen-alpha: 16 reasoning tokens atlow, 103 athigh).Verification
pnpm --filter @kilocode/web-shared typecheck: passesproviders/direct-byok/*,handlers/*andllm-proxy-helpers: 19 suites, 396 tests pass. The newdirect-byok/reasoning-object.test.tscovers the helper, each provider listed above,thinkingderivation and Kimi'snonehandling. NewcaptureProxyErrortests check the logged fields and that no field values (prompt or reasoning text) appear in the log.oxlinton the changed files: clean;oxfmtandgit diff --check: clean