Skip to content

fix(dispatch): stop leaking untranslated reasoning notation to providers - #858

Merged
mcowger merged 8 commits into
mainfrom
fix/reasoning-notation-egress
Sep 8, 2026
Merged

fix(dispatch): stop leaking untranslated reasoning notation to providers#858
mcowger merged 8 commits into
mainfrom
fix/reasoning-notation-egress

Conversation

@mcowger

@mcowger mcowger commented Sep 7, 2026

Copy link
Copy Markdown
Owner

What would you like me to do with these changes?

The auto-compat projection translated client reasoning intent into the
target provider's dialect (reasoning_effort, reasoning object,
enable_thinking, ...) but left the untranslated OpenAI-style fields in
the payload. Strict upstreams such as the Meta Model API hard-400 on
unknown parameters, and requests carrying a Responses-style reasoning
object failed on every target instead of being translated.

Also widen matchUnsupportedParameter to accept backtick-quoted param
names (Meta: "unknown parameter `reasoning`") so the reactive
same-target strip-and-retry safety net engages for that error shape
instead of falling straight through to target failover.
…s it

Follow-up to the egress stripping fix: the zai/deepseek/default branches
only deleted the reasoning object, leaving a client-sent reasoning_effort
in the payload whenever the provider's thinking dialect translates intent
to another field but cannot consume the effort string
(supportsReasoningEffort === false or no level mapping). Deletes now run
before the conditional write in each branch.
The provider adapter pane filtered reasoning_rewrite out of its checkbox
grid because the rules options editor only existed in the per-model
dialog. Extract the rules editor into a shared
ReasoningRewriteRulesEditor component used by both ProviderModelsEditor
and ProviderAdvancedEditor, and show the adapter checkbox (with its
rules editor) in the provider pane. Provider-level reasoning_rewrite
config was previously editable only via API.
The ant-ling branch only stripped reasoning_effort, leaving the client's
reasoning object in the outbound payload whenever the intent was a
disable (or otherwise inexpressible in ant-ling's dialect). Delete the
untranslated notations first, then write reasoning only when an enabled
effort maps — matching pi-ai's from-scratch ant-ling serialization.
@kody-ai

This comment has been minimized.

Comment thread packages/backend/src/services/dispatch/dispatcher-auto-compat.ts
…ort unknown

Addresses Kody review feedback: the default OpenAI-dialect branch deleted
the client's reasoning_effort unconditionally, silently dropping the
intent whenever compat.supportsReasoningEffort was undefined (unknown)
rather than false (provably unsupported). Only strip when support is
provably absent or a translated value is written; otherwise pass the
client value through
@kody-ai

This comment has been minimized.

…asoning object

When the client sends both a reasoning object (the authoritative intent)
and a stale reasoning_effort, deleting the object while preserving the
effort could reverse the intent (e.g. disabled + effort high). Strip the
effort whenever a recognized reasoning object was present, and only pass
reasoning_effort through unchanged when it WAS the intent source and
provider support is unknown
…not a malformed value

extractReasoningIntent ignores non-object reasoning values, so a malformed
field (e.g. reasoning: "high") never drives the intent; reasoning_effort
remains authoritative and must pass through when support is unknown. Gate
the stale-effort strip on reasoning being a recognized object
Comment thread packages/backend/src/services/dispatch/dispatcher-auto-compat.ts
@kody-ai

kody-ai Bot commented Sep 8, 2026

Copy link
Copy Markdown

Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

typeof [] === 'object', so an array-valued reasoning field would
previously be treated as an authoritative recognized intent source and
make a valid reasoning_effort look stale. Match the extractor's
record-only semantics with an explicit Array.isArray guard
@mcowger
mcowger merged commit f7e180e into main Sep 8, 2026
2 checks passed
@mcowger
mcowger deleted the fix/reasoning-notation-egress branch September 8, 2026 00:12
Comment on lines +205 to +211
// A client-side `reasoning` object is the AUTHORITATIVE intent source
// (extractReasoningIntent checks it before reasoning_effort), so when it is
// present a leftover reasoning_effort may contradict the intent we are about
// to translate — count it as stale no matter what the branch writes. A
// malformed non-object `reasoning` is IGNORED by the extractor, so it never
// drove the intent and must not make the effort look stale.
const hadReasoningObject =

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Bug high

Narrowing the guard to != null && typeof === 'object' drops the reasoning: null case, which the extractor resolves as a fallback to request.reasoning (line 91: source.reasoning ?? request.reasoning) — a field the anthropic and gemini request parsers populate. When the payload carries reasoning: null and request.reasoning holds the authoritative intent, the default branch deletes the null and lets a contradicting reasoning_effort (e.g. 'high' against enabled: false) reach the provider, reversing the translated intent pinned by the sibling test at line 434. Make the guard consult the same fallback source as the extractor.

// Mirror extractReasoningIntent's resolution order: a null/absent payload
// `reasoning` falls back to request.reasoning, which is then authoritative.
const resolvedReasoning = next.reasoning ?? intent.reasoning;
const hadReasoningObject = resolvedReasoning != null && typeof resolvedReasoning === 'object';
Prompt for LLM

File packages/backend/src/services/dispatch/dispatcher-auto-compat.ts:

Line 205 to 211:

Narrowing the guard to `!= null && typeof === 'object'` drops the `reasoning: null` case, which the extractor resolves as a fallback to `request.reasoning` (line 91: `source.reasoning ?? request.reasoning`) — a field the anthropic and gemini request parsers populate. When the payload carries `reasoning: null` and `request.reasoning` holds the authoritative intent, the default branch deletes the null and lets a contradicting `reasoning_effort` (e.g. 'high' against `enabled: false`) reach the provider, reversing the translated intent pinned by the sibling test at line 434. Make the guard consult the same fallback source as the extractor.

Suggested Code:

// Mirror extractReasoningIntent's resolution order: a null/absent payload
// `reasoning` falls back to request.reasoning, which is then authoritative.
const resolvedReasoning = next.reasoning ?? intent.reasoning;
const hadReasoningObject = resolvedReasoning != null && typeof resolvedReasoning === 'object';

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant