├── .github └── workflows │ └── publish.yaml ├── .gitignore ├── .nvmrc ├── .vscode-test.mjs ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── assets └── logo-small.png ├── esbuild.js ├── esbuild └── flow-plugin.js ├── eslint.config.mjs ├── package-lock.json ├── package.json ├── prettier.config.js ├── scripts └── versionCheck.js ├── src ├── ChatModel.ts ├── PanelProvider.ts ├── browser │ ├── dispose.ts │ ├── simpleBrowserManager.ts │ ├── simpleBrowserView.ts │ └── types.ts ├── core │ ├── activeChanges │ │ ├── activeFilesTracker.ts │ │ ├── fileStateFromPreviousCommit.ts │ │ ├── graphTopologicalSort.ts │ │ ├── timeline.ts │ │ └── trackCodeSymbolChanges.ts │ ├── chatState │ │ ├── convertStreamToMessage.ts │ │ ├── decorations │ │ │ └── add.ts │ │ ├── getContextForCodeSelection.ts │ │ ├── promptClassifier.ts │ │ ├── prompts.ts │ │ └── state.ts │ ├── completions │ │ ├── README.md │ │ ├── artificial-delay.ts │ │ ├── can-use-partial-completion.ts │ │ ├── completion-provider-config.ts │ │ ├── create-inline-completion-item-provider.ts │ │ ├── createInlineCompletionItemProvider.ts │ │ ├── detect-multiline.ts │ │ ├── detectIndent.ts │ │ ├── doc-context-getters.ts │ │ ├── first-completion-decoration-handler.ts │ │ ├── format-completion.ts │ │ ├── get-current-doc-context.ts │ │ ├── get-inline-completions.ts │ │ ├── helpers │ │ │ └── vscodeApi.ts │ │ ├── inline-completion-item-provider.ts │ │ ├── is-completion-visible.ts │ │ ├── language_config.ts │ │ ├── logger.ts │ │ ├── persistence-tracker.ts │ │ ├── providers │ │ │ ├── aideAgentProvider.ts │ │ │ ├── chatprovider.ts │ │ │ ├── dynamic-multiline.ts │ │ │ ├── editorSessionProvider.ts │ │ │ ├── fetch-and-process-completions.ts │ │ │ ├── hot-streak.ts │ │ │ ├── probeProvider.ts │ │ │ ├── provider.ts │ │ │ ├── reportEditorSessionAnswerStream.ts │ │ │ └── sidecarProvider.ts │ │ ├── request-manager.ts │ │ ├── reuse-last-candidate.ts │ │ ├── statistics.ts │ │ ├── suggested-autocomplete-items-cache.ts │ │ ├── testFile.ts │ │ ├── text-processing │ │ │ ├── index.ts │ │ │ ├── parse-and-truncate-completion.ts │ │ │ ├── parse-completion.ts │ │ │ ├── process-inline-completions.ts │ │ │ ├── string-comparator.ts │ │ │ ├── treeSitter │ │ │ │ ├── parseTree.ts │ │ │ │ └── wasm │ │ │ │ │ ├── tree-sitter-go.wasm │ │ │ │ │ ├── tree-sitter-javascript.wasm │ │ │ │ │ ├── tree-sitter-python.wasm │ │ │ │ │ ├── tree-sitter-rust.wasm │ │ │ │ │ ├── tree-sitter-tsx.wasm │ │ │ │ │ └── tree-sitter-typescript.wasm │ │ │ ├── truncate-multiline-completion.ts │ │ │ ├── truncate-parsed-completion.ts │ │ │ └── utils.ts │ │ ├── tracked-range.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── context │ │ └── providers │ │ │ ├── BaseContextProvider.ts │ │ │ ├── FileContextProvider.ts │ │ │ ├── RelativeFileContextProvider.ts │ │ │ └── types.ts │ ├── csEvents │ │ └── csEventHandler.ts │ ├── editor │ │ ├── activeView │ │ │ └── ranges.ts │ │ ├── codeSelection.ts │ │ └── limiter.ts │ ├── git │ │ └── helper.ts │ ├── inlineCompletion │ │ ├── README.md │ │ ├── commands.ts │ │ ├── helpers │ │ │ ├── cachedCompletions.ts │ │ │ ├── completionContextCheck.ts │ │ │ ├── middleOfLine.ts │ │ │ ├── multiLine.ts │ │ │ ├── promptCache.ts │ │ │ └── promptWrapper.ts │ │ ├── sidecarCompletion.ts │ │ └── statusBar.ts │ ├── languages │ │ ├── codeSymbolsIndexerTypes.ts │ │ ├── goCodeSymbols.ts │ │ ├── languageCodeSymbols.ts │ │ ├── tree-sitter-go.wasm │ │ ├── tree-sitter-javascript.wasm │ │ ├── tree-sitter-python.wasm │ │ ├── tree-sitter-rust.wasm │ │ ├── tree-sitter-tsx.wasm │ │ └── tree-sitter-typescript.wasm │ ├── llm │ │ └── recipe │ │ │ ├── helpers.ts │ │ │ └── prompts.ts │ ├── posthog │ │ ├── client.ts │ │ └── logChatPrompt.ts │ ├── quickActions │ │ └── fix.ts │ ├── server │ │ ├── applyEdits.ts │ │ ├── createFile.ts │ │ ├── diagnostics.ts │ │ ├── editedFiles.ts │ │ ├── goToDefinition.ts │ │ ├── goToImplementation.ts │ │ ├── goToReferences.ts │ │ ├── goToTypeDefinition.ts │ │ ├── inlayHints.ts │ │ ├── openFile.ts │ │ ├── outlineNodes.ts │ │ ├── previousWordCommand.ts │ │ ├── quickFix.ts │ │ ├── requestHandler.ts │ │ ├── symbolSearch.ts │ │ └── types.ts │ ├── sidecar │ │ ├── client.ts │ │ ├── default-shell.ts │ │ ├── providerConfigTypes.ts │ │ ├── ssestream.ts │ │ └── types.ts │ ├── storage │ │ └── types.ts │ ├── subscriptions │ │ └── timekeeper.ts │ ├── terminal │ │ ├── TerminalManager.ts │ │ ├── TerminalProcess.ts │ │ ├── TerminalRegistry.ts │ │ └── p-timeout.ts │ ├── test │ │ ├── testingA.ts │ │ ├── testingB.ts │ │ └── testingC.ts │ ├── testFramework │ │ └── jest.ts │ ├── timeline │ │ └── events │ │ │ ├── collection.ts │ │ │ └── type.ts │ └── utilities │ │ ├── activateLSP.ts │ │ ├── activeDirectories.ts │ │ ├── async.ts │ │ ├── commandRunner.ts │ │ ├── copySettings.ts │ │ ├── dir.ts │ │ ├── embeddingsHelpers.ts │ │ ├── extensionBlockList.ts │ │ ├── files.ts │ │ ├── gcpBucket.ts │ │ ├── getSelectionContext.ts │ │ ├── getUri.ts │ │ ├── ignore.ts │ │ ├── lspApi.ts │ │ ├── mergeModificationChangesToFile.ts │ │ ├── modelSelection.ts │ │ ├── objects.ts │ │ ├── openTabs.ts │ │ ├── parseTypescript.ts │ │ ├── path.ts │ │ ├── paths.ts │ │ ├── planTimer.ts │ │ ├── previousWordInText.ts │ │ ├── pythonServerClient.ts │ │ ├── readonlyFS.ts │ │ ├── reportIndexingUpdate.ts │ │ ├── ripGrep.ts │ │ ├── setupAntonBackend.ts │ │ ├── setupSidecarBinary.ts │ │ ├── sidecarUrl.ts │ │ ├── sleep.ts │ │ ├── stateManager.ts │ │ ├── stringifyEvent.ts │ │ ├── systemInstruction.ts │ │ ├── types.ts │ │ ├── uniqueId.ts │ │ └── workspaceContext.ts ├── declarations.d.ts ├── devtools │ └── react │ │ ├── DevtoolsManager.ts │ │ ├── dist │ │ ├── backend.js │ │ ├── backend.js.map │ │ ├── standalone.js │ │ └── standalone.js.map │ │ ├── proxy.ts │ │ └── types.ts ├── esbuild-plugins.js ├── extension.ts ├── icon.png ├── ide.ts ├── ideUtils.ts ├── index.d.ts ├── logger.ts ├── model.ts ├── preview-src │ ├── events.ts │ ├── preview-index.ts │ ├── preview-main.css │ └── sw.js ├── test.ts ├── testing.ts ├── types.ts ├── utils │ ├── files.js │ ├── getNonce.ts │ ├── path.ts │ └── port.ts └── webviews │ ├── @settings │ ├── form.tsx │ ├── preset-view.tsx │ ├── settings-view.tsx │ ├── use-preset.tsx │ └── welcome-view.tsx │ ├── @task │ ├── use-task.tsx │ └── view.tsx │ ├── app.tsx │ ├── assets │ ├── image.svg │ └── providers-logos │ │ ├── anthropic.svg │ │ ├── aws-bedrock.svg │ │ ├── gemini.svg │ │ ├── ollama.svg │ │ ├── open-router.svg │ │ └── openai.svg │ ├── components │ ├── button.tsx │ ├── checkbox.tsx │ ├── context-summary.tsx │ ├── cost-icon.tsx │ ├── detail-summary.tsx │ ├── exchange │ │ ├── exchange-base.tsx │ │ ├── request.tsx │ │ └── response.tsx │ ├── fileicon.tsx │ ├── input.tsx │ ├── input │ │ ├── InputToolbar.tsx │ │ ├── MentionExtension.ts │ │ ├── MentionList.tsx │ │ ├── TipTapEditor.tsx │ │ ├── resolveInput.ts │ │ ├── suggestions.ts │ │ └── types.ts │ ├── markdown-renderer.tsx │ ├── preset.tsx │ ├── progress-indicator.tsx │ ├── safeimg.tsx │ ├── select.tsx │ ├── spinner.tsx │ ├── task-definition-list.tsx │ ├── terminal-preview.tsx │ ├── textarea.tsx │ └── usage-list.tsx │ ├── hooks │ ├── useInputHistory.tsx │ └── useUpdatingRef.tsx │ ├── index.tsx │ ├── routes.tsx │ ├── store │ └── submenuContext.ts │ ├── style.css │ ├── svg.d.ts │ ├── tsconfig.json │ └── utils │ ├── cn.ts │ ├── form.ts │ ├── hooks │ └── use-async.ts │ ├── localStorage.ts │ ├── nonce.ts │ ├── string.ts │ └── types.ts ├── tailwind.config.js ├── tsconfig.json └── vsc-extension-quickstart.md /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | .DS_Store 7 | 8 | !src/devtools/react/dist -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20.11.0 2 | -------------------------------------------------------------------------------- /.vscode-test.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@vscode/test-cli'; 2 | 3 | export default defineConfig({ 4 | files: 'out/test/**/*.test.js', 5 | }); 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint", "connor4312.esbuild-problem-matchers", "ms-vscode.extension-test-runner"] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/dist/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "watch", 8 | "dependsOn": [ 9 | "npm: watch:tsc", 10 | "npm: watch:esbuild" 11 | ], 12 | "presentation": { 13 | "reveal": "never" 14 | }, 15 | "group": { 16 | "kind": "build", 17 | "isDefault": true 18 | } 19 | }, 20 | { 21 | "type": "npm", 22 | "script": "watch:esbuild", 23 | "group": "build", 24 | "problemMatcher": "$esbuild-watch", 25 | "isBackground": true, 26 | "label": "npm: watch:esbuild", 27 | "presentation": { 28 | "group": "watch", 29 | "reveal": "never" 30 | } 31 | }, 32 | { 33 | "type": "npm", 34 | "script": "watch:tsc", 35 | "group": "build", 36 | "problemMatcher": "$tsc-watch", 37 | "isBackground": true, 38 | "label": "npm: watch:tsc", 39 | "presentation": { 40 | "group": "watch", 41 | "reveal": "never" 42 | } 43 | }, 44 | { 45 | "type": "npm", 46 | "script": "watch-tests", 47 | "problemMatcher": "$tsc-watch", 48 | "isBackground": true, 49 | "presentation": { 50 | "reveal": "never", 51 | "group": "watchers" 52 | }, 53 | "group": "build" 54 | }, 55 | { 56 | "label": "tasks: watch-tests", 57 | "dependsOn": [ 58 | "npm: watch", 59 | "npm: watch-tests" 60 | ], 61 | "problemMatcher": [] 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/** 4 | node_modules/** 5 | src/** 6 | .gitignore 7 | .yarnrc 8 | esbuild.js 9 | vsc-extension-quickstart.md 10 | **/tsconfig.json 11 | **/eslint.config.mjs 12 | **/*.map 13 | **/*.ts 14 | **/.vscode-test.* 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "extension" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | **SotaSWE is a VSCode extension built atop [the leading agentic framework](https://github.com/codestoryai/sidecar) on SWE-bench Lite.** 7 | 8 | ![Latest release](https://img.shields.io/github/v/release/codestoryai/extension?label=version) 9 | ![Discord Shield](https://discord.com/api/guilds/1138070673756004464/widget.png?style=shield) 10 | 11 |
12 | 13 | SOTA SWE in action 14 | 15 |
16 |
17 | Click preview to watch SOTA SWE in action 18 |
19 |
20 | 21 | - **Agentic code editing** - SOTA SWE can complete complex tasks across your codebase in agentic fashion, taking multiple steps as necessary - starting from reading the right files from your codebase, making necessary changes in-place, and iterating on it's own work in case there are pending items or issues. 22 | - **Terminal command execution** - The agent can execute terminal commands on your behalf, and can also read the output of the command as part of it's decision making process. 23 | - **Bring your own key** - We support Claude Sonnet provided by Anthropic as well as Open Router. Just provide your API key as part of the onboarding and you are good to go. 24 | - **Provide context** - Though not a necessary step, you can guide the agent further by providing context using `@` to specify files as part of your prompt. 25 | 26 | ## Contributing 27 | 28 | There are many ways in which you can participate in this project, for example: 29 | 30 | - [Submit bugs and feature requests](https://github.com/codestoryai/extension/issues) 31 | - Review [source code changes](https://github.com/codestoryai/extension/pulls) 32 | 33 | If you are interested in fixing issues and contributing directly to the code base, 34 | 35 | 1. Install dependencies 36 | 37 | ```shell 38 | npm install 39 | ``` 40 | 41 | 2. Run the extension in development mode with hot-reload 42 | 43 | ```shell 44 | npm run watch 45 | ``` 46 | 47 | 3. Open the repo in VSCode and press `F5` to start debugging. 48 | 49 | 4. (Optional) if you'd like to run sidecar locally too, [follow the instructions here](https://github.com/codestoryai/sidecar/blob/main/HOW_TO_CONTRIBUTE.md). 50 | 51 | ## Feedback 52 | 53 | - Upvote [popular feature requests](https://github.com/codestoryai/extension/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc) 54 | - Join our community: [Discord](https://discord.gg/mtgrhXM5Xf) 55 | 56 | ## Code of Conduct 57 | 58 | This project has adopted the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). Please read the Code of Conduct before contributing to this project. 59 | 60 | ## License 61 | 62 | Copyright (c) 2024 CodeStory AI. All rights reserved. 63 | Licensed under the [GNU Affero General Public License v3.0](LICENSE.md). 64 | -------------------------------------------------------------------------------- /assets/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/assets/logo-small.png -------------------------------------------------------------------------------- /esbuild/flow-plugin.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const babel = require('@babel/core'); 4 | 5 | const babelFlowPlugin = { 6 | name: 'babel-flow', 7 | setup(build) { 8 | build.onLoad({ filter: /\.js$/ }, async (args) => { 9 | const source = await fs.promises.readFile(args.path, 'utf8'); 10 | const result = await babel.transformAsync(source, { 11 | filename: args.path, 12 | presets: ['@babel/preset-flow'], 13 | sourceMaps: true, 14 | }); 15 | let code = result.code; 16 | // Inline the source map if it exists 17 | if (result.map) { 18 | const dataUrl = 19 | 'data:application/json;base64,' + 20 | Buffer.from(JSON.stringify(result.map)).toString('base64'); 21 | code += `\n//# sourceMappingURL=${dataUrl}`; 22 | } 23 | return { 24 | contents: code, 25 | loader: 'js', 26 | }; 27 | }); 28 | }, 29 | }; 30 | 31 | module.exports = babelFlowPlugin; 32 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import typescriptEslint from '@typescript-eslint/eslint-plugin'; 2 | import tsParser from '@typescript-eslint/parser'; 3 | 4 | export default [ 5 | { 6 | ignores: ['src/devtools/react/dist/**'], 7 | }, 8 | { 9 | files: ['**/*.ts'], 10 | }, 11 | { 12 | plugins: { 13 | '@typescript-eslint': typescriptEslint, 14 | }, 15 | 16 | languageOptions: { 17 | parser: tsParser, 18 | ecmaVersion: 2022, 19 | sourceType: 'module', 20 | }, 21 | 22 | rules: { 23 | '@typescript-eslint/naming-convention': [ 24 | 'warn', 25 | { 26 | selector: 'import', 27 | format: ['camelCase', 'PascalCase'], 28 | }, 29 | ], 30 | 31 | curly: 'warn', 32 | eqeqeq: 'warn', 33 | 'no-throw-literal': 'warn', 34 | semi: 'warn', 35 | }, 36 | }, 37 | ]; 38 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: true, 6 | singleQuote: true, 7 | jsxSingleQuote: false, 8 | trailingComma: "es5", 9 | bracketSpacing: true, 10 | bracketSameLine: false, 11 | arrowParens: "always", 12 | proseWrap: "preserve", 13 | htmlWhitespaceSensitivity: "css", 14 | embeddedLanguageFormatting: "auto", 15 | plugins: [ 16 | "prettier-plugin-tailwindcss" 17 | ] 18 | }; -------------------------------------------------------------------------------- /scripts/versionCheck.js: -------------------------------------------------------------------------------- 1 | // Make sure odd-numbered version isn't published to main release. This will irreversibly cause us to bump the minor version by 2 2 | const fs = require("fs"); 3 | 4 | const packageJson = fs.readFileSync("package.json"); 5 | const packageJsonJson = JSON.parse(packageJson); 6 | const version = packageJsonJson.version; 7 | const minor = parseInt(version.split(".")[1]); 8 | if (minor % 2 !== 0) { 9 | throw new Error( 10 | "Do not publish odd-numbered version to main VS Code release!" 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/ChatModel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | import * as vscode from "vscode"; 3 | import { 4 | AideAgentResponseStream, 5 | AideAgentEditsInfo, 6 | AideAgentPlanInfo, 7 | AideCommand, 8 | AideAgentStreamingState, 9 | AideChatStep, 10 | AideAgentResponsePart, 11 | AideAgentThinkingForEdit, 12 | AideAgentPlanRegenerateInformation, 13 | } from "./types"; 14 | 15 | export class Response implements AideAgentResponseStream { 16 | constructor() {} 17 | 18 | markdown(value: unknown): void {} 19 | anchor(value: unknown, title?: unknown): void {} 20 | filetree(value: unknown, baseUri: unknown): void {} 21 | progress(value: unknown, task?: unknown): void {} 22 | reference(value: unknown, iconPath?: unknown): void {} 23 | textEdit( 24 | target: vscode.Uri, 25 | edits: vscode.TextEdit | vscode.TextEdit[] 26 | ): void {} 27 | codeblockUri(uri: vscode.Uri): void {} 28 | confirmation( 29 | title: string, 30 | message: string, 31 | data: any, 32 | buttons?: string[] 33 | ): void {} 34 | warning(message: string | vscode.MarkdownString): void {} 35 | codeCitation(value: vscode.Uri, license: string, snippet: string): void {} 36 | 37 | editsInfo(edits: AideAgentEditsInfo) {} 38 | 39 | planInfo(plan: AideAgentPlanInfo) {} 40 | button(command: AideCommand) {} 41 | buttonGroup(commands: AideCommand[]) {} 42 | streamingState(state: AideAgentStreamingState) {} 43 | codeEdit(edits: vscode.WorkspaceEdit) {} 44 | step(step: AideChatStep) {} 45 | push(part: AideAgentResponsePart) {} 46 | thinkingForEdit(part: AideAgentThinkingForEdit) {} 47 | regeneratePlan(planInformation: AideAgentPlanRegenerateInformation) {} 48 | close() {} 49 | } 50 | 51 | class ChatModel {} 52 | */ 53 | -------------------------------------------------------------------------------- /src/browser/dispose.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | 8 | export function disposeAll(disposables: vscode.Disposable[]) { 9 | while (disposables.length) { 10 | const item = disposables.pop(); 11 | item?.dispose(); 12 | } 13 | } 14 | 15 | export abstract class Disposable { 16 | private _isDisposed = false; 17 | 18 | protected _disposables: vscode.Disposable[] = []; 19 | 20 | public dispose(): any { 21 | if (this._isDisposed) { 22 | return; 23 | } 24 | this._isDisposed = true; 25 | disposeAll(this._disposables); 26 | } 27 | 28 | protected _register(value: T): T { 29 | if (this._isDisposed) { 30 | value.dispose(); 31 | } else { 32 | this._disposables.push(value); 33 | } 34 | return value; 35 | } 36 | 37 | protected get isDisposed() { 38 | return this._isDisposed; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/browser/simpleBrowserManager.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | // @ts-ignore 8 | // import { initialize, connectWithCustomMessagingProtocol } from "react-devtools-core"; 9 | import { ShowOptions, SimpleBrowserView } from './simpleBrowserView'; 10 | 11 | export class SimpleBrowserManager { 12 | 13 | private _activeView?: SimpleBrowserView; 14 | 15 | constructor( 16 | private readonly extensionUri: vscode.Uri, 17 | ) { 18 | //initialize(); 19 | } 20 | 21 | dispose() { 22 | this._activeView?.dispose(); 23 | this._activeView = undefined; 24 | } 25 | 26 | public show(inputUri: string | vscode.Uri, options?: ShowOptions): void { 27 | const url = typeof inputUri === 'string' ? inputUri : inputUri.toString(true); 28 | if (this._activeView) { 29 | this._activeView.show(url, options); 30 | } else { 31 | const view = SimpleBrowserView.create(this.extensionUri, url, options); 32 | this.registerWebviewListeners(view); 33 | 34 | this._activeView = view; 35 | } 36 | } 37 | 38 | public restore(panel: vscode.WebviewPanel, state: any): void { 39 | const url = state?.url ?? ''; 40 | const view = SimpleBrowserView.restore(this.extensionUri, url, panel, state.displayUrl); 41 | this.registerWebviewListeners(view); 42 | this._activeView ??= view; 43 | } 44 | 45 | private registerWebviewListeners(view: SimpleBrowserView) { 46 | view.onDispose(() => { 47 | if (this._activeView === view) { 48 | this._activeView = undefined; 49 | } 50 | }); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/core/activeChanges/activeFilesTracker.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { TextDocument, TextEditor } from 'vscode'; 7 | 8 | 9 | export class ActiveFilesTracker { 10 | private _activeFiles: string[] = []; 11 | 12 | constructor() { 13 | this._activeFiles = []; 14 | } 15 | 16 | getActiveFiles(): string[] { 17 | return this._activeFiles; 18 | } 19 | 20 | openTextDocument(document: TextDocument) { 21 | if (document.uri.scheme !== 'file') { 22 | return; 23 | } 24 | // Check if the document is already in our list (for example, if it's a re-open) 25 | const index = this._activeFiles.findIndex(filePath => filePath === document.uri.fsPath); 26 | 27 | if (index === -1) { 28 | // Append the document to the end of our list if it's a new open 29 | this._activeFiles.push(document.uri.fsPath); 30 | } 31 | } 32 | 33 | onCloseTextDocument(document: TextDocument) { 34 | if (document.uri.scheme !== 'file') { 35 | return; 36 | } 37 | // Remove the document from our list 38 | const index = this._activeFiles.findIndex(filePath => filePath === document.uri.fsPath); 39 | 40 | if (index !== -1) { 41 | this._activeFiles.splice(index, 1); 42 | } 43 | } 44 | 45 | onDidChangeActiveTextEditor(editor: TextEditor | undefined) { 46 | if (editor && editor.document) { 47 | if (editor.document.uri.scheme !== 'file') { 48 | return; 49 | } 50 | const index = this._activeFiles.findIndex(filePath => filePath === editor.document.uri.fsPath); 51 | 52 | // Move the document to the end of the list if it's already there 53 | if (index !== -1) { 54 | const [doc] = this._activeFiles.splice(index, 1); 55 | this._activeFiles.push(doc); 56 | } else { 57 | // If not in the list, add it (just as a precaution) 58 | this._activeFiles.push(editor.document.uri.fsPath); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/core/activeChanges/fileStateFromPreviousCommit.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { promisify } from 'util'; 7 | import { exec } from 'child_process'; 8 | import * as path from 'path'; 9 | // import path = require('path'); // Add this line to import the 'path' module 10 | import { Logger } from 'winston'; 11 | 12 | 13 | export interface FileStateFromPreviousCommit { 14 | filePath: string; 15 | fileContent: string; 16 | } 17 | 18 | 19 | export const fileStateFromPreviousCommit = async ( 20 | workingDirectory: string, 21 | logger: Logger, 22 | ): Promise => { 23 | // First we need to get the list of files which changed by querying 24 | // git about it 25 | let fileList: string[] = []; 26 | try { 27 | const { stdout } = await promisify(exec)('git diff --name-only HEAD', { cwd: workingDirectory }); 28 | fileList = stdout.split('\n').filter((file) => file !== ''); 29 | } catch (error) { 30 | logger.info((error as Error).toString()); 31 | } 32 | 33 | // Now that we the file list, lets get the content of the file at HEAD 34 | // so we have the initial state and the state of the file post HEAD 35 | // commit 36 | const fileStateFromPreviousCommit: FileStateFromPreviousCommit[] = []; 37 | for (const file of fileList) { 38 | try { 39 | const { stdout } = await promisify(exec)(`git show HEAD:${file}`, { cwd: workingDirectory }); 40 | fileStateFromPreviousCommit.push({ 41 | filePath: path.join(workingDirectory, file), 42 | fileContent: stdout, 43 | }); 44 | } catch (error) { 45 | logger.error((error as Error).toString()); 46 | } 47 | } 48 | return fileStateFromPreviousCommit; 49 | }; 50 | -------------------------------------------------------------------------------- /src/core/activeChanges/timeline.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { createPatch } from 'diff'; 6 | import { ExtensionContext, workspace } from 'vscode'; 7 | import { CodeSymbolChangeType } from './trackCodeSymbolChanges'; 8 | import { stateManager } from '../utilities/stateManager'; 9 | 10 | // The data we need to send over to the webview for rendering the timeline 11 | export interface CodeSymbolChangeWebView { 12 | name: string; 13 | startLine: number; 14 | endLine: number; 15 | changeType: CodeSymbolChangeType; 16 | filePath: string; 17 | workingDirectory: string; 18 | changeTime: Date; 19 | relativePath: string; 20 | componentIdentifier: string; 21 | commitIdentifier: string; 22 | displayName: string; 23 | diffPatch: string; 24 | } 25 | 26 | export const onDidOpenTextDocument = (context: ExtensionContext) => { 27 | workspace.onDidOpenTextDocument((doc) => { 28 | stateManager(context).updateDocuments(doc.uri.fsPath, doc.getText()); 29 | }); 30 | }; 31 | 32 | export const onTextDocumentChange = (context: ExtensionContext) => { 33 | const state = stateManager(context); 34 | return workspace.onDidSaveTextDocument(async (doc) => { 35 | const documents = state.getDocuments(); 36 | // console.log('something'); 37 | if (doc.uri.fsPath in documents) { 38 | const checkpoint = new Date(); 39 | const oldText = documents[doc.uri.fsPath] || ''; 40 | const newText = doc.getText(); 41 | // console.log('something'); 42 | 43 | const diff = 44 | `${checkpoint.toLocaleString('en-US')}\n` + 45 | createPatch(doc.uri.fsPath, oldText, newText) + 46 | '\n'; 47 | 48 | state.setCheckpoint(checkpoint); 49 | state.appendChanges(diff); 50 | state.updateDocuments(doc.uri.fsPath, newText); 51 | } 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /src/core/chatState/decorations/add.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { SidecarRequestRange, } from '../../server/types'; 8 | 9 | 10 | /** 11 | * Add a decoration on the editor 12 | */ 13 | export async function addDecoration(fsFilePath: string, range: SidecarRequestRange) { 14 | const uri = vscode.Uri.file(fsFilePath); 15 | const editor = vscode.window.visibleTextEditors.find(editor => editor.document.uri.toString() === uri.toString()); 16 | if (editor) { 17 | const decorations: vscode.DecorationOptions[] = [ 18 | { 19 | range: new vscode.Range(range.startPosition.line, range.startPosition.character, range.endPosition.line, range.endPosition.character), 20 | hoverMessage: new vscode.MarkdownString(`aide is following the symbol`), 21 | } 22 | ]; 23 | const goToDefinitionDecoration = vscode.window.createTextEditorDecorationType({ 24 | backgroundColor: 'rgba(0, 0, 255, 0.05)', // Reduce opacity for a more translucent background 25 | borderWidth: '0px', // Remove the border 26 | overviewRulerColor: 'rgba(0, 0, 255, 0.2)', // Reduce opacity for the overview ruler color 27 | overviewRulerLane: vscode.OverviewRulerLane.Right, 28 | cursor: 'pointer', 29 | textDecoration: 'underline', 30 | color: 'inherit' // Inherit the text color from the editor theme 31 | }); 32 | editor.setDecorations(goToDefinitionDecoration, decorations); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/core/chatState/promptClassifier.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | // const systemMessage = `You are an expert classifier who can detect the kind of request made by a developer working with a codebase. The developer is expected to give one of the following: An instruction, a search query, a request for an explanation, more general question or help on how to use the editor. 7 | 8 | // - An instruction will be used by an AI agent to make the requested changes to the codebase. 9 | // - The search query will be used by an AI agent to semantically look for code pointers within the codebase. 10 | // - Explanations are for explaining a selected piece of code or an entire file 11 | // - A general query could be a programming question that a developer might be facing when they're working. 12 | // - Help which the user is asking about help on using the editor or the chat. 13 | 14 | // You are expected to return only one word, which is one of: "instruction", "search", "explain" , "general" or "help". 15 | // `; 16 | 17 | 18 | // We want to figure out which instruction to use for the chat given the the 19 | // current user question. 20 | // There are couple of cases to consider here: 21 | // It might be that the user has used a slash command, in which case we already 22 | // know what instruction to use 23 | // otherwise we look at the current instruction the user has provided and pass 24 | // that through the classifier to figure out what sub-instruction to use 25 | 26 | 27 | export type UserMessageType = 'explain' | 'general' | 'instruction' | 'search' | 'help'; 28 | 29 | export const deterministicClassifier = (userMessage: string): UserMessageType | null => { 30 | if (userMessage.startsWith('/help')) { 31 | return 'help'; 32 | } 33 | if (userMessage.startsWith('/explain')) { 34 | return 'explain'; 35 | } 36 | if (userMessage.startsWith('/search')) { 37 | return 'search'; 38 | } 39 | if (userMessage.startsWith('/agent')) { 40 | return 'instruction'; 41 | } 42 | if (userMessage.startsWith('/general')) { 43 | return 'general'; 44 | } 45 | return null; 46 | }; 47 | -------------------------------------------------------------------------------- /src/core/chatState/prompts.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | 7 | // export const getDefaultPrompt = (codeContext?: string): string | null => { 8 | // if (codeContext === null) { 9 | // return null; 10 | // } else { 11 | // return ` 12 | // The use has provided you with the following code context which you should use to answer their question: 13 | // 14 | // ${codeContext} 15 | // 16 | // You have to answer the user question using the provided code Context and also previous context if they have provided you. 17 | // Before answering the user question, you should first understand the user question and think step by step and then answer it. 18 | // If you are generating code, you should generate it in the language of the code context. 19 | 20 | // You MUST follow the following format delimited with XML tags: 21 | 22 | // Step-by-step thoughts with explanations: 23 | // * Thought 1 - Explanation 1 24 | // * Thought 2 - Explanation 2 25 | // ... 26 | 27 | // 28 | // ... 29 | // 30 | // `; 31 | 32 | // } 33 | // }; 34 | -------------------------------------------------------------------------------- /src/core/chatState/state.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | const chatSystemPrompt = (agentCustomInstruction: string | null): string => { 7 | if (agentCustomInstruction) { 8 | return ` 9 | Your name is CodeStory bot. You are a brilliant and meticulous engineer assigned to help the user with any query they have. When you write code, the code works on the first try and is formatted perfectly. You can be asked to explain the code, in which case you should use the context you know to help the user out. You have the utmost care for the code that you write, so you do not make mistakes. Take into account the current repository\'s language, frameworks, and dependencies. You must always use markdown when referring to code symbols. 10 | You are given some additional context about the codebase and instructions by the user below, follow them to better help the user 11 | ${agentCustomInstruction} 12 | `; 13 | } else { 14 | return 'Your name is CodeStory bot. You are a brilliant and meticulous engineer assigned to help the user with any query they have. When you write code, the code works on the first try and is formatted perfectly. You can be asked to explain the code, in which case you should use the context you know to help the user out. You have the utmost care for the code that you write, so you do not make mistakes. Take into account the current repository\'s language, frameworks, and dependencies. You must always use markdown when referring to code symbols.'; 15 | } 16 | }; 17 | 18 | 19 | type RoleString = 'system' | 'user' | 'assistant' | undefined; 20 | type RoleStringForOpenai = 'system' | 'user' | 'assistant' | 'function'; 21 | 22 | 23 | const convertRoleToString = (role: RoleStringForOpenai): RoleString => { 24 | switch (role) { 25 | case 'system': 26 | return 'system'; 27 | case 'user': 28 | return 'user'; 29 | case 'assistant': 30 | return 'assistant'; 31 | default: 32 | return undefined; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/core/completions/README.md: -------------------------------------------------------------------------------- 1 | ### Completions (Not maintained) 2 | 3 | This part of the extension is not maintained at all and we have to spend more time 4 | and energy to make this work. 5 | Today its heavily derived from the work which Cody did around an year ago to make 6 | completions work (along with the codestory niceness like type-identifiers etc sprinkled 7 | on top). 8 | Use this with caution and if you are interested in revamping this part please Open an Issue 9 | and let us know! 10 | -------------------------------------------------------------------------------- /src/core/completions/can-use-partial-completion.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import type { TextDocument } from 'vscode'; 6 | 7 | import type { DocumentContext } from './get-current-doc-context'; 8 | import { hasCompleteFirstLine } from './text-processing'; 9 | import { parseAndTruncateCompletion } from './text-processing/parse-and-truncate-completion'; 10 | import type { InlineCompletionItemWithAnalytics } from './text-processing/process-inline-completions'; 11 | import { LoggingService } from './logger'; 12 | 13 | interface CanUsePartialCompletionParams { 14 | document: TextDocument; 15 | docContext: DocumentContext; 16 | isDynamicMultilineCompletion: boolean; 17 | logger: LoggingService; 18 | spanId: string; 19 | } 20 | 21 | /** 22 | * Evaluates a partial completion response and returns it when we can already use it. This is used 23 | * to terminate any streaming responses where we can get a token-by-token access to the result and 24 | * want to terminate as soon as stop conditions are triggered. 25 | * 26 | * Right now this handles two cases: 27 | * 1. When a single line completion is requested, it terminates after the first full line was 28 | * received. 29 | * 2. For a multi-line completion, it terminates when the completion will be truncated based on the 30 | * multi-line indentation logic. 31 | */ 32 | export function canUsePartialCompletion( 33 | partialResponse: string, 34 | params: CanUsePartialCompletionParams 35 | ): InlineCompletionItemWithAnalytics | null { 36 | const { docContext } = params; 37 | // console.log('sidecar.docContext.multilineTrigger', params.docContext.multilineTrigger); 38 | 39 | if (!hasCompleteFirstLine(partialResponse)) { 40 | params.logger.logInfo('sidecar.canUsePartialCompletion', { 41 | event_name: 'sidecar.canUsePartialCompletion.has_complete_first_line', 42 | event_value: 'false', 43 | id: params.spanId, 44 | partial_response: partialResponse, 45 | }); 46 | // console.log('sidecar.hasCompleteFirstLine', false); 47 | return null; 48 | } 49 | 50 | const item = parseAndTruncateCompletion(partialResponse, params); 51 | params.logger.logInfo('sidecar.canUsePartialCompletion.parse', { 52 | event_name: 'sidecar.can_use_partial_completion.parse_truncate_completion', 53 | id: params.spanId, 54 | partial_response: partialResponse, 55 | }); 56 | // console.log('sidecar.canUsePartialCompletion', item.insertText); 57 | // console.log('sidecar.item.lineTruncatedCount', item.lineTruncatedCount); 58 | 59 | // TODO(skcd): This condition is weird, what if we are getting the whole string back 60 | // then we do not have any line truncated count, so do we always end up returning null? 61 | // always??? 62 | // SKETCHY AF condition 63 | if (docContext.multilineTrigger) { 64 | return (item.lineTruncatedCount || 0) > 0 ? item : null; 65 | } 66 | 67 | return item.insertText.trim() === '' ? null : item; 68 | } 69 | -------------------------------------------------------------------------------- /src/core/completions/completion-provider-config.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | class CompletionProviderConfig { 6 | /** 7 | * Should be called before `InlineCompletionItemProvider` instance is created, so that the singleton 8 | * with resolved values is ready for downstream use. 9 | */ 10 | public async init(): Promise { 11 | } 12 | 13 | public get dynamicMultilineCompletions(): boolean { 14 | return true; 15 | } 16 | 17 | public get hotStreak(): boolean { 18 | return true; 19 | } 20 | 21 | public get fastPath(): boolean { 22 | return true; 23 | } 24 | 25 | public get contextStrategy(): ContextStrategy { 26 | return 'sidecar'; 27 | } 28 | } 29 | 30 | export type ContextStrategy = 31 | | 'sidecar'; 32 | 33 | /** 34 | * A singleton store for completion provider configuration values which allows us to 35 | * avoid propagating every feature flag and config value through completion provider 36 | * internal calls. It guarantees that `flagsToResolve` are resolved on `CompletionProvider` 37 | * creation and along with `Configuration`. 38 | * 39 | * A subset of relevant config values and feature flags is moved here from the existing 40 | * params waterfall. Ideally, we rely on this singleton as a source of truth for config values 41 | * and collapse function calls nested in `InlineCompletionItemProvider.generateCompletions()`. 42 | */ 43 | export const completionProviderConfig = new CompletionProviderConfig(); 44 | -------------------------------------------------------------------------------- /src/core/completions/create-inline-completion-item-provider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as vscode from 'vscode'; 6 | 7 | 8 | import { InlineCompletionItemProvider } from './inline-completion-item-provider'; 9 | import { SideCarClient } from '../sidecar/client'; 10 | 11 | interface InlineCompletionItemProviderArgs { 12 | triggerNotice: ((notice: { key: string }) => void) | null; 13 | sidecarClient: SideCarClient; 14 | } 15 | 16 | export async function createInlineCompletionItemProvider({ 17 | triggerNotice, 18 | sidecarClient, 19 | }: InlineCompletionItemProviderArgs): Promise { 20 | 21 | const disposables: vscode.Disposable[] = []; 22 | 23 | const completionsProvider = new InlineCompletionItemProvider({ 24 | sidecarClient, 25 | completeSuggestWidgetSelection: true, 26 | triggerNotice, 27 | }); 28 | 29 | disposables.push( 30 | vscode.languages.registerInlineCompletionItemProvider( 31 | { pattern: '**' }, 32 | completionsProvider 33 | ), 34 | completionsProvider 35 | ); 36 | 37 | return { 38 | dispose: () => { 39 | for (const disposable of disposables) { 40 | disposable.dispose(); 41 | } 42 | }, 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/core/completions/createInlineCompletionItemProvider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as vscode from 'vscode'; 6 | 7 | 8 | import { InlineCompletionItemProvider } from './inline-completion-item-provider'; 9 | import { SideCarClient } from '../sidecar/client'; 10 | 11 | interface InlineCompletionItemProviderArgs { 12 | triggerNotice: ((notice: { key: string }) => void) | null; 13 | sidecarClient: SideCarClient; 14 | } 15 | 16 | export async function createInlineCompletionItemProvider({ 17 | triggerNotice, 18 | sidecarClient, 19 | }: InlineCompletionItemProviderArgs): Promise { 20 | 21 | const disposables: vscode.Disposable[] = []; 22 | 23 | const completionsProvider = new InlineCompletionItemProvider({ 24 | sidecarClient, 25 | completeSuggestWidgetSelection: true, 26 | triggerNotice, 27 | }); 28 | 29 | disposables.push( 30 | vscode.languages.registerInlineCompletionItemProvider( 31 | { pattern: '**' }, 32 | completionsProvider 33 | ), 34 | completionsProvider 35 | ); 36 | 37 | return { 38 | dispose: () => { 39 | for (const disposable of disposables) { 40 | disposable.dispose(); 41 | } 42 | }, 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/core/completions/doc-context-getters.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as vscode from 'vscode'; 6 | 7 | import type { DocumentContext } from './get-current-doc-context'; 8 | import { CompletionIntent } from './artificial-delay'; 9 | 10 | export function getCurrentLinePrefixWithoutInjectedPrefix(docContext: DocumentContext): string { 11 | const { currentLinePrefix, injectedPrefix } = docContext; 12 | 13 | return injectedPrefix ? currentLinePrefix.slice(0, -injectedPrefix.length) : currentLinePrefix; 14 | } 15 | 16 | interface GetContextRangeParams { 17 | prefix: string; 18 | suffix: string; 19 | position: vscode.Position; 20 | } 21 | 22 | /** 23 | * @returns the range that overlaps the included prefix and suffix. 24 | */ 25 | export function getContextRange( 26 | document: vscode.TextDocument, 27 | params: GetContextRangeParams 28 | ): vscode.Range { 29 | const { prefix, suffix, position } = params; 30 | const offset = document.offsetAt(position); 31 | 32 | return new vscode.Range( 33 | document.positionAt(offset - prefix.length), 34 | document.positionAt(offset + suffix.length) 35 | ); 36 | } 37 | 38 | interface GetCompletionIntentParams { 39 | document: vscode.TextDocument; 40 | position: vscode.Position; 41 | prefix: string; 42 | } 43 | 44 | export function getCompletionIntent(_params: GetCompletionIntentParams): CompletionIntent | undefined { 45 | return undefined; 46 | // const { document, position, prefix } = params 47 | 48 | // const blockStart = getLanguageConfig(document.languageId)?.blockStart 49 | // const isBlockStartActive = blockStart && prefix.trimEnd().endsWith(blockStart) 50 | // // Use `blockStart` for the cursor position if it's active. 51 | // const positionBeforeCursor = isBlockStartActive 52 | // ? document.positionAt(prefix.lastIndexOf(blockStart)) 53 | // : { 54 | // line: position.line, 55 | // character: Math.max(0, position.character - 1), 56 | // } 57 | 58 | // const [completionIntent] = execQueryWrapper(document, positionBeforeCursor, 'getCompletionIntent') 59 | 60 | // return completionIntent?.name 61 | } 62 | -------------------------------------------------------------------------------- /src/core/completions/language_config.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | export interface LanguageConfig { 6 | blockStart: string; 7 | blockElseTest: RegExp; 8 | blockEnd: string | null; 9 | commentStart: string; 10 | } 11 | 12 | export function getLanguageConfig(languageId: string): LanguageConfig | null { 13 | switch (languageId) { 14 | case 'c': 15 | case 'cpp': 16 | case 'csharp': 17 | case 'dart': 18 | case 'go': 19 | case 'java': 20 | case 'javascript': 21 | case 'javascriptreact': 22 | case 'php': 23 | case 'typescript': 24 | case 'typescriptreact': 25 | case 'vue': 26 | case 'rust': 27 | return { 28 | blockStart: '{', 29 | blockElseTest: /^[\t ]*} else/, 30 | blockEnd: '}', 31 | commentStart: '// ', 32 | }; 33 | case 'python': { 34 | return { 35 | blockStart: ':', 36 | blockElseTest: /^[\t ]*(elif |else:)/, 37 | blockEnd: null, 38 | commentStart: '# ', 39 | }; 40 | } 41 | default: 42 | return null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/core/completions/providers/dynamic-multiline.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { insertIntoDocContext, type DocumentContext } from '../get-current-doc-context'; 6 | import { LoggingService } from '../logger'; 7 | import { getFirstLine } from '../text-processing'; 8 | 9 | interface GetUpdatedDocumentContextParams { 10 | insertText: string; 11 | languageId: string; 12 | docContext: DocumentContext; 13 | } 14 | 15 | /** 16 | * 1. Generates the object with `multilineTrigger` and `multilineTriggerPosition` fields pretending like the first line of the completion is already in the document. 17 | * 2. If the updated document context has the multiline trigger, returns the generated object 18 | * 3. Otherwise, returns an empty object. 19 | */ 20 | export function getDynamicMultilineDocContext( 21 | params: GetUpdatedDocumentContextParams, 22 | logger: LoggingService, 23 | spanId: string 24 | ): Pick | undefined { 25 | const { insertText, languageId, docContext } = params; 26 | 27 | const updatedDocContext = insertIntoDocContext( 28 | { 29 | languageId, 30 | insertText: getFirstLine(insertText), 31 | dynamicMultilineCompletions: true, 32 | docContext, 33 | }, 34 | logger, 35 | spanId 36 | ); 37 | 38 | const isMultilineBasedOnFirstLine = Boolean(updatedDocContext.multilineTrigger); 39 | 40 | if (isMultilineBasedOnFirstLine) { 41 | return { 42 | multilineTrigger: updatedDocContext.multilineTrigger, 43 | multilineTriggerPosition: updatedDocContext.multilineTriggerPosition, 44 | }; 45 | } 46 | 47 | return undefined; 48 | } 49 | -------------------------------------------------------------------------------- /src/core/completions/providers/provider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import type { Position, TextDocument } from 'vscode'; 6 | 7 | import type { DocumentContext } from '../get-current-doc-context'; 8 | 9 | import type { FetchCompletionResult, StreamCompletionResponse } from './fetch-and-process-completions'; 10 | import { TypeDefinitionProviderWithNode } from '../helpers/vscodeApi'; 11 | 12 | export interface ProviderConfig { 13 | /** 14 | * A factory to create instances of the provider. This pattern allows us to 15 | * inject provider specific parameters outside of the callers of the 16 | * factory. 17 | */ 18 | create(options: Omit): Provider; 19 | 20 | /** 21 | * Hints about the optimal context size (and length of the document prefix and suffix). It is 22 | * intended to (or possible to) be precise here because the truncation of the document 23 | * prefix/suffix uses characters, not the LLM's tokenizer. 24 | */ 25 | contextSizeHints: ProviderContextSizeHints; 26 | 27 | /** 28 | * A string identifier for the provider config used in event logs. 29 | */ 30 | identifier: string; 31 | 32 | /** 33 | * Defines which model is used with the respective provider. 34 | */ 35 | model: string; 36 | } 37 | 38 | interface ProviderContextSizeHints { 39 | /** Total max length of all context (prefix + suffix + snippets). */ 40 | totalChars: number; 41 | 42 | /** Max length of the document prefix (text before the cursor). */ 43 | prefixChars: number; 44 | 45 | /** Max length of the document suffix (text after the cursor). */ 46 | suffixChars: number; 47 | } 48 | 49 | export interface ProviderOptions { 50 | /** 51 | * A unique and descriptive identifier for the provider. 52 | */ 53 | id: string; 54 | 55 | /** 56 | * The span id of the current completion request. 57 | */ 58 | spanId: string; 59 | 60 | position: Position; 61 | document: TextDocument; 62 | docContext: DocumentContext; 63 | multiline: boolean; 64 | /** 65 | * Number of parallel LLM requests per completion. 66 | */ 67 | n: number; 68 | /** 69 | * Timeout in milliseconds for the first completion to be yielded from the completions generator. 70 | */ 71 | firstCompletionTimeout: number; 72 | 73 | // feature flags 74 | dynamicMultilineCompletions?: boolean; 75 | hotStreak?: boolean; 76 | fastPath?: boolean; 77 | } 78 | 79 | export abstract class Provider { 80 | constructor(public readonly options: Readonly) { } 81 | 82 | public abstract generateCompletions( 83 | abortSignal: AbortSignal, 84 | startTime: number, 85 | ): AsyncGenerator; 86 | 87 | public abstract generateCompletionsPlain( 88 | abortSignal: AbortSignal, 89 | startTime: number, 90 | clipBoardContext: string | null, 91 | identifierNodes: TypeDefinitionProviderWithNode[], 92 | ): AsyncIterable; 93 | } 94 | -------------------------------------------------------------------------------- /src/core/completions/statistics.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { createSubscriber } from './utils'; 6 | 7 | const subscriber = createSubscriber(); 8 | 9 | interface CompletionStatistics { 10 | suggested: number; 11 | accepted: number; 12 | } 13 | 14 | let statistics: CompletionStatistics = { 15 | suggested: 0, 16 | accepted: 0, 17 | }; 18 | 19 | export function getStatistics(): CompletionStatistics { 20 | return statistics; 21 | } 22 | 23 | export function logSuggested(): void { 24 | statistics = { ...statistics, suggested: statistics.suggested + 1 }; 25 | subscriber.notify(); 26 | } 27 | export function logAccepted(): void { 28 | statistics = { ...statistics, accepted: statistics.accepted + 1 }; 29 | subscriber.notify(); 30 | } 31 | 32 | export const registerChangeListener = subscriber.subscribe.bind(subscriber); 33 | -------------------------------------------------------------------------------- /src/core/completions/testFile.ts: -------------------------------------------------------------------------------- 1 | // write a function to add 2 numbers 2 | function add(a: number, b: number): number { 3 | return a + b; 4 | } 5 | -------------------------------------------------------------------------------- /src/core/completions/text-processing/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | -------------------------------------------------------------------------------- /src/core/completions/text-processing/string-comparator.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import levenshtein from 'js-levenshtein'; 6 | 7 | /** 8 | * Compares string by editing distance algorithm, returns true 9 | * whenever string are almost the same, means that strings are 10 | * the same if we can apply less than MAX_NUMBER_EDITS to the stringA 11 | * to convert it to the stringB. There are three possible types of edit 12 | * - Substitution 13 | * - Insertion 14 | * - Deletion 15 | * 16 | * For more details see https://en.wikipedia.org/wiki/Levenshtein_distance 17 | */ 18 | export const isAlmostTheSameString = (stringA: string, stringB: string, percentage = 0.15): boolean => { 19 | const maxLength = Math.max(stringA.length, stringB.length); 20 | const editOperations = levenshtein(stringA, stringB); 21 | 22 | // Strings are the same 23 | if (editOperations === 0) { 24 | return true; 25 | } 26 | 27 | const operationToLength = editOperations / maxLength; 28 | 29 | return percentage > operationToLength; 30 | }; 31 | -------------------------------------------------------------------------------- /src/core/completions/text-processing/treeSitter/wasm/tree-sitter-go.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/core/completions/text-processing/treeSitter/wasm/tree-sitter-go.wasm -------------------------------------------------------------------------------- /src/core/completions/text-processing/treeSitter/wasm/tree-sitter-javascript.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/core/completions/text-processing/treeSitter/wasm/tree-sitter-javascript.wasm -------------------------------------------------------------------------------- /src/core/completions/text-processing/treeSitter/wasm/tree-sitter-python.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/core/completions/text-processing/treeSitter/wasm/tree-sitter-python.wasm -------------------------------------------------------------------------------- /src/core/completions/text-processing/treeSitter/wasm/tree-sitter-rust.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/core/completions/text-processing/treeSitter/wasm/tree-sitter-rust.wasm -------------------------------------------------------------------------------- /src/core/completions/text-processing/treeSitter/wasm/tree-sitter-tsx.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/core/completions/text-processing/treeSitter/wasm/tree-sitter-tsx.wasm -------------------------------------------------------------------------------- /src/core/completions/text-processing/treeSitter/wasm/tree-sitter-typescript.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/core/completions/text-processing/treeSitter/wasm/tree-sitter-typescript.wasm -------------------------------------------------------------------------------- /src/core/completions/types.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import type * as vscode from 'vscode'; 6 | 7 | import type { DocumentContext } from './get-current-doc-context'; 8 | 9 | /** 10 | * @see vscode.InlineCompletionItem 11 | */ 12 | export interface InlineCompletionItem { 13 | insertText: string; 14 | /** 15 | * The range to replace. 16 | * Must begin and end on the same line. 17 | * 18 | * Prefer replacements over insertions to provide a better experience when the user deletes typed text. 19 | */ 20 | range?: vscode.Range; 21 | } 22 | 23 | /** 24 | * Keep property names in sync with the `EmbeddingsSearchResult` type. 25 | */ 26 | export interface FileContextSnippet { 27 | uri: vscode.Uri; 28 | startLine: number; 29 | endLine: number; 30 | content: string; 31 | } 32 | export interface SymbolContextSnippet extends FileContextSnippet { 33 | symbol: string; 34 | } 35 | export type ContextSnippet = FileContextSnippet | SymbolContextSnippet; 36 | 37 | export interface ContextRetrieverOptions { 38 | document: vscode.TextDocument; 39 | position: vscode.Position; 40 | docContext: DocumentContext; 41 | hints: { 42 | maxChars: number; 43 | maxMs: number; 44 | }; 45 | abortSignal?: AbortSignal; 46 | } 47 | 48 | /** 49 | * Interface for a general purpose retrieval strategy. During the retrieval phase, all retrievers 50 | * that are outlined in the execution plan will be called concurrently. 51 | */ 52 | export interface ContextRetriever extends vscode.Disposable { 53 | /** 54 | * The identifier of the retriever. Used for logging purposes. 55 | */ 56 | identifier: string; 57 | 58 | /** 59 | * Start a retrieval processes. Implementers should observe the hints to return the best results 60 | * in the available time. 61 | * 62 | * The client hints signalize _soft_ timeouts. When a hard timeout is reached, the retriever's 63 | * results will not be taken into account anymore so it's suggested to return _something_ during 64 | * the maxMs time. 65 | * 66 | * The abortSignal can be used to detect when the completion request becomes invalidated. When 67 | * triggered, any further work is ignored so you can stop immediately. 68 | */ 69 | retrieve(options: ContextRetrieverOptions): Promise; 70 | 71 | /** 72 | * Return true if the retriever supports the given languageId. 73 | */ 74 | isSupportedForLanguageId(languageId: string): boolean; 75 | } 76 | -------------------------------------------------------------------------------- /src/core/context/providers/BaseContextProvider.ts: -------------------------------------------------------------------------------- 1 | import { ContextItem, ContextProviderDescription, ContextProviderExtras, ContextSubmenuItem, IContextProvider, LoadSubmenuItemsArgs } from "./types"; 2 | 3 | export abstract class BaseContextProvider implements IContextProvider { 4 | options: { [key: string]: any }; 5 | 6 | constructor(options: { [key: string]: any }) { 7 | this.options = options; 8 | } 9 | 10 | static description: ContextProviderDescription; 11 | 12 | get description(): ContextProviderDescription { 13 | return (this.constructor as typeof BaseContextProvider).description; 14 | } 15 | 16 | abstract getContextItems( 17 | query: string, 18 | extras: ContextProviderExtras, 19 | ): Promise; 20 | 21 | async loadSubmenuItems( 22 | _args: LoadSubmenuItemsArgs, 23 | ): Promise { 24 | return []; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/core/context/providers/FileContextProvider.ts: -------------------------------------------------------------------------------- 1 | import { walkDir } from '../../utilities/dir'; 2 | import { groupByLastNPathParts, getBasename, getUniqueFilePath } from '../../utilities/path'; 3 | import { BaseContextProvider } from './BaseContextProvider'; 4 | import { 5 | ContextItem, 6 | ContextProviderDescription, 7 | ContextProviderExtras, 8 | ContextSubmenuItem, 9 | LoadSubmenuItemsArgs, 10 | } from './types'; 11 | 12 | const MAX_SUBMENU_ITEMS = 10_000; 13 | 14 | class FileContextProvider extends BaseContextProvider { 15 | static override description: ContextProviderDescription = { 16 | title: 'file', 17 | displayTitle: 'Files', 18 | description: 'Type to search', 19 | type: 'submenu', 20 | }; 21 | 22 | async getContextItems(query: string, extras: ContextProviderExtras): Promise { 23 | query = query.trim(); 24 | const content = await extras.ide.readFile(query); 25 | return [ 26 | { 27 | name: query.split(/[\\/]/).pop() ?? query, 28 | description: query, 29 | content: `\`\`\`${query}\n${content}\n\`\`\``, 30 | uri: { 31 | type: 'file', 32 | value: query, 33 | }, 34 | }, 35 | ]; 36 | } 37 | 38 | override async loadSubmenuItems(args: LoadSubmenuItemsArgs): Promise { 39 | const workspaceDirs = await args.ide.getWorkspaceDirs(); 40 | const results = await Promise.all( 41 | workspaceDirs.map((dir) => { 42 | return walkDir(dir, args.ide); 43 | }) 44 | ); 45 | const files = results.flat().slice(-MAX_SUBMENU_ITEMS); 46 | const fileGroups = groupByLastNPathParts(files, 2); 47 | 48 | return files.map((file) => { 49 | return { 50 | id: file, 51 | title: getBasename(file), 52 | description: getUniqueFilePath(file, fileGroups), 53 | }; 54 | }); 55 | } 56 | } 57 | 58 | export default FileContextProvider; 59 | -------------------------------------------------------------------------------- /src/core/context/providers/RelativeFileContextProvider.ts: -------------------------------------------------------------------------------- 1 | import { glob } from 'glob'; 2 | import path from 'path'; 3 | import { BaseContextProvider } from './BaseContextProvider'; 4 | import { 5 | ContextItem, 6 | ContextProviderDescription, 7 | ContextProviderExtras, 8 | } from './types'; 9 | 10 | 11 | class RelativeFileContextProvider extends BaseContextProvider { 12 | static override description: ContextProviderDescription = { 13 | title: 'relative-file', 14 | displayTitle: 'Relative Files', 15 | description: 'Resolve a relative file path in the project', 16 | type: 'query', 17 | }; 18 | 19 | async getContextItems(query: string, extras: ContextProviderExtras): Promise { 20 | query = query.trim(); 21 | if (!query) { return []; } 22 | console.log('will get relative files'); 23 | const workspaceDirs = await extras.ide.getWorkspaceDirs(); 24 | const fullPath = await matchPathToWorkspaceDirs(query, workspaceDirs); 25 | if (!fullPath) { return []; } 26 | const content = await extras.ide.readFile(fullPath); 27 | return [{ 28 | name: query.split(/[\\/]/).pop() ?? query, 29 | description: fullPath, 30 | content: `\`\`\`${query}\n${content}\n\`\`\``, 31 | uri: { 32 | type: 'file', 33 | value: fullPath, 34 | }, 35 | }]; 36 | } 37 | } 38 | 39 | async function matchPathToWorkspaceDirs(query: string, workspaceDirs: string[]) { 40 | for (const rootDir of workspaceDirs) { 41 | const matches = await glob(`**/${query}`, { 42 | cwd: rootDir, 43 | signal: AbortSignal.timeout(1000), 44 | }); 45 | if (matches.length > 0) { 46 | console.log('match', matches[0], path.join(rootDir, matches[0])); 47 | return path.join(rootDir, matches[0]); // Create full path 48 | } else { 49 | return null; 50 | } 51 | } 52 | return null; 53 | } 54 | 55 | export default RelativeFileContextProvider; 56 | -------------------------------------------------------------------------------- /src/core/context/providers/types.ts: -------------------------------------------------------------------------------- 1 | import { IDE } from "../../.."; 2 | 3 | export type ContextItemUriTypes = "file" | "url"; 4 | 5 | export interface ContextItemUri { 6 | type: ContextItemUriTypes; 7 | value: string; 8 | } 9 | 10 | export interface ContextItem { 11 | content: string; 12 | name: string; 13 | description: string; 14 | editing?: boolean; 15 | editable?: boolean; 16 | icon?: string; 17 | uri?: ContextItemUri; 18 | } 19 | 20 | export interface ContextProviderExtras { 21 | ide: IDE; 22 | } 23 | 24 | export type FetchFunction = (url: string | URL, init?: any) => Promise; 25 | 26 | export interface LoadSubmenuItemsArgs { 27 | ide: IDE; 28 | fetch: FetchFunction; 29 | } 30 | 31 | type ContextProviderName = "file" | "code" | "relative-file"; 32 | type ContextProviderType = "normal" | "query" | "submenu"; 33 | 34 | export interface ContextProviderDescription { 35 | title: ContextProviderName; 36 | displayTitle: string; 37 | description: string; 38 | renderInlineAs?: string; 39 | type: ContextProviderType; 40 | dependsOnIndexing?: boolean; 41 | } 42 | 43 | export interface IContextProvider { 44 | get description(): ContextProviderDescription; 45 | 46 | getContextItems( 47 | query: string, 48 | extras: ContextProviderExtras, 49 | ): Promise; 50 | 51 | loadSubmenuItems(args: LoadSubmenuItemsArgs): Promise 52 | } 53 | 54 | export interface ContextSubmenuItem { 55 | id: string; 56 | title: string; 57 | description: string; 58 | icon?: string; 59 | metadata?: any; 60 | } 61 | -------------------------------------------------------------------------------- /src/core/inlineCompletion/README.md: -------------------------------------------------------------------------------- 1 | # Inline completion 2 | 3 | - We want to implement the inline code completion, and some things to take care of 4 | - What files are open in the editor (so we can use that for context) 5 | - Maybe we use the LSP as well to keep improving the context 6 | -------------------------------------------------------------------------------- /src/core/inlineCompletion/helpers/completionContextCheck.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | // This can happen when the user has some content on the editor 4 | // but it does not match the current popup item 5 | export function currentEditorContentMatchesPopupItem( 6 | document: vscode.TextDocument, 7 | context: vscode.InlineCompletionContext 8 | ): boolean { 9 | if (context.selectedCompletionInfo) { 10 | const currentText = document.getText(context.selectedCompletionInfo.range); 11 | const selectedText = context.selectedCompletionInfo.text; 12 | 13 | if (!selectedText.startsWith(currentText)) { 14 | return false; 15 | } 16 | } 17 | return true; 18 | } 19 | -------------------------------------------------------------------------------- /src/core/inlineCompletion/helpers/middleOfLine.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export function isMiddleOfLine( 4 | doc: vscode.TextDocument, 5 | pos: vscode.Position 6 | ): boolean | undefined { 7 | const hasSuffixOnLine = 0 !== doc.lineAt(pos).text.substr(pos.character).length; 8 | 9 | const isSuffixIgnorable = (function (pos, doc) { 10 | const suffixOnLine = doc.lineAt(pos).text.substr(pos.character).trim(); 11 | return /^\s*[)}\]"'`]*\s*[:{;,]?\s*$/.test(suffixOnLine); 12 | })(pos, doc); 13 | 14 | if (!hasSuffixOnLine || isSuffixIgnorable) { 15 | return hasSuffixOnLine && isSuffixIgnorable; 16 | } 17 | return undefined; 18 | } 19 | -------------------------------------------------------------------------------- /src/core/inlineCompletion/helpers/multiLine.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as vscode from 'vscode'; 6 | 7 | export async function isMultiline( 8 | document: vscode.TextDocument, 9 | _position: vscode.Position, 10 | isMiddleOfLine: boolean, 11 | ): Promise { 12 | // TODO(skcd): Implement this properly later on, there are certain conditions 13 | // based on tree sitter that we can use to determine if a completion is multiline 14 | if (document.lineCount > 800) { 15 | return false; 16 | } 17 | if (!isMiddleOfLine) { 18 | 19 | } 20 | if (isMiddleOfLine) { 21 | 22 | } 23 | return true; 24 | } 25 | -------------------------------------------------------------------------------- /src/core/inlineCompletion/helpers/promptCache.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | // Use ES6 module syntax for exports and imports 6 | 7 | // Key generation function using template literals for clarity 8 | // do something better here with hashing or something, too much memory right now 9 | export function keyForPrompt(prefix: string, suffix: string): string { 10 | return `${prefix}${suffix}`; 11 | } 12 | 13 | export interface LRUCacheValue { 14 | completions: string[]; 15 | multiLine: boolean; 16 | } 17 | 18 | 19 | // Class definition for LRUCache 20 | export class LRUCache { 21 | private values: Map; 22 | private lruKeys: string[]; 23 | private size: number; 24 | 25 | constructor(size: number = 10) { 26 | this.values = new Map(); 27 | this.lruKeys = []; 28 | this.size = size; 29 | } 30 | 31 | private removeKeyFromLRU(key: string): void { 32 | const index = this.lruKeys.indexOf(key); 33 | if (index !== -1) { 34 | this.lruKeys.splice(index, 1); 35 | } 36 | } 37 | 38 | private touchKeyInLRU(key: string): void { 39 | this.removeKeyFromLRU(key); 40 | this.lruKeys.push(key); 41 | } 42 | 43 | public clear(): void { 44 | this.values.clear(); 45 | this.lruKeys = []; 46 | } 47 | 48 | public deleteKey(key: string): void { 49 | this.removeKeyFromLRU(key); 50 | if (this.values.has(key)) { 51 | this.values.delete(key); 52 | } 53 | } 54 | 55 | public get(key: string): LRUCacheValue | undefined { 56 | if (this.values.has(key)) { 57 | const value = this.values.get(key); 58 | this.touchKeyInLRU(key); 59 | return value; 60 | } 61 | return undefined; 62 | } 63 | 64 | public put(key: string, value: LRUCacheValue): void { 65 | let keysToRemove: string[] = []; 66 | if (this.values.has(key)) { 67 | keysToRemove = [key]; 68 | } else { 69 | if (this.lruKeys.length >= this.size) { 70 | keysToRemove = this.lruKeys.splice(0, 1); 71 | } 72 | } 73 | for (const keyToRemove of keysToRemove) { 74 | this.deleteKey(keyToRemove); 75 | } 76 | this.values.set(key, value); 77 | this.touchKeyInLRU(key); 78 | } 79 | } 80 | 81 | // we initialize a prompt completions lru cache here globally 82 | export const PromptCompletionsLRUCache = new LRUCache(100); 83 | -------------------------------------------------------------------------------- /src/core/inlineCompletion/helpers/promptWrapper.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as vscode from 'vscode'; 6 | 7 | export interface PromptData { 8 | type: string; 9 | prompt: { 10 | prefix: string; 11 | suffix: string; 12 | isFimEnabled: boolean; 13 | }; 14 | trailingWs: string; 15 | computeTimeMs: number; 16 | } 17 | 18 | export async function getPromptHelper( 19 | docText: string, 20 | insertOffset: number, 21 | _docRelPath: string, 22 | _docUri: vscode.Uri, 23 | _docLangId: string, 24 | ): Promise { 25 | // const suffixPercent = SUFFIX_PERCENT; 26 | 27 | const now = Date.now(); 28 | 29 | const completePrefix = docText.slice(0, insertOffset); 30 | const completeSuffix = docText.slice(insertOffset, docText.length - 1); 31 | 32 | const [trimmedPrefix, trailingWs] = trimLastLine(completePrefix); 33 | const now2 = Date.now(); 34 | 35 | return { 36 | type: 'prompt', 37 | prompt: { 38 | prefix: trimmedPrefix, 39 | suffix: completeSuffix, 40 | isFimEnabled: true, 41 | }, 42 | trailingWs: trailingWs, 43 | computeTimeMs: now2 - now, 44 | }; 45 | } 46 | 47 | export function trimLastLine(str: string): [string, string] { // returns [trimmedString, ws] 48 | const lines = str.split('\n'); 49 | // this is the last line 50 | const lastLine = lines[lines.length - 1]; 51 | // trim the last line 52 | const nTrailingWS = lastLine.length - lastLine.trimRight().length; 53 | // before the whitespace what is the whole prompt here 54 | const beforeWS = str.slice(0, str.length - nTrailingWS); 55 | // gets the trailing whitespace which is remaining in the complete string 56 | const ws = str.substr(beforeWS.length); 57 | return [lastLine.length === nTrailingWS ? beforeWS : str, ws]; 58 | } 59 | -------------------------------------------------------------------------------- /src/core/inlineCompletion/statusBar.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | 8 | /* 9 | const statusBarItemText = (enabled: boolean | undefined) => 10 | enabled ? '$(check) Aide' : '$(circle-slash) Aide'; 11 | 12 | const statusBarItemTooltip = (enabled: boolean | undefined) => 13 | enabled ? 'Tab autocomplete is enabled' : 'Click to enable tab autocomplete'; 14 | 15 | let lastStatusBar: vscode.StatusBarItem | undefined = undefined; 16 | */ 17 | 18 | export function setupStatusBar( 19 | _enabled: boolean | undefined, 20 | _loading?: boolean 21 | ) { 22 | /* 23 | const statusBarItem = vscode.window.createStatusBarItem( 24 | vscode.StatusBarAlignment.Right, 25 | ); 26 | statusBarItem.text = loading 27 | ? '$(loading~spin) Aide' 28 | : statusBarItemText(enabled); 29 | statusBarItem.tooltip = statusBarItemTooltip(enabled); 30 | statusBarItem.command = 'aide.inlineCompletion.toggle'; 31 | 32 | // Swap out with old status bar 33 | if (lastStatusBar) { 34 | lastStatusBar.dispose(); 35 | } 36 | statusBarItem.show(); 37 | lastStatusBar = statusBarItem; 38 | 39 | vscode.workspace.onDidChangeConfiguration((event) => { 40 | if (event.affectsConfiguration('aide')) { 41 | const config = vscode.workspace.getConfiguration('aide'); 42 | const enabled = config.get('inlineCompletion.enableTabAutocomplete'); 43 | statusBarItem.dispose(); 44 | setupStatusBar(enabled); 45 | } 46 | }); 47 | */ 48 | } 49 | 50 | export function startupStatusBar() { 51 | const config = vscode.workspace.getConfiguration('aide'); 52 | const enabled = config.get('inlineCompletion.enableTabAutocomplete'); 53 | if (enabled) { 54 | setupStatusBar(enabled); 55 | } 56 | } 57 | 58 | function checkInlineCompletionsEnabled(): boolean { 59 | const config = vscode.workspace.getConfiguration('aide'); 60 | return config.get('inlineCompletion.enableTabAutocomplete') ?? false; 61 | } 62 | 63 | export function setLoadingStatus() { 64 | if (checkInlineCompletionsEnabled()) { 65 | setupStatusBar(true, true); 66 | } 67 | } 68 | 69 | export function disableLoadingStatus() { 70 | if (checkInlineCompletionsEnabled()) { 71 | setupStatusBar(true, false); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/core/languages/codeSymbolsIndexerTypes.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { CodeSymbolInformation } from '../utilities/types'; 7 | 8 | 9 | export abstract class CodeSymbolsIndexer { 10 | public supportedFileFormats: string[]; 11 | public indexerType: string; 12 | 13 | constructor(indexerType: string, supportedFileFormats: string[]) { 14 | this.supportedFileFormats = supportedFileFormats; 15 | this.indexerType = indexerType; 16 | } 17 | 18 | abstract parseFileWithoutDependency(filePath: string, workingDirectory: string, storeInCache: boolean): Promise; 19 | 20 | abstract parseFileWithDependencies(filePath: string, workingDirectory: string, storeInCache: boolean): Promise; 21 | 22 | abstract parseFileWithContent(filePath: string, fileContents: string): Promise; 23 | } 24 | -------------------------------------------------------------------------------- /src/core/languages/tree-sitter-go.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/core/languages/tree-sitter-go.wasm -------------------------------------------------------------------------------- /src/core/languages/tree-sitter-javascript.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/core/languages/tree-sitter-javascript.wasm -------------------------------------------------------------------------------- /src/core/languages/tree-sitter-python.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/core/languages/tree-sitter-python.wasm -------------------------------------------------------------------------------- /src/core/languages/tree-sitter-rust.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/core/languages/tree-sitter-rust.wasm -------------------------------------------------------------------------------- /src/core/languages/tree-sitter-tsx.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/core/languages/tree-sitter-tsx.wasm -------------------------------------------------------------------------------- /src/core/languages/tree-sitter-typescript.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/core/languages/tree-sitter-typescript.wasm -------------------------------------------------------------------------------- /src/core/posthog/client.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { PostHog } from 'posthog-node'; 6 | import * as vscode from 'vscode'; 7 | import { getUserId } from '../utilities/uniqueId'; 8 | 9 | let postHogClient: PostHog | undefined; 10 | try { 11 | const codestoryConfiguration = vscode.workspace.getConfiguration('codestory'); 12 | const disableTelemetry = codestoryConfiguration.get('disableTelemetry'); 13 | if (disableTelemetry) { 14 | postHogClient = undefined; 15 | } else { 16 | postHogClient = new PostHog( 17 | 'phc_dKVAmUNwlfHYSIAH1kgnvq3iEw7ovE5YYvGhTyeRlaB', 18 | { host: 'https://app.posthog.com' } 19 | ); 20 | postHogClient.identify({ distinctId: getUserId() }); 21 | } 22 | } catch (err) { 23 | console.error('Error initializing PostHog client', err); 24 | } 25 | 26 | 27 | export default postHogClient; 28 | -------------------------------------------------------------------------------- /src/core/posthog/logChatPrompt.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import postHogClient from './client'; 7 | 8 | // this is not used 9 | export const logChatPrompt = ( 10 | prompt: string, 11 | githubRepoName: string, 12 | githubRepoHash: string, 13 | uniqueId: string, 14 | ) => { 15 | postHogClient?.capture({ 16 | distinctId: uniqueId, 17 | event: 'chat_message', 18 | properties: { 19 | prompt, 20 | githubRepoName, 21 | githubRepoHash, 22 | }, 23 | }); 24 | }; 25 | 26 | // this is not used 27 | export const logSearchPrompt = ( 28 | prompt: string, 29 | githubRepoName: string, 30 | githubRepoHash: string, 31 | uniqueId: string, 32 | ) => { 33 | postHogClient?.capture({ 34 | distinctId: uniqueId, 35 | event: 'search_prompt', 36 | properties: { 37 | prompt, 38 | githubRepoName, 39 | githubRepoHash, 40 | }, 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /src/core/quickActions/fix.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | /** 6 | * We are going to implement the Quick actions fix class here and invoke the cs-chat 7 | * to fix things here 8 | */ 9 | 10 | import * as vscode from 'vscode'; 11 | 12 | export class AideQuickFix implements vscode.CodeActionProvider { 13 | provideCodeActions( 14 | _document: vscode.TextDocument, 15 | _range: vscode.Range | vscode.Selection, 16 | context: vscode.CodeActionContext, 17 | _token: vscode.CancellationToken, 18 | ): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> { 19 | const codeActions: (vscode.CodeAction | vscode.Command)[] = []; 20 | if (!vscode.window.activeTextEditor) { 21 | return codeActions; 22 | } 23 | 24 | const severeDiagnostics = AideQuickFix.getSevereDiagnostics(context.diagnostics); 25 | if (severeDiagnostics.length === 0) { 26 | return codeActions; 27 | } 28 | 29 | const diagnosticRange = severeDiagnostics.map((diagnostic) => diagnostic.range).reduce((prev, current) => prev.union(current)); 30 | const selection = new vscode.Selection(diagnosticRange.start, diagnosticRange.end); 31 | const diagnosticsAsText = AideQuickFix.getDiagnosticsAsText(severeDiagnostics); 32 | 33 | const fixCodeAction = new vscode.CodeAction('Fix using Aide', vscode.CodeActionKind.QuickFix); 34 | fixCodeAction.diagnostics = severeDiagnostics; 35 | fixCodeAction.command = { 36 | title: '$(sparkle) ' + fixCodeAction.title, 37 | command: 'vscode.editorChat.start', 38 | arguments: [ 39 | { 40 | autoSend: true, 41 | message: `/fix ${diagnosticsAsText}`, 42 | position: diagnosticRange.start, 43 | initialSelection: selection, 44 | initialRange: diagnosticRange, 45 | }, 46 | ], 47 | tooltip: '$(sparkle) ', 48 | }; 49 | codeActions.push(fixCodeAction); 50 | 51 | return codeActions; 52 | } 53 | 54 | private static getSevereDiagnostics(diagnostics: readonly vscode.Diagnostic[]): vscode.Diagnostic[] { 55 | return diagnostics.filter((diagnostic) => diagnostic.severity <= vscode.DiagnosticSeverity.Warning); 56 | } 57 | 58 | private static getDiagnosticsAsText(diagnostics: vscode.Diagnostic[]): string { 59 | return diagnostics.map((diagnostic) => diagnostic.message).join(', '); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/core/server/createFile.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | /** 7 | * We can create a file at any location using the vscode api over here 8 | */ 9 | 10 | import * as vscode from 'vscode'; 11 | import { SidecarCreateFileRequest, SidecarCreateFilResponse } from './types'; 12 | 13 | export async function createFileResponse(request: SidecarCreateFileRequest): Promise { 14 | const filePath = request.fs_file_path; 15 | const result = await createFileIfNotExists(vscode.Uri.file(filePath)); 16 | return result; 17 | } 18 | 19 | export async function createFileIfNotExists(uri: vscode.Uri): Promise { 20 | try { 21 | await vscode.workspace.fs.stat(uri); 22 | return { 23 | success: false, 24 | }; 25 | } catch (error) { 26 | if (error.code === 'FileNotFound' || error.code === 'ENOENT') { 27 | const content = new TextEncoder().encode(''); // Empty content 28 | await vscode.workspace.fs.writeFile(uri, content); 29 | vscode.window.showInformationMessage('File created successfully.'); 30 | return { 31 | success: true, 32 | }; 33 | } else { 34 | return { 35 | success: false, 36 | }; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/core/server/goToDefinition.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { SidecarGoToDefinitionRequest, SidecarGoToDefinitionResponse } from './types'; 8 | import { shouldTrackFile } from '../utilities/openTabs'; 9 | 10 | export async function goToDefinition(request: SidecarGoToDefinitionRequest): Promise { 11 | const locations: any[] = await vscode.commands.executeCommand( 12 | 'vscode.executeDefinitionProvider', 13 | vscode.Uri.file(request.fs_file_path), 14 | new vscode.Position(request.position.line, request.position.character), 15 | ); 16 | const definitions = await Promise.all(locations.map(async (location) => { 17 | const uri = location.targetUri ?? location.uri; 18 | const range = location.targetRange ?? location.range; 19 | // we have to always open the text document first, this ends up sending 20 | // it over to the sidecar as a side-effect but that is fine 21 | 22 | // No need to await on this 23 | if (shouldTrackFile(uri)) { 24 | // console.log('we are tracking this uri'); 25 | // console.log(uri); 26 | // sidecarClient.documentOpen(textDocument.uri.fsPath, textDocument.getText(), textDocument.languageId); 27 | } 28 | 29 | // return the value as we would normally 30 | return { 31 | fs_file_path: uri.fsPath, 32 | range: { 33 | startPosition: { 34 | line: range.start.line, 35 | character: range.start.character, 36 | byteOffset: 0, 37 | }, 38 | endPosition: { 39 | line: range.end.line, 40 | character: range.end.character, 41 | byteOffset: 0, 42 | } 43 | }, 44 | }; 45 | })); 46 | // lets return all of them over here 47 | return { 48 | definitions, 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/core/server/goToImplementation.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { SidecarGoToImplementationRequest, SidecarGoToImplementationResponse } from './types'; 8 | import { shouldTrackFile } from '../utilities/openTabs'; 9 | 10 | export async function goToImplementation(request: SidecarGoToImplementationRequest): Promise { 11 | const locations: (vscode.Location | vscode.LocationLink)[] | undefined = await vscode.commands.executeCommand( 12 | 'vscode.executeImplementationProvider', 13 | vscode.Uri.file(request.fs_file_path), 14 | new vscode.Position(request.position.line, request.position.character), 15 | ); 16 | if (!locations) { 17 | return { implementation_locations: [] }; 18 | } 19 | 20 | const implementations = await Promise.all(locations.map(async (location) => { 21 | let uri: vscode.Uri; 22 | let range: vscode.Range; 23 | 24 | if ('targetUri' in location) { 25 | uri = location.targetUri; 26 | range = location.targetRange; 27 | } else { 28 | uri = location.uri; 29 | range = location.range; 30 | } 31 | 32 | if (shouldTrackFile(uri)) { 33 | // console.log('we are trakcing this uri'); 34 | // console.log(uri); 35 | } 36 | 37 | return { 38 | fs_file_path: uri.fsPath, 39 | range: { 40 | startPosition: { 41 | line: range.start.line, 42 | character: range.start.character, 43 | byteOffset: 0, 44 | }, 45 | endPosition: { 46 | line: range.end.line, 47 | character: range.end.character, 48 | byteOffset: 0, 49 | }, 50 | } 51 | }; 52 | })); 53 | return { 54 | implementation_locations: implementations, 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /src/core/server/goToReferences.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { SidecarGoToReferencesRequest, SidecarGoToRefernecesResponse } from './types'; 8 | import { shouldTrackFile } from '../utilities/openTabs'; 9 | 10 | export async function goToReferences(request: SidecarGoToReferencesRequest): Promise { 11 | const locations: vscode.Location[] = await vscode.commands.executeCommand( 12 | 'vscode.executeReferenceProvider', 13 | vscode.Uri.file(request.fs_file_path), 14 | new vscode.Position(request.position.line, request.position.character), 15 | ); 16 | 17 | const implementations = await Promise.all(locations.map(async (location) => { 18 | const uri = location.uri; 19 | const range = location.range; 20 | if (shouldTrackFile(uri)) { 21 | // console.log('we are trakcing this uri'); 22 | // console.log(uri); 23 | } 24 | return { 25 | fs_file_path: uri.fsPath, 26 | range: { 27 | startPosition: { 28 | line: range.start.line, 29 | character: range.start.character, 30 | byteOffset: 0, 31 | }, 32 | endPosition: { 33 | line: range.end.line, 34 | character: range.end.character, 35 | byteOffset: 0, 36 | }, 37 | } 38 | }; 39 | })); 40 | return { 41 | reference_locations: implementations, 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /src/core/server/goToTypeDefinition.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { SidecarGoToDefinitionRequest, SidecarGoToDefinitionResponse } from './types'; 8 | import { shouldTrackFile } from '../utilities/openTabs'; 9 | 10 | export async function goToTypeDefinition(request: SidecarGoToDefinitionRequest): Promise { 11 | const locations: any[] = await vscode.commands.executeCommand( 12 | 'vscode.executeTypeDefinitionProvider', 13 | vscode.Uri.file(request.fs_file_path), 14 | new vscode.Position(request.position.line, request.position.character), 15 | ); 16 | const definitions = await Promise.all(locations.map(async (location) => { 17 | const uri = location.targetUri ?? location.uri; 18 | const range = location.targetRange ?? location.range; 19 | // we have to always open the text document first, this ends up sending 20 | // it over to the sidecar as a side-effect but that is fine 21 | 22 | // No need to await on this 23 | if (shouldTrackFile(uri)) { 24 | // console.log('we are tracking this uri'); 25 | // console.log(uri); 26 | // sidecarClient.documentOpen(textDocument.uri.fsPath, textDocument.getText(), textDocument.languageId); 27 | } 28 | 29 | // return the value as we would normally 30 | return { 31 | fs_file_path: uri.fsPath, 32 | range: { 33 | startPosition: { 34 | line: range.start.line, 35 | character: range.start.character, 36 | byteOffset: 0, 37 | }, 38 | endPosition: { 39 | line: range.end.line, 40 | character: range.end.character, 41 | byteOffset: 0, 42 | } 43 | }, 44 | }; 45 | })); 46 | // lets return all of them over here 47 | return { 48 | definitions, 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /src/core/server/inlayHints.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { SidecarInlayHintResponse, SidecarInlayHintsRequest } from './types'; 7 | import * as vscode from 'vscode'; 8 | 9 | export async function inlayHints( 10 | request: SidecarInlayHintsRequest, 11 | ): Promise { 12 | const filePath = request.fs_file_path; 13 | const requestRange = request.range; 14 | const range = new vscode.Range(new vscode.Position(requestRange.startPosition.line, 0), new vscode.Position(requestRange.endPosition.line, requestRange.endPosition.character)); 15 | const inlayHintsProvider = await vscode.languages.getInlayHintsProvider('*'); 16 | const textDocument = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath)); 17 | const evenEmitter = new vscode.EventEmitter(); 18 | try { 19 | const hints = await inlayHintsProvider?.provideInlayHints(textDocument, range, { 20 | isCancellationRequested: false, 21 | onCancellationRequested: evenEmitter.event, 22 | }); 23 | console.log('inlayHints::generated_hints'); 24 | console.log('inlayHints::generated_hints::len', hints?.length); 25 | if (hints !== null && hints !== undefined) { 26 | const answerHints = hints.map((hint) => { 27 | const position = { 28 | line: hint.position.line, 29 | character: hint.position.character, 30 | byteOffset: 0, 31 | }; 32 | const paddingLeft = hint.paddingLeft ?? false; 33 | const paddingRight = hint.paddingRight ?? false; 34 | let values = null; 35 | if (typeof hint.label === 'string') { 36 | values = [hint.label]; 37 | } else { 38 | // its an array of inlay hints, and we have to grab the values 39 | // from here 40 | values = hint.label.map((hint) => { 41 | return hint.value; 42 | }); 43 | } 44 | return { 45 | position, 46 | padding_left: paddingLeft, 47 | padding_right: paddingRight, 48 | values, 49 | }; 50 | }); 51 | return { 52 | parts: answerHints, 53 | }; 54 | } 55 | } catch (exception) { 56 | console.log('exception'); 57 | console.log(exception); 58 | } 59 | return { 60 | parts: [], 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /src/core/server/openFile.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { SidecarOpenFileToolRequest, SidecarOpenFileToolResponse } from './types'; 8 | import path from 'path'; 9 | 10 | export async function openFileEditor(request: SidecarOpenFileToolRequest): Promise { 11 | let filePath = request.fs_file_path; 12 | const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; 13 | if (workspaceFolder && !path.isAbsolute(filePath)) { 14 | filePath = path.join(workspaceFolder.uri.fsPath, filePath); 15 | } 16 | 17 | try { 18 | // const stat = await vscode.workspace.fs.stat(vscode.Uri.file(filePath)); 19 | // console.log(stat); 20 | const textDocument = await vscode.workspace.openTextDocument(filePath); 21 | // we get back the text document over here 22 | const contents = textDocument.getText(); 23 | const language = textDocument.languageId; 24 | return { 25 | fs_file_path: filePath, 26 | file_contents: contents, 27 | language, 28 | exists: true, 29 | }; 30 | } catch { 31 | return { 32 | fs_file_path: filePath, 33 | file_contents: '', 34 | language: '', 35 | exists: false, 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/core/server/outlineNodes.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Uri, workspace, commands } from 'vscode'; 7 | import { SidecarGetOutlineNodesRequest, SidecarGetOutlineNodesResponse } from './types'; 8 | 9 | export async function getOutlineNodes(request: SidecarGetOutlineNodesRequest): Promise { 10 | const filePath = request.fs_file_path; 11 | const documentSymbols: any[] | undefined = await commands.executeCommand( 12 | 'vscode.executeDocumentSymbolProvider', 13 | Uri.file(filePath), 14 | ); 15 | const uri = Uri.file(filePath); 16 | const textDocument = await workspace.openTextDocument(uri); 17 | if (documentSymbols === undefined || documentSymbols === null || documentSymbols.length === 0) { 18 | return { 19 | file_content: textDocument.getText(), 20 | outline_nodes: [], 21 | language: textDocument.languageId, 22 | }; 23 | } 24 | try { 25 | return { 26 | file_content: textDocument.getText(), 27 | outline_nodes: documentSymbols ?? [], 28 | language: textDocument.languageId, 29 | }; 30 | // now we want to parse the document symbols and maybe map it back to outline 31 | // nodes here but thats too much, so we send it back to the rust side for handling 32 | // not worrying about it for now 33 | } catch (exception) { 34 | console.log('getOutlineNodesException'); 35 | console.error(exception); 36 | } 37 | return { 38 | file_content: textDocument.getText(), 39 | outline_nodes: [], 40 | language: textDocument.languageId, 41 | }; 42 | } -------------------------------------------------------------------------------- /src/core/server/previousWordCommand.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { getPreviousWordRange } from '../utilities/previousWordInText'; 8 | import { SidecarGetPreviousWordRangeRequest, SidecarGetPreviousWordRangeResponse } from './types'; 9 | 10 | export async function getPreviousWordAtPosition(request: SidecarGetPreviousWordRangeRequest): Promise { 11 | const filePath = request.fs_file_path; 12 | const position = request.current_position; 13 | const vscodePosition = new vscode.Position(position.line, position.character); 14 | const textDocument = await vscode.workspace.openTextDocument(filePath); 15 | const previousWordRange = getPreviousWordRange(textDocument, vscodePosition); 16 | if (previousWordRange) { 17 | return { 18 | fs_file_path: filePath, 19 | range: { 20 | startPosition: { 21 | line: previousWordRange.start.line, 22 | character: previousWordRange.start.character, 23 | byteOffset: 0, 24 | }, 25 | endPosition: { 26 | line: previousWordRange.end.line, 27 | character: previousWordRange.end.character, 28 | byteOffset: 0, 29 | } 30 | } 31 | }; 32 | } else { 33 | return { 34 | fs_file_path: filePath, 35 | range: null, 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/core/server/symbolSearch.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { SidecarSymbolSearchRequest, SidecarSymbolSearchResponse } from './types'; 8 | 9 | export async function symbolSearch(request: SidecarSymbolSearchRequest): Promise { 10 | const responses: { 11 | name: string; 12 | kind: vscode.SymbolKind; 13 | containerName: string; 14 | location: vscode.Location; 15 | }[] = await vscode.commands.executeCommand( 16 | 'vscode.executeWorkspaceSymbolProvider', 17 | request.search_string, 18 | ); 19 | return { 20 | locations: responses.map((response) => { 21 | return { 22 | name: response.name, 23 | kind: response.kind.toString(), 24 | fs_file_path: response.location.uri.fsPath, 25 | range: { 26 | startPosition: { 27 | line: response.location.range.start.line, 28 | character: response.location.range.start.character, 29 | byteOffset: 0, 30 | }, 31 | endPosition: { 32 | line: response.location.range.end.line, 33 | character: response.location.range.end.character, 34 | byteOffset: 0, 35 | }, 36 | }, 37 | }; 38 | }) 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/core/sidecar/default-shell.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | // This is a copy of the default-shell package, not installed as a dependency due to module system conflict. 7 | // https://github.com/sindresorhus/default-shell/blob/8313d3750fc88653956228ff5f0f90cad2eee51f/index.d.ts 8 | 9 | import process from 'node:process'; 10 | import { userInfo } from 'node:os'; 11 | 12 | export const detectDefaultShell = () => { 13 | const { env } = process; 14 | 15 | if (process.platform === 'win32') { 16 | return env.COMSPEC || 'cmd.exe'; 17 | } 18 | 19 | try { 20 | const { shell } = userInfo(); 21 | if (shell) { 22 | return shell; 23 | } 24 | } catch { } 25 | 26 | if (process.platform === 'darwin') { 27 | return env.SHELL || '/bin/zsh'; 28 | } 29 | 30 | return env.SHELL || '/bin/sh'; 31 | }; 32 | -------------------------------------------------------------------------------- /src/core/sidecar/providerConfigTypes.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | export interface OpenAIProviderConfig { 7 | readonly api_key: string; 8 | } 9 | 10 | export interface TogetherAIProviderConfig { 11 | readonly api_key: string; 12 | } 13 | 14 | export interface OllamaProviderConfig { } 15 | 16 | export interface AzureOpenAIProviderConfig { 17 | readonly deployment_id: string; 18 | readonly api_base: string; 19 | readonly api_key: string; 20 | readonly api_version: string; 21 | } 22 | 23 | export interface LMStudioProviderConfig { 24 | readonly api_base: string; 25 | } 26 | 27 | export interface OpenAICompatibleProviderConfig { 28 | readonly api_key: string; 29 | readonly api_base: string; 30 | } 31 | 32 | export interface CodeStoryProviderConfig { } 33 | 34 | export interface AnthropicProviderConfig { 35 | readonly api_key: string; 36 | } 37 | 38 | export interface FireworkAIProviderConfig { 39 | readonly api_key: string; 40 | } 41 | 42 | export interface GeminiProProviderConfig { 43 | readonly api_key: string; 44 | readonly api_base: string; 45 | } 46 | 47 | export interface OpenRouterProviderConfig { 48 | readonly api_key: string; 49 | } 50 | 51 | export interface LLMProviderAPIKeys { 52 | OpenAI?: OpenAIProviderConfig; 53 | TogetherAI?: TogetherAIProviderConfig; 54 | Ollama?: OllamaProviderConfig; 55 | OpenAIAzureConfig?: AzureOpenAIProviderConfig; 56 | LMStudio?: LMStudioProviderConfig; 57 | OpenAICompatible?: OpenAICompatibleProviderConfig; 58 | CodeStory?: CodeStoryProviderConfig; 59 | Anthropic?: AnthropicProviderConfig; 60 | FireworksAI?: FireworkAIProviderConfig; 61 | GeminiPro?: GeminiProProviderConfig; 62 | OpenRouter?: OpenRouterProviderConfig; 63 | } 64 | -------------------------------------------------------------------------------- /src/core/subscriptions/timekeeper.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | // Keeps track of the last time some operation was performed. 7 | // We can use this as a hack to restrict heavy operations to only under 8 | // certain time period, yes it might kill the smoothness of the experience 9 | // but it will help with perf issues 10 | 11 | // This is 2 seconds represented as milliseconds (cause js works on ms) 12 | export const FILE_SAVE_TIME_PERIOD = 2000; 13 | 14 | export class TimeKeeper { 15 | private lastUpdated: number; 16 | private timeBetweenUpdates: number; 17 | 18 | constructor( 19 | timeBetweenUpdates: number, 20 | ) { 21 | this.timeBetweenUpdates = timeBetweenUpdates; 22 | this.lastUpdated = -1; 23 | } 24 | 25 | public isInvocationAllowed( 26 | instant: number, 27 | ): boolean { 28 | // Implicitly updates the last time we invoked the function 29 | // using this in async scope might mess things up, but its fine for 30 | // now 31 | if (this.lastUpdated + this.timeBetweenUpdates < instant) { 32 | this.lastUpdated = instant; 33 | return true; 34 | } 35 | return false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/core/terminal/TerminalRegistry.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as vscode from 'vscode'; 6 | 7 | export interface TerminalInfo { 8 | terminal: vscode.Terminal; 9 | busy: boolean; 10 | lastCommand: string; 11 | id: number; 12 | } 13 | 14 | // Although vscode.window.terminals provides a list of all open terminals, there's no way to know whether they're busy or not (exitStatus does not provide useful information for most commands). In order to prevent creating too many terminals, we need to keep track of terminals through the life of the extension, as well as session specific terminals for the life of a task (to get latest unretrieved output). 15 | // Since we have promises keeping track of terminal processes, we get the added benefit of keep track of busy terminals even after a task is closed. 16 | export class TerminalRegistry { 17 | private static terminals: TerminalInfo[] = []; 18 | private static nextTerminalId = 1; 19 | 20 | static createTerminal(cwd?: string | vscode.Uri | undefined): TerminalInfo { 21 | const terminal = vscode.window.createTerminal({ 22 | cwd, 23 | name: 'SOTA-Sidecar', 24 | iconPath: new vscode.ThemeIcon('octoface'), 25 | }); 26 | const newInfo: TerminalInfo = { 27 | terminal, 28 | busy: false, 29 | lastCommand: '', 30 | id: this.nextTerminalId++, 31 | }; 32 | this.terminals.push(newInfo); 33 | return newInfo; 34 | } 35 | 36 | static getTerminal(id: number): TerminalInfo | undefined { 37 | const terminalInfo = this.terminals.find((t) => t.id === id); 38 | if (terminalInfo && this.isTerminalClosed(terminalInfo.terminal)) { 39 | this.removeTerminal(id); 40 | return undefined; 41 | } 42 | return terminalInfo; 43 | } 44 | 45 | static updateTerminal(id: number, updates: Partial) { 46 | const terminal = this.getTerminal(id); 47 | if (terminal) { 48 | Object.assign(terminal, updates); 49 | } 50 | } 51 | 52 | static removeTerminal(id: number) { 53 | this.terminals = this.terminals.filter((t) => t.id !== id); 54 | } 55 | 56 | static getAllTerminals(): TerminalInfo[] { 57 | this.terminals = this.terminals.filter((t) => !this.isTerminalClosed(t.terminal)); 58 | return this.terminals; 59 | } 60 | 61 | // The exit status of the terminal will be undefined while the terminal is active. (This value is set when onDidCloseTerminal is fired.) 62 | private static isTerminalClosed(terminal: vscode.Terminal): boolean { 63 | return terminal.exitStatus !== undefined; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/core/test/testingA.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | export class A { 6 | doA() { 7 | return 'A'; 8 | } 9 | 10 | doB() { 11 | return 'B'; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/core/test/testingB.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { A } from './testingA'; 6 | 7 | export class B { 8 | private _inner: A; 9 | 10 | constructor() { 11 | this._inner = new A(); 12 | console.log(this._inner.doA()); 13 | } 14 | 15 | doSomething() { 16 | return 'A'; 17 | } 18 | 19 | doInteresting() { 20 | return 'B'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/core/test/testingC.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { A } from './testingA'; 6 | 7 | export class C { 8 | private _inner: A; 9 | 10 | constructor() { 11 | this._inner = new A(); 12 | console.log(this._inner.doA()); 13 | } 14 | 15 | doSomething() { 16 | return 'A'; 17 | } 18 | 19 | doInteresting() { 20 | return 'B'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/core/testFramework/jest.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | // We are going to support the jest testing framework here 7 | // jest config is present in either package.json or jest.config.js 8 | // in package.json it is under the jest key 9 | // in jest.config.js it is under the module.exports key 10 | // I want to get the path to the jest config file 11 | // and also the location for the tests 12 | import { runCommandAsync } from '../utilities/commandRunner'; 13 | 14 | class JestTestSupport { 15 | private _testFramework: string; 16 | private _workingDirectory: string; 17 | 18 | constructor(workingDirectory: string) { 19 | this._testFramework = 'jest'; 20 | this._workingDirectory = workingDirectory; 21 | } 22 | 23 | getTestFramework(): string { 24 | return this._testFramework; 25 | } 26 | 27 | // List all the tests that are present in the project 28 | async listTests(): Promise { 29 | const { stdout } = await runCommandAsync(this._workingDirectory, 'node_modules/.bin/jest', [ 30 | '--listTests', 31 | '--json', 32 | '--silent', 33 | '--passWithNoTests', 34 | ]); 35 | return JSON.parse(stdout); 36 | } 37 | } 38 | 39 | 40 | void (async () => { 41 | const jestTestSupport = new JestTestSupport('/Users/skcd/scratch/ide/extensions/codestory'); 42 | const tests = await jestTestSupport.listTests(); 43 | console.log(tests); 44 | })(); 45 | -------------------------------------------------------------------------------- /src/core/utilities/activeDirectories.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | 8 | export const readActiveDirectoriesConfiguration = (workingDirectory: string): string[] => { 9 | const aideConfiguration = vscode.workspace.getConfiguration('aide'); 10 | const directoryPaths = aideConfiguration.get('activeDirectories'); 11 | if (directoryPaths === undefined) { 12 | return [workingDirectory]; 13 | } 14 | if (directoryPaths === '') { 15 | return [workingDirectory]; 16 | } 17 | if (typeof directoryPaths === 'string') { 18 | return directoryPaths.split(',').map((directoryPath: string) => { 19 | return directoryPath.trim(); 20 | }); 21 | } 22 | return [workingDirectory]; 23 | }; 24 | 25 | 26 | export const readTestSuiteRunCommand = (): string => { 27 | const aideConfiguration = vscode.workspace.getConfiguration('aide'); 28 | const testSuiteRunCommand = aideConfiguration.get('testSuiteRunCommand'); 29 | if (testSuiteRunCommand === undefined) { 30 | return 'NotPresent'; 31 | } 32 | if (testSuiteRunCommand === '') { 33 | return 'NotPresent'; 34 | } 35 | if (typeof testSuiteRunCommand === 'string') { 36 | return testSuiteRunCommand; 37 | } 38 | return 'NotPresent'; 39 | }; 40 | -------------------------------------------------------------------------------- /src/core/utilities/async.ts: -------------------------------------------------------------------------------- 1 | import { CancellationError } from "vscode"; 2 | 3 | export type ValueCallback = (value: T | Promise) => void; 4 | 5 | const enum DeferredOutcome { 6 | Resolved, 7 | Rejected, 8 | } 9 | 10 | /** 11 | * Creates a promise whose resolution or rejection can be controlled imperatively. 12 | */ 13 | export class DeferredPromise { 14 | private completeCallback!: ValueCallback; 15 | private errorCallback!: (err: unknown) => void; 16 | private outcome?: 17 | | { outcome: DeferredOutcome.Rejected; value: any } 18 | | { outcome: DeferredOutcome.Resolved; value: T }; 19 | 20 | public get isRejected() { 21 | return this.outcome?.outcome === DeferredOutcome.Rejected; 22 | } 23 | 24 | public get isResolved() { 25 | return this.outcome?.outcome === DeferredOutcome.Resolved; 26 | } 27 | 28 | public get isSettled() { 29 | return !!this.outcome; 30 | } 31 | 32 | public get value() { 33 | return this.outcome?.outcome === DeferredOutcome.Resolved 34 | ? this.outcome?.value 35 | : undefined; 36 | } 37 | 38 | public readonly p: Promise; 39 | 40 | constructor() { 41 | this.p = new Promise((c, e) => { 42 | this.completeCallback = c; 43 | this.errorCallback = e; 44 | }); 45 | } 46 | 47 | public complete(value: T) { 48 | return new Promise((resolve) => { 49 | this.completeCallback(value); 50 | this.outcome = { outcome: DeferredOutcome.Resolved, value }; 51 | resolve(); 52 | }); 53 | } 54 | 55 | public error(err: unknown) { 56 | return new Promise((resolve) => { 57 | this.errorCallback(err); 58 | this.outcome = { outcome: DeferredOutcome.Rejected, value: err }; 59 | resolve(); 60 | }); 61 | } 62 | 63 | public cancel() { 64 | return this.error(new CancellationError()); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/core/utilities/commandRunner.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { exec, spawn } from 'child_process'; 6 | 7 | export async function runCommandAsync( 8 | workingDirectory: string, 9 | cmd: string, 10 | args: string[] = [] 11 | ): Promise<{ stdout: string; stderr: string; exitCode: number }> { 12 | return new Promise((resolve, reject) => { 13 | let stdout = ''; 14 | let stderr = ''; 15 | 16 | const child = spawn(cmd, args, { 17 | cwd: workingDirectory, 18 | }); 19 | 20 | child.stdout.on('data', (data) => { 21 | stdout += data; 22 | }); 23 | 24 | child.stderr.on('data', (data) => { 25 | stderr += data; 26 | }); 27 | 28 | child.on('close', (code) => { 29 | if (code !== 0) { 30 | reject(new Error(`Command exited with code: ${code}, stderr: ${stderr}`)); 31 | } else { 32 | resolve({ stdout, stderr, exitCode: code }); 33 | } 34 | }); 35 | 36 | child.on('error', (error) => { 37 | reject(error); 38 | }); 39 | }); 40 | } 41 | 42 | 43 | export async function execCommand(command: string, workingDirectory: string): Promise { 44 | return new Promise((resolve, reject) => { 45 | exec(command, { cwd: workingDirectory }, (error, stdout, stderr) => { 46 | if (error) { 47 | reject(`exec error: ${error}`); 48 | return; 49 | } 50 | if (stderr) { 51 | reject(`shell error: ${stderr}`); 52 | return; 53 | } 54 | resolve(stdout.trim()); 55 | }); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /src/core/utilities/embeddingsHelpers.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | export const generateContextForEmbedding = ( 6 | codeSnippet: string, 7 | filePath: string, 8 | scopePart: string | null 9 | ): string => { 10 | return ` 11 | Code snippet: 12 | ${codeSnippet} 13 | 14 | File path it belongs to: 15 | ${filePath} 16 | 17 | Scope part: 18 | ${scopePart} 19 | `; 20 | }; 21 | -------------------------------------------------------------------------------- /src/core/utilities/extensionBlockList.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | export const EXCLUDED_EXTENSIONS = [ 6 | // graphics 7 | '.png', '.jpg', '.jpeg', '.ico', '.bmp', '.bpg', '.eps', '.pcx', '.ppm', '.tga', '.tiff', '.wmf', '.xpm', 8 | '.svg', '.riv', 9 | // fonts 10 | '.ttf', '.woff2', '.fnt', '.fon', '.otf', 11 | // documents 12 | '.pdf', '.ps', '.doc', '.dot', '.docx', '.dotx', '.xls', '.xlsx', '.xlt', '.odt', '.ott', '.ods', '.ots', '.dvi', '.pcl', 13 | // media 14 | '.mp3', '.ogg', '.ac3', '.aac', '.mod', '.mp4', '.mkv', '.avi', '.m4v', '.mov', '.flv', 15 | // compiled 16 | '.jar', '.pyc', '.war', '.ear', 17 | // compression 18 | '.tar', '.gz', '.bz2', '.xz', '.7z', '.bin', '.apk', '.deb', '.rpm', 19 | // executable 20 | '.com', '.exe', '.out', '.coff', '.obj', '.dll', '.app', '.class', 21 | // misc. 22 | '.log', '.wad', '.bsp', '.bak', '.sav', '.dat', '.lock', 23 | ]; 24 | 25 | 26 | export const isExcludedExtension = (extension: string): boolean => { 27 | if (EXCLUDED_EXTENSIONS.includes(extension)) { 28 | return true; 29 | } 30 | return false; 31 | }; 32 | -------------------------------------------------------------------------------- /src/core/utilities/files.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import * as path from 'path'; 6 | import * as fs from 'fs'; 7 | 8 | // Read and return data from sample.json file in working directory 9 | export const readJSONFromFile = () => { 10 | const filePath = path.join(__dirname, '../../sample.json'); 11 | const fileData = fs.readFileSync(filePath, 'utf-8'); 12 | return JSON.parse(fileData); 13 | }; 14 | -------------------------------------------------------------------------------- /src/core/utilities/gcpBucket.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { Storage } from '@google-cloud/storage'; 6 | import * as path from 'path'; 7 | import * as fs from 'fs'; 8 | import axios from 'axios'; 9 | 10 | // https://storage.googleapis.com/aide-binary/run 11 | 12 | async function ensureDirectoryExists(filePath: string): Promise { 13 | const parentDir = path.dirname(filePath); 14 | 15 | if (fs.existsSync(parentDir)) { 16 | // The parent directory already exists, so we don't need to create it 17 | return; 18 | } 19 | 20 | // Recursively create the parent directory 21 | await ensureDirectoryExists(parentDir); 22 | 23 | // Create the directory 24 | fs.mkdirSync(parentDir); 25 | } 26 | 27 | export const downloadFromGCPBucket = async (bucketName: string, srcFilename: string, destFilename: string) => { 28 | const storage = new Storage(); 29 | 30 | 31 | const options = { 32 | // Specify the source file 33 | source: srcFilename, 34 | 35 | // Specify the destination file 36 | destination: destFilename, 37 | }; 38 | 39 | await ensureDirectoryExists(destFilename); 40 | console.log('downloading from gcp bucket', { bucketName, destFilename, options }); 41 | // Download the file 42 | await storage.bucket(bucketName).file(srcFilename).download(options); 43 | }; 44 | 45 | 46 | export const downloadUsingURL = (bucketName: string, srcFileName: string, destFileName: string) => { 47 | return new Promise(async (resolve, reject) => { 48 | try { 49 | console.log('will download using url'); 50 | const url = `https://storage.googleapis.com/${bucketName}/${srcFileName}`; 51 | const response = await axios.get(url, { responseType: 'stream' }); 52 | const writer = fs.createWriteStream(destFileName); 53 | console.log('downloading from url', { url, destFileName }); 54 | response.data.pipe(writer); 55 | 56 | 57 | writer.on('finish', resolve); 58 | writer.on('error', reject); 59 | } 60 | catch (err) { 61 | reject(err); 62 | } 63 | }); 64 | }; 65 | 66 | // const bucketName = 'your-bucket-name'; 67 | // const srcFilename = 'path/in/bucket/filename.ext'; 68 | // const destFilename = 'local/path/filename.ext'; 69 | 70 | 71 | // void (async () => { 72 | // const bucketName = 'aide-binary'; 73 | // const srcFilename = 'run'; 74 | // await downloadUsingURL( 75 | // bucketName, 76 | // srcFilename, 77 | // '/Users/skcd/Desktop/run', 78 | // ); 79 | // })(); 80 | -------------------------------------------------------------------------------- /src/core/utilities/getUri.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { Uri, Webview } from 'vscode'; 6 | 7 | /** 8 | * A helper function which will get the webview URI of a given file or resource. 9 | * 10 | * @remarks This URI can be used within a webview's HTML as a link to the 11 | * given file/resource. 12 | * 13 | * @param webview A reference to the extension webview 14 | * @param extensionUri The URI of the directory containing the extension 15 | * @param pathList An array of strings representing the path to a file/resource 16 | * @returns A URI pointing to the file/resource 17 | */ 18 | export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) { 19 | return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList)); 20 | } 21 | -------------------------------------------------------------------------------- /src/core/utilities/ignore.ts: -------------------------------------------------------------------------------- 1 | import ignore from "ignore"; 2 | 3 | export const DEFAULT_IGNORE_FILETYPES = [ 4 | "*.DS_Store", 5 | "*-lock.json", 6 | "*.lock", 7 | "*.log", 8 | "*.ttf", 9 | "*.png", 10 | "*.jpg", 11 | "*.jpeg", 12 | "*.gif", 13 | "*.mp4", 14 | "*.svg", 15 | "*.ico", 16 | "*.pdf", 17 | "*.zip", 18 | "*.gz", 19 | "*.tar", 20 | "*.dmg", 21 | "*.tgz", 22 | "*.rar", 23 | "*.7z", 24 | "*.exe", 25 | "*.dll", 26 | "*.obj", 27 | "*.o", 28 | "*.o.d", 29 | "*.a", 30 | "*.lib", 31 | "*.so", 32 | "*.dylib", 33 | "*.ncb", 34 | "*.sdf", 35 | "*.woff", 36 | "*.woff2", 37 | "*.eot", 38 | "*.cur", 39 | "*.avi", 40 | "*.mpg", 41 | "*.mpeg", 42 | "*.mov", 43 | "*.mp3", 44 | "*.mp4", 45 | "*.mkv", 46 | "*.mkv", 47 | "*.webm", 48 | "*.jar", 49 | "*.onnx", 50 | "*.parquet", 51 | "*.pqt", 52 | "*.wav", 53 | "*.webp", 54 | "*.db", 55 | "*.sqlite", 56 | "*.wasm", 57 | "*.plist", 58 | "*.profraw", 59 | "*.gcda", 60 | "*.gcno", 61 | "go.sum", 62 | "*.env", 63 | "*.gitignore", 64 | "*.gitkeep", 65 | "*.continueignore", 66 | "config.json", 67 | "*.csv", 68 | "*.uasset", 69 | "*.pdb", 70 | "*.bin", 71 | "*.pag", 72 | "*.swp", 73 | "*.jsonl", 74 | // "*.prompt", // can be incredibly confusing for the LLM to have another set of instructions injected into the prompt 75 | ]; 76 | 77 | export const defaultIgnoreFile = ignore().add(DEFAULT_IGNORE_FILETYPES); 78 | export const DEFAULT_IGNORE_DIRS = [ 79 | ".git/", 80 | ".svn/", 81 | ".vscode/", 82 | ".idea/", 83 | ".vs/", 84 | "venv/", 85 | ".venv/", 86 | "env/", 87 | ".env/", 88 | "node_modules/", 89 | "dist/", 90 | "build/", 91 | "Build/", 92 | "target/", 93 | "out/", 94 | "bin/", 95 | ".pytest_cache/", 96 | ".vscode-test/", 97 | ".continue/", 98 | "__pycache__/", 99 | "site-packages/", 100 | ".gradle/", 101 | ".cache/", 102 | "gems/", 103 | "vendor/", 104 | ]; 105 | 106 | export const defaultIgnoreDir = ignore().add(DEFAULT_IGNORE_DIRS); 107 | 108 | export const DEFAULT_IGNORE = 109 | DEFAULT_IGNORE_FILETYPES.join("\n") + "\n" + DEFAULT_IGNORE_DIRS.join("\n"); 110 | 111 | export function gitIgArrayFromFile(file: string) { 112 | return file 113 | .split(/\r?\n/) // Split on new line 114 | .map((l) => l.trim()) // Remove whitespace 115 | .filter((l) => !/^#|^$/.test(l)); // Remove empty lines 116 | } 117 | -------------------------------------------------------------------------------- /src/core/utilities/lspApi.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as path from 'path'; 7 | import { CodeSymbolInformation } from './types'; 8 | 9 | 10 | // function isSymbolInformationArray(symbols: SymbolInformation[] | DocumentSymbol[]): symbols is SymbolInformation[] { 11 | // // Assuming SymbolInformation has a unique property 'location' 12 | // return (symbols.length > 0 && 'containerName' in symbols[0]); 13 | // } 14 | 15 | 16 | 17 | export const getCodeLocationPath = (directoryPath: string, filePath: string): string => { 18 | // Parse the filePath to get an object that includes properties like root, dir, base, ext and name 19 | const parsedFilePath = path.parse(filePath); 20 | 21 | // Remove the extension of the file 22 | const filePathWithoutExt = path.join(parsedFilePath.dir, parsedFilePath.name); 23 | 24 | // Find the relative path from directoryPath to filePathWithoutExt 25 | const relativePath = path.relative(directoryPath, filePathWithoutExt); 26 | 27 | // Replace backslashes with forward slashes to make it work consistently across different platforms (Windows uses backslashes) 28 | return relativePath.replace(/\//g, '.'); 29 | }; 30 | 31 | export const getSymbolsFromDocumentUsingLSP = async ( 32 | filePath: string, 33 | languageId: string, 34 | workingDirectory: string, 35 | ): Promise => { 36 | console.log(filePath, languageId, workingDirectory); 37 | return []; 38 | }; -------------------------------------------------------------------------------- /src/core/utilities/modelSelection.ts: -------------------------------------------------------------------------------- 1 | import { LanguageModels, ModelProviders } from 'vscode'; 2 | import { ModelSelection } from 'vscode'; 3 | 4 | export namespace MockModelSelection { 5 | export const slowModel: string = 'ClaudeSonnet'; 6 | export const fastModel: string = 'ClaudeSonnet'; 7 | 8 | export const models: LanguageModels = { 9 | // Gpt4: { 10 | // name: 'GPT-4', 11 | // contextLength: 8192, 12 | // temperature: 0.2, 13 | // provider: { 14 | // type: 'codestory', 15 | // }, 16 | // }, 17 | // DeepSeekCoder33BInstruct: { 18 | // name: 'DeepSeek Coder 33B Instruct', 19 | // contextLength: 16384, 20 | // temperature: 0.2, 21 | // provider: { 22 | // type: 'codestory', 23 | // }, 24 | // }, 25 | ClaudeSonnet: { 26 | name: 'Claude Sonnet', 27 | contextLength: 200000, 28 | temperature: 0.2, 29 | provider: { 30 | type: 'anthropic', 31 | }, 32 | }, 33 | ClaudeHaiku: { 34 | name: 'Claude Haiku', 35 | contextLength: 200000, 36 | temperature: 0.2, 37 | provider: { 38 | type: 'anthropic', 39 | }, 40 | }, 41 | }; 42 | 43 | export const providers: ModelProviders = { 44 | //"codestory": { 45 | // name: "CodeStory" 46 | //}, 47 | //"ollama": { 48 | // name: "Ollama" 49 | //} 50 | anthropic: { 51 | name: 'Anthropic', 52 | }, 53 | 'open-router': { 54 | name: 'Open Router', 55 | }, 56 | }; 57 | 58 | export function getConfiguration(): ModelSelection { 59 | return { 60 | slowModel, 61 | fastModel, 62 | models, 63 | providers, 64 | }; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/core/utilities/objects.ts: -------------------------------------------------------------------------------- 1 | export function equals(one: any, other: any): boolean { 2 | if (one === other) { 3 | return true; 4 | } 5 | if ( 6 | one === null || 7 | one === undefined || 8 | other === null || 9 | other === undefined 10 | ) { 11 | return false; 12 | } 13 | if (typeof one !== typeof other) { 14 | return false; 15 | } 16 | if (typeof one !== "object") { 17 | return false; 18 | } 19 | if (Array.isArray(one) !== Array.isArray(other)) { 20 | return false; 21 | } 22 | 23 | let i: number; 24 | let key: string; 25 | 26 | if (Array.isArray(one)) { 27 | if (one.length !== other.length) { 28 | return false; 29 | } 30 | for (i = 0; i < one.length; i++) { 31 | if (!equals(one[i], other[i])) { 32 | return false; 33 | } 34 | } 35 | } else { 36 | const oneKeys: string[] = []; 37 | 38 | for (key in one) { 39 | oneKeys.push(key); 40 | } 41 | oneKeys.sort(); 42 | const otherKeys: string[] = []; 43 | for (key in other) { 44 | otherKeys.push(key); 45 | } 46 | otherKeys.sort(); 47 | if (!equals(oneKeys, otherKeys)) { 48 | return false; 49 | } 50 | for (i = 0; i < oneKeys.length; i++) { 51 | if (!equals(one[oneKeys[i]], other[oneKeys[i]])) { 52 | return false; 53 | } 54 | } 55 | } 56 | return true; 57 | } 58 | -------------------------------------------------------------------------------- /src/core/utilities/path.ts: -------------------------------------------------------------------------------- 1 | const SEP_REGEX = /[\\/]/; 2 | 3 | export function getBasename(filepath: string): string { 4 | return filepath.split(SEP_REGEX).pop() ?? ""; 5 | } 6 | 7 | export function getLastNPathParts(filepath: string, n: number): string { 8 | if (n <= 0) { 9 | return ""; 10 | } 11 | return filepath.split(SEP_REGEX).slice(-n).join("/"); 12 | } 13 | 14 | export function groupByLastNPathParts( 15 | filepaths: string[], 16 | n: number, 17 | ): Record { 18 | return filepaths.reduce( 19 | (groups, item) => { 20 | const lastNParts = getLastNPathParts(item, n); 21 | if (!groups[lastNParts]) { 22 | groups[lastNParts] = []; 23 | } 24 | groups[lastNParts].push(item); 25 | return groups; 26 | }, 27 | {} as Record, 28 | ); 29 | } 30 | 31 | export function getUniqueFilePath( 32 | item: string, 33 | itemGroups: Record, 34 | ): string { 35 | const lastTwoParts = getLastNPathParts(item, 2); 36 | const group = itemGroups[lastTwoParts]; 37 | 38 | let n = 2; 39 | if (group.length > 1) { 40 | while ( 41 | group.some( 42 | (otherItem) => 43 | otherItem !== item && 44 | getLastNPathParts(otherItem, n) === getLastNPathParts(item, n), 45 | ) 46 | ) { 47 | n++; 48 | } 49 | } 50 | 51 | return getLastNPathParts(item, n); 52 | } 53 | -------------------------------------------------------------------------------- /src/core/utilities/paths.ts: -------------------------------------------------------------------------------- 1 | /* Credit to Cline: https://github.com/cline/cline/blob/ff725d35ff081f8c0a102d9dcf4618d892d8a440/src/utils/path.ts */ 2 | 3 | import * as path from 'path'; 4 | import os from 'os'; 5 | 6 | function toPosixPath(p: string) { 7 | // Extended-Length Paths in Windows start with "\\?\" to allow longer paths and bypass usual parsing. If detected, we return the path unmodified to maintain functionality, as altering these paths could break their special syntax. 8 | const isExtendedLengthPath = p.startsWith('\\\\?\\'); 9 | 10 | if (isExtendedLengthPath) { 11 | return p; 12 | } 13 | 14 | return p.replace(/\\/g, '/'); 15 | } 16 | 17 | // Declaration merging allows us to add a new method to the String type 18 | // You must import this file in your entry point (extension.ts) to have access at runtime 19 | declare global { 20 | interface String { 21 | toPosix(): string; 22 | } 23 | } 24 | 25 | String.prototype.toPosix = function (this: string): string { 26 | return toPosixPath(this); 27 | }; 28 | 29 | // Safe path comparison that works across different platforms 30 | export function arePathsEqual(path1?: string, path2?: string): boolean { 31 | if (!path1 && !path2) { 32 | return true; 33 | } 34 | if (!path1 || !path2) { 35 | return false; 36 | } 37 | 38 | path1 = normalizePath(path1); 39 | path2 = normalizePath(path2); 40 | 41 | if (process.platform === 'win32') { 42 | return path1.toLowerCase() === path2.toLowerCase(); 43 | } 44 | return path1 === path2; 45 | } 46 | 47 | function normalizePath(p: string): string { 48 | // normalize resolve ./.. segments, removes duplicate slashes, and standardizes path separators 49 | let normalized = path.normalize(p); 50 | // however it doesn't remove trailing slashes 51 | // remove trailing slash, except for root paths 52 | if (normalized.length > 1 && (normalized.endsWith('/') || normalized.endsWith('\\'))) { 53 | normalized = normalized.slice(0, -1); 54 | } 55 | return normalized; 56 | } 57 | 58 | export function getReadablePath(cwd: string, relPath?: string): string { 59 | relPath = relPath || ''; 60 | // path.resolve is flexible in that it will resolve relative paths like '../../' to the cwd and even ignore the cwd if the relPath is actually an absolute path 61 | const absolutePath = path.resolve(cwd, relPath); 62 | if (arePathsEqual(cwd, path.join(os.homedir(), 'Desktop'))) { 63 | // User opened vscode without a workspace, so cwd is the Desktop. Show the full absolute path to keep the user aware of where files are being created 64 | return absolutePath.toPosix(); 65 | } 66 | if (arePathsEqual(path.normalize(absolutePath), path.normalize(cwd))) { 67 | return path.basename(absolutePath).toPosix(); 68 | } else { 69 | // show the relative path to the cwd 70 | const normalizedRelPath = path.relative(cwd, absolutePath); 71 | if (absolutePath.includes(cwd)) { 72 | return normalizedRelPath.toPosix(); 73 | } else { 74 | // we are outside the cwd, so show the absolute path (useful for when cline passes in '../../' for example) 75 | return absolutePath.toPosix(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/core/utilities/planTimer.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | 8 | function pad(num: number): string { 9 | return num.toString().padStart(2, '0'); 10 | } 11 | 12 | export class AidePlanTimer { 13 | private _startTime: number; 14 | private _statusBar: vscode.StatusBarItem; 15 | private _timer: NodeJS.Timer; 16 | 17 | constructor() { 18 | this._startTime = Date.now(); 19 | const myStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); 20 | myStatusBarItem.show(); 21 | 22 | // start an interval timer here to keep updating 23 | const timer = setInterval(() => { 24 | const elapsedTime = Date.now() - this._startTime; 25 | 26 | const seconds = Math.floor((elapsedTime / 1000) % 60); 27 | const minutes = Math.floor((elapsedTime / (1000 * 60)) % 60); 28 | const hours = Math.floor((elapsedTime / (1000 * 60 * 60))); 29 | 30 | const timeString = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; 31 | myStatusBarItem.text = `$(clock) ${timeString}`; 32 | }, 1000); 33 | this._statusBar = myStatusBarItem; 34 | this._timer = timer; 35 | } 36 | 37 | /** 38 | * Starts the plan timer which keeps running with start of the plan 39 | */ 40 | startPlanTimer() { 41 | this._startTime = Date.now(); 42 | this._timer.refresh(); 43 | } 44 | 45 | /** 46 | * Returns back the status bar so we can register it 47 | */ 48 | statusBar(): vscode.StatusBarItem { 49 | return this._statusBar; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/core/utilities/previousWordInText.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | 8 | export function getPreviousWordRange(document: vscode.TextDocument, position: vscode.Position): vscode.Range | undefined { 9 | let line = position.line; 10 | let character = position.character - 1; // Start from the previous character 11 | 12 | while (line >= 0) { 13 | while (character >= 0) { 14 | const pos = new vscode.Position(line, character); 15 | const wordRange = document.getWordRangeAtPosition(pos); 16 | if (wordRange && wordRange.end.isBefore(position)) { 17 | return wordRange; 18 | } 19 | character--; 20 | } 21 | line--; 22 | if (line >= 0) { 23 | character = document.lineAt(line).text.length - 1; 24 | } 25 | } 26 | return undefined; 27 | } 28 | -------------------------------------------------------------------------------- /src/core/utilities/pythonServerClient.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { CodeSymbolsIndexer } from '../languages/codeSymbolsIndexerTypes'; 7 | import { CodeSymbolInformation } from './types'; 8 | import axios from 'axios'; 9 | import * as path from 'path'; 10 | import * as fs from 'fs'; 11 | import { v4 as uuidV4 } from 'uuid'; 12 | 13 | export class PythonServer extends CodeSymbolsIndexer { 14 | private _serverUrl: string; 15 | constructor(serverUrl: string) { 16 | super('python', ['py']); 17 | this._serverUrl = serverUrl; 18 | } 19 | 20 | async parseFile(filePath: string): Promise { 21 | const endpoint = `${this._serverUrl}/api/get_file_information_for_plugin`; 22 | try { 23 | const { data } = await axios.post(endpoint, { 24 | file_path: filePath, 25 | }); 26 | // console.log('Whats the data after parsing the file'); 27 | // console.log(data); 28 | const codeSymbols = JSON.parse(data).code_symbols as CodeSymbolInformation[]; 29 | // console.log('How many code symbols do we have: ' + codeSymbols.length); 30 | return codeSymbols; 31 | } catch (e) { 32 | // console.log(e); 33 | return []; 34 | } 35 | } 36 | 37 | async parseFileWithDependencies(filePath: string, _workingDirectory: string, _useCache: boolean = false): Promise { 38 | return await this.parseFile(filePath); 39 | } 40 | 41 | async parseFileWithoutDependency(filePath: string, _workingDirectory: string, _storeInCache: boolean = true): Promise { 42 | return await this.parseFile(filePath); 43 | } 44 | 45 | async parseFileWithContent(filePath: string, fileContents: string): Promise { 46 | const dirName = path.dirname(filePath); // Get the directory name 47 | const extName = path.extname(filePath); // Get the extension name 48 | const newFileName = uuidV4(); // Your new file name without extension 49 | const newFilePath = path.join(dirName, `${newFileName}${extName}`); 50 | // write the content to this file for now 51 | fs.writeFileSync(newFilePath, fileContents); 52 | const codeSymbolInformationHackedTogether = await this.parseFile(newFilePath); 53 | // delete the file at this point 54 | fs.unlinkSync(newFilePath); 55 | const codeSymbolInformation = codeSymbolInformationHackedTogether.map((codeSymbol) => { 56 | codeSymbol.symbolName = codeSymbol.symbolName.replace( 57 | newFileName, 58 | path.basename(filePath).replace(extName, '') 59 | ); 60 | codeSymbol.displayName = codeSymbol.displayName.replace( 61 | newFileName, 62 | path.basename(filePath).replace(extName, '') 63 | ); 64 | return codeSymbol; 65 | }); 66 | return codeSymbolInformation; 67 | } 68 | } 69 | 70 | 71 | // void (async () => { 72 | // const server = new PythonServer(`http://127.0.0.1:${PORT}`); 73 | // const result = await server.parseFile('/Users/skcd/scratch/anton/anton/server/start_server.py'); 74 | // console.log(result); 75 | // })(); 76 | -------------------------------------------------------------------------------- /src/core/utilities/readonlyFS.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as os from 'os'; 7 | import { env } from 'vscode'; 8 | 9 | export function checkReadonlyFSMode() { 10 | const platform = os.platform(); 11 | if (platform === 'darwin') { 12 | const appRoot = env.appRoot; 13 | if (appRoot.indexOf('AppTranslocation') !== -1) { 14 | return true; 15 | } 16 | // check if we are in readonly fs here 17 | } 18 | return false; 19 | } 20 | -------------------------------------------------------------------------------- /src/core/utilities/reportIndexingUpdate.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | import { RepoRef, SideCarClient } from '../sidecar/client'; 8 | import { sidecarNotIndexRepository } from './sidecarUrl'; 9 | 10 | 11 | export function reportIndexingPercentage(sidecarClient: SideCarClient, currentRepo: RepoRef) { 12 | // Here we want to go along with the stream of messages we get back from the 13 | // sidecar for indexing and use that to report progress, we will assume that 14 | // there is just 1 repository 15 | // We should check if we have already indexed the repository, if that's the 16 | // case then we don't try to re-index again 17 | const shouldNotIndexRepository = sidecarNotIndexRepository(); 18 | if (shouldNotIndexRepository) { 19 | return; 20 | } 21 | const title = 'Indexing progress'; 22 | vscode.window.withProgress( 23 | { 24 | location: vscode.ProgressLocation.Window, 25 | title, 26 | cancellable: false, 27 | }, 28 | async (progress) => { 29 | const repoStatus = await sidecarClient.getRepoStatus(); 30 | if (currentRepo.getRepresentation() in repoStatus.repo_map) { 31 | const repo = repoStatus.repo_map[currentRepo.getRepresentation()]; 32 | if (typeof repo.sync_status === 'string') { 33 | if ('done' === repo.sync_status) { 34 | return; 35 | } 36 | } 37 | } 38 | const stream = await sidecarClient.getRepoSyncStatus(); 39 | for await (const item of stream) { 40 | if ('ProgressEvent' in item) { 41 | const progressEvent = item.ProgressEvent.ev; 42 | if ('index_percent' in progressEvent) { 43 | const indexPercentage = progressEvent.index_percent; 44 | if (indexPercentage === 100) { 45 | progress.report({ 46 | message: 'Finished indexing', 47 | }); 48 | break; 49 | } 50 | progress.report({ 51 | message: `Indexed ${progressEvent.index_percent}%`, 52 | increment: progressEvent.index_percent / 100, 53 | }); 54 | } 55 | } else if ('KeepAlive' in item) { 56 | } 57 | } 58 | } 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /src/core/utilities/ripGrep.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | /** 7 | * Gets the ripgrep path which is bundled along with VSCode since the APIs 8 | * for findText is not stable yet 9 | * VSCode ships with an package which contains the ripgrep package and is bundled 10 | * along with the main app 11 | */ 12 | 13 | import fs from 'node:fs/promises'; 14 | import * as vscode from 'vscode'; 15 | import path from 'path'; 16 | 17 | export async function getRipGrepPath(): Promise { 18 | const rgExe = process.platform === 'win32' ? 'rg.exe' : 'rg'; 19 | const candidateDirs = ['node_modules/@vscode/ripgrep/bin', 'node_modules.asar.unpacked/@vscode/ripgrep/bin']; 20 | for (const dir of candidateDirs) { 21 | const rgPath = path.resolve(vscode.env.appRoot, dir, rgExe); 22 | const exists = await fs 23 | .access(rgPath) 24 | .then(() => true) 25 | .catch(() => false); 26 | if (exists) { 27 | return rgPath; 28 | } 29 | } 30 | 31 | console.log('rg not found'); 32 | return null; 33 | } 34 | -------------------------------------------------------------------------------- /src/core/utilities/sidecarUrl.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | 8 | const SIDECAR_URL = 'http://127.0.0.1:42424'; 9 | 10 | export const readSideCarURL = (): string => { 11 | const aideConfiguration = vscode.workspace.getConfiguration('aide'); 12 | const sideCarURL = aideConfiguration.get('sidecarURL'); 13 | if (sideCarURL === undefined) { 14 | return SIDECAR_URL; 15 | } 16 | if (sideCarURL === '') { 17 | return SIDECAR_URL; 18 | } 19 | if (typeof sideCarURL === 'string') { 20 | return sideCarURL; 21 | } 22 | return SIDECAR_URL; 23 | }; 24 | 25 | export const shouldUseUnstableToolAgent = (): boolean => { 26 | const aideConfiguration = vscode.workspace.getConfiguration('aide'); 27 | const shouldUseUnstableToolAgent = aideConfiguration.get('specialToolAgentUseAtYourOwnRisk'); 28 | if (shouldUseUnstableToolAgent === undefined) { 29 | return false; 30 | } 31 | if (typeof shouldUseUnstableToolAgent === 'boolean') { 32 | return shouldUseUnstableToolAgent; 33 | } 34 | return false; 35 | }; 36 | 37 | export const sidecarUseSelfRun = (): boolean => { 38 | const aideConfiguration = vscode.workspace.getConfiguration('aide'); 39 | const sideCarUseSelfRun = aideConfiguration.get('sidecarUseSelfRun'); 40 | if (sideCarUseSelfRun === undefined) { 41 | return false; 42 | } 43 | if (typeof sideCarUseSelfRun === 'boolean') { 44 | return sideCarUseSelfRun; 45 | } 46 | return false; 47 | }; 48 | 49 | export const sidecarNotIndexRepository = (): boolean => { 50 | const aideConfiguration = vscode.workspace.getConfiguration('aide'); 51 | const sideCarIndexRepository = aideConfiguration.get('disableIndexing'); 52 | if (sideCarIndexRepository === undefined) { 53 | return true; 54 | } 55 | if (typeof sideCarIndexRepository === 'boolean') { 56 | return sideCarIndexRepository; 57 | } 58 | return true; 59 | }; 60 | -------------------------------------------------------------------------------- /src/core/utilities/sleep.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | export function sleep(ms: number): Promise { 6 | return new Promise(resolve => setTimeout(resolve, ms)); 7 | } 8 | -------------------------------------------------------------------------------- /src/core/utilities/stateManager.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { ExtensionContext } from 'vscode'; 6 | import { CheckpointState, DocumentsState, ChangesState, HealthState, HealthStatus } from '../../types'; 7 | 8 | const isEmpty = (value: Record) => { 9 | return Object.keys(value).length === 0; 10 | }; 11 | 12 | export const stateManager = (context: ExtensionContext) => { 13 | const getCheckpoint = (): CheckpointState => { 14 | const checkPointState = context.workspaceState.get('checkpoint'); 15 | if (!checkPointState || isEmpty(checkPointState)) { 16 | return { 17 | timestamp: new Date(), 18 | }; 19 | } 20 | return { 21 | timestamp: new Date(checkPointState.timestamp), 22 | }; 23 | }; 24 | 25 | const setCheckpoint = async (checkpoint: Date) => { 26 | await context.workspaceState.update('checkpoint', { 27 | timestamp: checkpoint.toLocaleString('en-US'), 28 | }); 29 | }; 30 | 31 | const getDocuments = (): DocumentsState => { 32 | const documents = context.workspaceState.get('documents'); 33 | if (!documents || isEmpty(documents)) { 34 | return {}; 35 | } 36 | return documents; 37 | }; 38 | 39 | const updateDocuments = async (key: string, value: string) => { 40 | const documents = getDocuments(); 41 | documents[key] = value; 42 | await context.workspaceState.update('documents', documents); 43 | }; 44 | 45 | const getChanges = (): ChangesState => { 46 | const changes = context.workspaceState.get('changes'); 47 | if (!changes || isEmpty(changes)) { 48 | return { changes: '' }; 49 | } 50 | return changes; 51 | }; 52 | 53 | const appendChanges = async (diff: string) => { 54 | const currentChanges = getChanges(); 55 | const changes = { changes: currentChanges.changes + '\n' + diff }; 56 | await context.workspaceState.update('changes', changes); 57 | }; 58 | 59 | const getHealth = async (): Promise => { 60 | const health = context.workspaceState.get('anton_health'); 61 | if (!health || isEmpty(health)) { 62 | return { status: 'UNAVAILABLE' }; 63 | } 64 | return health; 65 | }; 66 | 67 | const setHealth = async (status: HealthStatus) => { 68 | await context.workspaceState.update('anton_health', { status }); 69 | }; 70 | 71 | return { 72 | getCheckpoint, 73 | setCheckpoint, 74 | getDocuments, 75 | updateDocuments, 76 | getChanges, 77 | appendChanges, 78 | getHealth, 79 | setHealth, 80 | }; 81 | }; 82 | -------------------------------------------------------------------------------- /src/core/utilities/stringifyEvent.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { SymbolNavigationActionType } from 'vscode'; 7 | 8 | export function getSymbolNavigationActionTypeLabel(actionType: SymbolNavigationActionType): string { 9 | switch (actionType) { 10 | case SymbolNavigationActionType.GoToDefinition: 11 | return 'GoToDefinition'; 12 | case SymbolNavigationActionType.GoToDeclaration: 13 | return 'GoToDeclaration'; 14 | case SymbolNavigationActionType.GoToTypeDefinition: 15 | return 'GoToTypeDefinition'; 16 | case SymbolNavigationActionType.GoToImplementation: 17 | return 'GoToImplementation'; 18 | case SymbolNavigationActionType.GoToReferences: 19 | return 'GoToReferences'; 20 | case SymbolNavigationActionType.GenericGoToLocation: 21 | return 'GenericGoToLocation'; 22 | default: 23 | return 'NotTracked'; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/core/utilities/systemInstruction.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from 'vscode'; 7 | 8 | export const readCustomSystemInstruction = (): string | null => { 9 | const aideConfiguration = vscode.workspace.getConfiguration('aide'); 10 | const systemInstruction = aideConfiguration.get('systemInstruction'); 11 | if (systemInstruction === undefined) { 12 | return null; 13 | } 14 | if (systemInstruction === '') { 15 | return null; 16 | } 17 | if (typeof systemInstruction === 'string') { 18 | return systemInstruction; 19 | } 20 | return null; 21 | }; 22 | -------------------------------------------------------------------------------- /src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | // declare module 'eventsource'; 2 | -------------------------------------------------------------------------------- /src/devtools/react/DevtoolsManager.ts: -------------------------------------------------------------------------------- 1 | // @ts-expect-error external 2 | import Devtools from './dist/standalone.js'; 3 | import * as vscode from 'vscode'; 4 | import { proxy } from './proxy'; 5 | import { DevtoolsStatus, InspectedElementPayload } from './types'; 6 | 7 | export class ReactDevtoolsManager { 8 | private _onStatusChange = new vscode.EventEmitter(); 9 | onStatusChange = this._onStatusChange.event; 10 | 11 | private _onInspectedElementChange = new vscode.EventEmitter(); 12 | onInspectedElementChange = this._onInspectedElementChange.event; 13 | 14 | private _status: DevtoolsStatus = 'idle'; 15 | get status() { 16 | return this._status; 17 | } 18 | 19 | private _insepectedElement: InspectedElementPayload | null = null; 20 | get inspectedElement() { 21 | return this._insepectedElement; 22 | } 23 | 24 | private _Devtools: Devtools; 25 | 26 | constructor() { 27 | this._Devtools = Devtools 28 | .setStatusListener(this.updateStatus.bind(this)) 29 | .setDataCallback(this.updateInspectedElement.bind(this)) 30 | .startServer(8097, 'localhost'); 31 | } 32 | 33 | private updateStatus(_message: string, status: DevtoolsStatus) { 34 | this._status = status; 35 | console.log('Update from devtools ', status); 36 | this._onStatusChange.fire(status); 37 | } 38 | 39 | private updateInspectedElement(payload: InspectedElementPayload) { 40 | this._insepectedElement = payload; 41 | if (payload.type !== 'no-change') { 42 | console.log('inspected element', payload); 43 | this._onInspectedElementChange.fire(payload); 44 | } 45 | } 46 | 47 | proxy(port: number, reactDevtoolsPort = 8097) { 48 | if (this.status !== 'server-connected') { 49 | throw new Error('Devtools server is not connected, cannot proxy'); 50 | } 51 | return proxy(port, reactDevtoolsPort); 52 | } 53 | 54 | startInspectingHost() { 55 | this._Devtools.startInspectingHost(); 56 | } 57 | 58 | stopInspectingHost() { 59 | this._Devtools.stopInspectingHost(); 60 | } 61 | } 62 | 63 | /* 64 | if (status === 'server-connected') { 65 | console.log('Devtools server connected', Devtools.currentPort); 66 | const proxyPort = await proxy(5173, Devtools.currentPort); 67 | console.log("Proxy server set up at ", proxyPort); 68 | } else if (status === 'devtools-connected') { 69 | console.log('Devtools connected'); 70 | webviewView.webview.postMessage({ 71 | type: 'react-devtools-connected', 72 | view: View.Task, 73 | }); 74 | } 75 | */ -------------------------------------------------------------------------------- /src/esbuild-plugins.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | /** 5 | * @type {import('esbuild').Plugin} 6 | */ 7 | const esbuildProblemMatcherPlugin = { 8 | name: 'esbuild-problem-matcher', 9 | 10 | setup(build) { 11 | build.onStart(() => { 12 | console.log('[watch] build started'); 13 | }); 14 | build.onEnd((result) => { 15 | result.errors.forEach(({ text, location }) => { 16 | console.error(`✘ [ERROR] ${text}`); 17 | console.error(` ${location.file}:${location.line}:${location.column}:`); 18 | }); 19 | console.log('[watch] build finished'); 20 | }); 21 | }, 22 | }; 23 | 24 | /** 25 | * Creates a file copy plugin for esbuild 26 | * @param {Array<{from: string, to: string}>} files - Array of file paths to copy 27 | * @returns {import('esbuild').Plugin} 28 | */ 29 | function copyFilesPlugin(files) { 30 | return { 31 | name: 'copy-files', 32 | setup(build) { 33 | build.onEnd(async () => { 34 | const outdir = build.initialOptions.outdir; 35 | if (!outdir) { 36 | throw new Error('outdir is required for copy-files plugin'); 37 | } 38 | 39 | for (const file of files) { 40 | try { 41 | // Ensure source file exists 42 | if (!fs.existsSync(file.from)) { 43 | console.warn(`Source file not found: ${file.from}`); 44 | continue; 45 | } 46 | 47 | // Create destination directory if it doesn't exist 48 | const destDir = path.dirname(path.join(outdir, file.to)); 49 | if (!fs.existsSync(destDir)) { 50 | fs.mkdirSync(destDir, { recursive: true }); 51 | } 52 | 53 | // Copy the file 54 | fs.copyFileSync(file.from, path.join(outdir, file.to)); 55 | 56 | console.log(`Copied: ${file.from} -> ${path.join(outdir, file.to)}`); 57 | } catch (error) { 58 | console.error(`Error copying file ${file.from}:`, error); 59 | } 60 | } 61 | }); 62 | }, 63 | }; 64 | } 65 | 66 | module.exports = { 67 | esbuildProblemMatcherPlugin, 68 | copyFilesPlugin, 69 | }; 70 | -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/icon.png -------------------------------------------------------------------------------- /src/ide.ts: -------------------------------------------------------------------------------- 1 | import { FileType, IDE } from '.'; 2 | import * as vscode from 'vscode'; 3 | import { uriFromFilePath, VSCodeIDEUtils } from './ideUtils'; 4 | 5 | export class VSCodeIDE implements IDE { 6 | private static MAX_BYTES = 100000; 7 | 8 | ideUtils: VSCodeIDEUtils; 9 | 10 | constructor() { 11 | this.ideUtils = new VSCodeIDEUtils(); 12 | } 13 | 14 | getWorkspaceDirs(): Promise { 15 | return Promise.resolve(this.ideUtils.getWorkspaceDirectories()); 16 | } 17 | 18 | listDir(dir: string): Promise<[string, FileType][]> { 19 | return vscode.workspace.fs.readDirectory(uriFromFilePath(dir)) as any; 20 | } 21 | 22 | pathSep(): Promise { 23 | return Promise.resolve(this.ideUtils.path.sep); 24 | } 25 | 26 | async readFile(filepath: string): Promise { 27 | try { 28 | filepath = this.ideUtils.getAbsolutePath(filepath); 29 | const uri = uriFromFilePath(filepath); 30 | 31 | // Check whether it's an open document 32 | const openTextDocument = vscode.workspace.textDocuments.find( 33 | (doc) => doc.uri.fsPath === uri.fsPath 34 | ); 35 | if (openTextDocument !== undefined) { 36 | return openTextDocument.getText(); 37 | } 38 | 39 | const fileStats = await vscode.workspace.fs.stat(uriFromFilePath(filepath)); 40 | if (fileStats.size > 10 * VSCodeIDE.MAX_BYTES) { 41 | return ''; 42 | } 43 | 44 | const bytes = await vscode.workspace.fs.readFile(uri); 45 | 46 | // Truncate the buffer to the first MAX_BYTES 47 | const truncatedBytes = bytes.slice(0, VSCodeIDE.MAX_BYTES); 48 | const contents = new TextDecoder().decode(truncatedBytes); 49 | return contents; 50 | } catch (e) { 51 | console.warn('Error reading file', e); 52 | return ''; 53 | } 54 | } 55 | 56 | showToast(level: 'info' | 'warning' | 'error', message: string) { 57 | switch (level) { 58 | case 'error': { 59 | return vscode.window.showErrorMessage(message); 60 | } 61 | case 'info': { 62 | return vscode.window.showInformationMessage(message); 63 | } 64 | case 'warning': { 65 | return vscode.window.showWarningMessage(message); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/ideUtils.ts: -------------------------------------------------------------------------------- 1 | import * as path from "node:path"; 2 | import * as vscode from "vscode"; 3 | 4 | function windowsToPosix(windowsPath: string): string { 5 | let posixPath = windowsPath.split("\\").join("/"); 6 | if (posixPath[1] === ":") { 7 | posixPath = posixPath.slice(2); 8 | } 9 | // posixPath = posixPath.replace(" ", "\\ "); 10 | return posixPath; 11 | } 12 | 13 | function isWindowsLocalButNotRemote(): boolean { 14 | return ( 15 | vscode.env.remoteName !== undefined && 16 | [ 17 | "wsl", 18 | "ssh-remote", 19 | "dev-container", 20 | "attached-container", 21 | "tunnel", 22 | ].includes(vscode.env.remoteName) && 23 | process.platform === "win32" 24 | ); 25 | } 26 | 27 | export function uriFromFilePath(filepath: string): vscode.Uri { 28 | let finalPath = filepath; 29 | if (vscode.env.remoteName) { 30 | if (isWindowsLocalButNotRemote()) { 31 | finalPath = windowsToPosix(filepath); 32 | } 33 | return vscode.Uri.parse( 34 | `vscode-remote://${vscode.env.remoteName}${finalPath}`, 35 | ); 36 | } else { 37 | return vscode.Uri.file(finalPath); 38 | } 39 | } 40 | 41 | export class VSCodeIDEUtils { 42 | private _cachedPath: path.PlatformPath | undefined; 43 | get path(): path.PlatformPath { 44 | if (this._cachedPath) { 45 | return this._cachedPath; 46 | } 47 | 48 | // Return "path" module for either windows or posix depending on sample workspace folder path format 49 | const sampleWorkspaceFolder = 50 | vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; 51 | const isWindows = sampleWorkspaceFolder 52 | ? !sampleWorkspaceFolder.startsWith("/") 53 | : false; 54 | 55 | this._cachedPath = isWindows ? path.win32 : path.posix; 56 | return this._cachedPath; 57 | } 58 | 59 | private _workspaceDirectories: string[] | undefined = undefined; 60 | getWorkspaceDirectories(): string[] { 61 | if (this._workspaceDirectories === undefined) { 62 | this._workspaceDirectories = 63 | vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath) || 64 | []; 65 | } 66 | 67 | return this._workspaceDirectories; 68 | } 69 | 70 | getAbsolutePath(filepath: string): string { 71 | const workspaceDirectories = this.getWorkspaceDirectories(); 72 | if (!this.path.isAbsolute(filepath) && workspaceDirectories.length === 1) { 73 | return this.path.join(workspaceDirectories[0], filepath); 74 | } else { 75 | return filepath; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | export enum FileType { 2 | Unknown = 0, 3 | File = 1, 4 | Directory = 2, 5 | SymbolicLink = 64, 6 | } 7 | 8 | export interface IDE { 9 | getWorkspaceDirs(): Promise; 10 | listDir(dir: string): Promise<[string, FileType][]>; 11 | pathSep(): Promise; 12 | readFile(filepath: string): Promise; 13 | } 14 | 15 | export interface RangeInFile { 16 | filepath: string; 17 | range: Range; 18 | } 19 | 20 | export interface MessagePart { 21 | type: 'text' | 'imageUrl'; 22 | text?: string; 23 | imageUrl?: { url: string }; 24 | } 25 | 26 | export type MessageContent = string | MessagePart[]; 27 | 28 | export type ContextItemUriTypes = 'file' | 'url'; 29 | 30 | export interface ContextItemUri { 31 | type: ContextItemUriTypes; 32 | value: string; 33 | } 34 | 35 | export interface ContextItemId { 36 | providerTitle: string; 37 | itemId: string; 38 | } 39 | 40 | export interface ContextItem { 41 | content: string; 42 | name: string; 43 | description: string; 44 | editing?: boolean; 45 | editable?: boolean; 46 | icon?: string; 47 | uri?: ContextItemUri; 48 | } 49 | 50 | export interface ContextItemWithId extends ContextItem { 51 | id: ContextItemId; 52 | } 53 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | import { window } from 'vscode'; 6 | import { createLogger, format } from 'winston'; 7 | // @ts-ignore 8 | import VSCTransport from 'winston-vscode'; 9 | 10 | const transport = new VSCTransport({ 11 | window: window, 12 | name: 'CodeStory', 13 | }); 14 | 15 | const logger = createLogger({ 16 | level: 'info', 17 | format: format.combine( 18 | format.splat(), 19 | format.printf((info: any) => { 20 | return info.message; 21 | }), 22 | format.errors({ stack: true }) 23 | ), 24 | transports: [transport], 25 | }); 26 | 27 | export default logger; 28 | -------------------------------------------------------------------------------- /src/preview-src/events.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | export function onceDocumentLoaded(f: () => void) { 7 | if (document.readyState === 'loading' || document.readyState as string === 'uninitialized') { 8 | document.addEventListener('DOMContentLoaded', f); 9 | } else { 10 | f(); 11 | } 12 | } -------------------------------------------------------------------------------- /src/preview-src/sw.js: -------------------------------------------------------------------------------- 1 | self.addEventListener('fetch', function (event) { 2 | console.log('Intercepted request for:', event.request.url); 3 | 4 | // Example: Modify requests or responses as needed 5 | // Here, we simply pass through the request 6 | event.respondWith( 7 | fetch(event.request) 8 | .then(function (response) { 9 | // Optionally, you can modify the response here 10 | return response; 11 | }) 12 | .catch(function (error) { 13 | console.error('Fetch error:', error); 14 | // Optionally, return a fallback response 15 | return new Response('Network error occurred', { 16 | status: 408, 17 | headers: { 'Content-Type': 'text/plain' }, 18 | }); 19 | }) 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codestoryai/extension/3b1850ba2a297002d0cef0c13abe2aff512a7451/src/test.ts -------------------------------------------------------------------------------- /src/testing.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | -------------------------------------------------------------------------------- /src/utils/getNonce.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | export function getNonce() { 6 | let text = ''; 7 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 8 | for (let i = 0; i < 32; i++) { 9 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 10 | } 11 | return text; 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/path.ts: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | export function checkIsValidFullPath(pathToCheck: string) { 4 | try { 5 | console.log('path to check'); 6 | fs.accessSync(pathToCheck); 7 | return true; 8 | } catch (err) { 9 | return false; 10 | } 11 | } 12 | 13 | export function checkIsValidURL(string: string) { 14 | try { 15 | new URL(string); 16 | return true; 17 | } catch (err) { 18 | return false; 19 | } 20 | } -------------------------------------------------------------------------------- /src/utils/port.ts: -------------------------------------------------------------------------------- 1 | 2 | export type PortPosition = { 3 | start: number; 4 | end: number; 5 | port: string; 6 | } 7 | 8 | export function findPortPosition(url: string): PortPosition | null { 9 | const regex = /^https?:\/\/localhost:(\d+)/; 10 | const match = regex.exec(url); 11 | 12 | if (match) { 13 | const portStart = match[0].lastIndexOf(':') + 1; 14 | const start = match.index + portStart; 15 | const port = match[1]; 16 | 17 | return { 18 | start, 19 | end: start + port.length, 20 | port 21 | }; 22 | } 23 | return null; 24 | } -------------------------------------------------------------------------------- /src/webviews/@settings/use-preset.tsx: -------------------------------------------------------------------------------- 1 | import { Event, NewPreset, Preset } from '../../model'; 2 | import { useAsync } from 'utils/hooks/use-async'; 3 | 4 | export type PresetsData = { 5 | presets: Map; 6 | activePresetId?: string; 7 | }; 8 | 9 | export async function getPresets() { 10 | return new Promise((resolve) => { 11 | const handleMessage = (event: MessageEvent) => { 12 | if (event.data.type === 'presets-loaded') { 13 | const { presets: presetsTuples, activePresetId } = event.data; 14 | const presetsMap = new Map(); 15 | presetsTuples.forEach(([presetId, preset]) => { 16 | presetsMap.set(presetId, preset); 17 | }); 18 | resolve({ presets: presetsMap, activePresetId }); 19 | window.removeEventListener('message', handleMessage); 20 | } 21 | }; 22 | window.addEventListener('message', handleMessage); 23 | 24 | vscode.postMessage({ 25 | type: 'get-presets', 26 | }); 27 | }); 28 | } 29 | 30 | export function usePresets(initialData: PresetsData) { 31 | const presets = useAsync(getPresets, { enabled: !!initialData, initialData }); 32 | 33 | function addPreset(preset: NewPreset) { 34 | return new Promise<{ valid: boolean; error?: string }>((resolve) => { 35 | const handleMessage = (event: MessageEvent) => { 36 | if (event.data.type === 'add-preset/response') { 37 | presets.execute(); 38 | resolve({ valid: event.data.valid, error: event.data.error }); 39 | window.removeEventListener('message', handleMessage); 40 | } 41 | }; 42 | window.addEventListener('message', handleMessage); 43 | 44 | vscode.postMessage({ 45 | type: 'add-preset', 46 | preset, 47 | }); 48 | }); 49 | } 50 | 51 | function updatePreset(preset: Preset) { 52 | return new Promise<{ valid: boolean; error?: string }>((resolve) => { 53 | const handleMessage = (event: MessageEvent) => { 54 | if (event.data.type === 'update-preset/response') { 55 | presets.execute(); 56 | resolve({ valid: event.data.valid, error: event.data.error }); 57 | window.removeEventListener('message', handleMessage); 58 | } 59 | }; 60 | window.addEventListener('message', handleMessage); 61 | 62 | vscode.postMessage({ 63 | type: 'update-preset', 64 | preset, 65 | }); 66 | }); 67 | } 68 | 69 | function deletePreset(presetId: string) { 70 | vscode.postMessage({ 71 | type: 'delete-preset', 72 | presetId, 73 | }); 74 | presets.execute(); 75 | } 76 | 77 | function setActivePreset(presetId: string) { 78 | vscode.postMessage({ 79 | type: 'set-active-preset', 80 | presetId, 81 | }); 82 | presets.execute(); 83 | } 84 | 85 | return Object.assign(presets, { addPreset, updatePreset, deletePreset, setActivePreset }); 86 | } 87 | -------------------------------------------------------------------------------- /src/webviews/@settings/welcome-view.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { PresetForm } from "./form"; 3 | import { Button } from "components/button"; 4 | 5 | export function WelcomeView() { 6 | 7 | const stableId = React.useId() 8 | 9 | return ( 10 |
11 |
12 |

Welcome to SotaSWE

13 |
14 |
15 | 16 | 17 |
18 |
19 | ); 20 | } -------------------------------------------------------------------------------- /src/webviews/@task/use-task.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Event, Task } from '../../model'; 3 | import { ContextItemWithId } from '../..'; 4 | 5 | export enum Status { 6 | Idle = 'idle', 7 | Loading = 'loading', 8 | Success = 'success', 9 | Error = 'error', 10 | } 11 | 12 | type AsyncState = 13 | | { status: Status.Idle; data: undefined } 14 | | { status: Status.Loading; data: undefined } 15 | | { status: Status.Success; data: T } 16 | | { status: Status.Error; data: undefined }; 17 | 18 | function getTask() { 19 | vscode.postMessage({ 20 | type: 'init', 21 | newSession: false, 22 | }); 23 | } 24 | 25 | export function useTask() { 26 | const [state, setState] = React.useState>({ 27 | status: Status.Idle, 28 | data: undefined, 29 | }); 30 | 31 | React.useEffect(() => { 32 | setState({ data: undefined, status: Status.Loading }); 33 | getTask(); 34 | }, []); 35 | 36 | // Initial message event 37 | React.useEffect(() => { 38 | const handleMessage = (event: MessageEvent) => { 39 | if (event.data.type === 'init-response') { 40 | setState({ status: Status.Success, data: { task: event.data.task } }); 41 | // console.log('sessionId', event.data.task.sessionId); 42 | } 43 | }; 44 | window.addEventListener('message', handleMessage); 45 | return () => { 46 | window.removeEventListener('message', handleMessage); 47 | }; 48 | }, []); 49 | 50 | // Added by Sandeep and Zi 51 | 52 | // piping the current state from the bridge 53 | React.useEffect(() => { 54 | // handles messages from the extension 55 | const messageHandler = (event: MessageEvent) => { 56 | const message = event.data; 57 | 58 | // Only PanelProvider sends updateState messages 59 | // if (message.command === 'initial-state') { 60 | // dispatch({ 61 | // initialAppState: message.initialAppState, 62 | // type: 'initial-state', 63 | // }); 64 | // } 65 | 66 | if (message.command === 'state-updated') { 67 | setState({ data: { task: message.initialAppState.currentTask }, status: Status.Success }); 68 | } 69 | }; 70 | 71 | // listen for messages 72 | window.addEventListener('message', messageHandler); 73 | return () => window.removeEventListener('message', messageHandler); 74 | }, []); 75 | 76 | function sendRequest( 77 | query: string, 78 | sessionId: string, 79 | variables: ContextItemWithId[], 80 | images: string[] 81 | ) { 82 | if (state.data) { 83 | const { preset } = state.data.task; 84 | 85 | vscode.postMessage({ 86 | type: 'task-feedback', 87 | query, 88 | sessionId, 89 | variables, 90 | images, 91 | modelSelection: { 92 | model: preset.model, 93 | provider: { 94 | name: preset.provider, 95 | apiBase: preset.customBaseUrl, 96 | apiKey: preset.apiKey, 97 | }, 98 | }, 99 | }); 100 | } 101 | } 102 | 103 | return Object.assign(state, { sendRequest }); 104 | } 105 | -------------------------------------------------------------------------------- /src/webviews/assets/image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/webviews/assets/providers-logos/anthropic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/webviews/assets/providers-logos/gemini.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/webviews/assets/providers-logos/open-router.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/webviews/components/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Slot, Slottable } from '@radix-ui/react-slot'; 3 | import { cva, type VariantProps } from 'class-variance-authority'; 4 | import { cn } from 'utils/cn'; 5 | 6 | const baseClassNames = [ 7 | 'focus-visible:outline-none focus-visible:ring-offset-2 focus-visible:ring-2 focus-visible:ring-focus-border disabled:opacity-50', 8 | '[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', // Do these work? 9 | 'group relative overflow-hidden inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xs text-xs font-medium transition-all', 10 | ]; 11 | 12 | const buttonVariants = cva(baseClassNames.join(' '), { 13 | variants: { 14 | variant: { 15 | primary: 16 | 'bg-button-primary-background text-button-primary-foreground hover:bg-button-primary-hover-background', 17 | secondary: 18 | 'bg-button-secondary-background text-button-secondary-foreground hover:text-button-secondary-foreground hover:bg-button-secondary-hover-background', 19 | destructive: 'text-error-foreground hover:text-error-foreground hover:brightness-125', 20 | ghost: 'text-button-secondary-foreground hover:text-foreground', 21 | }, 22 | size: { 23 | xs: 'px-1 py-0.5', 24 | sm: 'px-2 py-1', 25 | md: 'px-4 py-2', 26 | lg: 'rounded px-8 py-2', 27 | icon: 'h-10 w-10', 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: 'primary', 32 | size: 'sm', 33 | }, 34 | }); 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean; 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, children, ...rest }, ref) => { 44 | const Comp = asChild ? Slot : 'button'; 45 | return ( 46 | 47 | {variant === 'ghost' && ( 48 |
49 | )} 50 | {variant === 'destructive' && ( 51 |
52 | )} 53 | {children} 54 | 55 | ); 56 | } 57 | ); 58 | Button.displayName = 'Button'; 59 | 60 | export { Button, buttonVariants }; 61 | -------------------------------------------------------------------------------- /src/webviews/components/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from 'utils/cn'; 2 | import * as React from 'react'; 3 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; 4 | 5 | const Checkbox = React.forwardRef< 6 | React.ElementRef, 7 | React.ComponentPropsWithoutRef 8 | >(({ className, ...props }, ref) => ( 9 | 17 | 18 | 19 | 20 | 21 | )); 22 | Checkbox.displayName = CheckboxPrimitive.Root.displayName; 23 | 24 | export { Checkbox }; 25 | 26 | -------------------------------------------------------------------------------- /src/webviews/components/context-summary.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | interface ContextSummaryProps { 4 | context: string[]; 5 | } 6 | 7 | // function getUniqueExtensions(uris: string[]): string[] { 8 | // return Array.from( 9 | // uris.reduce((acc, uri) => { 10 | // const match = uri.match(/\.([^.]+)$/); 11 | // if (match) { 12 | // acc.add(uri); 13 | // } 14 | // return acc; 15 | // }, new Set()) 16 | // ); 17 | // } 18 | 19 | export function ContextSummary(props: ContextSummaryProps) { 20 | const { context } = props; 21 | //const urisWithUniqueIcons = getUniqueExtensions(context); 22 | const hasOneItem = context.length == 1; 23 | return ( 24 |

25 | @ {context.length} {hasOneItem ? "item" : "items"} 26 |

27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/webviews/components/cost-icon.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from 'utils/cn'; 2 | import { SimpleHTMLElementProps } from 'utils/types'; 3 | 4 | type CostIconProps = SimpleHTMLElementProps; 5 | 6 | export function CostIcon(props: CostIconProps) { 7 | const { className, ...rest } = props; 8 | return ( 9 | 17 | 18 | $ 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/webviews/components/detail-summary.tsx: -------------------------------------------------------------------------------- 1 | export function Detail() {} 2 | 3 | export function Summary() {} 4 | -------------------------------------------------------------------------------- /src/webviews/components/exchange/exchange-base.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from 'utils/cn'; 2 | import { SimpleHTMLElementProps } from '../../utils/types'; 3 | 4 | export function Exchange(props: SimpleHTMLElementProps) { 5 | const { className, children, ...rest } = props; 6 | return ( 7 |
8 | {children} 9 |
10 | ); 11 | } 12 | 13 | export function ExchangeHeader(props: SimpleHTMLElementProps) { 14 | const { children, className, ...rest } = props; 15 | return ( 16 |

17 | {children} 18 |

19 | ); 20 | } 21 | 22 | export function ExchangeContent(props: SimpleHTMLElementProps) { 23 | const { children, ...rest } = props; 24 | return
{children}
; 25 | } 26 | -------------------------------------------------------------------------------- /src/webviews/components/exchange/request.tsx: -------------------------------------------------------------------------------- 1 | import { Request } from '../../../model'; 2 | import { ContextSummary } from '../context-summary'; 3 | import { Exchange, ExchangeHeader, ExchangeContent } from './exchange-base'; 4 | 5 | export function RequestViewItem(props: Request) { 6 | const { username, message, context } = props; 7 | return ( 8 | 9 | {username} 10 | 11 |

{message}

12 | {context.length > 0 && } 13 |
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/webviews/components/fileicon.tsx: -------------------------------------------------------------------------------- 1 | import * as DOMPurify from 'isomorphic-dompurify'; 2 | import { themeIcons } from 'seti-file-icons'; 3 | 4 | const FileIcon = ({ 5 | filename, 6 | height, 7 | width, 8 | }: { 9 | filename: string; 10 | height: string; 11 | width: string; 12 | }) => { 13 | const filenameParts = filename.includes(' (') ? filename.split(' ') : [filename, '']; 14 | filenameParts.pop(); 15 | 16 | const getIcon = themeIcons({ 17 | blue: '#268bd2', 18 | grey: '#657b83', 19 | 'grey-light': '#839496', 20 | green: '#859900', 21 | orange: '#cb4b16', 22 | pink: '#d33682', 23 | purple: '#6c71c4', 24 | red: '#dc322f', 25 | white: '#fdf6e3', 26 | yellow: '#b58900', 27 | ignore: '#586e75', 28 | }); 29 | 30 | // Sanitize the SVG string before rendering it 31 | const { svg, color } = getIcon(filenameParts.join(' ')); 32 | const sanitizedSVG = DOMPurify.sanitize(svg); 33 | 34 | return ( 35 |
39 | ); 40 | }; 41 | 42 | export default FileIcon; 43 | -------------------------------------------------------------------------------- /src/webviews/components/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { cn } from 'utils/cn'; 3 | 4 | const Input = React.forwardRef>( 5 | ({ className, type, ...props }, ref) => { 6 | return ( 7 | 17 | ); 18 | } 19 | ); 20 | Input.displayName = 'Input'; 21 | 22 | export { Input }; 23 | -------------------------------------------------------------------------------- /src/webviews/components/input/types.ts: -------------------------------------------------------------------------------- 1 | import { ContextProviderDescription } from "../../../context/providers/types"; 2 | 3 | export type ComboBoxItemType = 4 | | "contextProvider" 5 | | "file" 6 | | "query" 7 | | "folder" 8 | | "action"; 9 | 10 | interface ComboBoxSubAction { 11 | label: string; 12 | icon: string; 13 | action: (item: ComboBoxItem) => void; 14 | } 15 | 16 | export interface ComboBoxItem { 17 | title: string; 18 | description: string; 19 | id?: string; 20 | content?: string; 21 | type: ComboBoxItemType; 22 | contextProvider?: ContextProviderDescription; 23 | query?: string; 24 | label?: string; 25 | icon?: string; 26 | action?: () => void; 27 | subActions?: ComboBoxSubAction[]; 28 | } 29 | -------------------------------------------------------------------------------- /src/webviews/components/preset.tsx: -------------------------------------------------------------------------------- 1 | import { Provider, ProviderType } from '../../model'; 2 | import AnthropicLogo from '../assets/providers-logos/anthropic.svg'; 3 | // import AWSBedrockLogo from '../assets/providers-logos/aws-bedrock.svg'; 4 | // import GeminiLogo from '../assets/providers-logos/gemini.svg'; 5 | // import OllamaLogo from '../assets/providers-logos/ollama.svg'; 6 | // import OpenAILogo from '../assets/providers-logos/openai.svg'; 7 | import OpenRouterLogo from '../assets/providers-logos/open-router.svg'; 8 | import { cn } from 'utils/cn'; 9 | 10 | const logoMap = new Map>>(); 11 | logoMap.set(Provider.Anthropic, AnthropicLogo); 12 | // logoMap.set(Provider.OpenAI, OpenAILogo); 13 | logoMap.set(Provider.OpenRouter, OpenRouterLogo); 14 | // logoMap.set(Provider.GoogleGemini, GeminiLogo); 15 | // logoMap.set(Provider.AWSBedrock, AWSBedrockLogo); 16 | // logoMap.set(Provider.OpenAICompatible, OpenAILogo); 17 | // logoMap.set(Provider.Ollama, OllamaLogo); 18 | 19 | export type PresetLogoProps = React.SVGProps & { 20 | provider: ProviderType; 21 | }; 22 | 23 | export function PresetLogo(props: PresetLogoProps) { 24 | const { provider, className, ...rest } = props; 25 | const Logo = logoMap.get(provider); 26 | return Logo ? : null; 27 | } 28 | -------------------------------------------------------------------------------- /src/webviews/components/progress-indicator.tsx: -------------------------------------------------------------------------------- 1 | // VSCode-like progress indicator 2 | 3 | import { cn } from 'utils/cn'; 4 | import { SimpleHTMLElementProps } from 'utils/types'; 5 | 6 | type ProgressIndicatorProps = SimpleHTMLElementProps & { 7 | label?: string; 8 | }; 9 | 10 | export function ProgressIndicator(props: ProgressIndicatorProps) { 11 | const { className, label = 'Loading...', ...rest } = props; 12 | return ( 13 |
20 |
21 |
22 |
23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/webviews/components/safeimg.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | interface SafeImg { 4 | src: string; 5 | height?: string; 6 | width?: string; 7 | className?: string; 8 | fallback: React.ReactNode; 9 | } 10 | 11 | const SafeImg: React.FC = ({ 12 | src, 13 | height, 14 | width, 15 | className, 16 | fallback, 17 | }: SafeImg) => { 18 | const [hasError, setHasError] = useState(false); 19 | 20 | const [cachedSrc, setCachedSrc] = useState(null); 21 | 22 | useEffect(() => { 23 | const cachedImage = localStorage.getItem(src); 24 | if (cachedImage) { 25 | console.log("Using cached image"); 26 | setCachedSrc(cachedImage); 27 | } else { 28 | fetch(src) 29 | .then((response) => response.blob()) 30 | .then((blob) => { 31 | const reader = new FileReader(); 32 | reader.onloadend = () => { 33 | localStorage.setItem(src, reader.result as string); 34 | setCachedSrc(reader.result as string); 35 | }; 36 | reader.readAsDataURL(blob); 37 | }) 38 | .catch((error) => { 39 | // console.error("Error fetching image:", error); 40 | }); 41 | } 42 | }, [src]); 43 | 44 | const handleError = () => { 45 | setHasError(true); 46 | setCachedSrc(null); 47 | }; 48 | 49 | return ( 50 | <> 51 | {!hasError ? ( 52 | 59 | ) : ( 60 | fallback 61 | )} 62 | 63 | ); 64 | }; 65 | 66 | export default SafeImg; 67 | -------------------------------------------------------------------------------- /src/webviews/components/select.tsx: -------------------------------------------------------------------------------- 1 | import * as RUISelect from '@radix-ui/react-select'; 2 | import * as React from 'react'; 3 | import { cn } from 'utils/cn'; 4 | 5 | export type SelectProps = RUISelect.SelectProps & { 6 | className?: string; 7 | id?: string; 8 | }; 9 | 10 | export function Select(props: SelectProps) { 11 | const { children, className, ...rest } = props; 12 | return ( 13 | 14 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | {children} 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | 39 | export type OptionProps = RUISelect.SelectItemProps; 40 | 41 | export function Option(props: OptionProps) { 42 | const { className, children, ...rest } = props; 43 | return ( 44 | 48 | {children} 49 | 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/webviews/components/spinner.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from 'utils/cn'; 2 | import { SimpleHTMLElementProps } from 'utils/types'; 3 | 4 | export const Spinner = ({ className, ...rest }: SimpleHTMLElementProps) => ( 5 |
12 | ); 13 | -------------------------------------------------------------------------------- /src/webviews/components/task-definition-list.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from 'utils/cn'; 2 | 3 | type TaskDLProps = React.DetailedHTMLProps< 4 | React.HTMLAttributes, 5 | HTMLDListElement 6 | >; 7 | 8 | type HTMLElementProps = React.DetailedHTMLProps, HTMLElement>; 9 | 10 | export function TaskDL(props: TaskDLProps) { 11 | const { className, children, ...rest } = props; 12 | return ( 13 |
14 |
{children}
15 |
16 | ); 17 | } 18 | 19 | export function TaskDT(props: HTMLElementProps) { 20 | const { children, ...rest } = props; 21 | return ( 22 |
23 | {children} 24 |
25 | ); 26 | } 27 | 28 | export function TaskDD(props: HTMLElementProps) { 29 | const { className, children, ...rest } = props; 30 | return ( 31 |
32 | {children} 33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/webviews/components/terminal-preview.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { cn } from 'utils/cn'; 3 | import { SimpleHTMLElementProps } from 'utils/types'; 4 | import { Spinner } from './spinner'; 5 | 6 | export type TerminalPreviewProps = SimpleHTMLElementProps & { 7 | busy: boolean; 8 | name?: string; 9 | lines: string[]; 10 | }; 11 | 12 | export function TerminalPreview(props: TerminalPreviewProps) { 13 | const { name, lines, className, busy, ...rest } = props; 14 | const hasOneLine = lines.length === 1; 15 | return ( 16 |
24 |
25 |
26 |         {name &&
27 |           React.createElement(
28 |             hasOneLine ? 'span' : 'p',
29 |             { className: 'flex gap-1' },
30 |             
31 |               {busy ? (
32 |                 
33 |               ) : (
34 |                 
35 |               )}
36 | 
37 |               name
38 |             
39 |           )}
40 |         {lines.map((line, i) => (
41 |           

42 | $ {line} 43 |

44 | ))} 45 |
46 |
47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /src/webviews/components/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { cn } from 'utils/cn'; 3 | 4 | const Textarea = React.forwardRef>( 5 | ({ className, ...props }, ref) => { 6 | return ( 7 |