Add official GLM API as CC provider (#4678) * Add UI elements * Add support for model configuration * fix: update API request parameters for improved handling * Add logo img * Fix tool calling with negative index * Include tool calls into 'last in context' calculation * feat: add support for captioning

4add4f0090dc41d194c75c52684e9df6468b698f

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
16 files changed, +160 -14Ignore whitespace
public/img/zai.svg+3 -0
@@ -0,0 +1,3 @@
1<svg version="1.1" x="0" y="0" xml:space="preserve" id="svg6" width="18.60156" height="15.82031" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18.60156 15.82031">
2 <path id="path2" d="M.4707 0v2.33008h7.09961c.36 0 .7004-.1807.9004-.4707L9.77147.00977S.4607.01.4707 0zm10.68946.00977L0 15.8203h7.4414L18.60157.00977Zm-.11914 13.48046c-.36 0-.7004.1807-.9004.4707l-1.31054 1.85938h9.30078v-2.33008z"/>
3</svg>
public/index.html+32 -6
@@ -693,7 +693,7 @@
693 </span>693 </span>
694 </div>694 </div>
695 </div>695 </div>
696 <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">696 <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,zai">
697 <div class="range-block-title" data-i18n="Temperature">697 <div class="range-block-title" data-i18n="Temperature">
698 Temperature698 Temperature
699 </div>699 </div>
@@ -745,7 +745,7 @@
745 </div>745 </div>
746 </div>746 </div>
747 </div>747 </div>
748 <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">748 <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,zai">
749 <div class="range-block-title" data-i18n="Top P">749 <div class="range-block-title" data-i18n="Top P">
750 Top P750 Top P
751 </div>751 </div>
@@ -1965,7 +1965,7 @@
1965 </b>1965 </b>
1966 </div>1966 </div>
1967 </div>1967 </div>
1968 <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">1968 <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,zai">
1969 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">1969 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
1970 <input id="openai_function_calling" type="checkbox" />1970 <input id="openai_function_calling" type="checkbox" />
1971 <span data-i18n="Enable function calling">Enable function calling</span>1971 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -1980,7 +1980,7 @@
1980 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>1980 <strong data-i18n="enable_functions_desc_4">Not supported when Prompt Post-Processing with "no tools" is used!</strong>
1981 </div>1981 </div>
1982 </div>1982 </div>
1983 <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt,electronhub,azure_openai">1983 <div class="range-block" data-source="openai,aimlapi,openrouter,mistralai,makersuite,vertexai,claude,custom,xai,pollinations,moonshot,cohere,cometapi,nanogpt,electronhub,azure_openai,zai">
1984 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">1984 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
1985 <input id="openai_image_inlining" type="checkbox" />1985 <input id="openai_image_inlining" type="checkbox" />
1986 <span data-i18n="Send inline images">Send inline images</span>1986 <span data-i18n="Send inline images">Send inline images</span>
@@ -1996,7 +1996,7 @@
1996 <code><i class="fa-solid fa-wand-magic-sparkles"></i></code>1996 <code><i class="fa-solid fa-wand-magic-sparkles"></i></code>
1997 <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>1997 <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>
1998 </div>1998 </div>
1999 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub,azure_openai">1999 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations,cohere,cometapi,nanogpt,moonshot,aimlapi,openrouter,mistralai,electronhub,azure_openai,zai">
2000 <div class="flex-container oneline-dropdown">2000 <div class="flex-container oneline-dropdown">
2001 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">2001 <label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
2002 Inline Image Quality2002 Inline Image Quality
@@ -2058,7 +2058,7 @@
2058 </span>2058 </span>
2059 </div>2059 </div>
2060 </div>2060 </div>
2061 <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi,electronhub,azure_openai,nanogpt">2061 <div class="range-block" data-source="deepseek,aimlapi,openrouter,custom,claude,xai,makersuite,vertexai,pollinations,moonshot,mistralai,fireworks,cometapi,electronhub,azure_openai,nanogpt,zai">
2062 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">2062 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
2063 <input id="openai_show_thoughts" type="checkbox" />2063 <input id="openai_show_thoughts" type="checkbox" />
2064 <span data-i18n="Request model reasoning">Request model reasoning</span>2064 <span data-i18n="Request model reasoning">Request model reasoning</span>
@@ -2801,6 +2801,7 @@
2801 <option value="perplexity">Perplexity</option>2801 <option value="perplexity">Perplexity</option>
2802 <option value="pollinations">Pollinations</option>2802 <option value="pollinations">Pollinations</option>
2803 <option value="xai">xAI (Grok)</option>2803 <option value="xai">xAI (Grok)</option>
2804 <option value="zai">Z.AI (GLM)</option>
2804 </optgroup>2805 </optgroup>
2805 </select>2806 </select>
2806 <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,vertexai,deepseek,xai">2807 <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,vertexai,deepseek,xai">
@@ -3674,6 +3675,31 @@
3674 <option value="kimi-thinking-preview">kimi-thinking-preview</option>3675 <option value="kimi-thinking-preview">kimi-thinking-preview</option>
3675 </select>3676 </select>
3676 </div>3677 </div>
3678 <div id="zai_form" data-source="zai">
3679 <h4>
3680 <a data-i18n="Z.AI API Key" href="https://z.ai/manage-apikey/apikey-list" target="_blank" rel="noopener noreferrer">
3681 Z.AI API Key
3682 </a>
3683 </h4>
3684 <div class="flex-container">
3685 <input id="api_key_zai" name="api_key_zai" class="text_pole flex1" value="" type="text" autocomplete="off">
3686 <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_zai"></div>
3687 </div>
3688 <div data-for="api_key_zai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3689 For privacy reasons, your API key will be hidden after you click 'Connect'.
3690 </div>
3691 <h4 data-i18n="Z.AI Model">Z.AI Model</h4>
3692 <select id="model_zai_select">
3693 <option value="glm-4.6">glm-4.6</option>
3694 <option value="glm-4.5v">glm-4.5v</option>
3695 <option value="glm-4.5">glm-4.5</option>
3696 <option value="glm-4.5-air">glm-4.5-air</option>
3697 <option value="glm-4.5-x">glm-4.5-x</option>
3698 <option value="glm-4.5-airx">glm-4.5-airx</option>
3699 <option value="glm-4.5-flash">glm-4.5-flash</option>
3700 <option value="glm-4-32b-0414-128k">glm-4-32b-0414-128k</option>
3701 </select>
3702 </div>
3677 <div id="azure_openai_settings" data-source="azure_openai">3703 <div id="azure_openai_settings" data-source="azure_openai">
3678 <!-- Azure Base URL -->3704 <!-- Azure Base URL -->
3679 <h4><span data-i18n="Azure Base URL">Azure Base URL</span></h4>3705 <h4><span data-i18n="Azure Base URL">Azure Base URL</span></h4>
public/script.js+2 -1
@@ -5264,7 +5264,7 @@ function setInContextMessages(msgInContextCount, type) {
5264 msgInContextCount++;5264 msgInContextCount++;
5265 }5265 }
52665266
5267 const lastMessageBlock = chatElement.find('.mes:not([is_system="true"])').eq(-msgInContextCount);5267 const lastMessageBlock = chatElement.find('.mes:not([is_system="true"]), .mes.toolCall').eq(-msgInContextCount);
5268 lastMessageBlock.addClass('lastInContext');5268 lastMessageBlock.addClass('lastInContext');
52695269
5270 if (lastMessageBlock.length === 0) {5270 if (lastMessageBlock.length === 0) {
@@ -5520,6 +5520,7 @@ export function extractJsonFromData(data, { mainApi = null, chatCompletionSource
5520 case chat_completion_sources.XAI:5520 case chat_completion_sources.XAI:
5521 case chat_completion_sources.ELECTRONHUB:5521 case chat_completion_sources.ELECTRONHUB:
5522 case chat_completion_sources.AZURE_OPENAI:5522 case chat_completion_sources.AZURE_OPENAI:
5523 case chat_completion_sources.ZAI:
5523 default:5524 default:
5524 result = tryParse(text);5525 result = tryParse(text);
5525 break;5526 break;
public/scripts/RossAscends-mods.js+1 -0
@@ -411,6 +411,7 @@ function RA_autoconnect(PrevApi) {
411 || (secret_state[SECRET_KEYS.MOONSHOT] && oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT)411 || (secret_state[SECRET_KEYS.MOONSHOT] && oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT)
412 || (secret_state[SECRET_KEYS.FIREWORKS] && oai_settings.chat_completion_source == chat_completion_sources.FIREWORKS)412 || (secret_state[SECRET_KEYS.FIREWORKS] && oai_settings.chat_completion_source == chat_completion_sources.FIREWORKS)
413 || (secret_state[SECRET_KEYS.COMETAPI] && oai_settings.chat_completion_source == chat_completion_sources.COMETAPI)413 || (secret_state[SECRET_KEYS.COMETAPI] && oai_settings.chat_completion_source == chat_completion_sources.COMETAPI)
414 || (secret_state[SECRET_KEYS.ZAI] && oai_settings.chat_completion_source == chat_completion_sources.ZAI)
414 || (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS)415 || (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS)
415 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)416 || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
416 || (secret_state[SECRET_KEYS.AZURE_OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI)417 || (secret_state[SECRET_KEYS.AZURE_OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI)
public/scripts/extensions/caption/index.js+1 -0
@@ -442,6 +442,7 @@ jQuery(async function () {
442 'moonshot': SECRET_KEYS.MOONSHOT,442 'moonshot': SECRET_KEYS.MOONSHOT,
443 'nanogpt': SECRET_KEYS.NANOGPT,443 'nanogpt': SECRET_KEYS.NANOGPT,
444 'electronhub': SECRET_KEYS.ELECTRONHUB,444 'electronhub': SECRET_KEYS.ELECTRONHUB,
445 'zai': SECRET_KEYS.ZAI,
445 };446 };
446447
447 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {448 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
public/scripts/extensions/caption/settings.html+2 -0
@@ -37,6 +37,7 @@
37 <option value="pollinations">Pollinations</option>37 <option value="pollinations">Pollinations</option>
38 <option value="vllm">vLLM</option>38 <option value="vllm">vLLM</option>
39 <option value="xai">xAI (Grok)</option>39 <option value="xai">xAI (Grok)</option>
40 <option value="zai">Z.AI (GLM)</option>
40 </select>41 </select>
41 </div>42 </div>
42 <div class="flex1 flex-container flexFlowColumn flexNoGap">43 <div class="flex1 flex-container flexFlowColumn flexNoGap">
@@ -151,6 +152,7 @@
151 <option data-type="ollama" value="mistral-small3.2">mistral-small3.2</option>152 <option data-type="ollama" value="mistral-small3.2">mistral-small3.2</option>
152 <option data-type="ollama" value="llama3.2-vision">llama3.2-vision</option>153 <option data-type="ollama" value="llama3.2-vision">llama3.2-vision</option>
153 <option data-type="ollama" value="llama4">llama4</option>154 <option data-type="ollama" value="llama4">llama4</option>
155 <option data-type="zai" value="glm-4.5v">glm-4.5v</option>
154 <option data-type="llamacpp" value="llamacpp_current" data-i18n="currently_loaded">[Currently loaded]</option>156 <option data-type="llamacpp" value="llamacpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
155 <option data-type="ooba" value="ooba_current" data-i18n="currently_loaded">[Currently loaded]</option>157 <option data-type="ooba" value="ooba_current" data-i18n="currently_loaded">[Currently loaded]</option>
156 <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>158 <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
public/scripts/extensions/shared.js+4 -0
@@ -255,6 +255,10 @@ function throwIfInvalidModel(useReverseProxy) {
255 if (multimodalApi === 'electronhub' && !secret_state[SECRET_KEYS.ELECTRONHUB]) {255 if (multimodalApi === 'electronhub' && !secret_state[SECRET_KEYS.ELECTRONHUB]) {
256 throw new Error('Electron Hub API key is not set.');256 throw new Error('Electron Hub API key is not set.');
257 }257 }
258
259 if (multimodalApi === 'zai' && !secret_state[SECRET_KEYS.ZAI]) {
260 throw new Error('Z.AI API key is not set.');
261 }
258}262}
259263
260/**264/**
public/scripts/openai.js+82 -5
@@ -189,6 +189,7 @@ export const chat_completion_sources = {
189 FIREWORKS: 'fireworks',189 FIREWORKS: 'fireworks',
190 COMETAPI: 'cometapi',190 COMETAPI: 'cometapi',
191 AZURE_OPENAI: 'azure_openai',191 AZURE_OPENAI: 'azure_openai',
192 ZAI: 'zai',
192};193};
193194
194const character_names_behavior = {195const character_names_behavior = {
@@ -294,6 +295,7 @@ export const settingsToUpdate = {
294 custom_prompt_post_processing: ['#custom_prompt_post_processing', 'custom_prompt_post_processing', false, true],295 custom_prompt_post_processing: ['#custom_prompt_post_processing', 'custom_prompt_post_processing', false, true],
295 google_model: ['#model_google_select', 'google_model', false, true],296 google_model: ['#model_google_select', 'google_model', false, true],
296 vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true],297 vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true],
298 zai_model: ['#model_zai_select', 'zai_model', false, true],
297 openai_max_context: ['#openai_max_context', 'openai_max_context', false, false],299 openai_max_context: ['#openai_max_context', 'openai_max_context', false, false],
298 openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false],300 openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false],
299 wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true, false],301 wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true, false],
@@ -391,6 +393,7 @@ const default_settings = {
391 cometapi_model: 'gpt-4o',393 cometapi_model: 'gpt-4o',
392 moonshot_model: 'kimi-latest',394 moonshot_model: 'kimi-latest',
393 fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct',395 fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct',
396 zai_model: 'glm-4.6',
394 azure_base_url: '',397 azure_base_url: '',
395 azure_deployment_name: '',398 azure_deployment_name: '',
396 azure_api_version: '2024-02-15-preview',399 azure_api_version: '2024-02-15-preview',
@@ -487,6 +490,7 @@ const oai_settings = {
487 cometapi_model: 'gpt-4o',490 cometapi_model: 'gpt-4o',
488 moonshot_model: 'kimi-latest',491 moonshot_model: 'kimi-latest',
489 fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct',492 fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct',
493 zai_model: 'glm-4.6',
490 azure_base_url: '',494 azure_base_url: '',
491 azure_deployment_name: '',495 azure_deployment_name: '',
492 azure_api_version: '2024-02-15-preview',496 azure_api_version: '2024-02-15-preview',
@@ -1532,7 +1536,7 @@ export async function prepareOpenAIMessages({
1532 const eventData = { chat, dryRun };1536 const eventData = { chat, dryRun };
1533 await eventSource.emit(event_types.CHAT_COMPLETION_PROMPT_READY, eventData);1537 await eventSource.emit(event_types.CHAT_COMPLETION_PROMPT_READY, eventData);
15341538
1535 openai_messages_count = chat.filter(x => !x?.tool_calls && (x?.role === 'user' || x?.role === 'assistant'))?.length || 0;1539 openai_messages_count = chat.filter(x => !x?.tool_calls && ['user', 'assistant', 'tool'].includes(x?.role)).length || 0;
15361540
1537 return [chat, promptManager.tokenHandler.counts];1541 return [chat, promptManager.tokenHandler.counts];
1538}1542}
@@ -1664,6 +1668,8 @@ export function getChatCompletionModel(source = null) {
1664 return oai_settings.fireworks_model;1668 return oai_settings.fireworks_model;
1665 case chat_completion_sources.AZURE_OPENAI:1669 case chat_completion_sources.AZURE_OPENAI:
1666 return oai_settings.azure_openai_model;1670 return oai_settings.azure_openai_model;
1671 case chat_completion_sources.ZAI:
1672 return oai_settings.zai_model;
1667 default:1673 default:
1668 console.error(`Unknown chat completion source: ${activeSource}`);1674 console.error(`Unknown chat completion source: ${activeSource}`);
1669 return '';1675 return '';
@@ -1921,7 +1927,7 @@ function saveModelList(data) {
1921 }1927 }
19221928
1923 if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {1929 if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
1924 // Clear only the "Other" optgroup for dynamic models1930 // Clear only the "Other" optgroup for dynamic models
1925 $('#google_other_models').empty();1931 $('#google_other_models').empty();
19261932
1927 // Get static model options that are already in the HTML1933 // Get static model options that are already in the HTML
@@ -1932,7 +1938,7 @@ function saveModelList(data) {
19321938
1933 // Add dynamic models to the "Other" group1939 // Add dynamic models to the "Other" group
1934 model_list.forEach((model) => {1940 model_list.forEach((model) => {
1935 // Only add if not already in static list1941 // Only add if not already in static list
1936 if (!staticModels.includes(model.id)) {1942 if (!staticModels.includes(model.id)) {
1937 $('#google_other_models').append(1943 $('#google_other_models').append(
1938 $('<option>', {1944 $('<option>', {
@@ -2299,7 +2305,8 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2299 const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;2305 const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;
2300 const isPollinations = oai_settings.chat_completion_source == chat_completion_sources.POLLINATIONS;2306 const isPollinations = oai_settings.chat_completion_source == chat_completion_sources.POLLINATIONS;
2301 const isMoonshot = oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT;2307 const isMoonshot = oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT;
2302 const isAzureOpenAI = oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI; // Add this line2308 const isAzureOpenAI = oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI;
2309 const isZai = oai_settings.chat_completion_source == chat_completion_sources.ZAI;
2303 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);2310 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
2304 const isQuiet = type === 'quiet';2311 const isQuiet = type === 'quiet';
2305 const isImpersonate = type === 'impersonate';2312 const isImpersonate = type === 'impersonate';
@@ -2500,6 +2507,14 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2500 generate_data['top_k'] = Number(oai_settings.top_k_openai);2507 generate_data['top_k'] = Number(oai_settings.top_k_openai);
2501 }2508 }
25022509
2510 // https://docs.z.ai/api-reference/llm/chat-completion
2511 if (isZai) {
2512 generate_data['top_p'] = generate_data.top_p || 0.01;
2513 generate_data['stop'] = getCustomStoppingStrings(1);
2514 delete generate_data.presence_penalty;
2515 delete generate_data.frequency_penalty;
2516 }
2517
2503 const seedSupportedSources = [2518 const seedSupportedSources = [
2504 chat_completion_sources.OPENAI,2519 chat_completion_sources.OPENAI,
2505 chat_completion_sources.AZURE_OPENAI,2520 chat_completion_sources.AZURE_OPENAI,
@@ -2681,7 +2696,7 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov
2681 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');2696 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');
2682 }2697 }
2683 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';2698 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
2684 } else if ([chat_completion_sources.CUSTOM, chat_completion_sources.POLLINATIONS, chat_completion_sources.AIMLAPI, chat_completion_sources.MOONSHOT, chat_completion_sources.COMETAPI, chat_completion_sources.ELECTRONHUB, chat_completion_sources.NANOGPT].includes(chat_completion_source)) {2699 } else if ([chat_completion_sources.CUSTOM, chat_completion_sources.POLLINATIONS, chat_completion_sources.AIMLAPI, chat_completion_sources.MOONSHOT, chat_completion_sources.COMETAPI, chat_completion_sources.ELECTRONHUB, chat_completion_sources.NANOGPT, chat_completion_sources.ZAI].includes(chat_completion_source)) {
2685 if (show_thoughts) {2700 if (show_thoughts) {
2686 state.reasoning +=2701 state.reasoning +=
2687 data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ??2702 data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content ??
@@ -3655,6 +3670,7 @@ function loadOpenAISettings(data, settings) {
3655 oai_settings.cometapi_model = settings.cometapi_model ?? default_settings.cometapi_model;3670 oai_settings.cometapi_model = settings.cometapi_model ?? default_settings.cometapi_model;
3656 oai_settings.moonshot_model = settings.moonshot_model ?? default_settings.moonshot_model;3671 oai_settings.moonshot_model = settings.moonshot_model ?? default_settings.moonshot_model;
3657 oai_settings.fireworks_model = settings.fireworks_model ?? default_settings.fireworks_model;3672 oai_settings.fireworks_model = settings.fireworks_model ?? default_settings.fireworks_model;
3673 oai_settings.zai_model = settings.zai_model ?? default_settings.zai_model;
3658 oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;3674 oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;
3659 oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;3675 oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;
3660 oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;3676 oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;
@@ -3760,6 +3776,8 @@ function loadOpenAISettings(data, settings) {
3760 $(`#model_pollinations_select option[value="${oai_settings.pollinations_model}"`).prop('selected', true);3776 $(`#model_pollinations_select option[value="${oai_settings.pollinations_model}"`).prop('selected', true);
3761 $('#model_moonshot_select').val(oai_settings.moonshot_model);3777 $('#model_moonshot_select').val(oai_settings.moonshot_model);
3762 $(`#model_moonshot_select option[value="${oai_settings.moonshot_model}"`).prop('selected', true);3778 $(`#model_moonshot_select option[value="${oai_settings.moonshot_model}"`).prop('selected', true);
3779 $('#model_zai_select').val(oai_settings.zai_model);
3780 $(`#model_zai_select option[value="${oai_settings.zai_model}"`).prop('selected', true);
3763 $('#custom_model_id').val(oai_settings.custom_model);3781 $('#custom_model_id').val(oai_settings.custom_model);
3764 $('#custom_api_url_text').val(oai_settings.custom_url);3782 $('#custom_api_url_text').val(oai_settings.custom_url);
3765 $('#azure_base_url').val(oai_settings.azure_base_url);3783 $('#azure_base_url').val(oai_settings.azure_base_url);
@@ -3933,6 +3951,7 @@ async function getStatusOpen() {
3933 chat_completion_sources.AI21,3951 chat_completion_sources.AI21,
3934 chat_completion_sources.VERTEXAI,3952 chat_completion_sources.VERTEXAI,
3935 chat_completion_sources.PERPLEXITY,3953 chat_completion_sources.PERPLEXITY,
3954 chat_completion_sources.ZAI,
3936 ];3955 ];
3937 if (noValidateSources.includes(oai_settings.chat_completion_source)) {3956 if (noValidateSources.includes(oai_settings.chat_completion_source)) {
3938 let status = t`Key saved; press \"Test Message\" to verify.`;3957 let status = t`Key saved; press \"Test Message\" to verify.`;
@@ -4059,6 +4078,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
4059 moonshot_model: settings.moonshot_model,4078 moonshot_model: settings.moonshot_model,
4060 fireworks_model: settings.fireworks_model,4079 fireworks_model: settings.fireworks_model,
4061 cometapi_model: settings.cometapi_model,4080 cometapi_model: settings.cometapi_model,
4081 zai_model: settings.zai_model,
4062 custom_model: settings.custom_model,4082 custom_model: settings.custom_model,
4063 custom_url: settings.custom_url,4083 custom_url: settings.custom_url,
4064 custom_include_body: settings.custom_include_body,4084 custom_include_body: settings.custom_include_body,
@@ -4713,6 +4733,29 @@ function getGroqMaxContext(model, isUnlocked) {
4713 return Object.entries(contextMap).find(([key]) => model.includes(key))?.[1] || max_128k;4733 return Object.entries(contextMap).find(([key]) => model.includes(key))?.[1] || max_128k;
4714}4734}
47154735
4736/**
4737 * Get the maximum context size for the Z.AI model
4738 * @param {string} model Model identifier
4739 * @param {boolean} isUnlocked If context limits are unlocked
4740 * @returns {number} Maximum context size in tokens
4741 */
4742function getZaiMaxContext(model, isUnlocked) {
4743 if (isUnlocked) {
4744 return unlocked_max;
4745 }
4746
4747 const contextMap = {
4748 'glm-4.6': max_200k,
4749 'glm-4.5': max_128k,
4750 'glm-4-32b-0414-128k': max_128k,
4751 'glm-4.5-air': max_128k,
4752 'glm-4.5v': max_64k,
4753 };
4754
4755 // Return context size if model found, otherwise default to 128k
4756 return Object.entries(contextMap).find(([key]) => model.includes(key))?.[1] || max_128k;
4757}
4758
4716function getMoonshotMaxContext(model, isUnlocked) {4759function getMoonshotMaxContext(model, isUnlocked) {
4717 if (isUnlocked) {4760 if (isUnlocked) {
4718 return unlocked_max;4761 return unlocked_max;
@@ -4980,6 +5023,11 @@ async function onModelChange() {
4980 oai_settings.azure_openai_model = value;5023 oai_settings.azure_openai_model = value;
4981 }5024 }
49825025
5026 if ($(this).is('#model_zai_select')) {
5027 console.log('ZAI model changed to', value);
5028 oai_settings.zai_model = value;
5029 }
5030
4983 if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) {5031 if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) {
4984 if (oai_settings.max_context_unlocked) {5032 if (oai_settings.max_context_unlocked) {
4985 $('#openai_max_context').attr('max', max_2mil);5033 $('#openai_max_context').attr('max', max_2mil);
@@ -5282,6 +5330,15 @@ async function onModelChange() {
5282 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');5330 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
5283 }5331 }
52845332
5333 if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) {
5334 const maxContext = getZaiMaxContext(oai_settings.zai_model, oai_settings.max_context_unlocked);
5335 $('#openai_max_context').attr('max', maxContext);
5336 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
5337 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
5338 oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
5339 $('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
5340 }
5341
5285 $('#openai_max_context_counter').attr('max', Number($('#openai_max_context').attr('max')));5342 $('#openai_max_context_counter').attr('max', Number($('#openai_max_context').attr('max')));
52865343
5287 saveSettingsDebounced();5344 saveSettingsDebounced();
@@ -5583,6 +5640,18 @@ async function onConnectButtonClick(e) {
5583 }5640 }
5584 }5641 }
55855642
5643 if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) {
5644 const api_key_zai = String($('#api_key_zai').val()).trim();
5645
5646 if (api_key_zai.length) {
5647 await writeSecret(SECRET_KEYS.ZAI, api_key_zai);
5648 }
5649
5650 if (!api_key_zai && !secret_state[SECRET_KEYS.ZAI]) {
5651 console.log('No secret key saved for ZAI');
5652 return;
5653 }
5654 }
55865655
5587 startStatusLoading();5656 startStatusLoading();
5588 saveSettingsDebounced();5657 saveSettingsDebounced();
@@ -5660,6 +5729,9 @@ function toggleChatCompletionForms() {
5660 else if (oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI) {5729 else if (oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI) {
5661 $('#azure_openai_model').trigger('change');5730 $('#azure_openai_model').trigger('change');
5662 }5731 }
5732 else if (oai_settings.chat_completion_source == chat_completion_sources.ZAI) {
5733 $('#model_zai_select').trigger('change');
5734 }
56635735
5664 $('[data-source]').each(function () {5736 $('[data-source]').each(function () {
5665 const validSources = $(this).data('source').split(',');5737 const validSources = $(this).data('source').split(',');
@@ -5774,6 +5846,8 @@ export function isImageInliningSupported() {
5774 'moonshot-v1-8k-vision-preview',5846 'moonshot-v1-8k-vision-preview',
5775 'moonshot-v1-32k-vision-preview',5847 'moonshot-v1-32k-vision-preview',
5776 'moonshot-v1-128k-vision-preview',5848 'moonshot-v1-128k-vision-preview',
5849 // Z.AI (GLM)
5850 'glm-4.5v',
5777 ];5851 ];
57785852
5779 switch (oai_settings.chat_completion_source) {5853 switch (oai_settings.chat_completion_source) {
@@ -5816,6 +5890,8 @@ export function isImageInliningSupported() {
5816 return visionSupportedModels.some(model => oai_settings.moonshot_model.includes(model));5890 return visionSupportedModels.some(model => oai_settings.moonshot_model.includes(model));
5817 case chat_completion_sources.NANOGPT:5891 case chat_completion_sources.NANOGPT:
5818 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.nanogpt_model)?.capabilities?.vision);5892 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.nanogpt_model)?.capabilities?.vision);
5893 case chat_completion_sources.ZAI:
5894 return visionSupportedModels.some(model => oai_settings.zai_model.includes(model));
5819 default:5895 default:
5820 return false;5896 return false;
5821 }5897 }
@@ -6664,6 +6740,7 @@ export function initOpenAI() {
6664 $('#model_moonshot_select').on('change', onModelChange);6740 $('#model_moonshot_select').on('change', onModelChange);
6665 $('#model_fireworks_select').on('change', onModelChange);6741 $('#model_fireworks_select').on('change', onModelChange);
6666 $('#azure_openai_model').on('change', onModelChange);6742 $('#azure_openai_model').on('change', onModelChange);
6743 $('#model_zai_select').on('change', onModelChange);
6667 $('#settings_preset_openai').on('change', onSettingsPresetChange);6744 $('#settings_preset_openai').on('change', onSettingsPresetChange);
6668 $('#new_oai_preset').on('click', onNewPresetClick);6745 $('#new_oai_preset').on('click', onNewPresetClick);
6669 $('#delete_oai_preset').on('click', onDeletePresetClick);6746 $('#delete_oai_preset').on('click', onDeletePresetClick);
public/scripts/reasoning.js+1 -0
@@ -127,6 +127,7 @@ export function extractReasoningFromData(data, {
127 case chat_completion_sources.COMETAPI:127 case chat_completion_sources.COMETAPI:
128 case chat_completion_sources.ELECTRONHUB:128 case chat_completion_sources.ELECTRONHUB:
129 case chat_completion_sources.NANOGPT:129 case chat_completion_sources.NANOGPT:
130 case chat_completion_sources.ZAI:
130 case chat_completion_sources.CUSTOM: {131 case chat_completion_sources.CUSTOM: {
131 return data?.choices?.[0]?.message?.reasoning_content132 return data?.choices?.[0]?.message?.reasoning_content
132 ?? data?.choices?.[0]?.message?.reasoning133 ?? data?.choices?.[0]?.message?.reasoning
public/scripts/secrets.js+3 -0
@@ -68,6 +68,7 @@ export const SECRET_KEYS = {
68 MINIMAX_GROUP_ID: 'minimax_group_id',68 MINIMAX_GROUP_ID: 'minimax_group_id',
69 MOONSHOT: 'api_key_moonshot',69 MOONSHOT: 'api_key_moonshot',
70 COMETAPI: 'api_key_cometapi',70 COMETAPI: 'api_key_cometapi',
71 ZAI: 'api_key_zai',
71};72};
7273
73const FRIENDLY_NAMES = {74const FRIENDLY_NAMES = {
@@ -124,6 +125,7 @@ const FRIENDLY_NAMES = {
124 [SECRET_KEYS.MOONSHOT]: 'Moonshot AI',125 [SECRET_KEYS.MOONSHOT]: 'Moonshot AI',
125 [SECRET_KEYS.COMETAPI]: 'CometAPI',126 [SECRET_KEYS.COMETAPI]: 'CometAPI',
126 [SECRET_KEYS.AZURE_OPENAI]: 'Azure OpenAI',127 [SECRET_KEYS.AZURE_OPENAI]: 'Azure OpenAI',
128 [SECRET_KEYS.ZAI]: 'Z.AI',
127};129};
128130
129const INPUT_MAP = {131const INPUT_MAP = {
@@ -163,6 +165,7 @@ const INPUT_MAP = {
163 [SECRET_KEYS.FIREWORKS]: '#api_key_fireworks',165 [SECRET_KEYS.FIREWORKS]: '#api_key_fireworks',
164 [SECRET_KEYS.COMETAPI]: '#api_key_cometapi',166 [SECRET_KEYS.COMETAPI]: '#api_key_cometapi',
165 [SECRET_KEYS.AZURE_OPENAI]: '#api_key_azure_openai',167 [SECRET_KEYS.AZURE_OPENAI]: '#api_key_azure_openai',
168 [SECRET_KEYS.ZAI]: '#api_key_zai',
166};169};
167170
168const getLabel = () => moment().format('L LT');171const getLabel = () => moment().format('L LT');
public/scripts/slash-commands.js+1 -0
@@ -4858,6 +4858,7 @@ function getModelOptions(quiet) {
4858 { id: 'model_moonshot_select', api: 'openai', type: chat_completion_sources.MOONSHOT },4858 { id: 'model_moonshot_select', api: 'openai', type: chat_completion_sources.MOONSHOT },
4859 { id: 'model_fireworks_select', api: 'openai', type: chat_completion_sources.FIREWORKS },4859 { id: 'model_fireworks_select', api: 'openai', type: chat_completion_sources.FIREWORKS },
4860 { id: 'model_cometapi_select', api: 'openai', type: chat_completion_sources.COMETAPI },4860 { id: 'model_cometapi_select', api: 'openai', type: chat_completion_sources.COMETAPI },
4861 { id: 'model_zai_select', api: 'openai', type: chat_completion_sources.ZAI },
4861 { id: 'model_novel_select', api: 'novel', type: null },4862 { id: 'model_novel_select', api: 'novel', type: null },
4862 { id: 'horde_model', api: 'koboldhorde', type: null },4863 { id: 'horde_model', api: 'koboldhorde', type: null },
4863 ];4864 ];
public/scripts/tool-calling.js+3 -2
@@ -444,9 +444,9 @@ export class ToolManager {
444 }444 }
445445
446 for (const toolCallDelta of toolCallDeltas) {446 for (const toolCallDelta of toolCallDeltas) {
447 const toolCallIndex = (typeof toolCallDelta?.index === 'number') ? toolCallDelta.index : toolCallDeltas.indexOf(toolCallDelta);447 const toolCallIndex = toolCallDelta?.index >= 0 ? toolCallDelta.index : toolCallDeltas.indexOf(toolCallDelta);
448448
449 if (isNaN(toolCallIndex) || toolCallIndex < 0) {449 if (isNaN(toolCallIndex)) {
450 continue;450 continue;
451 }451 }
452452
@@ -661,6 +661,7 @@ export class ToolManager {
661 chat_completion_sources.COMETAPI,661 chat_completion_sources.COMETAPI,
662 chat_completion_sources.ELECTRONHUB,662 chat_completion_sources.ELECTRONHUB,
663 chat_completion_sources.AZURE_OPENAI,663 chat_completion_sources.AZURE_OPENAI,
664 chat_completion_sources.ZAI,
664 ];665 ];
665 return supportedSources.includes(oai_settings.chat_completion_source);666 return supportedSources.includes(oai_settings.chat_completion_source);
666 }667 }
src/constants.js+1 -0
@@ -183,6 +183,7 @@ export const CHAT_COMPLETION_SOURCES = {
183 FIREWORKS: 'fireworks',183 FIREWORKS: 'fireworks',
184 COMETAPI: 'cometapi',184 COMETAPI: 'cometapi',
185 AZURE_OPENAI: 'azure_openai',185 AZURE_OPENAI: 'azure_openai',
186 ZAI: 'zai',
186};187};
187188
188/**189/**
src/endpoints/backends/chat-completions.js+15 -0
@@ -73,6 +73,7 @@ const API_POLLINATIONS = 'https://text.pollinations.ai/openai';
73const API_MOONSHOT = 'https://api.moonshot.ai/v1';73const API_MOONSHOT = 'https://api.moonshot.ai/v1';
74const API_FIREWORKS = 'https://api.fireworks.ai/inference/v1';74const API_FIREWORKS = 'https://api.fireworks.ai/inference/v1';
75const API_COMETAPI = 'https://api.cometapi.com/v1';75const API_COMETAPI = 'https://api.cometapi.com/v1';
76const API_ZAI = 'https://api.z.ai/api/paas/v4';
7677
77/**78/**
78 * Gets OpenRouter transforms based on the request.79 * Gets OpenRouter transforms based on the request.
@@ -1958,6 +1959,20 @@ router.post('/generate', function (request, response) {
1958 reasoning_effort: request.body.reasoning_effort,1959 reasoning_effort: request.body.reasoning_effort,
1959 };1960 };
1960 throw new Error('This provider is temporarily disabled.');1961 throw new Error('This provider is temporarily disabled.');
1962 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZAI) {
1963 apiUrl = API_ZAI;
1964 apiKey = readSecret(request.user.directories, SECRET_KEYS.ZAI);
1965 headers = {
1966 'Accept-Language': 'en-US,en',
1967 };
1968 bodyParams = {
1969 thinking: {
1970 type: request.body.include_reasoning ? 'enabled' : 'disabled',
1971 },
1972 };
1973 if (request.body.json_schema) {
1974 setJsonObjectFormat(bodyParams, request.body.messages, request.body.json_schema);
1975 }
1961 } else {1976 } else {
1962 console.warn('This chat completion source is not supported yet.');1977 console.warn('This chat completion source is not supported yet.');
1963 return response.status(400).send({ error: true });1978 return response.status(400).send({ error: true });
src/endpoints/openai.js+8 -0
@@ -85,6 +85,10 @@ router.post('/caption-image', async (request, response) => {
85 key = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);85 key = readSecret(request.user.directories, SECRET_KEYS.ELECTRONHUB);
86 }86 }
8787
88 if (request.body.api === 'zai') {
89 key = readSecret(request.user.directories, SECRET_KEYS.ZAI);
90 }
91
88 const noKeyTypes = ['custom', 'ooba', 'koboldcpp', 'vllm', 'llamacpp', 'pollinations'];92 const noKeyTypes = ['custom', 'ooba', 'koboldcpp', 'vllm', 'llamacpp', 'pollinations'];
89 if (!key && !request.body.reverse_proxy && !noKeyTypes.includes(request.body.api)) {93 if (!key && !request.body.reverse_proxy && !noKeyTypes.includes(request.body.api)) {
90 console.warn('No key found for API', request.body.api);94 console.warn('No key found for API', request.body.api);
@@ -177,6 +181,10 @@ router.post('/caption-image', async (request, response) => {
177 apiUrl = 'https://api.electronhub.ai/v1/chat/completions';181 apiUrl = 'https://api.electronhub.ai/v1/chat/completions';
178 }182 }
179183
184 if (request.body.api === 'zai') {
185 apiUrl = 'https://api.z.ai/api/paas/v4/chat/completions';
186 }
187
180 if (['koboldcpp', 'vllm', 'llamacpp', 'ooba'].includes(request.body.api)) {188 if (['koboldcpp', 'vllm', 'llamacpp', 'ooba'].includes(request.body.api)) {
181 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;189 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;
182 }190 }
src/endpoints/secrets.js+1 -0
@@ -61,6 +61,7 @@ export const SECRET_KEYS = {
61 MOONSHOT: 'api_key_moonshot',61 MOONSHOT: 'api_key_moonshot',
62 COMETAPI: 'api_key_cometapi',62 COMETAPI: 'api_key_cometapi',
63 AZURE_OPENAI: 'api_key_azure_openai',63 AZURE_OPENAI: 'api_key_azure_openai',
64 ZAI: 'api_key_zai',
64};65};
6566
66/**67/**