From 2a061f1fb04f64ccc80ac97d55c9f660e02e371d Mon Sep 17 00:00:00 2001 From: Guilhem Lemouel Date: Fri, 11 Sep 2026 10:27:30 +0200 Subject: [PATCH] fix(ai-chat): drop a carried effort the button cannot show or clear MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit kept the effort when switching to a provider the registry has no rules for, reasoning that discarding a real setting on the strength of never having heard of the provider was wrong. With the thinking row now hidden for those providers, keeping it is worse: `resolveEffectiveReasoning` sends an explicitly set effort whatever the model, so the level rides out on every turn while nothing on screen shows it and nothing can clear it — and a provider that rejects the field fails every turn with no visible cause. Dropping matches what the session chat did before this PR. The flow chat is unaffected: its own guard returns before `carriedReasoning` is reached, and its effort input stays askable in the Configure-inputs modal. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01QN7VboDEm9HAB1t4sMxMdE --- .../lib/components/copilot/chatModelSettings.test.ts | 9 +++++++++ .../src/lib/components/copilot/chatModelSettings.ts | 11 ++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/components/copilot/chatModelSettings.test.ts b/frontend/src/lib/components/copilot/chatModelSettings.test.ts index 9088bd2405..18927069af 100644 --- a/frontend/src/lib/components/copilot/chatModelSettings.test.ts +++ b/frontend/src/lib/components/copilot/chatModelSettings.test.ts @@ -135,6 +135,15 @@ describe('carriedReasoning', () => { expect(carriedReasoning(REASONING_OFF, REASONING_OFF, cap('gpt-5'))).toBeUndefined() }) + // A provider the registry has no rules for draws no thinking control, so a carried level + // would be invisible and unclearable — and still sent, since an explicitly set effort + // goes out whatever the model. + it('drops the effort where it has no rules for the provider', () => { + expect( + carriedReasoning('high', REASONING_OFF, getReasoningCapability('customai', 'deepseek-r1')) + ).toBeUndefined() + }) + it('has nothing to carry when no effort is set', () => { expect(carriedReasoning(undefined, '', cap('gpt-5.1'))).toBeUndefined() expect(carriedReasoning('', '', cap('gpt-5.1'))).toBeUndefined() diff --git a/frontend/src/lib/components/copilot/chatModelSettings.ts b/frontend/src/lib/components/copilot/chatModelSettings.ts index 0dda8d0e2f..bbe6111270 100644 --- a/frontend/src/lib/components/copilot/chatModelSettings.ts +++ b/frontend/src/lib/components/copilot/chatModelSettings.ts @@ -85,17 +85,18 @@ export const REASONING_PROVIDER_DEFAULT = 'default' * rejects the request or quietly runs at another one, and the button would name a level the * run never used. Off survives only onto a model that can truly disable. * - * A model the registry has no rules for keeps whatever it had: dropping on `supported: - * false` would discard a real setting on the strength of never having heard of the - * provider, and nothing would draw a control to put it back. + * A model the registry has no rules for drops it too, for the same reason: the button draws + * no thinking control there, so a carried level would be invisible and unclearable while + * still going out on the wire — `resolveEffectiveReasoning` sends an explicitly set effort + * whatever the model, and a provider that rejects the field would then fail every turn with + * nothing on screen to explain it. */ export function carriedReasoning( current: string | undefined, offToken: string | undefined, - capability: { levels: string[]; canDisable: boolean; known: boolean } + capability: { levels: string[]; canDisable: boolean } ): string | undefined { if (current === undefined || current === '') return undefined - if (!capability.known) return current if (offToken !== undefined && current === offToken) { return capability.canDisable ? current : undefined }