Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vscode): add copilot chat participant #2393

Merged
merged 2 commits into from
Feb 7, 2025
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 .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@

/apps/generate-ui-v2/ @MaxKless

/libs/vscode/copilot/ @MaxKless

/libs/vscode/nvm-tip/ @MaxKless

/libs/shared/schema/ @MaxKless
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,5 @@ apps/vscode-e2e/.screenshots
.nx/powerpack
**/cypress/downloads
migrations.json

test-output
25 changes: 24 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
"version": "0.2.0",
"configurations": [
{
"name": "Launch Extension",
"args": [
"--disable-extensions",
"--extensionDevelopmentPath=${workspaceFolder}/dist/apps/vscode"
],
"name": "Launch Extension",
"outFiles": [
"${workspaceFolder}/dist/apps/vscode",
"${workspaceFolder}/dist/apps/generate-ui-v2",
Expand All @@ -28,6 +28,29 @@
"!**/node_modules/**"
]
},
{
"name": "Launch Extension (with other extensions)",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/dist/apps/vscode"
],
"outFiles": [
"${workspaceFolder}/dist/apps/vscode",
"${workspaceFolder}/dist/apps/generate-ui-v2",
"${workspaceFolder}/node_modules"
],
"request": "launch",
"skipFiles": [
"<node_internals>/**",
"/Applications/Visual Studio Code.app/**"
],
"type": "extensionHost",
"enableTurboSourcemaps": true,
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
]
},

{
"type": "node",
"request": "attach",
Expand Down
30 changes: 30 additions & 0 deletions apps/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,30 @@
}
]
},
"chatParticipants": [
{
"id": "nx-console.nx",
"fullName": "Nx",
"name": "nx",
"description": "Get insights into your Nx workspace",
"isSticky": true,
"disambiguation": [
{
"category": "nx",
"description": "The user wants to include context-aware insights using their Nx monorepo.",
"examples": [
"Tell me about my nx workspace",
"What libraries exist in my workspace?",
"Find a core components library and create a new Dialog component.",
"Remove the deprecated properties from the Auth API and update all clients.",
"What is the impact of merging 'ui-common' and 'ui-core'?",
"Compare the retry logic in all data access libraries for the shop app",
"Show me all the core projects"
]
}
]
}
],
"commands": [
{
"command": "nxConsole.refreshWorkspace",
Expand Down Expand Up @@ -765,6 +789,12 @@
],
"default": "all",
"description": "Controls which notifications are shown for Nx Cloud."
},
"nxConsole.enableNxCopilotFeatures": {
"type": "boolean",
"default": false,
"scope": "machine",
"description": "Enable Nx-specific copilot features (Experimental)."
}
}
},
Expand Down
Binary file added apps/vscode/src/assets/nx.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion apps/vscode/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { initNxConversion } from '@nx-console/vscode-nx-conversion';
import { initHelpAndFeedbackView } from '@nx-console/vscode-nx-help-and-feedback-view';
import { initVscodeProjectGraph } from '@nx-console/vscode-project-graph';
import { initTypeScriptServerPlugin } from '@nx-console/vscode-typescript-plugin';
import { initCopilot } from '@nx-console/vscode-copilot';

import {
NxWorkspaceRefreshNotification,
Expand Down Expand Up @@ -253,6 +254,7 @@ async function setWorkspace(workspacePath: string) {
getNxlsClient().start(workspacePath);

tasks.registerTaskProvider('nx', CliTaskProvider.instance);
initOutputChannels(context);
initTasks(context);
registerVscodeAddDependency(context);

Expand All @@ -266,7 +268,7 @@ async function setWorkspace(workspacePath: string) {
initVscodeProjectDetails(context);
initVscodeProjectGraph(context);
initErrorDiagnostics(context);
initOutputChannels(context);
initCopilot(context);

nxProjectsTreeProvider = initNxProjectView(context);

Expand Down
3 changes: 3 additions & 0 deletions apps/vscode/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
{
"path": "../../libs/language-server/types/tsconfig.lib.json"
},
{
"path": "../../libs/vscode/copilot/tsconfig.lib.json"
},
{
"path": "../../libs/vscode/typescript-plugin/tsconfig.lib.json"
},
Expand Down
3 changes: 3 additions & 0 deletions apps/vscode/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
{
"path": "../../libs/language-server/types"
},
{
"path": "../../libs/vscode/copilot"
},
{
"path": "../../libs/vscode/typescript-plugin"
},
Expand Down
2 changes: 2 additions & 0 deletions libs/vscode/configuration/src/lib/configuration-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const GLOBAL_CONFIG_KEYS = [
'showNodeVersionOnStartup',
'nxWorkspacePath',
'nxCloudNotifications',
'enableNxCopilotFeatures',
] as const;

export type GlobalConfig = {
Expand All @@ -32,6 +33,7 @@ export type GlobalConfig = {
showNodeVersionOnStartup: boolean;
nxWorkspacePath: string;
nxCloudNotifications: 'all' | 'errors' | 'none';
enableNxCopilotFeatures: boolean;
};

/**
Expand Down
18 changes: 18 additions & 0 deletions libs/vscode/copilot/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
22 changes: 22 additions & 0 deletions libs/vscode/copilot/.spec.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"jsc": {
"target": "es2017",
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"decoratorMetadata": true,
"legacyDecorator": true
},
"keepClassNames": true,
"externalHelpers": true,
"loose": true
},
"module": {
"type": "es6"
},
"sourceMaps": true,
"exclude": []
}
7 changes: 7 additions & 0 deletions libs/vscode/copilot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# vscode-copilot

This library was generated with [Nx](https://nx.dev).

## Running unit tests

Run `nx test vscode-copilot` to execute the unit tests via [Jest](https://jestjs.io).
22 changes: 22 additions & 0 deletions libs/vscode/copilot/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable */
import { readFileSync } from 'fs';

// Reading the SWC compilation config for the spec files
const swcJestConfig = JSON.parse(
readFileSync(`${__dirname}/.spec.swcrc`, 'utf-8')
);

// Disable .swcrc look-up by SWC core because we're passing in swcJestConfig ourselves
swcJestConfig.swcrc = false;

export default {
displayName: 'vscode-copilot',
preset: '../../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['@swc/jest', swcJestConfig],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: 'test-output/jest/coverage',
passWithNoTests: true,
};
15 changes: 15 additions & 0 deletions libs/vscode/copilot/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "@nx-console/vscode-copilot",
"version": "0.0.1",
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts",
"nx": {
"name": "vscode-copilot",
"sourceRoot": "libs/vscode/copilot/src",
"projectType": "library",
"tags": [
"type:vscode"
]
}
}
1 change: 1 addition & 0 deletions libs/vscode/copilot/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/init-copilot.js';
131 changes: 131 additions & 0 deletions libs/vscode/copilot/src/lib/init-copilot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import {
CancellationToken,
ChatContext,
ChatRequest,
ChatRequestHandler,
ChatResponseStream,
ExtensionContext,
LanguageModelChatMessage,
Uri,
chat,
commands,
Command,
} from 'vscode';
import { getNxWorkspace } from '@nx-console/vscode-nx-workspace';
import { BASE_PROMPT, GENERATE_PROMPT } from './prompt.js';
import type { TargetConfiguration } from 'nx/src/devkit-exports.js';
import { GlobalConfigurationStore } from '@nx-console/vscode-configuration';

const OPEN_COPILOT_SETTING_COMMAND = 'nxConsole.openCopilotSettings';

export function initCopilot(context: ExtensionContext) {
const nxParticipant = chat.createChatParticipant('nx-console.nx', handler);
nxParticipant.iconPath = Uri.joinPath(
context.extensionUri,
'assets',
'nx.png'
);
}

const handler: ChatRequestHandler = async (
request: ChatRequest,
context: ChatContext,
stream: ChatResponseStream,
token: CancellationToken
) => {
const enableNxCopilotFeaturesSetting = GlobalConfigurationStore.instance.get(
'enableNxCopilotFeatures',
false
);

if (!enableNxCopilotFeaturesSetting) {
stream.markdown(
'The @nx copilot chat participant is experimental. To use it, please enable it in the settings.'
);

stream.button({
title: 'Enable Nx Copilot',
command: 'workbench.action.openSettings',
arguments: ['nxConsole.enableNxCopilotFeatures'],
});
return;
}
const prompt = BASE_PROMPT;

stream.progress('Retrieving workspace information...');

const projectGraph = await getPrunedProjectGraph();

// if (request.command === 'generate') {
// prompt = GENERATE_PROMPT;
// }

const messages = [LanguageModelChatMessage.User(prompt)];

messages.push(LanguageModelChatMessage.User(request.prompt));
messages.push(LanguageModelChatMessage.User(JSON.stringify(projectGraph)));

const chatResponse = await request.model.sendRequest(messages, {}, token);

for await (const fragment of chatResponse.text) {
stream.markdown(fragment);
}

return;
};

async function getPrunedProjectGraph() {
const nxWorkspace = await getNxWorkspace();
const projectGraph = nxWorkspace.projectGraph;
return {
nodes: Object.entries(projectGraph.nodes)
.map(([name, node]) => {
const prunedNode = {
type: node.type,
} as any;
if (node.data.metadata?.technologies) {
prunedNode.technologies = node.data.metadata.technologies;
}
if (node.data.targets) {
prunedNode.targets = Object.entries(node.data.targets)
.map(([key, target]) => {
const prunedTarget = {
executor: target.executor,
} as Partial<TargetConfiguration>;
if (target.command) {
prunedTarget.command = target.command;
}
if (target.options.commands) {
prunedTarget.command = target.options.commands;
}
if (target.configurations) {
prunedTarget.configurations = Object.keys(
target.configurations
);
}
return [key, prunedTarget] as const;
})
.reduce((acc, [key, target]) => {
acc[key] = target;
return acc;
}, {});
}

return [name, prunedNode] as const;
})
.reduce((acc, [name, node]) => {
acc[name] = node;
return acc;
}, {}),
dependencies: Object.entries(projectGraph.dependencies)
.filter(([key]) => !key.startsWith('npm:'))
.map(
([key, deps]) =>
[key, deps.filter((dep) => !dep.target.startsWith('npm:'))] as const
)
.reduce((acc, [key, value]) => {
acc[key] = value;
return acc;
}, {}),
};
}
Loading
Loading