feat: add gemma 4 for AI studio (#5493) * feat: add gemma 4 for AI studio * fix: update max context return value for gemma-3n-e4b-it model * refactor: iterate array of [regex, number] * gemma4: enable tool calling and sysprompt Co-authored-by: Copilot <copilot@github.com> --------- Co-authored-by: Copilot <copilot@github.com>

09d72828cb22bb7fe5e189eff322b5632babc513

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

Signed
4 files changed, +81 -26Showing whitespace changes
public/index.html+2 -0
@@ -3373,6 +3373,8 @@
3373 <option value="gemini-2.0-flash-lite">gemini-2.0-flash-lite</option>3373 <option value="gemini-2.0-flash-lite">gemini-2.0-flash-lite</option>
3374 </optgroup>3374 </optgroup>
3375 <optgroup label="Gemma">3375 <optgroup label="Gemma">
3376 <option value="gemma-4-31b-it">gemma-4-31b-it</option>
3377 <option value="gemma-4-26b-a4b-it">gemma-4-26b-a4b-it</option>
3376 <option value="gemma-3n-e4b-it">gemma-3n-e4b-it</option>3378 <option value="gemma-3n-e4b-it">gemma-3n-e4b-it</option>
3377 <option value="gemma-3n-e2b-it">gemma-3n-e2b-it</option>3379 <option value="gemma-3n-e2b-it">gemma-3n-e2b-it</option>
3378 <option value="gemma-3-27b-it">gemma-3-27b-it</option>3380 <option value="gemma-3-27b-it">gemma-3-27b-it</option>
public/scripts/extensions/caption/settings.html+5 -0
@@ -153,6 +153,11 @@
153 <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option>153 <option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option>
154 <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option>154 <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option>
155 <option data-type="google" value="gemini-robotics-er-1.5-preview">gemini-robotics-er-1.5-preview</option>155 <option data-type="google" value="gemini-robotics-er-1.5-preview">gemini-robotics-er-1.5-preview</option>
156 <option data-type="google" value="gemma-4-31b-it">gemma-4-31b-it</option>
157 <option data-type="google" value="gemma-4-26b-a4b-it">gemma-4-26b-a4b-it</option>
158 <option data-type="google" value="gemma-3-27b-it">gemma-3-27b-it</option>
159 <option data-type="google" value="gemma-3-12b-it">gemma-3-12b-it</option>
160 <option data-type="google" value="gemma-3-4b-it">gemma-3-4b-it</option>
156 <option data-type="vertexai" value="gemini-3.1-pro-preview">gemini-3.1-pro-preview</option>161 <option data-type="vertexai" value="gemini-3.1-pro-preview">gemini-3.1-pro-preview</option>
157 <option data-type="vertexai" value="gemini-3.1-flash-lite-preview">gemini-3.1-flash-lite-preview</option>162 <option data-type="vertexai" value="gemini-3.1-flash-lite-preview">gemini-3.1-flash-lite-preview</option>
158 <option data-type="vertexai" value="gemini-3.1-flash-image-preview">gemini-3.1-flash-image-preview</option>163 <option data-type="vertexai" value="gemini-3.1-flash-image-preview">gemini-3.1-flash-image-preview</option>
public/scripts/openai.js+69 -22
@@ -4934,6 +4934,65 @@ function getMaxContextOpenAI(value) {
4934}4934}
49354935
4936/**4936/**
4937 * Get the maximum context size for Gemini models based on model identifier and optional model list.
4938 * @param {string} model Model identifier
4939 * @param {boolean} isUnlocked Whether context limits are unlocked
4940 * @returns {number} Maximum context size in tokens
4941 */
4942function getGeminiMaxContext(model, isUnlocked) {
4943 if (isUnlocked) {
4944 return unlocked_max;
4945 }
4946
4947 if (Array.isArray(model_list) && model_list.length > 0) {
4948 const contextLength = model_list.find((record) => record.id === model)?.inputTokenLimit;
4949 if (Number.isFinite(contextLength) && contextLength > 0) {
4950 return contextLength;
4951 }
4952 }
4953
4954 /** @type {[RegExp, number][]} */
4955 const contextMap = [
4956 [/gemini-2\.5-flash-image/, max_32k],
4957 [/gemini-3-pro-image/, max_64k],
4958 [/gemini-(?:3[.\d]*|2\.(?:5|0))-(pro|flash)/, max_1mil],
4959 [/(gemini-exp|learnlm-2\.0-flash|gemini-robotics)/, max_1mil],
4960 [/gemma-3-27b-it/, max_128k],
4961 [/gemma-3n-e4b-it/, max_8k],
4962 [/gemma-3/, max_32k],
4963 [/gemma-4/, max_256k],
4964 ];
4965
4966 for (const [regex, max] of contextMap) {
4967 if (regex.test(model)) {
4968 return max;
4969 }
4970 }
4971
4972 return max_128k;
4973}
4974
4975/**
4976 * Get the maximum temperature for Gemini models based on model identifier and optional model list.
4977 * @param {string} model Model identifier
4978 * @returns {number} Maximum temperature for Gemini models
4979 */
4980function getGeminiMaxTemp(model) {
4981 if (Array.isArray(model_list) && model_list.length > 0) {
4982 const temp = model_list.find((record) => record.id === model)?.temperature;
4983 if (Number.isFinite(temp) && temp > 0) {
4984 return temp;
4985 }
4986 }
4987
4988 if (/(vision|ultra|gemma)/.test(model)) {
4989 return 1.0;
4990 }
4991
4992 return 2.0;
4993}
4994
4995/**
4937 * Get the maximum context size for the Mistral model4996 * Get the maximum context size for the Mistral model
4938 * @param {string} model Model identifier4997 * @param {string} model Model identifier
4939 * @param {boolean} isUnlocked Whether context limits are unlocked4998 * @param {boolean} isUnlocked Whether context limits are unlocked
@@ -5443,28 +5502,11 @@ async function onModelChange() {
5443 }5502 }
54445503
5445 if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) {5504 if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) {
5446 if (oai_settings.max_context_unlocked) {5505 const contextSize = getGeminiMaxContext(value, oai_settings.max_context_unlocked);
5447 $('#openai_max_context').attr('max', max_2mil);5506 const maxTemp = getGeminiMaxTemp(value);
5448 } else if (value.includes('gemini-2.5-flash-image')) {5507 $('#openai_max_context').attr('max', contextSize);
5449 $('#openai_max_context').attr('max', max_32k);5508 oai_settings.temp_openai = Math.min(maxTemp, oai_settings.temp_openai);
5450 } else if (value.includes('gemini-3-pro-image')) {5509 $('#temp_openai').attr('max', maxTemp).val(oai_settings.temp_openai).trigger('input');
5451 $('#openai_max_context').attr('max', max_64k);
5452 } else if (/gemini-3[.\d]*-(pro|flash)/.test(value) || /gemini-2.5-(pro|flash)/.test(value) || /gemini-2.0-(pro|flash)/.test(value)) {
5453 $('#openai_max_context').attr('max', max_1mil);
5454 } else if (value.includes('gemini-exp') || value.includes('learnlm-2.0-flash') || value.includes('gemini-robotics')) {
5455 $('#openai_max_context').attr('max', max_1mil);
5456 } else if (value.includes('gemma-3-27b-it')) {
5457 $('#openai_max_context').attr('max', max_128k);
5458 } else if (value.includes('gemma-3n-e4b-it')) {
5459 $('#openai_max_context').attr('max', max_8k);
5460 } else if (value.includes('gemma-3')) {
5461 $('#openai_max_context').attr('max', max_32k);
5462 } else {
5463 $('#openai_max_context').attr('max', max_32k);
5464 }
5465 let makersuite_max_temp = (value.includes('vision') || value.includes('ultra') || value.includes('gemma')) ? 1.0 : 2.0;
5466 oai_settings.temp_openai = Math.min(makersuite_max_temp, oai_settings.temp_openai);
5467 $('#temp_openai').attr('max', makersuite_max_temp).val(oai_settings.temp_openai).trigger('input');
5468 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);5510 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
5469 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');5511 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
5470 }5512 }
@@ -6040,6 +6082,10 @@ export function isImageInliningSupported() {
6040 'gemini-exp-1206',6082 'gemini-exp-1206',
6041 'learnlm',6083 'learnlm',
6042 'gemini-robotics',6084 'gemini-robotics',
6085 'gemma-3-27b',
6086 'gemma-3-12b',
6087 'gemma-3-4b',
6088 'gemma-4',
6043 // MistralAI6089 // MistralAI
6044 'mistral-small-2503',6090 'mistral-small-2503',
6045 'mistral-small-2506',6091 'mistral-small-2506',
@@ -6144,6 +6190,7 @@ export function isVideoInliningSupported() {
6144 'gemini-2.5',6190 'gemini-2.5',
6145 'gemini-exp-1206',6191 'gemini-exp-1206',
6146 'gemini-3',6192 'gemini-3',
6193 'gemma-4',
6147 // Z.AI (GLM)6194 // Z.AI (GLM)
6148 'glm-4.5v',6195 'glm-4.5v',
6149 'glm-4.6v',6196 'glm-4.6v',
src/endpoints/backends/chat-completions.js+5 -4
@@ -459,7 +459,7 @@ async function sendMakerSuiteRequest(request, response) {
459 const includeReasoning = Boolean(request.body.include_reasoning);459 const includeReasoning = Boolean(request.body.include_reasoning);
460 const aspectRatio = String(request.body.request_image_aspect_ratio);460 const aspectRatio = String(request.body.request_image_aspect_ratio);
461 const imageSize = String(request.body.request_image_resolution);461 const imageSize = String(request.body.request_image_resolution);
462 const isGemma = model.includes('gemma');462 const isGemma3 = /gemma-3/.test(model);
463 const isLearnLM = model.includes('learnlm');463 const isLearnLM = model.includes('learnlm');
464464
465 const responseMimeType = request.body.responseMimeType ?? (request.body.json_schema ? 'application/json' : undefined);465 const responseMimeType = request.body.responseMimeType ?? (request.body.json_schema ? 'application/json' : undefined);
@@ -519,13 +519,13 @@ async function sendMakerSuiteRequest(request, response) {
519 }519 }
520 }520 }
521521
522 const useSystemPrompt = !enableImageModality && !isGemma && request.body.use_sysprompt;522 const useSystemPrompt = !enableImageModality && !isGemma3 && request.body.use_sysprompt;
523523
524 const tools = [];524 const tools = [];
525 const prompt = convertGooglePrompt(request.body.messages, model, useSystemPrompt, getPromptNames(request));525 const prompt = convertGooglePrompt(request.body.messages, model, useSystemPrompt, getPromptNames(request));
526 const safetySettings = [...GEMINI_SAFETY, ...(useVertexAi ? VERTEX_SAFETY : [])];526 const safetySettings = [...GEMINI_SAFETY, ...(useVertexAi ? VERTEX_SAFETY : [])];
527527
528 if (Array.isArray(request.body.tools) && request.body.tools.length > 0 && !enableImageModality && !isGemma) {528 if (Array.isArray(request.body.tools) && request.body.tools.length > 0 && !enableImageModality && !isGemma3) {
529 const functionDeclarations = [];529 const functionDeclarations = [];
530 const customTools = [];530 const customTools = [];
531 for (const tool of request.body.tools) {531 for (const tool of request.body.tools) {
@@ -550,7 +550,7 @@ async function sendMakerSuiteRequest(request, response) {
550 }550 }
551 }551 }
552552
553 if (enableWebSearch && !enableImageModality && !isGemma && !isLearnLM && !noSearchModels.includes(model)) {553 if (enableWebSearch && !enableImageModality && !isGemma3 && !isLearnLM && !noSearchModels.includes(model)) {
554 // Tool use with function calling is unsupported554 // Tool use with function calling is unsupported
555 if (!tools.some(t => t.function_declarations)) {555 if (!tools.some(t => t.function_declarations)) {
556 tools.push({ google_search: {} });556 tools.push({ google_search: {} });
@@ -1832,6 +1832,7 @@ router.post('/status', async function (request, statusResponse) {
1832 const models = data.models1832 const models = data.models
1833 ?.filter(model => model.supportedGenerationMethods?.includes('generateContent'))1833 ?.filter(model => model.supportedGenerationMethods?.includes('generateContent'))
1834 ?.map(model => ({1834 ?.map(model => ({
1835 ...model,
1835 id: model.name.replace('models/', ''),1836 id: model.name.replace('models/', ''),
1836 })) || [];1837 })) || [];
18371838