-
Notifications
You must be signed in to change notification settings - Fork 3
fix: keep chat reasoning effort within the active model's capability #389
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,10 @@ export const THINKING_LEVEL_LABELS: Record<ThinkingLevel, string> = { | |
| adaptive: "Adaptive", | ||
| }; | ||
|
|
||
| export function isThinkingLevel(value: unknown): value is ThinkingLevel { | ||
| return typeof value === "string" && value in THINKING_LEVEL_LABELS; | ||
|
Comment on lines
+31
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Require own properties for thinking-level validation.
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| // Per-provider effort levels and defaults. | ||
| // | ||
| // Product decision (2026-07-23): the reasoning-effort picker is UNIFORM across | ||
|
|
@@ -82,6 +86,56 @@ export function getProviderReasoningConfig( | |
| return REASONING_BY_PROVIDER[provider] ?? FALLBACK_REASONING_CONFIG; | ||
| } | ||
|
|
||
| // The single level that is always safe to send: every provider config includes | ||
| // `off`, and the gateway accepts it for models that expose no reasoning control | ||
| // at all (local llama.cpp/Gemma). Used as the wire value whenever the active | ||
| // provider is still unknown, so the picker can never push a speculative `high` | ||
| // at a model that would reject it. | ||
| export const SAFE_THINKING_LEVEL: ThinkingLevel = "off"; | ||
|
|
||
| // Clamp a desired level to what the active provider actually supports, so the | ||
| // value pushed to the gateway is never one the model will reject. | ||
| // | ||
| // This is the last line of defence behind the picker's own gating: the picker | ||
| // only *offers* supported levels, but a level can still go stale across a model | ||
| // switch (a `high` chosen on DeepSeek carried into a local Gemma session before | ||
| // the header state has caught up). Routing every wire push through here means | ||
| // such a stale value is silently folded to the new provider's default (`off` | ||
| // for local Gemma) instead of reaching the gateway and erroring. | ||
| // | ||
| // When the provider is not yet known (`null` — the model catalog is still | ||
| // loading), returns `null` so the caller can hold the push until it knows what | ||
| // the active model supports, rather than guessing with the permissive fallback. | ||
| export function resolveWireThinkingLevel( | ||
| provider: string | null | undefined, | ||
| desired: ThinkingLevel, | ||
| ): ThinkingLevel | null { | ||
| if (!provider) return null; | ||
| const cfg = getProviderReasoningConfig(provider); | ||
| return cfg.levels.includes(desired) ? desired : cfg.default; | ||
| } | ||
|
|
||
| // The gateway rejects an unsupported effort with a message that also names the | ||
| // level it WILL accept, e.g.: | ||
| // | ||
| // thinkingLevel "high" is not supported for llamacpp/gemma4-e2b-it-q4_0 (use off) | ||
| // | ||
| // That parenthetical is the backend telling us the model's real capability. | ||
| // Parse it so a client that still races into this error can silently retry with | ||
| // the level the backend itself asked for, instead of surfacing a red banner. | ||
| // Returns null when the message isn't this specific rejection (any other | ||
| // failure should keep its normal error handling). | ||
| export function parseUnsupportedThinkingLevelError( | ||
| message: string | null | undefined, | ||
| ): ThinkingLevel | null { | ||
| if (typeof message !== "string") return null; | ||
| if (!/thinkinglevel/i.test(message) || !/not supported/i.test(message)) return null; | ||
| const match = /\(\s*use\s+([a-z-]+)\s*\)/i.exec(message); | ||
| if (!match) return null; | ||
| const suggested = match[1].toLowerCase(); | ||
| return isThinkingLevel(suggested) ? suggested : null; | ||
| } | ||
|
|
||
| export const PERSIST_KEY_PREFIX = "clawbox:chat:thinkingLevel"; | ||
|
|
||
| export function readPersistedThinkingLevel( | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Ignore fallbacks from superseded
sessions.patchrequests.A pending patch can reject after the user switches providers. The catch handler then writes the fallback under the captured old provider at Line 696 and updates the current shared
thinkingLevelat Line 698.For example, a pending remote-provider
highpatch can reject with(use off)after a switch to llama.cpp. This replaces the remote provider's persistedhighpreference withoff.Track a patch revision or the current provider and session key. Apply the fallback only when the rejected request is still current. Do not reset
lastSentThinkingReffor an obsolete request.As per path instructions,
src/components/**: “React 19 components with Tailwind CSS v4. Review for accessibility, proper state management, and XSS prevention.”🤖 Prompt for AI Agents
Source: Path instructions