diff --git a/changelog.d/fixes/combo-connection-scoped-reasoning-efforts.md b/changelog.d/fixes/combo-connection-scoped-reasoning-efforts.md new file mode 100644 index 0000000000..3a53f1323a --- /dev/null +++ b/changelog.d/fixes/combo-connection-scoped-reasoning-efforts.md @@ -0,0 +1 @@ +- **fix(catalog):** derive combo reasoning-effort tiers from the exact runtime-selectable connection scope, intersecting dynamic, pinned, allowlisted, and compatible provider-node evidence while failing closed on unknown capabilities. diff --git a/open-sse/config/grokBuild.ts b/open-sse/config/grokBuild.ts index c6e7dca7bf..ab648afd18 100644 --- a/open-sse/config/grokBuild.ts +++ b/open-sse/config/grokBuild.ts @@ -11,6 +11,7 @@ export const GROK_BUILD_TOKEN_URL = `${GROK_BUILD_OAUTH_ISSUER}/oauth2/token`; export const GROK_BUILD_DEFAULT_CLIENT_VERSION = "0.2.106"; export const GROK_BUILD_DEFAULT_CONTEXT_WINDOW = 256_000; export const GROK_BUILD_DEFAULT_REASONING_EFFORT = "high"; +export const GROK_BUILD_SUPPORTED_REASONING_EFFORTS = Object.freeze(["low", "medium", "high"]); export const GROK_BUILD_CLIENT_IDENTIFIER = "grok-shell"; export const GROK_BUILD_TOKEN_AUTH = "xai-grok-cli"; export const GROK_BUILD_REASONING_INCLUDE = "reasoning.encrypted_content"; diff --git a/open-sse/executors/grok-cli.ts b/open-sse/executors/grok-cli.ts index 275e8ebfdd..fc37b23ce2 100644 --- a/open-sse/executors/grok-cli.ts +++ b/open-sse/executors/grok-cli.ts @@ -12,13 +12,14 @@ import { GROK_BUILD_DEFAULT_REASONING_EFFORT, GROK_BUILD_REASONING_INCLUDE, GROK_BUILD_RESPONSES_URL, + GROK_BUILD_SUPPORTED_REASONING_EFFORTS, GROK_BUILD_TOKEN_URL, } from "../config/grokBuild.ts"; import { resolvePublicCred } from "../utils/publicCreds.ts"; import { BaseExecutor, type ExecutorLog, type ProviderCredentials } from "./base.ts"; const GROK_BUILD_MAX_TOOLS = 200; -const GROK_BUILD_SUPPORTED_REASONING_EFFORTS = new Set(["low", "medium", "high"]); +const GROK_BUILD_REASONING_EFFORT_SET = new Set(GROK_BUILD_SUPPORTED_REASONING_EFFORTS); const GROK_BUILD_REFRESH_MAX_ATTEMPTS = 3; const GROK_BUILD_REFRESH_MIN_DELAY_MS = 200; const GROK_BUILD_TERMINAL_REFRESH_ERRORS = new Set(["invalid_grant", "invalid_client"]); @@ -33,7 +34,6 @@ const GROK_BUILD_UNSUPPORTED_PARAMS = [ "reasoning_effort", ]; - /** * Grok Build's cli-chat-proxy is stricter about Responses `function_call_output.output` * than OpenAI's Responses API. Agent tool results can contain truncated / incomplete @@ -128,7 +128,7 @@ function normalizeGrokBuildReasoning( ): Record | null { const reasoning = asRequestRecord(value); const hasExplicitEffort = Object.prototype.hasOwnProperty.call(reasoning, "effort"); - if (!GROK_BUILD_SUPPORTED_REASONING_EFFORTS.has(String(reasoning.effort))) { + if (!GROK_BUILD_REASONING_EFFORT_SET.has(String(reasoning.effort))) { delete reasoning.effort; } if (model === "grok-composer-2.5-fast") { diff --git a/src/app/(dashboard)/dashboard/settings/components/ModelCapabilityOverridesTab.tsx b/src/app/(dashboard)/dashboard/settings/components/ModelCapabilityOverridesTab.tsx index 4989c397eb..e0b0ee66ab 100644 --- a/src/app/(dashboard)/dashboard/settings/components/ModelCapabilityOverridesTab.tsx +++ b/src/app/(dashboard)/dashboard/settings/components/ModelCapabilityOverridesTab.tsx @@ -9,7 +9,9 @@ import { type PricingCatalogProvider, } from "@/lib/modelCapabilityOverrideTargets"; -type ModelOverrideKey = "context_length" | "max_input_tokens" | "max_output_tokens"; +type ModelOverrideKey = + "context_length" | "max_input_tokens" | "max_output_tokens" | "reasoning_efforts"; +type ModelOverrideValue = number | string[]; type StatusTone = "success" | "error" | "info"; type ModelOverrideTarget = import("@/lib/modelCapabilityOverrideTargets").ModelOverrideTarget; @@ -22,7 +24,7 @@ interface PricingCatalogModel { interface ModelCapabilityOverride { target: string; key: ModelOverrideKey; - value: number; + value: ModelOverrideValue; } interface StatusMessage { @@ -70,7 +72,7 @@ function useModelCapabilityOverridesData() { }, [showStatus, t]); const saveOverride = useCallback( - async (target: string, key: ModelOverrideKey, value: number) => { + async (target: string, key: ModelOverrideKey, value: number | string) => { try { const response = await fetch("/api/model-capability-overrides", { method: "PATCH", @@ -150,7 +152,7 @@ function ModelCapabilityOverridesPanel({ }: { targets: ModelOverrideTarget[]; overrides: ModelCapabilityOverride[]; - onSave: (target: string, key: ModelOverrideKey, value: number) => void; + onSave: (target: string, key: ModelOverrideKey, value: number | string) => void; onRemove: (target: string, key: ModelOverrideKey) => void; }) { const [selectedTarget, setSelectedTarget] = useState(""); @@ -294,7 +296,7 @@ function ModelOverrideEditor({ activeOverrides: ModelCapabilityOverride[]; activeTarget: string; onRemove: (target: string, key: ModelOverrideKey) => void; - onSave: (target: string, key: ModelOverrideKey, value: number) => void; + onSave: (target: string, key: ModelOverrideKey, value: number | string) => void; }) { const t = useTranslations("settings"); return ( @@ -316,13 +318,18 @@ function ModelOverrideForm({ onSave, }: { activeTarget: string; - onSave: (target: string, key: ModelOverrideKey, value: number) => void; + onSave: (target: string, key: ModelOverrideKey, value: number | string) => void; }) { const t = useTranslations("settings"); const [key, setKey] = useState("context_length"); const [value, setValue] = useState(""); + const isReasoningEfforts = key === "reasoning_efforts"; const numericValue = Number(value); - const saveDisabled = !activeTarget || !Number.isInteger(numericValue) || numericValue <= 0; + const saveDisabled = + !activeTarget || + (isReasoningEfforts + ? value.length === 0 + : !Number.isInteger(numericValue) || numericValue <= 0); return (
@@ -334,14 +341,19 @@ function ModelOverrideForm({ + setValue(event.target.value)} - placeholder={t("modelOverrideValuePlaceholder")} + placeholder={t( + isReasoningEfforts + ? "modelOverrideReasoningEffortsPlaceholder" + : "modelOverrideValuePlaceholder" + )} className="flex-1 px-3 py-2 text-xs bg-bg-base border border-border rounded-md focus:outline-none focus:border-primary" />