Add new Grok models (#4541) * Add new Grok models * Fix xAI request fields filtering * Fetch xAI model lists from API

2679020ecd08ba2f07d37e80069c3d570aba3b86

equal-l2 <eng.equall2@gmail.com>

Signed
5 files changed, +77 -22Showing whitespace changes
public/index.html+3 -9
@@ -3624,19 +3624,13 @@
3624 <div data-for="api_key_xai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">3624 <div data-for="api_key_xai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
3625 For privacy reasons, your API key will be hidden after you click 'Connect'.3625 For privacy reasons, your API key will be hidden after you click 'Connect'.
3626 </div>3626 </div>
3627 <div>
3627 <h4 data-i18n="xAI Model">xAI Model</h4>3628 <h4 data-i18n="xAI Model">xAI Model</h4>
3628 <select id="model_xai_select">3629 <select id="model_xai_select">
3629 <option value="grok-4-0709">grok-4-0709</option>3630 <option value="" data-i18n="-- Connect to the API --">-- Connect to the API --</option>
3630 <option value="grok-3-beta">grok-3-beta</option>
3631 <option value="grok-3-fast-beta">grok-3-fast-beta</option>
3632 <option value="grok-3-mini-beta">grok-3-mini-beta</option>
3633 <option value="grok-3-mini-fast-beta">grok-3-mini-fast-beta</option>
3634 <option value="grok-2-vision-1212">grok-2-vision-1212</option>
3635 <option value="grok-2-1212">grok-2-1212</option>
3636 <option value="grok-vision-beta">grok-vision-beta</option>
3637 <option value="grok-beta">grok-beta</option>
3638 </select>3631 </select>
3639 </div>3632 </div>
3633 </div>
3640 <div id="aimlapi_form" data-source="aimlapi">3634 <div id="aimlapi_form" data-source="aimlapi">
3641 <h4>3635 <h4>
3642 <span data-i18n="AI/ML API Key">AI/ML API Key</span>3636 <span data-i18n="AI/ML API Key">AI/ML API Key</span>
public/scripts/extensions/caption/index.js+1 -0
@@ -550,6 +550,7 @@ jQuery(async function () {
550 await processEndpoint('nanogpt', '/api/backends/chat-completions/multimodal-models/nanogpt');550 await processEndpoint('nanogpt', '/api/backends/chat-completions/multimodal-models/nanogpt');
551 await processEndpoint('electronhub', '/api/backends/chat-completions/multimodal-models/electronhub');551 await processEndpoint('electronhub', '/api/backends/chat-completions/multimodal-models/electronhub');
552 await processEndpoint('mistral', '/api/backends/chat-completions/multimodal-models/mistral');552 await processEndpoint('mistral', '/api/backends/chat-completions/multimodal-models/mistral');
553 await processEndpoint('xai', '/api/backends/chat-completions/multimodal-models/xai');
553 }554 }
554555
555 await addSettings();556 await addSettings();
public/scripts/extensions/caption/settings.html+1 -4
@@ -47,7 +47,7 @@
47 </div>47 </div>
48 </label>48 </label>
49 <select id="caption_multimodal_model" class="flex1 text_pole">49 <select id="caption_multimodal_model" class="flex1 text_pole">
50 <!-- AI/ML API, OpenRouter, Pollinations, NanoGPT, Mistral are added externally by JavaScript -->50 <!-- AI/ML API, OpenRouter, Pollinations, NanoGPT, Mistral, xAI are added externally by JavaScript -->
51 <option data-type="cohere" value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option>51 <option data-type="cohere" value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option>
52 <option data-type="cohere" value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option>52 <option data-type="cohere" value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option>
53 <option data-type="cohere" value="command-a-vision-07-2025">command-a-vision-07-2025</option>53 <option data-type="cohere" value="command-a-vision-07-2025">command-a-vision-07-2025</option>
@@ -168,9 +168,6 @@
168 <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>168 <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
169 <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option>169 <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option>
170 <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>170 <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>
171 <option data-type="xai" value="grok-4-0709">grok-4-0709</option>
172 <option data-type="xai" value="grok-2-vision-1212">grok-2-vision-1212</option>
173 <option data-type="xai" value="grok-vision-beta">grok-vision-beta</option>
174 </select>171 </select>
175 </div>172 </div>
176 <div data-type="ollama">173 <div data-type="ollama">
public/scripts/openai.js+38 -8
@@ -2028,6 +2028,24 @@ function saveModelList(data) {
2028 .append(new Option(modelId || 'None', modelId || '', true, true))2028 .append(new Option(modelId || 'None', modelId || '', true, true))
2029 .trigger('change');2029 .trigger('change');
2030 }2030 }
2031
2032 if (oai_settings.chat_completion_source == chat_completion_sources.XAI) {
2033 $('#model_xai_select').empty();
2034 model_list.forEach((model) => {
2035 $('#model_xai_select').append(
2036 $('<option>', {
2037 value: model.id,
2038 text: model.id,
2039 }));
2040 });
2041
2042 const selectedModel = model_list.find(model => model.id === oai_settings.xai_model);
2043 if (model_list.length > 0 && (!selectedModel || !oai_settings.xai_model)) {
2044 oai_settings.xai_model = model_list[0].id;
2045 }
2046
2047 $('#model_xai_select').val(oai_settings.xai_model).trigger('change');
2048 }
2031}2049}
20322050
2033function appendOpenRouterOptions(model_list, groupModels = false, sort = false) {2051function appendOpenRouterOptions(model_list, groupModels = false, sort = false) {
@@ -2452,19 +2470,24 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2452 }2470 }
24532471
2454 if (isXAI) {2472 if (isXAI) {
2455 if (generate_data.model.includes('grok-4')) {2473 const model = generate_data.model;
2474 if (model.includes('grok-3-mini')) {
2456 delete generate_data.presence_penalty;2475 delete generate_data.presence_penalty;
2457 delete generate_data.frequency_penalty;2476 delete generate_data.frequency_penalty;
2458 delete generate_data.stop;2477 delete generate_data.stop;
2478 } else {
2479 // As of 2025/09/21, only grok-3-mini accepts reasoning_effort
2459 delete generate_data.reasoning_effort;2480 delete generate_data.reasoning_effort;
2460 }2481 }
2461 if (generate_data.model.includes('grok-3-mini')) {2482
2483 if (model.includes('grok-4') || model.includes('grok-code')) {
2462 delete generate_data.presence_penalty;2484 delete generate_data.presence_penalty;
2463 delete generate_data.frequency_penalty;2485 delete generate_data.frequency_penalty;
2486
2487 // grok-4-fast-non-reasoning accepts stop
2488 if (!model.includes('grok-4-fast-non-reasoning')) {
2489 delete generate_data.stop;
2464 }2490 }
2465 if (generate_data.model.includes('grok-vision')) {
2466 delete generate_data.tools;
2467 delete generate_data.tool_choice;
2468 }2491 }
2469 }2492 }
24702493
@@ -4916,6 +4939,10 @@ async function onModelChange() {
4916 }4939 }
49174940
4918 if ($(this).is('#model_xai_select')) {4941 if ($(this).is('#model_xai_select')) {
4942 if (!value) {
4943 console.debug('Null XAI model selected. Ignoring.');
4944 return;
4945 }
4919 console.log('XAI model changed to', value);4946 console.log('XAI model changed to', value);
4920 oai_settings.xai_model = value;4947 oai_settings.xai_model = value;
4921 }4948 }
@@ -5184,11 +5211,14 @@ async function onModelChange() {
5184 $('#openai_max_context').attr('max', unlocked_max);5211 $('#openai_max_context').attr('max', unlocked_max);
5185 } else if (oai_settings.xai_model.includes('grok-2-vision')) {5212 } else if (oai_settings.xai_model.includes('grok-2-vision')) {
5186 $('#openai_max_context').attr('max', max_32k);5213 $('#openai_max_context').attr('max', max_32k);
5187 } else if (oai_settings.xai_model.includes('grok-vision')) {5214 } else if (oai_settings.xai_model.includes('grok-4-fast')) {
5188 $('#openai_max_context').attr('max', max_8k);5215 $('#openai_max_context').attr('max', max_2mil);
5189 } else if (oai_settings.xai_model.includes('grok-4')) {5216 } else if (oai_settings.xai_model.includes('grok-4')) {
5190 $('#openai_max_context').attr('max', max_256k);5217 $('#openai_max_context').attr('max', max_256k);
5218 } else if (oai_settings.xai_model.includes('grok-code')) {
5219 $('#openai_max_context').attr('max', max_256k);
5191 } else {5220 } else {
5221 // grok 2 and grok 3
5192 $('#openai_max_context').attr('max', max_128k);5222 $('#openai_max_context').attr('max', max_128k);
5193 }5223 }
51945224
@@ -5737,7 +5767,6 @@ export function isImageInliningSupported() {
5737 // xAI (Grok)5767 // xAI (Grok)
5738 'grok-4',5768 'grok-4',
5739 'grok-2-vision',5769 'grok-2-vision',
5740 'grok-vision',
5741 // Moonshot5770 // Moonshot
5742 'moonshot-v1-8k-vision-preview',5771 'moonshot-v1-8k-vision-preview',
5743 'moonshot-v1-32k-vision-preview',5772 'moonshot-v1-32k-vision-preview',
@@ -5770,6 +5799,7 @@ export function isImageInliningSupported() {
5770 case chat_completion_sources.COHERE:5799 case chat_completion_sources.COHERE:
5771 return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model));5800 return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model));
5772 case chat_completion_sources.XAI:5801 case chat_completion_sources.XAI:
5802 // TODO: xAI's /models endpoint doesn't return modality info
5773 return visionSupportedModels.some(model => oai_settings.xai_model.includes(model));5803 return visionSupportedModels.some(model => oai_settings.xai_model.includes(model));
5774 case chat_completion_sources.AIMLAPI:5804 case chat_completion_sources.AIMLAPI:
5775 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.aimlapi_model)?.features?.includes('openai/chat-completion.vision'));5805 return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.aimlapi_model)?.features?.includes('openai/chat-completion.vision'));
src/endpoints/backends/chat-completions.js+34 -1
@@ -1012,7 +1012,7 @@ async function sendXaiRequest(request, response) {
1012 bodyParams['stop'] = request.body.stop;1012 bodyParams['stop'] = request.body.stop;
1013 }1013 }
10141014
1015 if (request.body.reasoning_effort && ['grok-3-mini-beta', 'grok-3-mini-fast-beta'].includes(request.body.model)) {1015 if (request.body.reasoning_effort) {
1016 bodyParams['reasoning_effort'] = request.body.reasoning_effort === 'high' ? 'high' : 'low';1016 bodyParams['reasoning_effort'] = request.body.reasoning_effort === 'high' ? 'high' : 'low';
1017 }1017 }
10181018
@@ -2238,4 +2238,37 @@ multimodalModels.post('/mistral', async (req, res) => {
2238 }2238 }
2239});2239});
22402240
2241multimodalModels.post('/xai', async (req, res) => {
2242 try {
2243 const key = readSecret(req.user.directories, SECRET_KEYS.XAI);
2244
2245 if (!key) {
2246 return res.json([]);
2247 }
2248
2249 // xAI's /models endpoint doesn't return modality info, so we must use /language-models instead
2250 const response = await fetch('https://api.x.ai/v1/language-models', {
2251 headers: {
2252 'Authorization': `Bearer ${key}`,
2253 },
2254 });
2255
2256 if (!response.ok) {
2257 return res.json([]);
2258 }
2259
2260 /** @type {any} */
2261 const data = await response.json();
2262 const multimodalModels = data.models.filter(m => m.input_modalities?.includes('image')).map(m => m.id);
2263 if (!multimodalModels.includes('grok-4-0709')) {
2264 // The endpoint says it doesn't support images, but it does
2265 multimodalModels.push('grok-4-0709');
2266 }
2267 return res.json(multimodalModels);
2268 } catch (error) {
2269 console.error(error);
2270 return res.sendStatus(500);
2271 }
2272});
2273
2241router.use('/multimodal-models', multimodalModels);2274router.use('/multimodal-models', multimodalModels);