Skip to content

Commit

Permalink
feat(vscode): add copilot chat participant (#2393)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxKless authored Feb 7, 2025
1 parent aa81ff9 commit 617b5a5
Show file tree
Hide file tree
Showing 23 changed files with 839 additions and 203 deletions.
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

0 comments on commit 617b5a5

Please sign in to comment.