Derive Vertex AI Project ID from Service Account JSON This commit refactors the Vertex AI integration to automatically derive the Project ID from the provided Service Account JSON. This simplifies the configuration process for users in "Full" (service account) authentication mode by removing the need to specify the Project ID separately.

75e3f599e6e808d7858176f382af2baabafa7b0c

InterestingDarkness <computeridiot999@gmail.com>

7 files changed, +48 -35Showing whitespace changes
public/index.html+0 -6
@@ -3266,12 +3266,6 @@
32663266 </a>
32673267 </h4>
32683268
3269- <!-- Project ID -->
3270- <div class="flex-container">
3271- <label for="vertexai_project_id" data-i18n="Project ID">Project ID:</label>
3272- <input id="vertexai_project_id" name="vertexai_project_id" class="text_pole flex1" value="" type="text" autocomplete="off" placeholder="your-gcp-project-id">
3273- </div>
3274-
32753269 <!-- Region -->
32763270 <div class="flex-container">
32773271 <label for="vertexai_region" data-i18n="Region">Region:</label>
public/scripts/extensions/shared.js+1 -4
@@ -180,13 +180,10 @@ function throwIfInvalidModel(useReverseProxy) {
180180 throw new Error('Google Vertex AI API key is not set for Express mode.');
181181 }
182182 } else if (authMode === 'full') {
183183 // Full mode requires Service Account JSON and projectregion settings
184184 if (!secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT]) {
185185 throw new Error('Service Account JSON is required for Vertex AI Full mode. Please validate and save your Service Account JSON.');
186186 }
187- if (!secret_state[SECRET_KEYS.VERTEXAI_PROJECT_ID]) {
188- throw new Error('Project ID is required for Vertex AI Full mode.');
189- }
190187 if (!oai_settings.vertexai_region) {
191188 throw new Error('Region is required for Vertex AI Full mode.');
192189 }
public/scripts/openai.js+1 -15
@@ -3496,8 +3496,6 @@ function loadOpenAISettings(data, settings) {
34963496 $('#claude_use_sysprompt').prop('checked', oai_settings.claude_use_sysprompt);
34973497 $('#use_makersuite_sysprompt').prop('checked', oai_settings.use_makersuite_sysprompt);
34983498 $('#vertexai_auth_mode').val(oai_settings.vertexai_auth_mode);
3499- // Don't display Project ID in input - it's stored in backend secrets
3500- $('#vertexai_project_id').val('');
35013499 $('#vertexai_region').val(oai_settings.vertexai_region);
35023500 // Don't display Service Account JSON in textarea - it's stored in backend secrets
35033501 $('#vertexai_service_account_json').val('');
@@ -5015,19 +5013,7 @@ async function onConnectButtonClick(e) {
50155013 }
50165014 } else {
50175015 // Full version - use service account
5018- // Check if we have a saved project ID, otherwise use the input value
5016+ // Project ID will be extracted from the Service Account JSON
5019- const savedProjectId = secret_state[SECRET_KEYS.VERTEXAI_PROJECT_ID];
5020- const inputProjectId = String($('#vertexai_project_id').val()).trim();
5021-
5022- if (!savedProjectId && !inputProjectId) {
5023- toastr.error(t`Project ID is required for Vertex AI full version`);
5024- return;
5025- }
5026-
5027- // Save project ID to secrets if we have an input value
5028- if (inputProjectId.length) {
5029- await writeSecret(SECRET_KEYS.VERTEXAI_PROJECT_ID, inputProjectId);
5030- }
50315017
50325018 // Check if service account JSON is saved in backend
50335019 if (!secret_state[SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT]) {
public/scripts/secrets.js+0 -1
@@ -44,7 +44,6 @@ export const SECRET_KEYS = {
4444 SERPER: 'api_key_serper',
4545 FALAI: 'api_key_falai',
4646 XAI: 'api_key_xai',
47- VERTEXAI_PROJECT_ID: 'vertexai_project_id',
4847 VERTEXAI_SERVICE_ACCOUNT: 'vertexai_service_account_json',
4948};
5049
src/endpoints/backends/chat-completions.js+14 -5
@@ -43,7 +43,7 @@ import {
4343 webTokenizers,
4444 getWebTokenizer,
4545} from '../tokenizers.js';
4646import { getVertexAIAuth, getProjectIdFromServiceAccount } from '../google.js';
4747
4848const API_OPENAI = 'https://api.openai.com/v1';
4949const API_CLAUDE = 'https://api.anthropic.com/v1';
@@ -528,10 +528,19 @@ async function sendMakerSuiteRequest(request, response) {
528528 url = `${apiUrl.toString().replace(/\/$/, '')}/v1/publishers/google/models/${model}:${responseType}?key=${keyParam}${stream ? '&alt=sse' : ''}`;
529529 } else if (authType === 'full') {
530530 // For Full mode (service account authentication), use project-specific URL
531531 // Only useGet project ID from secretsService Account JSON
532532 const projectIdserviceAccountJson = readSecret(request.user.directories, SECRET_KEYS.VERTEXAI_PROJECT_IDVERTEXAI_SERVICE_ACCOUNT);
533533 if (!projectIdserviceAccountJson) {
534534 console.warn('Vertex AI projectService IDAccount JSON is missing.');
535+ return response.status(400).send({ error: true });
536+ }
537+
538+ let projectId;
539+ try {
540+ const serviceAccount = JSON.parse(serviceAccountJson);
541+ projectId = getProjectIdFromServiceAccount(serviceAccount);
542+ } catch (error) {
543+ console.error('Failed to extract project ID from Service Account JSON:', error);
535544 return response.status(400).send({ error: true });
536545 }
537546 const region = request.body.vertexai_region || 'us-central1';
src/endpoints/google.js+32 -3
@@ -107,6 +107,25 @@ export async function getAccessToken(jwtToken) {
107107 return data.access_token;
108108}
109109
110+/**
111+ * Extracts the project ID from a Service Account JSON object.
112+ * @param {object} serviceAccount Service account JSON object
113+ * @returns {string} Project ID
114+ * @throws {Error} If project ID is not found in the service account
115+ */
116+export function getProjectIdFromServiceAccount(serviceAccount) {
117+ if (!serviceAccount || typeof serviceAccount !== 'object') {
118+ throw new Error('Invalid service account object');
119+ }
120+
121+ const projectId = serviceAccount.project_id;
122+ if (!projectId || typeof projectId !== 'string') {
123+ throw new Error('Project ID not found in service account JSON');
124+ }
125+
126+ return projectId;
127+}
128+
110129export const router = express.Router();
111130
112131router.post('/caption-image', async (request, response) => {
@@ -133,9 +152,19 @@ router.post('/caption-image', async (request, response) => {
133152 url = `${apiUrl.origin}/v1/publishers/google/models/${model}:generateContent?key=${keyParam}`;
134153 } else if (authType === 'full') {
135154 // Full mode: use project-specific URL with Authorization header
136- const projectId = readSecret(request.user.directories, SECRET_KEYS.VERTEXAI_PROJECT_ID);
155+ // Get project ID from Service Account JSON
137- if (!projectId) {
156+ const serviceAccountJson = readSecret(request.user.directories, SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT);
138- console.warn('Vertex AI project ID is missing.');
157+ if (!serviceAccountJson) {
158+ console.warn('Vertex AI Service Account JSON is missing.');
159+ return response.status(400).send({ error: true });
160+ }
161+
162+ let projectId;
163+ try {
164+ const serviceAccount = JSON.parse(serviceAccountJson);
165+ projectId = getProjectIdFromServiceAccount(serviceAccount);
166+ } catch (error) {
167+ console.error('Failed to extract project ID from Service Account JSON:', error);
139168 return response.status(400).send({ error: true });
140169 }
141170 const region = request.body.vertexai_region || 'us-central1';
src/endpoints/secrets.js+0 -1
@@ -54,7 +54,6 @@ export const SECRET_KEYS = {
5454 DEEPSEEK: 'api_key_deepseek',
5555 SERPER: 'api_key_serper',
5656 XAI: 'api_key_xai',
57- VERTEXAI_PROJECT_ID: 'vertexai_project_id',
5857 VERTEXAI_SERVICE_ACCOUNT: 'vertexai_service_account_json',
5958};
6059