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 @@
36243624 <div data-for="api_key_xai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'.">
36253625 For privacy reasons, your API key will be hidden after you click 'Connect'.
36263626 </div>
3627+ <div>
36273628 <h4 data-i18n="xAI Model">xAI Model</h4>
36283629 <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>
36383631 </select>
36393632 </div>
3633+ </div>
36403634 <div id="aimlapi_form" data-source="aimlapi">
36413635 <h4>
36423636 <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 () {
550550 await processEndpoint('nanogpt', '/api/backends/chat-completions/multimodal-models/nanogpt');
551551 await processEndpoint('electronhub', '/api/backends/chat-completions/multimodal-models/electronhub');
552552 await processEndpoint('mistral', '/api/backends/chat-completions/multimodal-models/mistral');
553+ await processEndpoint('xai', '/api/backends/chat-completions/multimodal-models/xai');
553554 }
554555
555556 await addSettings();
public/scripts/extensions/caption/settings.html+1 -4
@@ -47,7 +47,7 @@
4747 </div>
4848 </label>
4949 <select id="caption_multimodal_model" class="flex1 text_pole">
5050 <!-- AI/ML API, OpenRouter, Pollinations, NanoGPT, Mistral, xAI are added externally by JavaScript -->
5151 <option data-type="cohere" value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option>
5252 <option data-type="cohere" value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option>
5353 <option data-type="cohere" value="command-a-vision-07-2025">command-a-vision-07-2025</option>
@@ -168,9 +168,6 @@
168168 <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option>
169169 <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option>
170170 <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>
174171 </select>
175172 </div>
176173 <div data-type="ollama">
public/scripts/openai.js+38 -8
@@ -2028,6 +2028,24 @@ function saveModelList(data) {
20282028 .append(new Option(modelId || 'None', modelId || '', true, true))
20292029 .trigger('change');
20302030 }
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+ }
20312049}
20322050
20332051function appendOpenRouterOptions(model_list, groupModels = false, sort = false) {
@@ -2452,19 +2470,24 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
24522470 }
24532471
24542472 if (isXAI) {
2455- if (generate_data.model.includes('grok-4')) {
2473+ const model = generate_data.model;
2474+ if (model.includes('grok-3-mini')) {
24562475 delete generate_data.presence_penalty;
24572476 delete generate_data.frequency_penalty;
24582477 delete generate_data.stop;
2478+ } else {
2479+ // As of 2025/09/21, only grok-3-mini accepts reasoning_effort
24592480 delete generate_data.reasoning_effort;
24602481 }
2461- if (generate_data.model.includes('grok-3-mini')) {
2482+
2483+ if (model.includes('grok-4') || model.includes('grok-code')) {
24622484 delete generate_data.presence_penalty;
24632485 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;
24642490 }
2465- if (generate_data.model.includes('grok-vision')) {
2466- delete generate_data.tools;
2467- delete generate_data.tool_choice;
24682491 }
24692492 }
24702493
@@ -4916,6 +4939,10 @@ async function onModelChange() {
49164939 }
49174940
49184941 if ($(this).is('#model_xai_select')) {
4942+ if (!value) {
4943+ console.debug('Null XAI model selected. Ignoring.');
4944+ return;
4945+ }
49194946 console.log('XAI model changed to', value);
49204947 oai_settings.xai_model = value;
49214948 }
@@ -5184,11 +5211,14 @@ async function onModelChange() {
51845211 $('#openai_max_context').attr('max', unlocked_max);
51855212 } else if (oai_settings.xai_model.includes('grok-2-vision')) {
51865213 $('#openai_max_context').attr('max', max_32k);
51875214 } else if (oai_settings.xai_model.includes('grok-vision4-fast')) {
51885215 $('#openai_max_context').attr('max', max_8kmax_2mil);
51895216 } else if (oai_settings.xai_model.includes('grok-4')) {
51905217 $('#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);
51915220 } else {
5221+ // grok 2 and grok 3
51925222 $('#openai_max_context').attr('max', max_128k);
51935223 }
51945224
@@ -5737,7 +5767,6 @@ export function isImageInliningSupported() {
57375767 // xAI (Grok)
57385768 'grok-4',
57395769 'grok-2-vision',
5740- 'grok-vision',
57415770 // Moonshot
57425771 'moonshot-v1-8k-vision-preview',
57435772 'moonshot-v1-32k-vision-preview',
@@ -5770,6 +5799,7 @@ export function isImageInliningSupported() {
57705799 case chat_completion_sources.COHERE:
57715800 return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model));
57725801 case chat_completion_sources.XAI:
5802+ // TODO: xAI's /models endpoint doesn't return modality info
57735803 return visionSupportedModels.some(model => oai_settings.xai_model.includes(model));
57745804 case chat_completion_sources.AIMLAPI:
57755805 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) {
10121012 bodyParams['stop'] = request.body.stop;
10131013 }
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) {
10161016 bodyParams['reasoning_effort'] = request.body.reasoning_effort === 'high' ? 'high' : 'low';
10171017 }
10181018
@@ -2238,4 +2238,37 @@ multimodalModels.post('/mistral', async (req, res) => {
22382238 }
22392239});
22402240
2241+multimodalModels.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+
22412274router.use('/multimodal-models', multimodalModels);