using AIStudio.Provider; using Host = AIStudio.Provider.SelfHosted.Host; namespace AIStudio.Settings; public static partial class ProviderExtensions { /// /// The reasoning-related intent found in the configured additional API parameters. /// private enum ReasoningConfigurationState { /// /// No recognized reasoning parameter was found. /// NOT_CONFIGURED, /// /// A recognized reasoning parameter explicitly enables reasoning. /// EXPLICITLY_ENABLED, /// /// A recognized reasoning parameter explicitly disables reasoning. /// EXPLICITLY_DISABLED, } /// /// Get the effective reasoning indicator state for the configured provider instance. /// /// The configured provider. /// The effective reasoning indicator state. /// /// This combines static model capabilities with per-provider additional API parameters. /// For default-on models, an explicit disabling parameter hides the icon; for optional /// models, an explicit enabling parameter is required before the icon is shown. /// public static ReasoningIndicatorState GetReasoningIndicatorState(this Provider provider) { var capabilities = provider.GetModelCapabilities(); if (capabilities.Contains(Capability.ALWAYS_REASONING)) return ReasoningIndicatorState.ALWAYS_ON; var reasoningConfigurationState = GetReasoningConfigurationState(provider); if (capabilities.Contains(Capability.REASONING_BY_DEFAULT)) { return reasoningConfigurationState switch { ReasoningConfigurationState.EXPLICITLY_DISABLED => ReasoningIndicatorState.NONE, ReasoningConfigurationState.EXPLICITLY_ENABLED => ReasoningIndicatorState.CONFIGURED, _ => ReasoningIndicatorState.DEFAULT_ON, }; } if (capabilities.Contains(Capability.OPTIONAL_REASONING) && reasoningConfigurationState is ReasoningConfigurationState.EXPLICITLY_ENABLED) return ReasoningIndicatorState.CONFIGURED; return ReasoningIndicatorState.NONE; } /// /// Parse additional API parameters and dispatch them to provider-specific reasoning detectors. /// /// The configured provider whose additional API parameters should be inspected. /// The explicit reasoning configuration state, or if nothing known was found. private static ReasoningConfigurationState GetReasoningConfigurationState(Provider provider) { if (!AdditionalApiParametersParser.TryParse(provider.AdditionalJsonApiParameters, out var parameters, out _)) return ReasoningConfigurationState.NOT_CONFIGURED; return provider.UsedLLMProvider switch { LLMProviders.OPEN_AI => MergeReasoningStates( GetOpenAICompatibleReasoningState(parameters), GetReasoningEffortState(parameters)), LLMProviders.ANTHROPIC => GetAnthropicReasoningState(parameters), LLMProviders.MISTRAL or LLMProviders.PERPLEXITY => GetReasoningEffortState(parameters), LLMProviders.GOOGLE => MergeReasoningStates( GetOpenAICompatibleReasoningState(parameters), GetGoogleReasoningState(parameters)), LLMProviders.ALIBABA_CLOUD => MergeReasoningStates( GetOpenAICompatibleReasoningState(parameters), GetQwenReasoningState(parameters)), LLMProviders.OPEN_ROUTER or LLMProviders.X or LLMProviders.DEEP_SEEK or LLMProviders.GROQ or LLMProviders.FIREWORKS or LLMProviders.HUGGINGFACE or LLMProviders.HELMHOLTZ or LLMProviders.GWDG => MergeReasoningStates( GetOpenAICompatibleReasoningState(parameters), GetReasoningEffortState(parameters), GetQwenReasoningState(parameters), GetGoogleReasoningState(parameters)), LLMProviders.SELF_HOSTED => provider.Host switch { Host.OLLAMA => MergeReasoningStates( GetOpenAICompatibleReasoningState(parameters), GetOllamaReasoningState(parameters), GetQwenReasoningState(parameters)), Host.LLAMA_CPP => MergeReasoningStates( GetOpenAICompatibleReasoningState(parameters), GetLlamaCppReasoningState(parameters), GetQwenReasoningState(parameters)), Host.VLLM => MergeReasoningStates( GetOpenAICompatibleReasoningState(parameters), GetReasoningEffortState(parameters), GetVllmReasoningState(parameters), GetQwenReasoningState(parameters), GetGoogleReasoningState(parameters)), _ => MergeReasoningStates( GetOpenAICompatibleReasoningState(parameters), GetReasoningEffortState(parameters), GetQwenReasoningState(parameters), GetGoogleReasoningState(parameters)), }, _ => ReasoningConfigurationState.NOT_CONFIGURED, }; } /// /// Detect OpenAI-compatible reasoning parameters. /// /// The parsed additional API parameters. /// The detected reasoning configuration state. /// /// OpenAI-compatible providers commonly use a nested reasoning object and/or /// a top-level reasoning_effort parameter. /// private static ReasoningConfigurationState GetOpenAICompatibleReasoningState(IDictionary parameters) { var reasoningState = ReasoningConfigurationState.NOT_CONFIGURED; if (TryGetParameter(parameters, "reasoning", out var reasoning)) { reasoningState = reasoning switch { IDictionary reasoningObject when TryGetParameter(reasoningObject, "effort", out var effort) => GetLevelState(effort), IDictionary reasoningObject when TryGetParameter(reasoningObject, "summary", out var summary) => GetLevelState(summary), IDictionary => ReasoningConfigurationState.NOT_CONFIGURED, _ => GetLevelState(reasoning), }; } return MergeReasoningStates(reasoningState, GetReasoningEffortState(parameters)); } /// /// Detect a top-level reasoning_effort parameter. /// /// The parsed additional API parameters. /// The detected reasoning configuration state. private static ReasoningConfigurationState GetReasoningEffortState(IDictionary parameters) { return TryGetParameter(parameters, "reasoning_effort", out var reasoningEffort) ? GetLevelState(reasoningEffort) : ReasoningConfigurationState.NOT_CONFIGURED; } /// /// Detect Anthropic extended-thinking parameters. /// /// The parsed additional API parameters. /// The detected reasoning configuration state. private static ReasoningConfigurationState GetAnthropicReasoningState(IDictionary parameters) { if (!TryGetParameter(parameters, "thinking", out var thinking)) return ReasoningConfigurationState.NOT_CONFIGURED; return thinking switch { IDictionary thinkingObject when TryGetParameter(thinkingObject, "type", out var type) => GetAnthropicThinkingTypeState(type), _ => GetLevelState(thinking), }; } /// /// Detect Google Gemini thinking parameters across OpenAI-compatible additional parameters. /// /// The parsed additional API parameters. /// The detected reasoning configuration state. /// /// Google can expose thinking options through thinking_config, /// generation_config.thinking_config, thinking_level, and summary settings. /// Summary settings only prove that thinking is enabled when they request summaries; /// disabling summaries does not necessarily disable reasoning. /// private static ReasoningConfigurationState GetGoogleReasoningState(IDictionary parameters) { var states = new List(); if (TryGetParameter(parameters, "thinking_config", out var thinkingConfig) && thinkingConfig is IDictionary thinkingConfigObject) states.Add(GetGoogleThinkingConfigState(thinkingConfigObject)); if (TryGetParameter(parameters, "generation_config", out var generationConfig) && generationConfig is IDictionary generationConfigObject) { if (TryGetParameter(generationConfigObject, "thinking_config", out var nestedThinkingConfig) && nestedThinkingConfig is IDictionary nestedThinkingConfigObject) states.Add(GetGoogleThinkingConfigState(nestedThinkingConfigObject)); if (TryGetParameter(generationConfigObject, "thinking_summaries", out var thinkingSummaries)) states.Add(GetThinkingSummariesState(thinkingSummaries)); if (TryGetParameter(generationConfigObject, "thinking_level", out var thinkingLevel)) states.Add(GetLevelState(thinkingLevel)); } if (TryGetParameter(parameters, "thinking_summaries", out var topLevelThinkingSummaries)) states.Add(GetThinkingSummariesState(topLevelThinkingSummaries)); if (TryGetParameter(parameters, "thinking_level", out var topLevelThinkingLevel)) states.Add(GetLevelState(topLevelThinkingLevel)); return MergeReasoningStates(states); } /// /// Detect Google Gemini thinking-budget and include-thoughts settings. /// /// The parsed thinking_config object. /// The detected reasoning configuration state. private static ReasoningConfigurationState GetGoogleThinkingConfigState(IDictionary thinkingConfig) { var states = new List(); if (TryGetParameter(thinkingConfig, "thinking_budget", out var thinkingBudget) || TryGetParameter(thinkingConfig, "thinkingBudget", out thinkingBudget)) states.Add(GetBudgetState(thinkingBudget)); if (TryGetParameter(thinkingConfig, "include_thoughts", out var includeThoughts) || TryGetParameter(thinkingConfig, "includeThoughts", out includeThoughts)) states.Add(GetLevelState(includeThoughts)); return MergeReasoningStates(states); } /// /// Detect Google Gemini thinking-summary values that imply reasoning is active. /// /// The configured thinking-summary value. /// The detected reasoning configuration state. /// /// A disabled or missing summary does not prove that thinking is disabled, so only /// known enabling values are treated as explicit reasoning configuration. /// private static ReasoningConfigurationState GetThinkingSummariesState(object? value) => value switch { string text when text.Equals("auto", StringComparison.OrdinalIgnoreCase) || text.Equals("on", StringComparison.OrdinalIgnoreCase) || text.Equals("summarized", StringComparison.OrdinalIgnoreCase) => ReasoningConfigurationState.EXPLICITLY_ENABLED, true => ReasoningConfigurationState.EXPLICITLY_ENABLED, _ => ReasoningConfigurationState.NOT_CONFIGURED, }; /// /// Detect Ollama's think parameter. /// /// The parsed additional API parameters. /// The detected reasoning configuration state. private static ReasoningConfigurationState GetOllamaReasoningState(IDictionary parameters) { return TryGetParameter(parameters, "think", out var think) ? GetLevelState(think) : ReasoningConfigurationState.NOT_CONFIGURED; } /// /// Detect llama.cpp server reasoning parameters. /// /// The parsed additional API parameters. /// The detected reasoning configuration state. /// /// llama.cpp exposes runtime reasoning control through parameters such as /// reasoning, reasoning_budget, and template-specific kwargs. /// private static ReasoningConfigurationState GetLlamaCppReasoningState(IDictionary parameters) { var states = new List(); if (TryGetParameter(parameters, "reasoning", out var reasoning)) states.Add(GetLlamaCppReasoningModeState(reasoning)); if (TryGetParameter(parameters, "reasoning_budget", out var reasoningBudget)) states.Add(GetBudgetState(reasoningBudget)); if (TryGetParameter(parameters, "chat_template_kwargs", out var chatTemplateKwargs) && chatTemplateKwargs is IDictionary chatTemplateKwargsObject) states.Add(GetQwenReasoningState(chatTemplateKwargsObject)); return MergeReasoningStates(states); } /// /// Detect vLLM reasoning parameters. /// /// The parsed additional API parameters. /// The detected reasoning configuration state. /// /// vLLM supports both top-level reasoning fields and chat-template kwargs, depending /// on model family and reasoning parser configuration. /// private static ReasoningConfigurationState GetVllmReasoningState(IDictionary parameters) { var states = new List(); if (TryGetParameter(parameters, "thinking_token_budget", out var thinkingTokenBudget)) states.Add(GetBudgetState(thinkingTokenBudget)); if (TryGetParameter(parameters, "chat_template_kwargs", out var chatTemplateKwargs) && chatTemplateKwargs is IDictionary chatTemplateKwargsObject) { states.Add(GetQwenReasoningState(chatTemplateKwargsObject)); if (TryGetParameter(chatTemplateKwargsObject, "thinking", out var thinking)) states.Add(GetLevelState(thinking)); } return MergeReasoningStates(states); } /// /// Detect Qwen-style enable_thinking parameters. /// /// The parsed additional API parameters. /// The detected reasoning configuration state. /// /// Some OpenAI-compatible servers accept enable_thinking either at the /// top level or under chat_template_kwargs. /// private static ReasoningConfigurationState GetQwenReasoningState(IDictionary parameters) { var states = new List(); if (TryGetParameter(parameters, "enable_thinking", out var enableThinking)) states.Add(GetLevelState(enableThinking)); if (TryGetParameter(parameters, "chat_template_kwargs", out var chatTemplateKwargs) && chatTemplateKwargs is IDictionary chatTemplateKwargsObject && TryGetParameter(chatTemplateKwargsObject, "enable_thinking", out var nestedEnableThinking)) states.Add(GetLevelState(nestedEnableThinking)); return MergeReasoningStates(states); } /// /// Interpret Anthropic's thinking.type value. /// /// The configured Anthropic thinking type. /// The detected reasoning configuration state. private static ReasoningConfigurationState GetAnthropicThinkingTypeState(object? value) => value switch { string text when text.Equals("enabled", StringComparison.OrdinalIgnoreCase) || text.Equals("adaptive", StringComparison.OrdinalIgnoreCase) => ReasoningConfigurationState.EXPLICITLY_ENABLED, string text when IsDisabledText(text) => ReasoningConfigurationState.EXPLICITLY_DISABLED, _ => GetLevelState(value), }; /// /// Interpret llama.cpp's reasoning mode value. /// /// The configured llama.cpp reasoning mode. /// The detected reasoning configuration state. /// /// auto means the server decides from the model/template, so it is treated as /// not configured by the user rather than as explicitly enabled. /// private static ReasoningConfigurationState GetLlamaCppReasoningModeState(object? value) => value switch { string text when text.Equals("on", StringComparison.OrdinalIgnoreCase) => ReasoningConfigurationState.EXPLICITLY_ENABLED, string text when text.Equals("off", StringComparison.OrdinalIgnoreCase) => ReasoningConfigurationState.EXPLICITLY_DISABLED, string text when text.Equals("auto", StringComparison.OrdinalIgnoreCase) => ReasoningConfigurationState.NOT_CONFIGURED, _ => GetLevelState(value), }; /// /// Interpret token-budget style values used by several providers. /// /// The configured budget value. /// The detected reasoning configuration state. /// /// A zero budget disables reasoning; non-zero values, including unrestricted negative /// budgets, indicate that reasoning is available for the request. /// private static ReasoningConfigurationState GetBudgetState(object? value) => value switch { int i => i is 0 ? ReasoningConfigurationState.EXPLICITLY_DISABLED : ReasoningConfigurationState.EXPLICITLY_ENABLED, long l => l is 0 ? ReasoningConfigurationState.EXPLICITLY_DISABLED : ReasoningConfigurationState.EXPLICITLY_ENABLED, double d => Math.Abs(d) < double.Epsilon ? ReasoningConfigurationState.EXPLICITLY_DISABLED : ReasoningConfigurationState.EXPLICITLY_ENABLED, decimal m => m is 0 ? ReasoningConfigurationState.EXPLICITLY_DISABLED : ReasoningConfigurationState.EXPLICITLY_ENABLED, _ => GetLevelState(value), }; /// /// Interpret common boolean, numeric, and level-style reasoning values. /// /// The raw parsed parameter value. /// The detected reasoning configuration state. private static ReasoningConfigurationState GetLevelState(object? value) => value switch { bool booleanValue => booleanValue ? ReasoningConfigurationState.EXPLICITLY_ENABLED : ReasoningConfigurationState.EXPLICITLY_DISABLED, int i => i is 0 ? ReasoningConfigurationState.EXPLICITLY_DISABLED : ReasoningConfigurationState.EXPLICITLY_ENABLED, long l => l is 0 ? ReasoningConfigurationState.EXPLICITLY_DISABLED : ReasoningConfigurationState.EXPLICITLY_ENABLED, double d => Math.Abs(d) < double.Epsilon ? ReasoningConfigurationState.EXPLICITLY_DISABLED : ReasoningConfigurationState.EXPLICITLY_ENABLED, decimal m => m is 0 ? ReasoningConfigurationState.EXPLICITLY_DISABLED : ReasoningConfigurationState.EXPLICITLY_ENABLED, string text when IsDisabledText(text) => ReasoningConfigurationState.EXPLICITLY_DISABLED, string text when IsEnabledText(text) => ReasoningConfigurationState.EXPLICITLY_ENABLED, _ => ReasoningConfigurationState.NOT_CONFIGURED, }; /// /// Determine whether a string value is a known reasoning-enabling value. /// /// The string value to inspect. /// if the value should be treated as enabling reasoning. private static bool IsEnabledText(string text) { return text.Equals("true", StringComparison.OrdinalIgnoreCase) || text.Equals("yes", StringComparison.OrdinalIgnoreCase) || text.Equals("on", StringComparison.OrdinalIgnoreCase) || text.Equals("enabled", StringComparison.OrdinalIgnoreCase) || text.Equals("low", StringComparison.OrdinalIgnoreCase) || text.Equals("minimal", StringComparison.OrdinalIgnoreCase) || text.Equals("medium", StringComparison.OrdinalIgnoreCase) || text.Equals("high", StringComparison.OrdinalIgnoreCase) || text.Equals("max", StringComparison.OrdinalIgnoreCase); } /// /// Determine whether a string value is a known reasoning-disabling value. /// /// The string value to inspect. /// if the value should be treated as disabling reasoning. private static bool IsDisabledText(string text) { return string.IsNullOrWhiteSpace(text) || text.Equals("false", StringComparison.OrdinalIgnoreCase) || text.Equals("no", StringComparison.OrdinalIgnoreCase) || text.Equals("off", StringComparison.OrdinalIgnoreCase) || text.Equals("none", StringComparison.OrdinalIgnoreCase) || text.Equals("disabled", StringComparison.OrdinalIgnoreCase); } /// /// Merge multiple detected reasoning states into a single state. /// /// The detected states from provider-specific parameter checks. /// The merged state. /// /// Explicit disabling wins over enabling because user-provided off switches should /// suppress default-on reasoning indicators. /// private static ReasoningConfigurationState MergeReasoningStates(IEnumerable states) { var result = ReasoningConfigurationState.NOT_CONFIGURED; foreach (var state in states) { if (state is ReasoningConfigurationState.EXPLICITLY_DISABLED) return ReasoningConfigurationState.EXPLICITLY_DISABLED; if (state is ReasoningConfigurationState.EXPLICITLY_ENABLED) result = ReasoningConfigurationState.EXPLICITLY_ENABLED; } return result; } /// /// Merge multiple detected reasoning states into a single state. /// /// The detected states from provider-specific parameter checks. /// The merged state. private static ReasoningConfigurationState MergeReasoningStates(params ReasoningConfigurationState[] states) { return MergeReasoningStates(states.AsEnumerable()); } /// /// Try to read a parameter from a dictionary using case-insensitive key matching. /// /// The parsed parameter dictionary. /// The parameter name to find. /// The matched parameter value, if found. /// if a matching key was found; otherwise . private static bool TryGetParameter(IDictionary parameters, string key, out object? value) { value = null; if (parameters.Count is 0) return false; var foundKey = parameters.Keys.FirstOrDefault(k => string.Equals(k, key, StringComparison.OrdinalIgnoreCase)); if (foundKey is null) return false; value = parameters[foundKey]; return true; } }