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 @@
2845 <option value="zai">Z.AI (GLM)</option>2845 <option value="zai">Z.AI (GLM)</option>
2846 </optgroup>2846 </optgroup>
2847 </select>2847 </select>
2848 <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,vertexai,deepseek,xai">2848 <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,vertexai,deepseek,xai,zai,moonshot">
2849 <div class="inline-drawer-toggle inline-drawer-header">2849 <div class="inline-drawer-toggle inline-drawer-header">
2850 <b data-i18n="Reverse Proxy">Reverse Proxy</b>2850 <b data-i18n="Reverse Proxy">Reverse Proxy</b>
2851 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>2851 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
@@ -2909,7 +2909,7 @@
2909 </div>2909 </div>
2910 </div>2910 </div>
2911 </div>2911 </div>
2912 <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,vertexai,deepseek,xai">2912 <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,vertexai,deepseek,xai,zai,moonshot">
2913 <div class="reverse_proxy_warning">2913 <div class="reverse_proxy_warning">
2914 <b>2914 <b>
2915 <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy.">2915 <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 () {
489 'vertexai': SECRET_KEYS.VERTEXAI,489 'vertexai': SECRET_KEYS.VERTEXAI,
490 'anthropic': SECRET_KEYS.CLAUDE,490 'anthropic': SECRET_KEYS.CLAUDE,
491 'xai': SECRET_KEYS.XAI,491 'xai': SECRET_KEYS.XAI,
492 'zai': SECRET_KEYS.ZAI,
493 'moonshot': SECRET_KEYS.MOONSHOT,
492 };494 };
493495
494 if (reverseProxyApis[api]) {496 if (reverseProxyApis[api]) {
@@ -502,11 +504,9 @@ jQuery(async function () {
502 'groq': SECRET_KEYS.GROQ,504 'groq': SECRET_KEYS.GROQ,
503 'cohere': SECRET_KEYS.COHERE,505 'cohere': SECRET_KEYS.COHERE,
504 'aimlapi': SECRET_KEYS.AIMLAPI,506 'aimlapi': SECRET_KEYS.AIMLAPI,
505 'moonshot': SECRET_KEYS.MOONSHOT,
506 'nanogpt': SECRET_KEYS.NANOGPT,507 'nanogpt': SECRET_KEYS.NANOGPT,
507 'chutes': SECRET_KEYS.CHUTES,508 'chutes': SECRET_KEYS.CHUTES,
508 'electronhub': SECRET_KEYS.ELECTRONHUB,509 'electronhub': SECRET_KEYS.ELECTRONHUB,
509 'zai': SECRET_KEYS.ZAI,
510 };510 };
511511
512 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {512 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
public/scripts/extensions/caption/settings.html+1 -1
@@ -191,7 +191,7 @@
191 <input id="caption_ollama_custom_model" class="text_pole" type="text" placeholder="e.g. gemma3:latest" />191 <input id="caption_ollama_custom_model" class="text_pole" type="text" placeholder="e.g. gemma3:latest" />
192 </div>192 </div>
193 </div>193 </div>
194 <label data-type="openai,anthropic,google,vertexai,mistral,xai" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid.">194 <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.">
195 <input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">195 <input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">
196 <span data-i18n="Allow reverse proxy">Allow reverse proxy</span>196 <span data-i18n="Allow reverse proxy">Allow reverse proxy</span>
197 </label>197 </label>
public/scripts/extensions/shared.js+1 -1
@@ -15,7 +15,7 @@ import { createThumbnail, isValidUrl } from '../utils.js';
15 */15 */
16export async function getMultimodalCaption(base64Img, prompt) {16export async function getMultimodalCaption(base64Img, prompt) {
17 const useReverseProxy =17 const useReverseProxy =
18 (['openai', 'anthropic', 'google', 'mistral', 'vertexai', 'xai'].includes(extension_settings.caption.multimodal_api))18 (['openai', 'anthropic', 'google', 'mistral', 'vertexai', 'xai', 'zai', 'moonshot'].includes(extension_settings.caption.multimodal_api))
19 && extension_settings.caption.allow_reverse_proxy19 && extension_settings.caption.allow_reverse_proxy
20 && oai_settings.reverse_proxy20 && oai_settings.reverse_proxy
21 && isValidUrl(oai_settings.reverse_proxy);21 && isValidUrl(oai_settings.reverse_proxy);
public/scripts/openai.js+6 -2
@@ -2486,6 +2486,8 @@ export async function createGenerationParameters(settings, model, type, messages
2486 chat_completion_sources.VERTEXAI,2486 chat_completion_sources.VERTEXAI,
2487 chat_completion_sources.DEEPSEEK,2487 chat_completion_sources.DEEPSEEK,
2488 chat_completion_sources.XAI,2488 chat_completion_sources.XAI,
2489 chat_completion_sources.ZAI,
2490 chat_completion_sources.MOONSHOT,
2489 ];2491 ];
24902492
2491 // Sources that support logprobs2493 // Sources that support logprobs
@@ -4129,6 +4131,8 @@ async function getStatusOpen() {
4129 chat_completion_sources.VERTEXAI,4131 chat_completion_sources.VERTEXAI,
4130 chat_completion_sources.DEEPSEEK,4132 chat_completion_sources.DEEPSEEK,
4131 chat_completion_sources.XAI,4133 chat_completion_sources.XAI,
4134 chat_completion_sources.ZAI,
4135 chat_completion_sources.MOONSHOT,
4132 ];4136 ];
4133 if (oai_settings.reverse_proxy && validateProxySources.includes(oai_settings.chat_completion_source)) {4137 if (oai_settings.reverse_proxy && validateProxySources.includes(oai_settings.chat_completion_source)) {
4134 await validateReverseProxy();4138 await validateReverseProxy();
@@ -5590,11 +5594,11 @@ async function onConnectButtonClick(e) {
5590 [chat_completion_sources.DEEPSEEK]: { key: SECRET_KEYS.DEEPSEEK, selector: '#api_key_deepseek', proxy: true },5594 [chat_completion_sources.DEEPSEEK]: { key: SECRET_KEYS.DEEPSEEK, selector: '#api_key_deepseek', proxy: true },
5591 [chat_completion_sources.XAI]: { key: SECRET_KEYS.XAI, selector: '#api_key_xai', proxy: true },5595 [chat_completion_sources.XAI]: { key: SECRET_KEYS.XAI, selector: '#api_key_xai', proxy: true },
5592 [chat_completion_sources.AIMLAPI]: { key: SECRET_KEYS.AIMLAPI, selector: '#api_key_aimlapi', proxy: false },5596 [chat_completion_sources.AIMLAPI]: { key: SECRET_KEYS.AIMLAPI, selector: '#api_key_aimlapi', proxy: false },
5593 [chat_completion_sources.MOONSHOT]: { key: SECRET_KEYS.MOONSHOT, selector: '#api_key_moonshot', proxy: false },5597 [chat_completion_sources.MOONSHOT]: { key: SECRET_KEYS.MOONSHOT, selector: '#api_key_moonshot', proxy: true },
5594 [chat_completion_sources.FIREWORKS]: { key: SECRET_KEYS.FIREWORKS, selector: '#api_key_fireworks', proxy: false },5598 [chat_completion_sources.FIREWORKS]: { key: SECRET_KEYS.FIREWORKS, selector: '#api_key_fireworks', proxy: false },
5595 [chat_completion_sources.COMETAPI]: { key: SECRET_KEYS.COMETAPI, selector: '#api_key_cometapi', proxy: false },5599 [chat_completion_sources.COMETAPI]: { key: SECRET_KEYS.COMETAPI, selector: '#api_key_cometapi', proxy: false },
5596 [chat_completion_sources.AZURE_OPENAI]: { key: SECRET_KEYS.AZURE_OPENAI, selector: '#api_key_azure_openai', proxy: false },5600 [chat_completion_sources.AZURE_OPENAI]: { key: SECRET_KEYS.AZURE_OPENAI, selector: '#api_key_azure_openai', proxy: false },
5597 [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: false },5601 [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: true },
5598 [chat_completion_sources.CHUTES]: { key: SECRET_KEYS.CHUTES, selector: '#api_key_chutes', proxy: false },5602 [chat_completion_sources.CHUTES]: { key: SECRET_KEYS.CHUTES, selector: '#api_key_chutes', proxy: false },
5599 };5603 };
56005604
src/endpoints/backends/chat-completions.js+7 -6
@@ -1701,8 +1701,8 @@ router.post('/status', async function (request, statusResponse) {
1701 headers = {};1701 headers = {};
1702 throw new Error('This provider is temporarily disabled.');1702 throw new Error('This provider is temporarily disabled.');
1703 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MOONSHOT) {1703 } 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();
1705 apiKey = readSecret(request.user.directories, SECRET_KEYS.MOONSHOT);1705 apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MOONSHOT);
1706 headers = {};1706 headers = {};
1707 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.FIREWORKS) {1707 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.FIREWORKS) {
1708 apiUrl = API_FIREWORKS;1708 apiUrl = API_FIREWORKS;
@@ -2249,8 +2249,8 @@ router.post('/generate', async function (request, response) {
2249 setJsonObjectFormat(bodyParams, request.body.messages, request.body.json_schema);2249 setJsonObjectFormat(bodyParams, request.body.messages, request.body.json_schema);
2250 }2250 }
2251 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MOONSHOT) {2251 } 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();
2253 apiKey = readSecret(request.user.directories, SECRET_KEYS.MOONSHOT);2253 apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MOONSHOT);
2254 headers = {};2254 headers = {};
2255 bodyParams = {};2255 bodyParams = {};
2256 request.body.json_schema2256 request.body.json_schema
@@ -2265,8 +2265,9 @@ router.post('/generate', async function (request, response) {
2265 };2265 };
2266 throw new Error('This provider is temporarily disabled.');2266 throw new Error('This provider is temporarily disabled.');
2267 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZAI) {2267 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZAI) {
2268 apiUrl = request.body.zai_endpoint === ZAI_ENDPOINT.CODING ? API_ZAI_CODING : API_ZAI_COMMON;2268 const defaultApiUrl = request.body.zai_endpoint === ZAI_ENDPOINT.CODING ? API_ZAI_CODING : API_ZAI_COMMON;
2269 apiKey = readSecret(request.user.directories, SECRET_KEYS.ZAI);2269 apiUrl = new URL(request.body.reverse_proxy || defaultApiUrl).toString();
2270 apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.ZAI);
2270 headers = {2271 headers = {
2271 'Accept-Language': 'en-US,en',2272 'Accept-Language': 'en-US,en',
2272 };2273 };
src/endpoints/openai.js+9 -6
@@ -73,7 +73,7 @@ router.post('/caption-image', async (request, response) => {
73 key = readSecret(request.user.directories, SECRET_KEYS.COHERE);73 key = readSecret(request.user.directories, SECRET_KEYS.COHERE);
74 }74 }
7575
76 if (request.body.api === 'moonshot') {76 if (request.body.api === 'moonshot' && !request.body.reverse_proxy) {
77 key = readSecret(request.user.directories, SECRET_KEYS.MOONSHOT);77 key = readSecret(request.user.directories, SECRET_KEYS.MOONSHOT);
78 }78 }
7979
@@ -89,8 +89,11 @@ router.post('/caption-image', async (request, response) => {
89 key = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);89 key = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
90 }90 }
9191
92 if (request.body.api === 'zai') {92 if (request.body.api === 'zai' && !request.body.reverse_proxy) {
93 key = readSecret(request.user.directories, SECRET_KEYS.ZAI);93 key = readSecret(request.user.directories, SECRET_KEYS.ZAI);
94 }
95
96 if (request.body.api === 'zai') {
94 bodyParams.max_tokens = 4096; // default is 102497 bodyParams.max_tokens = 4096; // default is 1024
95 }98 }
9699
@@ -174,7 +177,7 @@ router.post('/caption-image', async (request, response) => {
174 apiUrl = 'https://text.pollinations.ai/openai/chat/completions';177 apiUrl = 'https://text.pollinations.ai/openai/chat/completions';
175 }178 }
176179
177 if (request.body.api === 'moonshot') {180 if (request.body.api === 'moonshot' && !request.body.reverse_proxy) {
178 apiUrl = 'https://api.moonshot.ai/v1/chat/completions';181 apiUrl = 'https://api.moonshot.ai/v1/chat/completions';
179 }182 }
180183
@@ -190,13 +193,14 @@ router.post('/caption-image', async (request, response) => {
190 apiUrl = 'https://api.electronhub.ai/v1/chat/completions';193 apiUrl = 'https://api.electronhub.ai/v1/chat/completions';
191 }194 }
192195
193 if (request.body.api === 'zai') {196 if (request.body.api === 'zai' && !request.body.reverse_proxy) {
194 apiUrl = request.body.zai_endpoint === ZAI_ENDPOINT.CODING197 apiUrl = request.body.zai_endpoint === ZAI_ENDPOINT.CODING
195 ? 'https://api.z.ai/api/coding/paas/v4/chat/completions'198 ? 'https://api.z.ai/api/coding/paas/v4/chat/completions'
196 : 'https://api.z.ai/api/paas/v4/chat/completions';199 : 'https://api.z.ai/api/paas/v4/chat/completions';
200 }
197201
198 // Handle video inlining for Z.AI202 // Handle video inlining for Z.AI
199 if (/data:video\/\w+;base64,/.test(request.body.image)) {203 if (request.body.api === 'zai' && /data:video\/\w+;base64,/.test(request.body.image)) {
200 const message = body.messages.find(msg => Array.isArray(msg.content));204 const message = body.messages.find(msg => Array.isArray(msg.content));
201 if (message) {205 if (message) {
202 const imgContent = message.content.find(c => c.type === 'image_url');206 const imgContent = message.content.find(c => c.type === 'image_url');
@@ -207,7 +211,6 @@ router.post('/caption-image', async (request, response) => {
207 }211 }
208 }212 }
209 }213 }
210 }
211214
212 if (['koboldcpp', 'vllm', 'llamacpp', 'ooba'].includes(request.body.api)) {215 if (['koboldcpp', 'vllm', 'llamacpp', 'ooba'].includes(request.body.api)) {
213 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;216 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;