Zai moonshot reverse proxy (#4923) * adding reverse proxy support * update * added handling for the image caption extension

a8eb1545170ab1cc31ace6bba4d41e32e624910c

Subwolf <jeraran23@gmail.com>

Signed
7 files changed, +28 -20Showing whitespace changes
public/index.html+2 -2
@@ -2845,7 +2845,7 @@
28452845 <option value="zai">Z.AI (GLM)</option>
28462846 </optgroup>
28472847 </select>
28482848 <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,vertexai,deepseek,xai,zai,moonshot">
28492849 <div class="inline-drawer-toggle inline-drawer-header">
28502850 <b data-i18n="Reverse Proxy">Reverse Proxy</b>
28512851 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
@@ -2909,7 +2909,7 @@
29092909 </div>
29102910 </div>
29112911 </div>
29122912 <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,vertexai,deepseek,xai,zai,moonshot">
29132913 <div class="reverse_proxy_warning">
29142914 <b>
29152915 <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy.">
public/scripts/extensions/caption/index.js+2 -2
@@ -489,6 +489,8 @@ jQuery(async function () {
489489 'vertexai': SECRET_KEYS.VERTEXAI,
490490 'anthropic': SECRET_KEYS.CLAUDE,
491491 'xai': SECRET_KEYS.XAI,
492+ 'zai': SECRET_KEYS.ZAI,
493+ 'moonshot': SECRET_KEYS.MOONSHOT,
492494 };
493495
494496 if (reverseProxyApis[api]) {
@@ -502,11 +504,9 @@ jQuery(async function () {
502504 'groq': SECRET_KEYS.GROQ,
503505 'cohere': SECRET_KEYS.COHERE,
504506 'aimlapi': SECRET_KEYS.AIMLAPI,
505- 'moonshot': SECRET_KEYS.MOONSHOT,
506507 'nanogpt': SECRET_KEYS.NANOGPT,
507508 'chutes': SECRET_KEYS.CHUTES,
508509 'electronhub': SECRET_KEYS.ELECTRONHUB,
509- 'zai': SECRET_KEYS.ZAI,
510510 };
511511
512512 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
public/scripts/extensions/caption/settings.html+1 -1
@@ -191,7 +191,7 @@
191191 <input id="caption_ollama_custom_model" class="text_pole" type="text" placeholder="e.g. gemma3:latest" />
192192 </div>
193193 </div>
194194 <label data-type="openai,anthropic,google,vertexai,mistral,xai,zai,moonshot" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid.">
195195 <input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">
196196 <span data-i18n="Allow reverse proxy">Allow reverse proxy</span>
197197 </label>
public/scripts/extensions/shared.js+1 -1
@@ -15,7 +15,7 @@ import { createThumbnail, isValidUrl } from '../utils.js';
1515 */
1616export async function getMultimodalCaption(base64Img, prompt) {
1717 const useReverseProxy =
1818 (['openai', 'anthropic', 'google', 'mistral', 'vertexai', 'xai', 'zai', 'moonshot'].includes(extension_settings.caption.multimodal_api))
1919 && extension_settings.caption.allow_reverse_proxy
2020 && oai_settings.reverse_proxy
2121 && isValidUrl(oai_settings.reverse_proxy);
public/scripts/openai.js+6 -2
@@ -2486,6 +2486,8 @@ export async function createGenerationParameters(settings, model, type, messages
24862486 chat_completion_sources.VERTEXAI,
24872487 chat_completion_sources.DEEPSEEK,
24882488 chat_completion_sources.XAI,
2489+ chat_completion_sources.ZAI,
2490+ chat_completion_sources.MOONSHOT,
24892491 ];
24902492
24912493 // Sources that support logprobs
@@ -4129,6 +4131,8 @@ async function getStatusOpen() {
41294131 chat_completion_sources.VERTEXAI,
41304132 chat_completion_sources.DEEPSEEK,
41314133 chat_completion_sources.XAI,
4134+ chat_completion_sources.ZAI,
4135+ chat_completion_sources.MOONSHOT,
41324136 ];
41334137 if (oai_settings.reverse_proxy && validateProxySources.includes(oai_settings.chat_completion_source)) {
41344138 await validateReverseProxy();
@@ -5590,11 +5594,11 @@ async function onConnectButtonClick(e) {
55905594 [chat_completion_sources.DEEPSEEK]: { key: SECRET_KEYS.DEEPSEEK, selector: '#api_key_deepseek', proxy: true },
55915595 [chat_completion_sources.XAI]: { key: SECRET_KEYS.XAI, selector: '#api_key_xai', proxy: true },
55925596 [chat_completion_sources.AIMLAPI]: { key: SECRET_KEYS.AIMLAPI, selector: '#api_key_aimlapi', proxy: false },
55935597 [chat_completion_sources.MOONSHOT]: { key: SECRET_KEYS.MOONSHOT, selector: '#api_key_moonshot', proxy: falsetrue },
55945598 [chat_completion_sources.FIREWORKS]: { key: SECRET_KEYS.FIREWORKS, selector: '#api_key_fireworks', proxy: false },
55955599 [chat_completion_sources.COMETAPI]: { key: SECRET_KEYS.COMETAPI, selector: '#api_key_cometapi', proxy: false },
55965600 [chat_completion_sources.AZURE_OPENAI]: { key: SECRET_KEYS.AZURE_OPENAI, selector: '#api_key_azure_openai', proxy: false },
55975601 [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: falsetrue },
55985602 [chat_completion_sources.CHUTES]: { key: SECRET_KEYS.CHUTES, selector: '#api_key_chutes', proxy: false },
55995603 };
56005604
src/endpoints/backends/chat-completions.js+7 -6
@@ -1701,8 +1701,8 @@ router.post('/status', async function (request, statusResponse) {
17011701 headers = {};
17021702 throw new Error('This provider is temporarily disabled.');
17031703 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MOONSHOT) {
1704- apiUrl = API_MOONSHOT;
1704+ apiUrl = new URL(request.body.reverse_proxy || API_MOONSHOT).toString();
17051705 apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MOONSHOT);
17061706 headers = {};
17071707 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.FIREWORKS) {
17081708 apiUrl = API_FIREWORKS;
@@ -2249,8 +2249,8 @@ router.post('/generate', async function (request, response) {
22492249 setJsonObjectFormat(bodyParams, request.body.messages, request.body.json_schema);
22502250 }
22512251 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MOONSHOT) {
2252- apiUrl = API_MOONSHOT;
2252+ apiUrl = new URL(request.body.reverse_proxy || API_MOONSHOT).toString();
22532253 apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MOONSHOT);
22542254 headers = {};
22552255 bodyParams = {};
22562256 request.body.json_schema
@@ -2265,8 +2265,9 @@ router.post('/generate', async function (request, response) {
22652265 };
22662266 throw new Error('This provider is temporarily disabled.');
22672267 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZAI) {
22682268 apiUrlconst defaultApiUrl = request.body.zai_endpoint === ZAI_ENDPOINT.CODING ? API_ZAI_CODING : API_ZAI_COMMON;
22692269 apiKeyapiUrl = readSecretnew URL(request.userbody.directories,reverse_proxy SECRET_KEYS|| defaultApiUrl).ZAItoString();
2270+ apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.ZAI);
22702271 headers = {
22712272 'Accept-Language': 'en-US,en',
22722273 };
src/endpoints/openai.js+9 -6
@@ -73,7 +73,7 @@ router.post('/caption-image', async (request, response) => {
7373 key = readSecret(request.user.directories, SECRET_KEYS.COHERE);
7474 }
7575
7676 if (request.body.api === 'moonshot' && !request.body.reverse_proxy) {
7777 key = readSecret(request.user.directories, SECRET_KEYS.MOONSHOT);
7878 }
7979
@@ -89,8 +89,11 @@ router.post('/caption-image', async (request, response) => {
8989 key = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
9090 }
9191
9292 if (request.body.api === 'zai' && !request.body.reverse_proxy) {
9393 key = readSecret(request.user.directories, SECRET_KEYS.ZAI);
94+ }
95+
96+ if (request.body.api === 'zai') {
9497 bodyParams.max_tokens = 4096; // default is 1024
9598 }
9699
@@ -174,7 +177,7 @@ router.post('/caption-image', async (request, response) => {
174177 apiUrl = 'https://text.pollinations.ai/openai/chat/completions';
175178 }
176179
177180 if (request.body.api === 'moonshot' && !request.body.reverse_proxy) {
178181 apiUrl = 'https://api.moonshot.ai/v1/chat/completions';
179182 }
180183
@@ -190,13 +193,14 @@ router.post('/caption-image', async (request, response) => {
190193 apiUrl = 'https://api.electronhub.ai/v1/chat/completions';
191194 }
192195
193196 if (request.body.api === 'zai' && !request.body.reverse_proxy) {
194197 apiUrl = request.body.zai_endpoint === ZAI_ENDPOINT.CODING
195198 ? 'https://api.z.ai/api/coding/paas/v4/chat/completions'
196199 : 'https://api.z.ai/api/paas/v4/chat/completions';
200+ }
197201
198202 // Handle video inlining for Z.AI
199203 if (request.body.api === 'zai' && /data:video\/\w+;base64,/.test(request.body.image)) {
200204 const message = body.messages.find(msg => Array.isArray(msg.content));
201205 if (message) {
202206 const imgContent = message.content.find(c => c.type === 'image_url');
@@ -207,7 +211,6 @@ router.post('/caption-image', async (request, response) => {
207211 }
208212 }
209213 }
210- }
211214
212215 if (['koboldcpp', 'vllm', 'llamacpp', 'ooba'].includes(request.body.api)) {
213216 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;