diff options
-rw-r--r-- | continuedev/pyproject.toml | 2 | ||||
-rw-r--r-- | continuedev/src/continuedev/core/sdk.py | 2 | ||||
-rw-r--r-- | continuedev/src/continuedev/libs/llm/proxy_server.py | 11 | ||||
-rw-r--r-- | continuedev/src/continuedev/libs/util/dedent.py | 8 | ||||
-rw-r--r-- | continuedev/src/continuedev/server/ide.py | 9 | ||||
-rw-r--r-- | continuedev/src/continuedev/server/ide_protocol.py | 2 | ||||
-rw-r--r-- | continuedev/src/continuedev/steps/chat.py | 26 | ||||
-rw-r--r-- | continuedev/src/continuedev/steps/core/core.py | 43 | ||||
-rw-r--r-- | continuedev/src/continuedev/steps/main.py | 7 | ||||
-rw-r--r-- | extension/package-lock.json | 165 | ||||
-rw-r--r-- | extension/package.json | 7 | ||||
-rw-r--r-- | extension/react-app/src/components/TextDialog.tsx | 3 | ||||
-rw-r--r-- | extension/react-app/src/tabs/gui.tsx | 22 | ||||
-rw-r--r-- | extension/server_version.txt | 1 | ||||
-rw-r--r-- | extension/src/activation/environmentSetup.ts | 87 | ||||
-rw-r--r-- | extension/src/continueIdeClient.ts | 4 | ||||
-rw-r--r-- | extension/src/suggestions.ts | 23 | ||||
-rw-r--r-- | extension/src/telemetry.ts | 4 |
18 files changed, 327 insertions, 99 deletions
diff --git a/continuedev/pyproject.toml b/continuedev/pyproject.toml index e33627e7..6727e29a 100644 --- a/continuedev/pyproject.toml +++ b/continuedev/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Nate Sesti <sestinj@gmail.com>"] readme = "README.md" [tool.poetry.dependencies] -python = "^3.9" +python = "^3.8" diff-match-patch = "^20230430" fastapi = "^0.95.1" typer = "^0.7.0" diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 632f8683..50a14bed 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -198,7 +198,7 @@ class ContinueSDK(AbstractContinueSDK): # Don't insert after latest user message or function call i = -1 - if history_context[i].role == "user" or history_context[i].role == "function": + if len(history_context) > 0 and (history_context[i].role == "user" or history_context[i].role == "function"): i -= 1 history_context.insert(i, msg) diff --git a/continuedev/src/continuedev/libs/llm/proxy_server.py b/continuedev/src/continuedev/libs/llm/proxy_server.py index bd831ad9..69c96ee8 100644 --- a/continuedev/src/continuedev/libs/llm/proxy_server.py +++ b/continuedev/src/continuedev/libs/llm/proxy_server.py @@ -5,6 +5,11 @@ import aiohttp from ...core.main import ChatMessage from ..llm import LLM from ..util.count_tokens import DEFAULT_ARGS, DEFAULT_MAX_TOKENS, compile_chat_messages, CHAT_MODELS, count_tokens +import certifi +import ssl + +ca_bundle_path = certifi.where() +ssl_context = ssl.create_default_context(cafile=ca_bundle_path) # SERVER_URL = "http://127.0.0.1:8080" SERVER_URL = "https://proxy-server-l6vsfbzhba-uw.a.run.app" @@ -31,7 +36,7 @@ class ProxyServer(LLM): async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]: args = self.default_args | kwargs - async with aiohttp.ClientSession() as session: + async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session: async with session.post(f"{SERVER_URL}/complete", json={ "messages": compile_chat_messages(args["model"], with_history, prompt, functions=None), "unique_id": self.unique_id, @@ -47,7 +52,7 @@ class ProxyServer(LLM): messages = compile_chat_messages( self.default_model, messages, None, functions=args.get("functions", None)) - async with aiohttp.ClientSession() as session: + async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session: async with session.post(f"{SERVER_URL}/stream_chat", json={ "messages": messages, "unique_id": self.unique_id, @@ -71,7 +76,7 @@ class ProxyServer(LLM): messages = compile_chat_messages( self.default_model, with_history, prompt, functions=args.get("functions", None)) - async with aiohttp.ClientSession() as session: + async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session: async with session.post(f"{SERVER_URL}/stream_complete", json={ "messages": messages, "unique_id": self.unique_id, diff --git a/continuedev/src/continuedev/libs/util/dedent.py b/continuedev/src/continuedev/libs/util/dedent.py index 74edd173..87876d4b 100644 --- a/continuedev/src/continuedev/libs/util/dedent.py +++ b/continuedev/src/continuedev/libs/util/dedent.py @@ -3,11 +3,19 @@ from typing import Tuple def dedent_and_get_common_whitespace(s: str) -> Tuple[str, str]: lines = s.splitlines() + if len(lines) == 0: + return "", "" # Longest common whitespace prefix lcp = lines[0].split(lines[0].strip())[0] + # Iterate through the lines for i in range(1, len(lines)): + # Empty lines are wildcards + if lines[i].strip() == "": + continue + # Iterate through the leading whitespace characters of the current line for j in range(0, len(lcp)): + # If it doesn't have the same whitespace as lcp, then update lcp if j >= len(lines[i]) or lcp[j] != lines[i][j]: lcp = lcp[:j] if lcp == "": diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index f3deecdb..e2685493 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -7,6 +7,7 @@ import uuid from fastapi import WebSocket, Body, APIRouter from uvicorn.main import Server +from ..libs.util.telemetry import capture_event from ..libs.util.queue import AsyncSubscriptionQueue from ..models.filesystem import FileSystem, RangeInFile, EditDiff, RangeInFileWithContents, RealFileSystem from ..models.filesystem_edit import AddDirectory, AddFile, DeleteDirectory, DeleteFile, FileSystemEdit, FileEdit, FileEditWithFullContents, RenameDirectory, RenameFile, SequentialFileSystemEdit @@ -145,6 +146,8 @@ class IdeProtocolServer(AbstractIdeProtocolServer): elif message_type == "commandOutput": output = data["output"] self.onCommandOutput(output) + elif message_type == "acceptRejectSuggestion": + self.onAcceptRejectSuggestion(data["accepted"]) elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory", "getUserSecret", "runCommand", "uniqueId"]: self.sub_queue.post(message_type, data) else: @@ -205,8 +208,10 @@ class IdeProtocolServer(AbstractIdeProtocolServer): # This is where you might have triggers: plugins can subscribe to certian events # like file changes, tracebacks, etc... - def onAcceptRejectSuggestion(self, suggestionId: str, accepted: bool): - pass + def onAcceptRejectSuggestion(self, accepted: bool): + capture_event(self.unique_id, "accept_reject_suggestion", { + "accepted": accepted + }) def onFileSystemUpdate(self, update: FileSystemEdit): # Access to Autopilot (so SessionManager) diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py index 17a09c3d..de2eea27 100644 --- a/continuedev/src/continuedev/server/ide_protocol.py +++ b/continuedev/src/continuedev/server/ide_protocol.py @@ -36,7 +36,7 @@ class AbstractIdeProtocolServer(ABC): """Show suggestions to the user and wait for a response""" @abstractmethod - def onAcceptRejectSuggestion(self, suggestionId: str, accepted: bool): + def onAcceptRejectSuggestion(self, accepted: bool): """Called when the user accepts or rejects a suggestion""" @abstractmethod diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py index b10ec3d7..9d556655 100644 --- a/continuedev/src/continuedev/steps/chat.py +++ b/continuedev/src/continuedev/steps/chat.py @@ -21,6 +21,7 @@ openai.api_key = OPENAI_API_KEY class SimpleChatStep(Step): user_input: str name: str = "Chat" + manage_own_chat_context: bool = True async def run(self, sdk: ContinueSDK): self.description = f"`{self.user_input}`\n\n" @@ -29,16 +30,35 @@ class SimpleChatStep(Step): self.description = "" await sdk.update_ui() - async for chunk in sdk.models.default.stream_complete(self.user_input, with_history=await sdk.get_chat_context()): + messages = await sdk.get_chat_context() + messages.append(ChatMessage( + role="user", + content=self.user_input, + summary=self.user_input + )) + + completion = "" + async for chunk in sdk.models.gpt4.stream_chat(messages): if sdk.current_step_was_deleted(): return - self.description += chunk - await sdk.update_ui() + if "content" in chunk: + self.description += chunk["content"] + completion += chunk["content"] + await sdk.update_ui() self.name = (await sdk.models.gpt35.complete( f"Write a short title for the following chat message: {self.description}")).strip() + if self.name.startswith('"') and self.name.endswith('"'): + self.name = self.name[1:-1] + + self.chat_context.append(ChatMessage( + role="assistant", + content=completion, + summary=self.name + )) + class AddFileStep(Step): name: str = "Add File" diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index 729f5e66..4eb2445c 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -154,25 +154,32 @@ class DefaultModelEditCodeStep(Step): _prompt_and_completion: str = "" - async def describe(self, models: Models) -> Coroutine[str, None, None]: - description = await models.gpt3516k.complete( - f"{self._prompt_and_completion}\n\nPlease give brief a description of the changes made above using markdown bullet points. Be concise and only mention changes made to the commit before, not prefix or suffix:") - self.name = await models.gpt3516k.complete(f"Write a very short title to describe this requested change (no quotes): '{self.user_input}'. This is the title:") + def _cleanup_output(self, output: str) -> str: + output = output.replace('\\"', '"') + output = output.replace("\\'", "'") + output = output.replace("\\n", "\n") + output = output.replace("\\t", "\t") + output = output.replace("\\\\", "\\") + if output.startswith('"') and output.endswith('"'): + output = output[1:-1] - # Remove quotes from title and description if they are wrapped - if description.startswith('"') and description.endswith('"'): - description = description[1:-1] + return output - if self.name.startswith('"') and self.name.endswith('"'): - self.name = self.name[1:-1] + async def describe(self, models: Models) -> Coroutine[str, None, None]: + description = await models.gpt3516k.complete(dedent(f"""\ + {self._prompt_and_completion} + + Please give brief a description of the changes made above using markdown bullet points. Be concise and only mention changes made to the commit before, not prefix or suffix:""")) + name = await models.gpt3516k.complete(f"Write a very short title to describe this requested change (no quotes): '{self.user_input}'. This is the title:") + self.name = self._cleanup_output(name) - return f"`{self.user_input}`\n\n" + description + return f"`{self.user_input}`\n\n{self._cleanup_output(description)}" async def get_prompt_parts(self, rif: RangeInFileWithContents, sdk: ContinueSDK, full_file_contents: str): # We don't know here all of the functions being passed in. # We care because if this prompt itself goes over the limit, then the entire message will have to be cut from the completion. # Overflow won't happen, but prune_chat_messages in count_tokens.py will cut out this whole thing, instead of us cutting out only as many lines as we need. - model_to_use = sdk.models.default + model_to_use = sdk.models.gpt4 BUFFER_FOR_FUNCTIONS = 400 total_tokens = model_to_use.count_tokens( @@ -360,7 +367,7 @@ class DefaultModelEditCodeStep(Step): # Insert the suggestion replacement = "\n".join(current_block_lines) - start_line = current_block_start + 1 + start_line = current_block_start end_line = current_block_start + index_of_last_line_in_block await sdk.ide.showSuggestion(FileEdit( filepath=rif.filepath, @@ -368,10 +375,9 @@ class DefaultModelEditCodeStep(Step): start_line, 0, end_line, 0), replacement=replacement )) - if replacement == "": - current_line_in_file += 1 # Reset current block / update variables + current_line_in_file += 1 offset_from_blocks += len(current_block_lines) original_lines_below_previous_blocks = original_lines_below_previous_blocks[ index_of_last_line_in_block + 1:] @@ -493,7 +499,7 @@ class DefaultModelEditCodeStep(Step): await sdk.ide.showSuggestion(FileEdit( filepath=rif.filepath, range=Range.from_shorthand( - current_block_start + 1, 0, current_block_start + len(original_lines_below_previous_blocks), 0), + current_block_start, 0, current_block_start + len(original_lines_below_previous_blocks), 0), replacement="\n".join(current_block_lines) )) @@ -585,10 +591,17 @@ class UserInputStep(Step): name: str = "User Input" hide: bool = True + manage_own_chat_context: bool = True + async def describe(self, models: Models) -> Coroutine[str, None, None]: return self.user_input async def run(self, sdk: ContinueSDK) -> Coroutine[UserInputObservation, None, None]: + self.chat_context.append(ChatMessage( + role="user", + content=self.user_input, + summary=self.user_input + )) return UserInputObservation(user_input=self.user_input) diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index def1af4e..3cf78c40 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -266,6 +266,13 @@ class EditHighlightedCodeStep(Step): range_in_files = [RangeInFile.from_entire_file( filepath, content) for filepath, content in contents.items()] + # If still no highlighted code, create a new file and edit there + if len(range_in_files) == 0: + # Create a new file + new_file_path = "new_file.txt" + await sdk.add_file(new_file_path) + range_in_files = [RangeInFile.from_entire_file(new_file_path, "")] + await sdk.run_step(DefaultModelEditCodeStep(user_input=self.user_input, range_in_files=range_in_files)) diff --git a/extension/package-lock.json b/extension/package-lock.json index a3b528ac..0a1ba1b6 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,17 +1,18 @@ { "name": "continue", - "version": "0.0.99", + "version": "0.0.104", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.99", + "version": "0.0.104", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", "@reduxjs/toolkit": "^1.9.3", "@segment/analytics-node": "^0.0.1-beta.16", + "@sentry/node": "^7.57.0", "@styled-icons/heroicons-outline": "^10.47.0", "@vitejs/plugin-react-swc": "^3.3.2", "axios": "^1.2.5", @@ -35,7 +36,7 @@ "@types/node-fetch": "^2.6.2", "@types/react-dom": "^18.2.4", "@types/styled-components": "^5.1.26", - "@types/vscode": "^1.74.0", + "@types/vscode": "1.60", "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", @@ -49,7 +50,7 @@ "vsce": "^2.15.0" }, "engines": { - "vscode": "^1.74.0" + "vscode": "^1.60.0" } }, "node_modules/@alloc/quick-lru": { @@ -1331,6 +1332,71 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, + "node_modules/@sentry-internal/tracing": { + "version": "7.57.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.57.0.tgz", + "integrity": "sha512-tpViyDd8AhQGYYhI94xi2aaDopXOPfL2Apwrtb3qirWkomIQ2K86W1mPmkce+B0cFOnW2Dxv/ZTFKz6ghjK75A==", + "dependencies": { + "@sentry/core": "7.57.0", + "@sentry/types": "7.57.0", + "@sentry/utils": "7.57.0", + "tslib": "^2.4.1 || ^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/core": { + "version": "7.57.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.57.0.tgz", + "integrity": "sha512-l014NudPH0vQlzybtXajPxYFfs9w762NoarjObC3gu76D1jzBBFzhdRelkGpDbSLNTIsKhEDDRpgAjBWJ9icfw==", + "dependencies": { + "@sentry/types": "7.57.0", + "@sentry/utils": "7.57.0", + "tslib": "^2.4.1 || ^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node": { + "version": "7.57.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.57.0.tgz", + "integrity": "sha512-63mjyUVM6sfJFVQ5TGVRVGUsoEfESl5ABzIW1W0s9gUiQPaG8SOdaQJglb2VNrkMYxnRHgD8Q9LUh/qcmUyPGw==", + "dependencies": { + "@sentry-internal/tracing": "7.57.0", + "@sentry/core": "7.57.0", + "@sentry/types": "7.57.0", + "@sentry/utils": "7.57.0", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^2.4.1 || ^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/types": { + "version": "7.57.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.57.0.tgz", + "integrity": "sha512-D7ifoUfxuVCUyktIr5Gc+jXUbtcUMmfHdTtTbf1XCZHua5mJceK9wtl3YCg3eq/HK2Ppd52BKnTzEcS5ZKQM+w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils": { + "version": "7.57.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.57.0.tgz", + "integrity": "sha512-YXrkMCiNklqkXctn4mKYkrzNCf/dfVcRUQrkXjeBC+PHXbcpPyaJgInNvztR7Skl8lE3JPGPN4v5XhLxK1bUUg==", + "dependencies": { + "@sentry/types": "7.57.0", + "tslib": "^2.4.1 || ^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -1774,9 +1840,9 @@ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, "node_modules/@types/vscode": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.74.0.tgz", - "integrity": "sha512-LyeCIU3jb9d38w0MXFwta9r0Jx23ugujkAxdwLTNCyspdZTKUc43t7ppPbCiPoQ/Ivd/pnDFZrb4hWd45wrsgA==", + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.60.0.tgz", + "integrity": "sha512-wZt3VTmzYrgZ0l/3QmEbCq4KAJ71K3/hmMQ/nfpv84oH8e81KKwPEoQ5v8dNCxfHFVJ1JabHKmCvqdYOoVm1Ow==", "dev": true }, "node_modules/@types/ws": { @@ -2940,6 +3006,14 @@ "node": "> 0.10" } }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -5183,6 +5257,11 @@ "node": ">=8" } }, + "node_modules/lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -8183,8 +8262,7 @@ "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -9808,6 +9886,56 @@ } } }, + "@sentry-internal/tracing": { + "version": "7.57.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.57.0.tgz", + "integrity": "sha512-tpViyDd8AhQGYYhI94xi2aaDopXOPfL2Apwrtb3qirWkomIQ2K86W1mPmkce+B0cFOnW2Dxv/ZTFKz6ghjK75A==", + "requires": { + "@sentry/core": "7.57.0", + "@sentry/types": "7.57.0", + "@sentry/utils": "7.57.0", + "tslib": "^2.4.1 || ^1.9.3" + } + }, + "@sentry/core": { + "version": "7.57.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.57.0.tgz", + "integrity": "sha512-l014NudPH0vQlzybtXajPxYFfs9w762NoarjObC3gu76D1jzBBFzhdRelkGpDbSLNTIsKhEDDRpgAjBWJ9icfw==", + "requires": { + "@sentry/types": "7.57.0", + "@sentry/utils": "7.57.0", + "tslib": "^2.4.1 || ^1.9.3" + } + }, + "@sentry/node": { + "version": "7.57.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.57.0.tgz", + "integrity": "sha512-63mjyUVM6sfJFVQ5TGVRVGUsoEfESl5ABzIW1W0s9gUiQPaG8SOdaQJglb2VNrkMYxnRHgD8Q9LUh/qcmUyPGw==", + "requires": { + "@sentry-internal/tracing": "7.57.0", + "@sentry/core": "7.57.0", + "@sentry/types": "7.57.0", + "@sentry/utils": "7.57.0", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^2.4.1 || ^1.9.3" + } + }, + "@sentry/types": { + "version": "7.57.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.57.0.tgz", + "integrity": "sha512-D7ifoUfxuVCUyktIr5Gc+jXUbtcUMmfHdTtTbf1XCZHua5mJceK9wtl3YCg3eq/HK2Ppd52BKnTzEcS5ZKQM+w==" + }, + "@sentry/utils": { + "version": "7.57.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.57.0.tgz", + "integrity": "sha512-YXrkMCiNklqkXctn4mKYkrzNCf/dfVcRUQrkXjeBC+PHXbcpPyaJgInNvztR7Skl8lE3JPGPN4v5XhLxK1bUUg==", + "requires": { + "@sentry/types": "7.57.0", + "tslib": "^2.4.1 || ^1.9.3" + } + }, "@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -10116,9 +10244,9 @@ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, "@types/vscode": { - "version": "1.74.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.74.0.tgz", - "integrity": "sha512-LyeCIU3jb9d38w0MXFwta9r0Jx23ugujkAxdwLTNCyspdZTKUc43t7ppPbCiPoQ/Ivd/pnDFZrb4hWd45wrsgA==", + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.60.0.tgz", + "integrity": "sha512-wZt3VTmzYrgZ0l/3QmEbCq4KAJ71K3/hmMQ/nfpv84oH8e81KKwPEoQ5v8dNCxfHFVJ1JabHKmCvqdYOoVm1Ow==", "dev": true }, "@types/ws": { @@ -10937,6 +11065,11 @@ "easy-table": "1.1.0" } }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, "core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -12603,6 +12736,11 @@ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" }, + "lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -14662,8 +14800,7 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "tsutils": { "version": "3.21.0", diff --git a/extension/package.json b/extension/package.json index 93649e3a..2a375138 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,10 +14,10 @@ "displayName": "Continue", "pricing": "Free", "description": "The open-source coding autopilot", - "version": "0.0.99", + "version": "0.0.104", "publisher": "Continue", "engines": { - "vscode": "^1.74.0" + "vscode": "^1.60.0" }, "categories": [ "Other", @@ -223,7 +223,7 @@ "@types/node-fetch": "^2.6.2", "@types/react-dom": "^18.2.4", "@types/styled-components": "^5.1.26", - "@types/vscode": "^1.74.0", + "@types/vscode": "1.60", "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", @@ -240,6 +240,7 @@ "@electron/rebuild": "^3.2.10", "@reduxjs/toolkit": "^1.9.3", "@segment/analytics-node": "^0.0.1-beta.16", + "@sentry/node": "^7.57.0", "@styled-icons/heroicons-outline": "^10.47.0", "@vitejs/plugin-react-swc": "^3.3.2", "axios": "^1.2.5", diff --git a/extension/react-app/src/components/TextDialog.tsx b/extension/react-app/src/components/TextDialog.tsx index 2632e572..a564f884 100644 --- a/extension/react-app/src/components/TextDialog.tsx +++ b/extension/react-app/src/components/TextDialog.tsx @@ -52,6 +52,7 @@ const TextDialog = (props: { showDialog: boolean; onEnter: (text: string) => void; onClose: () => void; + message?: string; }) => { const [text, setText] = useState(""); const textAreaRef = React.createRef<HTMLTextAreaElement>(); @@ -75,7 +76,7 @@ const TextDialog = (props: { }} > <Dialog> - <P>Thanks for your feedback. We'll get back to you soon!</P> + <P>{props.message || ""}</P> <TextArea rows={10} ref={textAreaRef} diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index 658aa503..e0b32a24 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -103,6 +103,7 @@ function GUI(props: GUIProps) { } as any); const [showFeedbackDialog, setShowFeedbackDialog] = useState(false); + const [feedbackDialogMessage, setFeedbackDialogMessage] = useState(""); const topGuiDivRef = useRef<HTMLDivElement>(null); const client = useContinueGUIProtocol(); @@ -259,6 +260,7 @@ function GUI(props: GUIProps) { onClose={() => { setShowFeedbackDialog(false); }} + message={feedbackDialogMessage} ></TextDialog> <TopGUIDiv @@ -396,17 +398,24 @@ function GUI(props: GUIProps) { </div> <HeaderButtonWithText onClick={() => { - client?.changeDefaultModel( - usingFastModel ? "gpt-4" : "gpt-3.5-turbo" - ); + // client?.changeDefaultModel( + // usingFastModel ? "gpt-4" : "gpt-3.5-turbo" + // ); + if (!usingFastModel) { + // Show the dialog + setFeedbackDialogMessage( + "We don't yet support local models, but we're working on it! If privacy is a concern of yours, please use the feedback button in the bottom right to let us know." + ); + setShowFeedbackDialog(true); + } setUsingFastModel((prev) => !prev); }} - text={usingFastModel ? "gpt-3.5-turbo" : "gpt-4"} + text={usingFastModel ? "local" : "gpt-4"} > <div style={{ fontSize: "18px", marginLeft: "2px", marginRight: "2px" }} > - {usingFastModel ? "⚡" : "🧠"} + {usingFastModel ? "🔒" : "🧠"} </div> </HeaderButtonWithText> <HeaderButtonWithText @@ -428,6 +437,9 @@ function GUI(props: GUIProps) { <HeaderButtonWithText onClick={() => { // Set dialog open + setFeedbackDialogMessage( + "Having trouble using Continue? Want a new feature? Let us know! This box is anonymous, but we will promptly address your feedback." + ); setShowFeedbackDialog(true); }} text="Feedback" diff --git a/extension/server_version.txt b/extension/server_version.txt new file mode 100644 index 00000000..da5ce208 --- /dev/null +++ b/extension/server_version.txt @@ -0,0 +1 @@ +0.0.101
\ No newline at end of file diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index d4c81d2e..4e6be9b3 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -9,6 +9,7 @@ import { getContinueServerUrl } from "../bridge"; import fetch from "node-fetch"; import * as vscode from "vscode"; import fkill from "fkill"; +import { sendTelemetryEvent, TelemetryEvent } from "../telemetry"; const MAX_RETRIES = 5; async function retryThenFail( @@ -17,13 +18,16 @@ async function retryThenFail( ): Promise<any> { try { return await fn(); - } catch (e) { + } catch (e: any) { if (retries > 0) { return await retryThenFail(fn, retries - 1); } vscode.window.showErrorMessage( "Failed to set up Continue extension. Please email nate@continue.dev and we'll get this fixed ASAP!" ); + sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, { + error: e.message, + }); throw e; } } @@ -47,6 +51,12 @@ async function runCommand(cmd: string): Promise<[string, string | undefined]> { stdout = ""; } + if (stderr) { + sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, { + error: stderr, + }); + } + return [stdout, stderr]; } @@ -71,9 +81,11 @@ async function getPythonPipCommands() { } } + let pipCmd = pythonCmd.endsWith("3") ? "pip3" : "pip"; + const version = stdout.split(" ")[1]; const [major, minor] = version.split("."); - if (parseInt(major) !== 3 || parseInt(minor) < 7) { + if (parseInt(major) !== 3 || parseInt(minor) < 8) { // Need to check specific versions const checkPython3VersionExists = async (minorVersion: number) => { const [stdout, stderr] = await runCommand( @@ -82,24 +94,27 @@ async function getPythonPipCommands() { return typeof stderr === "undefined" || stderr === ""; }; - const validVersions = [7, 8, 9, 10, 11, 12]; + const VALID_VERSIONS = [8, 9, 10, 11, 12]; let versionExists = false; - for (const minorVersion of validVersions) { + for (const minorVersion of VALID_VERSIONS) { if (await checkPython3VersionExists(minorVersion)) { versionExists = true; - break; + pythonCmd = `python3.${minorVersion}`; + pipCmd = `pip3.${minorVersion}`; } } if (!versionExists) { vscode.window.showErrorMessage( - "Continue requires Python3 version 3.7 or greater. Please update your Python3 installation, reload VS Code, and try again." + "Continue requires Python3 version 3.8 or greater. Please update your Python3 installation, reload VS Code, and try again." ); - throw new Error("Python3 is not installed."); + throw new Error("Python3.8 or greater is not installed."); } + } else { + pythonCmd = `python${major}.${minor}`; + pipCmd = `pip${major}.${minor}`; } - const pipCmd = pythonCmd.endsWith("3") ? "pip3" : "pip"; return [pythonCmd, pipCmd]; } @@ -283,34 +298,33 @@ export async function startContinuePythonServer() { return; } - if (await checkServerRunning(serverUrl)) { - // Kill the server if it is running an old version - if (fs.existsSync(serverVersionPath())) { - const serverVersion = fs.readFileSync(serverVersionPath(), "utf8"); - if (serverVersion === getExtensionVersion()) { - return; + return await retryThenFail(async () => { + if (await checkServerRunning(serverUrl)) { + // Kill the server if it is running an old version + if (fs.existsSync(serverVersionPath())) { + const serverVersion = fs.readFileSync(serverVersionPath(), "utf8"); + if (serverVersion === getExtensionVersion()) { + return; + } } + console.log("Killing old server..."); + await fkill(":65432"); } - console.log("Killing old server..."); - await fkill(":65432"); - } - // Do this after above check so we don't have to waste time setting up the env - await setupPythonEnv(); + // Do this after above check so we don't have to waste time setting up the env + await setupPythonEnv(); - let activateCmd = ". env/bin/activate"; - let pythonCmd = "python3"; - if (process.platform == "win32") { - activateCmd = ".\\env\\Scripts\\activate"; - pythonCmd = "python"; - } + const [pythonCmd] = await getPythonPipCommands(); + const activateCmd = + process.platform == "win32" + ? ".\\env\\Scripts\\activate" + : ". env/bin/activate"; - let command = `cd "${path.join( - getExtensionUri().fsPath, - "scripts" - )}" && ${activateCmd} && cd .. && ${pythonCmd} -m scripts.run_continue_server`; + const command = `cd "${path.join( + getExtensionUri().fsPath, + "scripts" + )}" && ${activateCmd} && cd .. && ${pythonCmd} -m scripts.run_continue_server`; - return await retryThenFail(async () => { console.log("Starting Continue python server..."); return new Promise(async (resolve, reject) => { @@ -318,22 +332,25 @@ export async function startContinuePythonServer() { const child = spawn(command, { shell: true, }); - child.stdout.on("data", (data: any) => { - console.log(`stdout: ${data}`); - }); child.stderr.on("data", (data: any) => { + console.log(`stdout: ${data}`); if ( data.includes("Uvicorn running on") || // Successfully started the server data.includes("address already in use") // The server is already running (probably a simultaneously opened VS Code window) ) { console.log("Successfully started Continue python server"); resolve(null); - } else { - console.log(`stderr: ${data}`); + } else if (data.includes("ERROR") || data.includes("Traceback")) { + sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, { + error: data, + }); } }); child.on("error", (error: any) => { console.log(`error: ${error.message}`); + sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, { + error: error.message, + }); }); // Write the current version of vscode to a file called server_version.txt diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 8f45b849..999bca88 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -408,6 +408,10 @@ class IdeProtocolClient { sendHighlightedCode(highlightedCode: (RangeInFile & { contents: string })[]) { this.messenger?.send("highlightedCodePush", { highlightedCode }); } + + sendAcceptRejectSuggestion(accepted: boolean) { + this.messenger?.send("acceptRejectSuggestion", { accepted }); + } } export default IdeProtocolClient; diff --git a/extension/src/suggestions.ts b/extension/src/suggestions.ts index baa49711..6e5a444f 100644 --- a/extension/src/suggestions.ts +++ b/extension/src/suggestions.ts @@ -5,7 +5,7 @@ import { translate, readFileAtRange } from "./util/vscode"; import * as fs from "fs"; import * as path from "path"; import { registerAllCodeLensProviders } from "./lang-server/codeLens"; -import { extensionContext } from "./activation/activate"; +import { extensionContext, ideProtocolClient } from "./activation/activate"; export interface SuggestionRanges { oldRange: vscode.Range; @@ -241,19 +241,14 @@ function selectSuggestion( suggestions = JSON.parse(rawData); } - if (accept === "new" || (accept === "selected" && suggestion.newSelected)) { - suggestions.push({ - accepted: true, - timestamp: Date.now(), - suggestion: suggestion.newContent, - }); - } else { - suggestions.push({ - accepted: false, - timestamp: Date.now(), - suggestion: suggestion.newContent, - }); - } + const accepted = + accept === "new" || (accept === "selected" && suggestion.newSelected); + suggestions.push({ + accepted, + timestamp: Date.now(), + suggestion: suggestion.newContent, + }); + ideProtocolClient.sendAcceptRejectSuggestion(accepted); // Write the updated suggestions back to the file fs.writeFileSync( diff --git a/extension/src/telemetry.ts b/extension/src/telemetry.ts index ea71a545..db5cb8ca 100644 --- a/extension/src/telemetry.ts +++ b/extension/src/telemetry.ts @@ -35,6 +35,8 @@ export enum TelemetryEvent { AutoDebugThisTest = "AutoDebugThisTest", // Command run to generate docstring GenerateDocstring = "GenerateDocstring", + // Error setting up the extension + ExtensionSetupError = "ExtensionSetupError", } export function sendTelemetryEvent( @@ -48,4 +50,4 @@ export function sendTelemetryEvent( userId: vscode.env.machineId, properties, }); -}
\ No newline at end of file +} |