Merge branch 'staging' into vision-cleanup
| @@ -2048,16 +2048,20 @@ | |||
| 2048 | </span> | 2048 | </span> |
| 2049 | </div> | 2049 | </div> |
| 2050 | </div> | 2050 | </div> |
| 2051 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai"> | 2051 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,openrouter"> |
| 2052 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models."> | 2052 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models."> |
| 2053 | <label for="openai_reasoning_effort"> | 2053 | <label for="openai_reasoning_effort"> |
| 2054 | <span data-i18n="Reasoning Effort">Reasoning Effort</span> | 2054 | <span data-i18n="Reasoning Effort">Reasoning Effort</span> |
| 2055 | <i data-source="claude" class="opacity50p fa-solid fa-circle-info" title="Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%), but minimum 1024 tokens."></i> | 2055 | <i data-source="openai,custom,xai,openrouter" class="opacity50p fa-solid fa-circle-info" title="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level."></i> |
| 2056 | <i data-source="claude,makersuite" class="opacity50p fa-solid fa-circle-info" title="Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent."></i> | ||
| 2056 | </label> | 2057 | </label> |
| 2057 | <select id="openai_reasoning_effort"> | 2058 | <select id="openai_reasoning_effort"> |
| 2059 | <option data-i18n="openai_reasoning_effort_auto" value="auto">Auto</option> | ||
| 2060 | <option data-i18n="openai_reasoning_effort_minimum" value="min">Mininum</option> | ||
| 2058 | <option data-i18n="openai_reasoning_effort_low" value="low">Low</option> | 2061 | <option data-i18n="openai_reasoning_effort_low" value="low">Low</option> |
| 2059 | <option data-i18n="openai_reasoning_effort_medium" value="medium">Medium</option> | 2062 | <option data-i18n="openai_reasoning_effort_medium" value="medium">Medium</option> |
| 2060 | <option data-i18n="openai_reasoning_effort_high" value="high">High</option> | 2063 | <option data-i18n="openai_reasoning_effort_high" value="high">High</option> |
| 2064 | <option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option> | ||
| 2061 | </select> | 2065 | </select> |
| 2062 | </div> | 2066 | </div> |
| 2063 | </div> | 2067 | </div> |
| @@ -6955,15 +6955,23 @@ export async function saveChat({ chatName, withMetadata, mesId, force = false } | |||
| 6955 | throw new Error(result.statusText); | 6955 | throw new Error(result.statusText); |
| 6956 | } | 6956 | } |
| 6957 | 6957 | ||
| 6958 | const forceSaveConfirmed = await Popup.show.confirm( | 6958 | const popupResult = await Popup.show.input( |
| 6959 | t`ERROR: Chat integrity check failed.`, | 6959 | t`ERROR: Chat integrity check failed while saving the file.`, |
| 6960 | t`Continuing the operation may result in data loss. Would you like to overwrite the chat file anyway? Pressing "NO" will cancel the save operation.`, | 6960 | t`<p>After you click OK, the page will be reloaded to prevent data corruption.</p> |
| 6961 | { okButton: t`Yes, overwrite`, cancelButton: t`No, cancel` }, | 6961 | <p>To confirm an overwrite (and potentially <b>LOSE YOUR DATA</b>), enter <code>OVERWRITE</code> (in all caps) in the box below before clicking OK.</p>`, |
| 6962 | ) === POPUP_RESULT.AFFIRMATIVE; | 6962 | '', |
| 6963 | { okButton: 'OK', cancelButton: false }, | ||
| 6964 | ); | ||
| 6963 | 6965 | ||
| 6964 | if (forceSaveConfirmed) { | 6966 | const forceSaveConfirmed = popupResult === 'OVERWRITE'; |
| 6965 | await saveChat({ chatName, withMetadata, mesId, force: true }); | 6967 | |
| 6968 | if (!forceSaveConfirmed) { | ||
| 6969 | console.warn('Chat integrity check failed, and user did not confirm the overwrite. Reloading the page.'); | ||
| 6970 | window.location.reload(); | ||
| 6971 | return; | ||
| 6966 | } | 6972 | } |
| 6973 | |||
| 6974 | await saveChat({ chatName, withMetadata, mesId, force: true }); | ||
| 6967 | } catch (error) { | 6975 | } catch (error) { |
| 6968 | console.error(error); | 6976 | console.error(error); |
| 6969 | toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Chat could not be saved`); | 6977 | toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Chat could not be saved`); |
| @@ -216,6 +216,15 @@ const openrouter_middleout_types = { | |||
| 216 | OFF: 'off', | 216 | OFF: 'off', |
| 217 | }; | 217 | }; |
| 218 | 218 | ||
| 219 | export const reasoning_effort_types = { | ||
| 220 | auto: 'auto', | ||
| 221 | low: 'low', | ||
| 222 | medium: 'medium', | ||
| 223 | high: 'high', | ||
| 224 | min: 'min', | ||
| 225 | max: 'max', | ||
| 226 | }; | ||
| 227 | |||
| 219 | const sensitiveFields = [ | 228 | const sensitiveFields = [ |
| 220 | 'reverse_proxy', | 229 | 'reverse_proxy', |
| 221 | 'proxy_password', | 230 | 'proxy_password', |
| @@ -382,7 +391,7 @@ const default_settings = { | |||
| 382 | continue_postfix: continue_postfix_types.SPACE, | 391 | continue_postfix: continue_postfix_types.SPACE, |
| 383 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, | 392 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, |
| 384 | show_thoughts: true, | 393 | show_thoughts: true, |
| 385 | reasoning_effort: 'medium', | 394 | reasoning_effort: reasoning_effort_types.auto, |
| 386 | enable_web_search: false, | 395 | enable_web_search: false, |
| 387 | request_images: false, | 396 | request_images: false, |
| 388 | seed: -1, | 397 | seed: -1, |
| @@ -463,7 +472,7 @@ const oai_settings = { | |||
| 463 | continue_postfix: continue_postfix_types.SPACE, | 472 | continue_postfix: continue_postfix_types.SPACE, |
| 464 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, | 473 | custom_prompt_post_processing: custom_prompt_post_processing_types.NONE, |
| 465 | show_thoughts: true, | 474 | show_thoughts: true, |
| 466 | reasoning_effort: 'medium', | 475 | reasoning_effort: reasoning_effort_types.auto, |
| 467 | enable_web_search: false, | 476 | enable_web_search: false, |
| 468 | request_images: false, | 477 | request_images: false, |
| 469 | seed: -1, | 478 | seed: -1, |
| @@ -1937,6 +1946,31 @@ async function sendAltScaleRequest(messages, logit_bias, signal, type) { | |||
| 1937 | return data.output; | 1946 | return data.output; |
| 1938 | } | 1947 | } |
| 1939 | 1948 | ||
| 1949 | function getReasoningEffort() { | ||
| 1950 | // These sources expect the effort as string. | ||
| 1951 | const reasoningEffortSources = [ | ||
| 1952 | chat_completion_sources.OPENAI, | ||
| 1953 | chat_completion_sources.CUSTOM, | ||
| 1954 | chat_completion_sources.XAI, | ||
| 1955 | chat_completion_sources.OPENROUTER, | ||
| 1956 | ]; | ||
| 1957 | |||
| 1958 | if (!reasoningEffortSources.includes(oai_settings.chat_completion_source)) { | ||
| 1959 | return oai_settings.reasoning_effort; | ||
| 1960 | } | ||
| 1961 | |||
| 1962 | switch (oai_settings.reasoning_effort) { | ||
| 1963 | case reasoning_effort_types.auto: | ||
| 1964 | return undefined; | ||
| 1965 | case reasoning_effort_types.min: | ||
| 1966 | return reasoning_effort_types.low; | ||
| 1967 | case reasoning_effort_types.max: | ||
| 1968 | return reasoning_effort_types.high; | ||
| 1969 | default: | ||
| 1970 | return oai_settings.reasoning_effort; | ||
| 1971 | } | ||
| 1972 | } | ||
| 1973 | |||
| 1940 | /** | 1974 | /** |
| 1941 | * Send a chat completion request to backend | 1975 | * Send a chat completion request to backend |
| 1942 | * @param {string} type (impersonate, quiet, continue, etc) | 1976 | * @param {string} type (impersonate, quiet, continue, etc) |
| @@ -2023,7 +2057,7 @@ async function sendOpenAIRequest(type, messages, signal) { | |||
| 2023 | 'char_name': name2, | 2057 | 'char_name': name2, |
| 2024 | 'group_names': getGroupNames(), | 2058 | 'group_names': getGroupNames(), |
| 2025 | 'include_reasoning': Boolean(oai_settings.show_thoughts), | 2059 | 'include_reasoning': Boolean(oai_settings.show_thoughts), |
| 2026 | 'reasoning_effort': String(oai_settings.reasoning_effort), | 2060 | 'reasoning_effort': getReasoningEffort(), |
| 2027 | 'enable_web_search': Boolean(oai_settings.enable_web_search), | 2061 | 'enable_web_search': Boolean(oai_settings.enable_web_search), |
| 2028 | 'request_images': Boolean(oai_settings.request_images), | 2062 | 'request_images': Boolean(oai_settings.request_images), |
| 2029 | 'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing, | 2063 | 'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing, |
| @@ -28,7 +28,8 @@ import { | |||
| 28 | cachingAtDepthForOpenRouterClaude, | 28 | cachingAtDepthForOpenRouterClaude, |
| 29 | cachingAtDepthForClaude, | 29 | cachingAtDepthForClaude, |
| 30 | getPromptNames, | 30 | getPromptNames, |
| 31 | calculateBudgetTokens, | 31 | calculateClaudeBudgetTokens, |
| 32 | calculateGoogleBudgetTokens, | ||
| 32 | } from '../../prompt-converters.js'; | 33 | } from '../../prompt-converters.js'; |
| 33 | 34 | ||
| 34 | import { readSecret, SECRET_KEYS } from '../secrets.js'; | 35 | import { readSecret, SECRET_KEYS } from '../secrets.js'; |
| @@ -202,7 +203,7 @@ async function sendClaudeRequest(request, response) { | |||
| 202 | // No prefill when thinking | 203 | // No prefill when thinking |
| 203 | voidPrefill = true; | 204 | voidPrefill = true; |
| 204 | const reasoningEffort = request.body.reasoning_effort; | 205 | const reasoningEffort = request.body.reasoning_effort; |
| 205 | const budgetTokens = calculateBudgetTokens(requestBody.max_tokens, reasoningEffort, requestBody.stream); | 206 | const budgetTokens = calculateClaudeBudgetTokens(requestBody.max_tokens, reasoningEffort, requestBody.stream); |
| 206 | const minThinkTokens = 1024; | 207 | const minThinkTokens = 1024; |
| 207 | if (requestBody.max_tokens <= minThinkTokens) { | 208 | if (requestBody.max_tokens <= minThinkTokens) { |
| 208 | const newValue = requestBody.max_tokens + minThinkTokens; | 209 | const newValue = requestBody.max_tokens + minThinkTokens; |
| @@ -340,6 +341,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 340 | const stream = Boolean(request.body.stream); | 341 | const stream = Boolean(request.body.stream); |
| 341 | const enableWebSearch = Boolean(request.body.enable_web_search); | 342 | const enableWebSearch = Boolean(request.body.enable_web_search); |
| 342 | const requestImages = Boolean(request.body.request_images); | 343 | const requestImages = Boolean(request.body.request_images); |
| 344 | const reasoningEffort = String(request.body.reasoning_effort); | ||
| 343 | const isThinking = model.includes('thinking'); | 345 | const isThinking = model.includes('thinking'); |
| 344 | const isGemma = model.includes('gemma'); | 346 | const isGemma = model.includes('gemma'); |
| 345 | 347 | ||
| @@ -412,13 +414,26 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 412 | tools.push({ function_declarations: functionDeclarations }); | 414 | tools.push({ function_declarations: functionDeclarations }); |
| 413 | } | 415 | } |
| 414 | 416 | ||
| 417 | // One more models list to maintain, yay | ||
| 418 | const thinkingBudgetModels = [ | ||
| 419 | 'gemini-2.5-flash-preview-04-17', | ||
| 420 | ]; | ||
| 421 | |||
| 422 | if (thinkingBudgetModels.includes(model)) { | ||
| 423 | const thinkingBudget = calculateGoogleBudgetTokens(generationConfig.maxOutputTokens, reasoningEffort); | ||
| 424 | |||
| 425 | if (Number.isInteger(thinkingBudget)) { | ||
| 426 | generationConfig.thinkingConfig = { thinkingBudget: thinkingBudget }; | ||
| 427 | } | ||
| 428 | } | ||
| 429 | |||
| 415 | let body = { | 430 | let body = { |
| 416 | contents: prompt.contents, | 431 | contents: prompt.contents, |
| 417 | safetySettings: safetySettings, | 432 | safetySettings: safetySettings, |
| 418 | generationConfig: generationConfig, | 433 | generationConfig: generationConfig, |
| 419 | }; | 434 | }; |
| 420 | 435 | ||
| 421 | if (useSystemPrompt) { | 436 | if (useSystemPrompt && Array.isArray(prompt.system_instruction.parts) && prompt.system_instruction.parts.length) { |
| 422 | body.systemInstruction = prompt.system_instruction; | 437 | body.systemInstruction = prompt.system_instruction; |
| 423 | } | 438 | } |
| 424 | 439 | ||
| @@ -868,7 +883,7 @@ async function sendXaiRequest(request, response) { | |||
| 868 | bodyParams['stop'] = request.body.stop; | 883 | bodyParams['stop'] = request.body.stop; |
| 869 | } | 884 | } |
| 870 | 885 | ||
| 871 | if (['grok-3-mini-beta', 'grok-3-mini-fast-beta'].includes(request.body.model)) { | 886 | if (request.body.reasoning_effort && ['grok-3-mini-beta', 'grok-3-mini-fast-beta'].includes(request.body.model)) { |
| 872 | bodyParams['reasoning_effort'] = request.body.reasoning_effort === 'high' ? 'high' : 'low'; | 887 | bodyParams['reasoning_effort'] = request.body.reasoning_effort === 'high' ? 'high' : 'low'; |
| 873 | } | 888 | } |
| 874 | 889 | ||
| @@ -1210,6 +1225,10 @@ router.post('/generate', function (request, response) { | |||
| 1210 | bodyParams['route'] = 'fallback'; | 1225 | bodyParams['route'] = 'fallback'; |
| 1211 | } | 1226 | } |
| 1212 | 1227 | ||
| 1228 | if (request.body.reasoning_effort) { | ||
| 1229 | bodyParams['reasoning'] = { effort: request.body.reasoning_effort }; | ||
| 1230 | } | ||
| 1231 | |||
| 1213 | let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number'); | 1232 | let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1, 'number'); |
| 1214 | if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) { | 1233 | if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) { |
| 1215 | cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth); | 1234 | cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth); |
| @@ -1258,7 +1277,7 @@ router.post('/generate', function (request, response) { | |||
| 1258 | } | 1277 | } |
| 1259 | 1278 | ||
| 1260 | // A few of OpenAIs reasoning models support reasoning effort | 1279 | // A few of OpenAIs reasoning models support reasoning effort |
| 1261 | if ([CHAT_COMPLETION_SOURCES.CUSTOM, CHAT_COMPLETION_SOURCES.OPENAI].includes(request.body.chat_completion_source)) { | 1280 | if (request.body.reasoning_effort && [CHAT_COMPLETION_SOURCES.CUSTOM, CHAT_COMPLETION_SOURCES.OPENAI].includes(request.body.chat_completion_source)) { |
| 1262 | if (['o1', 'o3-mini', 'o3-mini-2025-01-31', 'o4-mini', 'o4-mini-2025-04-16', 'o3', 'o3-2025-04-16'].includes(request.body.model)) { | 1281 | if (['o1', 'o3-mini', 'o3-mini-2025-01-31', 'o4-mini', 'o4-mini-2025-04-16', 'o3', 'o3-2025-04-16'].includes(request.body.model)) { |
| 1263 | bodyParams['reasoning_effort'] = request.body.reasoning_effort; | 1282 | bodyParams['reasoning_effort'] = request.body.reasoning_effort; |
| 1264 | } | 1283 | } |
| @@ -3,6 +3,15 @@ import { getConfigValue, tryParse } from './util.js'; | |||
| 3 | 3 | ||
| 4 | const PROMPT_PLACEHOLDER = getConfigValue('promptPlaceholder', 'Let\'s get started.'); | 4 | const PROMPT_PLACEHOLDER = getConfigValue('promptPlaceholder', 'Let\'s get started.'); |
| 5 | 5 | ||
| 6 | const REASONING_EFFORT = { | ||
| 7 | auto: 'auto', | ||
| 8 | low: 'low', | ||
| 9 | medium: 'medium', | ||
| 10 | high: 'high', | ||
| 11 | min: 'min', | ||
| 12 | max: 'max', | ||
| 13 | }; | ||
| 14 | |||
| 6 | /** | 15 | /** |
| 7 | * @typedef {object} PromptNames | 16 | * @typedef {object} PromptNames |
| 8 | * @property {string} charName Character name | 17 | * @property {string} charName Character name |
| @@ -356,7 +365,7 @@ export function convertCohereMessages(messages, names) { | |||
| 356 | * @param {string} model Model name | 365 | * @param {string} model Model name |
| 357 | * @param {boolean} useSysPrompt Use system prompt | 366 | * @param {boolean} useSysPrompt Use system prompt |
| 358 | * @param {PromptNames} names Prompt names | 367 | * @param {PromptNames} names Prompt names |
| 359 | * @returns {{contents: *[], system_instruction: {parts: {text: string}}}} Prompt for Google MakerSuite models | 368 | * @returns {{contents: *[], system_instruction: {parts: {text: string}[]}}} Prompt for Google MakerSuite models |
| 360 | */ | 369 | */ |
| 361 | export function convertGooglePrompt(messages, model, useSysPrompt, names) { | 370 | export function convertGooglePrompt(messages, model, useSysPrompt, names) { |
| 362 | const visionSupportedModelPrefix = [ | 371 | const visionSupportedModelPrefix = [ |
| @@ -369,8 +378,8 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) { | |||
| 369 | ]; | 378 | ]; |
| 370 | 379 | ||
| 371 | const isMultimodal = visionSupportedModelPrefix.some(prefix => model.startsWith(prefix)); | 380 | const isMultimodal = visionSupportedModelPrefix.some(prefix => model.startsWith(prefix)); |
| 381 | const sysPrompt = []; | ||
| 372 | 382 | ||
| 373 | let sys_prompt = ''; | ||
| 374 | if (useSysPrompt) { | 383 | if (useSysPrompt) { |
| 375 | while (messages.length > 1 && messages[0].role === 'system') { | 384 | while (messages.length > 1 && messages[0].role === 'system') { |
| 376 | // Append example names if not already done by the frontend (e.g. for group chats). | 385 | // Append example names if not already done by the frontend (e.g. for group chats). |
| @@ -384,12 +393,12 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) { | |||
| 384 | messages[0].content = `${names.charName}: ${messages[0].content}`; | 393 | messages[0].content = `${names.charName}: ${messages[0].content}`; |
| 385 | } | 394 | } |
| 386 | } | 395 | } |
| 387 | sys_prompt += `${messages[0].content}\n\n`; | 396 | sysPrompt.push(messages[0].content); |
| 388 | messages.shift(); | 397 | messages.shift(); |
| 389 | } | 398 | } |
| 390 | } | 399 | } |
| 391 | 400 | ||
| 392 | const system_instruction = { parts: [{ text: sys_prompt.trim() }] }; | 401 | const system_instruction = { parts: sysPrompt.map(text => ({ text })) }; |
| 393 | const toolNameMap = {}; | 402 | const toolNameMap = {}; |
| 394 | 403 | ||
| 395 | const contents = []; | 404 | const contents = []; |
| @@ -919,25 +928,32 @@ export function cachingAtDepthForOpenRouterClaude(messages, cachingAtDepth) { | |||
| 919 | } | 928 | } |
| 920 | 929 | ||
| 921 | /** | 930 | /** |
| 922 | * Calculate the budget tokens for a given reasoning effort. | 931 | * Calculate the Claude budget tokens for a given reasoning effort. |
| 923 | * @param {number} maxTokens Maximum tokens | 932 | * @param {number} maxTokens Maximum tokens |
| 924 | * @param {string} reasoningEffort Reasoning effort | 933 | * @param {string} reasoningEffort Reasoning effort |
| 925 | * @param {boolean} stream If streaming is enabled | 934 | * @param {boolean} stream If streaming is enabled |
| 926 | * @returns {number} Budget tokens | 935 | * @returns {number} Budget tokens |
| 927 | */ | 936 | */ |
| 928 | export function calculateBudgetTokens(maxTokens, reasoningEffort, stream) { | 937 | export function calculateClaudeBudgetTokens(maxTokens, reasoningEffort, stream) { |
| 929 | let budgetTokens = 0; | 938 | let budgetTokens = 0; |
| 930 | 939 | ||
| 931 | switch (reasoningEffort) { | 940 | switch (reasoningEffort) { |
| 932 | case 'low': | 941 | case REASONING_EFFORT.min: |
| 942 | budgetTokens = 1024; | ||
| 943 | break; | ||
| 944 | case REASONING_EFFORT.low: | ||
| 933 | budgetTokens = Math.floor(maxTokens * 0.1); | 945 | budgetTokens = Math.floor(maxTokens * 0.1); |
| 934 | break; | 946 | break; |
| 935 | case 'medium': | 947 | case REASONING_EFFORT.auto: |
| 948 | case REASONING_EFFORT.medium: | ||
| 936 | budgetTokens = Math.floor(maxTokens * 0.25); | 949 | budgetTokens = Math.floor(maxTokens * 0.25); |
| 937 | break; | 950 | break; |
| 938 | case 'high': | 951 | case REASONING_EFFORT.high: |
| 939 | budgetTokens = Math.floor(maxTokens * 0.5); | 952 | budgetTokens = Math.floor(maxTokens * 0.5); |
| 940 | break; | 953 | break; |
| 954 | case REASONING_EFFORT.max: | ||
| 955 | budgetTokens = Math.floor(maxTokens * 0.95); | ||
| 956 | break; | ||
| 941 | } | 957 | } |
| 942 | 958 | ||
| 943 | budgetTokens = Math.max(budgetTokens, 1024); | 959 | budgetTokens = Math.max(budgetTokens, 1024); |
| @@ -948,3 +964,37 @@ export function calculateBudgetTokens(maxTokens, reasoningEffort, stream) { | |||
| 948 | 964 | ||
| 949 | return budgetTokens; | 965 | return budgetTokens; |
| 950 | } | 966 | } |
| 967 | |||
| 968 | /** | ||
| 969 | * Calculate the Google budget tokens for a given reasoning effort. | ||
| 970 | * @param {number} maxTokens Maximum tokens | ||
| 971 | * @param {string} reasoningEffort Reasoning effort | ||
| 972 | * @returns {number?} Budget tokens | ||
| 973 | */ | ||
| 974 | export function calculateGoogleBudgetTokens(maxTokens, reasoningEffort) { | ||
| 975 | let budgetTokens = 0; | ||
| 976 | |||
| 977 | switch (reasoningEffort) { | ||
| 978 | case REASONING_EFFORT.auto: | ||
| 979 | return null; | ||
| 980 | case REASONING_EFFORT.min: | ||
| 981 | budgetTokens = 0; | ||
| 982 | break; | ||
| 983 | case REASONING_EFFORT.low: | ||
| 984 | budgetTokens = Math.floor(maxTokens * 0.1); | ||
| 985 | break; | ||
| 986 | case REASONING_EFFORT.medium: | ||
| 987 | budgetTokens = Math.floor(maxTokens * 0.25); | ||
| 988 | break; | ||
| 989 | case REASONING_EFFORT.high: | ||
| 990 | budgetTokens = Math.floor(maxTokens * 0.5); | ||
| 991 | break; | ||
| 992 | case REASONING_EFFORT.max: | ||
| 993 | budgetTokens = maxTokens; | ||
| 994 | break; | ||
| 995 | } | ||
| 996 | |||
| 997 | budgetTokens = Math.min(budgetTokens, 24576); | ||
| 998 | |||
| 999 | return budgetTokens; | ||
| 1000 | } | ||