From 226eb2a5ffe213a95381ec05a3c74f827adb5097 Mon Sep 17 00:00:00 2001 From: James Dakin <145860848+jd-pr@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:30:33 -0400 Subject: [PATCH] feat: make GitHub Copilot an optional dependency Fixes issue where extension/database silently fails to connect without active GitHub Copilot subscription. - Remove github.copilot-chat from extensionDependencies array - Add runtime detection for Copilot availability in extension.ts - Implement graceful fallback messages in chatRequestHandler.ts - Preserve all database management features for users without Copilot - Enable AI features only when Copilot is available This allows the extension to activate for all users while maintaining AI functionality for those with active Copilot subscriptions. --- package.json | 3 - src/extension.ts | 20 +++- src/utils/chatRequestHandler.ts | 203 +++++++++++++++++++------------- 3 files changed, 136 insertions(+), 90 deletions(-) diff --git a/package.json b/package.json index 52c9406..cbe72d6 100644 --- a/package.json +++ b/package.json @@ -63,9 +63,6 @@ "vscode-test": "^1.5.0" }, "activationEvents": [], - "extensionDependencies": [ - "github.copilot-chat" - ], "contributes": { "viewsContainers": { "activitybar": [ diff --git a/src/extension.ts b/src/extension.ts index 9f8aafb..2e5c361 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -24,10 +24,20 @@ export function activate(context: vscode.ExtensionContext) { supabase }); - //it's important to use an inline callback here due to scoping issues. - //setting the handler to pg.handle would not work as "this" would not - //be set right. - const participant = vscode.chat.createChatParticipant('supabase.clippy', createChatRequestHandler(supabase)); + // Check if GitHub Copilot Chat is available before registering chat participant + const copilotChatExtension = vscode.extensions.getExtension('github.copilot-chat'); + + if (copilotChatExtension) { + // Only register chat participant if Copilot Chat is available + try { + const participant = vscode.chat.createChatParticipant('supabase.clippy', createChatRequestHandler(supabase)); + context.subscriptions.push(participant); + } catch (error) { + console.log('Supabase: Chat features unavailable - GitHub Copilot Chat may not be active', error); + } + } else { + console.log('Supabase: Chat features disabled - GitHub Copilot Chat extension not found'); + } - context.subscriptions.push(participant, connectSupabaseView, databaseView); + context.subscriptions.push(connectSupabaseView, databaseView); } diff --git a/src/utils/chatRequestHandler.ts b/src/utils/chatRequestHandler.ts index 05f26f2..68efd07 100644 --- a/src/utils/chatRequestHandler.ts +++ b/src/utils/chatRequestHandler.ts @@ -56,70 +56,89 @@ export const createChatRequestHandler = (supabase: SupabaseApi): vscode.ChatRequ return { metadata: { command: 'show' } }; } else if (request.command === 'migration') { try { - const [model] = await vscode.lm.selectChatModels(MODEL_SELECTOR); - if (model) { - try { - // Create new migration file (execute supabase migration new copilot). - const migrationName = `copilot`; // TODO: generate from prompt. - const cmd = `${Commands.NEW_MIGRATION} ${migrationName}`; - executeCommand(cmd); - - // Get schema context. - const schema = await supabase.getSchema(); - // TODO: figure out how to modify the prompt to only generate valid SQL. Currently Copilot generates a markdown response. - const messages = [ - vscode.LanguageModelChatMessage.User( - `You're a friendly PostgreSQL assistant called Supabase Clippy, helping with writing database migrations.` - ), - vscode.LanguageModelChatMessage.User( - `Please provide help with ${prompt}. The reference database schema for question is ${schema}. IMPORTANT: Be sure you only use the tables and columns from this schema in your answer!` - ) - ]; - const chatResponse = await model.sendRequest(messages, {}, token); - let responseText = ''; - - for await (const fragment of chatResponse.text) { - stream.markdown(fragment); - responseText += fragment; - } + // Check if language models are available + let models; + try { + models = await vscode.lm.selectChatModels(MODEL_SELECTOR); + } catch (err) { + stream.markdown('⚠️ AI-powered migration generation requires an active GitHub Copilot subscription.\n\n' + + 'You can still create migrations manually using:\n' + + '- The "Create new migration" button in the Database view\n' + + '- Running `supabase migration new` in the terminal'); + return { metadata: { command: 'migration' } }; + } - // Open migration file in editor. - let filePath = await getFilePath(); - while (!(await isFileEmpty(filePath))) { - await new Promise((resolve) => setTimeout(resolve, 500)); - filePath = await getFilePath(); - } + if (!models || models.length === 0) { + stream.markdown('⚠️ GitHub Copilot language models are not available.\n\n' + + 'Please ensure you have:\n' + + '1. An active GitHub Copilot subscription\n' + + '2. Signed in to GitHub in VS Code\n\n' + + 'You can still create migrations manually.'); + return { metadata: { command: 'migration' } }; + } - const openPath = vscode.Uri.file(filePath); - const doc = await vscode.workspace.openTextDocument(openPath); - await vscode.window.showTextDocument(doc); - const textEditor = vscode.window.activeTextEditor; - - // Extract SQL from markdown and write to migration file. - const sql = extractCode(responseText); - - if (textEditor) { - for await (const statement of sql) { - await textEditor.edit((edit) => { - const lastLine = textEditor.document.lineAt(textEditor.document.lineCount - 1); - const position = new vscode.Position(lastLine.lineNumber, lastLine.text.length); - edit.insert(position, statement); - }); - } - await textEditor.document.save(); - } + const [model] = models; + try { + // Create new migration file (execute supabase migration new copilot). + const migrationName = `copilot`; // TODO: generate from prompt. + const cmd = `${Commands.NEW_MIGRATION} ${migrationName}`; + executeCommand(cmd); + + // Get schema context. + const schema = await supabase.getSchema(); + // TODO: figure out how to modify the prompt to only generate valid SQL. Currently Copilot generates a markdown response. + const messages = [ + vscode.LanguageModelChatMessage.User( + `You're a friendly PostgreSQL assistant called Supabase Clippy, helping with writing database migrations.` + ), + vscode.LanguageModelChatMessage.User( + `Please provide help with ${prompt}. The reference database schema for question is ${schema}. IMPORTANT: Be sure you only use the tables and columns from this schema in your answer!` + ) + ]; + const chatResponse = await model.sendRequest(messages, {}, token); + let responseText = ''; + + for await (const fragment of chatResponse.text) { + stream.markdown(fragment); + responseText += fragment; + } - // Render button to apply migration. - stream.markdown('\n\nMake sure to review the migration file before applying it!'); - stream.button({ - command: 'databaseProvider.db_push', - title: vscode.l10n.t('Apply migration.') - }); - } catch (err) { - stream.markdown( - "🤔 I can't find the schema for the database. Please check that `supabase start` is running." - ); + // Open migration file in editor. + let filePath = await getFilePath(); + while (!(await isFileEmpty(filePath))) { + await new Promise((resolve) => setTimeout(resolve, 500)); + filePath = await getFilePath(); } + + const openPath = vscode.Uri.file(filePath); + const doc = await vscode.workspace.openTextDocument(openPath); + await vscode.window.showTextDocument(doc); + const textEditor = vscode.window.activeTextEditor; + + // Extract SQL from markdown and write to migration file. + const sql = extractCode(responseText); + + if (textEditor) { + for await (const statement of sql) { + await textEditor.edit((edit) => { + const lastLine = textEditor.document.lineAt(textEditor.document.lineCount - 1); + const position = new vscode.Position(lastLine.lineNumber, lastLine.text.length); + edit.insert(position, statement); + }); + } + await textEditor.document.save(); + } + + // Render button to apply migration. + stream.markdown('\n\nMake sure to review the migration file before applying it!'); + stream.button({ + command: 'databaseProvider.db_push', + title: vscode.l10n.t('Apply migration.') + }); + } catch (err) { + stream.markdown( + "🤔 I can't find the schema for the database. Please check that `supabase start` is running." + ); } } catch (err) { handleError(err, stream); @@ -128,29 +147,49 @@ export const createChatRequestHandler = (supabase: SupabaseApi): vscode.ChatRequ return { metadata: { command: 'migration' } }; } else { try { - const [model] = await vscode.lm.selectChatModels(MODEL_SELECTOR); - if (model) { - try { - const schema = await supabase.getSchema(); - - const messages = [ - vscode.LanguageModelChatMessage.User( - `You're a friendly PostgreSQL assistant called Supabase Clippy, helping with writing SQL.` - ), - vscode.LanguageModelChatMessage.User( - `Please provide help with ${prompt}. The reference database schema for this question is ${schema}. IMPORTANT: Be sure you only use the tables and columns from this schema in your answer.` - ) - ]; - - const chatResponse = await model.sendRequest(messages, {}, token); - for await (const fragment of chatResponse.text) { - stream.markdown(fragment); - } - } catch (err) { - stream.markdown( - "🤔 I can't find the schema for the database. Please check that `supabase start` is running." - ); + // Check if language models are available + let models; + try { + models = await vscode.lm.selectChatModels(MODEL_SELECTOR); + } catch (err) { + stream.markdown('⚠️ AI features require an active GitHub Copilot subscription.\n\n' + + 'However, you can still use all database management features:\n' + + '- View and explore tables\n' + + '- Create migrations manually\n' + + '- Run database commands\n' + + '- Generate TypeScript types'); + return { metadata: { command: '' } }; + } + + if (!models || models.length === 0) { + stream.markdown('⚠️ GitHub Copilot language models are not available.\n\n' + + 'Please ensure you have:\n' + + '1. An active GitHub Copilot subscription\n' + + '2. Signed in to GitHub in VS Code'); + return { metadata: { command: '' } }; + } + + const [model] = models; + try { + const schema = await supabase.getSchema(); + + const messages = [ + vscode.LanguageModelChatMessage.User( + `You're a friendly PostgreSQL assistant called Supabase Clippy, helping with writing SQL.` + ), + vscode.LanguageModelChatMessage.User( + `Please provide help with ${prompt}. The reference database schema for this question is ${schema}. IMPORTANT: Be sure you only use the tables and columns from this schema in your answer.` + ) + ]; + + const chatResponse = await model.sendRequest(messages, {}, token); + for await (const fragment of chatResponse.text) { + stream.markdown(fragment); } + } catch (err) { + stream.markdown( + "🤔 I can't find the schema for the database. Please check that `supabase start` is running." + ); } } catch (err) { handleError(err, stream);