feat: Add Azure OpenAI chat completions provider (#4456) * feat: Add Azure OpenAI chat completions provider This commit introduces comprehensive support for integrating Azure OpenAI as a new chat completion source within SillyTavern. The implementation specifically navigates Azure's reliance on deployment names, differing from standard model selection. Includes: - **New Feat / UI:** Dedicated settings in `public/index.html` for Azure parameters (Base URL, Deployment Name, API Version, API Key) and a new `public/img/azure_openai.svg` icon. - **Frontend Logic (`public/scripts/openai.js`):** Manages Azure OpenAI settings and dynamic URL generation. The 'Connect' button triggers a `GET /models` endpoint call solely for API configuration validation (key, URL, version). Upon successful validation, a subsequent `POST` request is made to the AI, and the active model is extracted from its response payload, then populated for UI display (no direct user model selection). - **Backend Logic (`src/endpoints/backends/chat-completions.js`):** Implements `sendAzureOpenAIRequest` to proxy requests, handling Azure’s unique authentication and URL structures. Enhanced the `/status` endpoint for Azure-specific validation. - **Secret Management:** Secure handling of Azure OpenAI API keys in `public/scripts/secrets.js` and `src/endpoints/secrets.js`. - **Autoconnect:** `public/scripts/RossAscends-mods.js` modified to enable automatic connection for Azure OpenAI if an API key is present. Impact: - Users can now connect to and utilize Azure OpenAI services. - Requires new configuration details in the UI. - Enhances API routing and validation with Azure-specific behavior. - Navigates model handling: model name is *derived dynamically* from AI response, not directly selected, aligning with internal SillyTavern structure. * fixed the html issues, openai.ujs and secrets.js modified: public/index.html# modified: public/scripts/openai.js modified: public/scripts/secrets.js * Extends Azure OpenAI with advanced capabilities Enables advanced features such as function calling, image inlining, and model reasoning in the UI for Azure OpenAI. Refactors the backend to support structured output (JSON mode), native thinking, and reasoning effort for compatible Azure OpenAI models. Adds support for logprobs, multiple responses (`n`), and seed, aligning with standard OpenAI behavior. Improves request handling with a retry mechanism for rate limits and more robust error reporting. Ensures correct parameter handling for model-specific features and conflicts, such as when native thinking is enabled. Optimizes the Azure OpenAI status probe for efficiency. modified: public/index.html modified: public/scripts/openai.js modified: src/endpoints/backends/chat-completions.js * PR fixes Simplification Removed reasoning logic from backend modified: src/endpoints/backends/chat-completions.js modified: src/endpoints/backends/chat-completions.js * Fix PR comments Removed the front end compiled url UI element and related. Misc simplifications modified: public/index.html modified: public/scripts/openai.js modified: src/endpoints/backends/chat-completions.js * Fixed accidental api temporary disabled modified: public/index.html * Don't transfer status code verbatim * Fix formatting * Enable tool calling * Fix logo coloration * Move model arrays to shared constants * Fix capitalization * Remove obsolete comment * Improve response schema parameter format * Fix tokenizer model selection --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

1147507d06de13ecb137294a7e9156fc4698f288

nfuller286 <80130411+nfuller286@users.noreply.github.com>

Signed
11 files changed, +412 -46Showing whitespace changes
public/img/azure_openai.svg+1 -0
@@ -0,0 +1 @@
1+<svg id="uuid-adbdae8e-5a41-46d1-8c18-aa73cdbfee32" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" height="100px" width="100px" transform="rotate(0) scale(1, 1)"><path d="m0,2.7v12.6c0,1.491,1.209,2.7,2.7,2.7h12.6c1.491,0,2.7-1.209,2.7-2.7V2.7c0-1.491-1.209-2.7-2.7-2.7H2.7C1.209,0,0,1.209,0,2.7ZM10.8,0v3.6c0,3.976,3.224,7.2,7.2,7.2h-3.6c-3.976,0-7.199,3.222-7.2,7.198v-3.598c0-3.976-3.224-7.2-7.2-7.2h3.6c3.976,0,7.2-3.224,7.2-7.2Z" stroke-width="0"/></svg>
public/index.html+57 -13
@@ -652,7 +652,7 @@
652652 <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="65536">
653653 </div>
654654 </div>
655655 <div class="range-block" data-source="openai,custom,xai,aimlapi,moonshot,azure_openai">
656656 <div class="range-block-title" data-i18n="Multiple swipes per generation">
657657 Multiple swipes per generation
658658 </div>
@@ -691,7 +691,7 @@
691691 </span>
692692 </div>
693693 </div>
694694 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai">
695695 <div class="range-block-title" data-i18n="Temperature">
696696 Temperature
697697 </div>
@@ -704,7 +704,7 @@
704704 </div>
705705 </div>
706706 </div>
707707 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai">
708708 <div class="range-block-title" data-i18n="Frequency Penalty">
709709 Frequency Penalty
710710 </div>
@@ -717,7 +717,7 @@
717717 </div>
718718 </div>
719719 </div>
720720 <div class="range-block" data-source="openai,aimlapi,openrouter,custom,cohere,perplexity,groq,mistralai,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai">
721721 <div class="range-block-title" data-i18n="Presence Penalty">
722722 Presence Penalty
723723 </div>
@@ -743,7 +743,7 @@
743743 </div>
744744 </div>
745745 </div>
746746 <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,electronhub,nanogpt,deepseek,xai,pollinations,moonshot,fireworks,cometapi,azure_openai">
747747 <div class="range-block-title" data-i18n="Top P">
748748 Top P
749749 </div>
@@ -980,7 +980,7 @@
980980 </div>
981981 </div>
982982 </div>
983983 <div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,electronhub,nanogpt,xai,pollinations,aimlapi,makersuite,vertexai,azure_openai">
984984 <div class="range-block-title justifyLeft" data-i18n="Seed">
985985 Seed
986986 </div>
@@ -1984,7 +1984,7 @@
19841984 </b>
19851985 </div>
19861986 </div>
19871987 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,aimlapi,openrouter,groq,deepseek,makersuite,vertexai,ai21,xai,pollinations,moonshot,fireworks,cometapi,electronhub,azure_openai">
19881988 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
19891989 <input id="openai_function_calling" type="checkbox" />
19901990 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -1999,7 +1999,7 @@
19991999 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>
20002000 </div>
20012001 </div>
20022002 <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt,electronhub,azure_openai">
20032003 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
20042004 <input id="openai_image_inlining" type="checkbox" />
20052005 <span data-i18n="Send inline images">Send inline images</span>
@@ -2015,7 +2015,7 @@
20152015 <code><i class="fa-solid fa-wand-magic-sparkles"></i></code>
20162016 <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>
20172017 </div>
20182018 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub,azure_openai">
20192019 <div class="flex-container oneline-dropdown">
20202020 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
20212021 Inline Image Quality
@@ -2077,7 +2077,7 @@
20772077 </span>
20782078 </div>
20792079 </div>
20802080 <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi,electronhub,azure_openai">
20812081 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
20822082 <input id="openai_show_thoughts" type="checkbox" />
20832083 <span data-i18n="Request model reasoning">Request model reasoning</span>
@@ -2091,7 +2091,7 @@
20912091 </span>
20922092 </div>
20932093 </div>
20942094 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations,perplexity,cometapi,electronhub,azure_openai">
20952095 <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models.">
20962096 <label for="openai_reasoning_effort">
20972097 <span data-i18n="Reasoning Effort">Reasoning Effort</span>
@@ -2105,7 +2105,7 @@
21052105 <option data-i18n="openai_reasoning_effort_high" value="high">High</option>
21062106 <option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option>
21072107 </select>
21082108 <div class="toggle-description justifyLeft marginBot5" data-source="openai,custom,xai,aimlapi,openrouter,perplexity,electronhub,azure_openai" data-i18n="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.">
21092109 OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level.
21102110 </div>
21112111 <div class="toggle-description justifyLeft marginBot5" data-source="claude" data-i18n="Allocates a portion of the response length for thinking (min: 1024 tokens, low: 10%, medium: 25%, high: 50%, max: 95%), but minimum 1024 tokens. Auto does not request thinking.">
@@ -2144,7 +2144,7 @@
21442144 </div>
21452145 </div>
21462146 </div>
21472147 <div class="range-block m-t-1" data-source="openai,aimlapi,openrouter,custom,azure_openai">
21482148 <div id="logit_bias_openai" class="range-block-title openai_restorable" data-i18n="Logit Bias">
21492149 Logit Bias
21502150 </div>
@@ -2802,6 +2802,7 @@
28022802 <optgroup>
28032803 <option value="ai21">AI21</option>
28042804 <option value="aimlapi">AI/ML API</option>
2805+ <option value="azure_openai">Azure OpenAI</option>
28052806 <option value="claude">Claude</option>
28062807 <option value="cohere">Cohere</option>
28072808 <!-- Temporarily disabled. -->
@@ -3735,6 +3736,49 @@
37353736 <option value="kimi-thinking-preview">kimi-thinking-preview</option>
37363737 </select>
37373738 </div>
3739+ <div id="azure_openai_settings" data-source="azure_openai">
3740+ <!-- Azure Base URL -->
3741+ <h4><span data-i18n="Azure Base URL">Azure Base URL</span></h4>
3742+ <div class="flex-container">
3743+ <input id="azure_base_url" data-setting="azure_base_url" class="text_pole wide100p" type="text" placeholder="https://your-resource.openai.azure.com/">
3744+ </div>
3745+
3746+ <!-- Azure Deployment Name -->
3747+ <h4><span data-i18n="Deployment Name">Deployment Name</span></h4>
3748+ <div class="flex-container">
3749+ <input id="azure_deployment_name" data-setting="azure_deployment_name" class="text_pole wide100p" type="text" placeholder="your-deployment-name" title="The name of your model deployment in Azure." data-i18n="[title]The name of your model deployment in Azure.">
3750+ </div>
3751+
3752+ <!-- Azure API Version Dropdown -->
3753+ <h4><span data-i18n="API Version">API Version</span></h4>
3754+ <div class="flex-container">
3755+ <select id="azure_api_version" data-setting="azure_api_version" class="text_pole wide100p">
3756+ <option value="2025-04-01-preview">2025-04-01-preview</option>
3757+ <option value="2024-10-21">2024-10-21</option>
3758+ </select>
3759+ </div>
3760+
3761+ <!-- Azure API Key -->
3762+ <h4><span data-i18n="Azure API Key">Azure API Key</span></h4>
3763+ <div class="flex-container">
3764+ <input id="api_key_azure_openai" data-setting="api_key_azure_openai" class="text_pole flex1" type="password" autocomplete="off">
3765+ <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_azure_openai"></div>
3766+ </div>
3767+ <div class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'." data-for="api_key_azure_openai">
3768+ For privacy reasons, your API key will be hidden after you click 'Connect'.
3769+ </div>
3770+
3771+ <!-- Model Name (Select) -->
3772+ <h4><span data-i18n="Model Name">Model Name</span></h4>
3773+ <div class="flex-container">
3774+ <select id="azure_openai_model" data-setting="azure_openai_model" class="text_pole wide100p">
3775+ <option value="" disabled selected data-i18n="Click 'Connect' to fetch model name">Click 'Connect' to fetch model name</option>
3776+ </select>
3777+ </div>
3778+ <div>
3779+ <small data-i18n="The underlying model of your deployment. This is detected automatically when you connect.">The underlying model of your deployment. This is detected automatically when you connect.</small>
3780+ </div>
3781+ </div>
37383782 <div id="prompt_post_processing_form">
37393783 <h4>
37403784 <span data-i18n="Prompt Post-Processing">
public/script.js+1 -0
@@ -5370,6 +5370,7 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource
53705370 case chat_completion_sources.COHERE:
53715371 case chat_completion_sources.XAI:
53725372 case chat_completion_sources.ELECTRONHUB:
5373+ case chat_completion_sources.AZURE_OPENAI:
53735374 default:
53745375 result = tryParse(text);
53755376 break;
public/scripts/RossAscends-mods.js+1 -0
@@ -412,6 +412,7 @@ function RA_autoconnect(PrevApi) {
412412 || (secret_state[SECRET_KEYS.COMETAPI] && oai_settings.chat_completion_source == chat_completion_sources.COMETAPI)
413413 || (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS)
414414 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
415+ || (secret_state[SECRET_KEYS.AZURE_OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI)
415416 ) {
416417 $('#api_button_openai').trigger('click');
417418 }
public/scripts/openai.js+125 -13
@@ -188,6 +188,7 @@ export const chat_completion_sources = {
188188 MOONSHOT: 'moonshot',
189189 FIREWORKS: 'fireworks',
190190 COMETAPI: 'cometapi',
191+ AZURE_OPENAI: 'azure_openai',
191192};
192193
193194const character_names_behavior = {
@@ -241,6 +242,8 @@ const sensitiveFields = [
241242 'custom_include_headers',
242243 'vertexai_region',
243244 'vertexai_express_project_id',
245+ 'azure_base_url',
246+ 'azure_deployment_name',
244247];
245248
246249/**
@@ -331,6 +334,10 @@ export const settingsToUpdate = {
331334 n: ['#n_openai', 'n', false, false],
332335 bypass_status_check: ['#openai_bypass_status_check', 'bypass_status_check', true, true],
333336 request_images: ['#openai_request_images', 'request_images', true, false],
337+ azure_base_url: ['#azure_base_url', 'azure_base_url', false, true],
338+ azure_deployment_name: ['#azure_deployment_name', 'azure_deployment_name', false, true],
339+ azure_api_version: ['#azure_api_version', 'azure_api_version', false, true],
340+ azure_openai_model: ['#azure_openai_model', 'azure_openai_model', false, true],
334341 extensions: ['#NULL_SELECTOR', 'extensions', false, false],
335342};
336343
@@ -380,6 +387,10 @@ const default_settings = {
380387 cometapi_model: 'gpt-4o',
381388 moonshot_model: 'kimi-latest',
382389 fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct',
390+ azure_base_url: '',
391+ azure_deployment_name: '',
392+ azure_api_version: '2024-02-15-preview',
393+ azure_openai_model: '',
383394 custom_model: '',
384395 custom_url: '',
385396 custom_include_body: '',
@@ -470,6 +481,10 @@ const oai_settings = {
470481 cometapi_model: 'gpt-4o',
471482 moonshot_model: 'kimi-latest',
472483 fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct',
484+ azure_base_url: '',
485+ azure_deployment_name: '',
486+ azure_api_version: '2024-02-15-preview',
487+ azure_openai_model: '',
473488 custom_model: '',
474489 custom_url: '',
475490 custom_include_body: '',
@@ -1641,6 +1656,8 @@ export function getChatCompletionModel(source = null) {
16411656 return oai_settings.moonshot_model;
16421657 case chat_completion_sources.FIREWORKS:
16431658 return oai_settings.fireworks_model;
1659+ case chat_completion_sources.AZURE_OPENAI:
1660+ return oai_settings.azure_openai_model;
16441661 default:
16451662 console.error(`Unknown chat completion source: ${activeSource}`);
16461663 return '';
@@ -1957,6 +1974,16 @@ function saveModelList(data) {
19571974
19581975 $('#model_cometapi_select').val(oai_settings.cometapi_model).trigger('change');
19591976 }
1977+
1978+ if (oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI) {
1979+ const modelId = model_list?.[0]?.id || '';
1980+ oai_settings.azure_openai_model = modelId;
1981+
1982+ $('#azure_openai_model')
1983+ .empty()
1984+ .append(new Option(modelId || 'None', modelId || '', true, true))
1985+ .trigger('change');
1986+ }
19601987}
19611988
19621989function appendOpenRouterOptions(model_list, groupModels = false, sort = false) {
@@ -2068,6 +2095,7 @@ function getReasoningEffort() {
20682095 // These sources expect the effort as string.
20692096 const reasoningEffortSources = [
20702097 chat_completion_sources.OPENAI,
2098+ chat_completion_sources.AZURE_OPENAI,
20712099 chat_completion_sources.CUSTOM,
20722100 chat_completion_sources.XAI,
20732101 chat_completion_sources.AIMLAPI,
@@ -2087,7 +2115,7 @@ function getReasoningEffort() {
20872115 case reasoning_effort_types.auto:
20882116 return undefined;
20892117 case reasoning_effort_types.min:
20902118 return [chat_completion_sources.OPENAI ===, chat_completion_sources.AZURE_OPENAI].includes(oai_settings.chat_completion_source) && /^gpt-5/.test(oai_settings.openai_modelgetChatCompletionModel())
20912119 ? reasoning_effort_types.min
20922120 : reasoning_effort_types.low;
20932121 case reasoning_effort_types.max:
@@ -2154,15 +2182,16 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
21542182 const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;
21552183 const isPollinations = oai_settings.chat_completion_source == chat_completion_sources.POLLINATIONS;
21562184 const isMoonshot = oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT;
2185+ const isAzureOpenAI = oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI; // Add this line
21572186 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
21582187 const isQuiet = type === 'quiet';
21592188 const isImpersonate = type === 'impersonate';
21602189 const isContinue = type === 'continue';
21612190 const stream = oai_settings.stream_openai && !isQuiet && !((isOAI || isAzureOpenAI) && ['o1-2024-12-17', 'o1'].includes(oai_settings.openai_modelgetChatCompletionModel()));
21622191 const useLogprobs = !!power_user.request_token_probabilities;
21632192 const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isAzureOpenAI || isCustom || isXAI || isAimlapi || isMoonshot);
21642193
21652194 const logitBiasSources = [chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI, chat_completion_sources.OPENROUTER, chat_completion_sources.CUSTOM];
21662195 if (oai_settings.bias_preset_selected
21672196 && logitBiasSources.includes(oai_settings.chat_completion_source)
21682197 && Array.isArray(oai_settings.bias_presets[oai_settings.bias_preset_selected])
@@ -2200,6 +2229,16 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
22002229 'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing,
22012230 };
22022231
2232+ if (isAzureOpenAI) {
2233+ generate_data.azure_base_url = oai_settings.azure_base_url;
2234+ generate_data.azure_deployment_name = oai_settings.azure_deployment_name;
2235+ generate_data.azure_api_version = oai_settings.azure_api_version;
2236+ // Reasoning effort is not supported on some Azure models (e.g. GPT-3.x, GPT-4.x)
2237+ if (/^gpt-[34]/.test(oai_settings.azure_openai_model)) {
2238+ delete generate_data.reasoning_effort;
2239+ }
2240+ }
2241+
22032242 if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) {
22042243 await ToolManager.registerFunctionToolsOpenAI(generate_data);
22052244 }
@@ -2217,18 +2256,18 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
22172256 }
22182257
22192258 // Add logprobs request (currently OpenAI only, max 5 on their side)
22202259 if (useLogprobs && (isOAI || isAzureOpenAI || isCustom || isDeepSeek || isXAI || isAimlapi)) {
22212260 generate_data['logprobs'] = 5;
22222261 }
22232262
22242263 // Remove logit bias/logprobs/stop-strings if not supported by the model
22252264 const isVision = (m) => ['gpt', 'vision'].every(x => m.includes(x));
22262265 if ((isOAI && isVision(oai_settings.openai_model)) || (isAzureOpenAI && isVision(oai_settings.azure_openai_model)) || (isOpenRouter && isVision(oai_settings.openrouter_model))) {
22272266 delete generate_data.logit_bias;
22282267 delete generate_data.stop;
22292268 delete generate_data.logprobs;
22302269 }
22312270 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'))) {
22322271 delete generate_data.logprobs;
22332272 }
22342273
@@ -2341,6 +2380,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
23412380
23422381 const seedSupportedSources = [
23432382 chat_completion_sources.OPENAI,
2383+ chat_completion_sources.AZURE_OPENAI,
23442384 chat_completion_sources.OPENROUTER,
23452385 chat_completion_sources.MISTRALAI,
23462386 chat_completion_sources.CUSTOM,
@@ -2358,7 +2398,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
23582398 generate_data['seed'] = oai_settings.seed;
23592399 }
23602400
23612401 if ((isOAI && /^(o1|o3|o4)/.test(oai_settings.openai_model)) || (isAzureOpenAI && /^(o1|o3|o4)/.test(oai_settings.azure_openai_model))) {
23622402 generate_data.max_completion_tokens = generate_data.max_tokens;
23632403 delete generate_data.max_tokens;
23642404 delete generate_data.logprobs;
@@ -2381,7 +2421,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
23812421 }
23822422 }
23832423
23842424 if ((isOAI && /^gpt-5/.test(oai_settings.openai_model)) || (isAzureOpenAI && /^gpt-5/.test(oai_settings.azure_openai_model))) {
23852425 generate_data.max_completion_tokens = generate_data.max_tokens;
23862426 delete generate_data.max_tokens;
23872427 delete generate_data.logprobs;
@@ -2551,6 +2591,7 @@ function parseChatCompletionLogprobs(data) {
25512591
25522592 switch (oai_settings.chat_completion_source) {
25532593 case chat_completion_sources.OPENAI:
2594+ case chat_completion_sources.AZURE_OPENAI:
25542595 case chat_completion_sources.DEEPSEEK:
25552596 case chat_completion_sources.XAI:
25562597 case chat_completion_sources.CUSTOM:
@@ -2559,7 +2600,7 @@ function parseChatCompletionLogprobs(data) {
25592600 }
25602601 // OpenAI Text Completion API is treated as a chat completion source
25612602 // by SillyTavern, hence its presence in this function.
25622603 return textCompletionModels.includes(oai_settings.openai_modelgetChatCompletionModel())
25632604 ? parseOpenAITextLogprobs(data.choices[0]?.logprobs)
25642605 : parseOpenAIChatLogprobs(data.choices[0]?.logprobs);
25652606 default:
@@ -3497,6 +3538,10 @@ function loadOpenAISettings(data, settings) {
34973538 oai_settings.custom_include_headers = settings.custom_include_headers ?? default_settings.custom_include_headers;
34983539 oai_settings.custom_prompt_post_processing = settings.custom_prompt_post_processing ?? default_settings.custom_prompt_post_processing;
34993540 oai_settings.google_model = settings.google_model ?? default_settings.google_model;
3541+ oai_settings.azure_base_url = settings.azure_base_url ?? default_settings.azure_base_url;
3542+ oai_settings.azure_deployment_name = settings.azure_deployment_name ?? default_settings.azure_deployment_name;
3543+ oai_settings.azure_api_version = settings.azure_api_version ?? default_settings.azure_api_version;
3544+ oai_settings.azure_openai_model = settings.azure_openai_model ?? default_settings.azure_openai_model;
35003545 oai_settings.vertexai_model = settings.vertexai_model ?? default_settings.vertexai_model;
35013546 oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source;
35023547 oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models;
@@ -3593,6 +3638,11 @@ function loadOpenAISettings(data, settings) {
35933638 $(`#model_moonshot_select option[value="${oai_settings.moonshot_model}"`).prop('selected', true);
35943639 $('#custom_model_id').val(oai_settings.custom_model);
35953640 $('#custom_api_url_text').val(oai_settings.custom_url);
3641+ $('#azure_base_url').val(oai_settings.azure_base_url);
3642+ $('#azure_deployment_name').val(oai_settings.azure_deployment_name);
3643+ $('#azure_api_version').val(oai_settings.azure_api_version);
3644+ $('#azure_openai_model').val(oai_settings.azure_openai_model);
3645+
35963646 $('#openai_max_context').val(oai_settings.openai_max_context);
35973647 $('#openai_max_context_counter').val(`${oai_settings.openai_max_context}`);
35983648 $('#model_openrouter_select').val(oai_settings.openrouter_model);
@@ -3771,6 +3821,12 @@ async function getStatusOpen() {
37713821 return resultCheckStatus();
37723822 }
37733823
3824+ if (oai_settings.chat_completion_source === chat_completion_sources.AZURE_OPENAI && !isValidUrl(oai_settings.azure_base_url)) {
3825+ console.debug('Invalid endpoint URL of Azure OpenAI API:', oai_settings.azure_base_url);
3826+ setOnlineStatus(t`Invalid Azure endpoint URL. Requests may fail.`);
3827+ return resultCheckStatus();
3828+ }
3829+
37743830 let data = {
37753831 reverse_proxy: oai_settings.reverse_proxy,
37763832 proxy_password: oai_settings.proxy_password,
@@ -3796,6 +3852,12 @@ async function getStatusOpen() {
37963852 data.custom_include_headers = oai_settings.custom_include_headers;
37973853 }
37983854
3855+ if (oai_settings.chat_completion_source === chat_completion_sources.AZURE_OPENAI) {
3856+ data.azure_base_url = oai_settings.azure_base_url;
3857+ data.azure_deployment_name = oai_settings.azure_deployment_name;
3858+ data.azure_api_version = oai_settings.azure_api_version;
3859+ }
3860+
37993861 const canBypass = (oai_settings.chat_completion_source === chat_completion_sources.OPENAI && oai_settings.bypass_status_check) || oai_settings.chat_completion_source === chat_completion_sources.CUSTOM;
38003862 if (canBypass) {
38013863 setOnlineStatus(t`Status check bypassed`);
@@ -3877,6 +3939,10 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
38773939 custom_prompt_post_processing: settings.custom_prompt_post_processing,
38783940 google_model: settings.google_model,
38793941 vertexai_model: settings.vertexai_model,
3942+ azure_base_url: settings.azure_base_url,
3943+ azure_deployment_name: settings.azure_deployment_name,
3944+ azure_api_version: settings.azure_api_version,
3945+ azure_openai_model: settings.azure_openai_model,
38803946 temperature: settings.temp_openai,
38813947 frequency_penalty: settings.freq_pen_openai,
38823948 presence_penalty: settings.pres_pen_openai,
@@ -4839,6 +4905,14 @@ async function onModelChange() {
48394905 oai_settings.cometapi_model = value;
48404906 }
48414907
4908+ if ($(this).is('#azure_openai_model')) {
4909+ if (!value) {
4910+ console.debug('Null Azure OpenAI model selected. Ignoring.');
4911+ return;
4912+ }
4913+ oai_settings.azure_openai_model = value;
4914+ }
4915+
48424916 if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) {
48434917 if (oai_settings.max_context_unlocked) {
48444918 $('#openai_max_context').attr('max', max_2mil);
@@ -4913,7 +4987,7 @@ async function onModelChange() {
49134987 $('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
49144988 }
49154989
49164990 if (oai_settings[chat_completion_sources.chat_completion_source ==AZURE_OPENAI, chat_completion_sources.OPENAI].includes(oai_settings.chat_completion_source)) {
49174991 $('#openai_max_context').attr('max', getMaxContextOpenAI(value));
49184992 oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
49194993 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
@@ -5419,6 +5493,20 @@ async function onConnectButtonClick(e) {
54195493 }
54205494 }
54215495
5496+ if (oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI) {
5497+ const api_key_azure_openai = String($('#api_key_azure_openai').val()).trim();
5498+
5499+ if (api_key_azure_openai.length) {
5500+ await writeSecret(SECRET_KEYS.AZURE_OPENAI, api_key_azure_openai);
5501+ }
5502+
5503+ if (!api_key_azure_openai && !secret_state[SECRET_KEYS.AZURE_OPENAI]) {
5504+ console.log('No secret key saved for Azure OpenAI');
5505+ return;
5506+ }
5507+ }
5508+
5509+
54225510 startStatusLoading();
54235511 saveSettingsDebounced();
54245512 await getStatusOpen();
@@ -5492,6 +5580,9 @@ function toggleChatCompletionForms() {
54925580 else if (oai_settings.chat_completion_source == chat_completion_sources.COMETAPI) {
54935581 $('#model_cometapi_select').trigger('change');
54945582 }
5583+ else if (oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI) {
5584+ $('#azure_openai_model').trigger('change');
5585+ }
54955586
54965587 $('[data-source]').each(function () {
54975588 const validSources = $(this).data('source').split(',');
@@ -5611,10 +5702,15 @@ export function isImageInliningSupported() {
56115702
56125703 switch (oai_settings.chat_completion_source) {
56135704 case chat_completion_sources.OPENAI:
5705+ case chat_completion_sources.AZURE_OPENAI: {
5706+ const modelToCheck = oai_settings.chat_completion_source === chat_completion_sources.AZURE_OPENAI
5707+ ? oai_settings.azure_openai_model
5708+ : oai_settings.openai_model;
56145709 return visionSupportedModels.some(model =>
56155710 oai_settings.openai_modelmodelToCheck.includes(model)
56165711 && ['gpt-4-turbo-preview', 'o1-mini', 'o3-mini'].some(x => !oai_settings.openai_modelmodelToCheck.includes(x)),
56175712 );
5713+ }
56185714 case chat_completion_sources.MAKERSUITE:
56195715 return visionSupportedModels.some(model => oai_settings.google_model.includes(model));
56205716 case chat_completion_sources.VERTEXAI:
@@ -6295,6 +6391,21 @@ export function initOpenAI() {
62956391 saveSettingsDebounced();
62966392 });
62976393
6394+ $('#azure_base_url').on('input', function () {
6395+ oai_settings.azure_base_url = String($(this).val());
6396+ saveSettingsDebounced();
6397+ });
6398+
6399+ $('#azure_deployment_name').on('input', function () {
6400+ oai_settings.azure_deployment_name = String($(this).val());
6401+ saveSettingsDebounced();
6402+ });
6403+
6404+ $('#azure_api_version').on('input change', function () {
6405+ oai_settings.azure_api_version = String($(this).val());
6406+ saveSettingsDebounced();
6407+ });
6408+
62986409 $('#character_names_none').on('input', function () {
62996410 oai_settings.names_behavior = character_names_behavior.NONE;
63006411 setNamesBehaviorControls();
@@ -6453,6 +6564,7 @@ export function initOpenAI() {
64536564 $('#model_cometapi_select').on('change', onModelChange);
64546565 $('#model_moonshot_select').on('change', onModelChange);
64556566 $('#model_fireworks_select').on('change', onModelChange);
6567+ $('#azure_openai_model').on('change', onModelChange);
64566568 $('#settings_preset_openai').on('change', onSettingsPresetChange);
64576569 $('#new_oai_preset').on('click', onNewPresetClick);
64586570 $('#delete_oai_preset').on('click', onDeletePresetClick);
public/scripts/secrets.js+3 -0
@@ -47,6 +47,7 @@ export const SECRET_KEYS = {
4747 PERPLEXITY: 'api_key_perplexity',
4848 GROQ: 'api_key_groq',
4949 AZURE_TTS: 'api_key_azure_tts',
50+ AZURE_OPENAI: 'api_key_azure_openai',
5051 FEATHERLESS: 'api_key_featherless',
5152 HUGGINGFACE: 'api_key_huggingface',
5253 STABILITY: 'api_key_stability',
@@ -122,6 +123,7 @@ const FRIENDLY_NAMES = {
122123 [SECRET_KEYS.MINIMAX_GROUP_ID]: 'MiniMax Group ID',
123124 [SECRET_KEYS.MOONSHOT]: 'Moonshot AI',
124125 [SECRET_KEYS.COMETAPI]: 'CometAPI',
126+ [SECRET_KEYS.AZURE_OPENAI]: 'Azure OpenAI',
125127};
126128
127129const INPUT_MAP = {
@@ -160,6 +162,7 @@ const INPUT_MAP = {
160162 [SECRET_KEYS.MOONSHOT]: '#api_key_moonshot',
161163 [SECRET_KEYS.FIREWORKS]: '#api_key_fireworks',
162164 [SECRET_KEYS.COMETAPI]: '#api_key_cometapi',
165+ [SECRET_KEYS.AZURE_OPENAI]: '#api_key_azure_openai',
163166};
164167
165168const getLabel = () => moment().format('L LT');
public/scripts/tokenizers.js+4 -0
@@ -586,6 +586,10 @@ export function getTokenizerModel() {
586586 const nemoTokenizer = 'nemo';
587587 const deepseekTokenizer = 'deepseek';
588588
589+ if (oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI) {
590+ return oai_settings.azure_openai_model || turboTokenizer;
591+ }
592+
589593 if (oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK) {
590594 return deepseekTokenizer;
591595 }
public/scripts/tool-calling.js+1 -0
@@ -659,6 +659,7 @@ export class ToolManager {
659659 chat_completion_sources.FIREWORKS,
660660 chat_completion_sources.COMETAPI,
661661 chat_completion_sources.ELECTRONHUB,
662+ chat_completion_sources.AZURE_OPENAI,
662663 ];
663664 return supportedSources.includes(oai_settings.chat_completion_source);
664665 }
src/constants.js+40 -0
@@ -182,6 +182,7 @@ export const CHAT_COMPLETION_SOURCES = {
182182 MOONSHOT: 'moonshot',
183183 FIREWORKS: 'fireworks',
184184 COMETAPI: 'cometapi',
185+ AZURE_OPENAI: 'azure_openai',
185186};
186187
187188/**
@@ -408,6 +409,45 @@ export const VLLM_KEYS = [
408409 'guided_whitespace_pattern',
409410];
410411
412+export const AZURE_OPENAI_KEYS = [
413+ 'messages',
414+ 'temperature',
415+ 'frequency_penalty',
416+ 'presence_penalty',
417+ 'top_p',
418+ 'max_tokens',
419+ 'max_completion_tokens',
420+ 'stream',
421+ 'logit_bias',
422+ 'stop',
423+ 'n',
424+ 'logprobs',
425+ 'seed',
426+ 'tools',
427+ 'tool_choice',
428+ 'reasoning_effort',
429+];
430+
431+export const OPENAI_REASONING_EFFORT_MODELS = [
432+ 'o1',
433+ 'o3-mini',
434+ 'o3-mini-2025-01-31',
435+ 'o4-mini',
436+ 'o4-mini-2025-04-16',
437+ 'o3',
438+ 'o3-2025-04-16',
439+ 'gpt-5',
440+ 'gpt-5-2025-08-07',
441+ 'gpt-5-mini',
442+ 'gpt-5-mini-2025-08-07',
443+ 'gpt-5-nano',
444+ 'gpt-5-nano-2025-08-07',
445+];
446+
447+export const OPENAI_REASONING_EFFORT_MAP = {
448+ min: 'minimal',
449+};
450+
411451export const LOG_LEVELS = {
412452 DEBUG: 0,
413453 INFO: 1,
src/endpoints/backends/chat-completions.js+178 -20
@@ -6,8 +6,11 @@ import urlJoin from 'url-join';
66
77import {
88 AIMLAPI_HEADERS,
9+ AZURE_OPENAI_KEYS,
910 CHAT_COMPLETION_SOURCES,
1011 GEMINI_SAFETY,
12+ OPENAI_REASONING_EFFORT_MAP,
13+ OPENAI_REASONING_EFFORT_MODELS,
1114 OPENROUTER_HEADERS,
1215} from '../../constants.js';
1316import {
@@ -1294,6 +1297,100 @@ async function sendElectronHubRequest(request, response) {
12941297 }
12951298}
12961299
1300+/**
1301+ * Sends a chat completion request to Azure OpenAI.
1302+ * @param {express.Request} request Express request object (contains request.body with all generate_data)
1303+ * @param {express.Response} response Express response object
1304+ */
1305+async function sendAzureOpenAIRequest(request, response) {
1306+ // 1. GATHER & VALIDATE SETTINGS
1307+ const { azure_base_url, azure_deployment_name, azure_api_version } = request.body;
1308+ const apiKey = readSecret(request.user.directories, SECRET_KEYS.AZURE_OPENAI);
1309+ if (!azure_base_url || !azure_deployment_name || !azure_api_version || !apiKey) {
1310+ return response.status(400).send({
1311+ error: {
1312+ message: 'Azure OpenAI configuration is incomplete. Please provide Base URL, Deployment Name, API Version, and API Key in the connection settings.',
1313+ },
1314+ });
1315+ }
1316+
1317+ // 2. PREPARE THE REQUEST
1318+ const url = new URL(`/openai/deployments/${azure_deployment_name}/chat/completions`, azure_base_url);
1319+ url.searchParams.set('api-version', azure_api_version);
1320+ const endpointUrl = url.toString();
1321+
1322+ // Create the base payload with all standard parameters
1323+ const apiRequestBody = /** @type {any} */ ({});
1324+ for (const key of AZURE_OPENAI_KEYS) {
1325+ if (Object.hasOwn(request.body, key)) {
1326+ apiRequestBody[key] = request.body[key];
1327+ }
1328+ }
1329+
1330+ // Handle Structured Output (JSON Mode) by translating the custom `json_schema` object.
1331+ if (request.body.json_schema) {
1332+ apiRequestBody['response_format'] = {
1333+ type: 'json_schema',
1334+ json_schema: {
1335+ name: request.body.json_schema.name,
1336+ strict: request.body.json_schema.strict ?? true,
1337+ schema: request.body.json_schema.value,
1338+ },
1339+ };
1340+ }
1341+
1342+ // Adjust logprobs for Azure OpenAI, which follows the OpenAI Chat Completions API spec.
1343+ if (typeof apiRequestBody.logprobs === 'number' && apiRequestBody.logprobs > 0) {
1344+ apiRequestBody.top_logprobs = apiRequestBody.logprobs;
1345+ apiRequestBody.logprobs = true;
1346+ }
1347+
1348+ // Do not send reasoning effort to models which do not support it
1349+ apiRequestBody['reasoning_effort'] = OPENAI_REASONING_EFFORT_MODELS.includes(request.body.model)
1350+ ? OPENAI_REASONING_EFFORT_MAP[request.body.reasoning_effort] ?? request.body.reasoning_effort
1351+ : undefined;
1352+
1353+ const controller = new AbortController();
1354+ request.socket.removeAllListeners('close');
1355+ request.socket.on('close', () => controller.abort());
1356+
1357+ const config = {
1358+ method: 'POST',
1359+ headers: {
1360+ 'Content-Type': 'application/json',
1361+ 'api-key': apiKey,
1362+ },
1363+ body: JSON.stringify(apiRequestBody),
1364+ signal: controller.signal,
1365+ };
1366+
1367+ console.info(`Sending request to Azure OpenAI: ${endpointUrl}`);
1368+ console.debug('Azure OpenAI Request Body:', apiRequestBody);
1369+ try {
1370+ const fetchResponse = await fetch(endpointUrl, config);
1371+
1372+ if (request.body.stream) {
1373+ return forwardFetchResponse(fetchResponse, response);
1374+ }
1375+
1376+ if (fetchResponse.ok) {
1377+ /** @type {any} */
1378+ const json = await fetchResponse.json();
1379+ console.debug('Azure OpenAI response:', json);
1380+ return response.send(json);
1381+ }
1382+
1383+ const text = await fetchResponse.text();
1384+ const data = tryParse(text) || { error: { message: fetchResponse.statusText || 'Unknown error occurred' } };
1385+ return response.status(500).send(data);
1386+ } catch (error) {
1387+ const message = error.name === 'AbortError'
1388+ ? 'Request was aborted by the client.'
1389+ : (error.message || 'An unknown network error occurred.');
1390+ return response.status(500).send({ error: { message, ...error } });
1391+ }
1392+}
1393+
12971394export const router = express.Router();
12981395
12991396router.post('/status', async function (request, statusResponse) {
@@ -1404,6 +1501,84 @@ router.post('/status', async function (request, statusResponse) {
14041501 console.error('Error fetching Google AI Studio models:', error);
14051502 return statusResponse.send({ error: true, bypass: true, data: { data: [] } });
14061503 }
1504+ } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.AZURE_OPENAI) {
1505+ const { azure_base_url, azure_deployment_name, azure_api_version } = request.body;
1506+ const apiKey = readSecret(request.user.directories, SECRET_KEYS.AZURE_OPENAI);
1507+
1508+ // 1) Validate configuration from the frontend
1509+ if (!apiKey || !azure_base_url || !azure_deployment_name || !azure_api_version) {
1510+ console.warn('Azure OpenAI status check failed: missing config from frontend.');
1511+ return statusResponse.status(400).send({ error: true, message: 'Azure configuration is incomplete.' });
1512+ }
1513+ // 2) Build URLs using the URL API for consistency and robustness.
1514+ const modelsUrl = new URL('/openai/models', azure_base_url);
1515+ modelsUrl.searchParams.set('api-version', azure_api_version);
1516+
1517+ const chatUrl = new URL(`/openai/deployments/${azure_deployment_name}/chat/completions`, azure_base_url);
1518+ chatUrl.searchParams.set('api-version', azure_api_version);
1519+
1520+ // Map common status codes to user-friendly error messages
1521+ const azureStatusErrorMap = {
1522+ 400: 'API version may be invalid for this resource.',
1523+ 401: 'Invalid API key or insufficient permissions.',
1524+ 403: 'Invalid API key or insufficient permissions.',
1525+ 404: 'Endpoint URL appears incorrect (404).',
1526+ };
1527+
1528+ try {
1529+ // ---- A) GET /models: fast sanity check for endpoint + api key + api version ----
1530+ const apiConfigTest = await fetch(modelsUrl, {
1531+ method: 'GET',
1532+ headers: { 'api-key': apiKey, 'Accept': 'application/json' },
1533+ });
1534+
1535+ if (!apiConfigTest.ok) {
1536+ let errText = '';
1537+ try { errText = await apiConfigTest.text(); } catch { /* response body may be empty */ }
1538+
1539+ console.warn('Azure OpenAI GET /models failed:', apiConfigTest.status, apiConfigTest.statusText, errText || '');
1540+
1541+ const defaultMessage = `Azure Models endpoint error: ${apiConfigTest.statusText}`;
1542+ const message = azureStatusErrorMap[apiConfigTest.status] ?? defaultMessage;
1543+ return statusResponse.status(apiConfigTest.status).send({ error: true, message });
1544+ }
1545+
1546+ // ---- B) POST /chat/completions: verify deployment + read underlying model ID ----
1547+ // Small, deterministic probe to minimize cost/latency
1548+ const modelPayload = {
1549+ messages: [{ role: 'user', content: 'Say word Hi' }],
1550+ stream: false,
1551+ max_completion_tokens: 5,
1552+ };
1553+
1554+ const modelRequest = await fetch(chatUrl, {
1555+ method: 'POST',
1556+ headers: { 'api-key': apiKey, 'Content-Type': 'application/json', 'Accept': 'application/json' },
1557+ body: JSON.stringify(modelPayload),
1558+ });
1559+
1560+ let modelResponse;
1561+ try {
1562+ modelResponse = await modelRequest.json();
1563+ } catch {
1564+ modelResponse = { raw: 'Failed to parse JSON response from chat completions probe.' };
1565+ }
1566+
1567+ const modelId = /** @type {any} */ (modelResponse)?.model;
1568+ if (!modelId) {
1569+ console.warn('Azure status check succeeded but could not find a model ID in the response.');
1570+ console.debug('Azure Response Body:', modelResponse);
1571+ // Keep a benign success to avoid UX disruption in the UI
1572+ return statusResponse.send({ data: [] });
1573+ }
1574+
1575+ console.info(color.green('Azure OpenAI connection successful. Detected model:'), modelId);
1576+ // Consistent response format: always an array of { id }
1577+ return statusResponse.send({ data: [{ id: modelId }] });
1578+ } catch (error) {
1579+ console.error('Azure OpenAI status check connection error:', error);
1580+ return statusResponse.status(500).send({ error: true, message: 'Failed to connect to the Azure endpoint.' });
1581+ }
14071582 } else {
14081583 console.warn('This chat completion source is not supported yet.');
14091584 return statusResponse.status(400).send({ error: true });
@@ -1596,6 +1771,7 @@ router.post('/generate', function (request, response) {
15961771 case CHAT_COMPLETION_SOURCES.AIMLAPI: return sendAimlapiRequest(request, response);
15971772 case CHAT_COMPLETION_SOURCES.XAI: return sendXaiRequest(request, response);
15981773 case CHAT_COMPLETION_SOURCES.ELECTRONHUB: return sendElectronHubRequest(request, response);
1774+ case CHAT_COMPLETION_SOURCES.AZURE_OPENAI: return sendAzureOpenAIRequest(request, response);
15991775 }
16001776
16011777 let apiUrl;
@@ -1803,26 +1979,8 @@ router.post('/generate', function (request, response) {
18031979
18041980 // A few of OpenAIs reasoning models support reasoning effort
18051981 if (request.body.reasoning_effort && [CHAT_COMPLETION_SOURCES.CUSTOM, CHAT_COMPLETION_SOURCES.OPENAI].includes(request.body.chat_completion_source)) {
1806- const reasoningEffortModels = [
1982+ if (OPENAI_REASONING_EFFORT_MODELS.includes(request.body.model)) {
1807- 'o1',
1983+ bodyParams['reasoning_effort'] = OPENAI_REASONING_EFFORT_MAP[request.body.reasoning_effort] ?? request.body.reasoning_effort;
1808- 'o3-mini',
1809- 'o3-mini-2025-01-31',
1810- 'o4-mini',
1811- 'o4-mini-2025-04-16',
1812- 'o3',
1813- 'o3-2025-04-16',
1814- 'gpt-5',
1815- 'gpt-5-2025-08-07',
1816- 'gpt-5-mini',
1817- 'gpt-5-mini-2025-08-07',
1818- 'gpt-5-nano',
1819- 'gpt-5-nano-2025-08-07',
1820- ];
1821- const reasoningEffortMap = {
1822- min: 'minimal',
1823- };
1824- if (reasoningEffortModels.includes(request.body.model)) {
1825- bodyParams['reasoning_effort'] = reasoningEffortMap[request.body.reasoning_effort] ?? request.body.reasoning_effort;
18261984 }
18271985 }
18281986
src/endpoints/secrets.js+1 -0
@@ -60,6 +60,7 @@ export const SECRET_KEYS = {
6060 MINIMAX_GROUP_ID: 'minimax_group_id',
6161 MOONSHOT: 'api_key_moonshot',
6262 COMETAPI: 'api_key_cometapi',
63+ AZURE_OPENAI: 'api_key_azure_openai',
6364};
6465
6566/**