diff --git a/coderd/exp_chats.go b/coderd/exp_chats.go index 70149f0ece..8a3e1f6cfc 100644 --- a/coderd/exp_chats.go +++ b/coderd/exp_chats.go @@ -5572,6 +5572,9 @@ func (api *API) getChatAdvisorConfig(rw http.ResponseWriter, r *http.Request) { } resp.MaxUsesPerRun = max(resp.MaxUsesPerRun, 0) resp.MaxOutputTokens = max(resp.MaxOutputTokens, 0) + if resp.ModelConfigID == uuid.Nil { + resp.ReasoningEffort = nil + } resp.Enabled = api.Experiments.Enabled(codersdk.ExperimentChatAdvisor) httpapi.Write(ctx, rw, http.StatusOK, resp) @@ -5601,13 +5604,21 @@ func (api *API) putChatAdvisorConfig(rw http.ResponseWriter, r *http.Request) { }) return } - if req.ModelConfigID != uuid.Nil { + if req.ModelConfigID == uuid.Nil { + if req.ReasoningEffort != nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "reasoning_effort requires model_config_id.", + }) + return + } + } else { // Use system context because GetChatModelConfigByID requires // deployment-config read access, which can be broader than the - // handler's explicit update check. The lookup only validates that - // the referenced model exists before persisting deployment config. + // handler's explicit update check. The lookup validates the model and + // any selected reasoning effort before persisting deployment config. //nolint:gocritic // This admin-authorized validation lookup intentionally bypasses read authz. - if _, err := api.Database.GetChatModelConfigByID(dbauthz.AsSystemRestricted(ctx), req.ModelConfigID); err != nil { + modelConfig, err := api.Database.GetChatModelConfigByID(dbauthz.AsSystemRestricted(ctx), req.ModelConfigID) + if err != nil { if errors.Is(err, sql.ErrNoRows) || httpapi.Is404Error(err) { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("model_config_id %q does not match any existing model config.", req.ModelConfigID), @@ -5620,6 +5631,10 @@ func (api *API) putChatAdvisorConfig(rw http.ResponseWriter, r *http.Request) { }) return } + if status, response := validateChatModelOverrideEffort(modelConfig, req.ReasoningEffort); response != nil { + httpapi.Write(ctx, rw, status, *response) + return + } } raw, err := json.Marshal(req) diff --git a/coderd/exp_chats_test.go b/coderd/exp_chats_test.go index e55b4a74db..1a1ba00abc 100644 --- a/coderd/exp_chats_test.go +++ b/coderd/exp_chats_test.go @@ -13988,13 +13988,21 @@ func TestChatAdvisorConfig_RoundTripModelConfigID(t *testing.T) { adminClient := newChatClient(t) coderdtest.CreateFirstUser(t, adminClient.Client) - modelConfig := createChatModelConfig(t, adminClient) + modelConfig := createAdditionalChatModelConfigWithReasoningEffort( + t, + adminClient, + "openai", + "gpt-5.2", + codersdk.ChatModelReasoningEffortMedium, + codersdk.ChatModelReasoningEffortXHigh, + ) want := codersdk.AdvisorConfig{ Enabled: true, MaxUsesPerRun: 3, MaxOutputTokens: 2048, ModelConfigID: modelConfig.ID, + ReasoningEffort: ptr.Ref(codersdk.ChatModelReasoningEffortHigh), } err := adminClient.UpdateChatAdvisorConfig(ctx, want) @@ -14021,6 +14029,44 @@ func TestChatAdvisorConfig_InvalidModelConfigID(t *testing.T) { require.Contains(t, sdkErr.Message, "does not match any existing model config") } +func TestChatAdvisorConfig_ReasoningEffortRequiresModelConfig(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitLong) + adminClient := newChatClient(t) + coderdtest.CreateFirstUser(t, adminClient.Client) + + err := adminClient.UpdateChatAdvisorConfig(ctx, codersdk.UpdateAdvisorConfigRequest{ + ReasoningEffort: ptr.Ref(codersdk.ChatModelReasoningEffortHigh), + }) + sdkErr := requireSDKError(t, err, http.StatusBadRequest) + require.Equal(t, "reasoning_effort requires model_config_id.", sdkErr.Message) +} + +func TestChatAdvisorConfig_ReasoningEffortMustBeSelectable(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitLong) + adminClient := newChatClient(t) + coderdtest.CreateFirstUser(t, adminClient.Client) + modelConfig := createAdditionalChatModelConfigWithReasoningEffort( + t, + adminClient, + "openai", + "gpt-5.2", + codersdk.ChatModelReasoningEffortLow, + codersdk.ChatModelReasoningEffortMedium, + ) + + err := adminClient.UpdateChatAdvisorConfig(ctx, codersdk.UpdateAdvisorConfigRequest{ + ModelConfigID: modelConfig.ID, + ReasoningEffort: ptr.Ref(codersdk.ChatModelReasoningEffortHigh), + }) + sdkErr := requireSDKError(t, err, http.StatusBadRequest) + require.Equal(t, "Invalid reasoning_effort value.", sdkErr.Message) + require.Equal(t, "Must be one of none, minimal, low, medium.", sdkErr.Detail) +} + func TestChatAdvisorConfig_RoundTripZeroValues(t *testing.T) { t.Parallel() @@ -14053,13 +14099,21 @@ func TestChatAdvisorConfig_OverwriteClearsPreviousValues(t *testing.T) { adminClient := newChatClient(t) coderdtest.CreateFirstUser(t, adminClient.Client) - modelConfig := createChatModelConfig(t, adminClient) + modelConfig := createAdditionalChatModelConfigWithReasoningEffort( + t, + adminClient, + "openai", + "gpt-5.2", + codersdk.ChatModelReasoningEffortMedium, + codersdk.ChatModelReasoningEffortXHigh, + ) rich := codersdk.AdvisorConfig{ Enabled: true, MaxUsesPerRun: 5, MaxOutputTokens: 1024, ModelConfigID: modelConfig.ID, + ReasoningEffort: ptr.Ref(codersdk.ChatModelReasoningEffortHigh), } err := adminClient.UpdateChatAdvisorConfig(ctx, rich) require.NoError(t, err) diff --git a/coderd/x/chatd/advisor_internal_test.go b/coderd/x/chatd/advisor_internal_test.go index e3b7623307..befb74f84a 100644 --- a/coderd/x/chatd/advisor_internal_test.go +++ b/coderd/x/chatd/advisor_internal_test.go @@ -316,6 +316,10 @@ func TestResolveAdvisorModelOverride(t *testing.T) { providerID := uuid.New() rawOptions, err := json.Marshal(codersdk.ChatModelCallConfig{ Temperature: func() *float64 { v := 0.42; return &v }(), + ReasoningEffort: &codersdk.ChatModelReasoningEffortConfig{ + Default: ptr.Ref(codersdk.ChatModelReasoningEffortLow), + Max: ptr.Ref(codersdk.ChatModelReasoningEffortXHigh), + }, }) require.NoError(t, err) store := &advisorOverrideStubStore{ @@ -343,7 +347,10 @@ func TestResolveAdvisorModelOverride(t *testing.T) { gotModel, gotCfg := p.resolveAdvisorModelOverrideOrFallback( ctx, database.Chat{}, - codersdk.AdvisorConfig{ModelConfigID: configID}, + codersdk.AdvisorConfig{ + ModelConfigID: configID, + ReasoningEffort: ptr.Ref(codersdk.ChatModelReasoningEffortHigh), + }, fallbackModel, fallbackCallConfig, modelBuildOptions{ActiveAPIKeyID: uuid.NewString()}, @@ -359,6 +366,9 @@ func TestResolveAdvisorModelOverride(t *testing.T) { require.Equal(t, "gpt-5.2", gotModel.Model()) require.NotNil(t, gotCfg.Temperature) require.InDelta(t, 0.42, *gotCfg.Temperature, 1e-9) + require.NotNil(t, gotCfg.ReasoningEffort) + require.Equal(t, codersdk.ChatModelReasoningEffortHigh, *gotCfg.ReasoningEffort.Default) + require.Equal(t, codersdk.ChatModelReasoningEffortHigh, *gotCfg.ReasoningEffort.Max) }) t.Run("AIProviderIDResolvesOverrideProviderKeys", func(t *testing.T) { t.Parallel() diff --git a/coderd/x/chatd/chatd.go b/coderd/x/chatd/chatd.go index 1a3616d69e..6c866765f9 100644 --- a/coderd/x/chatd/chatd.go +++ b/coderd/x/chatd/chatd.go @@ -350,6 +350,19 @@ func (p *Server) resolveAdvisorModelOverride( return fallbackModel, fallbackCallConfig, nil } + if advisorCfg.ReasoningEffort != nil { + resolvedEffort := chatprovider.ResolveReasoningEffort( + advisorCfg.ReasoningEffort, + overrideCallConfig.ReasoningEffort, + ) + if resolvedEffort != nil { + overrideCallConfig.ReasoningEffort = &codersdk.ChatModelReasoningEffortConfig{ + Default: resolvedEffort, + Max: resolvedEffort, + } + } + } + return overrideModel, overrideCallConfig, nil } @@ -398,8 +411,8 @@ func (p *Server) newAdvisorRuntime( } advisorCallConfig.MaxOutputTokens = ptr.Ref(maxOutputTokens) - // The advisor has no per-turn effort selection; its model config's - // default effort applies. + // The override resolver pins an explicit advisor effort into the model + // config. Fallback models keep their configured default effort. advisorReasoningEffort := chatprovider.ResolveReasoningEffort( nil, advisorCallConfig.ReasoningEffort, diff --git a/codersdk/chats.go b/codersdk/chats.go index cbb8c8aa9b..c35af0c4ce 100644 --- a/codersdk/chats.go +++ b/codersdk/chats.go @@ -903,6 +903,9 @@ type AdvisorConfig struct { // resolved (e.g. the referenced model config was soft-deleted or // its provider was disabled after the admin saved this config). ModelConfigID uuid.UUID `json:"model_config_id" format:"uuid"` + // ReasoningEffort overrides the selected advisor model's configured default. + // It requires a non-zero ModelConfigID. + ReasoningEffort *string `json:"reasoning_effort,omitempty"` } // UpdateAdvisorConfigRequest is the request body for updating advisor diff --git a/docs/ai-coder/agents/platform-controls/advisor.md b/docs/ai-coder/agents/platform-controls/advisor.md index 9ef54da4a7..0de6a84c61 100644 --- a/docs/ai-coder/agents/platform-controls/advisor.md +++ b/docs/ai-coder/agents/platform-controls/advisor.md @@ -37,6 +37,7 @@ Once the experiment is enabled, configure the advisor under **AI Settings** > | Max uses per turn | `0` (unlimited) | Caps how many times the root agent can call the advisor in a single chat turn. Must be a non-negative integer. | | Max output tokens | `0` (server default) | Caps the advisor model's response length. `0` uses the server default of 16,384 tokens. Must be a non-negative integer. | | Advisor model | Use chat model | Optional dedicated chat model config for the advisor. When unset, the advisor reuses the root agent's model. | +| Reasoning effort | Model default | Overrides the selected advisor model's reasoning effort. Available only when the model supports selectable effort. | The advisor is not available in plan mode or to subagents. Failed advisor invocations refund the per-turn budget, and advisor calls are not metered diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 81eda3801b..901e65f6ab 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1077,6 +1077,11 @@ export interface AdvisorConfig { * its provider was disabled after the admin saved this config). */ readonly model_config_id: string; + /** + * ReasoningEffort overrides the selected advisor model's configured default. + * It requires a non-zero ModelConfigID. + */ + readonly reasoning_effort?: string; } // From codersdk/users.go @@ -9025,6 +9030,11 @@ export interface UpdateAdvisorConfigRequest { * its provider was disabled after the admin saved this config). */ readonly model_config_id: string; + /** + * ReasoningEffort overrides the selected advisor model's configured default. + * It requires a non-zero ModelConfigID. + */ + readonly reasoning_effort?: string; } // From codersdk/deployment.go diff --git a/site/src/pages/AISettingsPage/CoderAgentsPage/CoderAgentsPageView.stories.tsx b/site/src/pages/AISettingsPage/CoderAgentsPage/CoderAgentsPageView.stories.tsx index 04e818b5ed..81734060b2 100644 --- a/site/src/pages/AISettingsPage/CoderAgentsPage/CoderAgentsPageView.stories.tsx +++ b/site/src/pages/AISettingsPage/CoderAgentsPage/CoderAgentsPageView.stories.tsx @@ -55,6 +55,17 @@ const claudeSonnetModelConfig = buildModelConfig({ context_limit: 200_000, }); +const advisorReasoningModelConfig = buildModelConfig({ + id: "model-advisor-gpt-5.2", + model: "gpt-5.2", + display_name: "GPT 5.2", + context_limit: 200_000, + model_config: { + reasoning_effort: { default: "medium", max: "xhigh" }, + }, + reasoning_efforts: ["none", "minimal", "low", "medium", "high", "xhigh"], +}); + const titleModelConfig = buildModelConfig({ id: "model-title-gpt-4o-mini", model: "gpt-4o-mini", @@ -97,6 +108,7 @@ const exploreDisabledModelConfig = buildModelConfig({ const allModelConfigs: TypesGen.ChatModelConfig[] = [ generalModelConfig, claudeSonnetModelConfig, + advisorReasoningModelConfig, titleModelConfig, exploreFallbackModelConfig, generalDisabledModelConfig, @@ -514,7 +526,7 @@ export const AdvisorSettingsVisible: Story = { }), ).toHaveValue(16384); expect( - within(section).getByRole("combobox", { name: "Advisor model" }), + within(section).getByRole("combobox", { name: "Use chat model" }), ).toBeInTheDocument(); // Changing a value exposes the Save button. @@ -537,6 +549,47 @@ export const AdvisorSettingsVisible: Story = { }, }; +export const AdvisorReasoningEffort: Story = { + args: buildArgs({ + showAdvisorSettings: true, + advisorConfigData: { + enabled: true, + max_uses_per_run: 3, + max_output_tokens: 16384, + model_config_id: advisorReasoningModelConfig.id, + reasoning_effort: "medium", + }, + }), + play: async ({ canvasElement, args }) => { + const section = await getSection(canvasElement, "Advisor"); + const modelSelector = within(section).getByRole("combobox", { + name: "GPT 5.2", + }); + expect(modelSelector).toBeInTheDocument(); + const maxUses = within(section).getByRole("spinbutton", { + name: "Max uses per turn", + }); + await userEvent.clear(maxUses); + await userEvent.type(maxUses, "5"); + + const saveButton = await within(section).findByRole("button", { + name: "Save", + }); + expect(saveButton).toBeEnabled(); + await userEvent.click(saveButton); + await waitFor(() => { + expect(args.onSaveAdvisorConfig).toHaveBeenCalledWith( + expect.objectContaining({ + max_uses_per_run: 5, + model_config_id: advisorReasoningModelConfig.id, + reasoning_effort: "medium", + }), + expect.anything(), + ); + }); + }, +}; + export const AdvisorClearButton: Story = { args: buildArgs({ showAdvisorSettings: true, @@ -544,10 +597,11 @@ export const AdvisorClearButton: Story = { enabled: true, max_uses_per_run: 3, max_output_tokens: 16384, - model_config_id: generalModelConfig.id, + model_config_id: advisorReasoningModelConfig.id, + reasoning_effort: "high", }, }), - play: async ({ canvasElement }) => { + play: async ({ canvasElement, args }) => { const section = await getSection(canvasElement, "Advisor"); const clearButton = within(section).getByRole("button", { name: "Clear" }); await userEvent.click(clearButton); @@ -562,8 +616,24 @@ export const AdvisorClearButton: Story = { }), ).toHaveValue(0); expect( - within(section).getByRole("combobox", { name: "Advisor model" }), + within(section).getByRole("combobox", { name: "Use chat model" }), ).toHaveTextContent("Use chat model"); + const saveButton = within(section).getByRole("button", { name: "Save" }); + await waitFor(() => { + expect(saveButton).toBeEnabled(); + }); + await userEvent.click(saveButton); + await waitFor(() => { + expect(args.onSaveAdvisorConfig).toHaveBeenCalledWith( + { + enabled: true, + max_uses_per_run: 0, + max_output_tokens: 0, + model_config_id: "00000000-0000-0000-0000-000000000000", + }, + expect.anything(), + ); + }); }, }; diff --git a/site/src/pages/AISettingsPage/CoderAgentsPage/CoderAgentsPageView.tsx b/site/src/pages/AISettingsPage/CoderAgentsPage/CoderAgentsPageView.tsx index 2018150861..f7267db626 100644 --- a/site/src/pages/AISettingsPage/CoderAgentsPage/CoderAgentsPageView.tsx +++ b/site/src/pages/AISettingsPage/CoderAgentsPage/CoderAgentsPageView.tsx @@ -201,6 +201,7 @@ export const CoderAgentsPageView: FC = ({ isAdvisorConfigFetching={isAdvisorConfigFetching} isAdvisorConfigLoadError={isAdvisorConfigLoadError} modelConfigs={modelConfigsData ?? []} + providerInfoByID={providerInfoByID} modelConfigsError={modelConfigsError} isLoadingModelConfigs={isLoadingModelConfigs} isFetchingModelConfigs={isFetchingModelConfigs} diff --git a/site/src/pages/AgentsPage/components/AdvisorSettings.tsx b/site/src/pages/AgentsPage/components/AdvisorSettings.tsx index 2605446abb..6395429742 100644 --- a/site/src/pages/AgentsPage/components/AdvisorSettings.tsx +++ b/site/src/pages/AgentsPage/components/AdvisorSettings.tsx @@ -7,20 +7,14 @@ import type { UpdateAdvisorConfigRequest, } from "#/api/typesGenerated"; import { Button } from "#/components/Button/Button"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "#/components/Select/Select"; import { useTemporarySavedState } from "#/components/TemporarySavedState/TemporarySavedState"; +import { ModelSelector } from "#/pages/AgentsPage/components/ChatElements/ModelSelector"; +import type { ProviderInfo } from "#/pages/AgentsPage/utils/modelOptions"; +import { pickReasoningEffort } from "#/pages/AgentsPage/utils/reasoningEffort"; import { AgentSettingLayout } from "#/pages/AISettingsPage/CoderAgentsPage/components/AgentSettingLayout"; import { cn } from "#/utils/cn"; const nilUUID = "00000000-0000-0000-0000-000000000000"; -const chatModelFallbackValue = "__use-chat-model__"; -const unavailableModelValue = "__unavailable-model__"; interface MutationCallbacks { onSuccess?: () => void; @@ -33,6 +27,7 @@ interface AdvisorSettingsProps { isAdvisorConfigFetching: boolean; isAdvisorConfigLoadError: boolean; modelConfigs: readonly ChatModelConfig[]; + providerInfoByID: ReadonlyMap; modelConfigsError: unknown; isLoadingModelConfigs: boolean; isFetchingModelConfigs: boolean; @@ -49,6 +44,7 @@ type AdvisorSettingsFormValues = { max_uses_per_run: string; max_output_tokens: string; model_config_id: string; + reasoning_effort: string; }; const isUnsetModelConfigId = (id: string): boolean => @@ -78,6 +74,7 @@ const normalizeAdvisorConfig = ( !isUnsetModelConfigId(config.model_config_id) ? config.model_config_id : "", + reasoning_effort: config?.reasoning_effort ?? "", }); const toAdvisorConfigRequest = ( @@ -89,6 +86,9 @@ const toAdvisorConfigRequest = ( model_config_id: isUnsetModelConfigId(values.model_config_id) ? nilUUID : values.model_config_id, + ...(!isUnsetModelConfigId(values.model_config_id) && values.reasoning_effort + ? { reasoning_effort: values.reasoning_effort } + : {}), }); const isNonNegativeIntegerString = (value: string): boolean => { @@ -115,15 +115,13 @@ const validateAdvisorConfig = (values: AdvisorSettingsFormValues) => { return errors; }; -const getModelDisplayName = (config: ChatModelConfig): string => - config.display_name.trim() || config.model; - export const AdvisorSettings: FC = ({ advisorConfigData, isAdvisorConfigLoading, isAdvisorConfigFetching, isAdvisorConfigLoadError, modelConfigs, + providerInfoByID, modelConfigsError, isLoadingModelConfigs, isFetchingModelConfigs, @@ -136,7 +134,27 @@ export const AdvisorSettings: FC = ({ const maxOutputTokensId = useId(); const { isSavedVisible, showSavedState } = useTemporarySavedState(); const hasLoadedAdvisorConfig = advisorConfigData !== undefined; - const enabledModelConfigs = modelConfigs.filter((config) => config.enabled); + const enabledModelOptions = modelConfigs + .filter((config) => config.enabled) + .map((config) => { + const providerInfo = providerInfoByID.get(config.ai_provider_id); + const reasoningEffort = config.model_config?.reasoning_effort; + const reasoningEfforts = config.reasoning_efforts ?? []; + return { + id: config.id, + provider: providerInfo?.provider ?? "", + providerId: config.ai_provider_id, + providerLabel: providerInfo?.displayName, + providerIcon: providerInfo?.icon, + model: config.model, + displayName: config.display_name.trim() || config.model, + contextLimit: config.context_limit, + ...(reasoningEffort?.default + ? { reasoningEffortDefault: reasoningEffort.default } + : {}), + ...(reasoningEfforts.length > 0 ? { reasoningEfforts } : {}), + }; + }); const form = useFormik({ enableReinitialize: true, @@ -157,7 +175,37 @@ export const AdvisorSettings: FC = ({ !modelConfigsError && !modelConfigs.some((config) => config.id === source.model_config_id) ) { - source = { ...source, model_config_id: "" }; + source = { ...source, model_config_id: "", reasoning_effort: "" }; + } + // A stored effort can become unselectable if the model config's + // efforts changed after the setting was saved. Submit the same + // sanitized effort the slider displays so the backend does not + // reject the stale value. An empty effort stays empty so the + // advisor keeps following the model config's default. When the + // saved model is proven unavailable (disabled), drop the effort so + // unrelated edits still save; while configs are loading, + // refetching, or errored, preserve the stored effort. + const submitOption = enabledModelOptions.find( + (option) => option.id === source.model_config_id, + ); + if (source.reasoning_effort) { + if (submitOption) { + source = { + ...source, + reasoning_effort: + pickReasoningEffort( + source.reasoning_effort, + submitOption.reasoningEfforts ?? [], + submitOption.reasoningEffortDefault, + ) ?? "", + }; + } else if ( + !isLoadingModelConfigs && + !isFetchingModelConfigs && + !modelConfigsError + ) { + source = { ...source, reasoning_effort: "" }; + } } const request = toAdvisorConfigRequest(source); onSaveAdvisorConfig(request, { @@ -177,27 +225,20 @@ export const AdvisorSettings: FC = ({ !hasLoadedAdvisorConfig; const isModelSelectDisabled = isFormDisabled || isLoadingModelConfigs || Boolean(modelConfigsError); + const selectedModelOption = enabledModelOptions.find( + (option) => option.id === form.values.model_config_id, + ); + const selectedReasoningEffort = selectedModelOption + ? pickReasoningEffort( + form.values.reasoning_effort, + selectedModelOption.reasoningEfforts ?? [], + selectedModelOption.reasoningEffortDefault, + ) + : undefined; const hasUnavailableSelectedModel = !isLoadingModelConfigs && !isUnsetModelConfigId(form.values.model_config_id) && - !enabledModelConfigs.some( - (config) => config.id === form.values.model_config_id, - ); - const selectedModelConfig = modelConfigs.find( - (config) => config.id === form.values.model_config_id, - ); - const selectedModelLabel = isUnsetModelConfigId(form.values.model_config_id) - ? "Use chat model" - : isLoadingModelConfigs - ? "Loading..." - : selectedModelConfig - ? getModelDisplayName(selectedModelConfig) - : `Unavailable model (${form.values.model_config_id})`; - const selectedModelValue = isUnsetModelConfigId(form.values.model_config_id) - ? chatModelFallbackValue - : hasUnavailableSelectedModel - ? unavailableModelValue - : form.values.model_config_id; + selectedModelOption === undefined; const canSave = hasLoadedAdvisorConfig && form.dirty && form.isValid; return ( @@ -248,42 +289,45 @@ export const AdvisorSettings: FC = ({ disabled={isFormDisabled} className="w-36" /> - + placeholder={ + hasUnavailableSelectedModel ? "Unavailable model" : "Use chat model" + } + unsetLabel="Use chat model" + emptyMessage={ + isLoadingModelConfigs + ? "Loading models..." + : "No enabled models found." + } + className="h-10 w-[22rem] max-w-full justify-between rounded-md border border-border border-solid bg-transparent px-3 text-sm" + contentClassName="min-w-[18rem]" + reasoningEffort={selectedReasoningEffort} + onReasoningEffortChange={(value) => + void form.setFieldValue("reasoning_effort", value) + } + />