diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index bac48dda..b6090aba 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -24,13 +24,11 @@ import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js'; import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js'; import { ChatMode, FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js'; import { WarningBox } from '../void-settings-tsx/WarningBox.js'; -import { getModelSelectionState, getModelCapabilities } from '../../../../common/modelCapabilities.js'; +import { getModelCapabilities, getIsResoningEnabledState } from '../../../../common/modelCapabilities.js'; import { AlertTriangle, Ban, ChevronRight, Dot, Pencil, X } from 'lucide-react'; import { ChatMessage, StagingSelectionItem, ToolMessage, ToolRequestApproval } from '../../../../common/chatThreadServiceTypes.js'; -import { ResolveReason, ToolCallParams, ToolName, ToolNameWithApproval } from '../../../../common/toolsServiceTypes.js'; +import { ToolCallParams, ToolName, ToolNameWithApproval } from '../../../../common/toolsServiceTypes.js'; import { JumpToFileButton, useApplyButtonHTML } from '../markdown/ApplyBlockHoverButtons.js'; -import { DiffZone } from '../../../editCodeService.js'; -import { ScrollType } from '../../../../../../../editor/common/editorCommon.js'; import { IsRunningType } from '../../../chatThreadService.js'; @@ -160,11 +158,12 @@ const ReasoningOptionSlider = ({ featureName }: { featureName: FeatureName }) => if (!modelSelection) return null const { modelName, providerName } = modelSelection - const { canToggleReasoning, reasoningBudgetSlider } = getModelCapabilities(providerName, modelName).supportsReasoning || {} + const { reasoningCapabilities } = getModelCapabilities(providerName, modelName) + const { canTurnOffReasoning, reasoningBudgetSlider } = reasoningCapabilities || {} - const { isReasoningEnabled } = getModelSelectionState(providerName, modelName, voidSettingsState.optionsOfModelSelection[providerName]?.[modelName]) - - if (canToggleReasoning && !reasoningBudgetSlider) { // if it's just a on/off toggle without a power slider (no models right now) + const modelSelectionOptions = voidSettingsState.optionsOfModelSelection[providerName]?.[modelName] + const isReasoningEnabled = getIsResoningEnabledState(providerName, modelName, modelSelectionOptions) + if (canTurnOffReasoning && !reasoningBudgetSlider) { // if it's just a on/off toggle without a power slider (no models right now) return null // unused right now // return
// {isReasoningEnabled ? 'Thinking' : 'Thinking'} @@ -183,7 +182,7 @@ const ReasoningOptionSlider = ({ featureName }: { featureName: FeatureName }) => const nSteps = 8 // only used in calculating stepSize, stepSize is what actually matters const stepSize = Math.round((max - min_) / nSteps) - const min = canToggleReasoning ? min_ - stepSize : min_ + const min = canTurnOffReasoning ? min_ - stepSize : min_ return
Thinking @@ -195,7 +194,7 @@ const ReasoningOptionSlider = ({ featureName }: { featureName: FeatureName }) => step={stepSize} value={value} onChange={(newVal) => { - const disabled = newVal === min && canToggleReasoning + const disabled = newVal === min && canTurnOffReasoning voidSettingsService.setOptionsOfModelSelection(modelSelection.providerName, modelSelection.modelName, { reasoningEnabled: !disabled, reasoningBudget: newVal }) }} /> diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-command-bar-tsx/VoidCommandBar.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-command-bar-tsx/VoidCommandBar.tsx index 36d13173..be9fb5d2 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-command-bar-tsx/VoidCommandBar.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-command-bar-tsx/VoidCommandBar.tsx @@ -185,10 +185,12 @@ const VoidCommandBar = ({ uri, editor }: { uri: URI | null, editor: ICodeEditor
- File {(currUriIdx ?? 0) + 1} of {sortedCommandBarURIs.length} + {`File ${(currUriIdx ?? 0) + 1} of ${sortedCommandBarURIs.length}`}
- Diff {(currDiffIdx ?? 0) + 1} of {sortedDiffIds?.length ?? 0} + {sortedDiffIds?.length ?? 0 === 0 ? + '(No changes)' + : `Diff ${(currDiffIdx ?? 0) + 1} of ${sortedDiffIds?.length ?? 0}`}
diff --git a/src/vs/workbench/contrib/void/common/modelCapabilities.ts b/src/vs/workbench/contrib/void/common/modelCapabilities.ts index 95de9604..88a45939 100644 --- a/src/vs/workbench/contrib/void/common/modelCapabilities.ts +++ b/src/vs/workbench/contrib/void/common/modelCapabilities.ts @@ -40,6 +40,8 @@ export const defaultModelsOfProvider = { vLLM: [ // autodetected ], openRouter: [ // https://openrouter.ai/models + 'anthropic/claude-3.7-sonnet:thinking', + 'anthropic/claude-3.7-sonnet', 'anthropic/claude-3.5-sonnet', 'deepseek/deepseek-r1', 'mistralai/codestral-2501', @@ -79,10 +81,11 @@ type ModelOptions = { supportsTools: false | 'anthropic-style' | 'openai-style'; supportsFIM: boolean; - supportsReasoning: false | { + reasoningCapabilities: false | { + readonly supportsReasoning: true; // reasoning options if supports reasoning - readonly canToggleReasoning: boolean; // whether or not the user can disable reasoning mode (false if the model only supports reasoning) - readonly canIOReasoning: boolean; // whether or not the model actually outputs reasoning + readonly canTurnOffReasoning: boolean; // whether or not the user can disable reasoning mode (false if the model only supports reasoning) + readonly canIOReasoning: boolean; // whether or not the model actually outputs reasoning (eg o1 lets us control reasoning but not output it) readonly reasoningMaxOutputTokens?: number; // overrides normal maxOutputTokens // <-- UNUSED (except anthropic) readonly reasoningBudgetSlider?: { type: 'slider'; min: number; max: number; default: number }; @@ -95,7 +98,7 @@ type ModelOptions = { type ProviderReasoningIOSettings = { // include this in payload to get reasoning - input?: { includeInPayload?: { [key: string]: any }, }; + input?: { includeInPayload?: (reasoningState: SendableReasoningInfo) => null | { [key: string]: any }, }; // nameOfFieldInDelta: reasoning output is in response.choices[0].delta[deltaReasoningField] // needsManualParse: whether we must manually parse out the tags output?: @@ -118,7 +121,7 @@ const modelOptionsDefaults: ModelOptions = { supportsSystemMessage: false, supportsTools: false, supportsFIM: false, - supportsReasoning: false, + reasoningCapabilities: false, } @@ -127,70 +130,70 @@ const openSourceModelOptions_assumingOAICompat = { supportsFIM: false, supportsSystemMessage: false, supportsTools: false, - supportsReasoning: { canToggleReasoning: false, canIOReasoning: true, openSourceThinkTags: ['', ''] }, + reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: true, openSourceThinkTags: ['', ''] }, }, 'deepseekCoderV2': { supportsFIM: false, supportsSystemMessage: false, // unstable supportsTools: false, - supportsReasoning: false, + reasoningCapabilities: false, }, 'codestral': { supportsFIM: true, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, // llama 'llama3': { supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'llama3.1': { supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'llama3.2': { supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'llama3.3': { supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, // qwen 'qwen2.5coder': { supportsFIM: true, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'qwq': { supportsFIM: false, // no FIM, yes reasoning supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: { canToggleReasoning: false, canIOReasoning: true, openSourceThinkTags: ['', ''] }, + reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: true, openSourceThinkTags: ['', ''] }, }, // FIM only 'starcoder2': { supportsFIM: true, supportsSystemMessage: false, supportsTools: false, - supportsReasoning: false, + reasoningCapabilities: false, }, 'codegemma:2b': { supportsFIM: true, supportsSystemMessage: false, supportsTools: false, - supportsReasoning: false, + reasoningCapabilities: false, }, } as const satisfies { [s: string]: Partial } @@ -233,8 +236,9 @@ const anthropicModelOptions = { supportsFIM: false, supportsSystemMessage: 'separated', supportsTools: 'anthropic-style', - supportsReasoning: { - canToggleReasoning: true, + reasoningCapabilities: { + supportsReasoning: true, + canTurnOffReasoning: true, canIOReasoning: true, reasoningMaxOutputTokens: 64_000, // can bump it to 128_000 with beta mode output-128k-2025-02-19 reasoningBudgetSlider: { type: 'slider', min: 1024, max: 32_000, default: 1024 }, // they recommend batching if max > 32_000 @@ -247,7 +251,7 @@ const anthropicModelOptions = { supportsFIM: false, supportsSystemMessage: 'separated', supportsTools: 'anthropic-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'claude-3-5-haiku-20241022': { contextWindow: 200_000, @@ -256,7 +260,7 @@ const anthropicModelOptions = { supportsFIM: false, supportsSystemMessage: 'separated', supportsTools: 'anthropic-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'claude-3-opus-20240229': { contextWindow: 200_000, @@ -265,7 +269,7 @@ const anthropicModelOptions = { supportsFIM: false, supportsSystemMessage: 'separated', supportsTools: 'anthropic-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'claude-3-sonnet-20240229': { // no point of using this, but including this for people who put it in contextWindow: 200_000, cost: { input: 3.00, output: 15.00 }, @@ -273,11 +277,21 @@ const anthropicModelOptions = { supportsFIM: false, supportsSystemMessage: 'separated', supportsTools: 'anthropic-style', - supportsReasoning: false, + reasoningCapabilities: false, } } as const satisfies { [s: string]: ModelOptions } const anthropicSettings: ProviderSettings = { + providerReasoningIOSettings: { + input: { + includeInPayload: (reasoningInfo) => { + if (reasoningInfo?.type === 'budgetEnabled') { + return { thinking: { type: 'enabled', budget_tokens: reasoningInfo.reasoningBudget } } + } + return null + } + }, + }, modelOptions: anthropicModelOptions, modelOptionsFallback: (modelName) => { let fallbackName: keyof typeof anthropicModelOptions | null = null @@ -288,7 +302,7 @@ const anthropicSettings: ProviderSettings = { if (modelName.includes('claude-3-sonnet')) fallbackName = 'claude-3-sonnet-20240229' if (fallbackName) return { modelName: fallbackName, ...anthropicModelOptions[fallbackName] } return { modelName, ...modelOptionsDefaults, maxOutputTokens: 4_096 } - } + }, } @@ -301,7 +315,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing supportsFIM: false, supportsTools: false, supportsSystemMessage: 'developer-role', - supportsReasoning: { canIOReasoning: false, canToggleReasoning: false }, // it doesn't actually output reasoning, but our logic is fine with it + reasoningCapabilities: { supportsReasoning: true, canIOReasoning: false, canTurnOffReasoning: false }, // it doesn't actually output reasoning, but our logic is fine with it }, 'o3-mini': { contextWindow: 200_000, @@ -310,7 +324,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing supportsFIM: false, supportsTools: false, supportsSystemMessage: 'developer-role', - supportsReasoning: { canIOReasoning: false, canToggleReasoning: false }, + reasoningCapabilities: { supportsReasoning: true, canIOReasoning: false, canTurnOffReasoning: false }, }, 'gpt-4o': { contextWindow: 128_000, @@ -319,7 +333,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing supportsFIM: false, supportsTools: 'openai-style', supportsSystemMessage: 'system-role', - supportsReasoning: false, + reasoningCapabilities: false, }, 'o1-mini': { contextWindow: 128_000, @@ -328,7 +342,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing supportsFIM: false, supportsTools: false, supportsSystemMessage: false, // does not support any system - supportsReasoning: { canIOReasoning: false, canToggleReasoning: false }, + reasoningCapabilities: { supportsReasoning: true, canIOReasoning: false, canTurnOffReasoning: false }, }, 'gpt-4o-mini': { contextWindow: 128_000, @@ -337,7 +351,7 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing supportsFIM: false, supportsTools: 'openai-style', supportsSystemMessage: 'system-role', // ?? - supportsReasoning: false, + reasoningCapabilities: false, }, } as const satisfies { [s: string]: ModelOptions } @@ -363,7 +377,7 @@ const xAIModelOptions = { supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, } as const satisfies { [s: string]: ModelOptions } @@ -387,7 +401,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', // we are assuming OpenAI SDK when calling gemini - supportsReasoning: false, + reasoningCapabilities: false, }, 'gemini-2.0-flash-lite-preview-02-05': { contextWindow: 1_048_576, @@ -396,7 +410,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'gemini-1.5-flash': { contextWindow: 1_048_576, @@ -405,7 +419,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'gemini-1.5-pro': { contextWindow: 2_097_152, @@ -414,7 +428,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'gemini-1.5-flash-8b': { contextWindow: 1_048_576, @@ -423,7 +437,7 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, } as const satisfies { [s: string]: ModelOptions } @@ -469,7 +483,7 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'llama-3.1-8b-instant': { contextWindow: 128_000, @@ -478,7 +492,7 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'qwen-2.5-coder-32b': { contextWindow: 128_000, @@ -487,7 +501,7 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq supportsFIM: false, // unfortunately looks like no FIM support on groq supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'qwen-qwq-32b': { // https://huggingface.co/Qwen/QwQ-32B contextWindow: 128_000, @@ -496,11 +510,21 @@ const groqModelOptions = { // https://console.groq.com/docs/models, https://groq supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: { canIOReasoning: true, canToggleReasoning: false, openSourceThinkTags: ['', ''] }, // we're using reasoning_format:parsed so really don't need to know openSourceThinkTags + reasoningCapabilities: { supportsReasoning: true, canIOReasoning: true, canTurnOffReasoning: false, openSourceThinkTags: ['', ''] }, // we're using reasoning_format:parsed so really don't need to know openSourceThinkTags }, } as const satisfies { [s: string]: ModelOptions } const groqSettings: ProviderSettings = { - providerReasoningIOSettings: { input: { includeInPayload: { reasoning_format: 'parsed' } }, output: { nameOfFieldInDelta: 'reasoning' }, }, // Must be set to either parsed or hidden when using tool calling https://console.groq.com/docs/reasoning + providerReasoningIOSettings: { + input: { + includeInPayload: (reasoningInfo) => { + if (reasoningInfo?.type === 'budgetEnabled') { + return { reasoning_format: 'parsed' } + } + return null + } + }, + output: { nameOfFieldInDelta: 'reasoning' }, + }, // Must be set to either parsed or hidden when using tool calling https://console.groq.com/docs/reasoning modelOptions: groqModelOptions, modelOptionsFallback: (modelName) => { return null } } @@ -536,6 +560,21 @@ const openRouterModelOptions_assumingOpenAICompat = { maxOutputTokens: null, cost: { input: 0.8, output: 2.4 }, }, + 'anthropic/claude-3.7-sonnet:thinking': { + contextWindow: 200_000, + maxOutputTokens: null, + cost: { input: 3.00, output: 15.00 }, + supportsFIM: false, + supportsSystemMessage: 'system-role', + supportsTools: 'openai-style', + reasoningCapabilities: { // same as anthropic, see above + supportsReasoning: true, + canTurnOffReasoning: false, + canIOReasoning: true, + reasoningMaxOutputTokens: 64_000, + reasoningBudgetSlider: { type: 'slider', min: 1024, max: 32_000, default: 1024 }, // they recommend batching if max > 32_000 + }, + }, 'anthropic/claude-3.7-sonnet': { contextWindow: 200_000, maxOutputTokens: null, @@ -543,7 +582,7 @@ const openRouterModelOptions_assumingOpenAICompat = { supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: { canIOReasoning: true, canToggleReasoning: false }, // TODO!!! false for now + reasoningCapabilities: false, // stupidly, openrouter separates thinking from non-thinking }, 'anthropic/claude-3.5-sonnet': { contextWindow: 200_000, @@ -552,7 +591,7 @@ const openRouterModelOptions_assumingOpenAICompat = { supportsFIM: false, supportsSystemMessage: 'system-role', supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'mistralai/codestral-2501': { ...openSourceModelOptions_assumingOAICompat.codestral, @@ -560,7 +599,7 @@ const openRouterModelOptions_assumingOpenAICompat = { maxOutputTokens: null, cost: { input: 0.3, output: 0.9 }, supportsTools: 'openai-style', - supportsReasoning: false, + reasoningCapabilities: false, }, 'qwen/qwen-2.5-coder-32b-instruct': { ...openSourceModelOptions_assumingOAICompat['qwen2.5coder'], @@ -581,7 +620,18 @@ const openRouterModelOptions_assumingOpenAICompat = { const openRouterSettings: ProviderSettings = { // reasoning: OAICompat + response.choices[0].delta.reasoning : payload should have {include_reasoning: true} https://openrouter.ai/announcements/reasoning-tokens-for-thinking-models providerReasoningIOSettings: { - input: { includeInPayload: { include_reasoning: true } }, + input: { + includeInPayload: (reasoningInfo) => { + if (reasoningInfo?.type === 'budgetEnabled') { + return { + reasoning: { + max_tokens: reasoningInfo.reasoningBudget + } + } + } + return null + } + }, output: { nameOfFieldInDelta: 'reasoning' }, }, modelOptions: openRouterModelOptions_assumingOpenAICompat, @@ -632,12 +682,45 @@ export const getProviderCapabilities = (providerName: ProviderName) => { return { providerReasoningIOSettings } } -// state from optionsOfModelSelection -export const getModelSelectionState = (providerName: ProviderName, modelName: string, modelSelectionOptions: ModelSelectionOptions | undefined): { isReasoningEnabled: boolean, reasoningBudget: number | undefined } => { - const { canToggleReasoning, reasoningBudgetSlider } = getModelCapabilities(providerName, modelName).supportsReasoning || {} - const defaultEnabledVal = canToggleReasoning ? true : false +export type SendableReasoningInfo = { + type: 'budgetEnabled', + isReasoningEnabled: true, + reasoningBudget: number, +} | null + + + +export const getIsResoningEnabledState = ( + providerName: ProviderName, + modelName: string, + modelSelectionOptions: ModelSelectionOptions | undefined, +) => { + const { supportsReasoning } = getModelCapabilities(providerName, modelName).reasoningCapabilities || {} + if (!supportsReasoning) return false + + const defaultEnabledVal = true // if can't toggle reasoning, then this must be true. just true as default const isReasoningEnabled = modelSelectionOptions?.reasoningEnabled ?? defaultEnabledVal - const reasoningBudget = reasoningBudgetSlider?.type === 'slider' ? modelSelectionOptions?.reasoningBudget ?? reasoningBudgetSlider?.default : undefined - return { isReasoningEnabled, reasoningBudget } + return isReasoningEnabled +} + + +// used to force reasoning state (complex) into something simple we can just read from when sending a message +export const getSendableReasoningInfo = ( + providerName: ProviderName, + modelName: string, + modelSelectionOptions: ModelSelectionOptions | undefined, +): SendableReasoningInfo => { + + const { canIOReasoning, reasoningBudgetSlider } = getModelCapabilities(providerName, modelName).reasoningCapabilities || {} + if (!canIOReasoning) return null + const isReasoningEnabled = getIsResoningEnabledState(providerName, modelName, modelSelectionOptions) + if (!isReasoningEnabled) return null + + // check for reasoning budget + const reasoningBudget = reasoningBudgetSlider?.type === 'slider' ? modelSelectionOptions?.reasoningBudget ?? reasoningBudgetSlider?.default : undefined + if (reasoningBudget) { + return { type: 'budgetEnabled', isReasoningEnabled: isReasoningEnabled, reasoningBudget: reasoningBudget } + } + return null } diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts index a34535bf..2b23c0cb 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts @@ -11,7 +11,7 @@ import { extractReasoningOnFinalMessage, extractReasoningOnTextWrapper } from '. import { LLMChatMessage, LLMFIMMessage, ModelListParams, OllamaModelResponse, OnError, OnFinalMessage, OnText } from '../../common/sendLLMMessageTypes.js'; import { defaultProviderSettings, displayInfoOfProviderName, ModelSelectionOptions, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js'; import { prepareFIMMessage, prepareMessages } from './preprocessLLMMessages.js'; -import { getModelSelectionState, getModelCapabilities, getProviderCapabilities } from '../../common/modelCapabilities.js'; +import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities } from '../../common/modelCapabilities.js'; import { InternalToolInfo, ToolName, isAToolName } from '../../common/toolsServiceTypes.js'; @@ -150,32 +150,41 @@ const _sendOpenAICompatibleFIM = ({ messages: messages_, onFinalMessage, onError -const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, aiInstructions, tools: tools_ }: SendChatParams_Internal) => { +const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, providerName, aiInstructions, tools: tools_ }: SendChatParams_Internal) => { const { modelName, - supportsReasoning, supportsSystemMessage, supportsTools, - // maxOutputTokens, right now we are ignoring this + // maxOutputTokens, + reasoningCapabilities, } = getModelCapabilities(providerName, modelName_) - const { - canIOReasoning, - openSourceThinkTags, - } = supportsReasoning || {} - - const { providerReasoningIOSettings } = getProviderCapabilities(providerName) - const { messages } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsTools, supportsAnthropicReasoningSignature: false }) + // reasoning + const { canIOReasoning, openSourceThinkTags, } = reasoningCapabilities || {} + const reasoningInfo = getSendableReasoningInfo(providerName, modelName_, modelSelectionOptions) // user's modelName_ here + const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {} + + // tools const tools = (supportsTools && ((tools_?.length ?? 0) !== 0)) ? tools_?.map(tool => toOpenAICompatibleTool(tool)) : undefined - - const includeInPayload = canIOReasoning ? providerReasoningIOSettings?.input?.includeInPayload || {} : {} - const toolsObj = tools ? { tools: tools, tool_choice: 'auto', parallel_tool_calls: false, } as const : {} - const openai: OpenAI = newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload }) - const options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelName, messages: messages, stream: true, ...toolsObj, } + // max tokens + // const maxTokens = reasoningInfo?.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningMaxOutputTokens : maxOutputTokens + + // instance + const { messages } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsTools, supportsAnthropicReasoningSignature: false }) + const openai: OpenAI = newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload }) + const options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { + model: modelName, + messages: messages, + stream: true, + // max_completion_tokens: maxTokens, + ...toolsObj, + } + + // open source models - manually parse think tokens const { needsManualParse: needsManualReasoningParse, nameOfFieldInDelta: nameOfReasoningFieldInDelta } = providerReasoningIOSettings?.output ?? {} const manuallyParseReasoning = needsManualReasoningParse && canIOReasoning && openSourceThinkTags if (manuallyParseReasoning) { @@ -300,31 +309,32 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM supportsSystemMessage, supportsTools, maxOutputTokens, - supportsReasoning, + reasoningCapabilities, } = getModelCapabilities(providerName, modelName_) - const { - isReasoningEnabled, - reasoningBudget, - } = getModelSelectionState(providerName, modelName_, modelSelectionOptions) // user's modelName_ here - - const { messages, separateSystemMessageStr } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsTools, supportsAnthropicReasoningSignature: true }) const thisConfig = settingsOfProvider.anthropic - const anthropic = new Anthropic({ apiKey: thisConfig.apiKey, dangerouslyAllowBrowser: true }); + const { providerReasoningIOSettings } = getProviderCapabilities(providerName) + + // reasoning + const reasoningInfo = getSendableReasoningInfo(providerName, modelName_, modelSelectionOptions) // user's modelName_ here + const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {} + + // tools const tools = ((tools_?.length ?? 0) !== 0) ? tools_?.map(tool => toAnthropicTool(tool)) : undefined - - const toolsObj: Partial = tools ? { tools: tools, tool_choice: { type: 'auto', disable_parallel_tool_use: true } // one tool at a time } : {} + // anthropic-specific - max tokens + const maxTokens = reasoningInfo?.isReasoningEnabled && reasoningCapabilities ? reasoningCapabilities.reasoningMaxOutputTokens : maxOutputTokens - const enableThinking = supportsReasoning && isReasoningEnabled && reasoningBudget - const maxTokens = enableThinking ? supportsReasoning.reasoningMaxOutputTokens : maxOutputTokens - const thinkingObj: Partial = enableThinking ? { - thinking: { type: 'enabled', budget_tokens: reasoningBudget } // thinking enabled - } : {} + // instance + const { messages, separateSystemMessageStr } = prepareMessages({ messages: messages_, aiInstructions, supportsSystemMessage, supportsTools, supportsAnthropicReasoningSignature: true }) + const anthropic = new Anthropic({ + apiKey: thisConfig.apiKey, + dangerouslyAllowBrowser: true + }); const stream = anthropic.messages.stream({ system: separateSystemMessageStr, @@ -332,7 +342,7 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM model: modelName, max_tokens: maxTokens ?? 4_096, // anthropic requires this ...toolsObj, - ...thinkingObj, + ...includeInPayload, }) // when receive text