diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddDropdown.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddDropdown.svelte
index 699605fd82..905c2fe6f0 100644
--- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddDropdown.svelte
+++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddDropdown.svelte
@@ -71,7 +71,9 @@
-
+
+
{#snippet child({ props })}
{#if reasoning.modelSupportsThinking}
-
+
{#if reasoning.thinkingEnabled}
- {:else}
+ {:else if reasoning.isOff}
+ {:else}
+
{/if}
- {reasoning.thinkingEnabled ? reasoning.currentEffort : 'off'}
+ {reasoning.currentEffort}
@@ -37,14 +37,13 @@
>
{#each reasoning.levels as level (level.value)}
{@const tokenLabel = reasoning.tokenLabel(level)}
-
{/if}
-
+
{/each}
diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddSheet.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddSheet.svelte
index c533acc750..021e0e4539 100644
--- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddSheet.svelte
+++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddSheet.svelte
@@ -116,14 +116,16 @@
{#if reasoning.thinkingEnabled}
- {:else}
+ {:else if reasoning.isOff}
+ {:else}
+
{/if}
Reasoning
- {reasoning.thinkingEnabled ? reasoning.currentEffort : 'off'}
+ {reasoning.currentEffort}
diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormReasoningEffortSubmenu.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormReasoningEffortSubmenu.svelte
deleted file mode 100644
index 0a4277e94f..0000000000
--- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormReasoningEffortSubmenu.svelte
+++ /dev/null
@@ -1,127 +0,0 @@
-
-
-{#if modelSupportsThinking}
-
-
- {#if thinkingEnabled}
-
- {:else}
-
- {/if}
-
- Thinking
-
- {#if thinkingEnabled}
- {currentEffort}
- {:else}
- off
- {/if}
-
-
-
-
-{/if}
diff --git a/tools/ui/src/lib/constants/reasoning-effort.ts b/tools/ui/src/lib/constants/reasoning-effort.ts
index 28a24420e6..f21ea588ad 100644
--- a/tools/ui/src/lib/constants/reasoning-effort.ts
+++ b/tools/ui/src/lib/constants/reasoning-effort.ts
@@ -6,6 +6,7 @@ import type { ReasoningEffortLevel } from '$lib/types';
* Keys match the ReasoningEffort enum values for type-safe lookups.
*/
export const REASONING_EFFORT_LABELS: Record = {
+ [ReasoningEffort.DEFAULT]: 'Default',
[ReasoningEffort.OFF]: 'Off',
[ReasoningEffort.LOW]: 'Low',
[ReasoningEffort.MEDIUM]: 'Medium',
@@ -14,7 +15,8 @@ export const REASONING_EFFORT_LABELS: Record = {
};
export const REASONING_EFFORT_LEVELS: ReasoningEffortLevel[] = [
- { value: ReasoningEffort.OFF, label: 'Off', isOff: true },
+ { value: ReasoningEffort.DEFAULT, label: 'Default' },
+ { value: ReasoningEffort.OFF, label: 'Off' },
{ value: ReasoningEffort.LOW, label: 'Low' },
{ value: ReasoningEffort.MEDIUM, label: 'Medium' },
{ value: ReasoningEffort.HIGH, label: 'High' },
diff --git a/tools/ui/src/lib/enums/reasoning-effort.enums.ts b/tools/ui/src/lib/enums/reasoning-effort.enums.ts
index 172f118e7f..6bf86ed4ec 100644
--- a/tools/ui/src/lib/enums/reasoning-effort.enums.ts
+++ b/tools/ui/src/lib/enums/reasoning-effort.enums.ts
@@ -3,6 +3,7 @@
* These values are sent to the server and mapped to token budgets.
*/
export enum ReasoningEffort {
+ DEFAULT = 'default',
OFF = 'off',
LOW = 'low',
MEDIUM = 'medium',
diff --git a/tools/ui/src/lib/hooks/use-reasoning-menu.svelte.ts b/tools/ui/src/lib/hooks/use-reasoning-menu.svelte.ts
index 6a459e66bd..ce9b77884d 100644
--- a/tools/ui/src/lib/hooks/use-reasoning-menu.svelte.ts
+++ b/tools/ui/src/lib/hooks/use-reasoning-menu.svelte.ts
@@ -17,6 +17,7 @@ import { isRouterMode } from '$lib/stores/server.svelte';
export interface UseReasoningMenuReturn {
readonly modelSupportsThinking: boolean;
readonly thinkingEnabled: boolean;
+ readonly isOff: boolean;
readonly currentEffort: ReasoningEffort;
readonly levels: ReasoningEffortLevel[];
isSelected(level: ReasoningEffortLevel): boolean;
@@ -59,8 +60,10 @@ export function useReasoningMenu(): UseReasoningMenuReturn {
return supportsThinking() || modelSupportsThinkingFromMessages;
});
- const thinkingEnabled = $derived(conversationsStore.getThinkingEnabled());
const currentEffort = $derived(conversationsStore.getReasoningEffort());
+ const thinkingEnabled = $derived(
+ currentEffort !== ReasoningEffort.OFF && currentEffort !== ReasoningEffort.DEFAULT
+ );
return {
get modelSupportsThinking() {
@@ -69,6 +72,9 @@ export function useReasoningMenu(): UseReasoningMenuReturn {
get thinkingEnabled() {
return thinkingEnabled;
},
+ get isOff() {
+ return currentEffort === ReasoningEffort.OFF;
+ },
get currentEffort() {
return currentEffort;
},
@@ -76,20 +82,15 @@ export function useReasoningMenu(): UseReasoningMenuReturn {
return REASONING_EFFORT_LEVELS;
},
isSelected(level: ReasoningEffortLevel): boolean {
- if (level.isOff) return !thinkingEnabled;
- return thinkingEnabled && currentEffort === level.value;
+ return currentEffort === level.value;
},
tokenLabel(level: ReasoningEffortLevel): string | null {
- if (level.isOff) return null;
+ if (level.value === ReasoningEffort.DEFAULT) return 'Model default';
const tokens = REASONING_EFFORT_TOKENS[level.value];
+ if (tokens === undefined) return null;
return tokens === -1 ? 'Unlimited' : `Max ${tokens.toLocaleString()} tokens`;
},
select(level: ReasoningEffortLevel): void {
- if (level.isOff) {
- conversationsStore.setThinkingEnabled(false);
- return;
- }
- conversationsStore.setThinkingEnabled(true);
conversationsStore.setReasoningEffort(level.value as ReasoningEffort);
}
};
diff --git a/tools/ui/src/lib/services/chat.service.ts b/tools/ui/src/lib/services/chat.service.ts
index 15f4cc3b5f..d2455614fb 100644
--- a/tools/ui/src/lib/services/chat.service.ts
+++ b/tools/ui/src/lib/services/chat.service.ts
@@ -271,10 +271,14 @@ export class ChatService {
const reasoningBudgetTokens =
enableThinking && reasoningEffort ? (REASONING_EFFORT_TOKENS[reasoningEffort] ?? -1) : -1;
- requestBody.chat_template_kwargs = {
- ...(requestBody.chat_template_kwargs ?? {}),
- enable_thinking: enableThinking
- };
+ // an explicit user choice injects the kwarg, otherwise it is omitted so
+ // the server default applies (--reasoning flag or chat template)
+ if (enableThinking !== undefined) {
+ requestBody.chat_template_kwargs = {
+ ...(requestBody.chat_template_kwargs ?? {}),
+ enable_thinking: enableThinking
+ };
+ }
if (reasoningBudgetTokens >= 0) {
requestBody.thinking_budget_tokens = reasoningBudgetTokens;
diff --git a/tools/ui/src/lib/stores/chat.svelte.ts b/tools/ui/src/lib/stores/chat.svelte.ts
index ab418ed9cd..801d6c838f 100644
--- a/tools/ui/src/lib/stores/chat.svelte.ts
+++ b/tools/ui/src/lib/stores/chat.svelte.ts
@@ -2373,9 +2373,12 @@ class ChatStore {
if (currentConfig.excludeReasoningFromContext) apiOptions.excludeReasoningFromContext = true;
- apiOptions.enableThinking = conversationsStore.getThinkingEnabled();
+ // an explicit reasoning choice overrides the server default, DEFAULT sends nothing
const effort = conversationsStore.getReasoningEffort();
- if (effort !== ReasoningEffort.OFF) apiOptions.reasoningEffort = effort;
+ if (effort !== ReasoningEffort.DEFAULT) {
+ apiOptions.enableThinking = effort !== ReasoningEffort.OFF;
+ if (effort !== ReasoningEffort.OFF) apiOptions.reasoningEffort = effort;
+ }
if (hasValue(currentConfig.temperature))
apiOptions.temperature = Number(currentConfig.temperature);
diff --git a/tools/ui/src/lib/stores/conversations.svelte.ts b/tools/ui/src/lib/stores/conversations.svelte.ts
index 5c54e72ace..9649cfe03d 100644
--- a/tools/ui/src/lib/stores/conversations.svelte.ts
+++ b/tools/ui/src/lib/stores/conversations.svelte.ts
@@ -80,25 +80,17 @@ class ConversationsStore {
/** Whether the store has been initialized */
isInitialized = $state(false);
- /** Global (non-conversation-specific) thinking toggle default, derived from reasoning effort */
- pendingThinkingEnabled = $state(false);
-
/** Global (non-conversation-specific) reasoning effort default */
- pendingReasoningEffort = $state(
- ConversationsStore.loadReasoningEffortDefault()
- );
+ pendingReasoningEffort = $state(ConversationsStore.loadReasoningEffortDefault());
- /** Last non-off reasoning effort, restored when re-enabling thinking globally */
- private lastNonOffEffort: ReasoningEffort | null = null;
-
- /** Load reasoning effort default from localStorage */
- private static loadReasoningEffortDefault(): ReasoningEffort | ReasoningEffort.OFF {
- if (typeof globalThis.localStorage === 'undefined') return ReasoningEffort.OFF;
+ /** Load reasoning effort default from localStorage, DEFAULT defers to the server */
+ private static loadReasoningEffortDefault(): ReasoningEffort {
+ if (typeof globalThis.localStorage === 'undefined') return ReasoningEffort.DEFAULT;
try {
const raw = localStorage.getItem(REASONING_EFFORT_DEFAULT_LOCALSTORAGE_KEY);
- return (raw as ReasoningEffort | ReasoningEffort.OFF) || ReasoningEffort.OFF;
+ return (raw as ReasoningEffort) || ReasoningEffort.DEFAULT;
} catch {
- return ReasoningEffort.OFF;
+ return ReasoningEffort.DEFAULT;
}
}
@@ -235,17 +227,10 @@ class ConversationsStore {
// servers without a per-conversation override to `mcpServers[i].enabled`,
// and only explicit toggles are stored on the conversation.
- // Inherit global thinking/reasoning defaults into the new conversation
- const thinkingEnabled = this.getThinkingEnabled();
- conversation.thinkingEnabled = thinkingEnabled;
- conversation.reasoningEffort =
- this.pendingReasoningEffort === ReasoningEffort.OFF ? undefined : this.pendingReasoningEffort;
+ // Inherit the global reasoning default into the new conversation
+ conversation.reasoningEffort = this.pendingReasoningEffort;
await DatabaseService.updateConversation(conversation.id, {
- thinkingEnabled,
- reasoningEffort:
- this.pendingReasoningEffort === ReasoningEffort.OFF
- ? undefined
- : this.pendingReasoningEffort
+ reasoningEffort: this.pendingReasoningEffort
});
this.conversations = [conversation, ...this.conversations];
@@ -793,63 +778,21 @@ class ConversationsStore {
await this.setMcpServerOverride(serverId, undefined);
}
- /**
- * Gets the effective thinking-enabled state for the active conversation.
- * Returns the conversation override if set, otherwise the global default.
- */
- getThinkingEnabled(): boolean {
- if (this.activeConversation) {
- if (this.activeConversation.thinkingEnabled !== undefined) {
- return this.activeConversation.thinkingEnabled;
- }
- }
- return this.getReasoningEffort() !== ReasoningEffort.OFF;
- }
-
- /**
- * Sets the thinking-enabled state for the active conversation.
- * If no conversation exists, stores the global default.
- * @param enabled - The enabled state
- */
- async setThinkingEnabled(enabled: boolean): Promise {
- if (!this.activeConversation) {
- if (enabled) {
- const effort = this.lastNonOffEffort ?? ReasoningEffort.LOW;
- this.pendingReasoningEffort = effort;
- this.saveReasoningEffortDefaults();
- } else {
- if (this.pendingReasoningEffort !== ReasoningEffort.OFF) {
- this.lastNonOffEffort = this.pendingReasoningEffort;
- }
- this.pendingReasoningEffort = ReasoningEffort.OFF;
- this.saveReasoningEffortDefaults();
- }
- return;
- }
-
- this.activeConversation = {
- ...this.activeConversation,
- thinkingEnabled: enabled
- };
-
- await DatabaseService.updateConversation(this.activeConversation.id, {
- thinkingEnabled: enabled
- });
-
- const convIndex = this.conversations.findIndex((c) => c.id === this.activeConversation!.id);
- if (convIndex !== -1) {
- this.conversations[convIndex].thinkingEnabled = enabled;
- this.conversations = [...this.conversations];
- }
- }
-
/**
* Gets the effective reasoning effort for the active conversation.
* Returns the conversation override if set, otherwise the global default.
+ * DEFAULT means no override is sent and the server decides.
*/
- getReasoningEffort(): ReasoningEffort | ReasoningEffort.OFF {
+ getReasoningEffort(): ReasoningEffort {
if (this.activeConversation) {
- return this.activeConversation.reasoningEffort ?? this.pendingReasoningEffort;
+ if (this.activeConversation.reasoningEffort !== undefined) {
+ return this.activeConversation.reasoningEffort;
+ }
+ // conversations created before the tri-state store an explicit
+ // opt-out only as thinkingEnabled = false
+ if (this.activeConversation.thinkingEnabled === false) {
+ return ReasoningEffort.OFF;
+ }
}
return this.pendingReasoningEffort;
}
@@ -857,7 +800,7 @@ class ConversationsStore {
/**
* Sets the reasoning effort for the active conversation.
* If no conversation exists, stores the global default.
- * @param effort - The effort level ('low' | 'medium' | 'high' | 'max')
+ * @param effort - The effort level ('default' | 'off' | 'low' | 'medium' | 'high' | 'max')
*/
async setReasoningEffort(effort: ReasoningEffort): Promise {
if (!this.activeConversation) {
diff --git a/tools/ui/src/lib/types/reasoning.ts b/tools/ui/src/lib/types/reasoning.ts
index b72a3b77f1..417c79609f 100644
--- a/tools/ui/src/lib/types/reasoning.ts
+++ b/tools/ui/src/lib/types/reasoning.ts
@@ -1,6 +1,5 @@
export interface ReasoningEffortLevel {
value: string;
label: string;
- isOff?: boolean;
hasInfo?: boolean;
}