Zai moonshot reverse proxy (#4923) * adding reverse proxy support * update * added handling for the image caption extension
Signed| @@ -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."> |
| @@ -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 | }; |
| 493 | 495 | ||
| 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 | }; |
| 511 | 511 | ||
| 512 | if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) { | 512 | if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) { |
| @@ -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> |
| @@ -15,7 +15,7 @@ import { createThumbnail, isValidUrl } from '../utils.js'; | |||
| 15 | */ | 15 | */ |
| 16 | export async function getMultimodalCaption(base64Img, prompt) { | 16 | export 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_proxy | 19 | && extension_settings.caption.allow_reverse_proxy |
| 20 | && oai_settings.reverse_proxy | 20 | && oai_settings.reverse_proxy |
| 21 | && isValidUrl(oai_settings.reverse_proxy); | 21 | && isValidUrl(oai_settings.reverse_proxy); |
| @@ -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 | ]; |
| 2490 | 2492 | ||
| 2491 | // Sources that support logprobs | 2493 | // 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 | }; |
| 5600 | 5604 | ||
| @@ -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_schema | 2256 | 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 | }; |
| @@ -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 | } |
| 75 | 75 | ||
| 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 | } |
| 79 | 79 | ||
| @@ -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 | } |
| 91 | 91 | ||
| 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 1024 | 97 | bodyParams.max_tokens = 4096; // default is 1024 |
| 95 | } | 98 | } |
| 96 | 99 | ||
| @@ -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 | } |
| 176 | 179 | ||
| 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 | } |
| 180 | 183 | ||
| @@ -190,21 +193,21 @@ 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 | } |
| 192 | 195 | ||
| 193 | if (request.body.api === 'zai') { | 196 | if (request.body.api === 'zai' && !request.body.reverse_proxy) { |
| 194 | apiUrl = request.body.zai_endpoint === ZAI_ENDPOINT.CODING | 197 | 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 | } | ||
| 197 | 201 | ||
| 198 | // Handle video inlining for Z.AI | 202 | // 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'); |
| 203 | if (imgContent) { | 207 | if (imgContent) { |
| 204 | imgContent.type = 'video_url'; | 208 | imgContent.type = 'video_url'; |
| 205 | imgContent.video_url = imgContent.image_url; | 209 | imgContent.video_url = imgContent.image_url; |
| 206 | delete imgContent.image_url; | 210 | delete imgContent.image_url; |
| 207 | } | ||
| 208 | } | 211 | } |
| 209 | } | 212 | } |
| 210 | } | 213 | } |