Facillitate extension use of ConnectionManagerRequestService (#4841) * Separate prompt-building functionality from request-sending functionality * removing logs and clarifying comments * separating parameter construction functionality to allow ConnectionManagerRequestService to use all other preset parameters * fixing chat completion issues, adding documentation to new functions. * Improving ConnectionManagerRequestService errors. Adding parseReasoningFromString option to override reasoning template. * Adjusting TextCompletionService prompt formatting * linting * Use settingsToUpdate to convert from OAI preset to OAI settings. * lint * throw errors when profile ID not found * Fix missed instances of global completion settings being used (CC and TC), replaced with optional argument. Specified typing for ChatCompletionSettings and TextCompletionSettings. * Adjusting parameters of parseReasoningFromString and adding getReasoningTemplateByName * using messages.role as a fallback for custom requests, fixing newline removal. * parameters => settings I like how it sounds better * ditto * You know I had to do it to 'em * Update getCustomTokenBans * Fix calculateLogitBias * Fix param attributes * Fix type checks * Less strict role type on ChatCompletionMessage * Add missing space * fixing getChatCompletionModel to use an arbitrary chat completion settings object * Fixing issues with preset overriding custom data passed. * Pass model to createGenerationParameters externally * Unify seed param handling for CHUTES * Fix non-existing CC source * Use strict comparison * Use global settings as a base for generation parameters creation * removing unnecessary handling of preset fields * don't pass preset prompts, use the passed payload override messages * refactoring text generation prompt building of last line * Pass model to getReasoningEffort * Pass model name to canPerformToolCalls * Pass model to createTextGenGenerationData --------- Co-authored-by: qvink <qvink@users.noreply.github.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

a4cc9b3989dd1a96b1dc48e21c3b4ca951577dd0

qvink <191186569+qvink@users.noreply.github.com>

Signed
8 files changed, +632 -488Showing whitespace changes
public/global.d.ts+6 -0
@@ -146,6 +146,12 @@ declare global {
146146 paused: boolean;
147147 }
148148
149+ interface ChatCompletionMessage {
150+ name?: string;
151+ role: string;
152+ content: string;
153+ }
154+
149155 // Global namespace modules
150156 interface Window {
151157 ai: any;
public/scripts/custom-request.js+134 -123
@@ -1,9 +1,9 @@
11import { getPresetManager } from './preset-manager.js';
22import { extractJsonFromData, extractMessageFromData, getGenerateUrl, getRequestHeaders, name1, name2 } from '../script.js';
33import { getTextGenServer, createTextGenGenerationData, setting_names, textgenerationwebui_settings } from './textgen-settings.js';
44import { extractReasoningFromData } from './reasoning.js';
55import { formatInstructModeChat, formatInstructModePrompt, getInstructStoppingSequences, names_behavior_types } from './instruct-mode.js';
66import { getStreamingReply, tryParseStreamingError, createGenerationParameters, settingsToUpdate, oai_settings } from './openai.js';
77import EventSourceStream from './sse-stream.js';
88
99// #region Type Definitions
@@ -34,6 +34,7 @@ import EventSourceStream from './sse-stream.js';
3434
3535/**
3636 * @typedef {Object} ChatCompletionMessage
37+ * @property {string} [name] - The name of the message author (optional)
3738 * @property {string} role - The role of the message author (e.g., "user", "assistant", "system")
3839 * @property {string} content - The content of the message
3940 */
@@ -125,7 +126,7 @@ export class TextCompletionService {
125126
126127 const json = await response.json();
127128 if (!response.ok || json.error) {
128- throw json;
129+ throw new Error(String(json.error?.message || 'Response not OK'));
129130 }
130131
131132 if (!extractData) {
@@ -188,57 +189,27 @@ export class TextCompletionService {
188189 }
189190
190191 /**
191- * Process and send a text completion request with optional preset & instruct
192+ * Return a formatted prompt string given an array of messages, a chosen instruct preset, and instruct settings.
192193 * @param {Record<string, any> & TextCompletionRequestBase & {prompt: (ChatCompletionMessage & {ignoreInstruct?: boolean})[] |string}} customprompt An array of messages
193- * @param {Object} options - Configuration options
194+ * @param {InstructSettings|string} instructPreset Either the name of an instruct preset or the instruct preset object itself.
194- * @param {string?} [options.presetName] - Name of the preset to use for generation settings
195+ * @param {Partial<InstructSettings>} instructSettings Optional instruct settings
195- * @param {string?} [options.instructName] - Name of instruct preset for message formatting
196- * @param {Partial<InstructSettings>?} [options.instructSettings] - Override instruct settings
197- * @param {boolean} extractData - Whether to extract structured data from response
198- * @param {AbortSignal?} [signal]
199- * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
200- * @throws {Error}
201196 */
202- static async processRequest(
197+ static constructPrompt(prompt, instructPreset, instructSettings) {
203- custom,
198+ // InstructPreset may either be a name or itself a preset
204- options = {},
199+ if (typeof instructPreset === 'string') {
205- extractData = true,
200+ const instructPresetManager = getPresetManager('instruct');
206- signal = null,
201+ instructPreset = instructPresetManager?.getCompletionPresetByName(instructPreset);
207- ) {
208- const { presetName, instructName } = options;
209- let requestData = { ...custom };
210- const prompt = custom.prompt;
211-
212- // Apply generation preset if specified
213- if (presetName) {
214- const presetManager = getPresetManager(this.TYPE);
215- if (presetManager) {
216- const preset = presetManager.getCompletionPresetByName(presetName);
217- if (preset) {
218- // Convert preset to payload and merge with custom parameters
219- const presetPayload = this.presetToGeneratePayload(preset, {});
220- requestData = { ...presetPayload, ...requestData };
221- } else {
222- console.warn(`Preset "${presetName}" not found, continuing with default settings`);
223- }
224- } else {
225- console.warn('Preset manager not found, continuing with default settings');
226- }
227202 }
228203
229-
230- /** @type {InstructSettings | undefined} */
231- let instructPreset;
232- // Handle instruct formatting if requested
233- if (Array.isArray(prompt) && instructName) {
234- const instructPresetManager = getPresetManager('instruct');
235- instructPreset = instructPresetManager?.getCompletionPresetByName(instructName);
236- if (instructPreset) {
237204 // Clone the preset to avoid modifying the original
238205 instructPreset = structuredClone(instructPreset);
239- instructPreset.names_behavior = names_behavior_types.NONE;
206+ if (instructSettings) { // apply any additional settings
240- if (options.instructSettings) {
207+ Object.assign(instructPreset, instructSettings);
241- Object.assign(instructPreset, options.instructSettings);
208+ }
209+
210+ // Make the type check shut up. We 100% don't have a string here.
211+ if (typeof instructPreset === 'string') {
212+ return;
242213 }
243214
244215 // Format messages using instruct formatting
@@ -254,52 +225,76 @@ export class TextCompletionService {
254225 // 2. If prefill is active, format all messages except the last one
255226 if (!isLastMessage || !prefillActive) {
256227 messageContent = formatInstructModeChat(
257- message.role,
228+ message.name ?? message.role,
258229 message.content,
259230 message.role === 'user',
260231 message.role === 'system',
261232 undefined,
262- undefined,
233+ name1, // for macros
263- undefined,
234+ name2, // for macros
264235 undefined,
265236 instructPreset,
266237 );
267238 }
268239
269240 // Add prompt formatting for the last message.
241+ // e.g. "<|im_start|>assistant"
270242 if (isLastMessage) {
271- if (!prefillActive) { // e.g. "<|im_start|>user:"
243+ let last_line = formatInstructModePrompt(
272- messageContent += formatInstructModePrompt(
244+ 'assistant', // for sequences using {{name}}
273- undefined,
245+ false, // not an impersonation
274- false,
246+ prefillActive ? message.content : undefined, // if using prefill, last message is the prefill
275- undefined,
247+ name1, // for macros
276- undefined,
248+ name2, // for macros
277- undefined,
249+ true, // quiet
278- false,
279250 false,
280251 instructPreset,
281252 );
282- } else { // e.g. "<|im_start|>assistant: Hello, my name is"
253+
283- const overriddenInstructPreset = structuredClone(instructPreset);
254+ if (prefillActive) { // content is the prefilled message
284- overriddenInstructPreset.output_suffix = '';
255+ if (last_line.endsWith('\n') && !message.content.endsWith('\n')) {
285- overriddenInstructPreset.wrap = false;
256+ last_line = last_line.slice(0, -1); // remove newline after prefill if it's not in the prefill itself
286- messageContent = formatInstructModeChat(
257+ }
287- message.role,
258+ messageContent = last_line;
288- message.content,
259+ } else { // append last line to content (e.g. "<|im_start|>assistant:")
289- false, // since it is assistant
260+ messageContent += last_line;
290- false,
291- undefined,
292- undefined,
293- undefined,
294- undefined,
295- overriddenInstructPreset,
296- );
297261 }
298262 }
299263 }
300264 formattedMessages.push(messageContent);
301265 }
302266 requestData.prompt = return formattedMessages.join('');
267+ }
268+
269+
270+ /**
271+ * Process and send a text completion request with optional preset & instruct
272+ * @param {TextCompletionPayload} requestData
273+ * @param {Object} options - Configuration options
274+ * @param {string?} [options.presetName] - Name of the preset to use for generation settings
275+ * @param {string?} [options.instructName] - Name of instruct preset for message formatting
276+ * @param {Partial<InstructSettings>?} [options.instructSettings] - Override instruct settings
277+ * @param {boolean} extractData - Whether to extract structured data from response
278+ * @param {AbortSignal?} [signal]
279+ * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
280+ * @throws {Error}
281+ */
282+ static async processRequest(requestData, options = {}, extractData = true, signal = null) {
283+ const { presetName, instructName } = options;
284+
285+ // remove any undefined params in given request data
286+ requestData = this.createRequestData(requestData);
287+
288+ /** @type {InstructSettings | undefined} */
289+ let instructPreset;
290+ const prompt = requestData.prompt;
291+ // Handle instruct formatting if requested
292+ if (Array.isArray(prompt)) {
293+ if (instructName) {
294+ const instructPresetManager = getPresetManager('instruct');
295+ instructPreset = instructPresetManager?.getCompletionPresetByName(instructName);
296+ if (instructPreset) {
297+ requestData.prompt = this.constructPrompt(prompt, instructPreset, options.instructSettings);
303298 const stoppingStrings = getInstructStoppingSequences({ customInstruct: instructPreset, useStopStrings: false });
304299 requestData.stop = stoppingStrings;
305300 requestData.stopping_strings = stoppingStrings;
@@ -307,18 +302,33 @@ export class TextCompletionService {
307302 console.warn(`Instruct preset "${instructName}" not found, using basic formatting`);
308303 requestData.prompt = prompt.map(x => x.content).join('\n\n');
309304 }
305+ } else {
306+ requestData.prompt = prompt.map(x => x.content).join('\n\n');
307+ }
310308 } else if (typeof prompt === 'string') {
311309 requestData.prompt = prompt;
310+ }
311+
312+ // Apply generation preset if specified
313+ if (presetName) {
314+ const presetManager = getPresetManager(this.TYPE);
315+ if (presetManager) {
316+ const preset = presetManager.getCompletionPresetByName(presetName);
317+ if (preset) {
318+ // Convert preset to payload and merge with custom data
319+ requestData = this.presetToGeneratePayload(preset, {}, requestData);
312320 } else {
313- requestData.prompt = prompt.map(x => x.content).join('\n\n');
321+ console.warn(`Preset "${presetName}" not found, continuing with default settings`);
322+ }
323+ } else {
324+ console.warn('Preset manager not found, continuing with default settings');
325+ }
314326 }
315327
316- // @ts-ignore
328+ const response = await this.sendRequest(requestData, extractData, signal);
317- const data = this.createRequestData(requestData);
318329
319- const response = await this.sendRequest(data, extractData, signal);
320330 // Remove stopping strings from the end
321331 if (!datarequestData.stream && extractData) {
322332 /** @type {ExtractedData} */
323333 // @ts-ignore
324334 const extractedData = response;
@@ -377,31 +387,30 @@ export class TextCompletionService {
377387 * Converts a preset to a valid text completion payload.
378388 * Only supports temperature.
379389 * @param {Object} preset - The preset configuration
380390 * @param {Object} customPresetoverridePreset - Additional parameters to override preset values
391+ * @param {Object} overridePayload - Additional parameters to override payload values
381392 * @returns {Object} - Formatted payload for text completion API
382393 */
383394 static presetToGeneratePayload(preset, customPresetoverridePreset = {}, overridePayload = {}) {
384395 if (!preset || typeof preset !== 'object') {
385396 throw new Error('Invalid preset: must be an object');
386397 }
387398
388399 // Mergeapply preset with custom parametersoverrides
389400 const settingspreset = { ...preset, ...customPresetoverridePreset };
390-
391- // Initialize base payload with common parameters
392- let payload = {
393- 'temperature': settings.temp >= 0 ? Number(settings.temp) : undefined,
394- 'min_p': settings.min_p >= 0 ? Number(settings.min_p) : undefined,
395- };
396401
397- // Remove undefined values to avoid API errors
402+ // Only take fields from the preset specified in setting_names to use as TextCompletionSettings
398- Object.keys(payload).forEach(key => {
403+ const settings = structuredClone(textgenerationwebui_settings);
399- if (payload[key] === undefined) {
404+ for (const [key, value] of Object.entries(preset)) {
400- delete payload[key];
405+ if (!setting_names.includes(key)) continue;
406+ settings[key] = value;
401407 }
402- });
403408
404- return payload;
409+ // convert to a generation payload
410+ const payload = createTextGenGenerationData(settings, overridePayload.model, overridePayload.prompt, preset.genamt);
411+
412+ // apply overrides
413+ return this.createRequestData({ ...payload, ...overridePayload });
405414 }
406415}
407416
@@ -461,7 +470,7 @@ export class ChatCompletionService {
461470 if (!data.stream) {
462471 const json = await response.json();
463472 if (!response.ok || json.error) {
464- throw json;
473+ throw new Error(String(json.error?.message || 'Response not OK'));
465474 }
466475
467476 if (!extractData) {
@@ -523,7 +532,7 @@ export class ChatCompletionService {
523532
524533 /**
525534 * Process and send a chat completion request with optional preset
526535 * @param {ChatCompletionPayload} customrequestData - payload data, overriding preset if given
527536 * @param {Object} options - Configuration options
528537 * @param {string?} [options.presetName] - Name of the preset to use for generation settings
529538 * @param {boolean} [extractData=true] - Whether to extract structured data from response
@@ -531,9 +540,9 @@ export class ChatCompletionService {
531540 * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
532541 * @throws {Error}
533542 */
534543 static async processRequest(customrequestData, options, extractData = true, signal = null) {
535544 const { presetName } = options;
536545 let requestData = { ..this.custom }createRequestData(requestData);
537546
538547 // Apply generation preset if specified
539548 if (presetName) {
@@ -542,8 +551,7 @@ export class ChatCompletionService {
542551 const preset = presetManager.getCompletionPresetByName(presetName);
543552 if (preset) {
544553 // Convert preset to payload and merge with custom parameters
545554 const presetPayloadrequestData = await this.presetToGeneratePayload(preset, {}, requestData);
546- requestData = { ...presetPayload, ...requestData };
547555 } else {
548556 console.warn(`Preset "${presetName}" not found, continuing with default settings`);
549557 }
@@ -552,38 +560,41 @@ export class ChatCompletionService {
552560 }
553561 }
554562
555563 const datareturn =await this.createRequestDatasendRequest(requestData, extractData, signal);
556-
557- return await this.sendRequest(data, extractData, signal);
558564 }
559565
560566 /**
561567 * Converts a preset to a valid chat completion payload
562568 * Only supports temperature.
563569 * @param {Object} preset - The preset configuration
564570 * @param {Object} customParamsoverridePreset - Additional parameters to override preset values
565571 * @returnsparam {Object} overridePayload - FormattedAdditional payloadparameters forto chatoverride completionpayload APIvalues
572+ * @returns {Promise<any>} - Formatted payload for chat completion API
566573 */
567574 static async presetToGeneratePayload(preset, customParamsoverridePreset = {}, overridePayload = {}) {
568575 if (!preset || typeof preset !== 'object') {
569576 throw new Error('Invalid preset: must be an object');
570577 }
571578
572579 // Mergeapply preset with custom parametersoverrides
573580 const settingspreset = { ...preset, ...customParamsoverridePreset };
574581
575582 // InitializeFix baseany payloadfields withbefore commonconverting parametersto settings
576- const payload = {
583+ preset.bias_preset_selected = preset.bias_presets !== undefined ? preset.bias_preset_selected : undefined; // presets might have bias_preset_selected but not bias_presets, but settings need both or neither.
577- temperature: settings.temperature >= 0 ? Number(settings.temperature) : undefined,
578- };
579584
580585 // RemoveConvert undefinedfrom valuespreset to avoid API errorsChatCompletionSettings
581- Object.keys(payload).forEach(key => {
586+ const settings = structuredClone(oai_settings);
582- if (payload[key] === undefined) {
587+ for (const [key, value] of Object.entries(preset)) {
583588 delete const payloadsettingToUpdate = settingsToUpdate[key];
589+ if (!settingToUpdate) continue;
590+ settings[settingToUpdate[1]] = value;
584591 }
585- });
586592
587- return payload;
593+ // Convert from settings to generation payload
594+ const data = await createGenerationParameters(settings, overridePayload.model, 'quiet', overridePayload.messages);
595+ const payload = data.generate_data;
596+
597+ // apply overrides
598+ return this.createRequestData({ ...payload, ...overridePayload });
588599 }
589600}
public/scripts/extensions/shared.js+45 -1
@@ -397,7 +397,7 @@ export class ConnectionManagerRequestService {
397397 throw new Error('Connection Manager is not available');
398398 }
399399
400- const profile = context.extensionSettings.connectionManager.profiles.find((p) => p.id === profileId);
400+ const profile = this.getProfile(profileId);
401401 const selectedApiMap = this.validateProfile(profile);
402402
403403 try {
@@ -454,6 +454,38 @@ export class ConnectionManagerRequestService {
454454 }
455455
456456 /**
457+ * If using text completion, return a formatted prompt string given an array of messages, a given profile ID, and optional instruct settings.
458+ * If using chat completion, simply return the given prompt as-is.
459+ * @param {ChatCompletionMessage[]} prompt An array of prompt messages.
460+ * @param {string} profileId ID of a given connection profile (from which to infer a completion preset).
461+ * @param {InstructSettings} instructSettings optional instruct settings
462+ */
463+ static constructPrompt(prompt, profileId, instructSettings = null) {
464+ const context = SillyTavern.getContext();
465+ const profile = this.getProfile(profileId);
466+ const selectedApiMap = this.validateProfile(profile);
467+ const instructName = profile.instruct;
468+
469+ switch (selectedApiMap.selected) {
470+ case 'openai': {
471+ if (!selectedApiMap.source) {
472+ throw new Error(`API type ${selectedApiMap.selected} does not support chat completions`);
473+ }
474+ return prompt;
475+ }
476+ case 'textgenerationwebui': {
477+ if (!selectedApiMap.type) {
478+ throw new Error(`API type ${selectedApiMap.selected} does not support text completions`);
479+ }
480+ return context.TextCompletionService.constructPrompt(prompt, instructName, instructSettings);
481+ }
482+ default: {
483+ throw new Error(`Unknown API type ${selectedApiMap.selected}`);
484+ }
485+ }
486+ }
487+
488+ /**
457489 * Respects allowed types.
458490 * @returns {import('./connection-manager/index.js').ConnectionProfile[]}
459491 */
@@ -468,6 +500,18 @@ export class ConnectionManagerRequestService {
468500 }
469501
470502 /**
503+ * Return profile data given the profile ID
504+ * @param {string} profileId
505+ * @returns {import('./connection-manager/index.js').ConnectionProfile?} [profile]
506+ * @throws {Error}
507+ */
508+ static getProfile(profileId) {
509+ const profile = SillyTavern.getContext().extensionSettings.connectionManager.profiles.find((p) => p.id === profileId);
510+ if (!profile) throw new Error(`Profile not found (ID: ${profileId})`);
511+ return profile;
512+ }
513+
514+ /**
471515 * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile]
472516 * @returns {boolean}
473517 */
public/scripts/openai.js+251 -200
@@ -18,7 +18,6 @@ import {
1818 getMediaDisplay,
1919 getMediaIndex,
2020 getRequestHeaders,
21- getStoppingStrings,
2221 is_send_press,
2322 main_api,
2423 name1,
@@ -1565,62 +1564,63 @@ function checkModerationError(data, { quiet = false } = {}) {
15651564
15661565/**
15671566 * Gets the API model for the selected chat completion source.
15681567 * @param {stringChatCompletionSettings} source If it's set,settings ignoresChat activecompletion sourcesettings
15691568 * @returns {string} API model
15701569 */
15711570export function getChatCompletionModel(sourcesettings = null) {
15721571 const activeSourcesettings = sourcesettings ?? oai_settings.chat_completion_source;
1573- switch (activeSource) {
1572+ const source = settings.chat_completion_source;
1573+ switch (source) {
15741574 case chat_completion_sources.CLAUDE:
15751575 return oai_settingssettings.claude_model;
15761576 case chat_completion_sources.OPENAI:
15771577 return oai_settingssettings.openai_model;
15781578 case chat_completion_sources.MAKERSUITE:
15791579 return oai_settingssettings.google_model;
15801580 case chat_completion_sources.VERTEXAI:
15811581 return oai_settingssettings.vertexai_model;
15821582 case chat_completion_sources.OPENROUTER:
15831583 return oai_settingssettings.openrouter_model !== openrouter_website_model ? oai_settingssettings.openrouter_model : null;
15841584 case chat_completion_sources.AI21:
15851585 return oai_settingssettings.ai21_model;
15861586 case chat_completion_sources.MISTRALAI:
15871587 return oai_settingssettings.mistralai_model;
15881588 case chat_completion_sources.CUSTOM:
15891589 return oai_settingssettings.custom_model;
15901590 case chat_completion_sources.COHERE:
15911591 return oai_settingssettings.cohere_model;
15921592 case chat_completion_sources.PERPLEXITY:
15931593 return oai_settingssettings.perplexity_model;
15941594 case chat_completion_sources.GROQ:
15951595 return oai_settingssettings.groq_model;
15961596 case chat_completion_sources.SILICONFLOW:
15971597 return oai_settingssettings.siliconflow_model;
15981598 case chat_completion_sources.ELECTRONHUB:
15991599 return oai_settingssettings.electronhub_model;
16001600 case chat_completion_sources.CHUTES:
16011601 return oai_settingssettings.chutes_model;
16021602 case chat_completion_sources.NANOGPT:
16031603 return oai_settingssettings.nanogpt_model;
16041604 case chat_completion_sources.DEEPSEEK:
16051605 return oai_settingssettings.deepseek_model;
16061606 case chat_completion_sources.AIMLAPI:
16071607 return oai_settingssettings.aimlapi_model;
16081608 case chat_completion_sources.XAI:
16091609 return oai_settingssettings.xai_model;
16101610 case chat_completion_sources.POLLINATIONS:
16111611 return oai_settingssettings.pollinations_model;
16121612 case chat_completion_sources.COMETAPI:
16131613 return oai_settingssettings.cometapi_model;
16141614 case chat_completion_sources.MOONSHOT:
16151615 return oai_settingssettings.moonshot_model;
16161616 case chat_completion_sources.FIREWORKS:
16171617 return oai_settingssettings.fireworks_model;
16181618 case chat_completion_sources.AZURE_OPENAI:
16191619 return oai_settingssettings.azure_openai_model;
16201620 case chat_completion_sources.ZAI:
16211621 return oai_settingssettings.zai_model;
16221622 default:
16231623 console.error(`Unknown chat completion source: ${activeSourcesource}`);
16241624 return '';
16251625 }
16261626}
@@ -2300,7 +2300,16 @@ function getAimlapiModelTemplate(option) {
23002300 `));
23012301}
23022302
2303-function getReasoningEffort() {
2303+/**
2304+ * Get the reasoning effort from chat completion settings
2305+ * @param {ChatCompletionSettings} settings Chat completion settings
2306+ * @param {string} model Model name (optional, used for ElectronHub)
2307+ * @returns {string} Reasoning effort, if present
2308+ */
2309+function getReasoningEffort(settings = null, model = null) {
2310+ settings = settings ?? oai_settings;
2311+ model = model ?? getChatCompletionModel(settings);
2312+
23042313 // These sources expect the effort as string.
23052314 const reasoningEffortSources = [
23062315 chat_completion_sources.OPENAI,
@@ -2316,31 +2325,31 @@ function getReasoningEffort() {
23162325 chat_completion_sources.CHUTES,
23172326 ];
23182327
23192328 if (!reasoningEffortSources.includes(oai_settingssettings.chat_completion_source)) {
23202329 return oai_settingssettings.reasoning_effort;
23212330 }
23222331
23232332 function resolveReasoningEffort() {
23242333 switch (oai_settingssettings.reasoning_effort) {
23252334 case reasoning_effort_types.auto:
23262335 return undefined;
23272336 case reasoning_effort_types.min:
23282337 return [chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI].includes(oai_settingssettings.chat_completion_source) && /^gpt-5/.test(getChatCompletionModel()model)
23292338 ? reasoning_effort_types.min
23302339 : reasoning_effort_types.low;
23312340 case reasoning_effort_types.max:
23322341 return reasoning_effort_types.high;
23332342 default:
23342343 return oai_settingssettings.reasoning_effort;
23352344 }
23362345 }
23372346
23382347 const reasoningEffort = resolveReasoningEffort();
23392348
23402349 // Check if the resolved effort supported by the model
23412350 if (oai_settingssettings.chat_completion_source === chat_completion_sources.ELECTRONHUB) {
23422351 if (Array.isArray(model_list) && reasoningEffort) {
23432352 const currentModel = model_list.find(m => m.id === oai_settings.electronhub_modelmodel);
23442353 const supportedEfforts = currentModel?.metadata?.supported_reasoning_efforts;
23452354 if (Array.isArray(supportedEfforts) && supportedEfforts.includes(reasoningEffort)) {
23462355 return reasoningEffort;
@@ -2352,72 +2361,117 @@ function getReasoningEffort() {
23522361 return reasoningEffort;
23532362}
23542363
2355-function getVerbosity() {
2364+/**
2356- if (oai_settings.verbosity === verbosity_levels.auto) {
2365+ * Get the verbosity from chat completion settings
2366+ * @param {ChatCompletionSettings} settings Chat completion settings
2367+ * @returns {string} Verbosity level, if present
2368+ */
2369+function getVerbosity(settings = null) {
2370+ settings = settings ?? oai_settings;
2371+
2372+ if (settings.verbosity === verbosity_levels.auto) {
23572373 return undefined;
23582374 }
23592375
23602376 // TODO: Adjust verbosity based on model capabilities
23612377 return oai_settingssettings.verbosity;
23622378}
23632379
23642380/**
2365- * Send a chat completion request to backend
2381+ * Build the generation parameter object for an OAI request.
23662382 * @param {stringChatCompletionSettings} typesettings (impersonate,Initial quiet,chat continue,completion etc)settings
23672383 * @param {Arraystring} messagesmodel Model name
2368- * @param {AbortSignal?} signal
2384+ * @param {string} type Request type (impersonate, quiet, continue, etc)
2369- * @param {import('../script.js').AdditionalRequestOptions} options
2385+ * @param {ChatCompletionMessage[]} messages Array of chat completion messages
2370- * @returns {Promise<unknown>}
2386+ * @param {import('../script.js').AdditionalRequestOptions} options Additional request options
2371- * @throws {Error}
2387+ * @returns {Promise<object>} Final generation parameters object appropriate for the chat completion source
23722388 */
2373-
2389+export async function createGenerationParameters(settings, model, type, messages, { jsonSchema = null } = {}) {
2374-async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } = {}) {
2375- // Provide default abort signal
2376- if (!signal) {
2377- signal = new AbortController().signal;
2378- }
2379-
23802390 // HACK: Filter out null and non-object messages
23812391 if (!Array.isArray(messages)) {
23822392 throw new Error('messages must be an array');
23832393 }
2384-
23852394 messages = messages.filter(msg => msg && typeof msg === 'object');
23862395
2387- let logit_bias = {};
2396+ // "OpenAI-like" sources
2388- const isClaude = oai_settings.chat_completion_source == chat_completion_sources.CLAUDE;
2397+ const gptSources = [
2389- const isOpenRouter = oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER;
2398+ chat_completion_sources.OPENAI,
2390- const isGoogle = oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE;
2399+ chat_completion_sources.AZURE_OPENAI,
2391- const isVertexAI = oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI;
2400+ chat_completion_sources.OPENROUTER,
2392- const isOAI = oai_settings.chat_completion_source == chat_completion_sources.OPENAI;
2401+ ];
2393- const isMistral = oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI;
2402+
2394- const isCustom = oai_settings.chat_completion_source == chat_completion_sources.CUSTOM;
2403+ // Sources that support the "seed" parameter
2395- const isCohere = oai_settings.chat_completion_source == chat_completion_sources.COHERE;
2404+ const seedSupportedSources = [
2396- const isPerplexity = oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY;
2405+ chat_completion_sources.OPENAI,
2397- const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ;
2406+ chat_completion_sources.AZURE_OPENAI,
2398- const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;
2407+ chat_completion_sources.OPENROUTER,
2399- const isAimlapi = oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI;
2408+ chat_completion_sources.MISTRALAI,
2400- const isElectronHub = oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB;
2409+ chat_completion_sources.CUSTOM,
2401- const isChutes = oai_settings.chat_completion_source == chat_completion_sources.CHUTES;
2410+ chat_completion_sources.COHERE,
2402- const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;
2411+ chat_completion_sources.GROQ,
2403- const isPollinations = oai_settings.chat_completion_source == chat_completion_sources.POLLINATIONS;
2412+ chat_completion_sources.ELECTRONHUB,
2404- const isMoonshot = oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT;
2413+ chat_completion_sources.NANOGPT,
2405- const isAzureOpenAI = oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI;
2414+ chat_completion_sources.XAI,
2406- const isZai = oai_settings.chat_completion_source == chat_completion_sources.ZAI;
2415+ chat_completion_sources.POLLINATIONS,
2407- const isNanoGPT = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;
2416+ chat_completion_sources.AIMLAPI,
2408- const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
2417+ chat_completion_sources.VERTEXAI,
2409- const isQuiet = type === 'quiet';
2418+ chat_completion_sources.MAKERSUITE,
2410- const isImpersonate = type === 'impersonate';
2419+ chat_completion_sources.CHUTES,
2411- const isContinue = type === 'continue';
2420+ ];
2412- const stream = oai_settings.stream_openai && !isQuiet && !((isOAI || isAzureOpenAI) && ['o1-2024-12-17', 'o1'].includes(getChatCompletionModel()));
2421+
2413- const useLogprobs = !!power_user.request_token_probabilities;
2422+ // Sources that support proxying
2414- const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isAzureOpenAI || isCustom || isXAI || isAimlapi || isMoonshot);
2423+ const proxySupportedSources = [
2424+ chat_completion_sources.CLAUDE,
2425+ chat_completion_sources.OPENAI,
2426+ chat_completion_sources.MISTRALAI,
2427+ chat_completion_sources.MAKERSUITE,
2428+ chat_completion_sources.VERTEXAI,
2429+ chat_completion_sources.DEEPSEEK,
2430+ chat_completion_sources.XAI,
2431+ ];
2432+
2433+ // Sources that support logprobs
2434+ const logprobsSupportedSources = [
2435+ chat_completion_sources.OPENAI,
2436+ chat_completion_sources.AZURE_OPENAI,
2437+ chat_completion_sources.CUSTOM,
2438+ chat_completion_sources.DEEPSEEK,
2439+ chat_completion_sources.XAI,
2440+ chat_completion_sources.AIMLAPI,
2441+ chat_completion_sources.CHUTES,
2442+ ];
2443+
2444+ // Sources that support logit bias
2445+ const logitBiasSources = [
2446+ chat_completion_sources.OPENAI,
2447+ chat_completion_sources.AZURE_OPENAI,
2448+ chat_completion_sources.OPENROUTER,
2449+ chat_completion_sources.ELECTRONHUB,
2450+ chat_completion_sources.CHUTES,
2451+ chat_completion_sources.CUSTOM,
2452+ ];
2453+
2454+ // Sources that support "n" parameter for multi-swipe
2455+ const multiswipeSources = [
2456+ chat_completion_sources.OPENAI,
2457+ chat_completion_sources.AZURE_OPENAI,
2458+ chat_completion_sources.CUSTOM,
2459+ chat_completion_sources.XAI,
2460+ chat_completion_sources.AIMLAPI,
2461+ chat_completion_sources.MOONSHOT,
2462+ ];
2463+
2464+ const isO1 = gptSources.includes(settings.chat_completion_source) && ['o1-2024-12-17', 'o1'].includes(model);
2465+ const stream = settings.stream_openai && type !== 'quiet' && !isO1;
2466+
2467+ const noMultiSwipeTypes = ['quiet', 'impersonate', 'continue'];
2468+ const canMultiSwipe = settings.n > 1 && !noMultiSwipeTypes.includes(type) && multiswipeSources.includes(settings.chat_completion_source);
24152469
2416- const logitBiasSources = [chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI, chat_completion_sources.OPENROUTER, chat_completion_sources.ELECTRONHUB, chat_completion_sources.CHUTES, chat_completion_sources.CUSTOM];
2470+ let logit_bias = {};
24172471 if (oai_settingssettings.bias_preset_selected
24182472 && logitBiasSources.includes(oai_settingssettings.chat_completion_source)
24192473 && Array.isArray(oai_settingssettings.bias_presets[oai_settingssettings.bias_preset_selected])
24202474 && oai_settingssettings.bias_presets[oai_settingssettings.bias_preset_selected].length) {
24212475 logit_bias = biasCache || await calculateLogitBias();
24222476 biasCache = logit_bias;
24232477 }
@@ -2426,45 +2480,44 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
24262480 logit_bias = undefined;
24272481 }
24282482
2429- const model = getChatCompletionModel();
24302483 const generate_data = {
24312484 'type': type,
24322485 'messages': messages,
24332486 'model': model,
24342487 'temperature': Number(oai_settingssettings.temp_openai),
24352488 'frequency_penalty': Number(oai_settingssettings.freq_pen_openai),
24362489 'presence_penalty': Number(oai_settingssettings.pres_pen_openai),
24372490 'top_p': Number(oai_settingssettings.top_p_openai),
24382491 'max_tokens': oai_settingssettings.openai_max_tokens,
24392492 'stream': stream,
24402493 'logit_bias': logit_bias,
24412494 'stop': getCustomStoppingStrings(openai_max_stop_strings),
24422495 'chat_completion_source': oai_settingssettings.chat_completion_source,
24432496 'n': canMultiSwipe ? oai_settingssettings.n : undefined,
24442497 'user_name': name1,
24452498 'char_name': name2,
24462499 'group_names': getGroupNames(),
24472500 'include_reasoning': Boolean(oai_settingssettings.show_thoughts),
24482501 'reasoning_effort': getReasoningEffort(settings, model),
24492502 'enable_web_search': Boolean(oai_settingssettings.enable_web_search),
24502503 'request_images': Boolean(oai_settingssettings.request_images),
24512504 'request_image_resolution': String(oai_settingssettings.request_image_resolution),
24522505 'request_image_aspect_ratio': String(oai_settingssettings.request_image_aspect_ratio),
24532506 'custom_prompt_post_processing': oai_settingssettings.custom_prompt_post_processing,
24542507 'verbosity': getVerbosity(settings),
24552508 };
24562509
24572510 if (isAzureOpenAIsettings.chat_completion_source === chat_completion_sources.AZURE_OPENAI) {
24582511 generate_data.azure_base_url = oai_settingssettings.azure_base_url;
24592512 generate_data.azure_deployment_name = oai_settingssettings.azure_deployment_name;
24602513 generate_data.azure_api_version = oai_settingssettings.azure_api_version;
24612514 // Reasoning effort is not supported on some Azure models (e.g. GPT-3.x, GPT-4.x)
24622515 if (/^gpt-[34]/.test(oai_settings.azure_openai_modelmodel)) {
24632516 delete generate_data.reasoning_effort;
24642517 }
24652518 }
24662519
24672520 if (!canMultiSwipe && ToolManager.canPerformToolCalls(type, settings, model)) {
24682521 await ToolManager.registerFunctionToolsOpenAI(generate_data);
24692522 }
24702523
@@ -2473,99 +2526,95 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
24732526 delete generate_data.stop;
24742527 }
24752528
2476- // Proxy is only supported for Claude, OpenAI, Mistral, Google MakerSuite, and Vertex AI
2529+ if (settings.reverse_proxy && proxySupportedSources.includes(settings.chat_completion_source)) {
2477- if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) {
24782530 await validateReverseProxy();
24792531 generate_data['reverse_proxy'] = oai_settingssettings.reverse_proxy;
24802532 generate_data['proxy_password'] = oai_settingssettings.proxy_password;
24812533 }
24822534
24832535 // Add logprobs request (currently OpenAI only, max 5 onper theirOpenAI sidedocs)
2484- if (useLogprobs && (isOAI || isAzureOpenAI || isCustom || isDeepSeek || isXAI || isAimlapi || isChutes)) {
2536+ const useLogprobs = !!power_user.request_token_probabilities;
2537+ if (useLogprobs && logprobsSupportedSources.includes(settings.chat_completion_source)) {
24852538 generate_data['logprobs'] = 5;
24862539 }
24872540
24882541 // Remove logit bias/logprobs/stop-strings if not supported by the model
24892542 const isVision = (m) => ['gpt', 'vision'].every(x => m.includes(x));
2490- if ((isOAI && isVision(oai_settings.openai_model)) || (isAzureOpenAI && isVision(oai_settings.azure_openai_model)) || (isOpenRouter && isVision(oai_settings.openrouter_model))) {
2543+ if (gptSources.includes(settings.chat_completion_source) && isVision(model)) {
24912544 delete generate_data.logit_bias;
24922545 delete generate_data.stop;
24932546 delete generate_data.logprobs;
24942547 }
2495- if ((isOAI && oai_settings.openai_model.includes('gpt-4.5')) || (isAzureOpenAI && oai_settings.azure_openai_model.includes('gpt-4.5')) || (isOpenRouter && oai_settings.openrouter_model.includes('gpt-4.5'))) {
2548+ if (gptSources.includes(settings.chat_completion_source) && model.includes('gpt-4.5')) {
24962549 delete generate_data.logprobs;
24972550 }
24982551
24992552 if (isClaudesettings.chat_completion_source === chat_completion_sources.CLAUDE) {
25002553 generate_data['top_k'] = Number(oai_settingssettings.top_k_openai);
25012554 generate_data['use_sysprompt'] = oai_settingssettings.use_sysprompt;
25022555 generate_data['stop'] = getCustomStoppingStrings(); // Claude shouldn't have limits on stop strings.
25032556 // Don't add a prefill on quiet gens (summarization) and when using continue prefill.
25042557 if (type !isQuiet== 'quiet' && !(isContinuetype === 'continue' && oai_settingssettings.continue_prefill)) {
2505- generate_data['assistant_prefill'] = isImpersonate ? substituteParams(oai_settings.assistant_impersonation) : substituteParams(oai_settings.assistant_prefill);
2558+ generate_data['assistant_prefill'] = type === 'impersonate'
2559+ ? substituteParams(settings.assistant_impersonation)
2560+ : substituteParams(settings.assistant_prefill);
25062561 }
25072562 }
25082563
25092564 if (isOpenRoutersettings.chat_completion_source === chat_completion_sources.OPENROUTER) {
25102565 generate_data['top_k'] = Number(oai_settingssettings.top_k_openai);
25112566 generate_data['min_p'] = Number(oai_settingssettings.min_p_openai);
25122567 generate_data['repetition_penalty'] = Number(oai_settingssettings.repetition_penalty_openai);
25132568 generate_data['top_a'] = Number(oai_settingssettings.top_a_openai);
25142569 generate_data['use_fallback'] = oai_settingssettings.openrouter_use_fallback;
25152570 generate_data['provider'] = oai_settingssettings.openrouter_providers;
25162571 generate_data['allow_fallbacks'] = oai_settingssettings.openrouter_allow_fallbacks;
25172572 generate_data['middleout'] = oai_settingssettings.openrouter_middleout;
2518-
2519- if (isTextCompletion) {
2520- generate_data['stop'] = getStoppingStrings(isImpersonate, isContinue);
2521- }
25222573 }
25232574
2524- if (isGoogle || isVertexAI) {
2575+ if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(settings.chat_completion_source)) {
25252576 const stopStringsLimit = 5;
25262577 generate_data['top_k'] = Number(oai_settingssettings.top_k_openai);
25272578 generate_data['stop'] = getCustomStoppingStrings(stopStringsLimit).slice(0, stopStringsLimit).filter(x => x.length >= 1 && x.length <= 16);
25282579 generate_data['use_sysprompt'] = oai_settingssettings.use_sysprompt;
25292580 if (isVertexAIsettings.chat_completion_source === chat_completion_sources.VERTEXAI) {
25302581 generate_data['vertexai_auth_mode'] = oai_settingssettings.vertexai_auth_mode;
25312582 generate_data['vertexai_region'] = oai_settingssettings.vertexai_region;
25322583 generate_data['vertexai_express_project_id'] = oai_settingssettings.vertexai_express_project_id;
25332584 }
25342585 }
25352586
25362587 if (isMistralsettings.chat_completion_source === chat_completion_sources.MISTRALAI) {
25372588 generate_data['safe_prompt'] = false; // already defaults to false, but just incase they change that in the future.
25382589 generate_data['stop'] = getCustomStoppingStrings(); // Mistral shouldn't have limits on stop strings.
25392590 }
25402591
25412592 if (isCustomsettings.chat_completion_source === chat_completion_sources.CUSTOM) {
25422593 generate_data['custom_url'] = oai_settingssettings.custom_url;
25432594 generate_data['custom_include_body'] = oai_settingssettings.custom_include_body;
25442595 generate_data['custom_exclude_body'] = oai_settingssettings.custom_exclude_body;
25452596 generate_data['custom_include_headers'] = oai_settingssettings.custom_include_headers;
25462597 }
25472598
25482599 if (isCoheresettings.chat_completion_source === chat_completion_sources.COHERE) {
25492600 // Clamp to 0.01 -> 0.99
25502601 generate_data['top_p'] = Math.min(Math.max(Number(oai_settingssettings.top_p_openai), 0.01), 0.99);
25512602 generate_data['top_k'] = Number(oai_settingssettings.top_k_openai);
25522603 // Clamp to 0 -> 1
25532604 generate_data['frequency_penalty'] = Math.min(Math.max(Number(oai_settingssettings.freq_pen_openai), 0), 1);
25542605 generate_data['presence_penalty'] = Math.min(Math.max(Number(oai_settingssettings.pres_pen_openai), 0), 1);
25552606 generate_data['stop'] = getCustomStoppingStrings(5);
25562607 }
25572608
25582609 if (isPerplexitysettings.chat_completion_source === chat_completion_sources.PERPLEXITY) {
25592610 generate_data['top_k'] = Number(oai_settingssettings.top_k_openai);
25602611 generate_data['frequency_penalty'] = Number(oai_settingssettings.freq_pen_openai);
25612612 generate_data['presence_penalty'] = Number(oai_settingssettings.pres_pen_openai);
2562-
2563- // YEAH BRO JUST USE OPENAI CLIENT BRO
25642613 delete generate_data['stop'];
25652614 }
25662615
25672616 // https://console.groq.com/docs/openai
25682617 if (isGroqsettings.chat_completion_source === chat_completion_sources.GROQ) {
25692618 delete generate_data.logprobs;
25702619 delete generate_data.logit_bias;
25712620 delete generate_data.top_logprobs;
@@ -2573,12 +2622,11 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
25732622 }
25742623
25752624 // https://api-docs.deepseek.com/api/create-chat-completion
25762625 if (isDeepSeeksettings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
25772626 generate_data.top_p = generate_data.top_p || Number.EPSILON;
25782627 }
25792628
25802629 if (isXAIsettings.chat_completion_source === chat_completion_sources.XAI) {
2581- const model = generate_data.model;
25822630 if (model.includes('grok-3-mini')) {
25832631 delete generate_data.presence_penalty;
25842632 delete generate_data.frequency_penalty;
@@ -2599,61 +2647,44 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
25992647 }
26002648 }
26012649
26022650 if (isPollinationssettings.chat_completion_source === chat_completion_sources.POLLINATIONS) {
26032651 delete generate_data.max_tokens;
26042652 }
26052653
26062654 // https://docs.electronhub.ai/api-reference/chat/completions
26072655 if (isElectronHubsettings.chat_completion_source === chat_completion_sources.ELECTRONHUB) {
26082656 generate_data['top_k'] = Number(oai_settingssettings.top_k_openai);
26092657 }
26102658
26112659 if (isChutessettings.chat_completion_source === chat_completion_sources.CHUTES) {
26122660 generate_data['min_p'] = Number(oai_settingssettings.min_p_openai);
26132661 generate_data['top_k'] = oai_settingssettings.top_k_openai > 0 ? Number(oai_settingssettings.top_k_openai) : undefined;
26142662 generate_data['repetition_penalty'] = Number(oai_settingssettings.repetition_penalty_openai);
2615- generate_data['seed'] = oai_settings.seed >= 0 ? oai_settings.seed : undefined;
26162663 generate_data['stop'] = getCustomStoppingStrings();
26172664 }
26182665
26192666 // https://docs.z.ai/api-reference/llm/chat-completion
26202667 if (isZaisettings.chat_completion_source === chat_completion_sources.ZAI) {
26212668 generate_data['top_p'] = generate_data.top_p || 0.01;
26222669 generate_data['stop'] = getCustomStoppingStrings(1);
26232670 generate_data['zai_endpoint'] = oai_settingssettings.zai_endpoint || ZAI_ENDPOINT.COMMON;
26242671 delete generate_data.presence_penalty;
26252672 delete generate_data.frequency_penalty;
26262673 }
26272674
26282675 // https://docs.nano-gpt.com/api-reference/endpoint/chat-completion#temperature-&-nucleus
26292676 if (isNanoGPTsettings.chat_completion_source === chat_completion_sources.NANOGPT) {
26302677 generate_data['top_k'] = Number(oai_settingssettings.top_k_openai);
26312678 generate_data['min_p'] = Number(oai_settingssettings.min_p_openai);
26322679 generate_data['repetition_penalty'] = Number(oai_settingssettings.repetition_penalty_openai);
26332680 generate_data['top_a'] = Number(oai_settingssettings.top_a_openai);
26342681 }
26352682
2636- const seedSupportedSources = [
2683+ if (seedSupportedSources.includes(settings.chat_completion_source) && settings.seed >= 0) {
2637- chat_completion_sources.OPENAI,
2684+ generate_data['seed'] = settings.seed;
2638- chat_completion_sources.AZURE_OPENAI,
2639- chat_completion_sources.OPENROUTER,
2640- chat_completion_sources.MISTRALAI,
2641- chat_completion_sources.CUSTOM,
2642- chat_completion_sources.COHERE,
2643- chat_completion_sources.GROQ,
2644- chat_completion_sources.ELECTRONHUB,
2645- chat_completion_sources.NANOGPT,
2646- chat_completion_sources.XAI,
2647- chat_completion_sources.POLLINATIONS,
2648- chat_completion_sources.AIMLAPI,
2649- chat_completion_sources.VERTEXAI,
2650- chat_completion_sources.MAKERSUITE,
2651- ];
2652- if (seedSupportedSources.includes(oai_settings.chat_completion_source) && oai_settings.seed >= 0) {
2653- generate_data['seed'] = oai_settings.seed;
26542685 }
26552686
26562687 if ((isOAI && /^(o1|o3|o4)/gptSources.testincludes(model)settings.chat_completion_source) || (isAzureOpenAI && /^(o1|o3|o4)/.test(model))) {
26572688 generate_data.max_completion_tokens = generate_data.max_tokens;
26582689 delete generate_data.max_tokens;
26592690 delete generate_data.logprobs;
@@ -2676,7 +2707,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
26762707 }
26772708 }
26782709
26792710 if ((isOAI && /^gpt-5/gptSources.testincludes(model)settings.chat_completion_source) || (isAzureOpenAI && /^gpt-5/.test(model))) {
26802711 generate_data.max_completion_tokens = generate_data.max_tokens;
26812712 delete generate_data.max_tokens;
26822713 delete generate_data.logprobs;
@@ -2703,6 +2734,26 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
27032734 generate_data.json_schema = jsonSchema;
27042735 }
27052736
2737+ return { generate_data, stream, canMultiSwipe };
2738+}
2739+
2740+/**
2741+ * Send a chat completion request to backend
2742+ * @param {string} type Request type (impersonate, quiet, continue, etc)
2743+ * @param {ChatCompletionMessage[]} messages Array of chat completion messages
2744+ * @param {AbortSignal?} signal Abort signal for request cancellation
2745+ * @param {import('../script.js').AdditionalRequestOptions} options Additional request options
2746+ * @returns {Promise<unknown>}
2747+ * @throws {Error}
2748+ */
2749+async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } = {}) {
2750+ // Provide default abort signal
2751+ if (!signal) {
2752+ signal = new AbortController().signal;
2753+ }
2754+
2755+ const model = getChatCompletionModel(oai_settings);
2756+ const { generate_data, stream, canMultiSwipe } = await createGenerationParameters(oai_settings, model, type, messages, { jsonSchema });
27062757 await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data);
27072758
27082759 const generate_url = '/api/backends/chat-completions/generate';
public/scripts/reasoning.js+19 -4
@@ -1220,23 +1220,38 @@ export function removeReasoningFromString(str) {
12201220}
12211221
12221222/**
1223- * Parses reasoning from a string using the power user reasoning settings.
1223+ * Returns the reasoning template object from its name
1224+ * @param {string} name of the template
1225+ * @returns {ReasoningTemplate} the reasoning template object
1226+ * @throws {Error}
1227+ */
1228+export function getReasoningTemplateByName(name) {
1229+ const template = reasoning_templates.find(p => p.name === name);
1230+ if (!template) throw new Error(`Unknown reasoning template name: "${name}"`);
1231+ return template;
1232+}
1233+
1234+/**
1235+ * Parses reasoning from a string using the power user reasoning settings or optional template.
12241236 * @typedef {Object} ParsedReasoning
12251237 * @property {string} reasoning Reasoning block
12261238 * @property {string} content Message content
12271239 * @param {string} str Content of the message
12281240 * @param {Object} options Optional arguments
12291241 * @param {boolean} [options.strict=true] Whether the reasoning block **has** to be at the beginning of the provided string (excluding whitespaces), or can be anywhere in it
1242+ * @param {ReasoningTemplate} template Optional reasoning template to use instead of power_user.reasoning
12301243 * @returns {ParsedReasoning|null} Parsed reasoning block and message content
12311244 */
12321245export function parseReasoningFromString(str, { strict = true } = {}, template = null) {
1246+ template = template ?? power_user.reasoning; // if no template given, use the currently selected template
1247+
12331248 // Both prefix and suffix must be defined
12341249 if (!power_user.reasoningtemplate.prefix || !power_user.reasoningtemplate.suffix) {
12351250 return null;
12361251 }
12371252
12381253 try {
12391254 const regex = new RegExp(`${(strict ? '^\\s*?' : '')}${escapeRegex(power_user.reasoningtemplate.prefix)}(.*?)${escapeRegex(power_user.reasoningtemplate.suffix)}`, 's');
12401255
12411256 let didReplace = false;
12421257 let reasoning = '';
public/scripts/st-context.js+2 -1
@@ -99,7 +99,7 @@ import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariabl
9999import { convertCharacterBook, getWorldInfoPrompt, loadWorldInfo, reloadEditor, saveWorldInfo, updateWorldInfoList } from './world-info.js';
100100import { ChatCompletionService, TextCompletionService } from './custom-request.js';
101101import { ConnectionManagerRequestService } from './extensions/shared.js';
102102import { updateReasoningUI, parseReasoningFromString, getReasoningTemplateByName } from './reasoning.js';
103103import { IGNORE_SYMBOL } from './constants.js';
104104
105105export function getContext() {
@@ -259,6 +259,7 @@ export function getContext() {
259259 ConnectionManagerRequestService,
260260 updateReasoningUI,
261261 parseReasoningFromString,
262+ getReasoningTemplateByName,
262263 unshallowCharacter,
263264 unshallowGroupMembers,
264265 openThirdPartyExtensionMenu,
public/scripts/textgen-settings.js+147 -111
@@ -139,7 +139,7 @@ export const SERVER_INPUTS = {
139139};
140140
141141const KOBOLDCPP_ORDER = [6, 0, 1, 3, 4, 2, 5];
142142export const settingstextgenerationwebui_settings = {
143143 temp: 0.7,
144144 temperature_last: true,
145145 top_p: 0.5,
@@ -231,7 +231,6 @@ const settings = {
231231};
232232
233233export {
234- settings as textgenerationwebui_settings,
235234 showSamplerControls as showTGSamplerControls,
236235};
237236
@@ -318,7 +317,7 @@ export const setting_names = [
318317const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba');
319318
320319export function validateTextGenUrl() {
321320 const selector = SERVER_INPUTS[settingstextgenerationwebui_settings.type];
322321
323322 if (!selector) {
324323 return;
@@ -342,7 +341,7 @@ export function validateTextGenUrl() {
342341 * @returns {string} API URL
343342 */
344343export function getTextGenServer(type = null) {
345344 const selectedType = type ?? settingstextgenerationwebui_settings.type;
346345 switch (selectedType) {
347346 case FEATHERLESS:
348347 return FEATHERLESS_SERVER;
@@ -357,7 +356,7 @@ export function getTextGenServer(type = null) {
357356 case OPENROUTER:
358357 return OPENROUTER_SERVER;
359358 default:
360359 return settingstextgenerationwebui_settings.server_urls[selectedType] ?? '';
361360 }
362361}
363362
@@ -368,7 +367,7 @@ async function selectPreset(name) {
368367 return;
369368 }
370369
371370 settingstextgenerationwebui_settings.preset = name;
372371 for (const name of setting_names) {
373372 const value = preset[name];
374373 setSettingByName(name, value, true);
@@ -382,7 +381,7 @@ async function selectPreset(name) {
382381export function formatTextGenURL(value) {
383382 try {
384383 const noFormatTypes = [MANCER, TOGETHERAI, INFERMATICAI, DREAMGEN, OPENROUTER];
385384 if (noFormatTypes.includes(settingstextgenerationwebui_settings.type)) {
386385 return value;
387386 }
388387
@@ -399,7 +398,7 @@ function convertPresets(presets) {
399398}
400399
401400function getTokenizerForTokenIds() {
402401 if (power_user.tokenizer === tokenizers.API_CURRENT && TEXTGEN_TOKENIZERS.includes(settingstextgenerationwebui_settings.type)) {
403402 return tokenizers.API_CURRENT;
404403 }
405404
@@ -407,11 +406,11 @@ function getTokenizerForTokenIds() {
407406 return power_user.tokenizer;
408407 }
409408
410409 if (settingstextgenerationwebui_settings.type === OPENROUTER) {
411410 return getCurrentOpenRouterModelTokenizer();
412411 }
413412
414413 if (settingstextgenerationwebui_settings.type === DREAMGEN) {
415414 return getCurrentDreamGenModelTokenizer();
416415 }
417416
@@ -419,10 +418,13 @@ function getTokenizerForTokenIds() {
419418}
420419
421420/**
421+ * Gets the custom token bans from settings and macros.
422+ * @param {TextCompletionSettings} settings Text completion settings to use
422423 * @typedef {{banned_tokens: string, banned_strings: string[]}} TokenBanResult
423424 * @returns {TokenBanResult} String with comma-separated banned token IDs
424425 */
425426function getCustomTokenBans(settings = null) {
427+ settings = settings ?? textgenerationwebui_settings;
426428 if (!settings.send_banned_tokens || (!settings.banned_tokens && !settings.global_banned_tokens && !textgenerationwebui_banned_in_macros.length)) {
427429 return {
428430 banned_tokens: '',
@@ -491,15 +493,18 @@ function getCustomTokenBans() {
491493function toggleBannedStringsKillSwitch(isEnabled, title) {
492494 $('#send_banned_tokens_textgenerationwebui').prop('checked', isEnabled);
493495 $('#send_banned_tokens_label').find('.menu_button').toggleClass('toggleEnabled', isEnabled).prop('title', title);
494496 settingstextgenerationwebui_settings.send_banned_tokens = isEnabled;
495497 saveSettingsDebounced();
496498}
497499
498500/**
499501 * Calculates logit bias object from the logit bias list.
502+ * @param {TextCompletionSettings} settings Text completion settings
500503 * @returns {object} Logit bias object
501504 */
502505function calculateLogitBias(settings = null) {
506+ settings = settings ?? textgenerationwebui_settings;
507+
503508 if (!Array.isArray(settings.logit_bias) || settings.logit_bias.length === 0) {
504509 return {};
505510 }
@@ -535,25 +540,25 @@ export async function loadTextGenSettings(data, loadedSettings) {
535540 await loadApiSelectedSamplers();
536541 textgenerationwebui_presets = convertPresets(data.textgenerationwebui_presets);
537542 textgenerationwebui_preset_names = data.textgenerationwebui_preset_names ?? [];
538543 Object.assign(settingstextgenerationwebui_settings, loadedSettings.textgenerationwebui_settings ?? {});
539544
540545 if (loadedSettings.api_server_textgenerationwebui) {
541546 for (const type of Object.keys(SERVER_INPUTS)) {
542547 settingstextgenerationwebui_settings.server_urls[type] = loadedSettings.api_server_textgenerationwebui;
543548 }
544549 delete loadedSettings.api_server_textgenerationwebui;
545550 }
546551
547552 for (const [type, selector] of Object.entries(SERVER_INPUTS)) {
548553 const control = $(selector);
549554 control.val(settingstextgenerationwebui_settings.server_urls[type] ?? '').on('input', function () {
550555 settingstextgenerationwebui_settings.server_urls[type] = String($(this).val()).trim();
551556 saveSettingsDebounced();
552557 });
553558 }
554559
555560 if (loadedSettings.api_use_mancer_webui) {
556561 settingstextgenerationwebui_settings.type = MANCER;
557562 }
558563
559564 for (const name of textgenerationwebui_preset_names) {
@@ -563,20 +568,20 @@ export async function loadTextGenSettings(data, loadedSettings) {
563568 $('#settings_preset_textgenerationwebui').append(option);
564569 }
565570
566571 if (settingstextgenerationwebui_settings.preset) {
567572 $('#settings_preset_textgenerationwebui').val(settingstextgenerationwebui_settings.preset);
568573 }
569574
570575 for (const i of setting_names) {
571576 const value = settingstextgenerationwebui_settings[i];
572577 setSettingByName(i, value);
573578 }
574579
575580 $('#textgen_type').val(settingstextgenerationwebui_settings.type);
576581 $('#openrouter_providers_text').val(settingstextgenerationwebui_settings.openrouter_providers).trigger('change');
577582 showSamplerControls(settingstextgenerationwebui_settings.type);
578583 BIAS_CACHE.delete(BIAS_KEY);
579584 displayLogitBias(settingstextgenerationwebui_settings.logit_bias, BIAS_KEY);
580585
581586 registerDebugFunction('change-mancer-url', 'Change Mancer base URL', 'Change Mancer API server base URL', () => {
582587 const result = prompt(`Enter Mancer base URL\nDefault: ${MANCER_SERVER_DEFAULT}`, MANCER_SERVER);
@@ -648,7 +653,7 @@ async function getStatusTextgen() {
648653 return resultCheckStatus();
649654 }
650655
651656 if ([textgen_types.GENERIC, textgen_types.OOBA].includes(settingstextgenerationwebui_settings.type) && settingstextgenerationwebui_settings.bypass_status_check) {
652657 setOnlineStatus(t`Status check bypassed`);
653658 return resultCheckStatus();
654659 }
@@ -659,46 +664,46 @@ async function getStatusTextgen() {
659664 headers: getRequestHeaders(),
660665 body: JSON.stringify({
661666 api_server: endpoint,
662667 api_type: settingstextgenerationwebui_settings.type,
663668 }),
664669 signal: abortStatusCheck.signal,
665670 });
666671
667672 const data = await response.json();
668673
669674 if (settingstextgenerationwebui_settings.type === textgen_types.MANCER) {
670675 loadMancerModels(data?.data);
671676 setOnlineStatus(settingstextgenerationwebui_settings.mancer_model);
672677 } else if (settingstextgenerationwebui_settings.type === textgen_types.TOGETHERAI) {
673678 loadTogetherAIModels(data?.data);
674679 setOnlineStatus(settingstextgenerationwebui_settings.togetherai_model);
675680 } else if (settingstextgenerationwebui_settings.type === textgen_types.OLLAMA) {
676681 loadOllamaModels(data?.data);
677682 setOnlineStatus(settingstextgenerationwebui_settings.ollama_model || t`Connected`);
678683 } else if (settingstextgenerationwebui_settings.type === textgen_types.INFERMATICAI) {
679684 loadInfermaticAIModels(data?.data);
680685 setOnlineStatus(settingstextgenerationwebui_settings.infermaticai_model);
681686 } else if (settingstextgenerationwebui_settings.type === textgen_types.DREAMGEN) {
682687 loadDreamGenModels(data?.data);
683688 setOnlineStatus(settingstextgenerationwebui_settings.dreamgen_model);
684689 } else if (settingstextgenerationwebui_settings.type === textgen_types.OPENROUTER) {
685690 loadOpenRouterModels(data?.data);
686691 setOnlineStatus(settingstextgenerationwebui_settings.openrouter_model);
687692 } else if (settingstextgenerationwebui_settings.type === textgen_types.VLLM) {
688693 loadVllmModels(data?.data);
689694 setOnlineStatus(settingstextgenerationwebui_settings.vllm_model);
690695 } else if (settingstextgenerationwebui_settings.type === textgen_types.APHRODITE) {
691696 loadAphroditeModels(data?.data);
692697 setOnlineStatus(settingstextgenerationwebui_settings.aphrodite_model);
693698 } else if (settingstextgenerationwebui_settings.type === textgen_types.FEATHERLESS) {
694699 loadFeatherlessModels(data?.data);
695700 setOnlineStatus(settingstextgenerationwebui_settings.featherless_model);
696701 } else if (settingstextgenerationwebui_settings.type === textgen_types.TABBY) {
697702 loadTabbyModels(data?.data);
698703 setOnlineStatus(settingstextgenerationwebui_settings.tabby_model || data?.result);
699704 } else if (settingstextgenerationwebui_settings.type === textgen_types.GENERIC) {
700705 loadGenericModels(data?.data);
701706 setOnlineStatus(settingstextgenerationwebui_settings.generic_model || data?.result || t`Connected`);
702707 } else {
703708 setOnlineStatus(data?.result);
704709 }
@@ -718,7 +723,7 @@ async function getStatusTextgen() {
718723 const wantsInstructDerivation = !autoSelected && (power_user.instruct.enabled && power_user.instruct_derived);
719724 const wantsContextDerivation = !autoSelected && power_user.context_derived;
720725 const wantsContextSize = power_user.context_size_derived;
721726 const supportsChatTemplate = [textgen_types.KOBOLDCPP, textgen_types.LLAMACPP].includes(settingstextgenerationwebui_settings.type);
722727
723728 if (supportsChatTemplate && (wantsInstructDerivation || wantsContextDerivation || wantsContextSize)) {
724729 const response = await fetch('/api/backends/text-completions/props', {
@@ -726,7 +731,7 @@ async function getStatusTextgen() {
726731 headers: getRequestHeaders(),
727732 body: JSON.stringify({
728733 api_server: endpoint,
729734 api_type: settingstextgenerationwebui_settings.type,
730735 }),
731736 });
732737
@@ -795,15 +800,15 @@ export function initTextGenSettings() {
795800 $('#koboldcpp_order').children().each(function () {
796801 order.push($(this).data('id'));
797802 });
798803 settingstextgenerationwebui_settings.sampler_order = order;
799804 console.log('Samplers reordered:', settingstextgenerationwebui_settings.sampler_order);
800805 saveSettingsDebounced();
801806 },
802807 });
803808
804809 $('#koboldcpp_default_order').on('click', function () {
805810 settingstextgenerationwebui_settings.sampler_order = KOBOLDCPP_ORDER;
806811 sortKoboldItemsByOrder(settingstextgenerationwebui_settings.sampler_order);
807812 saveSettingsDebounced();
808813 });
809814
@@ -814,16 +819,16 @@ export function initTextGenSettings() {
814819 $('#llamacpp_samplers_sortable').children().each(function () {
815820 order.push($(this).data('name'));
816821 });
817822 settingstextgenerationwebui_settings.samplers = order;
818823 console.log('Samplers reordered:', settingstextgenerationwebui_settings.samplers);
819824 saveSettingsDebounced();
820825 },
821826 });
822827
823828 $('#llamacpp_samplers_default_order').on('click', function () {
824829 sortLlamacppItemsByOrder(LLAMACPP_DEFAULT_ORDER);
825830 settingstextgenerationwebui_settings.samplers = LLAMACPP_DEFAULT_ORDER;
826831 console.log('Default samplers order loaded:', settingstextgenerationwebui_settings.samplers);
827832 saveSettingsDebounced();
828833 });
829834
@@ -834,8 +839,8 @@ export function initTextGenSettings() {
834839 $('#sampler_priority_container').children().each(function () {
835840 order.push($(this).data('name'));
836841 });
837842 settingstextgenerationwebui_settings.sampler_priority = order;
838843 console.log('Samplers reordered:', settingstextgenerationwebui_settings.sampler_priority);
839844 saveSettingsDebounced();
840845 },
841846 });
@@ -847,8 +852,8 @@ export function initTextGenSettings() {
847852 $('#sampler_priority_container_aphrodite').children().each(function () {
848853 order.push($(this).data('name'));
849854 });
850855 settingstextgenerationwebui_settings.samplers_priorities = order;
851856 console.log('Samplers reordered:', settingstextgenerationwebui_settings.samplers_priorities);
852857 saveSettingsDebounced();
853858 },
854859 });
@@ -858,12 +863,12 @@ export function initTextGenSettings() {
858863
859864 if (json_schema_string) {
860865 try {
861866 settingstextgenerationwebui_settings.json_schema = JSON.parse(json_schema_string);
862867 } catch {
863868 settingstextgenerationwebui_settings.json_schema = null;
864869 }
865870 } else {
866871 settingstextgenerationwebui_settings.json_schema = null;
867872 }
868873
869874 saveSettingsDebounced();
@@ -871,38 +876,38 @@ export function initTextGenSettings() {
871876
872877 $('#textgenerationwebui_default_order').on('click', function () {
873878 sortOobaItemsByOrder(OOBA_DEFAULT_ORDER);
874879 settingstextgenerationwebui_settings.sampler_priority = OOBA_DEFAULT_ORDER;
875880 console.log('Default samplers order loaded:', settingstextgenerationwebui_settings.sampler_priority);
876881 saveSettingsDebounced();
877882 });
878883
879884 $('#aphrodite_default_order').on('click', function () {
880885 sortAphroditeItemsByOrder(APHRODITE_DEFAULT_ORDER);
881886 settingstextgenerationwebui_settings.samplers_priorities = APHRODITE_DEFAULT_ORDER;
882887 console.log('Default samplers order loaded:', settingstextgenerationwebui_settings.samplers_priorities);
883888 saveSettingsDebounced();
884889 });
885890
886891 $('#textgen_type').on('change', function () {
887892 const type = String($(this).val());
888893 settingstextgenerationwebui_settings.type = type;
889894
890895 if ([VLLM, APHRODITE, INFERMATICAI].includes(settingstextgenerationwebui_settings.type)) {
891896 $('#mirostat_mode_textgenerationwebui').attr('step', 2); //Aphro disallows mode 1
892897 $('#do_sample_textgenerationwebui').prop('checked', true); //Aphro should always do sample; 'otherwise set temp to 0 to mimic no sample'
893898 $('#ban_eos_token_textgenerationwebui').prop('checked', false); //Aphro should not ban EOS, just ignore it; 'add token '2' to ban list do to this'
894899 //special handling for vLLM/Aphrodite topK -1 disable state
895900 $('#top_k_textgenerationwebui').attr('min', -1);
896901 if ($('#top_k_textgenerationwebui').val() === '0' || settingstextgenerationwebui_settings['top_k'] === 0) {
897902 settingstextgenerationwebui_settings['top_k'] = -1;
898903 $('#top_k_textgenerationwebui').val('-1').trigger('input');
899904 }
900905 } else {
901906 $('#mirostat_mode_textgenerationwebui').attr('step', 1);
902907 //undo special vLLM/Aphrodite setup for topK
903908 $('#top_k_textgenerationwebui').attr('min', 0);
904909 if ($('#top_k_textgenerationwebui').val() === '-1' || settingstextgenerationwebui_settings['top_k'] === -1) {
905910 settingstextgenerationwebui_settings['top_k'] = 0;
906911 $('#top_k_textgenerationwebui').val('0').trigger('input');
907912 }
908913 }
@@ -913,7 +918,7 @@ export function initTextGenSettings() {
913918
914919 $('#main_api').trigger('change');
915920
916921 if (!SERVER_INPUTS[type] || settingstextgenerationwebui_settings.server_urls[type]) {
917922 $('#api_button_textgenerationwebui').trigger('click');
918923 }
919924
@@ -929,7 +934,7 @@ export function initTextGenSettings() {
929934 $('#samplerResetButton').off('click').on('click', function () {
930935 const inputs = {
931936 'temp_textgenerationwebui': 1,
932937 'top_k_textgenerationwebui': [INFERMATICAI, APHRODITE, VLLM].includes(settingstextgenerationwebui_settings.type) ? -1 : 0,
933938 'top_p_textgenerationwebui': 1,
934939 'min_p_textgenerationwebui': 0,
935940 'rep_pen_textgenerationwebui': 1,
@@ -1007,19 +1012,19 @@ export function initTextGenSettings() {
10071012
10081013 if (isCheckbox) {
10091014 const value = $(this).prop('checked');
10101015 settingstextgenerationwebui_settings[id] = value;
10111016 }
10121017 else if (isText) {
10131018 const value = $(this).val();
10141019 settingstextgenerationwebui_settings[id] = value;
10151020 }
10161021 else {
10171022 const value = Number($(this).val());
10181023 $(`#${id}_counter_textgenerationwebui`).val(value);
10191024 settingstextgenerationwebui_settings[id] = value;
10201025 //special handling for vLLM/Aphrodite using -1 as disabled instead of 0
10211026 if ($(this).attr('id') === 'top_k_textgenerationwebui' && [INFERMATICAI, APHRODITE, VLLM].includes(settingstextgenerationwebui_settings.type) && value === 0) {
10221027 settingstextgenerationwebui_settings[id] = -1;
10231028 $(this).val(-1);
10241029 }
10251030 }
@@ -1027,7 +1032,7 @@ export function initTextGenSettings() {
10271032 });
10281033 }
10291034
10301035 $('#textgen_logit_bias_new_entry').on('click', () => createNewLogitBiasEntry(settingstextgenerationwebui_settings.logit_bias, BIAS_KEY));
10311036
10321037 $('#openrouter_providers_text').on('change', function () {
10331038 const selectedProviders = $(this).val();
@@ -1037,7 +1042,7 @@ export function initTextGenSettings() {
10371042 return;
10381043 }
10391044
10401045 settingstextgenerationwebui_settings.openrouter_providers = selectedProviders;
10411046
10421047 saveSettingsDebounced();
10431048 });
@@ -1086,10 +1091,10 @@ function showSamplerControls(apiType = null) {
10861091 if (!typeSpecificControlled) $(this).show();
10871092 });
10881093
10891094 showTypeSpecificControls(apiType ?? settingstextgenerationwebui_settings.type);
10901095
10911096 const prioritizeManualSamplerSelect = isSamplerManualPriorityEnabled(apiType ?? settingstextgenerationwebui_settings.type);
10921097 const samplersActivatedManually = getActiveManualApiSamplers(apiType ?? settingstextgenerationwebui_settings.type);
10931098
10941099 if (!samplersActivatedManually?.length || !prioritizeManualSamplerSelect) return;
10951100
@@ -1150,13 +1155,13 @@ function insertMissingArrayItems(source, target) {
11501155function setSettingByName(setting, value, trigger) {
11511156 if ('extensions' === setting) {
11521157 value = value || {};
11531158 settingstextgenerationwebui_settings.extensions = value;
11541159 return;
11551160 }
11561161
11571162 if ('json_schema' === setting) {
11581163 settingstextgenerationwebui_settings.json_schema = value ?? null;
11591164 $('#tabby_json_schema').val(value ? JSON.stringify(settingstextgenerationwebui_settings.json_schema, null, 2) : '');
11601165 return;
11611166 }
11621167
@@ -1167,7 +1172,7 @@ function setSettingByName(setting, value, trigger) {
11671172 if ('sampler_order' === setting) {
11681173 value = Array.isArray(value) ? value : KOBOLDCPP_ORDER;
11691174 sortKoboldItemsByOrder(value);
11701175 settingstextgenerationwebui_settings.sampler_order = value;
11711176 return;
11721177 }
11731178
@@ -1175,7 +1180,7 @@ function setSettingByName(setting, value, trigger) {
11751180 value = Array.isArray(value) ? value : OOBA_DEFAULT_ORDER;
11761181 insertMissingArrayItems(OOBA_DEFAULT_ORDER, value);
11771182 sortOobaItemsByOrder(value);
11781183 settingstextgenerationwebui_settings.sampler_priority = value;
11791184 return;
11801185 }
11811186
@@ -1183,7 +1188,7 @@ function setSettingByName(setting, value, trigger) {
11831188 value = Array.isArray(value) ? value : APHRODITE_DEFAULT_ORDER;
11841189 insertMissingArrayItems(APHRODITE_DEFAULT_ORDER, value);
11851190 sortAphroditeItemsByOrder(value);
11861191 settingstextgenerationwebui_settings.samplers_priorities = value;
11871192 return;
11881193 }
11891194
@@ -1191,12 +1196,12 @@ function setSettingByName(setting, value, trigger) {
11911196 value = Array.isArray(value) ? value : LLAMACPP_DEFAULT_ORDER;
11921197 insertMissingArrayItems(LLAMACPP_DEFAULT_ORDER, value);
11931198 sortLlamacppItemsByOrder(value);
11941199 settingstextgenerationwebui_settings.samplers = value;
11951200 return;
11961201 }
11971202
11981203 if ('logit_bias' === setting) {
11991204 settingstextgenerationwebui_settings.logit_bias = Array.isArray(value) ? value : [];
12001205 return;
12011206 }
12021207
@@ -1234,8 +1239,8 @@ function setSettingByName(setting, value, trigger) {
12341239
12351240/**
12361241 * Sends a streaming request for textgenerationwebui.
12371242 * @param {object} generate_data
12381243 * @param {AbortSignal} signal
12391244 * @returns {Promise<(function(): AsyncGenerator<{swipes: [], text: string, toolCalls: [], logprobs: {token: string, topLogprobs: Candidate[]}|null}, void, *>)|*>}
12401245 * @throws {Error} - If the response status is not OK, or from within the generator
12411246 */
@@ -1304,7 +1309,7 @@ export function parseTextgenLogprobs(token, logprobs) {
13041309 return null;
13051310 }
13061311
13071312 switch (settingstextgenerationwebui_settings.type) {
13081313 case KOBOLDCPP:
13091314 case TABBY:
13101315 case VLLM:
@@ -1406,7 +1411,13 @@ function toIntArray(string) {
14061411 return string.split(',').map(x => parseInt(x)).filter(x => !isNaN(x));
14071412}
14081413
1409-export function getTextGenModel() {
1414+/**
1415+ * Gets the text generation model specified by the given text completion settings
1416+ * @param {TextCompletionSettings} settings Text completion settings to use
1417+ * @returns {string} model name
1418+ */
1419+export function getTextGenModel(settings = null) {
1420+ settings = settings ?? textgenerationwebui_settings;
14101421 switch (settings.type) {
14111422 case OOBA:
14121423 if (settings.custom_model) {
@@ -1455,10 +1466,16 @@ export function getTextGenModel() {
14551466}
14561467
14571468export function isJsonSchemaSupported() {
14581469 return [TABBY, LLAMACPP].includes(settingstextgenerationwebui_settings.type) && main_api === 'textgenerationwebui';
14591470}
14601471
1461-function isDynamicTemperatureSupported() {
1472+/**
1473+ * Returns whether dynamic temperature is supported by the given text completion settings
1474+ * @param {TextCompletionSettings} settings Text completion settings to use
1475+ * @returns {boolean} Whether dynamic temperature supported
1476+ */
1477+function isDynamicTemperatureSupported(settings = null) {
1478+ settings = settings ?? textgenerationwebui_settings;
14621479 return settings.dynatemp && DYNATEMP_BLOCK?.dataset?.tgType?.includes(settings.type);
14631480}
14641481
@@ -1468,7 +1485,7 @@ function isDynamicTemperatureSupported() {
14681485 * @returns {number} Number of logprobs to request
14691486 */
14701487export function getLogprobsNumber(type = null) {
14711488 const selectedType = type ?? settingstextgenerationwebui_settings.type;
14721489 if (selectedType === VLLM || selectedType === INFERMATICAI) {
14731490 return 5;
14741491 }
@@ -1504,10 +1521,25 @@ export function replaceMacrosInList(str) {
15041521 }
15051522}
15061523
1507-export async function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) {
1524+/**
1525+ * Build the generation parameter object for an text completion request
1526+ * @param {TextCompletionSettings} settings Text completion settings to use
1527+ * @param {string} model Model to use
1528+ * @param {string} finalPrompt The final prompt to send
1529+ * @param {number} maxTokens Max allowed generation tokens
1530+ * @param {boolean} isImpersonate Whether this is for an impersonation
1531+ * @param {boolean} isContinue Whether this is for a continue
1532+ * @param {object} cfgValues Additional parameters (guidanceScale, negativePrompt)
1533+ * @param {string} type Request type (impersonate, quiet, continue, etc)
1534+ * @returns {object} Final generation parameters object appropriate for the text completion source
1535+ */
1536+export function createTextGenGenerationData(settings, model, finalPrompt = null, maxTokens = null, isImpersonate = false, isContinue = false, cfgValues = null, type = 'quiet') {
1537+ settings = settings ?? textgenerationwebui_settings;
1538+ model = model ?? getTextGenModel(settings);
1539+
15081540 const canMultiSwipe = !isContinue && !isImpersonate && type !== 'quiet';
15091541 const dynatemp = isDynamicTemperatureSupported(settings);
15101542 const { banned_tokens, banned_strings } = getCustomTokenBans(settings);
15111543 const jsonSchema = isObject(settings.json_schema)
15121544 ? settings.json_schema_allow_empty
15131545 ? settings.json_schema
@@ -1516,10 +1548,10 @@ export async function getTextGenGenerationData(finalPrompt, maxTokens, isImperso
15161548
15171549 let params = {
15181550 'prompt': finalPrompt,
15191551 'model': getTextGenModel()model,
15201552 'max_new_tokens': maxTokens,
15211553 'max_tokens': maxTokens,
15221554 'logprobs': power_user.request_token_probabilities ? getLogprobsNumber(settings.type) : undefined,
15231555 'temperature': dynatemp ? (settings.min_temp + settings.max_temp) / 2 : settings.temp,
15241556 'top_p': settings.top_p,
15251557 'typical_p': settings.typical_p,
@@ -1571,7 +1603,7 @@ export async function getTextGenGenerationData(finalPrompt, maxTokens, isImperso
15711603 banned_tokens,
15721604 'banned_strings': banned_strings,
15731605 'api_type': settings.type,
15741606 'api_server': getTextGenServer(settings.type),
15751607 'sampler_order': settings.type === textgen_types.KOBOLDCPP ? settings.sampler_order : undefined,
15761608 'xtc_threshold': settings.xtc_threshold,
15771609 'xtc_probability': settings.xtc_probability,
@@ -1713,7 +1745,7 @@ export async function getTextGenGenerationData(finalPrompt, maxTokens, isImperso
17131745 }
17141746
17151747 if (Array.isArray(settings.logit_bias) && settings.logit_bias.length) {
17161748 const logitBias = BIAS_CACHE.get(BIAS_KEY) || calculateLogitBias(settings);
17171749 BIAS_CACHE.set(BIAS_KEY, logitBias);
17181750 params.logit_bias = logitBias;
17191751 }
@@ -1750,8 +1782,12 @@ export async function getTextGenGenerationData(finalPrompt, maxTokens, isImperso
17501782 delete params.guided_json;
17511783 }
17521784 }
1785+ return params;
1786+}
17531787
1788+export async function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) {
1789+ const model = getTextGenModel(textgenerationwebui_settings);
1790+ const params = createTextGenGenerationData(textgenerationwebui_settings, model, finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type);
17541791 await eventSource.emit(event_types.TEXT_COMPLETION_SETTINGS_READY, params);
1755-
17561792 return params;
17571793}
public/scripts/tool-calling.js+28 -48
@@ -1,7 +1,7 @@
11import { DOMPurify } from '../lib.js';
22
33import { addOneMessage, chat, event_types, eventSource, main_api, saveChatConditional, system_avatar, systemUserName } from '../script.js';
44import { chat_completion_sources, custom_prompt_post_processing_types, getChatCompletionModel, model_list, oai_settings } from './openai.js';
55import { Popup } from './popup.js';
66import { SlashCommand } from './slash-commands/SlashCommand.js';
77import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
@@ -589,65 +589,41 @@ export class ToolManager {
589589
590590 /**
591591 * Checks if tool calling is supported for the current settings and generation type.
592+ * @param {ChatCompletionSettings} settings Optional chat completion settings
593+ * @param {string} model Optional model name
592594 * @returns {boolean} Whether tool calling is supported for the given type
593595 */
594596 static isToolCallingSupported(settings = null, model = null) {
595- if (main_api !== 'openai' || !oai_settings.function_calling) {
597+ settings = settings ?? oai_settings;
598+ model = model ?? getChatCompletionModel(settings);
599+
600+ if (main_api !== 'openai' || !settings.function_calling) {
596601 return false;
597602 }
598603
599604 // Post-processing will forcefully remove past tool calls from the prompt, making them useless
600605 const { NONE, MERGE_TOOLS, SEMI_TOOLS, STRICT_TOOLS } = custom_prompt_post_processing_types;
601606 const allowedPromptPostProcessing = [NONE, MERGE_TOOLS, SEMI_TOOLS, STRICT_TOOLS];
602607 if (!allowedPromptPostProcessing.includes(oai_settingssettings.custom_prompt_post_processing)) {
603608 return false;
604609 }
605610
606- if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS && Array.isArray(model_list)) {
611+ const currentModel = Array.isArray(model_list) ? model_list.find(m => m.id === model) : null;
607- const currentModel = model_list.find(model => model.id === oai_settings.pollinations_model);
608612 if (currentModel) {
613+ switch (settings.chat_completion_source) {
614+ case chat_completion_sources.POLLINATIONS:
609615 return currentModel.tools;
610- }
616+ case chat_completion_sources.FIREWORKS:
611- }
612-
613- if (oai_settings.chat_completion_source === chat_completion_sources.FIREWORKS && Array.isArray(model_list)) {
614- const currentModel = model_list.find(model => model.id === oai_settings.fireworks_model);
615- if (currentModel) {
616617 return currentModel.supports_tools;
617- }
618+ case chat_completion_sources.OPENROUTER:
618- }
619+ return currentModel.supported_parameters?.includes('tools');
619-
620+ case chat_completion_sources.MISTRALAI:
620- if (oai_settings.chat_completion_source === chat_completion_sources.OPENROUTER && Array.isArray(model_list)) {
621+ return currentModel.capabilities?.function_calling;
621- const currentModel = model_list.find(model => model.id === oai_settings.openrouter_model);
622+ case chat_completion_sources.AIMLAPI:
622- if (Array.isArray(currentModel?.supported_parameters)) {
623+ return currentModel.features?.includes('openai/chat-completion.function');
623- return currentModel.supported_parameters.includes('tools');
624+ case chat_completion_sources.CHUTES:
624- }
625- }
626-
627- if (oai_settings.chat_completion_source === chat_completion_sources.MISTRALAI && Array.isArray(model_list)) {
628- const currentModel = model_list.find(model => model.id === oai_settings.mistralai_model);
629- if (currentModel && currentModel.capabilities) {
630- return currentModel.capabilities.function_calling;
631- }
632- }
633-
634- if (oai_settings.chat_completion_source === chat_completion_sources.AIMLAPI && Array.isArray(model_list)) {
635- const currentModel = model_list.find(model => model.id === oai_settings.aimlapi_model);
636- if (Array.isArray(currentModel?.features)) {
637- return currentModel.features.includes('openai/chat-completion.function');
638- }
639- }
640-
641- if (oai_settings.chat_completion_source === chat_completion_sources.CHUTES && Array.isArray(model_list)) {
642- const currentModel = model_list.find(model => model.id === oai_settings.chutes_model);
643- if (currentModel) {
644625 return currentModel.supported_features?.includes('tools');
645- }
626+ case chat_completion_sources.ELECTRONHUB:
646- }
647-
648- if (oai_settings.chat_completion_source === chat_completion_sources.ELECTRONHUB && Array.isArray(model_list)) {
649- const currentModel = model_list.find(model => model.id === oai_settings.electronhub_model);
650- if (currentModel) {
651627 return currentModel.metadata?.function_call;
652628 }
653629 }
@@ -676,17 +652,21 @@ export class ToolManager {
676652 chat_completion_sources.ZAI,
677653 chat_completion_sources.SILICONFLOW,
678654 ];
679655 return supportedSources.includes(oai_settingssettings.chat_completion_source);
680656 }
681657
682658 /**
683659 * Checks if tool calls can be performed for the current settings and generation type.
684660 * @param {string} type Generation type
661+ * @param {ChatCompletionSettings} settings Optional chat completion settings
662+ * @param {string} model Optional model name
685663 * @returns {boolean} Whether tool calls can be performed for the given type
686664 */
687665 static canPerformToolCalls(type, settings = null, model = null) {
666+ settings = settings ?? oai_settings;
667+ model = model ?? getChatCompletionModel(settings);
688668 const noToolCallTypes = ['impersonate', 'quiet', 'continue'];
689669 const isSupported = ToolManager.isToolCallingSupported(settings, model);
690670 return isSupported && !noToolCallTypes.includes(type);
691671 }
692672