From b5a1f0ceb439a63c7b670ded240fa2cb24b1c921 Mon Sep 17 00:00:00 2001 From: Harold Shen Date: Mon, 8 Sep 2025 16:39:01 -0400 Subject: [PATCH 1/6] Make GCA installation optional --- firebase-vscode/package.json | 3 +- .../src/data-connect/ai-tools/firebase-mcp.ts | 62 +++++++++++++++++-- firebase-vscode/src/test/default_wdio.conf.ts | 5 +- .../integration/fishfood/gemini-install.ts | 54 ++++++++++++++++ .../src/test/utils/install-extensions.ts | 2 +- .../test/utils/page_objects/notifications.ts | 25 +++++++- .../src/test/utils/page_objects/sidebar.ts | 4 ++ 7 files changed, 143 insertions(+), 12 deletions(-) create mode 100644 firebase-vscode/src/test/integration/fishfood/gemini-install.ts diff --git a/firebase-vscode/package.json b/firebase-vscode/package.json index 9b99df68292..a891ded2b2f 100644 --- a/firebase-vscode/package.json +++ b/firebase-vscode/package.json @@ -11,7 +11,6 @@ "repository": "https://github.com/firebase/firebase-tools", "sideEffects": false, "extensionDependencies": [ - "google.geminicodeassist", "graphql.vscode-graphql-syntax" ], "categories": [ @@ -217,7 +216,7 @@ "test-compile": "npm run copyfiles && webpack --config src/test/webpack.test.js", "lint": "eslint src --ext ts", "test": "npm run test:unit && npm run test:e2e", - "install:extensions": "code-server --install-extension Google.geminicodeassist --extensions-dir ./prebuilt-extensions/ && code-server --install-extension graphql.vscode-graphql-syntax --extensions-dir ./prebuilt-extensions/", + "install:extensions": "code-server --install-extension graphql.vscode-graphql-syntax --extensions-dir ./prebuilt-extensions/", "pretest:e2e": "curl -fsSL https://code-server.dev/install.sh | sh -s -- --edge && npm run install:extensions", "pretest:unit": "npm run test-compile && tsc -p src/test/tsconfig.test.json", "test:unit": "node ./dist/test/firebase-vscode/src/test/runTest.js", diff --git a/firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts b/firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts index 55447bceb2b..3cfd262609e 100644 --- a/firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts +++ b/firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts @@ -1,16 +1,69 @@ -import { gemini, gemini as geminiToolModule } from "../../../../src/init/features/aitools/gemini"; +import { + gemini, + gemini as geminiToolModule, +} from "../../../../src/init/features/aitools/gemini"; import * as vscode from "vscode"; import { firebaseConfig } from "../config"; import { ExtensionBrokerImpl } from "../../extension-broker"; import { AnalyticsLogger, DATA_CONNECT_EVENT_NAME } from "../../analytics"; +const GEMINI_EXTENSION_ID = "google.geminicodeassist"; +async function ensureGeminiExtension(): Promise { + let geminiExtension = vscode.extensions.getExtension(GEMINI_EXTENSION_ID); -export function registerFirebaseMCP(broker: ExtensionBrokerImpl, analyticsLogger: AnalyticsLogger): vscode.Disposable { + if (geminiExtension) { + if (!geminiExtension.isActive) { + await geminiExtension.activate(); + } + return true; + } + + const selection = await vscode.window.showInformationMessage( + "The Firebase Assistant requires the Gemini Code Assist extension. Do you want to install it?", + "Yes", + "No", + ); + + if (selection !== "Yes") { + vscode.window.showWarningMessage( + "Cannot open Firebase Assistant without the Gemini Code Assist extension.", + ); + return false; + } + + const disposable = vscode.extensions.onDidChange(async () => { + geminiExtension = vscode.extensions.getExtension(GEMINI_EXTENSION_ID); + if (geminiExtension) { + writeToGeminiConfig(); + await vscode.commands.executeCommand("cloudcode.gemini.chatView.focus"); + await vscode.commands.executeCommand("geminicodeassist.agent.chat.new"); // opens a new chat when an old one exists; + disposable.dispose(); + } + }); + vscode.commands.executeCommand( + "workbench.extensions.installExtension", + GEMINI_EXTENSION_ID, + ); + + return false; +} + +export function registerFirebaseMCP( + broker: ExtensionBrokerImpl, + analyticsLogger: AnalyticsLogger, +): vscode.Disposable { const geminiActivateSub = broker.on("firebase.activate.gemini", async () => { analyticsLogger.logger.logUsage( DATA_CONNECT_EVENT_NAME.TRY_FIREBASE_AGENT_CLICKED, ); + + const geminiReady = await ensureGeminiExtension(); + + if (!geminiReady) { + return; + } + writeToGeminiConfig(); await vscode.commands.executeCommand("cloudcode.gemini.chatView.focus"); await vscode.commands.executeCommand("geminicodeassist.agent.chat.new"); // opens a new chat when an old one exists; @@ -32,7 +85,6 @@ export function registerFirebaseMCP(broker: ExtensionBrokerImpl, analyticsLogger // Writes the Firebase MCP server to the gemini code assist config file export function writeToGeminiConfig() { - const config = firebaseConfig.value?.tryReadValue; if (!config) { vscode.window.showErrorMessage("Could not read firebase.json"); @@ -40,5 +92,7 @@ export function writeToGeminiConfig() { return; } - geminiToolModule.configure(config, config.projectDir, [/** TODO: Create "dataconnect" .md file */]); + geminiToolModule.configure(config, config.projectDir, [ + /** TODO: Create "dataconnect" .md file */ + ]); } diff --git a/firebase-vscode/src/test/default_wdio.conf.ts b/firebase-vscode/src/test/default_wdio.conf.ts index db9779a0777..f736d15bdfa 100644 --- a/firebase-vscode/src/test/default_wdio.conf.ts +++ b/firebase-vscode/src/test/default_wdio.conf.ts @@ -42,10 +42,7 @@ export const config: WebdriverIO.Config = { logLevel: "debug", beforeTest: async function () { - await browser.pause(3000); // give some time for extension dependencies to load their README page - await browser.executeWorkbench((vscode) => { - vscode.commands.executeCommand("workbench.action.closeAllEditors"); // close GCA home page - }); + await browser.pause(1000); // give some time for extension dependency to load }, afterTest: async function (test) { diff --git a/firebase-vscode/src/test/integration/fishfood/gemini-install.ts b/firebase-vscode/src/test/integration/fishfood/gemini-install.ts new file mode 100644 index 00000000000..07dd2b847f9 --- /dev/null +++ b/firebase-vscode/src/test/integration/fishfood/gemini-install.ts @@ -0,0 +1,54 @@ +import { browser, expect } from "@wdio/globals"; +import { firebaseSuite, firebaseTest } from "../../utils/test_hooks"; +import { FirebaseCommands } from "../../utils/page_objects/commands"; +import { Workbench, Notification } from "wdio-vscode-service"; +import { Notifications } from "../../utils/page_objects/notifications"; +import { FirebaseSidebar } from "../../utils/page_objects/sidebar"; + +firebaseSuite("Gemini Install", async function () { + firebaseTest( + "should prompt to install Gemini and open chat view", + async function () { + const workbench = await browser.getWorkbench(); + + const sidebar = new FirebaseSidebar(workbench); + await sidebar.openExtensionSidebar(); + + await sidebar.runInStudioContext(async (studio) => { + await studio.geminiButton.waitForDisplayed(); + await studio.geminiButton.click(); + }); + + const notificationUtil = new Notifications(workbench); + const installNotification = + await notificationUtil.getGeminiInstallNotification(); + expect(installNotification).toExist(); + + // Click "Yes" + await notificationUtil.clickYesFromGeminiInstallNotification( + installNotification!, // verified in expect statement above, + ); + + // Wait for the Gemini view to appear in the activity bar + const geminiView = await workbench + .getActivityBar() + .getViewControl("Gemini Code Assist"); + await geminiView?.wait(30000); + + // Verify that the Gemini chat view is focused + const chatView = await workbench.getEditorView().webView$; + await chatView.waitForExist({ timeout: 50000 }); + const chatViewTitle = await chatView.getTitle(); + expect(chatViewTitle).toBe( + "[Extension Development Host] Gemini Code Assist - Welcome — fishfood", + ); + + await browser.executeWorkbench((vscode) => { + vscode.commands.executeCommand( + "workbench.extensions.uninstallExtension", + "google.geminicodeassist", + ); + }); + }, + ); +}); diff --git a/firebase-vscode/src/test/utils/install-extensions.ts b/firebase-vscode/src/test/utils/install-extensions.ts index 807bce7f073..b178f505952 100644 --- a/firebase-vscode/src/test/utils/install-extensions.ts +++ b/firebase-vscode/src/test/utils/install-extensions.ts @@ -2,7 +2,7 @@ import { execSync } from "child_process"; async function installExtensions() { // List of extensions to install - const extensions: string[] = ["graphql.vscode-graphql-syntax", "google.geminicodeassist"]; + const extensions: string[] = ["graphql.vscode-graphql-syntax"]; // Install each extension extensions.forEach((extension) => { diff --git a/firebase-vscode/src/test/utils/page_objects/notifications.ts b/firebase-vscode/src/test/utils/page_objects/notifications.ts index ba54ee84f72..3f9bdde081c 100644 --- a/firebase-vscode/src/test/utils/page_objects/notifications.ts +++ b/firebase-vscode/src/test/utils/page_objects/notifications.ts @@ -33,9 +33,32 @@ export class Notifications { async editVariablesFromNotification(notification: Notification) { // takeAction doesn't work in wdio vscode - const editButton = await notification.elem.$(".monaco-button=Edit variables"); + const editButton = await notification.elem.$( + ".monaco-button=Edit variables", + ); if (editButton) { await editButton.click(); } } + + async getGeminiInstallNotification() { + const notifications = await this.workbench.getNotifications(); + return notifications.find(async (n) => { + const message = await n.getMessage(); + console.log("NOTIF MESSAGE: ", message); + return message.includes( + "The Firebase Assistant requires the Gemini Code Assist extension", + ); + }); + } + + async clickYesFromGeminiInstallNotification(notification: Notification) { + // takeAction doesn't work in wdio vscode + const yesButton = await notification.elem.$( + ".monaco-button=Yes", + ); + if (yesButton) { + await yesButton.click(); + } + } } diff --git a/firebase-vscode/src/test/utils/page_objects/sidebar.ts b/firebase-vscode/src/test/utils/page_objects/sidebar.ts index 77e52678ea8..6cc482be786 100644 --- a/firebase-vscode/src/test/utils/page_objects/sidebar.ts +++ b/firebase-vscode/src/test/utils/page_objects/sidebar.ts @@ -140,6 +140,10 @@ export class StudioView { get fdcDeployElement() { return $(`vscode-button=${TEXT.DEPLOY_FDC_ENABLED}`); } + + get geminiButton() { + return $("vscode-button=Build your schema and queries with AI"); + } } export class SchemaExplorerView { From c7152589a06ec4bac05153231218575379b55d24 Mon Sep 17 00:00:00 2001 From: Harold Shen Date: Mon, 8 Sep 2025 16:44:28 -0400 Subject: [PATCH 2/6] remove console log --- firebase-vscode/src/test/utils/page_objects/notifications.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/firebase-vscode/src/test/utils/page_objects/notifications.ts b/firebase-vscode/src/test/utils/page_objects/notifications.ts index 3f9bdde081c..52bbc82cd16 100644 --- a/firebase-vscode/src/test/utils/page_objects/notifications.ts +++ b/firebase-vscode/src/test/utils/page_objects/notifications.ts @@ -45,7 +45,6 @@ export class Notifications { const notifications = await this.workbench.getNotifications(); return notifications.find(async (n) => { const message = await n.getMessage(); - console.log("NOTIF MESSAGE: ", message); return message.includes( "The Firebase Assistant requires the Gemini Code Assist extension", ); From 155c45e436c3f6e4df342235bd549edf9645a44e Mon Sep 17 00:00:00 2001 From: Harold Shen Date: Tue, 9 Sep 2025 12:58:06 -0400 Subject: [PATCH 3/6] address gemini review --- .../src/data-connect/ai-tools/firebase-mcp.ts | 16 +++++++++------- .../test/integration/fishfood/gemini-install.ts | 3 ++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts b/firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts index 3cfd262609e..23038527397 100644 --- a/firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts +++ b/firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts @@ -35,9 +35,7 @@ async function ensureGeminiExtension(): Promise { const disposable = vscode.extensions.onDidChange(async () => { geminiExtension = vscode.extensions.getExtension(GEMINI_EXTENSION_ID); if (geminiExtension) { - writeToGeminiConfig(); - await vscode.commands.executeCommand("cloudcode.gemini.chatView.focus"); - await vscode.commands.executeCommand("geminicodeassist.agent.chat.new"); // opens a new chat when an old one exists; + openGeminiChat(); disposable.dispose(); } }); @@ -49,6 +47,13 @@ async function ensureGeminiExtension(): Promise { return false; } +// Writes MCP config, then opens up Gemini with a new chat +async function openGeminiChat() { + writeToGeminiConfig(); + await vscode.commands.executeCommand("cloudcode.gemini.chatView.focus"); + await vscode.commands.executeCommand("geminicodeassist.agent.chat.new"); +} + export function registerFirebaseMCP( broker: ExtensionBrokerImpl, analyticsLogger: AnalyticsLogger, @@ -63,10 +68,7 @@ export function registerFirebaseMCP( if (!geminiReady) { return; } - - writeToGeminiConfig(); - await vscode.commands.executeCommand("cloudcode.gemini.chatView.focus"); - await vscode.commands.executeCommand("geminicodeassist.agent.chat.new"); // opens a new chat when an old one exists; + openGeminiChat(); }); const mcpDocsSub = broker.on("docs.mcp.clicked", () => { diff --git a/firebase-vscode/src/test/integration/fishfood/gemini-install.ts b/firebase-vscode/src/test/integration/fishfood/gemini-install.ts index 07dd2b847f9..32d827f09a9 100644 --- a/firebase-vscode/src/test/integration/fishfood/gemini-install.ts +++ b/firebase-vscode/src/test/integration/fishfood/gemini-install.ts @@ -33,7 +33,8 @@ firebaseSuite("Gemini Install", async function () { const geminiView = await workbench .getActivityBar() .getViewControl("Gemini Code Assist"); - await geminiView?.wait(30000); + expect(geminiView).toExist(); + await geminiView!.wait(30000); // Verify that the Gemini chat view is focused const chatView = await workbench.getEditorView().webView$; From 67b51cda62c17f3357746ec14e0853d5dbc5e740 Mon Sep 17 00:00:00 2001 From: Harold Shen Date: Tue, 9 Sep 2025 12:59:01 -0400 Subject: [PATCH 4/6] changelog --- firebase-vscode/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/firebase-vscode/CHANGELOG.md b/firebase-vscode/CHANGELOG.md index 7b393ff3fee..f07612df80c 100644 --- a/firebase-vscode/CHANGELOG.md +++ b/firebase-vscode/CHANGELOG.md @@ -1,5 +1,7 @@ ## NEXT +- [Changed] Gemini Code Assist is now optionally installed when using the "Build with AI" feature + ## 1.7.0 - Update internal `firebase-tools` dependency to 14.15.2 From 2d62c96462638e5745438420caac0aaf11c5eaaa Mon Sep 17 00:00:00 2001 From: Harold Shen Date: Tue, 9 Sep 2025 12:59:51 -0400 Subject: [PATCH 5/6] await --- firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts b/firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts index 23038527397..b920c612c2e 100644 --- a/firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts +++ b/firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts @@ -35,7 +35,7 @@ async function ensureGeminiExtension(): Promise { const disposable = vscode.extensions.onDidChange(async () => { geminiExtension = vscode.extensions.getExtension(GEMINI_EXTENSION_ID); if (geminiExtension) { - openGeminiChat(); + await openGeminiChat(); disposable.dispose(); } }); @@ -68,7 +68,7 @@ export function registerFirebaseMCP( if (!geminiReady) { return; } - openGeminiChat(); + await openGeminiChat(); }); const mcpDocsSub = broker.on("docs.mcp.clicked", () => { From b31fde7dcc453dc2e8aa6e9ddd873b8d10960647 Mon Sep 17 00:00:00 2001 From: Harold Shen Date: Tue, 9 Sep 2025 14:00:12 -0400 Subject: [PATCH 6/6] fix test --- .../src/test/integration/fishfood/gemini-install.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/firebase-vscode/src/test/integration/fishfood/gemini-install.ts b/firebase-vscode/src/test/integration/fishfood/gemini-install.ts index 32d827f09a9..f74a310ec53 100644 --- a/firebase-vscode/src/test/integration/fishfood/gemini-install.ts +++ b/firebase-vscode/src/test/integration/fishfood/gemini-install.ts @@ -29,13 +29,6 @@ firebaseSuite("Gemini Install", async function () { installNotification!, // verified in expect statement above, ); - // Wait for the Gemini view to appear in the activity bar - const geminiView = await workbench - .getActivityBar() - .getViewControl("Gemini Code Assist"); - expect(geminiView).toExist(); - await geminiView!.wait(30000); - // Verify that the Gemini chat view is focused const chatView = await workbench.getEditorView().webView$; await chatView.waitForExist({ timeout: 50000 });