Add Vertex AI express mode support (#3977) * Add Vertex AI express mode support Split Google AI Studio and Vertex AI * Add support for Vertex AI, including updating default models and related settings, modifying frontend HTML to include Vertex AI options, and adjusting request processing logic in the backend API. * Log API name in the console * Merge sysprompt toggles back * Use Gemma tokenizers for Vertex and LearnLM * AI Studio parity updates * Add link to express mode doc. Also technically it's not a form * Split title * Use array includes * Add support for Google Vertex AI in image captioning feature * Specify caption API name, add to compression list --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

157315cd6890c3738b95f3d164ffc477e3469425

NijikaMyWaifu <139055015+NijikaMyWaifu@users.noreply.github.com>

Signed
18 files changed, +191 -40Showing whitespace changes
default/content/presets/openai/Default.json+1 -0
@@ -15,6 +15,7 @@
15 "custom_exclude_body": "",15 "custom_exclude_body": "",
16 "custom_include_headers": "",16 "custom_include_headers": "",
17 "google_model": "gemini-pro",17 "google_model": "gemini-pro",
18 "vertexai_model": "gemini-2.0-flash-001",
18 "temperature": 1,19 "temperature": 1,
19 "frequency_penalty": 0,20 "frequency_penalty": 0,
20 "presence_penalty": 0,21 "presence_penalty": 0,
public/index.html+46 -13
@@ -691,7 +691,7 @@
691 </span>691 </span>
692 </div>692 </div>
693 </div>693 </div>
694 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai">694 <div class="range-block" data-source="openai,claude,windowai,openrouter,ai21,scale,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai">
695 <div class="range-block-title" data-i18n="Temperature">695 <div class="range-block-title" data-i18n="Temperature">
696 Temperature696 Temperature
697 </div>697 </div>
@@ -730,7 +730,7 @@
730 </div>730 </div>
731 </div>731 </div>
732 </div>732 </div>
733 <div class="range-block" data-source="claude,openrouter,makersuite,cohere,perplexity">733 <div class="range-block" data-source="claude,openrouter,makersuite,vertexai,cohere,perplexity">
734 <div class="range-block-title" data-i18n="Top K">734 <div class="range-block-title" data-i18n="Top K">
735 Top K735 Top K
736 </div>736 </div>
@@ -743,7 +743,7 @@
743 </div>743 </div>
744 </div>744 </div>
745 </div>745 </div>
746 <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai">746 <div class="range-block" data-source="openai,claude,openrouter,ai21,scale,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai">
747 <div class="range-block-title" data-i18n="Top P">747 <div class="range-block-title" data-i18n="Top P">
748 Top P748 Top P
749 </div>749 </div>
@@ -1969,7 +1969,7 @@
1969 </span>1969 </span>
1970 </div>1970 </div>
1971 </div>1971 </div>
1972 <div class="range-block" data-source="makersuite,openrouter,claude">1972 <div class="range-block" data-source="makersuite,vertexai,openrouter,claude">
1973 <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand">1973 <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand">
1974 <input id="openai_enable_web_search" type="checkbox" />1974 <input id="openai_enable_web_search" type="checkbox" />
1975 <span data-i18n="Enable web search">Enable web search</span>1975 <span data-i18n="Enable web search">Enable web search</span>
@@ -1983,7 +1983,7 @@
1983 </b>1983 </b>
1984 </div>1984 </div>
1985 </div>1985 </div>
1986 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,ai21,xai,pollinations">1986 <div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,vertexai,ai21,xai,pollinations">
1987 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">1987 <label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
1988 <input id="openai_function_calling" type="checkbox" />1988 <input id="openai_function_calling" type="checkbox" />
1989 <span data-i18n="Enable function calling">Enable function calling</span>1989 <span data-i18n="Enable function calling">Enable function calling</span>
@@ -1993,7 +1993,7 @@
1993 <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>1993 <span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>
1994 </div>1994 </div>
1995 </div>1995 </div>
1996 <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,claude,custom,01ai,xai,pollinations">1996 <div class="range-block" data-source="openai,openrouter,mistralai,makersuite,vertexai,claude,custom,01ai,xai,pollinations">
1997 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">1997 <label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
1998 <input id="openai_image_inlining" type="checkbox" />1998 <input id="openai_image_inlining" type="checkbox" />
1999 <span data-i18n="Send inline images">Send inline images</span>1999 <span data-i18n="Send inline images">Send inline images</span>
@@ -2018,7 +2018,7 @@
2018 </div>2018 </div>
2019 </div>2019 </div>
2020 </div>2020 </div>
2021 <div class="range-block" data-source="makersuite">2021 <div class="range-block" data-source="makersuite,vertexai">
2022 <label for="openai_request_images" class="checkbox_label widthFreeExpand">2022 <label for="openai_request_images" class="checkbox_label widthFreeExpand">
2023 <input id="openai_request_images" type="checkbox" />2023 <input id="openai_request_images" type="checkbox" />
2024 <span>2024 <span>
@@ -2030,12 +2030,12 @@
2030 <span data-i18n="Allows the model to return image attachments.">2030 <span data-i18n="Allows the model to return image attachments.">
2031 Allows the model to return image attachments.2031 Allows the model to return image attachments.
2032 </span>2032 </span>
2033 <em data-source="makersuite" data-i18n="Request inline images_desc_2">2033 <em data-source="makersuite,vertexai" data-i18n="Request inline images_desc_2">
2034 Incompatible with the following features: function calling, web search, system prompt.2034 Incompatible with the following features: function calling, web search, system prompt.
2035 </em>2035 </em>
2036 </div>2036 </div>
2037 </div>2037 </div>
2038 <div class="range-block" data-source="makersuite">2038 <div class="range-block" data-source="makersuite,vertexai">
2039 <label for="use_makersuite_sysprompt" class="checkbox_label widthFreeExpand">2039 <label for="use_makersuite_sysprompt" class="checkbox_label widthFreeExpand">
2040 <input id="use_makersuite_sysprompt" type="checkbox" />2040 <input id="use_makersuite_sysprompt" type="checkbox" />
2041 <span>2041 <span>
@@ -2060,12 +2060,12 @@
2060 </span>2060 </span>
2061 </div>2061 </div>
2062 </div>2062 </div>
2063 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,openrouter,pollinations">2063 <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,openrouter,pollinations">
2064 <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models.">2064 <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models.">
2065 <label for="openai_reasoning_effort">2065 <label for="openai_reasoning_effort">
2066 <span data-i18n="Reasoning Effort">Reasoning Effort</span>2066 <span data-i18n="Reasoning Effort">Reasoning Effort</span>
2067 <i data-source="openai,custom,xai,openrouter" class="opacity50p fa-solid fa-circle-info" title="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level." data-i18n="[title]OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level."></i>2067 <i data-source="openai,custom,xai,openrouter" class="opacity50p fa-solid fa-circle-info" title="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level." data-i18n="[title]OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level."></i>
2068 <i data-source="claude,makersuite" class="opacity50p fa-solid fa-circle-info" title="Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent." data-i18n="[title]Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent."></i>2068 <i data-source="claude,makersuite,vertexai" class="opacity50p fa-solid fa-circle-info" title="Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent." data-i18n="[title]Allocates a portion of the response length for thinking (low: 10%, medium: 25%, high: 50%). Other options are model-dependent."></i>
2069 </label>2069 </label>
2070 <select id="openai_reasoning_effort">2070 <select id="openai_reasoning_effort">
2071 <option data-i18n="openai_reasoning_effort_auto" value="auto">Auto</option>2071 <option data-i18n="openai_reasoning_effort_auto" value="auto">Auto</option>
@@ -2769,6 +2769,7 @@
2769 <option value="deepseek">DeepSeek</option>2769 <option value="deepseek">DeepSeek</option>
2770 <option value="groq">Groq</option>2770 <option value="groq">Groq</option>
2771 <option value="makersuite">Google AI Studio</option>2771 <option value="makersuite">Google AI Studio</option>
2772 <option value="vertexai">Google Vertex AI (Express mode)</option>
2772 <option value="mistralai">MistralAI</option>2773 <option value="mistralai">MistralAI</option>
2773 <option value="nanogpt">NanoGPT</option>2774 <option value="nanogpt">NanoGPT</option>
2774 <option value="openrouter">OpenRouter</option>2775 <option value="openrouter">OpenRouter</option>
@@ -2779,7 +2780,7 @@
2779 <option value="xai">xAI (Grok)</option>2780 <option value="xai">xAI (Grok)</option>
2780 </optgroup>2781 </optgroup>
2781 </select>2782 </select>
2782 <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek,xai">2783 <div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,vertexai,deepseek,xai">
2783 <div class="inline-drawer-toggle inline-drawer-header">2784 <div class="inline-drawer-toggle inline-drawer-header">
2784 <b data-i18n="Reverse Proxy">Reverse Proxy</b>2785 <b data-i18n="Reverse Proxy">Reverse Proxy</b>
2785 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>2786 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
@@ -2843,7 +2844,7 @@
2843 </div>2844 </div>
2844 </div>2845 </div>
2845 </div>2846 </div>
2846 <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,deepseek,xai">2847 <div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,vertexai,deepseek,xai">
2847 <div class="reverse_proxy_warning">2848 <div class="reverse_proxy_warning">
2848 <b>2849 <b>
2849 <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy.">2850 <div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy.">
@@ -3209,6 +3210,38 @@
3209 </select>3210 </select>
3210 </div>3211 </div>
3211 </form>3212 </form>
3213 <div id="vertexai_form" data-source="vertexai">
3214 <h4>
3215 <span data-i18n="Google Vertex AI API Key">
3216 Google Vertex AI API Key
3217 </span>
3218 <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/start/express-mode/overview" data-i18n="(Express mode keys only)" target="_blank" rel="noopener noreferrer">
3219 (Express mode keys only)
3220 </a>
3221 </h4>
3222 <div class="flex-container">
3223 <input id="api_key_vertexai" name="api_key_vertexai" class="text_pole flex1" value="" type="text" autocomplete="off">
3224 <div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_vertexai"></div>
3225 </div>
3226 <div data-for="api_key_vertexai" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
3227 For privacy reasons, your API key will be hidden after you reload the page.
3228 </div>
3229 <div>
3230 <h4 data-i18n="Google Model">Google Model</h4>
3231 <select id="model_vertexai_select">
3232 <optgroup label="Gemini 2.5">
3233 <option value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>
3234 <option value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>
3235 <option value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>
3236 <option value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>
3237 </optgroup>
3238 <optgroup label="Gemini 2.0">
3239 <option value="gemini-2.0-flash-001">gemini-2.0-flash-001</option>
3240 <option value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option>
3241 </optgroup>
3242 </select>
3243 </div>
3244 </div>
3212 <form id="mistralai_form" data-source="mistralai" action="javascript:void(null);" method="post" enctype="multipart/form-data">3245 <form id="mistralai_form" data-source="mistralai" action="javascript:void(null);" method="post" enctype="multipart/form-data">
3213 <h4 data-i18n="MistralAI API Key">MistralAI API Key</h4>3246 <h4 data-i18n="MistralAI API Key">MistralAI API Key</h4>
3214 <div class="flex-container">3247 <div class="flex-container">
public/script.js+1 -0
@@ -5941,6 +5941,7 @@ function extractImageFromData(data, { mainApi = null, chatCompletionSource = nul
5941 switch (mainApi ?? main_api) {5941 switch (mainApi ?? main_api) {
5942 case 'openai': {5942 case 'openai': {
5943 switch (chatCompletionSource ?? oai_settings.chat_completion_source) {5943 switch (chatCompletionSource ?? oai_settings.chat_completion_source) {
5944 case chat_completion_sources.VERTEXAI:
5944 case chat_completion_sources.MAKERSUITE: {5945 case chat_completion_sources.MAKERSUITE: {
5945 const inlineData = data?.responseContent?.parts?.find(x => x.inlineData)?.inlineData;5946 const inlineData = data?.responseContent?.parts?.find(x => x.inlineData)?.inlineData;
5946 if (inlineData) {5947 if (inlineData) {
public/scripts/RossAscends-mods.js+1 -0
@@ -402,6 +402,7 @@ function RA_autoconnect(PrevApi) {
402 || (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER)402 || (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER)
403 || (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21)403 || (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21)
404 || (secret_state[SECRET_KEYS.MAKERSUITE] && oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE)404 || (secret_state[SECRET_KEYS.MAKERSUITE] && oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE)
405 || (secret_state[SECRET_KEYS.VERTEXAI] && oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI)
405 || (secret_state[SECRET_KEYS.MISTRALAI] && oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI)406 || (secret_state[SECRET_KEYS.MISTRALAI] && oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI)
406 || (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)407 || (secret_state[SECRET_KEYS.COHERE] && oai_settings.chat_completion_source == chat_completion_sources.COHERE)
407 || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)408 || (secret_state[SECRET_KEYS.PERPLEXITY] && oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY)
public/scripts/extensions/caption/index.js+1 -0
@@ -414,6 +414,7 @@ jQuery(async function () {
414 'openai': SECRET_KEYS.OPENAI,414 'openai': SECRET_KEYS.OPENAI,
415 'mistral': SECRET_KEYS.MISTRALAI,415 'mistral': SECRET_KEYS.MISTRALAI,
416 'google': SECRET_KEYS.MAKERSUITE,416 'google': SECRET_KEYS.MAKERSUITE,
417 'vertexai': SECRET_KEYS.VERTEXAI,
417 'anthropic': SECRET_KEYS.CLAUDE,418 'anthropic': SECRET_KEYS.CLAUDE,
418 };419 };
419420
public/scripts/extensions/caption/settings.html+8 -1
@@ -22,6 +22,7 @@
22 <option value="cohere">Cohere</option>22 <option value="cohere">Cohere</option>
23 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>23 <option value="custom" data-i18n="Custom (OpenAI-compatible)">Custom (OpenAI-compatible)</option>
24 <option value="google">Google AI Studio</option>24 <option value="google">Google AI Studio</option>
25 <option value="vertexai">Google Vertex AI</option>
25 <option value="groq">Groq</option>26 <option value="groq">Groq</option>
26 <option value="koboldcpp">KoboldCpp</option>27 <option value="koboldcpp">KoboldCpp</option>
27 <option value="llamacpp">llama.cpp</option>28 <option value="llamacpp">llama.cpp</option>
@@ -111,6 +112,12 @@
111 <option data-type="google" value="gemini-1.5-flash-8b-exp-0827">gemini-1.5-flash-8b-exp-0827</option>112 <option data-type="google" value="gemini-1.5-flash-8b-exp-0827">gemini-1.5-flash-8b-exp-0827</option>
112 <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option>113 <option data-type="google" value="learnlm-2.0-flash-experimental">learnlm-2.0-flash-experimental</option>
113 <option data-type="google" value="learnlm-1.5-pro-experimental">learnlm-1.5-pro-experimental</option>114 <option data-type="google" value="learnlm-1.5-pro-experimental">learnlm-1.5-pro-experimental</option>
115 <option data-type="vertexai" value="gemini-2.5-pro-preview-05-06">gemini-2.5-pro-preview-05-06</option>
116 <option data-type="vertexai" value="gemini-2.5-pro-preview-03-25">gemini-2.5-pro-preview-03-25</option>
117 <option data-type="vertexai" value="gemini-2.5-flash-preview-05-20">gemini-2.5-flash-preview-05-20</option>
118 <option data-type="vertexai" value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>
119 <option data-type="vertexai" value="gemini-2.0-flash-001">gemini-2.0-flash-001</option>
120 <option data-type="vertexai" value="gemini-2.0-flash-lite-001">gemini-2.0-flash-lite-001</option>
114 <option data-type="groq" value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option>121 <option data-type="groq" value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option>
115 <option data-type="groq" value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option>122 <option data-type="groq" value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option>
116 <option data-type="groq" value="llava-v1.5-7b-4096-preview">llava-v1.5-7b-4096-preview</option>123 <option data-type="groq" value="llava-v1.5-7b-4096-preview">llava-v1.5-7b-4096-preview</option>
@@ -170,7 +177,7 @@
170 <div data-type="ollama">177 <div data-type="ollama">
171 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>.178 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>.
172 </div>179 </div>
173 <label data-type="openai,anthropic,google,mistral" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid.">180 <label data-type="openai,anthropic,google,vertexai,mistral" class="checkbox_label flexBasis100p" for="caption_allow_reverse_proxy" title="Allow using reverse proxy if defined and valid.">
174 <input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">181 <input id="caption_allow_reverse_proxy" type="checkbox" class="checkbox">
175 <span data-i18n="Allow reverse proxy">Allow reverse proxy</span>182 <span data-i18n="Allow reverse proxy">Allow reverse proxy</span>
176 </label>183 </label>
public/scripts/extensions/shared.js+8 -2
@@ -15,7 +15,7 @@ import { createThumbnail, isValidUrl } from '../utils.js';
15 */15 */
16export async function getMultimodalCaption(base64Img, prompt) {16export async function getMultimodalCaption(base64Img, prompt) {
17 const useReverseProxy =17 const useReverseProxy =
18 (['openai', 'anthropic', 'google', 'mistral'].includes(extension_settings.caption.multimodal_api))18 (['openai', 'anthropic', 'google', 'mistral', 'vertexai'].includes(extension_settings.caption.multimodal_api))
19 && extension_settings.caption.allow_reverse_proxy19 && extension_settings.caption.allow_reverse_proxy
20 && oai_settings.reverse_proxy20 && oai_settings.reverse_proxy
21 && isValidUrl(oai_settings.reverse_proxy);21 && isValidUrl(oai_settings.reverse_proxy);
@@ -38,7 +38,8 @@ export async function getMultimodalCaption(base64Img, prompt) {
38 const isVllm = extension_settings.caption.multimodal_api === 'vllm';38 const isVllm = extension_settings.caption.multimodal_api === 'vllm';
39 const base64Bytes = base64Img.length * 0.75;39 const base64Bytes = base64Img.length * 0.75;
40 const compressionLimit = 2 * 1024 * 1024;40 const compressionLimit = 2 * 1024 * 1024;
41 if ((['google', 'openrouter', 'mistral', 'groq'].includes(extension_settings.caption.multimodal_api) && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) {41 const thumbnailNeeded = ['google', 'openrouter', 'mistral', 'groq', 'vertexai'].includes(extension_settings.caption.multimodal_api);
42 if ((thumbnailNeeded && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) {
42 const maxSide = 1024;43 const maxSide = 1024;
43 base64Img = await createThumbnail(base64Img, maxSide, maxSide, 'image/jpeg');44 base64Img = await createThumbnail(base64Img, maxSide, maxSide, 'image/jpeg');
44 }45 }
@@ -94,6 +95,7 @@ export async function getMultimodalCaption(base64Img, prompt) {
94 function getEndpointUrl() {95 function getEndpointUrl() {
95 switch (extension_settings.caption.multimodal_api) {96 switch (extension_settings.caption.multimodal_api) {
96 case 'google':97 case 'google':
98 case 'vertexai':
97 return '/api/google/caption-image';99 return '/api/google/caption-image';
98 case 'anthropic':100 case 'anthropic':
99 return '/api/anthropic/caption-image';101 return '/api/anthropic/caption-image';
@@ -143,6 +145,10 @@ function throwIfInvalidModel(useReverseProxy) {
143 throw new Error('Google AI Studio API key is not set.');145 throw new Error('Google AI Studio API key is not set.');
144 }146 }
145147
148 if (extension_settings.caption.multimodal_api === 'vertexai' && !secret_state[SECRET_KEYS.VERTEXAI] && !useReverseProxy) {
149 throw new Error('Google Vertex AI API key is not set.');
150 }
151
146 if (extension_settings.caption.multimodal_api === 'mistral' && !secret_state[SECRET_KEYS.MISTRALAI] && !useReverseProxy) {152 if (extension_settings.caption.multimodal_api === 'mistral' && !secret_state[SECRET_KEYS.MISTRALAI] && !useReverseProxy) {
147 throw new Error('Mistral AI API key is not set.');153 throw new Error('Mistral AI API key is not set.');
148 }154 }
public/scripts/openai.js+58 -7
@@ -176,6 +176,7 @@ export const chat_completion_sources = {
176 OPENROUTER: 'openrouter',176 OPENROUTER: 'openrouter',
177 AI21: 'ai21',177 AI21: 'ai21',
178 MAKERSUITE: 'makersuite',178 MAKERSUITE: 'makersuite',
179 VERTEXAI: 'vertexai',
179 MISTRALAI: 'mistralai',180 MISTRALAI: 'mistralai',
180 CUSTOM: 'custom',181 CUSTOM: 'custom',
181 COHERE: 'cohere',182 COHERE: 'cohere',
@@ -277,6 +278,7 @@ export const settingsToUpdate = {
277 custom_include_headers: ['#custom_include_headers', 'custom_include_headers', false, true],278 custom_include_headers: ['#custom_include_headers', 'custom_include_headers', false, true],
278 custom_prompt_post_processing: ['#custom_prompt_post_processing', 'custom_prompt_post_processing', false, true],279 custom_prompt_post_processing: ['#custom_prompt_post_processing', 'custom_prompt_post_processing', false, true],
279 google_model: ['#model_google_select', 'google_model', false, true],280 google_model: ['#model_google_select', 'google_model', false, true],
281 vertexai_model: ['#model_vertexai_select', 'vertexai_model', false, true],
280 openai_max_context: ['#openai_max_context', 'openai_max_context', false, false],282 openai_max_context: ['#openai_max_context', 'openai_max_context', false, false],
281 openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false],283 openai_max_tokens: ['#openai_max_tokens', 'openai_max_tokens', false, false],
282 wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true, false],284 wrap_in_quotes: ['#wrap_in_quotes', 'wrap_in_quotes', true, false],
@@ -350,6 +352,7 @@ const default_settings = {
350 openai_model: 'gpt-4-turbo',352 openai_model: 'gpt-4-turbo',
351 claude_model: 'claude-3-5-sonnet-20240620',353 claude_model: 'claude-3-5-sonnet-20240620',
352 google_model: 'gemini-1.5-pro',354 google_model: 'gemini-1.5-pro',
355 vertexai_model: 'gemini-2.0-flash-001',
353 ai21_model: 'jamba-1.6-large',356 ai21_model: 'jamba-1.6-large',
354 mistralai_model: 'mistral-large-latest',357 mistralai_model: 'mistral-large-latest',
355 cohere_model: 'command-r-plus',358 cohere_model: 'command-r-plus',
@@ -433,6 +436,7 @@ const oai_settings = {
433 openai_model: 'gpt-4-turbo',436 openai_model: 'gpt-4-turbo',
434 claude_model: 'claude-3-5-sonnet-20240620',437 claude_model: 'claude-3-5-sonnet-20240620',
435 google_model: 'gemini-1.5-pro',438 google_model: 'gemini-1.5-pro',
439 vertexai_model: 'gemini-2.0-flash-001',
436 ai21_model: 'jamba-1.6-large',440 ai21_model: 'jamba-1.6-large',
437 mistralai_model: 'mistral-large-latest',441 mistralai_model: 'mistral-large-latest',
438 cohere_model: 'command-r-plus',442 cohere_model: 'command-r-plus',
@@ -1666,6 +1670,8 @@ export function getChatCompletionModel(source = null) {
1666 return '';1670 return '';
1667 case chat_completion_sources.MAKERSUITE:1671 case chat_completion_sources.MAKERSUITE:
1668 return oai_settings.google_model;1672 return oai_settings.google_model;
1673 case chat_completion_sources.VERTEXAI:
1674 return oai_settings.vertexai_model;
1669 case chat_completion_sources.OPENROUTER:1675 case chat_completion_sources.OPENROUTER:
1670 return oai_settings.openrouter_model !== openrouter_website_model ? oai_settings.openrouter_model : null;1676 return oai_settings.openrouter_model !== openrouter_website_model ? oai_settings.openrouter_model : null;
1671 case chat_completion_sources.AI21:1677 case chat_completion_sources.AI21:
@@ -2048,6 +2054,7 @@ async function sendOpenAIRequest(type, messages, signal) {
2048 const isOpenRouter = oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER;2054 const isOpenRouter = oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER;
2049 const isScale = oai_settings.chat_completion_source == chat_completion_sources.SCALE;2055 const isScale = oai_settings.chat_completion_source == chat_completion_sources.SCALE;
2050 const isGoogle = oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE;2056 const isGoogle = oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE;
2057 const isVertexAI = oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI;
2051 const isOAI = oai_settings.chat_completion_source == chat_completion_sources.OPENAI;2058 const isOAI = oai_settings.chat_completion_source == chat_completion_sources.OPENAI;
2052 const isMistral = oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI;2059 const isMistral = oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI;
2053 const isCustom = oai_settings.chat_completion_source == chat_completion_sources.CUSTOM;2060 const isCustom = oai_settings.chat_completion_source == chat_completion_sources.CUSTOM;
@@ -2123,8 +2130,8 @@ async function sendOpenAIRequest(type, messages, signal) {
2123 delete generate_data.stop;2130 delete generate_data.stop;
2124 }2131 }
21252132
2126 // Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite2133 // Proxy is only supported for Claude, OpenAI, Mistral, Google MakerSuite, and Vertex AI
2127 if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) {2134 if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) {
2128 await validateReverseProxy();2135 await validateReverseProxy();
2129 generate_data['reverse_proxy'] = oai_settings.reverse_proxy;2136 generate_data['reverse_proxy'] = oai_settings.reverse_proxy;
2130 generate_data['proxy_password'] = oai_settings.proxy_password;2137 generate_data['proxy_password'] = oai_settings.proxy_password;
@@ -2175,7 +2182,7 @@ async function sendOpenAIRequest(type, messages, signal) {
2175 generate_data['api_url_scale'] = oai_settings.api_url_scale;2182 generate_data['api_url_scale'] = oai_settings.api_url_scale;
2176 }2183 }
21772184
2178 if (isGoogle) {2185 if (isGoogle || isVertexAI) {
2179 const stopStringsLimit = 5;2186 const stopStringsLimit = 5;
2180 generate_data['top_k'] = Number(oai_settings.top_k_openai);2187 generate_data['top_k'] = Number(oai_settings.top_k_openai);
2181 generate_data['stop'] = getCustomStoppingStrings(stopStringsLimit).slice(0, stopStringsLimit).filter(x => x.length >= 1 && x.length <= 16);2188 generate_data['stop'] = getCustomStoppingStrings(stopStringsLimit).slice(0, stopStringsLimit).filter(x => x.length >= 1 && x.length <= 16);
@@ -2382,7 +2389,7 @@ export function getStreamingReply(data, state, { chatCompletionSource = null, ov
2382 state.reasoning += data?.delta?.thinking || '';2389 state.reasoning += data?.delta?.thinking || '';
2383 }2390 }
2384 return data?.delta?.text || '';2391 return data?.delta?.text || '';
2385 } else if (chat_completion_source === chat_completion_sources.MAKERSUITE) {2392 } else if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(chat_completion_source)) {
2386 const inlineData = data?.candidates?.[0]?.content?.parts?.find(x => x.inlineData)?.inlineData;2393 const inlineData = data?.candidates?.[0]?.content?.parts?.find(x => x.inlineData)?.inlineData;
2387 if (inlineData) {2394 if (inlineData) {
2388 state.image = `data:${inlineData.mimeType};base64,${inlineData.data}`;2395 state.image = `data:${inlineData.mimeType};base64,${inlineData.data}`;
@@ -2772,7 +2779,13 @@ class Message {
2772 * @returns {Promise<string>} Compressed image as a Data URL.2779 * @returns {Promise<string>} Compressed image as a Data URL.
2773 */2780 */
2774 async compressImage(image) {2781 async compressImage(image) {
2775 if ([chat_completion_sources.OPENROUTER, chat_completion_sources.MAKERSUITE, chat_completion_sources.MISTRALAI].includes(oai_settings.chat_completion_source)) {2782 const compressImageSources = [
2783 chat_completion_sources.OPENROUTER,
2784 chat_completion_sources.MAKERSUITE,
2785 chat_completion_sources.MISTRALAI,
2786 chat_completion_sources.VERTEXAI,
2787 ];
2788 if (compressImageSources.includes(oai_settings.chat_completion_source)) {
2776 const sizeThreshold = 2 * 1024 * 1024;2789 const sizeThreshold = 2 * 1024 * 1024;
2777 const dataSize = image.length * 0.75;2790 const dataSize = image.length * 0.75;
2778 const maxSide = 1024;2791 const maxSide = 1024;
@@ -3368,6 +3381,7 @@ function loadOpenAISettings(data, settings) {
3368 oai_settings.custom_include_headers = settings.custom_include_headers ?? default_settings.custom_include_headers;3381 oai_settings.custom_include_headers = settings.custom_include_headers ?? default_settings.custom_include_headers;
3369 oai_settings.custom_prompt_post_processing = settings.custom_prompt_post_processing ?? default_settings.custom_prompt_post_processing;3382 oai_settings.custom_prompt_post_processing = settings.custom_prompt_post_processing ?? default_settings.custom_prompt_post_processing;
3370 oai_settings.google_model = settings.google_model ?? default_settings.google_model;3383 oai_settings.google_model = settings.google_model ?? default_settings.google_model;
3384 oai_settings.vertexai_model = settings.vertexai_model ?? default_settings.vertexai_model;
3371 oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source;3385 oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source;
3372 oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale;3386 oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale;
3373 oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models;3387 oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models;
@@ -3432,6 +3446,8 @@ function loadOpenAISettings(data, settings) {
3432 $(`#model_windowai_select option[value="${oai_settings.windowai_model}"`).prop('selected', true);3446 $(`#model_windowai_select option[value="${oai_settings.windowai_model}"`).prop('selected', true);
3433 $('#model_google_select').val(oai_settings.google_model);3447 $('#model_google_select').val(oai_settings.google_model);
3434 $(`#model_google_select option[value="${oai_settings.google_model}"`).prop('selected', true);3448 $(`#model_google_select option[value="${oai_settings.google_model}"`).prop('selected', true);
3449 $('#model_vertexai_select').val(oai_settings.vertexai_model);
3450 $(`#model_vertexai_select option[value="${oai_settings.vertexai_model}"`).prop('selected', true);
3435 $('#model_ai21_select').val(oai_settings.ai21_model);3451 $('#model_ai21_select').val(oai_settings.ai21_model);
3436 $(`#model_ai21_select option[value="${oai_settings.ai21_model}"`).prop('selected', true);3452 $(`#model_ai21_select option[value="${oai_settings.ai21_model}"`).prop('selected', true);
3437 $('#model_mistralai_select').val(oai_settings.mistralai_model);3453 $('#model_mistralai_select').val(oai_settings.mistralai_model);
@@ -3627,6 +3643,7 @@ async function getStatusOpen() {
3627 chat_completion_sources.CLAUDE,3643 chat_completion_sources.CLAUDE,
3628 chat_completion_sources.AI21,3644 chat_completion_sources.AI21,
3629 chat_completion_sources.MAKERSUITE,3645 chat_completion_sources.MAKERSUITE,
3646 chat_completion_sources.VERTEXAI,
3630 chat_completion_sources.PERPLEXITY,3647 chat_completion_sources.PERPLEXITY,
3631 chat_completion_sources.GROQ,3648 chat_completion_sources.GROQ,
3632 ];3649 ];
@@ -3648,7 +3665,16 @@ async function getStatusOpen() {
3648 chat_completion_source: oai_settings.chat_completion_source,3665 chat_completion_source: oai_settings.chat_completion_source,
3649 };3666 };
36503667
3651 if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) {3668 const validateProxySources = [
3669 chat_completion_sources.CLAUDE,
3670 chat_completion_sources.OPENAI,
3671 chat_completion_sources.MISTRALAI,
3672 chat_completion_sources.MAKERSUITE,
3673 chat_completion_sources.VERTEXAI,
3674 chat_completion_sources.DEEPSEEK,
3675 chat_completion_sources.XAI,
3676 ];
3677 if (oai_settings.reverse_proxy && validateProxySources.includes(oai_settings.chat_completion_source)) {
3652 await validateReverseProxy();3678 await validateReverseProxy();
3653 }3679 }
36543680
@@ -3740,6 +3766,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
3740 custom_include_headers: settings.custom_include_headers,3766 custom_include_headers: settings.custom_include_headers,
3741 custom_prompt_post_processing: settings.custom_prompt_post_processing,3767 custom_prompt_post_processing: settings.custom_prompt_post_processing,
3742 google_model: settings.google_model,3768 google_model: settings.google_model,
3769 vertexai_model: settings.vertexai_model,
3743 temperature: settings.temp_openai,3770 temperature: settings.temp_openai,
3744 frequency_penalty: settings.freq_pen_openai,3771 frequency_penalty: settings.freq_pen_openai,
3745 presence_penalty: settings.pres_pen_openai,3772 presence_penalty: settings.pres_pen_openai,
@@ -4495,6 +4522,11 @@ async function onModelChange() {
4495 oai_settings.google_model = value;4522 oai_settings.google_model = value;
4496 }4523 }
44974524
4525 if ($(this).is('#model_vertexai_select')) {
4526 console.log('Vertex AI model changed to', value);
4527 oai_settings.vertexai_model = value;
4528 }
4529
4498 if ($(this).is('#model_mistralai_select')) {4530 if ($(this).is('#model_mistralai_select')) {
4499 // Upgrade old mistral models to new naming scheme4531 // Upgrade old mistral models to new naming scheme
4500 // would have done this in loadOpenAISettings, but it wasn't updating on preset change?4532 // would have done this in loadOpenAISettings, but it wasn't updating on preset change?
@@ -4575,7 +4607,7 @@ async function onModelChange() {
4575 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');4607 $('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
4576 }4608 }
45774609
4578 if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {4610 if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(oai_settings.chat_completion_source)) {
4579 if (oai_settings.max_context_unlocked) {4611 if (oai_settings.max_context_unlocked) {
4580 $('#openai_max_context').attr('max', max_2mil);4612 $('#openai_max_context').attr('max', max_2mil);
4581 } else if (value.includes('gemini-1.5-pro')) {4613 } else if (value.includes('gemini-1.5-pro')) {
@@ -4943,6 +4975,19 @@ async function onConnectButtonClick(e) {
4943 }4975 }
4944 }4976 }
49454977
4978 if (oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI) {
4979 const api_key_vertexai = String($('#api_key_vertexai').val()).trim();
4980
4981 if (api_key_vertexai.length) {
4982 await writeSecret(SECRET_KEYS.VERTEXAI, api_key_vertexai);
4983 }
4984
4985 if (!secret_state[SECRET_KEYS.VERTEXAI] && !oai_settings.reverse_proxy) {
4986 console.log('No secret key saved for Vertex AI');
4987 return;
4988 }
4989 }
4990
4946 if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {4991 if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
4947 const api_key_claude = String($('#api_key_claude').val()).trim();4992 const api_key_claude = String($('#api_key_claude').val()).trim();
49484993
@@ -5120,6 +5165,9 @@ function toggleChatCompletionForms() {
5120 else if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {5165 else if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
5121 $('#model_google_select').trigger('change');5166 $('#model_google_select').trigger('change');
5122 }5167 }
5168 else if (oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI) {
5169 $('#model_vertexai_select').trigger('change');
5170 }
5123 else if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) {5171 else if (oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) {
5124 $('#model_openrouter_select').trigger('change');5172 $('#model_openrouter_select').trigger('change');
5125 }5173 }
@@ -5281,6 +5329,8 @@ export function isImageInliningSupported() {
5281 );5329 );
5282 case chat_completion_sources.MAKERSUITE:5330 case chat_completion_sources.MAKERSUITE:
5283 return visionSupportedModels.some(model => oai_settings.google_model.includes(model));5331 return visionSupportedModels.some(model => oai_settings.google_model.includes(model));
5332 case chat_completion_sources.VERTEXAI:
5333 return visionSupportedModels.some(model => oai_settings.vertexai_model.includes(model));
5284 case chat_completion_sources.CLAUDE:5334 case chat_completion_sources.CLAUDE:
5285 return visionSupportedModels.some(model => oai_settings.claude_model.includes(model));5335 return visionSupportedModels.some(model => oai_settings.claude_model.includes(model));
5286 case chat_completion_sources.OPENROUTER:5336 case chat_completion_sources.OPENROUTER:
@@ -5887,6 +5937,7 @@ export function initOpenAI() {
5887 $('#model_windowai_select').on('change', onModelChange);5937 $('#model_windowai_select').on('change', onModelChange);
5888 $('#model_scale_select').on('change', onModelChange);5938 $('#model_scale_select').on('change', onModelChange);
5889 $('#model_google_select').on('change', onModelChange);5939 $('#model_google_select').on('change', onModelChange);
5940 $('#model_vertexai_select').on('change', onModelChange);
5890 $('#model_openrouter_select').on('change', onModelChange);5941 $('#model_openrouter_select').on('change', onModelChange);
5891 $('#openrouter_group_models').on('change', onOpenrouterModelSortChange);5942 $('#openrouter_group_models').on('change', onOpenrouterModelSortChange);
5892 $('#openrouter_sort_models').on('change', onOpenrouterModelSortChange);5943 $('#openrouter_sort_models').on('change', onOpenrouterModelSortChange);
public/scripts/reasoning.js+1 -0
@@ -114,6 +114,7 @@ export function extractReasoningFromData(data, {
114 case chat_completion_sources.OPENROUTER:114 case chat_completion_sources.OPENROUTER:
115 return data?.choices?.[0]?.message?.reasoning ?? '';115 return data?.choices?.[0]?.message?.reasoning ?? '';
116 case chat_completion_sources.MAKERSUITE:116 case chat_completion_sources.MAKERSUITE:
117 case chat_completion_sources.VERTEXAI:
117 return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? '';118 return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? '';
118 case chat_completion_sources.CLAUDE:119 case chat_completion_sources.CLAUDE:
119 return data?.content?.find(part => part.type === 'thinking')?.thinking ?? '';120 return data?.content?.find(part => part.type === 'thinking')?.thinking ?? '';
public/scripts/secrets.js+2 -0
@@ -16,6 +16,7 @@ export const SECRET_KEYS = {
16 AI21: 'api_key_ai21',16 AI21: 'api_key_ai21',
17 SCALE_COOKIE: 'scale_cookie',17 SCALE_COOKIE: 'scale_cookie',
18 MAKERSUITE: 'api_key_makersuite',18 MAKERSUITE: 'api_key_makersuite',
19 VERTEXAI: 'api_key_vertexai',
19 SERPAPI: 'api_key_serpapi',20 SERPAPI: 'api_key_serpapi',
20 MISTRALAI: 'api_key_mistralai',21 MISTRALAI: 'api_key_mistralai',
21 TOGETHERAI: 'api_key_togetherai',22 TOGETHERAI: 'api_key_togetherai',
@@ -56,6 +57,7 @@ const INPUT_MAP = {
56 [SECRET_KEYS.AI21]: '#api_key_ai21',57 [SECRET_KEYS.AI21]: '#api_key_ai21',
57 [SECRET_KEYS.SCALE_COOKIE]: '#scale_cookie',58 [SECRET_KEYS.SCALE_COOKIE]: '#scale_cookie',
58 [SECRET_KEYS.MAKERSUITE]: '#api_key_makersuite',59 [SECRET_KEYS.MAKERSUITE]: '#api_key_makersuite',
60 [SECRET_KEYS.VERTEXAI]: '#api_key_vertexai',
59 [SECRET_KEYS.VLLM]: '#api_key_vllm',61 [SECRET_KEYS.VLLM]: '#api_key_vllm',
60 [SECRET_KEYS.APHRODITE]: '#api_key_aphrodite',62 [SECRET_KEYS.APHRODITE]: '#api_key_aphrodite',
61 [SECRET_KEYS.TABBY]: '#api_key_tabby',63 [SECRET_KEYS.TABBY]: '#api_key_tabby',
public/scripts/slash-commands.js+1 -0
@@ -4127,6 +4127,7 @@ function getModelOptions(quiet) {
4127 { id: 'model_openrouter_select', api: 'openai', type: chat_completion_sources.OPENROUTER },4127 { id: 'model_openrouter_select', api: 'openai', type: chat_completion_sources.OPENROUTER },
4128 { id: 'model_ai21_select', api: 'openai', type: chat_completion_sources.AI21 },4128 { id: 'model_ai21_select', api: 'openai', type: chat_completion_sources.AI21 },
4129 { id: 'model_google_select', api: 'openai', type: chat_completion_sources.MAKERSUITE },4129 { id: 'model_google_select', api: 'openai', type: chat_completion_sources.MAKERSUITE },
4130 { id: 'model_vertexai_select', api: 'openai', type: chat_completion_sources.VERTEXAI },
4130 { id: 'model_mistralai_select', api: 'openai', type: chat_completion_sources.MISTRALAI },4131 { id: 'model_mistralai_select', api: 'openai', type: chat_completion_sources.MISTRALAI },
4131 { id: 'custom_model_id', api: 'openai', type: chat_completion_sources.CUSTOM },4132 { id: 'custom_model_id', api: 'openai', type: chat_completion_sources.CUSTOM },
4132 { id: 'model_cohere_select', api: 'openai', type: chat_completion_sources.COHERE },4133 { id: 'model_cohere_select', api: 'openai', type: chat_completion_sources.COHERE },
public/scripts/tokenizers.js+4 -0
@@ -676,6 +676,10 @@ export function getTokenizerModel() {
676 return gemmaTokenizer;676 return gemmaTokenizer;
677 }677 }
678678
679 if (oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI) {
680 return gemmaTokenizer;
681 }
682
679 if (oai_settings.chat_completion_source == chat_completion_sources.AI21) {683 if (oai_settings.chat_completion_source == chat_completion_sources.AI21) {
680 return jambaTokenizer;684 return jambaTokenizer;
681 }685 }
public/scripts/tool-calling.js+1 -0
@@ -592,6 +592,7 @@ export class ToolManager {
592 chat_completion_sources.COHERE,592 chat_completion_sources.COHERE,
593 chat_completion_sources.DEEPSEEK,593 chat_completion_sources.DEEPSEEK,
594 chat_completion_sources.MAKERSUITE,594 chat_completion_sources.MAKERSUITE,
595 chat_completion_sources.VERTEXAI,
595 chat_completion_sources.AI21,596 chat_completion_sources.AI21,
596 chat_completion_sources.XAI,597 chat_completion_sources.XAI,
597 chat_completion_sources.POLLINATIONS,598 chat_completion_sources.POLLINATIONS,
src/constants.js+1 -0
@@ -168,6 +168,7 @@ export const CHAT_COMPLETION_SOURCES = {
168 OPENROUTER: 'openrouter',168 OPENROUTER: 'openrouter',
169 AI21: 'ai21',169 AI21: 'ai21',
170 MAKERSUITE: 'makersuite',170 MAKERSUITE: 'makersuite',
171 VERTEXAI: 'vertexai',
171 MISTRALAI: 'mistralai',172 MISTRALAI: 'mistralai',
172 CUSTOM: 'custom',173 CUSTOM: 'custom',
173 COHERE: 'cohere',174 COHERE: 'cohere',
src/endpoints/backends/chat-completions.js+33 -10
@@ -52,6 +52,7 @@ const API_COHERE_V2 = 'https://api.cohere.ai/v2';
52const API_PERPLEXITY = 'https://api.perplexity.ai';52const API_PERPLEXITY = 'https://api.perplexity.ai';
53const API_GROQ = 'https://api.groq.com/openai/v1';53const API_GROQ = 'https://api.groq.com/openai/v1';
54const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';54const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
55const API_VERTEX_AI = 'https://us-central1-aiplatform.googleapis.com';
55const API_01AI = 'https://api.lingyiwanwu.com/v1';56const API_01AI = 'https://api.lingyiwanwu.com/v1';
56const API_AI21 = 'https://api.ai21.com/studio/v1';57const API_AI21 = 'https://api.ai21.com/studio/v1';
57const API_NANOGPT = 'https://nano-gpt.com/api/v1';58const API_NANOGPT = 'https://nano-gpt.com/api/v1';
@@ -337,13 +338,28 @@ async function sendScaleRequest(request, response) {
337 * @param {express.Response} response Express response338 * @param {express.Response} response Express response
338 */339 */
339async function sendMakerSuiteRequest(request, response) {340async function sendMakerSuiteRequest(request, response) {
340 const apiUrl = new URL(request.body.reverse_proxy || API_MAKERSUITE);341 const useVertexAi = request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.VERTEXAI;
341 const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE);342 const apiName = useVertexAi ? 'Google Vertex AI' : 'Google AI Studio';
343 let apiUrl;
344 let apiKey;
345
346 if (useVertexAi) {
347 apiUrl = new URL(request.body.reverse_proxy || API_VERTEX_AI);
348 apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.VERTEXAI);
342349
343 if (!request.body.reverse_proxy && !apiKey) {350 if (!request.body.reverse_proxy && !apiKey) {
344 console.warn('Google AI Studio API key is missing.');351 console.warn(`${apiName} API key is missing.`);
345 return response.status(400).send({ error: true });352 return response.status(400).send({ error: true });
346 }353 }
354 } else {
355 apiUrl = new URL(request.body.reverse_proxy || API_MAKERSUITE);
356 apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE);
357
358 if (!request.body.reverse_proxy && !apiKey) {
359 console.warn(`${apiName} API key is missing.`);
360 return response.status(400).send({ error: true });
361 }
362 }
347363
348 const model = String(request.body.model);364 const model = String(request.body.model);
349 const stream = Boolean(request.body.stream);365 const stream = Boolean(request.body.stream);
@@ -465,7 +481,7 @@ async function sendMakerSuiteRequest(request, response) {
465 }481 }
466482
467 const body = getGeminiBody();483 const body = getGeminiBody();
468 console.debug('Google AI Studio request:', body);484 console.debug(`${apiName} request:`, body);
469485
470 try {486 try {
471 const controller = new AbortController();487 const controller = new AbortController();
@@ -477,7 +493,13 @@ async function sendMakerSuiteRequest(request, response) {
477 const apiVersion = getConfigValue('gemini.apiVersion', 'v1beta');493 const apiVersion = getConfigValue('gemini.apiVersion', 'v1beta');
478 const responseType = (stream ? 'streamGenerateContent' : 'generateContent');494 const responseType = (stream ? 'streamGenerateContent' : 'generateContent');
479495
480 const generateResponse = await fetch(`${apiUrl.toString().replace(/\/$/, '')}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, {496 let url;
497 if (useVertexAi) {
498 url = `${apiUrl.toString().replace(/\/$/, '')}/v1/publishers/google/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`;
499 } else {
500 url = `${apiUrl.toString().replace(/\/$/, '')}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`;
501 }
502 const generateResponse = await fetch(url, {
481 body: JSON.stringify(body),503 body: JSON.stringify(body),
482 method: 'POST',504 method: 'POST',
483 headers: {505 headers: {
@@ -498,7 +520,7 @@ async function sendMakerSuiteRequest(request, response) {
498 }520 }
499 } else {521 } else {
500 if (!generateResponse.ok) {522 if (!generateResponse.ok) {
501 console.warn(`Google AI Studio API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`);523 console.warn(`${apiName} API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`);
502 return response.status(500).send({ error: true });524 return response.status(500).send({ error: true });
503 }525 }
504526
@@ -507,7 +529,7 @@ async function sendMakerSuiteRequest(request, response) {
507529
508 const candidates = generateResponseJson?.candidates;530 const candidates = generateResponseJson?.candidates;
509 if (!candidates || candidates.length === 0) {531 if (!candidates || candidates.length === 0) {
510 let message = 'Google AI Studio API returned no candidate';532 let message = `${apiName} API returned no candidate`;
511 console.warn(message, generateResponseJson);533 console.warn(message, generateResponseJson);
512 if (generateResponseJson?.promptFeedback?.blockReason) {534 if (generateResponseJson?.promptFeedback?.blockReason) {
513 message += `\nPrompt was blocked due to : ${generateResponseJson.promptFeedback.blockReason}`;535 message += `\nPrompt was blocked due to : ${generateResponseJson.promptFeedback.blockReason}`;
@@ -518,11 +540,11 @@ async function sendMakerSuiteRequest(request, response) {
518 const responseContent = candidates[0].content ?? candidates[0].output;540 const responseContent = candidates[0].content ?? candidates[0].output;
519 const functionCall = (candidates?.[0]?.content?.parts ?? []).some(part => part.functionCall);541 const functionCall = (candidates?.[0]?.content?.parts ?? []).some(part => part.functionCall);
520 const inlineData = (candidates?.[0]?.content?.parts ?? []).some(part => part.inlineData);542 const inlineData = (candidates?.[0]?.content?.parts ?? []).some(part => part.inlineData);
521 console.debug('Google AI Studio response:', util.inspect(generateResponseJson, { depth: 5, colors: true }));543 console.debug(`${apiName} response:`, util.inspect(generateResponseJson, { depth: 5, colors: true }));
522544
523 const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.filter(part => !part.thought)?.map(part => part.text)?.join('\n\n');545 const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.filter(part => !part.thought)?.map(part => part.text)?.join('\n\n');
524 if (!responseText && !functionCall && !inlineData) {546 if (!responseText && !functionCall && !inlineData) {
525 let message = 'Google AI Studio Candidate text empty';547 let message = `${apiName} Candidate text empty`;
526 console.warn(message, generateResponseJson);548 console.warn(message, generateResponseJson);
527 return response.send({ error: { message } });549 return response.send({ error: { message } });
528 }550 }
@@ -532,7 +554,7 @@ async function sendMakerSuiteRequest(request, response) {
532 return response.send(reply);554 return response.send(reply);
533 }555 }
534 } catch (error) {556 } catch (error) {
535 console.error('Error communicating with Google AI Studio API: ', error);557 console.error(`Error communicating with ${apiName} API:`, error);
536 if (!response.headersSent) {558 if (!response.headersSent) {
537 return response.status(500).send({ error: true });559 return response.status(500).send({ error: true });
538 }560 }
@@ -1189,6 +1211,7 @@ router.post('/generate', function (request, response) {
1189 case CHAT_COMPLETION_SOURCES.SCALE: return sendScaleRequest(request, response);1211 case CHAT_COMPLETION_SOURCES.SCALE: return sendScaleRequest(request, response);
1190 case CHAT_COMPLETION_SOURCES.AI21: return sendAI21Request(request, response);1212 case CHAT_COMPLETION_SOURCES.AI21: return sendAI21Request(request, response);
1191 case CHAT_COMPLETION_SOURCES.MAKERSUITE: return sendMakerSuiteRequest(request, response);1213 case CHAT_COMPLETION_SOURCES.MAKERSUITE: return sendMakerSuiteRequest(request, response);
1214 case CHAT_COMPLETION_SOURCES.VERTEXAI: return sendMakerSuiteRequest(request, response);
1192 case CHAT_COMPLETION_SOURCES.MISTRALAI: return sendMistralAIRequest(request, response);1215 case CHAT_COMPLETION_SOURCES.MISTRALAI: return sendMistralAIRequest(request, response);
1193 case CHAT_COMPLETION_SOURCES.COHERE: return sendCohereRequest(request, response);1216 case CHAT_COMPLETION_SOURCES.COHERE: return sendCohereRequest(request, response);
1194 case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response);1217 case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response);
src/endpoints/google.js+22 -6
@@ -7,6 +7,7 @@ import { readSecret, SECRET_KEYS } from './secrets.js';
7import { GEMINI_SAFETY } from '../constants.js';7import { GEMINI_SAFETY } from '../constants.js';
88
9const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';9const API_MAKERSUITE = 'https://generativelanguage.googleapis.com';
10const API_VERTEX_AI = 'https://us-central1-aiplatform.googleapis.com';
1011
11export const router = express.Router();12export const router = express.Router();
1213
@@ -14,12 +15,27 @@ router.post('/caption-image', async (request, response) => {
14 try {15 try {
15 const mimeType = request.body.image.split(';')[0].split(':')[1];16 const mimeType = request.body.image.split(';')[0].split(':')[1];
16 const base64Data = request.body.image.split(',')[1];17 const base64Data = request.body.image.split(',')[1];
17 const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE);18 const useVertexAi = request.body.api === 'vertexai';
18 const apiUrl = new URL(request.body.reverse_proxy || API_MAKERSUITE);19 const apiName = useVertexAi ? 'Google Vertex AI' : 'Google AI Studio';
20 let apiKey;
21 let apiUrl;
22 if (useVertexAi) {
23 apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.VERTEXAI);
24 apiUrl = new URL(request.body.reverse_proxy || API_VERTEX_AI);
25 } else {
26 apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.MAKERSUITE);
27 apiUrl = new URL(request.body.reverse_proxy || API_MAKERSUITE);
28 }
19 const model = request.body.model || 'gemini-2.0-flash';29 const model = request.body.model || 'gemini-2.0-flash';
20 const url = `${apiUrl.origin}/v1beta/models/${model}:generateContent?key=${apiKey}`;30 let url;
31 if (useVertexAi) {
32 url = `${apiUrl.origin}/v1/publishers/google/models/${model}:generateContent?key=${apiKey}`;
33 } else {
34 url = `${apiUrl.origin}/v1beta/models/${model}:generateContent?key=${apiKey}`;
35 }
21 const body = {36 const body = {
22 contents: [{37 contents: [{
38 role: 'user',
23 parts: [39 parts: [
24 { text: request.body.prompt },40 { text: request.body.prompt },
25 {41 {
@@ -32,7 +48,7 @@ router.post('/caption-image', async (request, response) => {
32 safetySettings: GEMINI_SAFETY,48 safetySettings: GEMINI_SAFETY,
33 };49 };
3450
35 console.debug('Multimodal captioning request', model, body);51 console.debug(`${apiName} captioning request`, model, body);
3652
37 const result = await fetch(url, {53 const result = await fetch(url, {
38 body: JSON.stringify(body),54 body: JSON.stringify(body),
@@ -44,13 +60,13 @@ router.post('/caption-image', async (request, response) => {
4460
45 if (!result.ok) {61 if (!result.ok) {
46 const error = await result.json();62 const error = await result.json();
47 console.error(`Google AI Studio API returned error: ${result.status} ${result.statusText}`, error);63 console.error(`${apiName} API returned error: ${result.status} ${result.statusText}`, error);
48 return response.status(500).send({ error: true });64 return response.status(500).send({ error: true });
49 }65 }
5066
51 /** @type {any} */67 /** @type {any} */
52 const data = await result.json();68 const data = await result.json();
53 console.info('Multimodal captioning response', data);69 console.info(`${apiName} captioning response`, data);
5470
55 const candidates = data?.candidates;71 const candidates = data?.candidates;
56 if (!candidates) {72 if (!candidates) {
src/endpoints/secrets.js+1 -0
@@ -26,6 +26,7 @@ export const SECRET_KEYS = {
26 ONERING_URL: 'oneringtranslator_url',26 ONERING_URL: 'oneringtranslator_url',
27 DEEPLX_URL: 'deeplx_url',27 DEEPLX_URL: 'deeplx_url',
28 MAKERSUITE: 'api_key_makersuite',28 MAKERSUITE: 'api_key_makersuite',
29 VERTEXAI: 'api_key_vertexai',
29 SERPAPI: 'api_key_serpapi',30 SERPAPI: 'api_key_serpapi',
30 TOGETHERAI: 'api_key_togetherai',31 TOGETHERAI: 'api_key_togetherai',
31 MISTRALAI: 'api_key_mistralai',32 MISTRALAI: 'api_key_mistralai',
src/endpoints/tokenizers.js+1 -1
@@ -463,7 +463,7 @@ export function getTokenizerModel(requestModel) {
463 return 'deepseek';463 return 'deepseek';
464 }464 }
465465
466 if (requestModel.includes('gemma') || requestModel.includes('gemini')) {466 if (requestModel.includes('gemma') || requestModel.includes('gemini') || requestModel.includes('learnlm')) {
467 return 'gemma';467 return 'gemma';
468 }468 }
469469