OpenRouter: Support reasoning blocks
| @@ -1977,7 +1977,7 @@ | |||
| 1977 | </span> | 1977 | </span> |
| 1978 | </div> | 1978 | </div> |
| 1979 | </div> | 1979 | </div> |
| 1980 | <div class="range-block" data-source="makersuite,deepseek"> | 1980 | <div class="range-block" data-source="makersuite,deepseek,openrouter"> |
| 1981 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> | 1981 | <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand"> |
| 1982 | <input id="openai_show_thoughts" type="checkbox" /> | 1982 | <input id="openai_show_thoughts" type="checkbox" /> |
| 1983 | <span> | 1983 | <span> |
| @@ -170,7 +170,7 @@ import { | |||
| 170 | isElementInViewport, | 170 | isElementInViewport, |
| 171 | copyText, | 171 | copyText, |
| 172 | } from './scripts/utils.js'; | 172 | } from './scripts/utils.js'; |
| 173 | import { debounce_timeout, THINK_BREAK } from './scripts/constants.js'; | 173 | import { debounce_timeout } from './scripts/constants.js'; |
| 174 | 174 | ||
| 175 | import { doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js'; | 175 | import { doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js'; |
| 176 | import { COMMENT_NAME_DEFAULT, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, stopScriptExecution } from './scripts/slash-commands.js'; | 176 | import { COMMENT_NAME_DEFAULT, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, stopScriptExecution } from './scripts/slash-commands.js'; |
| @@ -5700,10 +5700,6 @@ function getTextContextFromData(data) { | |||
| 5700 | function extractMessageFromData(data){ | 5700 | function extractMessageFromData(data){ |
| 5701 | const content = String(getTextContextFromData(data) ?? ''); | 5701 | const content = String(getTextContextFromData(data) ?? ''); |
| 5702 | 5702 | ||
| 5703 | if (content.includes(THINK_BREAK)) { | ||
| 5704 | return content.split(THINK_BREAK)[1]; | ||
| 5705 | } | ||
| 5706 | |||
| 5707 | return content; | 5703 | return content; |
| 5708 | } | 5704 | } |
| 5709 | 5705 | ||
| @@ -5713,14 +5709,15 @@ function extractMessageFromData(data){ | |||
| 5713 | * @returns {string} Extracted reasoning | 5709 | * @returns {string} Extracted reasoning |
| 5714 | */ | 5710 | */ |
| 5715 | function extractReasoningFromData(data) { | 5711 | function extractReasoningFromData(data) { |
| 5716 | const content = String(getTextContextFromData(data) ?? ''); | 5712 | if (main_api === 'openai' && oai_settings.show_thoughts) { |
| 5717 | 5713 | switch (oai_settings.chat_completion_source) { | |
| 5718 | if (content.includes(THINK_BREAK)) { | 5714 | case chat_completion_sources.DEEPSEEK: |
| 5719 | return content.split(THINK_BREAK)[0]; | 5715 | return data?.choices?.[0]?.message?.reasoning_content ?? ''; |
| 5720 | } | 5716 | case chat_completion_sources.OPENROUTER: |
| 5721 | 5717 | return data?.choices?.[0]?.message?.reasoning ?? ''; | |
| 5722 | if (main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK && oai_settings.show_thoughts) { | 5718 | case chat_completion_sources.MAKERSUITE: |
| 5723 | return data?.choices?.[0]?.message?.reasoning_content ?? ''; | 5719 | return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? ''; |
| 5720 | } | ||
| 5724 | } | 5721 | } |
| 5725 | 5722 | ||
| 5726 | return ''; | 5723 | return ''; |
| @@ -14,8 +14,3 @@ export const debounce_timeout = { | |||
| 14 | /** [5 sec] For delayed tasks, like auto-saving or completing batch operations that need a significant pause. */ | 14 | /** [5 sec] For delayed tasks, like auto-saving or completing batch operations that need a significant pause. */ |
| 15 | extended: 5000, | 15 | extended: 5000, |
| 16 | }; | 16 | }; |
| 17 | |||
| 18 | /** | ||
| 19 | * Custom boundary for splitting the text between the model's reasoning and the actual response. | ||
| 20 | */ | ||
| 21 | export const THINK_BREAK = '##�THINK_BREAK�##'; | ||
| @@ -2161,6 +2161,11 @@ function getStreamingReply(data, state) { | |||
| 2161 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || ''); | 2161 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || ''); |
| 2162 | } | 2162 | } |
| 2163 | return data.choices?.[0]?.delta?.content || ''; | 2163 | return data.choices?.[0]?.delta?.content || ''; |
| 2164 | } else if (oai_settings.chat_completion_source === chat_completion_sources.OPENROUTER) { | ||
| 2165 | if (oai_settings.show_thoughts) { | ||
| 2166 | state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || ''); | ||
| 2167 | } | ||
| 2168 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; | ||
| 2164 | } else { | 2169 | } else { |
| 2165 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; | 2170 | return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? ''; |
| 2166 | } | 2171 | } |
| @@ -235,6 +235,21 @@ async function* parseStreamData(json) { | |||
| 235 | } | 235 | } |
| 236 | return; | 236 | return; |
| 237 | } | 237 | } |
| 238 | else if (typeof json.choices[0].delta.reasoning === 'string' && json.choices[0].delta.reasoning.length > 0) { | ||
| 239 | for (let j = 0; j < json.choices[0].delta.reasoning.length; j++) { | ||
| 240 | const str = json.choices[0].delta.reasoning[j]; | ||
| 241 | const isLastSymbol = j === json.choices[0].delta.reasoning.length - 1; | ||
| 242 | const choiceClone = structuredClone(json.choices[0]); | ||
| 243 | choiceClone.delta.reasoning = str; | ||
| 244 | choiceClone.delta.content = isLastSymbol ? choiceClone.delta.content : ''; | ||
| 245 | const choices = [choiceClone]; | ||
| 246 | yield { | ||
| 247 | data: { ...json, choices }, | ||
| 248 | chunk: str, | ||
| 249 | }; | ||
| 250 | } | ||
| 251 | return; | ||
| 252 | } | ||
| 238 | else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) { | 253 | else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) { |
| 239 | for (let j = 0; j < json.choices[0].delta.content.length; j++) { | 254 | for (let j = 0; j < json.choices[0].delta.content.length; j++) { |
| 240 | const str = json.choices[0].delta.content[j]; | 255 | const str = json.choices[0].delta.content[j]; |
| @@ -413,8 +413,3 @@ export const VLLM_KEYS = [ | |||
| 413 | 'guided_decoding_backend', | 413 | 'guided_decoding_backend', |
| 414 | 'guided_whitespace_pattern', | 414 | 'guided_whitespace_pattern', |
| 415 | ]; | 415 | ]; |
| 416 | |||
| 417 | /** | ||
| 418 | * Custom boundary for splitting the text between the model's reasoning and the actual response. | ||
| 419 | */ | ||
| 420 | export const THINK_BREAK = '##�THINK_BREAK�##'; | ||
| @@ -7,7 +7,6 @@ import { | |||
| 7 | CHAT_COMPLETION_SOURCES, | 7 | CHAT_COMPLETION_SOURCES, |
| 8 | GEMINI_SAFETY, | 8 | GEMINI_SAFETY, |
| 9 | OPENROUTER_HEADERS, | 9 | OPENROUTER_HEADERS, |
| 10 | THINK_BREAK, | ||
| 11 | } from '../../constants.js'; | 10 | } from '../../constants.js'; |
| 12 | import { | 11 | import { |
| 13 | forwardFetchResponse, | 12 | forwardFetchResponse, |
| @@ -392,11 +391,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 392 | const responseContent = candidates[0].content ?? candidates[0].output; | 391 | const responseContent = candidates[0].content ?? candidates[0].output; |
| 393 | console.log('Google AI Studio response:', responseContent); | 392 | console.log('Google AI Studio response:', responseContent); |
| 394 | 393 | ||
| 395 | if (Array.isArray(responseContent?.parts) && isThinking && !showThoughts) { | 394 | const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.filter(part => !part.thought)?.map(part => part.text)?.join('\n\n'); |
| 396 | responseContent.parts = responseContent.parts.filter(part => !part.thought); | ||
| 397 | } | ||
| 398 | |||
| 399 | const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.map(part => part.text)?.join(THINK_BREAK); | ||
| 400 | if (!responseText) { | 395 | if (!responseText) { |
| 401 | let message = 'Google AI Studio Candidate text empty'; | 396 | let message = 'Google AI Studio Candidate text empty'; |
| 402 | console.log(message, generateResponseJson); | 397 | console.log(message, generateResponseJson); |
| @@ -404,7 +399,7 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 404 | } | 399 | } |
| 405 | 400 | ||
| 406 | // Wrap it back to OAI format | 401 | // Wrap it back to OAI format |
| 407 | const reply = { choices: [{ 'message': { 'content': responseText } }] }; | 402 | const reply = { choices: [{ 'message': { 'content': responseText } }], responseContent }; |
| 408 | return response.send(reply); | 403 | return response.send(reply); |
| 409 | } | 404 | } |
| 410 | } catch (error) { | 405 | } catch (error) { |
| @@ -993,6 +988,10 @@ router.post('/generate', jsonParser, function (request, response) { | |||
| 993 | bodyParams['route'] = 'fallback'; | 988 | bodyParams['route'] = 'fallback'; |
| 994 | } | 989 | } |
| 995 | 990 | ||
| 991 | if (request.body.show_thoughts) { | ||
| 992 | bodyParams['include_reasoning'] = true; | ||
| 993 | } | ||
| 994 | |||
| 996 | let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1); | 995 | let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1); |
| 997 | if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) { | 996 | if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) { |
| 998 | cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth); | 997 | cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth); |