Studio: Bypass Permissions menu fix, decimal GB sizes, and GLM-5.2 high/max/disabled thinking (#6444)
* Studio: fix Bypass Permissions menu freeze and show decimal GB for model sizes
Bypass Permissions freeze: the warning dialog lived inside the composer
"+"/More dropdown and kept the menu mounted via onSelect preventDefault,
so confirming or cancelling the dialog left both popovers frozen open.
Lift the dialog out of the menu into a store-driven
BypassPermissionsConfirmDialog mounted at a stable spot in the composer.
The menu item now closes normally on select and just toggles a new
bypassConfirmOpen store flag, so the popovers dismiss as expected.
Model search sizes: formatBytes divided bytes by 1024 but labelled the
result "GB", so unsloth/GLM-5.2-GGUF:UD-IQ1_S showed 201.8 GB where
Hugging Face reports 217 GB. Switch the search display to decimal
(base-1000) units to match what Hugging Face reports. The GPU-fit math
stays base-1024 since VRAM capacity is binary.
* Studio: address review feedback and add GLM-5.2 high/max/disabled thinking
Review feedback on the Bypass Permissions and size-format changes:
- Mount the Bypass Permissions warning dialog once at the chat-page root
instead of inside each Composer. It is driven by global store state, so
the per-composer mount meant Compare mode (multiple composers) rendered
duplicate dialogs and the shared-composer menu had none. A single root
mount fixes both.
- Defer opening the dialog past Radix's menu-close focus restoration with
setTimeout(0), so the dropdown does not steal focus back and break the
dialog's focus trap.
- Clamp the unit index in formatBytes so units[i] cannot go out of bounds
past TB (and to absorb log() float error at exact powers of 1000).
GLM-5.2 reasoning levels:
GLM-5.2's template gates thinking with enable_thinking and also reads a
reasoning_effort level ('high' or 'max'), so it needs high / max /
disabled rather than the binary toggle it got before (its style was
detected as enable_thinking, which made 'high' unreachable). Add a new
reasoning style 'enable_thinking_effort' that reuses the effort dropdown
but, unlike gpt-oss, can be fully disabled:
- detect_reasoning_flags classifies a template that has both
enable_thinking and reasoning_effort, extracting the discrete levels
from the quoted effort literals it branches on. Templates with only one
of the two (gpt-oss, Qwen3, DeepSeek, GLM-4.6) are unchanged.
- _request_reasoning_kwargs maps the new style to enable_thinking plus an
in-range reasoning_effort; disabling sends enable_thinking=false. The
gpt-oss reasoning_effort path is left untouched.
- The backend reports reasoning_effort_levels on the load/status response;
the frontend carries them through to the effort dropdown and sends
enable_thinking + reasoning_effort for this style.
Verified: backend reasoning kwargs render the real GLM-5.2 template to
"Reasoning Effort: High/Max" (thinking) and an empty <think></think>
(disabled); tsc, eslint, i18n parity and the production build all pass.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: address review feedback on reasoning effort and formatBytes
- chat-adapter localReasoningEffort: accept 'minimal' so a template that
branches on it (extracted into reasoning_effort_levels) is sent through
instead of being coerced to 'low' and then dropped by the backend.
- formatBytes: return '0 B' for non-finite / non-positive sizes (missing
metadata -> NaN, Infinity, negatives) and clamp the unit index lower
bound to 0, so sub-1-byte values can't produce a negative index.
* Studio: hybrid reasoning none gate and decimal GB in load progress
- _request_reasoning_kwargs: for enable_thinking_effort models, treat a
raw reasoning_effort='none' (OpenAI 'no reasoning' sentinel) as the
enable_thinking=false off gate, so a direct API caller can disable
thinking even without passing enable_thinking. The frontend already
sends enable_thinking=false; this only affects raw API callers.
- use-chat-model-runtime: the download / 'X of Y GB in memory' load
progress divided bytes by 1024**3 but labelled GB, so it disagreed with
the model picker and Hugging Face. Use decimal GB (1e9) to match.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Studio: carry hybrid reasoning levels on all load paths and harden formatBytes
Review follow-ups on the enable_thinking_effort work:
- Every model-load path now copies reasoning_effort_levels and derives
supportsReasoningOff, via a shared reasoningCapsFromLoad() helper. The
shared/Compare composer load and the three chat-adapter auto-load paths
previously set only reasoningStyle, so a GLM-style hybrid model loaded
through Compare or first-chat auto-load fell back to the default
low|medium|high and lost its Max / Off controls.
- The local send path clamps the effort to the loaded model's advertised
levels (clampReasoningEffortToLevels) instead of a hard-coded list. A
stale "max" carried over from an external provider no longer reaches a
pure reasoning_effort (gpt-oss) model that only accepts none|low|medium|
high, where the backend would have dropped it.
- formatBytes divides iteratively instead of via Math.log, which has float
error at exact powers of 1000 (log(1e12)/log(1000) = 3.9999... would
label 1 TB as "1000 GB"). Keeps the non-finite/non-positive guard.
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
830423fc03
commit
0c1127cb08
14 changed files with 319 additions and 99 deletions
|
|
@ -552,6 +552,27 @@ _TOOL_TEMPLATE_MARKERS = (
|
|||
)
|
||||
|
||||
|
||||
# Canonical reasoning_effort levels, weakest -> strongest. Used to read the
|
||||
# discrete set a template branches on (e.g. GLM-5.2 uses 'high' | 'max') so we
|
||||
# only ever offer levels the template actually understands.
|
||||
_REASONING_EFFORT_SCALE = ("minimal", "low", "medium", "high", "max")
|
||||
|
||||
|
||||
def _extract_reasoning_effort_levels(chat_template: str) -> list:
|
||||
"""Return the reasoning_effort levels a template references, in canonical
|
||||
(weakest -> strongest) order.
|
||||
|
||||
Looks for the quoted literals (e.g. ``'high'`` / ``"max"``) the template
|
||||
compares ``reasoning_effort`` against, so we surface exactly the levels it
|
||||
branches on and nothing else.
|
||||
"""
|
||||
return [
|
||||
level
|
||||
for level in _REASONING_EFFORT_SCALE
|
||||
if f"'{level}'" in chat_template or f'"{level}"' in chat_template
|
||||
]
|
||||
|
||||
|
||||
def detect_reasoning_flags(
|
||||
chat_template: Optional[str],
|
||||
model_identifier: Optional[str] = None,
|
||||
|
|
@ -571,6 +592,7 @@ def detect_reasoning_flags(
|
|||
"supports_reasoning": False,
|
||||
"reasoning_style": "enable_thinking",
|
||||
"reasoning_always_on": False,
|
||||
"reasoning_effort_levels": [],
|
||||
"supports_preserve_thinking": False,
|
||||
"supports_tools": False,
|
||||
}
|
||||
|
|
@ -579,7 +601,25 @@ def detect_reasoning_flags(
|
|||
tpl = chat_template
|
||||
prefix = f"{log_source}: " if log_source else ""
|
||||
|
||||
if "enable_thinking" in tpl:
|
||||
effort_levels = (
|
||||
_extract_reasoning_effort_levels(tpl)
|
||||
if ("reasoning_effort" in tpl and "enable_thinking" in tpl)
|
||||
else []
|
||||
)
|
||||
if "enable_thinking" in tpl and "reasoning_effort" in tpl and effort_levels:
|
||||
# GLM-5.2-style: an enable_thinking on/off gate PLUS a reasoning_effort
|
||||
# level among a discrete set (e.g. 'high' | 'max'). Distinct from
|
||||
# gpt-oss (reasoning_effort only, no on/off gate) and Qwen
|
||||
# (enable_thinking only). Disabling is enable_thinking=false; the levels
|
||||
# are the quoted effort literals the template actually branches on.
|
||||
flags["supports_reasoning"] = True
|
||||
flags["reasoning_style"] = "enable_thinking_effort"
|
||||
flags["reasoning_effort_levels"] = effort_levels
|
||||
logger.info(
|
||||
f"{prefix}model supports reasoning "
|
||||
f"(enable_thinking + reasoning_effort: {effort_levels})"
|
||||
)
|
||||
elif "enable_thinking" in tpl:
|
||||
flags["supports_reasoning"] = True
|
||||
flags["reasoning_style"] = "enable_thinking"
|
||||
logger.info(f"{prefix}model supports reasoning (enable_thinking)")
|
||||
|
|
@ -1185,6 +1225,7 @@ class LlamaCppBackend:
|
|||
self._supports_reasoning: bool = False
|
||||
self._reasoning_always_on: bool = False
|
||||
self._reasoning_style: str = "enable_thinking"
|
||||
self._reasoning_effort_levels: list = []
|
||||
self._supports_preserve_thinking: bool = False
|
||||
self._supports_tools: bool = False
|
||||
self._cache_type_kv: Optional[str] = None
|
||||
|
|
@ -1468,6 +1509,12 @@ class LlamaCppBackend:
|
|||
def reasoning_style(self) -> str:
|
||||
return self._reasoning_style
|
||||
|
||||
@property
|
||||
def reasoning_effort_levels(self) -> list:
|
||||
"""Discrete reasoning_effort levels the template offers (e.g. GLM-5.2's
|
||||
['high', 'max']). Empty unless reasoning_style == 'enable_thinking_effort'."""
|
||||
return self._reasoning_effort_levels
|
||||
|
||||
@property
|
||||
def supports_preserve_thinking(self) -> bool:
|
||||
return self._supports_preserve_thinking
|
||||
|
|
@ -1477,6 +1524,10 @@ class LlamaCppBackend:
|
|||
return self._reasoning_default
|
||||
|
||||
def _reasoning_kwargs(self, enable_thinking: bool) -> dict:
|
||||
if self._reasoning_style == "enable_thinking_effort":
|
||||
# GLM-5.2-style: enable_thinking is the on/off gate; when on, leave
|
||||
# the template's default effort (max) in place.
|
||||
return {"enable_thinking": enable_thinking}
|
||||
if self._reasoning_style == "reasoning_effort":
|
||||
return {"reasoning_effort": "high" if enable_thinking else "low"}
|
||||
return {"enable_thinking": enable_thinking}
|
||||
|
|
@ -1497,7 +1548,20 @@ class LlamaCppBackend:
|
|||
# Always-on reasoning models hardcode <think> tags and don't consume
|
||||
# enable_thinking / reasoning_effort -- skip.
|
||||
if self._supports_reasoning and not self._reasoning_always_on:
|
||||
if self._reasoning_style == "reasoning_effort":
|
||||
if self._reasoning_style == "enable_thinking_effort":
|
||||
# GLM-5.2-style: enable_thinking gates thinking on/off, and the
|
||||
# reasoning_effort level (e.g. 'high' | 'max') is only meaningful
|
||||
# while thinking is on. Disabling is enable_thinking=false; a raw
|
||||
# API caller can also disable via the OpenAI-style
|
||||
# reasoning_effort="none" sentinel. We never coerce off into a
|
||||
# 'low' effort the way gpt-oss does (those models genuinely
|
||||
# cannot disable).
|
||||
thinking_off = enable_thinking is False or reasoning_effort == "none"
|
||||
if enable_thinking is not None or reasoning_effort == "none":
|
||||
kwargs["enable_thinking"] = not thinking_off
|
||||
if not thinking_off and reasoning_effort in self._reasoning_effort_levels:
|
||||
kwargs["reasoning_effort"] = reasoning_effort
|
||||
elif self._reasoning_style == "reasoning_effort":
|
||||
if reasoning_effort in ("none", "low", "medium", "high"):
|
||||
kwargs["reasoning_effort"] = reasoning_effort
|
||||
elif reasoning_effort == "minimal":
|
||||
|
|
@ -3071,6 +3135,7 @@ class LlamaCppBackend:
|
|||
self._supports_reasoning = False
|
||||
self._reasoning_always_on = False
|
||||
self._reasoning_style = "enable_thinking"
|
||||
self._reasoning_effort_levels = []
|
||||
self._reasoning_default = True
|
||||
self._supports_preserve_thinking = False
|
||||
self._supports_tools = False
|
||||
|
|
@ -3290,6 +3355,7 @@ class LlamaCppBackend:
|
|||
)
|
||||
self._supports_reasoning = flags["supports_reasoning"]
|
||||
self._reasoning_style = flags["reasoning_style"]
|
||||
self._reasoning_effort_levels = flags.get("reasoning_effort_levels", [])
|
||||
self._reasoning_always_on = flags["reasoning_always_on"]
|
||||
self._supports_preserve_thinking = flags["supports_preserve_thinking"]
|
||||
self._supports_tools = flags["supports_tools"]
|
||||
|
|
@ -5399,6 +5465,7 @@ class LlamaCppBackend:
|
|||
)
|
||||
self._supports_reasoning = flags["supports_reasoning"]
|
||||
self._reasoning_style = flags["reasoning_style"]
|
||||
self._reasoning_effort_levels = flags.get("reasoning_effort_levels", [])
|
||||
self._reasoning_always_on = flags["reasoning_always_on"]
|
||||
self._supports_preserve_thinking = flags["supports_preserve_thinking"]
|
||||
self._supports_tools = flags["supports_tools"]
|
||||
|
|
@ -6388,6 +6455,7 @@ class LlamaCppBackend:
|
|||
self._supports_reasoning = False
|
||||
self._reasoning_always_on = False
|
||||
self._reasoning_style = "enable_thinking"
|
||||
self._reasoning_effort_levels = []
|
||||
self._reasoning_default = True
|
||||
self._supports_preserve_thinking = False
|
||||
self._supports_tools = False
|
||||
|
|
|
|||
|
|
@ -220,9 +220,15 @@ class LoadResponse(BaseModel):
|
|||
False,
|
||||
description = "Whether model supports thinking/reasoning mode (enable_thinking or reasoning_effort)",
|
||||
)
|
||||
reasoning_style: Literal["enable_thinking", "reasoning_effort"] = Field(
|
||||
"enable_thinking",
|
||||
description = "Reasoning control style: 'enable_thinking' (boolean) or 'reasoning_effort' (low|medium|high)",
|
||||
reasoning_style: Literal["enable_thinking", "reasoning_effort", "enable_thinking_effort"] = (
|
||||
Field(
|
||||
"enable_thinking",
|
||||
description = "Reasoning control style: 'enable_thinking' (boolean), 'reasoning_effort' (low|medium|high), or 'enable_thinking_effort' (on/off gate plus an effort level, e.g. GLM-5.2 high|max)",
|
||||
)
|
||||
)
|
||||
reasoning_effort_levels: List[str] = Field(
|
||||
default_factory = list,
|
||||
description = "Discrete reasoning_effort levels the template offers when reasoning_style is 'enable_thinking_effort' (e.g. ['high', 'max']); empty otherwise",
|
||||
)
|
||||
reasoning_always_on: bool = Field(
|
||||
False,
|
||||
|
|
@ -332,9 +338,15 @@ class InferenceStatusResponse(BaseModel):
|
|||
supports_reasoning: bool = Field(
|
||||
False, description = "Whether the active model supports reasoning/thinking mode"
|
||||
)
|
||||
reasoning_style: Literal["enable_thinking", "reasoning_effort"] = Field(
|
||||
"enable_thinking",
|
||||
description = "Reasoning control style: 'enable_thinking' (boolean) or 'reasoning_effort' (low|medium|high)",
|
||||
reasoning_style: Literal["enable_thinking", "reasoning_effort", "enable_thinking_effort"] = (
|
||||
Field(
|
||||
"enable_thinking",
|
||||
description = "Reasoning control style: 'enable_thinking' (boolean), 'reasoning_effort' (low|medium|high), or 'enable_thinking_effort' (on/off gate plus an effort level, e.g. GLM-5.2 high|max)",
|
||||
)
|
||||
)
|
||||
reasoning_effort_levels: List[str] = Field(
|
||||
default_factory = list,
|
||||
description = "Discrete reasoning_effort levels the template offers when reasoning_style is 'enable_thinking_effort' (e.g. ['high', 'max']); empty otherwise",
|
||||
)
|
||||
reasoning_always_on: bool = Field(
|
||||
False, description = "Whether reasoning is always on (not toggleable)"
|
||||
|
|
|
|||
|
|
@ -1039,6 +1039,7 @@ def _detect_safetensors_features(backend, chat_template: Optional[str]) -> dict:
|
|||
"supports_reasoning": False,
|
||||
"reasoning_style": "enable_thinking",
|
||||
"reasoning_always_on": False,
|
||||
"reasoning_effort_levels": [],
|
||||
"supports_preserve_thinking": False,
|
||||
"supports_tools": False,
|
||||
}
|
||||
|
|
@ -2243,6 +2244,7 @@ async def load_model(
|
|||
native_context_length = llama_backend.native_context_length,
|
||||
supports_reasoning = llama_backend.supports_reasoning,
|
||||
reasoning_style = llama_backend.reasoning_style,
|
||||
reasoning_effort_levels = llama_backend.reasoning_effort_levels,
|
||||
reasoning_always_on = llama_backend.reasoning_always_on,
|
||||
supports_preserve_thinking = llama_backend.supports_preserve_thinking,
|
||||
supports_tools = llama_backend.supports_tools,
|
||||
|
|
@ -2289,6 +2291,7 @@ async def load_model(
|
|||
),
|
||||
supports_reasoning = _sf_supports_reasoning,
|
||||
reasoning_style = _sf_reasoning_style,
|
||||
reasoning_effort_levels = _sf_flags.get("reasoning_effort_levels", []),
|
||||
reasoning_always_on = _sf_flags["reasoning_always_on"],
|
||||
supports_preserve_thinking = _sf_flags["supports_preserve_thinking"],
|
||||
supports_tools = _sf_flags["supports_tools"],
|
||||
|
|
@ -2553,6 +2556,7 @@ async def load_model(
|
|||
native_context_length = llama_backend.native_context_length,
|
||||
supports_reasoning = llama_backend.supports_reasoning,
|
||||
reasoning_style = llama_backend.reasoning_style,
|
||||
reasoning_effort_levels = llama_backend.reasoning_effort_levels,
|
||||
reasoning_always_on = llama_backend.reasoning_always_on,
|
||||
supports_preserve_thinking = llama_backend.supports_preserve_thinking,
|
||||
supports_tools = llama_backend.supports_tools,
|
||||
|
|
@ -2669,6 +2673,7 @@ async def load_model(
|
|||
requires_trust_remote_code = _requires_rc,
|
||||
supports_reasoning = _sf_flags["supports_reasoning"],
|
||||
reasoning_style = _sf_flags["reasoning_style"],
|
||||
reasoning_effort_levels = _sf_flags.get("reasoning_effort_levels", []),
|
||||
reasoning_always_on = _sf_flags["reasoning_always_on"],
|
||||
supports_preserve_thinking = _sf_flags["supports_preserve_thinking"],
|
||||
supports_tools = _sf_flags["supports_tools"],
|
||||
|
|
@ -3226,6 +3231,7 @@ async def get_status(current_subject: str = Depends(get_current_subject)):
|
|||
requires_trust_remote_code = False,
|
||||
supports_reasoning = llama_backend.supports_reasoning,
|
||||
reasoning_style = llama_backend.reasoning_style,
|
||||
reasoning_effort_levels = llama_backend.reasoning_effort_levels,
|
||||
reasoning_always_on = llama_backend.reasoning_always_on,
|
||||
supports_preserve_thinking = llama_backend.supports_preserve_thinking,
|
||||
supports_tools = llama_backend.supports_tools,
|
||||
|
|
@ -3286,6 +3292,7 @@ async def get_status(current_subject: str = Depends(get_current_subject)):
|
|||
),
|
||||
supports_reasoning = _sf_flags["supports_reasoning"],
|
||||
reasoning_style = _sf_flags["reasoning_style"],
|
||||
reasoning_effort_levels = _sf_flags.get("reasoning_effort_levels", []),
|
||||
reasoning_always_on = _sf_flags["reasoning_always_on"],
|
||||
supports_preserve_thinking = _sf_flags["supports_preserve_thinking"],
|
||||
supports_tools = _sf_flags["supports_tools"],
|
||||
|
|
|
|||
|
|
@ -272,10 +272,22 @@ function ListLabel({
|
|||
|
||||
/** Format bytes to a human-readable size string. */
|
||||
function formatBytes(bytes: number): string {
|
||||
if (bytes === 0) return "0 B";
|
||||
// Guard non-positive / non-finite sizes (0, missing -> NaN, Infinity) so we
|
||||
// never render "NaN undefined" or a negative unit index.
|
||||
if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
|
||||
// Decimal (base-1000) units to match what Hugging Face reports for a repo's
|
||||
// file sizes -- e.g. 217 GB, not the 201.8 GiB a base-1024 divide would show.
|
||||
// (GPU-fit math below stays base-1024 since VRAM is binary.)
|
||||
// Divide iteratively rather than via Math.log, which has float error at exact
|
||||
// powers of 1000 (log(1e12)/log(1000) = 3.9999... would mislabel 1 TB as
|
||||
// "1000 GB"); the loop also can't run off the end of units.
|
||||
const units = ["B", "KB", "MB", "GB", "TB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
const value = bytes / 1024 ** i;
|
||||
let i = 0;
|
||||
let value = bytes;
|
||||
while (value >= 1000 && i < units.length - 1) {
|
||||
value /= 1000;
|
||||
i += 1;
|
||||
}
|
||||
return `${value.toFixed(value < 10 ? 1 : 0)} ${units[i]}`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2078,7 +2078,11 @@ const ReasoningToggle: FC<{ side?: "top" | "bottom" }> = ({
|
|||
return null;
|
||||
}
|
||||
|
||||
const isEffort = effectiveReasoningStyle === "reasoning_effort";
|
||||
// enable_thinking_effort (GLM-5.2: high|max + disable) reuses the effort
|
||||
// dropdown; it just also carries an Off row via supportsReasoningOff.
|
||||
const isEffort =
|
||||
effectiveReasoningStyle === "reasoning_effort" ||
|
||||
effectiveReasoningStyle === "enable_thinking_effort";
|
||||
// Dropdown when there are effort levels or preserve-thinking; else a toggle.
|
||||
const useDropdown = isEffort || supportsPreserveThinking;
|
||||
const activeLook = isEffort
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ import {
|
|||
toExternalBackendProviderType,
|
||||
} from "../external-providers";
|
||||
import { pickFriendlyContainerName } from "../lib/friendly-names";
|
||||
import { tryAdoptServerActiveModel } from "../lib/apply-inference-status-to-store";
|
||||
import {
|
||||
reasoningCapsFromLoad,
|
||||
tryAdoptServerActiveModel,
|
||||
} from "../lib/apply-inference-status-to-store";
|
||||
import {
|
||||
clampReasoningEffortToLevels,
|
||||
getExternalMaxOutputTokens,
|
||||
|
|
@ -1302,7 +1305,7 @@ async function autoLoadSmallestModel(): Promise<{
|
|||
supportsReasoning: loadResp.supports_reasoning ?? false,
|
||||
reasoningAlwaysOn: loadResp.reasoning_always_on ?? false,
|
||||
reasoningEnabled: loadResp.supports_reasoning ?? false,
|
||||
reasoningStyle: loadResp.reasoning_style ?? "enable_thinking",
|
||||
...reasoningCapsFromLoad(loadResp),
|
||||
supportsPreserveThinking:
|
||||
loadResp.supports_preserve_thinking ?? false,
|
||||
supportsTools: loadResp.supports_tools ?? false,
|
||||
|
|
@ -1370,7 +1373,7 @@ async function autoLoadSmallestModel(): Promise<{
|
|||
supportsReasoning: sfLoadResp.supports_reasoning ?? false,
|
||||
reasoningAlwaysOn: sfLoadResp.reasoning_always_on ?? false,
|
||||
reasoningEnabled: sfLoadResp.supports_reasoning ?? false,
|
||||
reasoningStyle: sfLoadResp.reasoning_style ?? "enable_thinking",
|
||||
...reasoningCapsFromLoad(sfLoadResp),
|
||||
supportsPreserveThinking:
|
||||
sfLoadResp.supports_preserve_thinking ?? false,
|
||||
supportsTools: sfLoadResp.supports_tools ?? false,
|
||||
|
|
@ -1474,7 +1477,7 @@ async function autoLoadSmallestModel(): Promise<{
|
|||
supportsReasoning: loadResp.supports_reasoning ?? false,
|
||||
reasoningAlwaysOn: loadResp.reasoning_always_on ?? false,
|
||||
reasoningEnabled: loadResp.supports_reasoning ?? false,
|
||||
reasoningStyle: loadResp.reasoning_style ?? "enable_thinking",
|
||||
...reasoningCapsFromLoad(loadResp),
|
||||
supportsPreserveThinking: loadResp.supports_preserve_thinking ?? false,
|
||||
supportsTools: loadResp.supports_tools ?? false,
|
||||
...resolveToolsEnabledOnLoad(loadResp.supports_tools ?? false),
|
||||
|
|
@ -2218,6 +2221,7 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter {
|
|||
reasoningEnabled,
|
||||
reasoningStyle,
|
||||
reasoningEffort,
|
||||
reasoningEffortLevels,
|
||||
supportsPreserveThinking,
|
||||
preserveThinking,
|
||||
} = runtime;
|
||||
|
|
@ -2258,12 +2262,15 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter {
|
|||
reasoningEffort,
|
||||
externalReasoningCaps.reasoningEffortLevels,
|
||||
) as RequestReasoningEffort;
|
||||
const localReasoningEffort =
|
||||
reasoningEffort === "low" ||
|
||||
reasoningEffort === "medium" ||
|
||||
reasoningEffort === "high"
|
||||
? reasoningEffort
|
||||
: "low";
|
||||
// Clamp to the loaded local model's advertised levels so a stale value
|
||||
// (e.g. "max" carried over from an external model, or a level this model
|
||||
// lacks) becomes one the backend will honor instead of being dropped:
|
||||
// gpt-oss-style reasoning_effort gets low|medium|high, GLM-style
|
||||
// enable_thinking_effort gets high|max.
|
||||
const localReasoningEffort = clampReasoningEffortToLevels(
|
||||
reasoningEffort,
|
||||
reasoningEffortLevels,
|
||||
);
|
||||
const externalReasoningEnabled =
|
||||
!externalReasoningCaps.supportsReasoningOff ? true : reasoningEnabled;
|
||||
const buildRequestPayload = async (
|
||||
|
|
@ -2506,11 +2513,25 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter {
|
|||
...(sandboxSessionId ? { session_id: sandboxSessionId } : {}),
|
||||
...(useAdapter === undefined ? {} : { use_adapter: useAdapter }),
|
||||
...(supportsReasoning
|
||||
? reasoningStyle === "reasoning_effort"
|
||||
? reasoningEnabled
|
||||
? { reasoning_effort: localReasoningEffort }
|
||||
: {}
|
||||
: { thinking: { type: reasoningEnabled ? "enabled" : "disabled" } }
|
||||
? reasoningStyle === "enable_thinking_effort"
|
||||
? // GLM-5.2-style: on/off gate plus an effort level. Disabling
|
||||
// sends enable_thinking=false (a real disable); enabling sends
|
||||
// the chosen level (e.g. high|max).
|
||||
reasoningEnabled
|
||||
? {
|
||||
enable_thinking: true,
|
||||
reasoning_effort: localReasoningEffort,
|
||||
}
|
||||
: { enable_thinking: false }
|
||||
: reasoningStyle === "reasoning_effort"
|
||||
? reasoningEnabled
|
||||
? { reasoning_effort: localReasoningEffort }
|
||||
: {}
|
||||
: {
|
||||
thinking: {
|
||||
type: reasoningEnabled ? "enabled" : "disabled",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(supportsPreserveThinking
|
||||
? { preserve_thinking: preserveThinking }
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { ShieldBanIcon } from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
|
||||
|
|
@ -20,63 +18,80 @@ import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
|||
import { useChatRuntimeStore } from "@/features/chat/stores/chat-runtime-store";
|
||||
import { Tick02Icon } from "@/lib/tick-icon";
|
||||
|
||||
// "Bypass Permissions" entry for the composer "+" -> More menu. Mirrors the
|
||||
// "Bypass permissions" entry for the composer "+" -> More menu. Mirrors the
|
||||
// settings toggle: enabling demands the danger warning, disabling is immediate.
|
||||
// onSelect preventDefault keeps the menu mounted so the warning dialog (which
|
||||
// lives in this same fragment) survives instead of unmounting with the menu.
|
||||
// The menu closes normally on select (no preventDefault) -- the warning dialog
|
||||
// lives outside the menu (BypassPermissionsConfirmDialog, mounted once at the
|
||||
// chat-page root and driven by the store), so it survives the menu unmounting
|
||||
// and the "+"/More popovers don't stay frozen.
|
||||
export function BypassPermissionsMenuItem() {
|
||||
const bypassPermissions = useChatRuntimeStore((s) => s.bypassPermissions);
|
||||
const setBypassPermissions = useChatRuntimeStore(
|
||||
(s) => s.setBypassPermissions,
|
||||
);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const setBypassConfirmOpen = useChatRuntimeStore(
|
||||
(s) => s.setBypassConfirmOpen,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuItem
|
||||
className={
|
||||
bypassPermissions ? "text-bypass font-medium" : undefined
|
||||
<DropdownMenuItem
|
||||
className={bypassPermissions ? "text-bypass font-medium" : undefined}
|
||||
onSelect={() => {
|
||||
if (bypassPermissions) {
|
||||
setBypassPermissions(false);
|
||||
} else {
|
||||
// Defer past Radix's menu-close focus restoration: opening the dialog
|
||||
// synchronously here lets the dropdown grab focus back and breaks the
|
||||
// dialog's focus trap.
|
||||
setTimeout(() => setBypassConfirmOpen(true), 0);
|
||||
}
|
||||
onSelect={(e) => {
|
||||
if (bypassPermissions) {
|
||||
setBypassPermissions(false);
|
||||
} else {
|
||||
e.preventDefault();
|
||||
setDialogOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<HugeiconsIcon icon={ShieldBanIcon} strokeWidth={2} />
|
||||
Bypass permissions
|
||||
{bypassPermissions ? (
|
||||
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} className="ml-auto" />
|
||||
) : null}
|
||||
</DropdownMenuItem>
|
||||
<AlertDialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<AlertDialogContent size="sm">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Enable Bypass permissions?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Bypass permissions is dangerous since the AI model might delete,
|
||||
corrupt your machine, and or cause real world damage to you or the
|
||||
world - only accept if you are certain
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
variant="destructive"
|
||||
className="!bg-destructive !text-destructive-foreground hover:!bg-destructive/90"
|
||||
onClick={() => {
|
||||
setBypassPermissions(true);
|
||||
setDialogOpen(false);
|
||||
}}
|
||||
>
|
||||
I understand
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
}}
|
||||
>
|
||||
<HugeiconsIcon icon={ShieldBanIcon} strokeWidth={2} />
|
||||
Bypass permissions
|
||||
{bypassPermissions ? (
|
||||
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} className="ml-auto" />
|
||||
) : null}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
// The danger-confirmation dialog. Mounted once at the chat-page root (not inside
|
||||
// a Composer or the menu) and driven by global store state, so it works for both
|
||||
// the main and shared composers, never duplicates in Compare mode, and confirming
|
||||
// or cancelling never leaves the composer "+"/More popovers frozen open.
|
||||
export function BypassPermissionsConfirmDialog() {
|
||||
const open = useChatRuntimeStore((s) => s.bypassConfirmOpen);
|
||||
const setOpen = useChatRuntimeStore((s) => s.setBypassConfirmOpen);
|
||||
const setBypassPermissions = useChatRuntimeStore(
|
||||
(s) => s.setBypassPermissions,
|
||||
);
|
||||
|
||||
return (
|
||||
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||
<AlertDialogContent size="sm">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Enable Bypass permissions?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Bypass permissions is dangerous since the AI model might delete,
|
||||
corrupt your machine, and or cause real world damage to you or the
|
||||
world - only accept if you are certain
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
variant="destructive"
|
||||
className="!bg-destructive !text-destructive-foreground hover:!bg-destructive/90"
|
||||
onClick={() => {
|
||||
setBypassPermissions(true);
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
I understand
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ import {
|
|||
RegisterCompareHandle,
|
||||
SharedComposer,
|
||||
} from "./shared-composer";
|
||||
import { BypassPermissionsConfirmDialog } from "./bypass-permissions-menu-item";
|
||||
import {
|
||||
CHAT_CODE_TOOLS_ENABLED_KEY,
|
||||
CHAT_IMAGE_TOOLS_ENABLED_KEY,
|
||||
|
|
@ -2235,6 +2236,12 @@ export function ChatPage({
|
|||
{/* Portaled surfaces render to document.body, escaping the parent's hidden
|
||||
wrapper, so gate them on `active` to keep them off other tabs. */}
|
||||
{active && <GuidedTour {...tour.tourProps} />}
|
||||
{/* Single app-level mount for the Bypass permissions warning. It is driven
|
||||
by global store state, so it must live at one stable root (not inside a
|
||||
Composer) -- otherwise Compare mode's multiple composers would each
|
||||
render their own copy and the shared-composer menu would have none. It
|
||||
also portals to body, so gate it on `active` like the tour above. */}
|
||||
{active && <BypassPermissionsConfirmDialog />}
|
||||
<div className="relative flex min-h-0 min-w-0 flex-1 basis-0 flex-col overflow-hidden">
|
||||
<NativeModelDropOverlay state={nativeModelDropState} />
|
||||
{/* Fade under the top bar so messages dissolve as they scroll
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ import {
|
|||
resolveToolsEnabledOnLoad,
|
||||
saveSpeculativeType,
|
||||
useChatRuntimeStore,
|
||||
type ReasoningEffort,
|
||||
} from "../stores/chat-runtime-store";
|
||||
import { clampReasoningEffortToLevels } from "../provider-capabilities";
|
||||
import {
|
||||
applyActiveModelStatusToStore,
|
||||
clampLocalReasoningEffort,
|
||||
|
|
@ -677,14 +679,21 @@ export function useChatModelRuntime() {
|
|||
const reasoningStyle = loadResponse.reasoning_style ?? "enable_thinking";
|
||||
const supportsReasoning = loadResponse.supports_reasoning ?? false;
|
||||
const supportsTools = loadResponse.supports_tools ?? false;
|
||||
// GLM-5.2-style models report their own effort levels (e.g.
|
||||
// high|max); everything else keeps the default low/medium/high.
|
||||
const reasoningEffortLevels =
|
||||
reasoningStyle === "reasoning_effort"
|
||||
? (["low", "medium", "high"] as const)
|
||||
loadResponse.reasoning_effort_levels &&
|
||||
loadResponse.reasoning_effort_levels.length > 0
|
||||
? (loadResponse.reasoning_effort_levels as ReasoningEffort[])
|
||||
: (["low", "medium", "high"] as const);
|
||||
const existingReasoningEffort = useChatRuntimeStore.getState().reasoningEffort;
|
||||
const clampedReasoningEffort = clampLocalReasoningEffort(
|
||||
existingReasoningEffort,
|
||||
);
|
||||
const clampedReasoningEffort =
|
||||
reasoningStyle === "enable_thinking_effort"
|
||||
? clampReasoningEffortToLevels(
|
||||
existingReasoningEffort,
|
||||
reasoningEffortLevels,
|
||||
)
|
||||
: clampLocalReasoningEffort(existingReasoningEffort);
|
||||
const ggufMaxContextLength = reportedMaxCtx;
|
||||
const nextReasoningEnabled = reasoningAlwaysOn
|
||||
? true
|
||||
|
|
@ -927,8 +936,8 @@ export function useChatModelRuntime() {
|
|||
|
||||
if (prog.progress > 0 && prog.progress < 1) {
|
||||
hasShownProgress = true;
|
||||
const dlGb = prog.downloaded_bytes / (1024 ** 3);
|
||||
const totalGb = prog.expected_bytes / (1024 ** 3);
|
||||
const dlGb = prog.downloaded_bytes / 1e9;
|
||||
const totalGb = prog.expected_bytes / 1e9;
|
||||
const pct = Math.round(prog.progress * 100);
|
||||
const progressLabel = composeProgressLabel(
|
||||
dlGb,
|
||||
|
|
@ -960,7 +969,7 @@ export function useChatModelRuntime() {
|
|||
prog.progress === 0
|
||||
) {
|
||||
hasShownProgress = true;
|
||||
const dlGb = prog.downloaded_bytes / (1024 ** 3);
|
||||
const dlGb = prog.downloaded_bytes / 1e9;
|
||||
const est = estimate(dlSamples, prog.downloaded_bytes, 0);
|
||||
const rateSuffix =
|
||||
est.stable ? ` • ${formatRate(est.rate)}` : "";
|
||||
|
|
@ -1018,8 +1027,10 @@ export function useChatModelRuntime() {
|
|||
return;
|
||||
}
|
||||
if (prog.bytes_total <= 0) return; // nothing useful to render
|
||||
const loadedGb = prog.bytes_loaded / (1024 ** 3);
|
||||
const totalGb = prog.bytes_total / (1024 ** 3);
|
||||
// Decimal GB (1e9) so the total matches the file size Hugging Face
|
||||
// reports and the model-picker shows, not the smaller base-1024 GiB.
|
||||
const loadedGb = prog.bytes_loaded / 1e9;
|
||||
const totalGb = prog.bytes_total / 1e9;
|
||||
const pct = Math.min(99, Math.round(prog.fraction * 100));
|
||||
const est = estimate(mmapSamples, prog.bytes_loaded, prog.bytes_total);
|
||||
const base = `${loadedGb.toFixed(1)} of ${totalGb.toFixed(1)} GB in memory`;
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import {
|
|||
CHAT_REASONING_ENABLED_KEY,
|
||||
loadOptionalBool,
|
||||
type ReasoningEffort,
|
||||
type ReasoningStyle,
|
||||
resolveToolsEnabledOnLoad,
|
||||
useChatRuntimeStore,
|
||||
} from "../stores/chat-runtime-store";
|
||||
import { isMultimodalResponse, type InferenceStatusResponse } from "../types/api";
|
||||
import { clampReasoningEffortToLevels } from "../provider-capabilities";
|
||||
import type { ChatModelSummary } from "../types/runtime";
|
||||
|
||||
type LocalReasoningEffort = Extract<ReasoningEffort, "low" | "medium" | "high">;
|
||||
|
|
@ -49,6 +51,38 @@ export function clampLocalReasoningEffort(
|
|||
return "low";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reasoning capability fields derived from a model load/status response.
|
||||
*
|
||||
* Centralises the effort-levels + can-disable derivation so every load path
|
||||
* (main load, status sync, shared/Compare composer, first-chat auto-load) agrees:
|
||||
* a hybrid GLM-style `enable_thinking_effort` model keeps its high|max|Off
|
||||
* controls no matter which path loaded it, instead of falling back to the
|
||||
* default low|medium|high and losing Max/Off.
|
||||
*/
|
||||
export function reasoningCapsFromLoad(resp: {
|
||||
reasoning_style?: ReasoningStyle | null;
|
||||
reasoning_effort_levels?: string[] | null;
|
||||
}): {
|
||||
reasoningStyle: ReasoningStyle;
|
||||
reasoningEffortLevels: readonly ReasoningEffort[];
|
||||
supportsReasoningOff: boolean;
|
||||
} {
|
||||
const reasoningStyle: ReasoningStyle =
|
||||
resp.reasoning_style ?? "enable_thinking";
|
||||
const reasoningEffortLevels: readonly ReasoningEffort[] =
|
||||
resp.reasoning_effort_levels && resp.reasoning_effort_levels.length > 0
|
||||
? (resp.reasoning_effort_levels as ReasoningEffort[])
|
||||
: (["low", "medium", "high"] as const);
|
||||
// enable_thinking and enable_thinking_effort can both be turned off; only the
|
||||
// pure gpt-oss-style reasoning_effort is always-on.
|
||||
return {
|
||||
reasoningStyle,
|
||||
reasoningEffortLevels,
|
||||
supportsReasoningOff: reasoningStyle !== "reasoning_effort",
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveInferenceCheckpointId(
|
||||
status: InferenceStatusResponse,
|
||||
): string | null {
|
||||
|
|
@ -110,9 +144,11 @@ export function applyActiveModelStatusToStore(
|
|||
const supportsReasoning = status.supports_reasoning ?? false;
|
||||
const reasoningAlwaysOn = status.reasoning_always_on ?? false;
|
||||
const reasoningStyle = status.reasoning_style ?? "enable_thinking";
|
||||
// GLM-5.2-style models report their own effort levels (e.g. high|max);
|
||||
// everything else keeps the default low/medium/high.
|
||||
const reasoningEffortLevels =
|
||||
reasoningStyle === "reasoning_effort"
|
||||
? (["low", "medium", "high"] as const)
|
||||
status.reasoning_effort_levels && status.reasoning_effort_levels.length > 0
|
||||
? (status.reasoning_effort_levels as ReasoningEffort[])
|
||||
: (["low", "medium", "high"] as const);
|
||||
const supportsPreserveThinking = status.supports_preserve_thinking ?? false;
|
||||
const supportsTools = status.supports_tools ?? false;
|
||||
|
|
@ -128,9 +164,13 @@ export function applyActiveModelStatusToStore(
|
|||
: null;
|
||||
const currentSpecType = normalizeSpeculativeType(status.speculative_type);
|
||||
const prevState = useChatRuntimeStore.getState();
|
||||
const clampedReasoningEffort = clampLocalReasoningEffort(
|
||||
prevState.reasoningEffort,
|
||||
);
|
||||
const clampedReasoningEffort =
|
||||
reasoningStyle === "enable_thinking_effort"
|
||||
? clampReasoningEffortToLevels(
|
||||
prevState.reasoningEffort,
|
||||
reasoningEffortLevels,
|
||||
)
|
||||
: clampLocalReasoningEffort(prevState.reasoningEffort);
|
||||
const nextDefaultChatTemplate =
|
||||
status.chat_template === undefined
|
||||
? prevState.defaultChatTemplate
|
||||
|
|
|
|||
|
|
@ -30,7 +30,10 @@ export interface ProviderCapabilities {
|
|||
|
||||
export type ExternalReasoningCapabilities = {
|
||||
supportsReasoning: boolean;
|
||||
reasoningStyle: "enable_thinking" | "reasoning_effort";
|
||||
// Mirrors the store's ReasoningStyle. External providers only ever use the
|
||||
// first two; "enable_thinking_effort" exists so a local model's caps can be
|
||||
// assigned here without narrowing.
|
||||
reasoningStyle: "enable_thinking" | "reasoning_effort" | "enable_thinking_effort";
|
||||
reasoningAlwaysOn: boolean;
|
||||
supportsReasoningOff: boolean;
|
||||
reasoningEffortLevels: readonly (
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ import {
|
|||
import { listPromptEntries, type PromptEntry } from "./api/prompts-api";
|
||||
import { McpComposerButton } from "./mcp-composer-button";
|
||||
import { BypassPermissionsMenuItem } from "./bypass-permissions-menu-item";
|
||||
import { reasoningCapsFromLoad } from "./lib/apply-inference-status-to-store";
|
||||
import { KnowledgeBaseComposerButton } from "@/features/rag/components/knowledge-base-composer-button";
|
||||
import { NewProjectDialog } from "./components/new-project-dialog";
|
||||
import { useChatProjects } from "./hooks/use-chat-projects";
|
||||
|
|
@ -617,7 +618,11 @@ export function SharedComposer({
|
|||
const reasoningDisabled = !modelLoaded || !effectiveSupportsReasoning;
|
||||
const showReasoningControl =
|
||||
effectiveSupportsReasoning || effectiveReasoningAlwaysOn;
|
||||
const isEffort = effectiveReasoningStyle === "reasoning_effort";
|
||||
// enable_thinking_effort (GLM-5.2: high|max + disable) reuses the effort
|
||||
// dropdown; it just also carries an Off row via supportsReasoningOff.
|
||||
const isEffort =
|
||||
effectiveReasoningStyle === "reasoning_effort" ||
|
||||
effectiveReasoningStyle === "enable_thinking_effort";
|
||||
const thinkingActiveLook = isEffort
|
||||
? reasoningLockedOn || (effectiveReasoningVisualEnabled && !reasoningDisabled)
|
||||
: reasoningLockedOn || (effectiveReasoningEnabled && !reasoningDisabled);
|
||||
|
|
@ -1001,7 +1006,7 @@ export function SharedComposer({
|
|||
useChatRuntimeStore.setState({
|
||||
supportsReasoning: resp.supports_reasoning ?? false,
|
||||
reasoningAlwaysOn: resp.reasoning_always_on ?? false,
|
||||
reasoningStyle: resp.reasoning_style ?? "enable_thinking",
|
||||
...reasoningCapsFromLoad(resp),
|
||||
supportsPreserveThinking: resp.supports_preserve_thinking ?? false,
|
||||
supportsTools: resp.supports_tools ?? false,
|
||||
tensorParallel: resp.tensor_parallel ?? false,
|
||||
|
|
|
|||
|
|
@ -168,7 +168,13 @@ function saveLastExternalCheckpoint(value: string | null): void {
|
|||
}
|
||||
}
|
||||
|
||||
export type ReasoningStyle = "enable_thinking" | "reasoning_effort";
|
||||
// "enable_thinking_effort" is a hybrid: an on/off gate (enable_thinking) plus an
|
||||
// effort level among a discrete set (e.g. GLM-5.2's high|max). It reuses the
|
||||
// reasoning_effort dropdown UI but, unlike gpt-oss, can be fully disabled.
|
||||
export type ReasoningStyle =
|
||||
| "enable_thinking"
|
||||
| "reasoning_effort"
|
||||
| "enable_thinking_effort";
|
||||
/** One live DiffusionGemma denoising snapshot: the current canvas text at a
|
||||
* given step of a given block (block/step are 0-based; total = steps in block). */
|
||||
export type DiffusionCanvasFrame = {
|
||||
|
|
@ -533,6 +539,9 @@ type ChatRuntimeStore = {
|
|||
* (secrets are still stripped). Takes precedence over confirmToolCalls.
|
||||
*/
|
||||
bypassPermissions: boolean;
|
||||
/** Whether the "Enable Bypass Permissions?" warning dialog is open. Lifted out
|
||||
* of the composer menu so confirming/cancelling it doesn't leave the menu frozen. */
|
||||
bypassConfirmOpen: boolean;
|
||||
/**
|
||||
* Per-chat set of tool names the user chose to auto-approve via "Always
|
||||
* allow". Keyed by UI confirmation scope, not necessarily the backend
|
||||
|
|
@ -662,6 +671,7 @@ type ChatRuntimeStore = {
|
|||
setMcpEnabledForChat: (enabled: boolean) => void;
|
||||
setConfirmToolCalls: (enabled: boolean) => void;
|
||||
setBypassPermissions: (enabled: boolean) => void;
|
||||
setBypassConfirmOpen: (open: boolean) => void;
|
||||
allowToolAlways: (sessionId: string, toolName: string) => void;
|
||||
setToolConfirmation: (
|
||||
toolCallId: string,
|
||||
|
|
@ -973,6 +983,7 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set, get) => ({
|
|||
// the confirmation gate, so it must be re-enabled (through the warning
|
||||
// dialog) each session rather than silently reactivating on reload.
|
||||
bypassPermissions: false,
|
||||
bypassConfirmOpen: false,
|
||||
alwaysAllowToolsBySession: new Map<string, Set<string>>(),
|
||||
toolConfirmations: {},
|
||||
webFetchToolsEnabled: loadBool(CHAT_WEB_FETCH_TOOLS_ENABLED_KEY, false),
|
||||
|
|
@ -1338,6 +1349,8 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set, get) => ({
|
|||
// Deliberately not persisted (see init): a reload must not silently keep
|
||||
// the sandbox/confirmation bypass active without re-accepting the warning.
|
||||
set(() => ({ bypassPermissions })),
|
||||
setBypassConfirmOpen: (bypassConfirmOpen) =>
|
||||
set(() => ({ bypassConfirmOpen })),
|
||||
allowToolAlways: (sessionId, toolName) =>
|
||||
set((state) => {
|
||||
const current = state.alwaysAllowToolsBySession.get(sessionId);
|
||||
|
|
|
|||
|
|
@ -137,7 +137,8 @@ export interface LoadModelResponse {
|
|||
max_context_length?: number | null;
|
||||
native_context_length?: number | null;
|
||||
supports_reasoning?: boolean;
|
||||
reasoning_style?: "enable_thinking" | "reasoning_effort";
|
||||
reasoning_style?: "enable_thinking" | "reasoning_effort" | "enable_thinking_effort";
|
||||
reasoning_effort_levels?: string[];
|
||||
reasoning_always_on?: boolean;
|
||||
supports_preserve_thinking?: boolean;
|
||||
supports_tools?: boolean;
|
||||
|
|
@ -176,7 +177,8 @@ export interface InferenceStatusResponse {
|
|||
} | null;
|
||||
requires_trust_remote_code?: boolean;
|
||||
supports_reasoning?: boolean;
|
||||
reasoning_style?: "enable_thinking" | "reasoning_effort";
|
||||
reasoning_style?: "enable_thinking" | "reasoning_effort" | "enable_thinking_effort";
|
||||
reasoning_effort_levels?: string[];
|
||||
reasoning_always_on?: boolean;
|
||||
supports_preserve_thinking?: boolean;
|
||||
supports_tools?: boolean;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue