From c5102b0997baa81ce544514d6b5b4d5a2eae804f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 14 Jul 2023 13:45:10 -0700 Subject: insidious client_state vs application_state err --- extension/src/continueIdeClient.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'extension/src/continueIdeClient.ts') diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 4c1fdf1e..6dd117d3 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -386,8 +386,12 @@ class IdeProtocolClient { contents = editor.document.getText(); } }); - if (!contents) { - contents = fs.readFileSync(filepath, "utf-8"); + if (typeof contents === "undefined") { + if (fs.existsSync(filepath)) { + contents = fs.readFileSync(filepath, "utf-8"); + } else { + contents = ""; + } } return contents; } -- cgit v1.2.3-70-g09d2 From 98085ebc2eb39569db89a34c8d820a5d0bfe8f84 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 14 Jul 2023 17:40:16 -0700 Subject: fixed config explanation, don't read terminals --- continuedev/src/continuedev/steps/open_config.py | 4 ++-- extension/src/continueIdeClient.ts | 26 ++++++++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) (limited to 'extension/src/continueIdeClient.ts') diff --git a/continuedev/src/continuedev/steps/open_config.py b/continuedev/src/continuedev/steps/open_config.py index 87f03e9f..af55a95a 100644 --- a/continuedev/src/continuedev/steps/open_config.py +++ b/continuedev/src/continuedev/steps/open_config.py @@ -14,10 +14,10 @@ class OpenConfigStep(Step): "custom_commands": [ { "name": "test", - "description": "Write unit tests like I do for the highlighted code" + "description": "Write unit tests like I do for the highlighted code", "prompt": "Write a comprehensive set of unit tests for the selected code. It should setup, run tests that check for correctness including important edge cases, and teardown. Ensure that the tests are complete and sophisticated." } - ], + ] ``` `"name"` is the command you will type. `"description"` is the description displayed in the slash command menu. diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 6dd117d3..2c96763d 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -15,6 +15,7 @@ import { FileEditWithFullContents } from "../schema/FileEditWithFullContents"; import fs = require("fs"); import { WebsocketMessenger } from "./util/messenger"; import { diffManager } from "./diffs"; +import path = require("path"); class IdeProtocolClient { private messenger: WebsocketMessenger | null = null; @@ -350,25 +351,28 @@ class IdeProtocolClient { // ------------------------------------ // // Respond to request + private editorIsTerminal(editor: vscode.TextEditor) { + return ( + !!path.basename(editor.document.uri.fsPath).match(/\d/) || + (editor.document.languageId === "plaintext" && + editor.document.getText() === "accessible-buffer-accessible-buffer-") + ); + } + getOpenFiles(): string[] { return vscode.window.visibleTextEditors - .filter((editor) => { - return !( - editor.document.uri.fsPath.endsWith("/1") || - (editor.document.languageId === "plaintext" && - editor.document.getText() === - "accessible-buffer-accessible-buffer-") - ); - }) + .filter((editor) => !this.editorIsTerminal(editor)) .map((editor) => { return editor.document.uri.fsPath; }); } getVisibleFiles(): string[] { - return vscode.window.visibleTextEditors.map((editor) => { - return editor.document.uri.fsPath; - }); + return vscode.window.visibleTextEditors + .filter((editor) => !this.editorIsTerminal(editor)) + .map((editor) => { + return editor.document.uri.fsPath; + }); } saveFile(filepath: string) { -- cgit v1.2.3-70-g09d2 From 53536dce999f2858102b7bf969824141140c5fc7 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 15 Jul 2023 21:55:47 -0700 Subject: fixed reading of terminal and other vscode windows --- continuedev/src/continuedev/server/ide.py | 34 ++++++--------- extension/package-lock.json | 4 +- extension/package.json | 2 +- extension/src/activation/environmentSetup.ts | 54 ++++++++++++------------ extension/src/continueIdeClient.ts | 63 ++++++++++++++++------------ 5 files changed, 79 insertions(+), 78 deletions(-) (limited to 'extension/src/continueIdeClient.ts') diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index e5e8de02..a8868a9a 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -126,7 +126,8 @@ class IdeProtocolServer(AbstractIdeProtocolServer): workspace_directory: str = None unique_id: str = None - async def initialize(self) -> List[str]: + async def initialize(self, session_id: str) -> List[str]: + self.session_id = session_id await self._send_json("workspaceDirectory", {}) await self._send_json("uniqueId", {}) other_msgs = [] @@ -287,32 +288,24 @@ class IdeProtocolServer(AbstractIdeProtocolServer): pass def onFileEdits(self, edits: List[FileEditWithFullContents]): - # Send the file edits to ALL autopilots. - # Maybe not ideal behavior - for _, session in self.session_manager.sessions.items(): - session.autopilot.handle_manual_edits(edits) + session_manager.sessions[self.session_id].autopilot.handle_manual_edits( + edits) def onDeleteAtIndex(self, index: int): - for _, session in self.session_manager.sessions.items(): - create_async_task( - session.autopilot.delete_at_index(index), self.unique_id) + create_async_task( + session_manager.sessions[self.session_id].autopilot.delete_at_index(index), self.unique_id) def onCommandOutput(self, output: str): - # Send the output to ALL autopilots. - # Maybe not ideal behavior - for _, session in self.session_manager.sessions.items(): - create_async_task( - session.autopilot.handle_command_output(output), self.unique_id) + create_async_task( + self.session_manager.sessions[self.session_id].autopilot.handle_command_output(output), self.unique_id) def onHighlightedCodeUpdate(self, range_in_files: List[RangeInFileWithContents]): - for _, session in self.session_manager.sessions.items(): - create_async_task( - session.autopilot.handle_highlighted_code(range_in_files), self.unique_id) + create_async_task( + self.session_manager.sessions[self.session_id].autopilot.handle_highlighted_code(range_in_files), self.unique_id) def onMainUserInput(self, input: str): - for _, session in self.session_manager.sessions.items(): - create_async_task( - session.autopilot.accept_user_input(input), self.unique_id) + create_async_task( + self.session_manager.sessions[self.session_id].autopilot.accept_user_input(input), self.unique_id) # Request information. Session doesn't matter. async def getOpenFiles(self) -> List[str]: @@ -440,10 +433,9 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str = None): ideProtocolServer.handle_json(message_type, data)) ideProtocolServer = IdeProtocolServer(session_manager, websocket) - ideProtocolServer.session_id = session_id if session_id is not None: session_manager.registered_ides[session_id] = ideProtocolServer - other_msgs = await ideProtocolServer.initialize() + other_msgs = await ideProtocolServer.initialize(session_id) for other_msg in other_msgs: handle_msg(other_msg) diff --git a/extension/package-lock.json b/extension/package-lock.json index f1423041..6f777c72 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.173", + "version": "0.0.174", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.173", + "version": "0.0.174", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 0638e768..9fe38f7f 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "The open-source coding autopilot", - "version": "0.0.173", + "version": "0.0.174", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 928fe04b..df609a34 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -260,34 +260,34 @@ async function createPythonVenv(pythonCmd: string) { await vscode.window.showErrorMessage(msg); } else if (checkEnvExists()) { console.log("Successfully set up python env at ", `${serverPath()}/env`); + } else if ( + stderr?.includes("Permission denied") && + stderr?.includes("python.exe") + ) { + // This might mean that another window is currently using the python.exe file to install requirements + // So we want to wait and try again + let i = 0; + await new Promise((resolve, reject) => + setInterval(() => { + if (i > 5) { + reject("Timed out waiting for other window to create env..."); + } + if (checkEnvExists()) { + resolve(null); + } else { + console.log("Waiting for other window to create env..."); + } + i++; + }, 5000) + ); } else { - try { - // This might mean that another window is currently using the python.exe file to install requirements - // So we want to wait and try again - let i = 0; - await new Promise((resolve, reject) => - setInterval(() => { - if (i > 5) { - reject(); - } - if (checkEnvExists()) { - resolve(null); - } else { - console.log("Waiting for other window to create env..."); - } - i++; - }, 5000) - ); - } catch (e) { - const msg = [ - "Python environment not successfully created. Trying again. Here was the stdout + stderr: ", - `stdout: ${stdout}`, - `stderr: ${stderr}`, - `e: ${e}`, - ].join("\n\n"); - console.log(msg); - throw new Error(msg); - } + const msg = [ + "Python environment not successfully created. Trying again. Here was the stdout + stderr: ", + `stdout: ${stdout}`, + `stderr: ${stderr}`, + ].join("\n\n"); + console.log(msg); + throw new Error(msg); } } } diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 2c96763d..fac0a227 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -104,8 +104,11 @@ class IdeProtocolClient { // } // }); - // Setup listeners for any file changes in open editors + // Setup listeners for any selection changes in open editors vscode.window.onDidChangeTextEditorSelection((event) => { + if (this.editorIsTerminal(event.textEditor)) { + return; + } if (this._highlightDebounce) { clearTimeout(this._highlightDebounce); } @@ -376,20 +379,24 @@ class IdeProtocolClient { } saveFile(filepath: string) { - vscode.window.visibleTextEditors.forEach((editor) => { - if (editor.document.uri.fsPath === filepath) { - editor.document.save(); - } - }); + vscode.window.visibleTextEditors + .filter((editor) => !this.editorIsTerminal(editor)) + .forEach((editor) => { + if (editor.document.uri.fsPath === filepath) { + editor.document.save(); + } + }); } readFile(filepath: string): string { let contents: string | undefined; - vscode.window.visibleTextEditors.forEach((editor) => { - if (editor.document.uri.fsPath === filepath) { - contents = editor.document.getText(); - } - }); + vscode.window.visibleTextEditors + .filter((editor) => !this.editorIsTerminal(editor)) + .forEach((editor) => { + if (editor.document.uri.fsPath === filepath) { + contents = editor.document.getText(); + } + }); if (typeof contents === "undefined") { if (fs.existsSync(filepath)) { contents = fs.readFileSync(filepath, "utf-8"); @@ -429,25 +436,27 @@ class IdeProtocolClient { getHighlightedCode(): RangeInFile[] { // TODO let rangeInFiles: RangeInFile[] = []; - vscode.window.visibleTextEditors.forEach((editor) => { - editor.selections.forEach((selection) => { - // if (!selection.isEmpty) { - rangeInFiles.push({ - filepath: editor.document.uri.fsPath, - range: { - start: { - line: selection.start.line, - character: selection.start.character, - }, - end: { - line: selection.end.line, - character: selection.end.character, + vscode.window.visibleTextEditors + .filter((editor) => !this.editorIsTerminal(editor)) + .forEach((editor) => { + editor.selections.forEach((selection) => { + // if (!selection.isEmpty) { + rangeInFiles.push({ + filepath: editor.document.uri.fsPath, + range: { + start: { + line: selection.start.line, + character: selection.start.character, + }, + end: { + line: selection.end.line, + character: selection.end.character, + }, }, - }, + }); + // } }); - // } }); - }); return rangeInFiles; } -- cgit v1.2.3-70-g09d2 From dc64c73adb8c8a2aeb3210bc9f4ff1bd82c03de2 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 17 Jul 2023 21:09:30 -0700 Subject: show exact prompt/completion logs --- continuedev/src/continuedev/core/main.py | 1 + continuedev/src/continuedev/core/sdk.py | 28 +++++++++++-- continuedev/src/continuedev/libs/llm/openai.py | 47 +++++++++++++++++----- .../src/continuedev/libs/llm/proxy_server.py | 33 +++++++++++---- .../src/continuedev/libs/util/count_tokens.py | 7 ++++ continuedev/src/continuedev/server/gui.py | 9 +++++ continuedev/src/continuedev/server/ide.py | 14 ++++++- continuedev/src/continuedev/server/ide_protocol.py | 4 ++ .../src/continuedev/server/session_manager.py | 2 +- extension/package-lock.json | 4 +- extension/package.json | 2 +- .../react-app/src/components/StepContainer.tsx | 17 +++++++- .../src/hooks/ContinueGUIClientProtocol.ts | 2 + .../react-app/src/hooks/useContinueGUIProtocol.ts | 4 ++ extension/react-app/src/pages/gui.tsx | 1 + extension/src/continueIdeClient.ts | 38 +++++++++++++++++ 16 files changed, 185 insertions(+), 28 deletions(-) (limited to 'extension/src/continueIdeClient.ts') diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index 88690c83..5931d978 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -102,6 +102,7 @@ class HistoryNode(ContinueBaseModel): depth: int deleted: bool = False active: bool = True + logs: List[str] = [] def to_chat_messages(self) -> List[ChatMessage]: if self.step.description is None or self.step.manage_own_chat_context: diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 280fefa8..53214384 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -37,6 +37,25 @@ class Models: model_providers: List[ModelProvider] system_message: str + """ + Better to have sdk.llm.stream_chat(messages, model="claude-2"). + Then you also don't care that it' async. + And it's easier to add more models. + And intermediate shared code is easier to add. + And you can make constants like ContinueModels.GPT35 = "gpt-3.5-turbo" + PromptTransformer would be a good concept: You pass a prompt or list of messages and a model, then it outputs the prompt for that model. + Easy to reason about, can place anywhere. + And you can even pass a Prompt object to sdk.llm.stream_chat maybe, and it'll automatically be transformed for the given model. + This can all happen inside of Models? + + class Prompt: + def __init__(self, ...info): + '''take whatever info is needed to describe the prompt''' + + def to_string(self, model: str) -> str: + '''depending on the model, return the single prompt string''' + """ + def __init__(self, sdk: "ContinueSDK", model_providers: List[ModelProvider]): self.sdk = sdk self.model_providers = model_providers @@ -59,8 +78,8 @@ class Models: def __load_openai_model(self, model: str) -> OpenAI: api_key = self.provider_keys["openai"] if api_key == "": - return ProxyServer(self.sdk.ide.unique_id, model, system_message=self.system_message) - return OpenAI(api_key=api_key, default_model=model, system_message=self.system_message, azure_info=self.sdk.config.azure_openai_info) + return ProxyServer(self.sdk.ide.unique_id, model, system_message=self.system_message, write_log=self.sdk.write_log) + return OpenAI(api_key=api_key, default_model=model, system_message=self.system_message, azure_info=self.sdk.config.azure_openai_info, write_log=self.sdk.write_log) def __load_hf_inference_api_model(self, model: str) -> HuggingFaceInferenceAPI: api_key = self.provider_keys["hf_inference_api"] @@ -156,6 +175,9 @@ class ContinueSDK(AbstractContinueSDK): def history(self) -> History: return self.__autopilot.history + def write_log(self, message: str): + self.history.timeline[self.history.current_index].logs.append(message) + async def _ensure_absolute_path(self, path: str) -> str: if os.path.isabs(path): return path @@ -263,7 +285,7 @@ class ContinueSDK(AbstractContinueSDK): for rif in highlighted_code: msg = ChatMessage(content=f"{preface} ({rif.filepath}):\n```\n{rif.contents}\n```", - role="system", summary=f"{preface}: {rif.filepath}") + role="user", summary=f"{preface}: {rif.filepath}") # Don't insert after latest user message or function call i = -1 diff --git a/continuedev/src/continuedev/libs/llm/openai.py b/continuedev/src/continuedev/libs/llm/openai.py index 33d10985..64bb39a2 100644 --- a/continuedev/src/continuedev/libs/llm/openai.py +++ b/continuedev/src/continuedev/libs/llm/openai.py @@ -1,10 +1,11 @@ from functools import cached_property -from typing import Any, Coroutine, Dict, Generator, List, Union +import json +from typing import Any, Callable, Coroutine, Dict, Generator, List, Union from ...core.main import ChatMessage import openai from ..llm import LLM -from ..util.count_tokens import compile_chat_messages, CHAT_MODELS, DEFAULT_ARGS, count_tokens, prune_raw_prompt_from_top +from ..util.count_tokens import compile_chat_messages, CHAT_MODELS, DEFAULT_ARGS, count_tokens, format_chat_messages, prune_raw_prompt_from_top from ...core.config import AzureInfo @@ -12,11 +13,12 @@ class OpenAI(LLM): api_key: str default_model: str - def __init__(self, api_key: str, default_model: str, system_message: str = None, azure_info: AzureInfo = None): + def __init__(self, api_key: str, default_model: str, system_message: str = None, azure_info: AzureInfo = None, write_log: Callable[[str], None] = None): self.api_key = api_key self.default_model = default_model self.system_message = system_message self.azure_info = azure_info + self.write_log = write_log openai.api_key = api_key @@ -46,18 +48,29 @@ class OpenAI(LLM): args["stream"] = True if args["model"] in CHAT_MODELS: + messages = compile_chat_messages( + args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message) + self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}") + completion = "" async for chunk in await openai.ChatCompletion.acreate( - messages=compile_chat_messages( - args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message), + messages=messages, **args, ): if "content" in chunk.choices[0].delta: yield chunk.choices[0].delta.content + completion += chunk.choices[0].delta.content else: continue + + self.write_log(f"Completion: \n\n{completion}") else: + self.write_log(f"Prompt:\n\n{prompt}") + completion = "" async for chunk in await openai.Completion.acreate(prompt=prompt, **args): yield chunk.choices[0].text + completion += chunk.choices[0].text + + self.write_log(f"Completion:\n\n{completion}") async def stream_chat(self, messages: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]: args = self.default_args.copy() @@ -67,27 +80,39 @@ class OpenAI(LLM): if not args["model"].endswith("0613") and "functions" in args: del args["functions"] + messages = compile_chat_messages( + args["model"], messages, args["max_tokens"], functions=args.get("functions", None), system_message=self.system_message) + self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}") + completion = "" async for chunk in await openai.ChatCompletion.acreate( - messages=compile_chat_messages( - args["model"], messages, args["max_tokens"], functions=args.get("functions", None), system_message=self.system_message), + messages=messages, **args, ): yield chunk.choices[0].delta + if "content" in chunk.choices[0].delta: + completion += chunk.choices[0].delta.content + self.write_log(f"Completion: \n\n{completion}") async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]: args = {**self.default_args, **kwargs} if args["model"] in CHAT_MODELS: + messages = compile_chat_messages( + args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message) + self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}") resp = (await openai.ChatCompletion.acreate( - messages=compile_chat_messages( - args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message), + messages=messages, **args, )).choices[0].message.content + self.write_log(f"Completion: \n\n{resp}") else: + prompt = prune_raw_prompt_from_top( + args["model"], prompt, args["max_tokens"]) + self.write_log(f"Prompt:\n\n{prompt}") resp = (await openai.Completion.acreate( - prompt=prune_raw_prompt_from_top( - args["model"], prompt, args["max_tokens"]), + prompt=prompt, **args, )).choices[0].text + self.write_log(f"Completion:\n\n{resp}") return resp diff --git a/continuedev/src/continuedev/libs/llm/proxy_server.py b/continuedev/src/continuedev/libs/llm/proxy_server.py index 3ec492f3..91b5842a 100644 --- a/continuedev/src/continuedev/libs/llm/proxy_server.py +++ b/continuedev/src/continuedev/libs/llm/proxy_server.py @@ -1,10 +1,11 @@ + from functools import cached_property import json -from typing import Any, Coroutine, Dict, Generator, List, Literal, Union +from typing import Any, Callable, Coroutine, Dict, Generator, List, Literal, Union 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 +from ..util.count_tokens import DEFAULT_ARGS, DEFAULT_MAX_TOKENS, compile_chat_messages, CHAT_MODELS, count_tokens, format_chat_messages import certifi import ssl @@ -19,12 +20,14 @@ class ProxyServer(LLM): unique_id: str name: str default_model: Literal["gpt-3.5-turbo", "gpt-4"] + write_log: Callable[[str], None] - def __init__(self, unique_id: str, default_model: Literal["gpt-3.5-turbo", "gpt-4"], system_message: str = None): + def __init__(self, unique_id: str, default_model: Literal["gpt-3.5-turbo", "gpt-4"], system_message: str = None, write_log: Callable[[str], None] = None): self.unique_id = unique_id self.default_model = default_model self.system_message = system_message self.name = default_model + self.write_log = write_log @property def default_args(self): @@ -36,14 +39,19 @@ class ProxyServer(LLM): async def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Coroutine[Any, Any, str]: args = {**self.default_args, **kwargs} + messages = compile_chat_messages( + args["model"], with_history, args["max_tokens"], prompt, functions=None, system_message=self.system_message) + self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}") 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, args["max_tokens"], prompt, functions=None, system_message=self.system_message), + "messages": messages, "unique_id": self.unique_id, **args }) as resp: try: - return await resp.text() + response_text = await resp.text() + self.write_log(f"Completion: \n\n{response_text}") + return response_text except: raise Exception(await resp.text()) @@ -51,6 +59,7 @@ class ProxyServer(LLM): args = {**self.default_args, **kwargs} messages = compile_chat_messages( self.default_model, messages, args["max_tokens"], None, functions=args.get("functions", None), system_message=self.system_message) + self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}") async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session: async with session.post(f"{SERVER_URL}/stream_chat", json={ @@ -59,6 +68,7 @@ class ProxyServer(LLM): **args }) as resp: # This is streaming application/json instaed of text/event-stream + completion = "" async for line in resp.content.iter_chunks(): if line[1]: try: @@ -67,14 +77,19 @@ class ProxyServer(LLM): chunks = json_chunk.split("\n") for chunk in chunks: if chunk.strip() != "": - yield json.loads(chunk) + loaded_chunk = json.loads(chunk) + yield loaded_chunk + if "content" in loaded_chunk: + completion += loaded_chunk["content"] except: raise Exception(str(line[0])) + self.write_log(f"Completion: \n\n{completion}") async def stream_complete(self, prompt, with_history: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]: args = {**self.default_args, **kwargs} messages = compile_chat_messages( self.default_model, with_history, args["max_tokens"], prompt, functions=args.get("functions", None), system_message=self.system_message) + self.write_log(f"Prompt: \n\n{format_chat_messages(messages)}") async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl_context=ssl_context)) as session: async with session.post(f"{SERVER_URL}/stream_complete", json={ @@ -82,9 +97,13 @@ class ProxyServer(LLM): "unique_id": self.unique_id, **args }) as resp: + completion = "" async for line in resp.content.iter_any(): if line: try: - yield line.decode("utf-8") + decoded_line = line.decode("utf-8") + yield decoded_line + completion += decoded_line except: raise Exception(str(line)) + self.write_log(f"Completion: \n\n{completion}") diff --git a/continuedev/src/continuedev/libs/util/count_tokens.py b/continuedev/src/continuedev/libs/util/count_tokens.py index 1d5d6729..13de7990 100644 --- a/continuedev/src/continuedev/libs/util/count_tokens.py +++ b/continuedev/src/continuedev/libs/util/count_tokens.py @@ -107,3 +107,10 @@ def compile_chat_messages(model: str, msgs: List[ChatMessage], max_tokens: int, }) return history + + +def format_chat_messages(messages: List[ChatMessage]) -> str: + formatted = "" + for msg in messages: + formatted += f"<{msg['role'].capitalize()}>\n{msg['content']}\n\n" + return formatted diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 4201353e..ae57c0b6 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -99,6 +99,8 @@ class GUIProtocolServer(AbstractGUIProtocolServer): self.on_set_editing_at_indices(data["indices"]) elif message_type == "set_pinned_at_indices": self.on_set_pinned_at_indices(data["indices"]) + elif message_type == "show_logs_at_index": + self.on_show_logs_at_index(data["index"]) except Exception as e: print(e) @@ -166,6 +168,13 @@ class GUIProtocolServer(AbstractGUIProtocolServer): indices), self.session.autopilot.continue_sdk.ide.unique_id ) + def on_show_logs_at_index(self, index: int): + name = f"continue_logs.txt" + logs = "\n\n############################################\n\n".join( + ["This is a log of the exact prompt/completion pairs sent/received from the LLM during this step"] + self.session.autopilot.continue_sdk.history.timeline[index].logs) + create_async_task( + self.session.autopilot.ide.showVirtualFile(name, logs)) + @router.websocket("/ws") async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)): diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 43538407..aeff5623 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -224,6 +224,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer): "open": open }) + async def showVirtualFile(self, name: str, contents: str): + await self._send_json("showVirtualFile", { + "name": name, + "contents": contents + }) + async def setSuggestionsLocked(self, filepath: str, locked: bool = True): # Lock suggestions in the file so they don't ruin the offset before others are inserted await self._send_json("setSuggestionsLocked", { @@ -288,6 +294,8 @@ class IdeProtocolServer(AbstractIdeProtocolServer): pass def __get_autopilot(self): + if self.session_id not in self.session_manager.sessions: + return None return self.session_manager.sessions[self.session_id].autopilot def onFileEdits(self, edits: List[FileEditWithFullContents]): @@ -442,7 +450,8 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str = None): if session_id is not None: session_manager.registered_ides[session_id] = ideProtocolServer other_msgs = await ideProtocolServer.initialize(session_id) - capture_event(ideProtocolServer.unique_id, "session_started", { "session_id": ideProtocolServer.session_id }) + capture_event(ideProtocolServer.unique_id, "session_started", { + "session_id": ideProtocolServer.session_id}) for other_msg in other_msgs: handle_msg(other_msg) @@ -463,5 +472,6 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str = None): if websocket.client_state != WebSocketState.DISCONNECTED: await websocket.close() - capture_event(ideProtocolServer.unique_id, "session_ended", { "session_id": ideProtocolServer.session_id }) + capture_event(ideProtocolServer.unique_id, "session_ended", { + "session_id": ideProtocolServer.session_id}) session_manager.registered_ides.pop(ideProtocolServer.session_id) diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py index d0fb0bf8..0ae7e7fa 100644 --- a/continuedev/src/continuedev/server/ide_protocol.py +++ b/continuedev/src/continuedev/server/ide_protocol.py @@ -23,6 +23,10 @@ class AbstractIdeProtocolServer(ABC): async def setFileOpen(self, filepath: str, open: bool = True): """Set whether a file is open""" + @abstractmethod + async def showVirtualFile(self, name: str, contents: str): + """Show a virtual file""" + @abstractmethod async def setSuggestionsLocked(self, filepath: str, locked: bool = True): """Set whether suggestions are locked""" diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index 6d109ca6..90172a4e 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -100,7 +100,7 @@ class SessionManager: if session_id not in self.sessions: raise SessionNotFound(f"Session {session_id} not found") if self.sessions[session_id].ws is None: - print(f"Session {session_id} has no websocket") + # print(f"Session {session_id} has no websocket") return await self.sessions[session_id].ws.send_json({ diff --git a/extension/package-lock.json b/extension/package-lock.json index e67fa950..107a7001 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.178", + "version": "0.0.179", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.178", + "version": "0.0.179", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 121423ed..89c6daf5 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "The open-source coding autopilot", - "version": "0.0.178", + "version": "0.0.179", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 93b90f0d..bc8665fd 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useContext, useEffect, useRef, useState } from "react"; import styled, { keyframes } from "styled-components"; import { appear, @@ -13,12 +13,14 @@ import { ChevronRight, ArrowPath, XMark, + MagnifyingGlass, } from "@styled-icons/heroicons-outline"; import { StopCircle } from "@styled-icons/heroicons-solid"; import { HistoryNode } from "../../../schema/HistoryNode"; import HeaderButtonWithText from "./HeaderButtonWithText"; import MarkdownPreview from "@uiw/react-markdown-preview"; import { getMetaKeyLabel, isMetaEquivalentKeyPressed } from "../util"; +import { GUIClientContext } from "../App"; interface StepContainerProps { historyNode: HistoryNode; @@ -32,6 +34,7 @@ interface StepContainerProps { onToggle: () => void; isFirst: boolean; isLast: boolean; + index: number; } // #region styled components @@ -140,6 +143,7 @@ function StepContainer(props: StepContainerProps) { const naturalLanguageInputRef = useRef(null); const userInputRef = useRef(null); const isUserInput = props.historyNode.step.name === "UserInputStep"; + const client = useContext(GUIClientContext); useEffect(() => { if (userInputRef?.current) { @@ -210,6 +214,17 @@ function StepContainer(props: StepContainerProps) { */} <> + {(props.historyNode.logs as any)?.length > 0 && ( + { + e.stopPropagation(); + client?.showLogsAtIndex(props.index); + }} + > + + + )} { e.stopPropagation(); diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts index a179c2bf..6c0df8fc 100644 --- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -28,6 +28,8 @@ abstract class AbstractContinueGUIClientProtocol { abstract setPinnedAtIndices(indices: number[]): void; abstract toggleAddingHighlightedCode(): void; + + abstract showLogsAtIndex(index: number): void; } export default AbstractContinueGUIClientProtocol; diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts index 2060dd7f..fef5b2e1 100644 --- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts +++ b/extension/react-app/src/hooks/useContinueGUIProtocol.ts @@ -86,6 +86,10 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { toggleAddingHighlightedCode(): void { this.messenger.send("toggle_adding_highlighted_code", {}); } + + showLogsAtIndex(index: number): void { + this.messenger.send("show_logs_at_index", { index }); + } } export default ContinueGUIClientProtocol; diff --git a/extension/react-app/src/pages/gui.tsx b/extension/react-app/src/pages/gui.tsx index c35cf21b..fccc9b4b 100644 --- a/extension/react-app/src/pages/gui.tsx +++ b/extension/react-app/src/pages/gui.tsx @@ -311,6 +311,7 @@ function GUI(props: GUIProps) { ) ) : ( (); + onDidChange = this.onDidChangeEmitter.event; + + provideTextDocumentContent(uri: vscode.Uri): string { + return uri.query; + } + })(); + context.subscriptions.push( + vscode.workspace.registerTextDocumentContentProvider( + continueVirtualDocumentScheme, + documentContentProvider + ) + ); } async handleMessage( @@ -200,6 +221,9 @@ class IdeProtocolClient { this.openFile(data.filepath); // TODO: Close file if False break; + case "showVirtualFile": + this.showVirtualFile(data.name, data.contents); + break; case "setSuggestionsLocked": this.setSuggestionsLocked(data.filepath, data.locked); break; @@ -295,6 +319,20 @@ class IdeProtocolClient { openEditorAndRevealRange(filepath, undefined, vscode.ViewColumn.One); } + showVirtualFile(name: string, contents: string) { + vscode.workspace + .openTextDocument( + vscode.Uri.parse( + `${continueVirtualDocumentScheme}:${name}?${encodeURIComponent( + contents + )}` + ) + ) + .then((doc) => { + vscode.window.showTextDocument(doc, { preview: false }); + }); + } + setSuggestionsLocked(filepath: string, locked: boolean) { editorSuggestionsLocked.set(filepath, locked); // TODO: Rerender? -- cgit v1.2.3-70-g09d2 From 627f260cee108476e5335584e81f5e36f3e248cb Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 18 Jul 2023 16:31:39 -0700 Subject: CONTRIBUTING.md --- CONTRIBUTING.md | 94 +++++++++++++++++++++ continuedev/src/continuedev/core/main.py | 6 +- extension/react-app/src/App.tsx | 8 +- .../src/hooks/AbstractContinueGUIClientProtocol.ts | 35 ++++++++ .../src/hooks/ContinueGUIClientProtocol.ts | 93 +++++++++++++++++---- .../react-app/src/hooks/useContinueGUIProtocol.ts | 95 ---------------------- extension/react-app/src/hooks/useWebsocket.ts | 2 +- extension/src/activation/activate.ts | 14 +--- extension/src/continueIdeClient.ts | 10 +++ 9 files changed, 222 insertions(+), 135 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts delete mode 100644 extension/react-app/src/hooks/useContinueGUIProtocol.ts (limited to 'extension/src/continueIdeClient.ts') diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..7e49dc2d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,94 @@ +# Contributing to Continue + +## Table of Contents + +- [Continue Architecture](#continue-architecture) +- [Core Concepts](#core-concepts) + - [Step](#step) +- [Continue VS Code Client](#continue-vs-code-client) +- [Continue IDE Websockets Protocol](#continue-ide-websockets-protocol) +- [Continue GUI Websockets Protocol](#continue-gui-websockets-protocol) +- [Ways to Contribute](#ways-to-contribute) + - [Report Bugs](#report-bugs) + - [Suggest Enhancements](#suggest-enhancements) + - [Updating / Improving Documentation](#updating--improving-documentation) + +## Continue Architecture + +Continue consists of 3 components, designed so that Continue can easily be extended to work in any IDE: + +1. **Continue Server** - The Continue Server is responsible for keeping state, running the autopilot loop which takes actions, and communicating between the IDE and GUI. + +2. **Continue IDE Client** - The Continue IDE Client is a plugin for the IDE which implements the Continue IDE Protocol. This allows the server to request actions to be taken within the IDE, for example if `sdk.ide.setFileOpen("main.py")` is called on the server, it will communicate over websocketes with the IDE, which will open the file `main.py`. The first IDE Client we have built is for VS Code, but we plan to build clients for other IDEs in the future. The IDE Client must 1. implement the websockets protocol, as is done [here](./extension/src/continueIdeClient.ts) for VS Code and 2. launch the Continue Server, like [here](./extension/src/activation/environmentSetup.ts), and 3. display the Continue GUI in a sidebar, like [here](./extension/src/debugPanel.ts). + +3. **Continue GUI** - The Continue GUI is a React application that gives the user control over Continue. It displays the history of Steps, shows what context is included in the current Step, and lets the users enter natural language or slash commands to initiate new Steps. The GUI communicates with the Continue Server over its own websocket connection + +It is important that the IDE Client and GUI never communicate except when the IDE Client initially sets up the GUI. This ensures that the server is the source-of-truth for state, and that we can easily extend Continue to work in other IDEs. + +![Continue Architecture](https://continue.dev/docs/assets/images/continue-architecture-146a90742e25f6524452c74fe44fa2a0.png) + +## Core Concepts + +All of Continue's logic happens inside of the server, and it is built around a few core concepts. Most of these are Pydantic Models defined in [core/main.py](./continuedev/src/continuedev/core/main.py). + +### `Step` + +Everything in Continue is a "Step". The `Step` class defines 2 methods: + +1. `async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]` - This method defines what happens when the Step is run. It has access to the Continue SDK, which lets you take actions in the IDE, call LLMs, run nested Steps, and more. Optionally, a Step can return an `Observation` object, which a `Policy` can use to make decisions about what to do next. + +2. `async def describe(self, models: Models) -> Coroutine[str, None, None]` - After each Step is run, this method is called to asynchronously generate a summary title for the step. A `Models` object is passed so that you have access to LLMs to summarize for you. + +Steps are designed to be composable, so that you can easily build new Steps by combining existing ones. And because they are Pydantic models, they can instantly be used as tools useable by an LLM, for example with OpenAI's function-calling functionality (see [ChatWithFunctions](./continuedev/src/continuedev/steps/chat.py) for an example of this). + +Some of the most commonly used Steps are: + +- [`SimpleChatStep`](./continuedev/src/continuedev/steps/chat.py) - This is the default Step that is run when the user enters natural language input. It takes the user's input and runs it through the default LLM, then displays the result in the GUI. + +- [`EditHighlightedCodeStep`](./continuedev/src/continuedev/steps/core/core.py) - This is the Step run when a user highlights code, enters natural language, and presses CMD/CTRL+ENTER, or uses the slash command '/edit'. It opens a side-by-side diff editor, where updated code is streamed to fulfil the user's request. + +### `Autopilot` + +### `Observation` + +### `Policy` + +### Continue VS Code Client + +The starting point for the VS Code extension is [activate.ts](./extension/src/activation/activate.ts). The `activateExtension` function here will: + +1. Check whether the current version of the extension is up-to-date and, if not, display a notification + +2. Initialize the Continue IDE Client and establish a connection with the Continue Server + +3. Load the Continue GUI in the sidebar of the IDE and begin a new session + +### Continue IDE Websockets Protocol + +On the IDE side, this is implemented in [continueIdeClient.ts](./extension/src/continueIdeClient.ts). On the server side, this is implemented in [ide.py](./continuedev/src/continuedev/server/ide.py). You can see [ide_protocol.py](./continuedev/src/continuedev/server/ide_protocol.py) for the protocol definition. + +### Continue GUI Websockets Protocol + +On the GUI side, this is implemented in [ContinueGUIClientProtocol.ts](./extension/react-app/src/hooks/ContinueGUIClientProtocol.ts). On the server side, this is implemented in [gui.py](./continuedev/src/continuedev/server/gui.py). You can see [gui_protocol.py](./continuedev/src/continuedev/server/gui_protocol.py) or [AbstractContinueGUIClientProtocol.ts](./extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts) for the protocol definition. + +When state is updated on the server, we currently send the entirety of the object over websockets to the GUI. This will of course have to be improved soon. The state object, `FullState`, is defined in [core/main.py](./continuedev/src/continuedev/core/main.py) and includes: + +- `history`, a record of previously run Steps. Displayed in order in the sidebar. +- `active`, whether the autopilot is currently running a step. Displayed as a loader while step is running. +- `user_input_queue`, the queue of user inputs that have not yet been processed due to waiting for previous Steps to complete. Displayed below the `active` loader until popped from the queue. +- `default_model`, the default model used for completions. Displayed as a toggleable button on the bottom of the GUI. +- `highlighted_ranges`, the ranges of code that have been selected to include as context. Displayed just above the main text input. +- `slash_commands`, the list of available slash commands. Displayed in the main text input dropdown. +- `adding_highlighted_code`, whether highlighting of new code for context is locked. Displayed as a button adjacent to `highlighted_ranges`. + +Updates are sent with `await sdk.update_ui()` when needed explicitly or `await autopilot.update_subscribers()` automatically between each Step. The GUI can listen for state updates with `ContinueGUIClientProtocol.onStateUpdate()`. + +## Ways to Contribute + +### Report Bugs + +### Suggest Enhancements + +### Updating / Improving Documentation + +Continue is continuously improving, but a feature isn't complete until it is reflected in the documentation! diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index 5931d978..50d01f8d 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -259,10 +259,8 @@ class Step(ContinueBaseModel): def dict(self, *args, **kwargs): d = super().dict(*args, **kwargs) - if self.description is not None: - d["description"] = self.description - else: - d["description"] = "" + # Make sure description is always a string + d["description"] = self.description or "" return d @validator("name", pre=True, always=True) diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx index c9bd42e0..aa462171 100644 --- a/extension/react-app/src/App.tsx +++ b/extension/react-app/src/App.tsx @@ -2,7 +2,7 @@ import DebugPanel from "./components/DebugPanel"; import GUI from "./pages/gui"; import { createContext } from "react"; import useContinueGUIProtocol from "./hooks/useWebsocket"; -import ContinueGUIClientProtocol from "./hooks/useContinueGUIProtocol"; +import ContinueGUIClientProtocol from "./hooks/ContinueGUIClientProtocol"; export const GUIClientContext = createContext< ContinueGUIClientProtocol | undefined @@ -13,11 +13,7 @@ function App() { return ( - , title: "GUI" } - ]} - /> + , title: "GUI" }]} /> ); } diff --git a/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts new file mode 100644 index 00000000..6c0df8fc --- /dev/null +++ b/extension/react-app/src/hooks/AbstractContinueGUIClientProtocol.ts @@ -0,0 +1,35 @@ +abstract class AbstractContinueGUIClientProtocol { + abstract sendMainInput(input: string): void; + + abstract reverseToIndex(index: number): void; + + abstract sendRefinementInput(input: string, index: number): void; + + abstract sendStepUserInput(input: string, index: number): void; + + abstract onStateUpdate(state: any): void; + + abstract onAvailableSlashCommands( + callback: (commands: { name: string; description: string }[]) => void + ): void; + + abstract changeDefaultModel(model: string): void; + + abstract sendClear(): void; + + abstract retryAtIndex(index: number): void; + + abstract deleteAtIndex(index: number): void; + + abstract deleteContextAtIndices(indices: number[]): void; + + abstract setEditingAtIndices(indices: number[]): void; + + abstract setPinnedAtIndices(indices: number[]): void; + + abstract toggleAddingHighlightedCode(): void; + + abstract showLogsAtIndex(index: number): void; +} + +export default AbstractContinueGUIClientProtocol; diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts index 6c0df8fc..7d6c2a71 100644 --- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -1,35 +1,92 @@ -abstract class AbstractContinueGUIClientProtocol { - abstract sendMainInput(input: string): void; +import AbstractContinueGUIClientProtocol from "./AbstractContinueGUIClientProtocol"; +import { Messenger, WebsocketMessenger } from "./messenger"; +import { VscodeMessenger } from "./vscodeMessenger"; - abstract reverseToIndex(index: number): void; +class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { + messenger: Messenger; + // Server URL must contain the session ID param + serverUrlWithSessionId: string; - abstract sendRefinementInput(input: string, index: number): void; + constructor( + serverUrlWithSessionId: string, + useVscodeMessagePassing: boolean + ) { + super(); + this.serverUrlWithSessionId = serverUrlWithSessionId; + this.messenger = useVscodeMessagePassing + ? new VscodeMessenger(serverUrlWithSessionId) + : new WebsocketMessenger(serverUrlWithSessionId); + } - abstract sendStepUserInput(input: string, index: number): void; + sendMainInput(input: string) { + this.messenger.send("main_input", { input }); + } - abstract onStateUpdate(state: any): void; + reverseToIndex(index: number) { + this.messenger.send("reverse_to_index", { index }); + } - abstract onAvailableSlashCommands( + sendRefinementInput(input: string, index: number) { + this.messenger.send("refinement_input", { input, index }); + } + + sendStepUserInput(input: string, index: number) { + this.messenger.send("step_user_input", { input, index }); + } + + onStateUpdate(callback: (state: any) => void) { + this.messenger.onMessageType("state_update", (data: any) => { + if (data.state) { + callback(data.state); + } + }); + } + + onAvailableSlashCommands( callback: (commands: { name: string; description: string }[]) => void - ): void; + ) { + this.messenger.onMessageType("available_slash_commands", (data: any) => { + if (data.commands) { + callback(data.commands); + } + }); + } - abstract changeDefaultModel(model: string): void; + changeDefaultModel(model: string) { + this.messenger.send("change_default_model", { model }); + } - abstract sendClear(): void; + sendClear() { + this.messenger.send("clear_history", {}); + } - abstract retryAtIndex(index: number): void; + retryAtIndex(index: number) { + this.messenger.send("retry_at_index", { index }); + } - abstract deleteAtIndex(index: number): void; + deleteAtIndex(index: number) { + this.messenger.send("delete_at_index", { index }); + } - abstract deleteContextAtIndices(indices: number[]): void; + deleteContextAtIndices(indices: number[]) { + this.messenger.send("delete_context_at_indices", { indices }); + } - abstract setEditingAtIndices(indices: number[]): void; + setEditingAtIndices(indices: number[]) { + this.messenger.send("set_editing_at_indices", { indices }); + } - abstract setPinnedAtIndices(indices: number[]): void; + setPinnedAtIndices(indices: number[]) { + this.messenger.send("set_pinned_at_indices", { indices }); + } - abstract toggleAddingHighlightedCode(): void; + toggleAddingHighlightedCode(): void { + this.messenger.send("toggle_adding_highlighted_code", {}); + } - abstract showLogsAtIndex(index: number): void; + showLogsAtIndex(index: number): void { + this.messenger.send("show_logs_at_index", { index }); + } } -export default AbstractContinueGUIClientProtocol; +export default ContinueGUIClientProtocol; diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts deleted file mode 100644 index fef5b2e1..00000000 --- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts +++ /dev/null @@ -1,95 +0,0 @@ -import AbstractContinueGUIClientProtocol from "./ContinueGUIClientProtocol"; -// import { Messenger, WebsocketMessenger } from "../../../src/util/messenger"; -import { Messenger, WebsocketMessenger } from "./messenger"; -import { VscodeMessenger } from "./vscodeMessenger"; - -class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { - messenger: Messenger; - // Server URL must contain the session ID param - serverUrlWithSessionId: string; - - constructor( - serverUrlWithSessionId: string, - useVscodeMessagePassing: boolean - ) { - super(); - this.serverUrlWithSessionId = serverUrlWithSessionId; - if (useVscodeMessagePassing) { - this.messenger = new VscodeMessenger(serverUrlWithSessionId); - } else { - this.messenger = new WebsocketMessenger(serverUrlWithSessionId); - } - } - - sendMainInput(input: string) { - this.messenger.send("main_input", { input }); - } - - reverseToIndex(index: number) { - this.messenger.send("reverse_to_index", { index }); - } - - sendRefinementInput(input: string, index: number) { - this.messenger.send("refinement_input", { input, index }); - } - - sendStepUserInput(input: string, index: number) { - this.messenger.send("step_user_input", { input, index }); - } - - onStateUpdate(callback: (state: any) => void) { - this.messenger.onMessageType("state_update", (data: any) => { - if (data.state) { - callback(data.state); - } - }); - } - - onAvailableSlashCommands( - callback: (commands: { name: string; description: string }[]) => void - ) { - this.messenger.onMessageType("available_slash_commands", (data: any) => { - if (data.commands) { - callback(data.commands); - } - }); - } - - changeDefaultModel(model: string) { - this.messenger.send("change_default_model", { model }); - } - - sendClear() { - this.messenger.send("clear_history", {}); - } - - retryAtIndex(index: number) { - this.messenger.send("retry_at_index", { index }); - } - - deleteAtIndex(index: number) { - this.messenger.send("delete_at_index", { index }); - } - - deleteContextAtIndices(indices: number[]) { - this.messenger.send("delete_context_at_indices", { indices }); - } - - setEditingAtIndices(indices: number[]) { - this.messenger.send("set_editing_at_indices", { indices }); - } - - setPinnedAtIndices(indices: number[]) { - this.messenger.send("set_pinned_at_indices", { indices }); - } - - toggleAddingHighlightedCode(): void { - this.messenger.send("toggle_adding_highlighted_code", {}); - } - - showLogsAtIndex(index: number): void { - this.messenger.send("show_logs_at_index", { index }); - } -} - -export default ContinueGUIClientProtocol; diff --git a/extension/react-app/src/hooks/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts index e762666f..6b36be97 100644 --- a/extension/react-app/src/hooks/useWebsocket.ts +++ b/extension/react-app/src/hooks/useWebsocket.ts @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import { RootStore } from "../redux/store"; import { useSelector } from "react-redux"; -import ContinueGUIClientProtocol from "./useContinueGUIProtocol"; +import ContinueGUIClientProtocol from "./ContinueGUIClientProtocol"; import { postVscMessage } from "../vscode"; function useContinueGUIProtocol(useVscodeMessagePassing: boolean = true) { diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index 8ea08e89..a7f6c55b 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -36,8 +36,8 @@ export async function activateExtension(context: vscode.ExtensionContext) { }) .catch((e) => console.log("Error checking for extension updates: ", e)); - // Wrap the server start logic in a new Promise - const serverStartPromise = new Promise((resolve, reject) => { + // Start the server and display loader if taking > 2 seconds + await new Promise((resolve) => { let serverStarted = false; // Start the server and set serverStarted to true when done @@ -71,15 +71,6 @@ export async function activateExtension(context: vscode.ExtensionContext) { }, 2000); }); - // Await the server start promise - await serverStartPromise; - - // Register commands and providers - sendTelemetryEvent(TelemetryEvent.ExtensionActivated); - registerAllCodeLensProviders(context); - registerAllCommands(context); - registerQuickFixProvider(); - // Initialize IDE Protocol Client const serverUrl = getContinueServerUrl(); ideProtocolClient = new IdeProtocolClient( @@ -87,6 +78,7 @@ export async function activateExtension(context: vscode.ExtensionContext) { context ); + // Register Continue GUI as sidebar webview, and beging a new session { const sessionIdPromise = await ideProtocolClient.getSessionId(); const provider = new ContinueGUIWebviewViewProvider(sessionIdPromise); diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 14a8df72..a1370a01 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -16,6 +16,10 @@ import fs = require("fs"); import { WebsocketMessenger } from "./util/messenger"; import { diffManager } from "./diffs"; import path = require("path"); +import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; +import { registerAllCodeLensProviders } from "./lang-server/codeLens"; +import { registerAllCommands } from "./commands"; +import registerQuickFixProvider from "./lang-server/codeActions"; const continueVirtualDocumentScheme = "continue"; @@ -76,6 +80,12 @@ class IdeProtocolClient { this._serverUrl = serverUrl; this._newWebsocketMessenger(); + // Register commands and providers + sendTelemetryEvent(TelemetryEvent.ExtensionActivated); + registerAllCodeLensProviders(context); + registerAllCommands(context); + registerQuickFixProvider(); + // Setup listeners for any file changes in open editors // vscode.workspace.onDidChangeTextDocument((event) => { // if (this._makingEdit === 0) { -- cgit v1.2.3-70-g09d2 From b97ceee5cad5f3a645f067353aa33c17dfcf0a60 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 21 Jul 2023 16:43:17 -0700 Subject: notify to reload window after changing settings --- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- extension/src/continueIdeClient.ts | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) (limited to 'extension/src/continueIdeClient.ts') diff --git a/extension/package-lock.json b/extension/package-lock.json index d44b84c4..5c8e27d0 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.187", + "version": "0.0.188", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.187", + "version": "0.0.188", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 34bc8bc4..3d44c156 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "The open-source coding autopilot", - "version": "0.0.187", + "version": "0.0.188", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index a1370a01..3a42e773 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -167,6 +167,20 @@ class IdeProtocolClient { documentContentProvider ) ); + + // Listen for changes to settings.json + vscode.workspace.onDidChangeConfiguration((event) => { + if (event.affectsConfiguration("continue")) { + vscode.window.showInformationMessage( + "Please reload VS Code for changes to Continue settings to take effect.", + "Reload" + ).then((selection) => { + if (selection === "Reload") { + vscode.commands.executeCommand("workbench.action.reloadWindow"); + } + }); + } + }); } async handleMessage( -- cgit v1.2.3-70-g09d2 From 64a36f5c91f13d0098a9651d39a90a17b4aad1fd Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 21 Jul 2023 19:55:08 -0700 Subject: remove Segment telemetry from React --- extension/src/activation/activate.ts | 1 - extension/src/activation/environmentSetup.ts | 17 +-------- extension/src/continueIdeClient.ts | 20 +++++------ extension/src/suggestions.ts | 3 -- extension/src/telemetry.ts | 53 ---------------------------- 5 files changed, 11 insertions(+), 83 deletions(-) delete mode 100644 extension/src/telemetry.ts (limited to 'extension/src/continueIdeClient.ts') diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index a7f6c55b..65f026d8 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -1,7 +1,6 @@ import * as vscode from "vscode"; import { registerAllCommands } from "../commands"; import { registerAllCodeLensProviders } from "../lang-server/codeLens"; -import { sendTelemetryEvent, TelemetryEvent } from "../telemetry"; import IdeProtocolClient from "../continueIdeClient"; import { getContinueServerUrl } from "../bridge"; import { ContinueGUIWebviewViewProvider } from "../debugPanel"; diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 94481430..5a9345a6 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -9,7 +9,6 @@ import fetch from "node-fetch"; import * as vscode from "vscode"; import * as os from "os"; import fkill from "fkill"; -import { sendTelemetryEvent, TelemetryEvent } from "../telemetry"; const WINDOWS_REMOTE_SIGNED_SCRIPTS_ERROR = "A Python virtual enviroment cannot be activated because running scripts is disabled for this user. In order to use Continue, please enable signed scripts to run with this command in PowerShell: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`, reload VS Code, and then try again."; @@ -57,9 +56,6 @@ async function retryThenFail( vscode.window.showErrorMessage(msg); } - sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, { - error: e.message, - }); throw e; } } @@ -83,12 +79,6 @@ async function runCommand(cmd: string): Promise<[string, string | undefined]> { stdout = ""; } - if (stderr) { - sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, { - error: stderr, - }); - } - return [stdout, stderr]; } @@ -480,16 +470,11 @@ export async function startContinuePythonServer() { console.log("Successfully started Continue python server"); resolve(null); } else if (data.includes("ERROR") || data.includes("Traceback")) { - sendTelemetryEvent(TelemetryEvent.ExtensionSetupError, { - error: data, - }); + console.log("Error starting Continue python server: ", 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 3a42e773..802afc1d 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -16,7 +16,6 @@ import fs = require("fs"); import { WebsocketMessenger } from "./util/messenger"; import { diffManager } from "./diffs"; import path = require("path"); -import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; import { registerAllCodeLensProviders } from "./lang-server/codeLens"; import { registerAllCommands } from "./commands"; import registerQuickFixProvider from "./lang-server/codeActions"; @@ -81,7 +80,6 @@ class IdeProtocolClient { this._newWebsocketMessenger(); // Register commands and providers - sendTelemetryEvent(TelemetryEvent.ExtensionActivated); registerAllCodeLensProviders(context); registerAllCommands(context); registerQuickFixProvider(); @@ -171,14 +169,16 @@ class IdeProtocolClient { // Listen for changes to settings.json vscode.workspace.onDidChangeConfiguration((event) => { if (event.affectsConfiguration("continue")) { - vscode.window.showInformationMessage( - "Please reload VS Code for changes to Continue settings to take effect.", - "Reload" - ).then((selection) => { - if (selection === "Reload") { - vscode.commands.executeCommand("workbench.action.reloadWindow"); - } - }); + vscode.window + .showInformationMessage( + "Please reload VS Code for changes to Continue settings to take effect.", + "Reload" + ) + .then((selection) => { + if (selection === "Reload") { + vscode.commands.executeCommand("workbench.action.reloadWindow"); + } + }); } }); } diff --git a/extension/src/suggestions.ts b/extension/src/suggestions.ts index c2373223..5c2b8860 100644 --- a/extension/src/suggestions.ts +++ b/extension/src/suggestions.ts @@ -1,5 +1,4 @@ import * as vscode from "vscode"; -import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; import { openEditorAndRevealRange } from "./util/vscode"; import { translate } from "./util/vscode"; import { registerAllCodeLensProviders } from "./lang-server/codeLens"; @@ -244,7 +243,6 @@ function selectSuggestion( } export function acceptSuggestionCommand(key: SuggestionRanges | null = null) { - sendTelemetryEvent(TelemetryEvent.SuggestionAccepted); selectSuggestion("selected", key); } @@ -271,7 +269,6 @@ export function rejectAllSuggestionsCommand() { export async function rejectSuggestionCommand( key: SuggestionRanges | null = null ) { - sendTelemetryEvent(TelemetryEvent.SuggestionRejected); selectSuggestion("old", key); } diff --git a/extension/src/telemetry.ts b/extension/src/telemetry.ts deleted file mode 100644 index db5cb8ca..00000000 --- a/extension/src/telemetry.ts +++ /dev/null @@ -1,53 +0,0 @@ -import * as Segment from "@segment/analytics-node"; -import * as vscode from "vscode"; - -// Setup Segment -const SEGMENT_WRITE_KEY = "57yy2uYXH2bwMuy7djm9PorfFlYqbJL1"; -const analytics = new Segment.Analytics({ writeKey: SEGMENT_WRITE_KEY }); -analytics.identify({ - userId: vscode.env.machineId, - // traits: { - // name: "Michael Bolton", - // email: "mbolton@example.com", - // createdAt: new Date("2014-06-14T02:00:19.467Z"), - // }, -}); - -// Enum of telemetry events -export enum TelemetryEvent { - // Extension has been activated - ExtensionActivated = "ExtensionActivated", - // Suggestion has been accepted - SuggestionAccepted = "SuggestionAccepted", - // Suggestion has been rejected - SuggestionRejected = "SuggestionRejected", - // Queried universal prompt - UniversalPromptQuery = "UniversalPromptQuery", - // `Explain Code` button clicked - ExplainCode = "ExplainCode", - // `Generate Ideas` button clicked - GenerateIdeas = "GenerateIdeas", - // `Suggest Fix` button clicked - SuggestFix = "SuggestFix", - // `Create Test` button clicked - CreateTest = "CreateTest", - // `AutoDebug This Test` button clicked - AutoDebugThisTest = "AutoDebugThisTest", - // Command run to generate docstring - GenerateDocstring = "GenerateDocstring", - // Error setting up the extension - ExtensionSetupError = "ExtensionSetupError", -} - -export function sendTelemetryEvent( - event: TelemetryEvent, - properties?: Record -) { - if (!vscode.env.isTelemetryEnabled) return; - - analytics.track({ - event, - userId: vscode.env.machineId, - properties, - }); -} -- cgit v1.2.3-70-g09d2