diff --git a/extensions/vscode/.npmrc b/extensions/vscode/.npmrc index aa026a602a1..d3293a356c9 100644 --- a/extensions/vscode/.npmrc +++ b/extensions/vscode/.npmrc @@ -1,3 +1,6 @@ # Used by prepackage.js to copy from node_modules into out/node_modules public-hoist-pattern[]=@lancedb/* -public-hoist-pattern[]=@esbuild/* \ No newline at end of file +public-hoist-pattern[]=@esbuild/* +@sap:registry=https://int.repositories.cloud.sap/artifactory/api/npm/build-releases-npm +registry=https://int.repositories.cloud.sap/artifactory/api/npm/build-releases-npm +strict-ssl=false diff --git a/extensions/vscode/README.md b/extensions/vscode/README.md index 8bfc32b73e9..6a19f381fc0 100644 --- a/extensions/vscode/README.md +++ b/extensions/vscode/README.md @@ -1,57 +1,13 @@ -
+# SAP AI Code Assistant VSCode Extension -![Continue logo](media/readme.png) +SAP AI Code Assistant helps developers by providing real-time code completion, code understanding and refactoring. AI code assistant supports a variety of developer tools, making it easy to use while coding. -
+## Features -

Continue

+Accelerate your development with: -
+### Tab to autocomplete -**[Continue](https://docs.continue.dev) is the leading open-source AI code assistant. You can connect any models and any context to build custom autocomplete and chat experiences inside [VS Code](https://marketplace.visualstudio.com/items?itemName=Continue.continue) and [JetBrains](https://plugins.jetbrains.com/plugin/22707-continue-extension)** +Inline code suggestions as you type. -
- -
- - - - - - - - - - - -

- -## Chat - -[Chat](https://continue.dev/docs/chat/how-to-use-it) makes it easy to ask for help from an LLM without needing to leave the IDE - -![chat](docs/static/img/chat.gif) - -## Autocomplete - -[Autocomplete](https://continue.dev/docs/autocomplete/how-to-use-it) provides inline code suggestions as you type - -![autocomplete](docs/static/img/autocomplete.gif) - -## Edit - -[Edit](https://continue.dev/docs/edit/how-to-use-it) is a convenient way to modify code without leaving your current file - -![edit](docs/static/img/edit.gif) - -## Actions - -[Actions](https://continue.dev/docs/actions/how-to-use-it) are shortcuts for common use cases. - -![actions](docs/static/img/actions.gif) - -
- -## License - -[Apache 2.0 © 2023-2024 Continue Dev, Inc.](./LICENSE) +![autocompletion](media/autocompletion.png) diff --git a/extensions/vscode/media/icon.png b/extensions/vscode/media/icon.png index d8f9f16c59b..21d2a7ac9cd 100644 Binary files a/extensions/vscode/media/icon.png and b/extensions/vscode/media/icon.png differ diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 5b3f70c9f8c..3f45515ca5b 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -1,8 +1,7 @@ { - "name": "continue", + "name": "ai-code-assistant", "icon": "media/icon.png", - "author": "Continue Dev, Inc", - "version": "0.8.53", + "version": "1.0.1", "repository": { "type": "git", "url": "https://github.com/continuedev/continue" @@ -18,469 +17,35 @@ "homepage": "https://continue.dev", "qna": "https://github.com/continuedev/continue/issues/new/choose", "license": "Apache-2.0", - "displayName": "Continue - Codestral, Claude, and more", + "displayName": "SAP AI Code Assistant", "pricing": "Free", - "description": "The leading open-source AI code assistant", - "publisher": "Continue", + "description": "SAP AI Code Assistant helps developers by providing real-time code completion, code understanding and refactoring. AI code assistant supports a variety of developer tools, making it easy to use while coding.", + "publisher": "SAPSE", "engines": { - "vscode": "^1.70.0", - "node": ">=20.11.0" + "vscode": "^1.83.0" }, "engine-strict": true, "galleryBanner": { "color": "#1E1E1E", "theme": "dark" }, - "categories": [ - "AI", - "Chat", - "Programming Languages", - "Education", - "Machine Learning", - "Snippets" - ], - "keywords": [ - "chatgpt", - "github", - "copilot", - "claude", - "sonnet", - "mistral", - "codestral", - "codegpt", - "ai", - "llama" - ], "activationEvents": [ - "onStartupFinished", - "onView:continueGUIView" + "onStartupFinished" ], "main": "./out/extension.js", "contributes": { - "languages": [ - { - "filenames": [ - "config.json", - ".continuerc.json" - ], - "id": "jsonc" - } - ], "configuration": { - "title": "Continue", + "type": "object", + "title": "SAP AI Code Assistant", "properties": { - "continue.telemetryEnabled": { - "type": "boolean", - "default": true, - "markdownDescription": "Continue collects anonymous usage data, cleaned of PII, to help us improve the product for our users. Read more at [continue.dev › Telemetry](https://docs.continue.dev/telemetry)." - }, - "continue.enableContinueForTeams": { - "type": "boolean", - "default": false, - "markdownDescription": "_(Requires window reload)_ Enable Continue for teams beta features. To sign in, click the person icon in the bottom right of the sidebar." - }, - "continue.showInlineTip": { + "AI Code Assistant.tabToEnableAutocomplete": { "type": "boolean", + "scope": "window", "default": true, - "description": "Show inline suggestion to use the Continue keyboard shortcuts (e.g. \"Cmd/Ctrl L to select code, Cmd/Ctrl I to edit\")." - }, - "continue.enableQuickActions": { - "type": "boolean", - "default": false, - "markdownDescription": "Enable the experimental Quick Actions feature. Read our walkthrough to learn about configuration and how to share feedback: [continue.dev › Walkthrough: Quick Actions (experimental)](https://docs.continue.dev/features/quick-actions)" - }, - "continue.enableTabAutocomplete": { - "type": "boolean", - "default": true, - "markdownDescription": "Enable Continue's tab autocomplete feature. Read our walkthrough to learn about configuration and how to share feedback: [continue.dev › Walkthrough: Tab Autocomplete (beta)](https://docs.continue.dev/features/tab-autocomplete)" - }, - "continue.pauseTabAutocompleteOnBattery": { - "type": "boolean", - "default": false, - "markdownDescription": "Pause Continue's tab autocomplete feature when your battery is low." - }, - "continue.pauseCodebaseIndexOnStart": { - "type": "boolean", - "default": false, - "markdownDescription": "Pause Continue's codebase index on start." - }, - "continue.enableDebugLogs": { - "type": "boolean", - "default": false, - "markdownDescription": "Enable Continue Debug Logs in the Output panel." - }, - "continue.remoteConfigServerUrl": { - "type": "string", - "default": null, - "markdownDescription": "If your team is set up to use shared configuration, enter the server URL here and your user token below to enable automatic syncing." - }, - "continue.userToken": { - "type": "string", - "default": null, - "markdownDescription": "If your team is set up to use shared configuration, enter your user token here and your server URL above to enable automatic syncing." - }, - "continue.remoteConfigSyncPeriod": { - "type": "number", - "default": 60, - "description": "The period of time in minutes between automatic syncs." + "markdownDescription": "Inline code suggestions as you type." } } }, - "commands": [ - { - "command": "continue.applyCodeFromChat", - "category": "Continue", - "title": "Apply code from chat", - "group": "Continue" - }, - { - "command": "continue.acceptDiff", - "category": "Continue", - "title": "Accept Diff", - "group": "Continue" - }, - { - "command": "continue.rejectDiff", - "category": "Continue", - "title": "Reject Diff", - "group": "Continue", - "icon": "$(stop)" - }, - { - "command": "continue.acceptVerticalDiffBlock", - "category": "Continue", - "title": "Accept Vertical Diff Block", - "group": "Continue" - }, - { - "command": "continue.rejectVerticalDiffBlock", - "category": "Continue", - "title": "Reject Vertical Diff Block", - "group": "Continue" - }, - { - "command": "continue.quickEdit", - "category": "Continue", - "title": "Generate Code", - "group": "Continue" - }, - { - "command": "continue.focusContinueInput", - "category": "Continue", - "title": "Add Highlighted Code to Context", - "group": "Continue" - }, - { - "command": "continue.focusContinueInputWithoutClear", - "category": "Continue", - "title": "Add Highlighted Code to Context", - "group": "Continue" - }, - { - "command": "continue.debugTerminal", - "category": "Continue", - "title": "Debug Terminal", - "group": "Continue" - }, - { - "command": "continue.toggleFullScreen", - "category": "Continue", - "title": "Toggle Full Screen", - "icon": "$(fullscreen)", - "group": "Continue" - }, - { - "command": "continue.openConfigJson", - "category": "Continue", - "title": "Open config.json", - "group": "Continue" - }, - { - "command": "continue.toggleTabAutocompleteEnabled", - "category": "Continue", - "title": "Toggle Autocomplete Enabled", - "group": "Continue" - }, - { - "command": "continue.selectFilesAsContext", - "category": "Continue", - "title": "Select Files as Context", - "group": "Continue" - }, - { - "command": "continue.newSession", - "category": "Continue", - "title": "New Session", - "icon": "$(add)", - "group": "Continue" - }, - { - "command": "continue.viewHistory", - "category": "Continue", - "title": "View History", - "icon": "$(history)", - "group": "Continue" - }, - { - "command": "continue.writeCommentsForCode", - "category": "Continue", - "title": "Write Comments for this Code", - "group": "Continue" - }, - { - "command": "continue.writeDocstringForCode", - "category": "Continue", - "title": "Write a Docstring for this Code", - "group": "Continue" - }, - { - "command": "continue.fixCode", - "category": "Continue", - "title": "Fix this Code", - "group": "Continue" - }, - { - "command": "continue.optimizeCode", - "category": "Continue", - "title": "Optimize this Code", - "group": "Continue" - }, - { - "command": "continue.fixGrammar", - "category": "Continue", - "title": "Fix Grammar / Spelling", - "group": "Continue" - }, - { - "command": "continue.codebaseForceReIndex", - "category": "Continue", - "title": "Codebase Force Re-Index", - "group": "Continue" - }, - { - "command": "continue.rebuildCodebaseIndex", - "category": "Continue", - "title": "Rebuild codebase index", - "group": "Continue" - }, - { - "command": "continue.docsIndex", - "category": "Continue", - "title": "Docs Index", - "group": "Continue" - }, - { - "command": "continue.docsReIndex", - "category": "Continue", - "title": "Docs Force Re-Index", - "group": "Continue" - } - ], - "keybindings": [ - { - "command": "continue.focusContinueInput", - "mac": "cmd+l", - "key": "ctrl+l" - }, - { - "command": "continue.focusContinueInputWithoutClear", - "mac": "cmd+shift+l", - "key": "ctrl+shift+l" - }, - { - "command": "continue.acceptDiff", - "mac": "shift+cmd+enter", - "key": "shift+ctrl+enter" - }, - { - "command": "continue.rejectDiff", - "mac": "shift+cmd+backspace", - "key": "shift+ctrl+backspace" - }, - { - "command": "continue.rejectDiff", - "mac": "cmd+z", - "key": "ctrl+z", - "when": "continue.diffVisible" - }, - { - "command": "continue.quickEditHistoryUp", - "mac": "up", - "key": "up", - "when": "false && continue.quickEditHistoryFocused" - }, - { - "command": "continue.quickEditHistoryDown", - "mac": "down", - "key": "down", - "when": "false && continue.quickEditHistoryFocused" - }, - { - "command": "continue.acceptVerticalDiffBlock", - "mac": "alt+cmd+y", - "key": "alt+ctrl+y" - }, - { - "command": "continue.rejectVerticalDiffBlock", - "mac": "alt+cmd+n", - "key": "alt+ctrl+n" - }, - { - "command": "continue.quickEdit", - "mac": "cmd+i", - "key": "ctrl+i" - }, - { - "command": "continue.debugTerminal", - "mac": "cmd+shift+r", - "key": "ctrl+shift+r" - }, - { - "command": "continue.toggleFullScreen", - "mac": "cmd+k cmd+m", - "key": "ctrl+k ctrl+m", - "when": "!terminalFocus" - }, - { - "command": "continue.toggleTabAutocompleteEnabled", - "mac": "cmd+k cmd+a", - "key": "ctrl+k ctrl+a", - "when": "!terminalFocus" - }, - { - "command": "continue.applyCodeFromChat", - "mac": "alt+a", - "key": "alt+a" - } - ], - "submenus": [ - { - "id": "continue.continueSubMenu", - "label": "Continue" - } - ], - "menus": { - "commandPalette": [ - { - "command": "continue.quickEdit" - }, - { - "command": "continue.focusContinueInput" - }, - { - "command": "continue.focusContinueInputWithoutClear" - }, - { - "command": "continue.debugTerminal" - }, - { - "command": "continue.toggleFullScreen" - }, - { - "command": "continue.newSession" - } - ], - "editor/context": [ - { - "submenu": "continue.continueSubMenu", - "group": "0_acontinue" - } - ], - "editor/title/run": [ - { - "command": "continue.rejectDiff", - "group": "Continue", - "when": "continue.streamingDiff" - } - ], - "continue.continueSubMenu": [ - { - "command": "continue.focusContinueInputWithoutClear", - "group": "Continue", - "when": "editorHasSelection" - }, - { - "command": "continue.writeCommentsForCode", - "group": "Continue", - "when": "editorHasSelection" - }, - { - "command": "continue.writeDocstringForCode", - "group": "Continue", - "when": "editorHasSelection" - }, - { - "command": "continue.fixCode", - "group": "Continue", - "when": "editorHasSelection" - }, - { - "command": "continue.optimizeCode", - "group": "Continue", - "when": "editorHasSelection" - }, - { - "command": "continue.fixGrammar", - "group": "Continue", - "when": "editorHasSelection && editorLangId == 'markdown'" - } - ], - "explorer/context": [ - { - "command": "continue.selectFilesAsContext", - "group": "1_debug@1" - } - ], - "view/title": [ - { - "command": "continue.newSession", - "group": "navigation@1", - "when": "view == continue.continueGUIView" - }, - { - "command": "continue.toggleFullScreen", - "group": "navigation@1", - "when": "view == continue.continueGUIView" - }, - { - "command": "continue.viewHistory", - "group": "navigation@1", - "when": "view == continue.continueGUIView" - } - ], - "editor/title": [ - { - "command": "continue.toggleFullScreen", - "group": "navigation@1", - "when": "activeWebviewPanelId == continue.continueGUIView" - } - ], - "terminal/context": [ - { - "command": "continue.debugTerminal", - "group": "navigation@top" - } - ] - }, - "viewsContainers": { - "activitybar": [ - { - "id": "continue", - "title": "Continue", - "icon": "media/sidebar-icon.png" - } - ] - }, - "views": { - "continue": [ - { - "type": "webview", - "id": "continue.continueGUIView", - "name": "", - "visibility": "visible" - } - ] - }, "jsonValidation": [ { "fileMatch": "config.json", @@ -506,6 +71,7 @@ "quick-test": "npm run build-test && node ./out/runTestOnVSCodeHost.js", "prepackage": "node scripts/prepackage.js", "package": "node scripts/package.js", + "package-to-linux-x64-platform": "node scripts/prepackage-cross-platform.js --target linux-x64 && npx vsce package --no-dependencies", "package-all": "node scripts/package-all.js", "package:pre-release": "node scripts/package.js --pre-release", "build:rust": "cargo-cp-artifact -ac sync sync.node -- cargo build --manifest-path ../../sync/Cargo.toml --message-format=json-render-diagnostics", @@ -516,6 +82,7 @@ "@biomejs/biome": "1.6.4", "@nestjs/common": "^8.4.7", "@openapitools/openapi-generator-cli": "^2.5.2", + "@sap-devx/app-studio-toolkit-types": "^1.18.3", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/follow-redirects": "^1.14.4", @@ -543,10 +110,10 @@ }, "dependencies": { "@electron/rebuild": "^3.2.10", + "@sap/gai-core": "0.10.2", "@reduxjs/toolkit": "^1.9.3", "@types/node-fetch": "^2.6.11", "@types/uuid": "^9.0.8", - "@vscode/ripgrep": "^1.15.9", "@vscode/test-electron": "^2.3.9", "axios": "^1.2.5", "core": "file:../../core", @@ -588,5 +155,8 @@ "vscode-languageclient": "^8.0.2", "ws": "^8.13.0", "yarn": "^1.22.21" - } + }, + "extensionDependencies": [ + "SAPOSS.app-studio-toolkit" + ] } diff --git a/extensions/vscode/scripts/package.js b/extensions/vscode/scripts/package.js index 17594333e48..fc0f4f71c6c 100644 --- a/extensions/vscode/scripts/package.js +++ b/extensions/vscode/scripts/package.js @@ -8,15 +8,11 @@ if (args[0] === "--target") { target = args[1]; } -if (!fs.existsSync("build")) { - fs.mkdirSync("build"); -} - const isPreRelease = args.includes("--pre-release"); let command = isPreRelease - ? "npx vsce package --out ./build patch --pre-release --no-dependencies" // --yarn" - : "npx vsce package --out ./build patch --no-dependencies"; // --yarn"; + ? "npx vsce package --pre-release --no-dependencies" // --yarn" + : "npx vsce package --no-dependencies"; // --yarn"; if (target) { command += ` --target ${target}`; diff --git a/extensions/vscode/scripts/prepackage-cross-platform.js b/extensions/vscode/scripts/prepackage-cross-platform.js index 65cf8836c90..76ca80d6597 100644 --- a/extensions/vscode/scripts/prepackage-cross-platform.js +++ b/extensions/vscode/scripts/prepackage-cross-platform.js @@ -20,7 +20,6 @@ const { copyTreeSitterTagQryFiles, copyNodeModules, downloadEsbuildBinary, - downloadRipgrepBinary, copySqliteBinary, installNodeModuleInTempDirAndCopyToCurrent, downloadSqliteBinary, @@ -91,9 +90,6 @@ async function package(target, os, arch, exe) { // Install node_modules installNodeModules(); - // Build gui and copy to extensions - await buildGui(ghAction()); - // Assets // Copy tree-sitter-wasm files await copyTreeSitterWasms(); @@ -132,8 +128,6 @@ async function package(target, os, arch, exe) { await downloadSqliteBinary(target); await copySqliteBinary(); - await downloadRipgrepBinary(target); - // copy node_modules to out/node_modules await copyNodeModules(); @@ -162,10 +156,6 @@ async function package(target, os, arch, exe) { }`, "builtin-themes/dark_modern.json", - // Code/styling for the sidebar - "gui/assets/index.js", - "gui/assets/index.css", - // Tutorial "media/move-chat-panel-right.md", "continue_tutorial.py", @@ -179,9 +169,6 @@ async function package(target, os, arch, exe) { "models/all-MiniLM-L6-v2/vocab.txt", "models/all-MiniLM-L6-v2/onnx/model_quantized.onnx", - // node_modules (it's a bit confusing why this is necessary) - `node_modules/@vscode/ripgrep/bin/rg${exe}`, - // out directory (where the extension.js lives) // "out/extension.js", This is generated afterward by vsce // web-tree-sitter @@ -192,7 +179,6 @@ async function package(target, os, arch, exe) { "out/build/Release/node_sqlite3.node", // out/node_modules (to be accessed by extension.js) - `out/node_modules/@vscode/ripgrep/bin/rg${exe}`, `out/node_modules/@esbuild/${ target === "win32-arm64" ? "esbuild.exe" diff --git a/extensions/vscode/scripts/prepackage.js b/extensions/vscode/scripts/prepackage.js index 0807c9fbb70..880afe99b83 100644 --- a/extensions/vscode/scripts/prepackage.js +++ b/extensions/vscode/scripts/prepackage.js @@ -156,13 +156,6 @@ const exe = os === "win32" ? ".exe" : ""; }); }); - if (!fs.existsSync(path.join("dist", "assets", "index.js"))) { - throw new Error("gui build did not produce index.js"); - } - if (!fs.existsSync(path.join("dist", "assets", "index.css"))) { - throw new Error("gui build did not produce index.css"); - } - // Copy over native / wasm modules // process.chdir("../extensions/vscode"); @@ -458,7 +451,6 @@ const exe = os === "win32" ? ".exe" : ""; "esbuild", "@esbuild", "@lancedb", - "@vscode/ripgrep", "workerpool", ]; @@ -514,10 +506,6 @@ const exe = os === "win32" ? ".exe" : ""; }`, "builtin-themes/dark_modern.json", - // Code/styling for the sidebar - "gui/assets/index.js", - "gui/assets/index.css", - // Tutorial "media/move-chat-panel-right.md", "continue_tutorial.py", @@ -531,9 +519,6 @@ const exe = os === "win32" ? ".exe" : ""; "models/all-MiniLM-L6-v2/vocab.txt", "models/all-MiniLM-L6-v2/onnx/model_quantized.onnx", - // node_modules (it's a bit confusing why this is necessary) - `node_modules/@vscode/ripgrep/bin/rg${exe}`, - // out directory (where the extension.js lives) // "out/extension.js", This is generated afterward by vsce // web-tree-sitter @@ -544,7 +529,6 @@ const exe = os === "win32" ? ".exe" : ""; "out/build/Release/node_sqlite3.node", // out/node_modules (to be accessed by extension.js) - `out/node_modules/@vscode/ripgrep/bin/rg${exe}`, `out/node_modules/@esbuild/${ target === "win32-arm64" ? "esbuild.exe" diff --git a/extensions/vscode/scripts/utils.js b/extensions/vscode/scripts/utils.js index 93c1928cebe..d190315c143 100644 --- a/extensions/vscode/scripts/utils.js +++ b/extensions/vscode/scripts/utils.js @@ -255,7 +255,6 @@ async function copyNodeModules() { "esbuild", "@esbuild", "@lancedb", - "@vscode/ripgrep", "workerpool", ]; fs.mkdirSync("out/node_modules", { recursive: true }); @@ -400,43 +399,6 @@ async function copySqliteBinary() { }); } -async function downloadRipgrepBinary(target) { - console.log("[info] Downloading pre-built ripgrep binary"); - rimrafSync("node_modules/@vscode/ripgrep/bin"); - fs.mkdirSync("node_modules/@vscode/ripgrep/bin", { recursive: true }); - 4; - const downloadUrl = { - "darwin-arm64": - "https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-10/ripgrep-v13.0.0-10-aarch64-apple-darwin.tar.gz", - "linux-arm64": - "https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-10/ripgrep-v13.0.0-10-aarch64-unknown-linux-gnu.tar.gz", - "win32-arm64": - "https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-10/ripgrep-v13.0.0-10-aarch64-pc-windows-msvc.zip", - "linux-x64": - "https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-10/ripgrep-v13.0.0-10-x86_64-unknown-linux-musl.tar.gz", - "darwin-x64": - "https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-10/ripgrep-v13.0.0-10-x86_64-apple-darwin.tar.gz", - "win32-x64": - "https://github.com/microsoft/ripgrep-prebuilt/releases/download/v13.0.0-10/ripgrep-v13.0.0-10-x86_64-pc-windows-msvc.zip", - }[target]; - - if (target.startsWith("win")) { - execCmdSync( - `curl -L -o node_modules/@vscode/ripgrep/bin/build.zip ${downloadUrl}`, - ); - execCmdSync("cd node_modules/@vscode/ripgrep/bin && unzip build.zip"); - fs.unlinkSync("node_modules/@vscode/ripgrep/bin/build.zip"); - } else { - execCmdSync( - `curl -L -o node_modules/@vscode/ripgrep/bin/build.tar.gz ${downloadUrl}`, - ); - execCmdSync( - "cd node_modules/@vscode/ripgrep/bin && tar -xvzf build.tar.gz", - ); - fs.unlinkSync("node_modules/@vscode/ripgrep/bin/build.tar.gz"); - } -} - async function installNodeModuleInTempDirAndCopyToCurrent(packageName, toCopy) { console.log(`Copying ${packageName} to ${toCopy}`); // This is a way to install only one package without npm trying to install all the dependencies @@ -508,6 +470,5 @@ module.exports = { copySqliteBinary, installNodeModuleInTempDirAndCopyToCurrent, downloadSqliteBinary, - downloadRipgrepBinary, copyTokenizers, }; diff --git a/extensions/vscode/src/AICore/AICore.ts b/extensions/vscode/src/AICore/AICore.ts new file mode 100644 index 00000000000..edbd05489f5 --- /dev/null +++ b/extensions/vscode/src/AICore/AICore.ts @@ -0,0 +1,54 @@ +// BAS Customization +import { CompletionOptions, LLMOptions, ModelProvider } from "core/index.js"; +import { BaseLLM } from "core/llm/index.js"; + +import * as vscode from "vscode"; +import { ContinueGenie } from "./AICoreGenie/genie"; +import { BasToolkit } from "@sap-devx/app-studio-toolkit-types"; + +const basAPI: BasToolkit = vscode.extensions.getExtension("SAPOSS.app-studio-toolkit")?.exports; + +class AICore extends BaseLLM { + static providerName: ModelProvider = "aicore"; + static defaultOptions: Partial = { + model: "gpt-4o-mini", + }; + + constructor(options: LLMOptions) { + basAPI.getExtensionAPI("SAPSE.joule").then(async (jouleAPI: any) => { + // register SampleGenies + const continueGenie = new ContinueGenie(); + await jouleAPI.registerGenie(continueGenie); + }); + + super(options); + } + + async listModels(): Promise { + return ["gpt-4o-mini"]; + } + + protected async *_streamComplete(prompt: string, options: CompletionOptions): AsyncGenerator { + try { + const genie = await vscode.commands.executeCommand("joule.getGenie", "auto-completion-genie"); + if (!genie) { + console.debug("joule.getGenie - Not found the specified auto-completion-genie genie."); + return; + } + const serviceProxy = await (genie as any).getServiceProxy(); + const completionPayload = await serviceProxy.buildPayload(genie, prompt, []); + const response = await serviceProxy.requestCompletion(genie, completionPayload); + + let res = response.content; + if (res.includes("") && res.includes("")) { + res = res.slice("".length, res.length - "".length); + yield res; + } + } catch (error) { + console.error("Error fetching response from AI.core", error); + process.exit(1); + } + } +} + +export default AICore; diff --git a/extensions/vscode/src/AICore/AICoreGenie/genie-session.ts b/extensions/vscode/src/AICore/AICoreGenie/genie-session.ts new file mode 100644 index 00000000000..de8a4edaff3 --- /dev/null +++ b/extensions/vscode/src/AICore/AICoreGenie/genie-session.ts @@ -0,0 +1,54 @@ +// BAS Customization +import { + ActiveEnv, + CompletionPayload, + CompletionResponse, + GenieChatItem, + GenieSession, + IGenie, + UserContext, +} from "@sap/gai-core"; + +export class ContinueGenieSession extends GenieSession { + constructor(genie: IGenie, initialPrompt: string, userContext: UserContext, activeEnv: ActiveEnv) { + super(genie, initialPrompt, userContext, activeEnv); + } + + /** + * The pre process payload event before sending each session message + * @param payload + * @returns + */ + protected async preProcessPayload(payload: CompletionPayload): Promise { + return super.preProcessPayload(payload); + } + + /** + * send a new message, and append it to the session messages + * @param prompt + * @returns + */ + async send(prompt: string): Promise { + return super.send(prompt); + } + + /** + * modify an sent message and resend it again, all the following session messages will be removed + * @param newPrompt + * @param chatID + * @returns + */ + async resend(newPrompt: string, chatID: string): Promise { + return super.resend(newPrompt, chatID); + } + + /** + * The post process event after sending each session message + * @param response + * @returns + */ + protected async postProcess(response: CompletionResponse): Promise { + console.log("BAS Joule: Override the post process after sending each session message"); + return super.postProcess(response); + } +} diff --git a/extensions/vscode/src/AICore/AICoreGenie/genie.ts b/extensions/vscode/src/AICore/AICoreGenie/genie.ts new file mode 100644 index 00000000000..0691d65f7b7 --- /dev/null +++ b/extensions/vscode/src/AICore/AICoreGenie/genie.ts @@ -0,0 +1,118 @@ +// BAS Customization +import { AbstractGenie, ActiveEnv, IGenieSession, UserContext, GenieProfile } from "@sap/gai-core"; +import { getProfile } from "./profile"; +import { ContinueGenieSession } from "./genie-session"; + +export class ContinueGenie extends AbstractGenie { + constructor(gProfile: GenieProfile = getProfile()) { + super(gProfile); + } + + /** + * [Mandatory] The file-level checking machanism for waking up the current BAS genie + * (It can be invoked when an opened file tab is activated) + * @param activeEnv + * @returns + */ + // eslint-disable-next-line @typescript-eslint/require-await -- ignore + async match(activeEnv: ActiveEnv): Promise { + return false; + } + + /** + * The default initial prompt shown in the Joule input field when activating the current BAS genie + * (It's for UX quick input purpose) + * @returns + */ + async getInitialPrompt(activeEnv: ActiveEnv): Promise { + return super.getInitialPrompt(activeEnv); + } + + /** + * Override the auto attached system messages here + * @returns + */ + async getKnowledge(): Promise { + const knowledge = await super.getKnowledge(); + console.log("BAS Joule: the auto attached system messages are:"); + console.log(knowledge.join("\n")); + return knowledge; + } + + /** + * Override the auto attahced user context messages here + * @param prompt + * @param session + */ + async getContextPrompt(prompt: string, session: IGenieSession): Promise { + const userContext = await super.getContextPrompt(prompt, session); + console.log("BAS Joule: the auto attached user context message is:"); + console.log(userContext); + return userContext; + } + + /** + * Override the model vendor name here + * @returns + */ + async getModelVendor(): Promise { + const modeVendor = await super.getModelVendor(); + console.log("BAS Joule: the configured model is:"); + console.log(modeVendor); + return modeVendor; + } + + /** + * Override the model vendor name here + * @returns + */ + async getModelSettings(): Promise { + const modelSettings = await super.getModelSettings(); + console.log("BAS Joule: the configured model settings are:"); + console.log(modelSettings); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return -- ignore + return modelSettings; + } + + /** + * Override the startSession here, including attaching more info to the user context, and use a customized genie session + * All the conversational chat messages for one specific matched file will be maintained in one GenieSession object + * @param initialPrompt + * @param userContext + * @param activeEnv + * @returns + */ + async startSession(initialPrompt: string, userContext: UserContext, activeEnv: ActiveEnv): Promise { + try { + const profile = await this.getProfile(); + for (const metadataItem of profile.context.metadata) { + const name = metadataItem.name; + let contextValue = userContext[name]; + if (!contextValue) { + contextValue = metadataItem.defaultValue; + } + userContext[name] = contextValue; + } + } catch (error) { + console.error(error); + } + const genieSession = new ContinueGenieSession(this, initialPrompt, userContext, activeEnv); + this.sessions.push(genieSession); + return genieSession; + } + + /** + * The custom implementation for the user action `showResult` defined in profile + * @param response + * @param session + * @returns + */ + // async showResult(response: string, session: IGenieSession): Promise { + // eslint-disable-next-line @typescript-eslint/require-await -- ignore + async showResult(response: string): Promise { + if (response) { + return true; + } + return false; + } +} diff --git a/extensions/vscode/src/AICore/AICoreGenie/profile.ts b/extensions/vscode/src/AICore/AICoreGenie/profile.ts new file mode 100644 index 00000000000..7a044dc3e51 --- /dev/null +++ b/extensions/vscode/src/AICore/AICoreGenie/profile.ts @@ -0,0 +1,46 @@ +// BAS Customization +import type { GenieProfile } from "@sap/gai-core"; + +export function getProfile() { + return { + ID: "auto-completion-genie", + version: "1.0", + name: "BAS Joule for AI Code Assistant extension", + alias: "continue", + description: "I’m Joule, your specialized AI assistant.", + knowledge: { + role: "", + rules: [], + }, + feature: { + maxChatRounds: NaN, + streaming: true, + }, + examples: [], + context: { + metadata: [], + contextSyntax: "", + }, + llmModel: { + vendor: "openai-gpt", + settings: { + model_name: "gpt-4o-mini", + temperature: 0.5, + }, + }, + actions: [ + { + name: "showResult", + label: "Show Result", + language: "plaintext", + icon: "codicon:info", + }, + { + name: "previewStaging", + label: "Preview App", + language: "plaintext", + icon: "codicon:info", + }, + ], + } as GenieProfile; +} diff --git a/extensions/vscode/src/VsCodeIde.ts b/extensions/vscode/src/VsCodeIde.ts index ed713963617..c7ada220dfa 100644 --- a/extensions/vscode/src/VsCodeIde.ts +++ b/extensions/vscode/src/VsCodeIde.ts @@ -454,7 +454,6 @@ class VsCodeIde implements IDE { "out", "node_modules", "@vscode", - "ripgrep", "bin", "rg", ), diff --git a/extensions/vscode/src/activation/activate.ts b/extensions/vscode/src/activation/activate.ts index cce7bcfbe50..2f9ae534eff 100644 --- a/extensions/vscode/src/activation/activate.ts +++ b/extensions/vscode/src/activation/activate.ts @@ -8,6 +8,7 @@ import { getExtensionVersion } from "../util/util"; import { getExtensionUri } from "../util/vscode"; import { VsCodeContinueApi } from "./api"; import { setupInlineTips } from "./inlineTips"; +import { showConsentNotification } from "../../src/consent-notification"; export async function activateExtension(context: vscode.ExtensionContext) { // Add necessary files @@ -37,6 +38,9 @@ export async function activateExtension(context: vscode.ExtensionContext) { registerCustomContextProvider: api.registerCustomContextProvider.bind(api), }; + // BAS Customization - show consent popup + void showConsentNotification(context); + // 'export' public api-surface // or entire extension for testing return process.env.NODE_ENV === "test" diff --git a/extensions/vscode/src/autocomplete/statusBar.ts b/extensions/vscode/src/autocomplete/statusBar.ts index 333661d55cc..b888ffc7ccb 100644 --- a/extensions/vscode/src/autocomplete/statusBar.ts +++ b/extensions/vscode/src/autocomplete/statusBar.ts @@ -96,7 +96,9 @@ export function setupStatusBar( statusBarItem.tooltip = statusBarItemTooltip(status ?? statusBarStatus); statusBarItem.command = "continue.openTabAutocompleteConfigMenu"; - statusBarItem.show(); + + // BAS Customization - remove status bar for BAS + // statusBarItem.show(); if (status !== undefined) { statusBarStatus = status; } @@ -104,7 +106,7 @@ export function setupStatusBar( vscode.workspace.onDidChangeConfiguration((event) => { if (event.affectsConfiguration(CONTINUE_WORKSPACE_KEY)) { const enabled = getContinueWorkspaceConfig().get( - "enableTabAutocomplete", + "tabToEnableAutocomplete", ); if (enabled && statusBarStatus === StatusBarStatus.Paused) { return; @@ -123,7 +125,7 @@ export function getStatusBarStatus(): StatusBarStatus | undefined { export function monitorBatteryChanges(battery: Battery): vscode.Disposable { return battery.onChangeAC((acConnected: boolean) => { const config = vscode.workspace.getConfiguration(EXTENSION_NAME); - const enabled = config.get("enableTabAutocomplete"); + const enabled = config.get("tabToEnableAutocomplete"); if (!!enabled) { const pauseOnBattery = config.get( "pauseTabAutocompleteOnBattery", diff --git a/extensions/vscode/src/commands.ts b/extensions/vscode/src/commands.ts index 17140eaf871..47657207f87 100644 --- a/extensions/vscode/src/commands.ts +++ b/extensions/vscode/src/commands.ts @@ -570,13 +570,13 @@ const commandsMap: ( captureCommandTelemetry("toggleTabAutocompleteEnabled"); const config = vscode.workspace.getConfiguration(EXTENSION_NAME); - const enabled = config.get("enableTabAutocomplete"); + const enabled = config.get("tabToEnableAutocomplete"); const pauseOnBattery = config.get( "pauseTabAutocompleteOnBattery", ); if (!pauseOnBattery || battery.isACConnected()) { config.update( - "enableTabAutocomplete", + "tabToEnableAutocomplete", !enabled, vscode.ConfigurationTarget.Global, ); @@ -587,7 +587,7 @@ const commandsMap: ( setupStatusBar(StatusBarStatus.Enabled); } else { config.update( - "enableTabAutocomplete", + "tabToEnableAutocomplete", false, vscode.ConfigurationTarget.Global, ); @@ -595,7 +595,7 @@ const commandsMap: ( } else { setupStatusBar(StatusBarStatus.Paused); config.update( - "enableTabAutocomplete", + "tabToEnableAutocomplete", true, vscode.ConfigurationTarget.Global, ); @@ -667,7 +667,7 @@ const commandsMap: ( if (targetStatus !== undefined) { setupStatusBar(targetStatus); config.update( - "enableTabAutocomplete", + "tabToEnableAutocomplete", targetStatus === StatusBarStatus.Enabled, vscode.ConfigurationTarget.Global, ); diff --git a/extensions/vscode/src/consent-notification.ts b/extensions/vscode/src/consent-notification.ts new file mode 100644 index 00000000000..a6626692acd --- /dev/null +++ b/extensions/vscode/src/consent-notification.ts @@ -0,0 +1,27 @@ +import { window, ExtensionContext, workspace, ConfigurationTarget } from "vscode"; + +const SHOULD_SHOW_CONSENT = "shouldShowConsentNotification"; + +async function shouldShowConsent(context: ExtensionContext): Promise { + // If it is the first time the property is undefined, it will return true + return context.globalState.get(SHOULD_SHOW_CONSENT) ?? true; +} + +export async function showConsentNotification(context: ExtensionContext): Promise { + if (!(await shouldShowConsent(context))) { + return; // Early exit if consent has already been handled + } + const consentMessage = `You are using the SAP AI Code assistant tool.\nPlease note that this feature will reduce your AI quota as it generates the code. The generated code was created using AI and therefore must be reviewed.`; + const selection = await window.showInformationMessage(consentMessage, "OK", "Disable Tool"); + try { + // in any selection, we will update the global state to not show the consent notification again. + await context.globalState.update(SHOULD_SHOW_CONSENT, false); + if (selection === "Disable Tool") { + workspace + .getConfiguration("AI Code Assistant") + .update("tabToEnableAutocomplete", false, ConfigurationTarget.Global); + } + } catch (error) { + console.error("Error updating consent settings:", error); + } +} diff --git a/extensions/vscode/src/extension.ts b/extensions/vscode/src/extension.ts index e3967bdb24d..39f90413fe6 100644 --- a/extensions/vscode/src/extension.ts +++ b/extensions/vscode/src/extension.ts @@ -5,12 +5,15 @@ import { setupCa } from "core/util/ca"; import { Telemetry } from "core/util/posthog"; import * as vscode from "vscode"; -import { getExtensionVersion } from "./util/util"; +import { getExtensionVersion, isLLMServiceAvailable } from "./util/util"; async function dynamicImportAndActivate(context: vscode.ExtensionContext) { const { activateExtension } = await import("./activation/activate"); try { - return activateExtension(context); + // BAS Customization - check if LLM service is available + if (isLLMServiceAvailable()) { + return activateExtension(context); + } } catch (e) { console.log("Error activating extension: ", e); vscode.window diff --git a/extensions/vscode/src/extension/VsCodeExtension.ts b/extensions/vscode/src/extension/VsCodeExtension.ts index f2a6b8b7095..eff67c6ce08 100644 --- a/extensions/vscode/src/extension/VsCodeExtension.ts +++ b/extensions/vscode/src/extension/VsCodeExtension.ts @@ -166,7 +166,7 @@ export class VsCodeExtension { // Tab autocomplete const config = vscode.workspace.getConfiguration(EXTENSION_NAME); - const enabled = config.get("enableTabAutocomplete"); + const enabled = config.get("tabToEnableAutocomplete"); // Register inline completion provider setupStatusBar( diff --git a/extensions/vscode/src/util/constants.ts b/extensions/vscode/src/util/constants.ts index 2db7798f876..71bcbd94932 100644 --- a/extensions/vscode/src/util/constants.ts +++ b/extensions/vscode/src/util/constants.ts @@ -1,4 +1,4 @@ -export const EXTENSION_NAME = "continue"; +export const EXTENSION_NAME = "AI Code Assistant"; export const AUTH_TYPE = process.env.CONTROL_PLANE_ENV === "local" ? `${EXTENSION_NAME}-staging` diff --git a/extensions/vscode/src/util/loadAutocompleteModel.ts b/extensions/vscode/src/util/loadAutocompleteModel.ts index 4437c11d124..c1c67453341 100644 --- a/extensions/vscode/src/util/loadAutocompleteModel.ts +++ b/extensions/vscode/src/util/loadAutocompleteModel.ts @@ -3,11 +3,12 @@ import { ConfigHandler } from "core/config/ConfigHandler"; import Ollama from "core/llm/llms/Ollama"; import { GlobalContext } from "core/util/GlobalContext"; import * as vscode from "vscode"; +import AICore from "../AICore/AICore"; export class TabAutocompleteModel { private _llm: ILLM | undefined; - private defaultTag = "starcoder2:3b"; - private defaultTagName = "Starcoder2 3b"; + private defaultTag = "gpt-4o-mini"; + private defaultTagName = "gpt 4o mini"; private globalContext: GlobalContext = new GlobalContext(); private shownOllamaWarning = false; @@ -24,7 +25,8 @@ export class TabAutocompleteModel { } async getDefaultTabAutocompleteModel() { - const llm = new Ollama({ + // BAS Customization - use AICore as model provider + const llm = new AICore({ model: this.defaultTag, }); diff --git a/extensions/vscode/src/util/util.ts b/extensions/vscode/src/util/util.ts index 2d0ae72a7e8..c7c9971b4aa 100644 --- a/extensions/vscode/src/util/util.ts +++ b/extensions/vscode/src/util/util.ts @@ -1,6 +1,9 @@ const os = require("node:os"); import path from "node:path"; import * as vscode from "vscode"; +import { isBASBuildCodeEnv } from "@sap/gai-core"; + +const SERVICE_KEY_CONFIG_PROP = "joule.serviceKey"; function charIsEscapedAtIndex(index: number, str: string): boolean { if (index === 0) { @@ -119,7 +122,7 @@ export function getMetaKeyName() { } export function getExtensionVersion(): string { - const extension = vscode.extensions.getExtension("continue.continue"); + const extension = vscode.extensions.getExtension("SAPSE.ai-code-assistant"); return extension?.packageJSON.version || "0.1.0"; } @@ -135,3 +138,18 @@ export function getFullyQualifiedPath(filepath?: string) { } } } + +// BAS Customization +/** + * Returns the service key setting from the configuration. + * @returns {string | null} The service key setting or null if it is not set. + */ +export function getServiceKeySetting() { + const config = vscode.workspace.getConfiguration(); + return config.get(SERVICE_KEY_CONFIG_PROP, null); +} + +export const isLLMServiceAvailable = (): boolean => { + const isBuildCode = isBASBuildCodeEnv(); + return isBuildCode || !!getServiceKeySetting(); +}; diff --git a/extensions/vscode/src/util/vscode.ts b/extensions/vscode/src/util/vscode.ts index 9f9dd2093b1..63e6fbe99f8 100644 --- a/extensions/vscode/src/util/vscode.ts +++ b/extensions/vscode/src/util/vscode.ts @@ -22,7 +22,7 @@ export function getNonce() { } export function getExtensionUri(): vscode.Uri { - return vscode.extensions.getExtension("Continue.continue")!.extensionUri; + return vscode.extensions.getExtension("SAPSE.ai-code-assistant")!.extensionUri; } export function getViewColumnOfFile( diff --git a/extensions/vscode/src/util/workspaceConfig.ts b/extensions/vscode/src/util/workspaceConfig.ts index 3adc6d2524d..417d19bceb0 100644 --- a/extensions/vscode/src/util/workspaceConfig.ts +++ b/extensions/vscode/src/util/workspaceConfig.ts @@ -1,6 +1,6 @@ import { workspace } from "vscode"; -export const CONTINUE_WORKSPACE_KEY = "continue"; +export const CONTINUE_WORKSPACE_KEY = "AI Code Assistant"; export function getContinueWorkspaceConfig() { return workspace.getConfiguration(CONTINUE_WORKSPACE_KEY); diff --git a/media/autocompletion.png b/media/autocompletion.png new file mode 100644 index 00000000000..4acb055604c Binary files /dev/null and b/media/autocompletion.png differ diff --git a/package.json b/package.json new file mode 100644 index 00000000000..2ae7b36c358 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "@sap-bas/ai-code-assistant", + "version": "0.11.14", + "description": "AI code assistant helps developers by providing real-time code completion, code understanding and refactoring. AI code assistant supports a variety of developer tools, making it easy to use while coding.", + "scripts": { + "ci": "sh scripts/install-dependencies.sh", + "install-core": "cd core && npm install && npm link && cd -", + "install-vscode": "cd extensions/vscode && npm install && npm link @continuedev/core && npm run package", + "install-and-package": "npm run install-core && npm run install-vscode", + "ci:artifacts_only": "npm run install-and-package", + "install-vsix-for-linux": "cd extensions/vscode && npm install && npm link @continuedev/core && npm run package-to-linux-x64-platform" + }, + "license": "Apache-2.0", + "author": "SAP SE" +}