From 14f26dace8e200c9ab8d3e590f841ed88739227b Mon Sep 17 00:00:00 2001 From: Neta Date: Mon, 14 Oct 2024 16:28:30 +0300 Subject: [PATCH 1/3] BAS - support code completion --- extensions/vscode/.npmrc | 5 +- extensions/vscode/README.md | 58 +-- extensions/vscode/media/icon.png | Bin 3826 -> 2005 bytes extensions/vscode/package.json | 468 +----------------- extensions/vscode/scripts/package.js | 8 +- .../scripts/prepackage-cross-platform.js | 23 - extensions/vscode/scripts/prepackage.js | 170 +------ extensions/vscode/scripts/utils.js | 154 ------ extensions/vscode/src/AICore/AICore.ts | 74 +++ .../src/AICore/AICoreGenie/genie-session.ts | 54 ++ .../vscode/src/AICore/AICoreGenie/genie.ts | 118 +++++ .../vscode/src/AICore/AICoreGenie/profile.ts | 46 ++ extensions/vscode/src/VsCodeIde.ts | 1 - extensions/vscode/src/activation/activate.ts | 4 + .../vscode/src/autocomplete/statusBar.ts | 8 +- extensions/vscode/src/commands.ts | 10 +- extensions/vscode/src/consent-notification.ts | 27 + extensions/vscode/src/extension.ts | 8 +- .../vscode/src/extension/VsCodeExtension.ts | 2 +- extensions/vscode/src/util/constants.ts | 2 +- .../vscode/src/util/loadAutocompleteModel.ts | 8 +- extensions/vscode/src/util/util.ts | 20 +- extensions/vscode/src/util/vscode.ts | 2 +- extensions/vscode/src/util/workspaceConfig.ts | 2 +- media/autocompletion.png | Bin 0 -> 49888 bytes package.json | 15 + 26 files changed, 432 insertions(+), 855 deletions(-) create mode 100644 extensions/vscode/src/AICore/AICore.ts create mode 100644 extensions/vscode/src/AICore/AICoreGenie/genie-session.ts create mode 100644 extensions/vscode/src/AICore/AICoreGenie/genie.ts create mode 100644 extensions/vscode/src/AICore/AICoreGenie/profile.ts create mode 100644 extensions/vscode/src/consent-notification.ts create mode 100644 media/autocompletion.png create mode 100644 package.json diff --git a/extensions/vscode/.npmrc b/extensions/vscode/.npmrc index aa026a602a..d3293a356c 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 8bfc32b73e..6a19f381fc 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 d8f9f16c59b41ce3cf49ccb4909370ed3fb401c6..21d2a7ac9cdfc18c0712fb9704d1c8b4333c6d53 100644 GIT binary patch literal 2005 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 diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 5b3f70c9f8..7096a1d3c4 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.2", "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 17594333e4..fc0f4f71c6 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 65cf8836c9..1461b46213 100644 --- a/extensions/vscode/scripts/prepackage-cross-platform.js +++ b/extensions/vscode/scripts/prepackage-cross-platform.js @@ -12,15 +12,12 @@ const { autodetectPlatformAndArch, } = require("../../../scripts/util/index"); const { - copyConfigSchema, installNodeModules, - buildGui, copyOnnxRuntimeFromNodeModules, copyTreeSitterWasms, copyTreeSitterTagQryFiles, copyNodeModules, downloadEsbuildBinary, - downloadRipgrepBinary, copySqliteBinary, installNodeModuleInTempDirAndCopyToCurrent, downloadSqliteBinary, @@ -33,10 +30,6 @@ rimrafSync(path.join(__dirname, "..", "out")); fs.mkdirSync(path.join(__dirname, "..", "out", "node_modules"), { recursive: true, }); -const guiDist = path.join(__dirname, "..", "..", "..", "gui", "dist"); -if (!fs.existsSync(guiDist)) { - fs.mkdirSync(guiDist, { recursive: true }); -} // Get the target to package for let target = undefined; @@ -85,15 +78,9 @@ function isWin() { async function package(target, os, arch, exe) { console.log("[info] Packaging extension for target ", target); - // Copy config_schema.json to config.json in docs and intellij - copyConfigSchema(); - // Install node_modules installNodeModules(); - // Build gui and copy to extensions - await buildGui(ghAction()); - // Assets // Copy tree-sitter-wasm files await copyTreeSitterWasms(); @@ -132,8 +119,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 +147,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 +160,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 +170,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 0807c9fbb7..7a17072f6c 100644 --- a/extensions/vscode/scripts/prepackage.js +++ b/extensions/vscode/scripts/prepackage.js @@ -14,10 +14,6 @@ rimrafSync(path.join(__dirname, "..", "out")); fs.mkdirSync(path.join(__dirname, "..", "out", "node_modules"), { recursive: true, }); -const guiDist = path.join(__dirname, "..", "..", "..", "gui", "dist"); -if (!fs.existsSync(guiDist)) { - fs.mkdirSync(guiDist, { recursive: true }); -} // Get the target to package for let target = undefined; @@ -48,124 +44,25 @@ const exe = os === "win32" ? ".exe" : ""; (async () => { console.log("[info] Packaging extension for target ", target); - // Copy config_schema.json to config.json in docs and intellij - fs.copyFileSync( - "config_schema.json", - path.join("..", "..", "docs", "static", "schemas", "config.json"), - ); - fs.copyFileSync( - "config_schema.json", - path.join( - "..", - "intellij", - "src", - "main", - "resources", - "config_schema.json", - ), - ); - // Modify and copy for .continuerc.json - const schema = JSON.parse(fs.readFileSync("config_schema.json", "utf8")); - schema.definitions.SerializedContinueConfig.properties.mergeBehavior = { - type: "string", - enum: ["merge", "overwrite"], - default: "merge", - title: "Merge behavior", - markdownDescription: - "If set to 'merge', .continuerc.json will be applied on top of config.json (arrays and objects are merged). If set to 'overwrite', then every top-level property of .continuerc.json will overwrite that property from config.json.", - }; - fs.writeFileSync("continue_rc_schema.json", JSON.stringify(schema, null, 2)); - if (!process.cwd().endsWith("vscode")) { // This is sometimes run from root dir instead (e.g. in VS Code tasks) process.chdir("extensions/vscode"); } // Install node_modules // - execCmdSync("npm install"); + execCmdSync("npm install --registry=https://int.repositories.cloud.sap/artifactory/api/npm/build-releases-npm"); console.log("[info] npm install in extensions/vscode completed"); - process.chdir("../../gui"); - - execCmdSync("npm install"); - console.log("[info] npm install in gui completed"); if (ghAction()) { execCmdSync("npm run build"); } - // Copy over the dist folder to the JetBrains extension // - const intellijExtensionWebviewPath = path.join( - "..", - "extensions", - "intellij", - "src", - "main", - "resources", - "webview", - ); - - const indexHtmlPath = path.join(intellijExtensionWebviewPath, "index.html"); - fs.copyFileSync(indexHtmlPath, "tmp_index.html"); - rimrafSync(intellijExtensionWebviewPath); - fs.mkdirSync(intellijExtensionWebviewPath, { recursive: true }); - - await new Promise((resolve, reject) => { - ncp("dist", intellijExtensionWebviewPath, (error) => { - if (error) { - console.warn( - "[error] Error copying React app build to JetBrains extension: ", - error, - ); - reject(error); - } - resolve(); - }); - }); - - // Put back index.html - if (fs.existsSync(indexHtmlPath)) { - rimrafSync(indexHtmlPath); - } - fs.copyFileSync("tmp_index.html", indexHtmlPath); - fs.unlinkSync("tmp_index.html"); - - // Copy over other misc. files - fs.copyFileSync( - "../extensions/vscode/gui/onigasm.wasm", - path.join(intellijExtensionWebviewPath, "onigasm.wasm"), - ); - - console.log("[info] Copied gui build to JetBrains extension"); - - // Then copy over the dist folder to the VSCode extension // - const vscodeGuiPath = path.join("../extensions/vscode/gui"); - fs.mkdirSync(vscodeGuiPath, { recursive: true }); - await new Promise((resolve, reject) => { - ncp("dist", vscodeGuiPath, (error) => { - if (error) { - console.log( - "Error copying React app build to VSCode extension: ", - error, - ); - reject(error); - } else { - console.log("Copied gui build to VSCode extension"); - resolve(); - } - }); - }); - - 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"); - + if (!process.cwd().endsWith("vscode")) { + // This is sometimes run from root dir instead (e.g. in VS Code tasks) + process.chdir("../extensions/vscode"); + } fs.mkdirSync("bin", { recursive: true }); // onnxruntime-node @@ -269,22 +166,6 @@ const exe = os === "win32" ? ".exe" : ""; // }, // ); - // textmate-syntaxes - await new Promise((resolve, reject) => { - ncp( - path.join(__dirname, "../textmate-syntaxes"), - path.join(__dirname, "../gui/textmate-syntaxes"), - (error) => { - if (error) { - console.warn("[error] Error copying textmate-syntaxes", error); - reject(error); - } else { - resolve(); - } - }, - ); - }); - function ghAction() { return !!process.env.GITHUB_ACTIONS; } @@ -324,7 +205,7 @@ const exe = os === "win32" ? ".exe" : ""; process.chdir(tempDir); // Initialize a new package.json and install the package - execCmdSync(`npm init -y && npm i -f ${packageName} --no-save`); + execCmdSync(`npm init -y && npm i --registry=https://int.repositories.cloud.sap/artifactory/api/npm/build-releases-npm -f ${packageName} --no-save`); console.log( `Contents of: ${packageName}`, @@ -335,28 +216,24 @@ const exe = os === "win32" ? ".exe" : ""; // Ideally we validate file integrity in the validation at the end await new Promise((resolve) => setTimeout(resolve, 2000)); + // Ensure the target directory exists + const targetDir = path.join(currentDir, "node_modules", toCopy); + fs.mkdirSync(path.dirname(targetDir), { recursive: true }); + // Copy the installed package back to the current directory await new Promise((resolve, reject) => { - ncp( - path.join(tempDir, "node_modules", toCopy), - path.join(currentDir, "node_modules", toCopy), - { dereference: true }, - (error) => { - if (error) { - console.error( - `[error] Error copying ${packageName} package`, - error, - ); - reject(error); - } else { - resolve(); - } - }, - ); + ncp(path.join(tempDir, "node_modules", toCopy), targetDir, { dereference: true }, (error) => { + if (error) { + console.error(`[error] Error copying ${packageName} package`, error); + reject(error); + } else { + resolve(); + } + }); }); } finally { // Clean up the temporary directory - // rimrafSync(tempDir); + rimrafSync(tempDir); // Return to the original directory process.chdir(currentDir); @@ -458,7 +335,6 @@ const exe = os === "win32" ? ".exe" : ""; "esbuild", "@esbuild", "@lancedb", - "@vscode/ripgrep", "workerpool", ]; @@ -514,10 +390,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 +403,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 +413,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 93c1928ceb..e3e3e0c931 100644 --- a/extensions/vscode/scripts/utils.js +++ b/extensions/vscode/scripts/utils.js @@ -10,35 +10,6 @@ const { const continueDir = path.join(__dirname, "..", "..", ".."); -function copyConfigSchema() { - fs.copyFileSync( - "config_schema.json", - path.join("..", "..", "docs", "static", "schemas", "config.json"), - ); - fs.copyFileSync( - "config_schema.json", - path.join( - "..", - "intellij", - "src", - "main", - "resources", - "config_schema.json", - ), - ); - // Modify and copy for .continuerc.json - const schema = JSON.parse(fs.readFileSync("config_schema.json", "utf8")); - schema.definitions.SerializedContinueConfig.properties.mergeBehavior = { - type: "string", - enum: ["merge", "overwrite"], - default: "merge", - title: "Merge behavior", - markdownDescription: - "If set to 'merge', .continuerc.json will be applied on top of config.json (arrays and objects are merged). If set to 'overwrite', then every top-level property of .continuerc.json will overwrite that property from config.json.", - }; - fs.writeFileSync("continue_rc_schema.json", JSON.stringify(schema, null, 2)); -} - function copyTokenizers() { fs.copyFileSync( path.join(__dirname, "../../../core/llm/llamaTokenizerWorkerPool.mjs"), @@ -62,90 +33,6 @@ function installNodeModules() { // Install node_modules // execCmdSync("npm install"); console.log("[info] npm install in extensions/vscode completed"); - - process.chdir(path.join(continueDir, "gui")); - - execCmdSync("npm install"); - console.log("[info] npm install in gui completed"); -} - -async function buildGui(isGhAction) { - // Make sure we are in the right directory - if (!process.cwd().endsWith("gui")) { - process.chdir(path.join(continueDir, "gui")); - } - if (isGhAction) { - execCmdSync("npm run build"); - } - - // Copy over the dist folder to the JetBrains extension // - const intellijExtensionWebviewPath = path.join( - "..", - "extensions", - "intellij", - "src", - "main", - "resources", - "webview", - ); - - const indexHtmlPath = path.join(intellijExtensionWebviewPath, "index.html"); - fs.copyFileSync(indexHtmlPath, "tmp_index.html"); - rimrafSync(intellijExtensionWebviewPath); - fs.mkdirSync(intellijExtensionWebviewPath, { recursive: true }); - - await new Promise((resolve, reject) => { - ncp("dist", intellijExtensionWebviewPath, (error) => { - if (error) { - console.warn( - "[error] Error copying React app build to JetBrains extension: ", - error, - ); - reject(error); - } - resolve(); - }); - }); - - // Put back index.html - if (fs.existsSync(indexHtmlPath)) { - rimrafSync(indexHtmlPath); - } - fs.copyFileSync("tmp_index.html", indexHtmlPath); - fs.unlinkSync("tmp_index.html"); - - // Copy over other misc. files - fs.copyFileSync( - "../extensions/vscode/gui/onigasm.wasm", - path.join(intellijExtensionWebviewPath, "onigasm.wasm"), - ); - - console.log("[info] Copied gui build to JetBrains extension"); - - // Then copy over the dist folder to the VSCode extension // - const vscodeGuiPath = path.join("../extensions/vscode/gui"); - fs.mkdirSync(vscodeGuiPath, { recursive: true }); - await new Promise((resolve, reject) => { - ncp("dist", vscodeGuiPath, (error) => { - if (error) { - console.log( - "Error copying React app build to VSCode extension: ", - error, - ); - reject(error); - } else { - console.log("Copied gui build to VSCode extension"); - resolve(); - } - }); - }); - - 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"); - } } async function copyOnnxRuntimeFromNodeModules(target) { @@ -255,7 +142,6 @@ async function copyNodeModules() { "esbuild", "@esbuild", "@lancedb", - "@vscode/ripgrep", "workerpool", ]; fs.mkdirSync("out/node_modules", { recursive: true }); @@ -400,43 +286,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 @@ -497,9 +346,7 @@ async function installNodeModuleInTempDirAndCopyToCurrent(packageName, toCopy) { } module.exports = { - copyConfigSchema, installNodeModules, - buildGui, copyOnnxRuntimeFromNodeModules, copyTreeSitterWasms, copyTreeSitterTagQryFiles, @@ -508,6 +355,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 0000000000..1bae57550c --- /dev/null +++ b/extensions/vscode/src/AICore/AICore.ts @@ -0,0 +1,74 @@ +// 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; + +interface ModelDetail { + name: string; + version?: string; +} + +interface BackendDetails { + model: ModelDetail; +} + +interface ResourceDetails { + resources: { + backend_details: BackendDetails; + }; +} + +interface Resource { + id: string; + details?: ResourceDetails; +} + +class AICore extends BaseLLM { + static providerName: ModelProvider = "aicore"; + static defaultOptions: Partial = { + model: "gpt-4o", + }; + + 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", "gpt-35-turbo", "llama3"]; + } + + 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 0000000000..de8a4edaff --- /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 0000000000..0691d65f7b --- /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 0000000000..7a044dc3e5 --- /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 ed71396361..c7ada220df 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 cce7bcfbe5..2f9ae534ef 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 333661d55c..b888ffc7cc 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 17140eaf87..47657207f8 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 0000000000..a6626692ac --- /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 e3967bdb24..9bb2fa39c8 100644 --- a/extensions/vscode/src/extension.ts +++ b/extensions/vscode/src/extension.ts @@ -5,13 +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); - } catch (e) { + // BAS Customization - check if LLM service is available + if (isLLMServiceAvailable()) { + return activateExtension(context); + } } catch (e) { console.log("Error activating extension: ", e); vscode.window .showInformationMessage( diff --git a/extensions/vscode/src/extension/VsCodeExtension.ts b/extensions/vscode/src/extension/VsCodeExtension.ts index f2a6b8b709..eff67c6ce0 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 2db7798f87..71bcbd9493 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 4437c11d12..6267807339 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"; + private defaultTagName = "gpt 4o"; 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 2d0ae72a7e..c7c9971b4a 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 9f9dd2093b..63e6fbe99f 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 3adc6d2524..417d19bceb 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 0000000000000000000000000000000000000000..4acb055604c700ddb1c4a765c0b491d93046f22d GIT binary patch literal 49888 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%0A6GfhMS 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 A&Hw-a literal 0 HcmV?d00001 diff --git a/package.json b/package.json new file mode 100644 index 0000000000..2ae7b36c35 --- /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" +} From 26c643333c78e4161627ca0c13ca8ec4472446df Mon Sep 17 00:00:00 2001 From: Neta Date: Tue, 15 Oct 2024 13:29:38 +0300 Subject: [PATCH 2/3] fix: fixes --- .../scripts/prepackage-cross-platform.js | 10 ++ extensions/vscode/scripts/utils.js | 115 ++++++++++++++++++ extensions/vscode/src/AICore/AICore.ts | 22 +--- extensions/vscode/src/extension.ts | 3 +- .../vscode/src/util/loadAutocompleteModel.ts | 4 +- 5 files changed, 130 insertions(+), 24 deletions(-) diff --git a/extensions/vscode/scripts/prepackage-cross-platform.js b/extensions/vscode/scripts/prepackage-cross-platform.js index 1461b46213..16f6186f48 100644 --- a/extensions/vscode/scripts/prepackage-cross-platform.js +++ b/extensions/vscode/scripts/prepackage-cross-platform.js @@ -12,7 +12,9 @@ const { autodetectPlatformAndArch, } = require("../../../scripts/util/index"); const { + copyConfigSchema, installNodeModules, + buildGui, copyOnnxRuntimeFromNodeModules, copyTreeSitterWasms, copyTreeSitterTagQryFiles, @@ -31,6 +33,11 @@ fs.mkdirSync(path.join(__dirname, "..", "out", "node_modules"), { recursive: true, }); +const guiDist = path.join(__dirname, "..", "..", "..", "gui", "dist"); +if (!fs.existsSync(guiDist)) { + fs.mkdirSync(guiDist, { recursive: true }); +} + // Get the target to package for let target = undefined; const args = process.argv; @@ -78,6 +85,9 @@ function isWin() { async function package(target, os, arch, exe) { console.log("[info] Packaging extension for target ", target); + // Copy config_schema.json to config.json in docs and intellij + copyConfigSchema(); + // Install node_modules installNodeModules(); diff --git a/extensions/vscode/scripts/utils.js b/extensions/vscode/scripts/utils.js index e3e3e0c931..d190315c14 100644 --- a/extensions/vscode/scripts/utils.js +++ b/extensions/vscode/scripts/utils.js @@ -10,6 +10,35 @@ const { const continueDir = path.join(__dirname, "..", "..", ".."); +function copyConfigSchema() { + fs.copyFileSync( + "config_schema.json", + path.join("..", "..", "docs", "static", "schemas", "config.json"), + ); + fs.copyFileSync( + "config_schema.json", + path.join( + "..", + "intellij", + "src", + "main", + "resources", + "config_schema.json", + ), + ); + // Modify and copy for .continuerc.json + const schema = JSON.parse(fs.readFileSync("config_schema.json", "utf8")); + schema.definitions.SerializedContinueConfig.properties.mergeBehavior = { + type: "string", + enum: ["merge", "overwrite"], + default: "merge", + title: "Merge behavior", + markdownDescription: + "If set to 'merge', .continuerc.json will be applied on top of config.json (arrays and objects are merged). If set to 'overwrite', then every top-level property of .continuerc.json will overwrite that property from config.json.", + }; + fs.writeFileSync("continue_rc_schema.json", JSON.stringify(schema, null, 2)); +} + function copyTokenizers() { fs.copyFileSync( path.join(__dirname, "../../../core/llm/llamaTokenizerWorkerPool.mjs"), @@ -33,6 +62,90 @@ function installNodeModules() { // Install node_modules // execCmdSync("npm install"); console.log("[info] npm install in extensions/vscode completed"); + + process.chdir(path.join(continueDir, "gui")); + + execCmdSync("npm install"); + console.log("[info] npm install in gui completed"); +} + +async function buildGui(isGhAction) { + // Make sure we are in the right directory + if (!process.cwd().endsWith("gui")) { + process.chdir(path.join(continueDir, "gui")); + } + if (isGhAction) { + execCmdSync("npm run build"); + } + + // Copy over the dist folder to the JetBrains extension // + const intellijExtensionWebviewPath = path.join( + "..", + "extensions", + "intellij", + "src", + "main", + "resources", + "webview", + ); + + const indexHtmlPath = path.join(intellijExtensionWebviewPath, "index.html"); + fs.copyFileSync(indexHtmlPath, "tmp_index.html"); + rimrafSync(intellijExtensionWebviewPath); + fs.mkdirSync(intellijExtensionWebviewPath, { recursive: true }); + + await new Promise((resolve, reject) => { + ncp("dist", intellijExtensionWebviewPath, (error) => { + if (error) { + console.warn( + "[error] Error copying React app build to JetBrains extension: ", + error, + ); + reject(error); + } + resolve(); + }); + }); + + // Put back index.html + if (fs.existsSync(indexHtmlPath)) { + rimrafSync(indexHtmlPath); + } + fs.copyFileSync("tmp_index.html", indexHtmlPath); + fs.unlinkSync("tmp_index.html"); + + // Copy over other misc. files + fs.copyFileSync( + "../extensions/vscode/gui/onigasm.wasm", + path.join(intellijExtensionWebviewPath, "onigasm.wasm"), + ); + + console.log("[info] Copied gui build to JetBrains extension"); + + // Then copy over the dist folder to the VSCode extension // + const vscodeGuiPath = path.join("../extensions/vscode/gui"); + fs.mkdirSync(vscodeGuiPath, { recursive: true }); + await new Promise((resolve, reject) => { + ncp("dist", vscodeGuiPath, (error) => { + if (error) { + console.log( + "Error copying React app build to VSCode extension: ", + error, + ); + reject(error); + } else { + console.log("Copied gui build to VSCode extension"); + resolve(); + } + }); + }); + + 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"); + } } async function copyOnnxRuntimeFromNodeModules(target) { @@ -346,7 +459,9 @@ async function installNodeModuleInTempDirAndCopyToCurrent(packageName, toCopy) { } module.exports = { + copyConfigSchema, installNodeModules, + buildGui, copyOnnxRuntimeFromNodeModules, copyTreeSitterWasms, copyTreeSitterTagQryFiles, diff --git a/extensions/vscode/src/AICore/AICore.ts b/extensions/vscode/src/AICore/AICore.ts index 1bae57550c..9205e772bb 100644 --- a/extensions/vscode/src/AICore/AICore.ts +++ b/extensions/vscode/src/AICore/AICore.ts @@ -8,26 +8,6 @@ import { BasToolkit } from "@sap-devx/app-studio-toolkit-types"; const basAPI: BasToolkit = vscode.extensions.getExtension("SAPOSS.app-studio-toolkit")?.exports; -interface ModelDetail { - name: string; - version?: string; -} - -interface BackendDetails { - model: ModelDetail; -} - -interface ResourceDetails { - resources: { - backend_details: BackendDetails; - }; -} - -interface Resource { - id: string; - details?: ResourceDetails; -} - class AICore extends BaseLLM { static providerName: ModelProvider = "aicore"; static defaultOptions: Partial = { @@ -45,7 +25,7 @@ class AICore extends BaseLLM { } async listModels(): Promise { - return ["gpt-4o", "gpt-35-turbo", "llama3"]; + return ["gpt-4o-mini"]; } protected async *_streamComplete(prompt: string, options: CompletionOptions): AsyncGenerator { diff --git a/extensions/vscode/src/extension.ts b/extensions/vscode/src/extension.ts index 9bb2fa39c8..39f90413fe 100644 --- a/extensions/vscode/src/extension.ts +++ b/extensions/vscode/src/extension.ts @@ -13,7 +13,8 @@ async function dynamicImportAndActivate(context: vscode.ExtensionContext) { // BAS Customization - check if LLM service is available if (isLLMServiceAvailable()) { return activateExtension(context); - } } catch (e) { + } + } catch (e) { console.log("Error activating extension: ", e); vscode.window .showInformationMessage( diff --git a/extensions/vscode/src/util/loadAutocompleteModel.ts b/extensions/vscode/src/util/loadAutocompleteModel.ts index 6267807339..c1c6745334 100644 --- a/extensions/vscode/src/util/loadAutocompleteModel.ts +++ b/extensions/vscode/src/util/loadAutocompleteModel.ts @@ -7,8 +7,8 @@ import AICore from "../AICore/AICore"; export class TabAutocompleteModel { private _llm: ILLM | undefined; - private defaultTag = "gpt-4o"; - private defaultTagName = "gpt 4o"; + private defaultTag = "gpt-4o-mini"; + private defaultTagName = "gpt 4o mini"; private globalContext: GlobalContext = new GlobalContext(); private shownOllamaWarning = false; From afbea801a67be3668350ae338788d5b13c3fe796 Mon Sep 17 00:00:00 2001 From: Neta Date: Tue, 15 Oct 2024 13:56:10 +0300 Subject: [PATCH 3/3] fix: fixes --- extensions/vscode/package.json | 2 +- .../scripts/prepackage-cross-platform.js | 1 - extensions/vscode/scripts/prepackage.js | 154 +++++++++++++++--- extensions/vscode/src/AICore/AICore.ts | 2 +- 4 files changed, 137 insertions(+), 22 deletions(-) diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 7096a1d3c4..3f45515ca5 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -1,7 +1,7 @@ { "name": "ai-code-assistant", "icon": "media/icon.png", - "version": "1.0.2", + "version": "1.0.1", "repository": { "type": "git", "url": "https://github.com/continuedev/continue" diff --git a/extensions/vscode/scripts/prepackage-cross-platform.js b/extensions/vscode/scripts/prepackage-cross-platform.js index 16f6186f48..76ca80d659 100644 --- a/extensions/vscode/scripts/prepackage-cross-platform.js +++ b/extensions/vscode/scripts/prepackage-cross-platform.js @@ -32,7 +32,6 @@ rimrafSync(path.join(__dirname, "..", "out")); fs.mkdirSync(path.join(__dirname, "..", "out", "node_modules"), { recursive: true, }); - const guiDist = path.join(__dirname, "..", "..", "..", "gui", "dist"); if (!fs.existsSync(guiDist)) { fs.mkdirSync(guiDist, { recursive: true }); diff --git a/extensions/vscode/scripts/prepackage.js b/extensions/vscode/scripts/prepackage.js index 7a17072f6c..880afe99b8 100644 --- a/extensions/vscode/scripts/prepackage.js +++ b/extensions/vscode/scripts/prepackage.js @@ -14,6 +14,10 @@ rimrafSync(path.join(__dirname, "..", "out")); fs.mkdirSync(path.join(__dirname, "..", "out", "node_modules"), { recursive: true, }); +const guiDist = path.join(__dirname, "..", "..", "..", "gui", "dist"); +if (!fs.existsSync(guiDist)) { + fs.mkdirSync(guiDist, { recursive: true }); +} // Get the target to package for let target = undefined; @@ -44,25 +48,117 @@ const exe = os === "win32" ? ".exe" : ""; (async () => { console.log("[info] Packaging extension for target ", target); + // Copy config_schema.json to config.json in docs and intellij + fs.copyFileSync( + "config_schema.json", + path.join("..", "..", "docs", "static", "schemas", "config.json"), + ); + fs.copyFileSync( + "config_schema.json", + path.join( + "..", + "intellij", + "src", + "main", + "resources", + "config_schema.json", + ), + ); + // Modify and copy for .continuerc.json + const schema = JSON.parse(fs.readFileSync("config_schema.json", "utf8")); + schema.definitions.SerializedContinueConfig.properties.mergeBehavior = { + type: "string", + enum: ["merge", "overwrite"], + default: "merge", + title: "Merge behavior", + markdownDescription: + "If set to 'merge', .continuerc.json will be applied on top of config.json (arrays and objects are merged). If set to 'overwrite', then every top-level property of .continuerc.json will overwrite that property from config.json.", + }; + fs.writeFileSync("continue_rc_schema.json", JSON.stringify(schema, null, 2)); + if (!process.cwd().endsWith("vscode")) { // This is sometimes run from root dir instead (e.g. in VS Code tasks) process.chdir("extensions/vscode"); } // Install node_modules // - execCmdSync("npm install --registry=https://int.repositories.cloud.sap/artifactory/api/npm/build-releases-npm"); + execCmdSync("npm install"); console.log("[info] npm install in extensions/vscode completed"); + process.chdir("../../gui"); + + execCmdSync("npm install"); + console.log("[info] npm install in gui completed"); if (ghAction()) { execCmdSync("npm run build"); } - // Copy over native / wasm modules // - if (!process.cwd().endsWith("vscode")) { - // This is sometimes run from root dir instead (e.g. in VS Code tasks) - process.chdir("../extensions/vscode"); + // Copy over the dist folder to the JetBrains extension // + const intellijExtensionWebviewPath = path.join( + "..", + "extensions", + "intellij", + "src", + "main", + "resources", + "webview", + ); + + const indexHtmlPath = path.join(intellijExtensionWebviewPath, "index.html"); + fs.copyFileSync(indexHtmlPath, "tmp_index.html"); + rimrafSync(intellijExtensionWebviewPath); + fs.mkdirSync(intellijExtensionWebviewPath, { recursive: true }); + + await new Promise((resolve, reject) => { + ncp("dist", intellijExtensionWebviewPath, (error) => { + if (error) { + console.warn( + "[error] Error copying React app build to JetBrains extension: ", + error, + ); + reject(error); + } + resolve(); + }); + }); + + // Put back index.html + if (fs.existsSync(indexHtmlPath)) { + rimrafSync(indexHtmlPath); } + fs.copyFileSync("tmp_index.html", indexHtmlPath); + fs.unlinkSync("tmp_index.html"); + + // Copy over other misc. files + fs.copyFileSync( + "../extensions/vscode/gui/onigasm.wasm", + path.join(intellijExtensionWebviewPath, "onigasm.wasm"), + ); + + console.log("[info] Copied gui build to JetBrains extension"); + + // Then copy over the dist folder to the VSCode extension // + const vscodeGuiPath = path.join("../extensions/vscode/gui"); + fs.mkdirSync(vscodeGuiPath, { recursive: true }); + await new Promise((resolve, reject) => { + ncp("dist", vscodeGuiPath, (error) => { + if (error) { + console.log( + "Error copying React app build to VSCode extension: ", + error, + ); + reject(error); + } else { + console.log("Copied gui build to VSCode extension"); + resolve(); + } + }); + }); + + // Copy over native / wasm modules // + process.chdir("../extensions/vscode"); + fs.mkdirSync("bin", { recursive: true }); // onnxruntime-node @@ -166,6 +262,22 @@ const exe = os === "win32" ? ".exe" : ""; // }, // ); + // textmate-syntaxes + await new Promise((resolve, reject) => { + ncp( + path.join(__dirname, "../textmate-syntaxes"), + path.join(__dirname, "../gui/textmate-syntaxes"), + (error) => { + if (error) { + console.warn("[error] Error copying textmate-syntaxes", error); + reject(error); + } else { + resolve(); + } + }, + ); + }); + function ghAction() { return !!process.env.GITHUB_ACTIONS; } @@ -205,7 +317,7 @@ const exe = os === "win32" ? ".exe" : ""; process.chdir(tempDir); // Initialize a new package.json and install the package - execCmdSync(`npm init -y && npm i --registry=https://int.repositories.cloud.sap/artifactory/api/npm/build-releases-npm -f ${packageName} --no-save`); + execCmdSync(`npm init -y && npm i -f ${packageName} --no-save`); console.log( `Contents of: ${packageName}`, @@ -216,24 +328,28 @@ const exe = os === "win32" ? ".exe" : ""; // Ideally we validate file integrity in the validation at the end await new Promise((resolve) => setTimeout(resolve, 2000)); - // Ensure the target directory exists - const targetDir = path.join(currentDir, "node_modules", toCopy); - fs.mkdirSync(path.dirname(targetDir), { recursive: true }); - // Copy the installed package back to the current directory await new Promise((resolve, reject) => { - ncp(path.join(tempDir, "node_modules", toCopy), targetDir, { dereference: true }, (error) => { - if (error) { - console.error(`[error] Error copying ${packageName} package`, error); - reject(error); - } else { - resolve(); - } - }); + ncp( + path.join(tempDir, "node_modules", toCopy), + path.join(currentDir, "node_modules", toCopy), + { dereference: true }, + (error) => { + if (error) { + console.error( + `[error] Error copying ${packageName} package`, + error, + ); + reject(error); + } else { + resolve(); + } + }, + ); }); } finally { // Clean up the temporary directory - rimrafSync(tempDir); + // rimrafSync(tempDir); // Return to the original directory process.chdir(currentDir); diff --git a/extensions/vscode/src/AICore/AICore.ts b/extensions/vscode/src/AICore/AICore.ts index 9205e772bb..edbd05489f 100644 --- a/extensions/vscode/src/AICore/AICore.ts +++ b/extensions/vscode/src/AICore/AICore.ts @@ -11,7 +11,7 @@ const basAPI: BasToolkit = vscode.extensions.getExtension("SAPOSS.app-studio-too class AICore extends BaseLLM { static providerName: ModelProvider = "aicore"; static defaultOptions: Partial = { - model: "gpt-4o", + model: "gpt-4o-mini", }; constructor(options: LLMOptions) {