Z.AI: Add endpoint selection (#4789) * Z.AI: Add endpoint selection Closes #4778 * Add i18n support for Z.AI endpoint options * Update message for captioning ext. * Remove unused imports * Add api-url autoconnect guard

a66801213831b507e774324c894f18aadec2acbc

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

Signed
6 files changed, +62 -4Ignore whitespace
public/index.html+5 -0
@@ -3722,6 +3722,11 @@
37223722 <div data-for="api_key_zai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
37233723 For privacy reasons, your API key will be hidden after you click 'Connect'.
37243724 </div>
3725+ <h4 data-i18n="Z.AI Endpoint">Z.AI Endpoint</h4>
3726+ <select id="zai_endpoint">
3727+ <option value="common" data-i18n="Common API">Common API</option>
3728+ <option value="coding" data-i18n="Coding API">Coding API</option>
3729+ </select>
37253730 <h4 data-i18n="Z.AI Model">Z.AI Model</h4>
37263731 <select id="model_zai_select">
37273732 <option value="glm-4.6">glm-4.6</option>
public/scripts/extensions/caption/settings.html+3 -0
@@ -167,6 +167,9 @@
167167 <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>
168168 </select>
169169 </div>
170+ <div data-type="zai">
171+ <b>Will use Common API. Coding API is not supported!</b>
172+ </div>
170173 <div data-type="ollama">
171174 <div>
172175 The model must be downloaded first! Do it with the <code>ollama pull</code> command or <a href="#" id="caption_ollama_pull">click here</a>.
public/scripts/openai.js+16 -0
@@ -239,6 +239,11 @@ export const reasoning_effort_types = {
239239 max: 'max',
240240};
241241
242+export const ZAI_ENDPOINT = {
243+ COMMON: 'common',
244+ CODING: 'coding',
245+};
246+
242247const sensitiveFields = [
243248 'reverse_proxy',
244249 'proxy_password',
@@ -302,6 +307,7 @@ export const settingsToUpdate = {
302307 google_model: ['#model_google_select', 'google_model', false, true],
303308 vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true],
304309 zai_model: ['#model_zai_select', 'zai_model', false, true],
310+ zai_endpoint: ['#zai_endpoint', 'zai_endpoint', false, true],
305311 openai_max_context: ['#openai_max_context', 'openai_max_context', false, false],
306312 openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false],
307313 wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true, false],
@@ -402,6 +408,7 @@ const default_settings = {
402408 moonshot_model: 'kimi-latest',
403409 fireworks_model: 'accounts/fireworks/models/kimi-k2-instruct',
404410 zai_model: 'glm-4.6',
411+ zai_endpoint: ZAI_ENDPOINT.COMMON,
405412 azure_base_url: '',
406413 azure_deployment_name: '',
407414 azure_api_version: '2024-02-15-preview',
@@ -2487,6 +2494,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
24872494 if (isZai) {
24882495 generate_data['top_p'] = generate_data.top_p || 0.01;
24892496 generate_data['stop'] = getCustomStoppingStrings(1);
2497+ generate_data['zai_endpoint'] = oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON;
24902498 delete generate_data.presence_penalty;
24912499 delete generate_data.frequency_penalty;
24922500 }
@@ -3701,6 +3709,7 @@ function loadOpenAISettings(data, settings) {
37013709 oai_settings.moonshot_model = settings.moonshot_model ?? default_settings.moonshot_model;
37023710 oai_settings.fireworks_model = settings.fireworks_model ?? default_settings.fireworks_model;
37033711 oai_settings.zai_model = settings.zai_model ?? default_settings.zai_model;
3712+ oai_settings.zai_endpoint = settings.zai_endpoint ?? default_settings.zai_endpoint;
37043713 oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;
37053714 oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;
37063715 oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;
@@ -3812,6 +3821,8 @@ function loadOpenAISettings(data, settings) {
38123821 $(`#model_moonshot_select option[value="${oai_settings.moonshot_model}"`).prop('selected', true);
38133822 $('#model_zai_select').val(oai_settings.zai_model);
38143823 $(`#model_zai_select option[value="${oai_settings.zai_model}"`).prop('selected', true);
3824+ $('#zai_endpoint').val(oai_settings.zai_endpoint);
3825+ $(`#zai_endpoint option[value="${oai_settings.zai_endpoint}"`).prop('selected', true);
38153826 $('#custom_model_id').val(oai_settings.custom_model);
38163827 $('#custom_api_url_text').val(oai_settings.custom_url);
38173828 $('#azure_base_url').val(oai_settings.azure_base_url);
@@ -4114,6 +4125,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
41144125 fireworks_model: settings.fireworks_model,
41154126 cometapi_model: settings.cometapi_model,
41164127 zai_model: settings.zai_model,
4128+ zai_endpoint: settings.zai_endpoint,
41174129 custom_model: settings.custom_model,
41184130 custom_url: settings.custom_url,
41194131 custom_include_body: settings.custom_include_body,
@@ -6678,6 +6690,10 @@ export function initOpenAI() {
66786690 oai_settings.vertexai_express_project_id = String($(this).val());
66796691 saveSettingsDebounced();
66806692 });
6693+ $('#zai_endpoint').on('input', function () {
6694+ oai_settings.zai_endpoint = String($(this).val());
6695+ saveSettingsDebounced();
6696+ });
66816697 $('#vertexai_service_account_json').on('input', onVertexAIServiceAccountJsonChange);
66826698 $('#vertexai_validate_service_account').on('click', onVertexAIValidateServiceAccount);
66836699 $('#vertexai_clear_service_account').on('click', onVertexAIClearServiceAccount);
public/scripts/slash-commands.js+29 -2
@@ -61,7 +61,7 @@ import { hideChatMessageRange } from './chats.js';
6161import { getContext, saveMetadataDebounced } from './extensions.js';
6262import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
6363import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group, getGroupMembers } from './group-chats.js';
6464import { chat_completion_sources, oai_settings, promptManager, ZAI_ENDPOINT } from './openai.js';
6565import { user_avatar } from './personas.js';
6666import { addEphemeralStoppingString, chat_styles, context_presets, flushEphemeralStoppingStrings, power_user } from './power-user.js';
6767import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
@@ -2551,6 +2551,7 @@ export function initDefaultSlashCommands() {
25512551 typeList: [ARGUMENT_TYPE.STRING],
25522552 enumList: [
25532553 new SlashCommandEnumValue('custom', 'custom OpenAI-compatible', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'openai')), 'O'),
2554+ new SlashCommandEnumValue('zai', 'Z.AI', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'zai')), 'Z'),
25542555 new SlashCommandEnumValue('kobold', 'KoboldAI Classic', enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'kobold')), 'K'),
25552556 ...Object.values(textgen_types).map(api => new SlashCommandEnumValue(api, null, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === 'textgenerationwebui')), 'T')),
25562557 ],
@@ -2584,7 +2585,7 @@ export function initDefaultSlashCommands() {
25842585 ${t`If a manual API is provided to <b>set</b> the URL, make sure to set <code>connect=false</code>, as auto-connect only works for the currently selected API, or consider switching to it with <code>/api</code> first.`}
25852586 </div>
25862587 <div>
25872588 ${t`This slash command works for most of the Text Completion sources, KoboldAI Classic, and also Custom OpenAI compatible and Z.AI for the Chat Completion sources. If unsure which APIs are supported, check the auto-completion of the optional <code>api</code> argument of this command.`}
25882589 </div>
25892590 `,
25902591 }));
@@ -5159,6 +5160,32 @@ async function setApiUrlCallback({ api = null, connect = 'true', quiet = 'false'
51595160 return url;
51605161 }
51615162
5163+ const isCurrentlyZAI = main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.ZAI;
5164+ if (api === chat_completion_sources.ZAI || (!api && isCurrentlyZAI)) {
5165+ if (!url) {
5166+ return oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON;
5167+ }
5168+
5169+ const permittedValues = Object.values(ZAI_ENDPOINT);
5170+ if (!permittedValues.includes(url)) {
5171+ !isQuiet && toastr.warning(t`Valid options are: ${permittedValues.join(', ')}`, t`ZAI endpoint '${url}' is not a valid option.`);
5172+ return '';
5173+ }
5174+
5175+ if (!isCurrentlyZAI && autoConnect) {
5176+ toastr.warning(t`Z.AI is not the currently selected API, so we cannot do an auto-connect. Consider switching to it via /api beforehand.`);
5177+ return '';
5178+ }
5179+
5180+ $('#zai_endpoint').val(url).trigger('input');
5181+
5182+ if (autoConnect) {
5183+ $('#api_button_openai').trigger('click');
5184+ }
5185+
5186+ return oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON;
5187+ }
5188+
51625189 // Special handling for Kobold Classic API
51635190 const isCurrentlyKoboldClassic = main_api === 'kobold';
51645191 if (api === 'kobold' || (!api && isCurrentlyKoboldClassic)) {
src/constants.js+5 -0
@@ -512,3 +512,8 @@ export const MEDIA_EXTENSIONS = [
512512 'm4a',
513513 'aiff',
514514];
515+
516+export const ZAI_ENDPOINT = {
517+ COMMON: 'common',
518+ CODING: 'coding',
519+};
src/endpoints/backends/chat-completions.js+4 -2
@@ -13,6 +13,7 @@ import {
1313 OPENAI_REASONING_EFFORT_MODELS,
1414 OPENROUTER_HEADERS,
1515 VERTEX_SAFETY,
16+ ZAI_ENDPOINT,
1617} from '../../constants.js';
1718import {
1819 forwardFetchResponse,
@@ -75,7 +76,8 @@ const API_POLLINATIONS = 'https://text.pollinations.ai/openai';
7576const API_MOONSHOT = 'https://api.moonshot.ai/v1';
7677const API_FIREWORKS = 'https://api.fireworks.ai/inference/v1';
7778const API_COMETAPI = 'https://api.cometapi.com/v1';
7879const API_ZAIAPI_ZAI_COMMON = 'https://api.z.ai/api/paas/v4';
80+const API_ZAI_CODING = 'https://api.z.ai/api/coding/paas/v4';
7981const API_SILICONFLOW = 'https://api.siliconflow.com/v1';
8082
8183/**
@@ -1973,7 +1975,7 @@ router.post('/generate', function (request, response) {
19731975 };
19741976 throw new Error('This provider is temporarily disabled.');
19751977 } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.ZAI) {
1976- apiUrl = API_ZAI;
1978+ apiUrl = request.body.zai_endpoint === ZAI_ENDPOINT.CODING ? API_ZAI_CODING : API_ZAI_COMMON;
19771979 apiKey = readSecret(request.user.directories, SECRET_KEYS.ZAI);
19781980 headers = {
19791981 'Accept-Language': 'en-US,en',