Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions firebase-vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 1 addition & 2 deletions firebase-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"repository": "https://github.com/firebase/firebase-tools",
"sideEffects": false,
"extensionDependencies": [
"google.geminicodeassist",
"graphql.vscode-graphql-syntax"
],
"categories": [
Expand Down Expand Up @@ -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",
Expand Down
70 changes: 63 additions & 7 deletions firebase-vscode/src/data-connect/ai-tools/firebase-mcp.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,74 @@
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<boolean> {
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) {
await openGeminiChat();
disposable.dispose();
}
});
vscode.commands.executeCommand(
"workbench.extensions.installExtension",
GEMINI_EXTENSION_ID,
);

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,
): vscode.Disposable {
const geminiActivateSub = broker.on("firebase.activate.gemini", async () => {
analyticsLogger.logger.logUsage(
DATA_CONNECT_EVENT_NAME.TRY_FIREBASE_AGENT_CLICKED,
);
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;

const geminiReady = await ensureGeminiExtension();

if (!geminiReady) {
return;
}
await openGeminiChat();
});

const mcpDocsSub = broker.on("docs.mcp.clicked", () => {
Expand All @@ -32,13 +87,14 @@ 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");
// TODO: Consider writing to HOME_DIR in case of this failure
return;
}

geminiToolModule.configure(config, config.projectDir, [/** TODO: Create "dataconnect" .md file */]);
geminiToolModule.configure(config, config.projectDir, [
/** TODO: Create "dataconnect" .md file */
]);
}
5 changes: 1 addition & 4 deletions firebase-vscode/src/test/default_wdio.conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
48 changes: 48 additions & 0 deletions firebase-vscode/src/test/integration/fishfood/gemini-install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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,
);

// 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",
);
});
},
);
});
2 changes: 1 addition & 1 deletion firebase-vscode/src/test/utils/install-extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
24 changes: 23 additions & 1 deletion firebase-vscode/src/test/utils/page_objects/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,31 @@ 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();
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();
}
}
}
4 changes: 4 additions & 0 deletions firebase-vscode/src/test/utils/page_objects/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading