From 9ded1ea41e65d83e32ed74ca1fb5bd1f00a5d054 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 00:18:49 -0700 Subject: refactor: :goal_net: log errors from websocket, better logging from uvicorn --- continuedev/src/continuedev/server/gui.py | 4 ---- continuedev/src/continuedev/server/ide.py | 1 + continuedev/src/continuedev/server/main.py | 9 ++++++-- .../src/continuedev/server/session_manager.py | 13 ++++++++--- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- .../src/hooks/ContinueGUIClientProtocol.ts | 7 ++++++ extension/react-app/src/hooks/messenger.ts | 10 +++++++++ extension/react-app/src/hooks/vscodeMessenger.ts | 8 +++++++ extension/src/debugPanel.ts | 26 ++++++++++++++++++++-- 10 files changed, 70 insertions(+), 14 deletions(-) diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index c0957395..9bb621cb 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -32,10 +32,6 @@ class AppStatus: Server.handle_exit = AppStatus.handle_exit -async def session(x_continue_session_id: str = Header("anonymous")) -> Session: - return await session_manager.get_session(x_continue_session_id) - - async def websocket_session(session_id: str) -> Session: return await session_manager.get_session(session_id) diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 87374928..e4c2d714 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -482,6 +482,7 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str = None): "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e))}) raise e finally: + print("Closing ide websocket") if websocket.client_state != WebSocketState.DISCONNECTED: await websocket.close() diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index b92c9fa3..d78eaecf 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -59,12 +59,17 @@ LOG_CONFIG = { 'handlers': ['file'] } } + print(f"Log path: {log_path}") +sys.stdout = open(log_path, "a") +sys.stderr = open(log_path, "a") +print("Testing logs") def run_server(): - config = uvicorn.Config(app, host="0.0.0.0", - port=args.port, log_config=LOG_CONFIG) + config = uvicorn.Config(app, host="0.0.0.0", port=args.port, + # log_config=LOG_CONFIG + ) server = uvicorn.Server(config) server.run() diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index 3136f1bf..4e47098a 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -4,6 +4,8 @@ from typing import Any, Dict, List, Union from uuid import uuid4 import json +from fastapi.websockets import WebSocketState + from ..libs.util.paths import getSessionFilePath, getSessionsFolderPath from ..models.filesystem_edit import FileEditWithFullContents from ..libs.constants.main import CONTINUE_SESSIONS_FOLDER @@ -59,6 +61,8 @@ class SessionManager: return self.sessions[session_id] async def new_session(self, ide: AbstractIdeProtocolServer, session_id: Union[str, None] = None) -> Session: + print("New session: ", session_id) + full_state = None if session_id is not None and os.path.exists(getSessionFilePath(session_id)): with open(getSessionFilePath(session_id), "r") as f: @@ -82,13 +86,16 @@ class SessionManager: return session def remove_session(self, session_id: str): - del self.sessions[session_id] + print("Removing session: ", session_id) + if session_id in self.sessions: + ws_to_close = self.sessions[session_id].ide.websocket + if ws_to_close is not None and ws_to_close.client_state != WebSocketState.DISCONNECTED: + self.sessions[session_id].autopilot.ide.websocket.close() + del self.sessions[session_id] async def persist_session(self, session_id: str): """Save the session's FullState as a json file""" full_state = await self.sessions[session_id].autopilot.get_full_state() - if not os.path.exists(getSessionsFolderPath()): - os.mkdir(getSessionsFolderPath()) with open(getSessionFilePath(session_id), "w") as f: json.dump(full_state.dict(), f) diff --git a/extension/package-lock.json b/extension/package-lock.json index fcd97d50..b210256b 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.213", + "version": "0.0.214", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.213", + "version": "0.0.214", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index e1004a8c..72cfa6ab 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.213", + "version": "0.0.214", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts index b8019664..5a5d4c30 100644 --- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -17,6 +17,13 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { this.messenger = useVscodeMessagePassing ? new VscodeMessenger(serverUrlWithSessionId) : new WebsocketMessenger(serverUrlWithSessionId); + + this.messenger.onClose(() => { + console.log("GUI -> IDE websocket closed"); + }); + this.messenger.onError((error) => { + console.log("GUI -> IDE websocket error", error); + }); } sendMainInput(input: string) { diff --git a/extension/react-app/src/hooks/messenger.ts b/extension/react-app/src/hooks/messenger.ts index 00ce1fbb..ecf646c7 100644 --- a/extension/react-app/src/hooks/messenger.ts +++ b/extension/react-app/src/hooks/messenger.ts @@ -13,6 +13,8 @@ export abstract class Messenger { abstract onClose(callback: () => void): void; abstract sendAndReceive(messageType: string, data: any): Promise; + + abstract onError(callback: (error: any) => void): void; } export class WebsocketMessenger extends Messenger { @@ -20,6 +22,7 @@ export class WebsocketMessenger extends Messenger { private onMessageListeners: { [messageType: string]: ((data: object) => void)[]; } = {}; + private onErrorListeners: ((error: any) => void)[] = []; private onOpenListeners: (() => void)[] = []; private onCloseListeners: (() => void)[] = []; private serverUrl: string; @@ -37,6 +40,9 @@ export class WebsocketMessenger extends Messenger { this.onMessageType(messageType, listener); } } + for (const listener of this.onErrorListeners) { + this.onError(listener); + } return newWebsocket; } @@ -95,4 +101,8 @@ export class WebsocketMessenger extends Messenger { onClose(callback: () => void): void { this.websocket.addEventListener("close", callback); } + + onError(callback: (error: any) => void): void { + this.websocket.addEventListener("error", callback); + } } diff --git a/extension/react-app/src/hooks/vscodeMessenger.ts b/extension/react-app/src/hooks/vscodeMessenger.ts index ba19586b..13f5092b 100644 --- a/extension/react-app/src/hooks/vscodeMessenger.ts +++ b/extension/react-app/src/hooks/vscodeMessenger.ts @@ -38,6 +38,14 @@ export class VscodeMessenger extends Messenger { }); } + onError(callback: (error: any) => void): void { + window.addEventListener("message", (event: any) => { + if (event.data.type === "websocketForwardingError") { + callback(event.data.error); + } + }); + } + sendAndReceive(messageType: string, data: any): Promise { return new Promise((resolve) => { const handler = (event: any) => { diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index 3c4f8481..4785ba20 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -17,17 +17,20 @@ class WebsocketConnection { private _onMessage: (message: string) => void; private _onOpen: () => void; private _onClose: () => void; + private _onError: (e: any) => void; constructor( url: string, onMessage: (message: string) => void, onOpen: () => void, - onClose: () => void + onClose: () => void, + onError: (e: any) => void ) { this._ws = new WebSocket(url); this._onMessage = onMessage; this._onOpen = onOpen; this._onClose = onClose; + this._onError = onError; this._ws.addEventListener("message", (event) => { this._onMessage(event.data); @@ -38,6 +41,9 @@ class WebsocketConnection { this._ws.addEventListener("open", () => { this._onOpen(); }); + this._ws.addEventListener("error", (e: any) => { + this._onError(e); + }); } public send(message: string) { @@ -147,12 +153,20 @@ export function setupDebugPanel( url, }); }; + const onError = (e: any) => { + panel.webview.postMessage({ + type: "websocketForwardingError", + url, + error: e, + }); + }; try { const connection = new WebsocketConnection( url, onMessage, onOpen, - onClose + onClose, + onError ); websocketConnections[url] = connection; resolve(null); @@ -198,6 +212,14 @@ export function setupDebugPanel( if (typeof websocketConnections[url] === "undefined") { await connectWebsocket(url); } + console.log( + "Websocket connection requested by GUI already open at", + url + ); + panel.webview.postMessage({ + type: "websocketForwardingOpen", + url, + }); break; } case "websocketForwardingMessage": { -- cgit v1.2.3-70-g09d2 From 500f62fcc55ed7ccb04fd9ccef3c66c8b5ff1721 Mon Sep 17 00:00:00 2001 From: Nate Sesti <33237525+sestinj@users.noreply.github.com> Date: Fri, 28 Jul 2023 09:07:26 -0700 Subject: Update customization.md --- docs/docs/customization.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docs/customization.md b/docs/docs/customization.md index c768c97d..f383de48 100644 --- a/docs/docs/customization.md +++ b/docs/docs/customization.md @@ -25,6 +25,8 @@ If you have access, simply set `default_model` to the model you would like to us See our [5 minute quickstart](https://github.com/continuedev/ggml-server-example) to run any model locally with ggml. While these models don't yet perform as well, they are free, entirely private, and run offline. +Once the model is running on localhost:8000, set `default_model` in `~/.continue/config.py` to "ggml". + ### Self-hosting an open-source model If you want to self-host on Colab, RunPod, Replicate, HuggingFace, Haven, or another hosting provider you will need to wire up a new LLM class. It only needs to implement 3 methods: `stream_complete`, `complete`, and `stream_chat`, and you can see examples in `continuedev/src/continuedev/libs/llm`. -- cgit v1.2.3-70-g09d2 From 5bc80e2e6d3141922c966c404a6d32a496097960 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 10:23:12 -0700 Subject: refactor: :loud_sound: improve logs on startup of server --- continuedev/src/continuedev/server/gui.py | 2 +- continuedev/src/continuedev/server/session_manager.py | 10 ++++++---- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- extension/src/activation/environmentSetup.ts | 8 ++++++++ extension/src/debugPanel.ts | 17 +++++++++-------- 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 9bb621cb..58e875b9 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -198,4 +198,4 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we await websocket.close() await session_manager.persist_session(session.session_id) - session_manager.remove_session(session.session_id) + await session_manager.remove_session(session.session_id) diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index 4e47098a..511dc070 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -85,12 +85,14 @@ class SessionManager: create_async_task(autopilot.run_policy()) return session - def remove_session(self, session_id: str): + async def remove_session(self, session_id: str): print("Removing session: ", session_id) if session_id in self.sessions: - ws_to_close = self.sessions[session_id].ide.websocket - if ws_to_close is not None and ws_to_close.client_state != WebSocketState.DISCONNECTED: - self.sessions[session_id].autopilot.ide.websocket.close() + if session_id in self.registered_ides: + ws_to_close = self.registered_ides[session_id].websocket + if ws_to_close is not None and ws_to_close.client_state != WebSocketState.DISCONNECTED: + await self.sessions[session_id].autopilot.ide.websocket.close() + del self.sessions[session_id] async def persist_session(self, session_id: str): diff --git a/extension/package-lock.json b/extension/package-lock.json index b210256b..78409984 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.214", + "version": "0.0.215", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.214", + "version": "0.0.215", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 72cfa6ab..5df08aee 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.214", + "version": "0.0.215", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 44fb3b60..146a6e37 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -487,6 +487,14 @@ export async function startContinuePythonServer() { console.log(`error: ${error.message}`); }); + child.on("close", (code: any) => { + console.log(`child process exited with code ${code}`); + }); + + child.stdout.on("data", (data: any) => { + console.log(`stdout: ${data}`); + }); + // Write the current version of vscode to a file called server_version.txt fs.writeFileSync(serverVersionPath(), getExtensionVersion()); } catch (e) { diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index 4785ba20..d3972189 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -211,15 +211,16 @@ export function setupDebugPanel( let url = data.url; if (typeof websocketConnections[url] === "undefined") { await connectWebsocket(url); + } else { + console.log( + "Websocket connection requested by GUI already open at", + url + ); + panel.webview.postMessage({ + type: "websocketForwardingOpen", + url, + }); } - console.log( - "Websocket connection requested by GUI already open at", - url - ); - panel.webview.postMessage({ - type: "websocketForwardingOpen", - url, - }); break; } case "websocketForwardingMessage": { -- cgit v1.2.3-70-g09d2 From 8b95ef7de258de8498b328d9e6107a95f57f8d2c Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 12:13:07 -0700 Subject: fix: :ambulance: logging to file causing problems with starting server --- continuedev/src/continuedev/server/main.py | 78 +++++++++++++++++++--------- extension/package-lock.json | 4 +- extension/package.json | 2 +- extension/src/activation/environmentSetup.ts | 3 +- extension/src/continueIdeClient.ts | 2 + 5 files changed, 59 insertions(+), 30 deletions(-) diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index d78eaecf..1c7a135c 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -33,37 +33,63 @@ app.add_middleware( @app.get("/health") def health(): - print("Testing") + print("Health check") return {"status": "ok"} -# add cli arg for server port -parser = argparse.ArgumentParser() -parser.add_argument("-p", "--port", help="server port", - type=int, default=65432) -args = parser.parse_args() - -log_path = getLogFilePath() -LOG_CONFIG = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'file': { - 'level': 'DEBUG', - 'class': 'logging.FileHandler', - 'filename': log_path, +class Logger(object): + def __init__(self, log_file: str): + self.terminal = sys.stdout + self.log = open(log_file, "a") + + def write(self, message): + self.terminal.write(message) + self.log.write(message) + + def flush(self): + # this flush method is needed for python 3 compatibility. + # this handles the flush command by doing nothing. + # you might want to specify some extra behavior here. + pass + + def isatty(self): + return False + + +def setup_logging(): + log_path = getLogFilePath() + LOG_CONFIG = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'file': { + 'level': 'DEBUG', + 'class': 'logging.FileHandler', + 'filename': log_path, + }, }, - }, - 'root': { - 'level': 'DEBUG', - 'handlers': ['file'] + 'root': { + 'level': 'DEBUG', + 'handlers': ['file'] + } } -} -print(f"Log path: {log_path}") -sys.stdout = open(log_path, "a") -sys.stderr = open(log_path, "a") -print("Testing logs") + logger = Logger(log_path) + print(f"Log path: {log_path}") + # sys.stdout = logger + # sys.stderr = logger + print("Testing logs") + + +try: + # add cli arg for server port + parser = argparse.ArgumentParser() + parser.add_argument("-p", "--port", help="server port", + type=int, default=65432) + args = parser.parse_args() +except Exception as e: + print("Error parsing command line arguments: ", e) + raise e def run_server(): @@ -102,6 +128,7 @@ atexit.register(cleanup) if __name__ == "__main__": try: + # Uncomment to get CPU usage reports # import threading # def cpu_usage_loop(): @@ -112,6 +139,7 @@ if __name__ == "__main__": # cpu_thread = threading.Thread(target=cpu_usage_loop) # cpu_thread.start() + setup_logging() run_server() except Exception as e: print("Error starting Continue server: ", e) diff --git a/extension/package-lock.json b/extension/package-lock.json index 78409984..791bbef9 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.215", + "version": "0.0.216", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.215", + "version": "0.0.216", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 5df08aee..d71744d0 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.215", + "version": "0.0.216", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 146a6e37..04bcd0b6 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -463,9 +463,8 @@ export async function startContinuePythonServer() { const command = `cd "${serverPath()}" && ${activateCmd} && cd .. && ${pythonCmd} -m server.run_continue_server`; - console.log("Starting Continue python server..."); - return new Promise(async (resolve, reject) => { + console.log("Starting Continue python server..."); try { const child = spawn(command, { shell: true, diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 157b59cb..0b528054 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -62,9 +62,11 @@ class IdeProtocolClient { this._lastReloadTime = Math.min(2 * this._lastReloadTime, 5000); }; messenger.onOpen(() => { + console.log("IDE protocol websocket opened"); this._reconnectionTimeouts.forEach((to) => clearTimeout(to)); }); messenger.onClose(() => { + console.log("IDE protocol websocket closed"); reconnect(); }); messenger.onError(() => { -- cgit v1.2.3-70-g09d2 From 62db7c56e3837d1b61b672a24b72fb2ac07e0c40 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 13:00:58 -0700 Subject: refactor: :loud_sound: potential telemetry fixes --- continuedev/src/continuedev/core/sdk.py | 2 +- continuedev/src/continuedev/libs/util/telemetry.py | 15 ++++++++------- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 992bc1cf..264a6852 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -269,7 +269,7 @@ class ContinueSDK(AbstractContinueSDK): # When the config is loaded, setup posthog logger posthog_logger.setup( - self.ide.unique_id, config.config.allow_anonymous_telemetry or True) + self.ide.unique_id, config.config.allow_anonymous_telemetry) return config.config except Exception as e: diff --git a/continuedev/src/continuedev/libs/util/telemetry.py b/continuedev/src/continuedev/libs/util/telemetry.py index a967828e..e1efb668 100644 --- a/continuedev/src/continuedev/libs/util/telemetry.py +++ b/continuedev/src/continuedev/libs/util/telemetry.py @@ -10,20 +10,21 @@ POSTHOG_API_KEY = 'phc_JS6XFROuNbhJtVCEdTSYk6gl5ArRrTNMpCcguAXlSPs' class PostHogLogger: + unique_id: str = "NO_UNIQUE_ID" + allow_anonymous_telemetry: bool = True + def __init__(self, api_key: str): self.api_key = api_key - self.unique_id = None - self.allow_anonymous_telemetry = True - - def setup(self, unique_id: str, allow_anonymous_telemetry: bool): - self.unique_id = unique_id - self.allow_anonymous_telemetry = allow_anonymous_telemetry # The personal API key is necessary only if you want to use local evaluation of feature flags. self.posthog = Posthog(self.api_key, host='https://app.posthog.com') + def setup(self, unique_id: str, allow_anonymous_telemetry: bool): + self.unique_id = unique_id or "NO_UNIQUE_ID" + self.allow_anonymous_telemetry = allow_anonymous_telemetry or True + def capture_event(self, event_name: str, event_properties: Any): - if not self.allow_anonymous_telemetry or self.unique_id is None: + if not self.allow_anonymous_telemetry: return if in_codespaces: diff --git a/extension/package-lock.json b/extension/package-lock.json index 791bbef9..76be13dd 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.216", + "version": "0.0.217", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.216", + "version": "0.0.217", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index d71744d0..911206c8 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.216", + "version": "0.0.217", "publisher": "Continue", "engines": { "vscode": "^1.67.0" -- cgit v1.2.3-70-g09d2 From 7894c8ed1517394aa00f6e496a97d9e27d204f5f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 14:57:54 -0700 Subject: fix: :goal_net: catch errors when loading to meilisearch index --- continuedev/src/continuedev/core/context.py | 7 +++++-- continuedev/src/continuedev/libs/util/telemetry.py | 14 ++++++++++++++ extension/package-lock.json | 4 ++-- extension/package.json | 2 +- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/continuedev/src/continuedev/core/context.py b/continuedev/src/continuedev/core/context.py index f81fa57a..12908e02 100644 --- a/continuedev/src/continuedev/core/context.py +++ b/continuedev/src/continuedev/core/context.py @@ -166,8 +166,11 @@ class ContextManager: for item in context_items ] if len(documents) > 0: - async with Client('http://localhost:7700') as search_client: - await search_client.index(SEARCH_INDEX_NAME).add_documents(documents) + try: + async with Client('http://localhost:7700') as search_client: + await search_client.index(SEARCH_INDEX_NAME).add_documents(documents) + except Exception as e: + print("Error loading meilisearch index: ", e) # def compile_chat_messages(self, max_tokens: int) -> List[Dict]: # """ diff --git a/continuedev/src/continuedev/libs/util/telemetry.py b/continuedev/src/continuedev/libs/util/telemetry.py index e1efb668..6d1d4fed 100644 --- a/continuedev/src/continuedev/libs/util/telemetry.py +++ b/continuedev/src/continuedev/libs/util/telemetry.py @@ -24,6 +24,18 @@ class PostHogLogger: self.allow_anonymous_telemetry = allow_anonymous_telemetry or True def capture_event(self, event_name: str, event_properties: Any): + print("------- Logging event -------") + telemetry_path = os.path.expanduser("~/.continue/telemetry.log") + + # Make sure the telemetry file exists + if not os.path.exists(telemetry_path): + os.makedirs(os.path.dirname(telemetry_path), exist_ok=True) + open(telemetry_path, "w").close() + + with open(telemetry_path, "a") as f: + str_to_write = f"{event_name}: {event_properties}\n{self.unique_id}\n{self.allow_anonymous_telemetry}\n\n" + f.write(str_to_write) + if not self.allow_anonymous_telemetry: return @@ -34,5 +46,7 @@ class PostHogLogger: self.posthog.capture(self.unique_id, event_name, clean_pii_from_any(event_properties)) + print("------- Event logged -------") + posthog_logger = PostHogLogger(api_key=POSTHOG_API_KEY) diff --git a/extension/package-lock.json b/extension/package-lock.json index 76be13dd..8380ab3e 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.217", + "version": "0.0.218", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.217", + "version": "0.0.218", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 911206c8..893d7822 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.217", + "version": "0.0.218", "publisher": "Continue", "engines": { "vscode": "^1.67.0" -- cgit v1.2.3-70-g09d2 From 99ece78c8640495fbdabd95d30c26d620045b0ec Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 16:43:04 -0700 Subject: refactor: :loud_sound: replace all print with logger.debug --- continuedev/src/continuedev/core/autopilot.py | 5 +-- continuedev/src/continuedev/core/context.py | 36 +++++++++++-------- continuedev/src/continuedev/core/sdk.py | 5 +-- continuedev/src/continuedev/libs/chroma/query.py | 19 +++++----- .../src/continuedev/libs/util/create_async_task.py | 6 ++-- continuedev/src/continuedev/libs/util/logging.py | 30 ++++++++++++++++ .../continuedev/libs/util/step_name_to_steps.py | 3 +- continuedev/src/continuedev/libs/util/telemetry.py | 6 ++-- .../src/continuedev/models/generate_json_schema.py | 2 +- continuedev/src/continuedev/plugins/steps/main.py | 3 +- continuedev/src/continuedev/server/gui.py | 17 ++++----- continuedev/src/continuedev/server/ide.py | 30 ++++++++-------- continuedev/src/continuedev/server/main.py | 41 ++++------------------ .../src/continuedev/server/meilisearch_server.py | 5 +-- .../src/continuedev/server/session_manager.py | 9 ++--- extension/package-lock.json | 4 +-- extension/package.json | 2 +- 17 files changed, 123 insertions(+), 100 deletions(-) create mode 100644 continuedev/src/continuedev/libs/util/logging.py diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 42a58423..931cfb75 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -21,6 +21,7 @@ from ..libs.util.traceback_parsers import get_python_traceback, get_javascript_t from openai import error as openai_errors from ..libs.util.create_async_task import create_async_task from ..libs.util.telemetry import posthog_logger +from ..libs.util.logging import logger def get_error_title(e: Exception) -> str: @@ -153,7 +154,7 @@ class Autopilot(ContinueBaseModel): await self.update_subscribers() except Exception as e: - print(e) + logger.debug(e) def handle_manual_edits(self, edits: List[FileEditWithFullContents]): for edit in edits: @@ -258,7 +259,7 @@ class Autopilot(ContinueBaseModel): e) # Attach an InternalErrorObservation to the step and unhide it. - print( + logger.error( f"Error while running step: \n{error_string}\n{error_title}") posthog_logger.capture_event('step error', { 'error_message': error_string, 'error_title': error_title, 'step_name': step.name, 'params': step.dict()}) diff --git a/continuedev/src/continuedev/core/context.py b/continuedev/src/continuedev/core/context.py index 12908e02..78a747b2 100644 --- a/continuedev/src/continuedev/core/context.py +++ b/continuedev/src/continuedev/core/context.py @@ -7,7 +7,7 @@ from pydantic import BaseModel from .main import ChatMessage, ContextItem, ContextItemDescription, ContextItemId from ..server.meilisearch_server import check_meilisearch_running - +from ..libs.util.logging import logger SEARCH_INDEX_NAME = "continue_context_items" @@ -57,16 +57,22 @@ class ContextProvider(BaseModel): Default implementation uses the search index to get the item. """ async with Client('http://localhost:7700') as search_client: - result = await search_client.index( - SEARCH_INDEX_NAME).get_document(id.to_string()) - return ContextItem( - description=ContextItemDescription( - name=result["name"], - description=result["description"], - id=id - ), - content=result["content"] - ) + try: + result = await search_client.index( + SEARCH_INDEX_NAME).get_document(id.to_string()) + return ContextItem( + description=ContextItemDescription( + name=result["name"], + description=result["description"], + id=id + ), + content=result["content"] + ) + except Exception as e: + logger.warning( + f"Error while retrieving document from meilisearch: {e}") + + return None async def delete_context_with_ids(self, ids: List[ContextItemId]): """ @@ -100,8 +106,8 @@ class ContextProvider(BaseModel): if item.description.id.item_id == id.item_id: return - new_item = await self.get_item(id, query) - self.selected_items.append(new_item) + if new_item := await self.get_item(id, query): + self.selected_items.append(new_item) class ContextManager: @@ -146,7 +152,7 @@ class ContextManager: meilisearch_running = False if not meilisearch_running: - print( + logger.warning( "MeiliSearch not running, avoiding any dependent context providers") context_providers = list( filter(lambda cp: cp.title == "code", context_providers)) @@ -170,7 +176,7 @@ class ContextManager: async with Client('http://localhost:7700') as search_client: await search_client.index(SEARCH_INDEX_NAME).add_documents(documents) except Exception as e: - print("Error loading meilisearch index: ", e) + logger.debug(f"Error loading meilisearch index: {e}") # def compile_chat_messages(self, max_tokens: int) -> List[Dict]: # """ diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 264a6852..d75aac00 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -19,6 +19,7 @@ from ..plugins.steps.core.core import * from ..libs.llm.proxy_server import ProxyServer from ..libs.util.telemetry import posthog_logger from ..libs.util.paths import getConfigFilePath +from ..libs.util.logging import logger class Autopilot: @@ -161,7 +162,7 @@ class ContinueSDK(AbstractContinueSDK): config = sdk._load_config_dot_py() sdk.config = config except Exception as e: - print(e) + logger.debug(e) sdk.config = ContinueConfig() msg_step = MessageStep( name="Invalid Continue Config File", message=e.__repr__()) @@ -273,7 +274,7 @@ class ContinueSDK(AbstractContinueSDK): return config.config except Exception as e: - print("Error loading config.py: ", e) + logger.debug(f"Error loading config.py: {e}") return ContinueConfig() if self._last_valid_config is None else self._last_valid_config def get_code_context(self, only_editing: bool = False) -> List[RangeInFileWithContents]: diff --git a/continuedev/src/continuedev/libs/chroma/query.py b/continuedev/src/continuedev/libs/chroma/query.py index c27329f0..f09b813a 100644 --- a/continuedev/src/continuedev/libs/chroma/query.py +++ b/continuedev/src/continuedev/libs/chroma/query.py @@ -5,6 +5,7 @@ from llama_index import GPTVectorStoreIndex, StorageContext, load_index_from_sto from llama_index.langchain_helpers.text_splitter import TokenTextSplitter import os from .update import filter_ignored_files, load_gpt_index_documents +from ..util.logging import logger from functools import cached_property @@ -56,7 +57,8 @@ class ChromaIndexManager: try: text_chunks = text_splitter.split_text(doc.text) except: - print("ERROR (probably found special token): ", doc.text) + logger.warning( + f"ERROR (probably found special token): {doc.text}") continue filename = doc.extra_info["filename"] chunks[filename] = len(text_chunks) @@ -79,7 +81,7 @@ class ChromaIndexManager: index.storage_context.persist(persist_dir=self.index_dir) - print("Codebase index created") + logger.debug("Codebase index created") def get_modified_deleted_files(self) -> Tuple[List[str], List[str]]: """Get a list of all files that have been modified since the last commit.""" @@ -121,7 +123,7 @@ class ChromaIndexManager: del metadata["chunks"][file] - print(f"Deleted {file}") + logger.debug(f"Deleted {file}") for file in modified_files: @@ -132,7 +134,7 @@ class ChromaIndexManager: for i in range(num_chunks): index.delete(f"{file}::{i}") - print(f"Deleted old version of {file}") + logger.debug(f"Deleted old version of {file}") with open(file, "r") as f: text = f.read() @@ -145,19 +147,20 @@ class ChromaIndexManager: metadata["chunks"][file] = len(text_chunks) - print(f"Inserted new version of {file}") + logger.debug(f"Inserted new version of {file}") metadata["commit"] = self.current_commit with open(f"{self.index_dir}/metadata.json", "w") as f: json.dump(metadata, f, indent=4) - print("Codebase index updated") + logger.debug("Codebase index updated") def query_codebase_index(self, query: str) -> str: """Query the codebase index.""" if not self.check_index_exists(): - print("No index found for the codebase at ", self.index_dir) + logger.debug( + f"No index found for the codebase at {self.index_dir}") return "" storage_context = StorageContext.from_defaults( @@ -180,4 +183,4 @@ class ChromaIndexManager: documents = [Document(info)] index = GPTVectorStoreIndex(documents) index.save_to_disk(f'{self.index_dir}/additional_index.json') - print("Additional index replaced") + logger.debug("Additional index replaced") diff --git a/continuedev/src/continuedev/libs/util/create_async_task.py b/continuedev/src/continuedev/libs/util/create_async_task.py index 2473c638..00e87445 100644 --- a/continuedev/src/continuedev/libs/util/create_async_task.py +++ b/continuedev/src/continuedev/libs/util/create_async_task.py @@ -1,6 +1,7 @@ from typing import Coroutine, Union import traceback from .telemetry import posthog_logger +from .logging import logger import asyncio import nest_asyncio nest_asyncio.apply() @@ -14,8 +15,9 @@ def create_async_task(coro: Coroutine, unique_id: Union[str, None] = None): try: future.result() except Exception as e: - print("Exception caught from async task: ", - '\n'.join(traceback.format_exception(e))) + formatted_tb = '\n'.join(traceback.format_exception(e)) + logger.critical( + f"Exception caught from async task: {formatted_tb}") posthog_logger.capture_event("async_task_error", { "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e)) }) diff --git a/continuedev/src/continuedev/libs/util/logging.py b/continuedev/src/continuedev/libs/util/logging.py new file mode 100644 index 00000000..668d313f --- /dev/null +++ b/continuedev/src/continuedev/libs/util/logging.py @@ -0,0 +1,30 @@ +import logging + +from .paths import getLogFilePath + +# Create a logger +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +# Create a file handler +file_handler = logging.FileHandler(getLogFilePath()) +file_handler.setLevel(logging.DEBUG) + +# Create a console handler +console_handler = logging.StreamHandler() +console_handler.setLevel(logging.DEBUG) + +# Create a formatter +formatter = logging.Formatter( + '[%(asctime)s] [%(levelname)s] %(message)s') + +# Add the formatter to the handlers +file_handler.setFormatter(formatter) +console_handler.setFormatter(formatter) + +# Add the handlers to the logger +logger.addHandler(file_handler) +logger.addHandler(console_handler) + +# Log a test message +logger.debug('Testing logs') diff --git a/continuedev/src/continuedev/libs/util/step_name_to_steps.py b/continuedev/src/continuedev/libs/util/step_name_to_steps.py index baa25da6..ed1e79b7 100644 --- a/continuedev/src/continuedev/libs/util/step_name_to_steps.py +++ b/continuedev/src/continuedev/libs/util/step_name_to_steps.py @@ -14,6 +14,7 @@ from ...plugins.steps.on_traceback import DefaultOnTracebackStep from ...plugins.steps.clear_history import ClearHistoryStep from ...plugins.steps.open_config import OpenConfigStep from ...plugins.steps.help import HelpStep +from ...libs.util.logging import logger # This mapping is used to convert from string in ContinueConfig json to corresponding Step class. # Used for example in slash_commands and steps_on_startup @@ -38,6 +39,6 @@ def get_step_from_name(step_name: str, params: Dict) -> Step: try: return step_name_to_step_class[step_name](**params) except: - print( + logger.error( f"Incorrect parameters for step {step_name}. Parameters provided were: {params}") raise diff --git a/continuedev/src/continuedev/libs/util/telemetry.py b/continuedev/src/continuedev/libs/util/telemetry.py index 6d1d4fed..7e538c59 100644 --- a/continuedev/src/continuedev/libs/util/telemetry.py +++ b/continuedev/src/continuedev/libs/util/telemetry.py @@ -3,6 +3,7 @@ from posthog import Posthog import os from dotenv import load_dotenv from .commonregex import clean_pii_from_any +from .logging import logger load_dotenv() in_codespaces = os.getenv("CODESPACES") == "true" @@ -24,7 +25,8 @@ class PostHogLogger: self.allow_anonymous_telemetry = allow_anonymous_telemetry or True def capture_event(self, event_name: str, event_properties: Any): - print("------- Logging event -------") + # logger.debug( + # f"Logging to PostHog: {event_name} ({self.unique_id}, {self.allow_anonymous_telemetry}): {event_properties}") telemetry_path = os.path.expanduser("~/.continue/telemetry.log") # Make sure the telemetry file exists @@ -46,7 +48,5 @@ class PostHogLogger: self.posthog.capture(self.unique_id, event_name, clean_pii_from_any(event_properties)) - print("------- Event logged -------") - posthog_logger = PostHogLogger(api_key=POSTHOG_API_KEY) diff --git a/continuedev/src/continuedev/models/generate_json_schema.py b/continuedev/src/continuedev/models/generate_json_schema.py index 06614984..51869fdd 100644 --- a/continuedev/src/continuedev/models/generate_json_schema.py +++ b/continuedev/src/continuedev/models/generate_json_schema.py @@ -38,7 +38,7 @@ def main(): try: json = schema_json_of(model, indent=2, title=title) except Exception as e: - print(f"Failed to generate json schema for {title}: ", e) + print(f"Failed to generate json schema for {title}: {e}") continue with open(f"{SCHEMA_DIR}/{title}.json", "w") as f: diff --git a/continuedev/src/continuedev/plugins/steps/main.py b/continuedev/src/continuedev/plugins/steps/main.py index a8752df2..f28d9660 100644 --- a/continuedev/src/continuedev/plugins/steps/main.py +++ b/continuedev/src/continuedev/plugins/steps/main.py @@ -13,6 +13,7 @@ from ...core.sdk import ContinueSDK, Models from ...core.observation import Observation from .core.core import DefaultModelEditCodeStep from ...libs.util.calculate_diff import calculate_diff2 +from ...libs.util.logging import logger class Policy(BaseModel): @@ -105,7 +106,7 @@ class FasterEditHighlightedCodeStep(Step): # Temporarily doing this to generate description. self._prompt = prompt self._completion = completion - print(completion) + logger.debug(completion) # ALTERNATIVE DECODING STEP HERE raw_file_edits = [] diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 58e875b9..2adb680e 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -12,6 +12,7 @@ from .gui_protocol import AbstractGUIProtocolServer from ..libs.util.queue import AsyncSubscriptionQueue from ..libs.util.telemetry import posthog_logger from ..libs.util.create_async_task import create_async_task +from ..libs.util.logging import logger router = APIRouter(prefix="/gui", tags=["gui"]) @@ -25,7 +26,7 @@ class AppStatus: @staticmethod def handle_exit(*args, **kwargs): AppStatus.should_exit = True - print("Shutting down") + logger.debug("Shutting down") original_handler(*args, **kwargs) @@ -96,7 +97,7 @@ class GUIProtocolServer(AbstractGUIProtocolServer): elif message_type == "select_context_item": self.select_context_item(data["id"], data["query"]) except Exception as e: - print(e) + logger.debug(e) def on_main_input(self, input: str): # Do something with user input @@ -162,10 +163,10 @@ class GUIProtocolServer(AbstractGUIProtocolServer): @router.websocket("/ws") async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)): try: - print("Received websocket connection at url: ", websocket.url) + logger.debug(f"Received websocket connection at url: {websocket.url}") await websocket.accept() - print("Session started") + logger.debug("Session started") session_manager.register_websocket(session.session_id, websocket) protocol = GUIProtocolServer(session) protocol.websocket = websocket @@ -175,7 +176,7 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we while AppStatus.should_exit is False: message = await websocket.receive_text() - print("Received GUI message", message) + logger.debug(f"Received GUI message {message}") if type(message) is str: message = json.loads(message) @@ -186,14 +187,14 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we protocol.handle_json(message_type, data) except WebSocketDisconnect as e: - print("GUI websocket disconnected") + logger.debug("GUI websocket disconnected") except Exception as e: - print("ERROR in gui websocket: ", e) + logger.debug(f"ERROR in gui websocket: {e}") posthog_logger.capture_event("gui_error", { "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e))}) raise e finally: - print("Closing gui websocket") + logger.debug("Closing gui websocket") if websocket.client_state != WebSocketState.DISCONNECTED: await websocket.close() diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index e4c2d714..8a269cb7 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -19,6 +19,7 @@ from .gui import session_manager from .ide_protocol import AbstractIdeProtocolServer from ..libs.util.create_async_task import create_async_task from .session_manager import SessionManager +from ..libs.util.logging import logger import nest_asyncio nest_asyncio.apply() @@ -37,7 +38,7 @@ class AppStatus: @staticmethod def handle_exit(*args, **kwargs): AppStatus.should_exit = True - print("Shutting down") + logger.debug("Shutting down") original_handler(*args, **kwargs) @@ -140,7 +141,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): continue message_type = message["messageType"] data = message["data"] - print("Received message while initializing", message_type) + logger.debug(f"Received message while initializing {message_type}") if message_type == "workspaceDirectory": self.workspace_directory = data["workspaceDirectory"] elif message_type == "uniqueId": @@ -154,9 +155,10 @@ class IdeProtocolServer(AbstractIdeProtocolServer): async def _send_json(self, message_type: str, data: Any): if self.websocket.application_state == WebSocketState.DISCONNECTED: - print("Tried to send message, but websocket is disconnected", message_type) + logger.debug( + f"Tried to send message, but websocket is disconnected: {message_type}") return - print("Sending IDE message: ", message_type) + logger.debug(f"Sending IDE message: {message_type}") await self.websocket.send_json({ "messageType": message_type, "data": data @@ -167,7 +169,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): return await asyncio.wait_for(self.sub_queue.get(message_type), timeout=timeout) except asyncio.TimeoutError: raise Exception( - "IDE Protocol _receive_json timed out after 20 seconds", message_type) + f"IDE Protocol _receive_json timed out after 20 seconds: {message_type}") async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T: await self._send_json(message_type, data) @@ -354,7 +356,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): }, GetUserSecretResponse, "getUserSecret") return resp.value except Exception as e: - print("Error getting user secret", e) + logger.debug(f"Error getting user secret: {e}") return "" async def saveFile(self, filepath: str): @@ -437,15 +439,15 @@ class IdeProtocolServer(AbstractIdeProtocolServer): async def websocket_endpoint(websocket: WebSocket, session_id: str = None): try: await websocket.accept() - print("Accepted websocket connection from, ", websocket.client) + logger.debug(f"Accepted websocket connection from {websocket.client}") await websocket.send_json({"messageType": "connected", "data": {}}) # Start meilisearch try: await start_meilisearch() except Exception as e: - print("Failed to start MeiliSearch") - print(e) + logger.debug("Failed to start MeiliSearch") + logger.debug(e) def handle_msg(msg): message = json.loads(msg) @@ -455,7 +457,7 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str = None): message_type = message["messageType"] data = message["data"] - print("Received IDE message: ", message_type) + logger.debug(f"Received IDE message: {message_type}") create_async_task( ideProtocolServer.handle_json(message_type, data)) @@ -473,16 +475,16 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str = None): message = await websocket.receive_text() handle_msg(message) - print("Closing ide websocket") + logger.debug("Closing ide websocket") except WebSocketDisconnect as e: - print("IDE wbsocket disconnected") + logger.debug("IDE wbsocket disconnected") except Exception as e: - print("Error in ide websocket: ", e) + logger.debug(f"Error in ide websocket: {e}") posthog_logger.capture_event("gui_error", { "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e))}) raise e finally: - print("Closing ide websocket") + logger.debug("Closing ide websocket") if websocket.client_state != WebSocketState.DISCONNECTED: await websocket.close() diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index 1c7a135c..13f6b840 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -15,6 +15,7 @@ from .ide import router as ide_router from .gui import router as gui_router from .session_manager import session_manager from ..libs.util.paths import getLogFilePath +from ..libs.util.logging import logger app = FastAPI() @@ -33,7 +34,7 @@ app.add_middleware( @app.get("/health") def health(): - print("Health check") + logger.debug("Health check") return {"status": "ok"} @@ -56,31 +57,6 @@ class Logger(object): return False -def setup_logging(): - log_path = getLogFilePath() - LOG_CONFIG = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'file': { - 'level': 'DEBUG', - 'class': 'logging.FileHandler', - 'filename': log_path, - }, - }, - 'root': { - 'level': 'DEBUG', - 'handlers': ['file'] - } - } - - logger = Logger(log_path) - print(f"Log path: {log_path}") - # sys.stdout = logger - # sys.stderr = logger - print("Testing logs") - - try: # add cli arg for server port parser = argparse.ArgumentParser() @@ -88,21 +64,19 @@ try: type=int, default=65432) args = parser.parse_args() except Exception as e: - print("Error parsing command line arguments: ", e) + logger.debug(f"Error parsing command line arguments: {e}") raise e def run_server(): - config = uvicorn.Config(app, host="0.0.0.0", port=args.port, - # log_config=LOG_CONFIG - ) + config = uvicorn.Config(app, host="0.0.0.0", port=args.port) server = uvicorn.Server(config) server.run() async def cleanup_coroutine(): - print("Cleaning up sessions") + logger.debug("Cleaning up sessions") for session_id in session_manager.sessions: await session_manager.persist_session(session_id) @@ -121,7 +95,7 @@ def cpu_usage_report(): time.sleep(1) # Call cpu_percent again to get the CPU usage over the interval cpu_usage = process.cpu_percent(interval=None) - print(f"CPU usage: {cpu_usage}%") + logger.debug(f"CPU usage: {cpu_usage}%") atexit.register(cleanup) @@ -139,9 +113,8 @@ if __name__ == "__main__": # cpu_thread = threading.Thread(target=cpu_usage_loop) # cpu_thread.start() - setup_logging() run_server() except Exception as e: - print("Error starting Continue server: ", e) + logger.debug(f"Error starting Continue server: {e}") cleanup() raise e diff --git a/continuedev/src/continuedev/server/meilisearch_server.py b/continuedev/src/continuedev/server/meilisearch_server.py index 00f692f5..7f460afc 100644 --- a/continuedev/src/continuedev/server/meilisearch_server.py +++ b/continuedev/src/continuedev/server/meilisearch_server.py @@ -5,6 +5,7 @@ import subprocess from meilisearch_python_async import Client from ..libs.util.paths import getServerFolderPath +from ..libs.util.logging import logger def ensure_meilisearch_installed() -> bool: @@ -39,7 +40,7 @@ def ensure_meilisearch_installed() -> bool: shutil.rmtree(p, ignore_errors=True) # Download MeiliSearch - print("Downloading MeiliSearch...") + logger.debug("Downloading MeiliSearch...") subprocess.run( f"curl -L https://install.meilisearch.com | sh", shell=True, check=True, cwd=serverPath) @@ -82,6 +83,6 @@ async def start_meilisearch(): # Check if MeiliSearch is running if not await check_meilisearch_running() or not was_already_installed: - print("Starting MeiliSearch...") + logger.debug("Starting MeiliSearch...") subprocess.Popen(["./meilisearch", "--no-analytics"], cwd=serverPath, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, close_fds=True, start_new_session=True) diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index 511dc070..d30411cd 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -15,6 +15,7 @@ from ..core.autopilot import Autopilot from .ide_protocol import AbstractIdeProtocolServer from ..libs.util.create_async_task import create_async_task from ..libs.util.errors import SessionNotFound +from ..libs.util.logging import logger class Session: @@ -61,7 +62,7 @@ class SessionManager: return self.sessions[session_id] async def new_session(self, ide: AbstractIdeProtocolServer, session_id: Union[str, None] = None) -> Session: - print("New session: ", session_id) + logger.debug(f"New session: {session_id}") full_state = None if session_id is not None and os.path.exists(getSessionFilePath(session_id)): @@ -86,7 +87,7 @@ class SessionManager: return session async def remove_session(self, session_id: str): - print("Removing session: ", session_id) + logger.debug(f"Removing session: {session_id}") if session_id in self.sessions: if session_id in self.registered_ides: ws_to_close = self.registered_ides[session_id].websocket @@ -103,13 +104,13 @@ class SessionManager: def register_websocket(self, session_id: str, ws: WebSocket): self.sessions[session_id].ws = ws - print("Registered websocket for session", session_id) + logger.debug(f"Registered websocket for session {session_id}") async def send_ws_data(self, session_id: str, message_type: str, data: Any): 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") + # logger.debug(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 8380ab3e..34a8671b 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.218", + "version": "0.0.219", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.218", + "version": "0.0.219", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 893d7822..ceaff7d9 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.218", + "version": "0.0.219", "publisher": "Continue", "engines": { "vscode": "^1.67.0" -- cgit v1.2.3-70-g09d2 From cb0c815ad799050ecc0abdf3d15981e9832b9829 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 17:06:38 -0700 Subject: feat: :sparkles: allow custom OpenAI base_url --- continuedev/src/continuedev/core/config.py | 11 ++++++----- continuedev/src/continuedev/core/sdk.py | 2 +- continuedev/src/continuedev/libs/llm/openai.py | 20 +++++++++++--------- docs/docs/customization.md | 12 ++++++++---- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- 6 files changed, 29 insertions(+), 22 deletions(-) diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py index cb9c8977..e367e06c 100644 --- a/continuedev/src/continuedev/core/config.py +++ b/continuedev/src/continuedev/core/config.py @@ -25,10 +25,11 @@ class OnTracebackSteps(BaseModel): params: Optional[Dict] = {} -class AzureInfo(BaseModel): - endpoint: str - engine: str - api_version: str +class OpenAIServerInfo(BaseModel): + api_base: Optional[str] = None + engine: Optional[str] = None + api_version: Optional[str] = None + api_type: Literal["azure", "openai"] = "openai" class ContinueConfig(BaseModel): @@ -49,7 +50,7 @@ class ContinueConfig(BaseModel): slash_commands: Optional[List[SlashCommand]] = [] on_traceback: Optional[List[OnTracebackSteps]] = [] system_message: Optional[str] = None - azure_openai_info: Optional[AzureInfo] = None + openai_server_info: Optional[OpenAIServerInfo] = None context_providers: List[ContextProvider] = [] diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index d75aac00..9ee9ea06 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -81,7 +81,7 @@ class Models: api_key = self.provider_keys["openai"] if api_key == "": 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) + return OpenAI(api_key=api_key, default_model=model, system_message=self.system_message, openai_server_info=self.sdk.config.openai_server_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"] diff --git a/continuedev/src/continuedev/libs/llm/openai.py b/continuedev/src/continuedev/libs/llm/openai.py index a0773c1d..654c7326 100644 --- a/continuedev/src/continuedev/libs/llm/openai.py +++ b/continuedev/src/continuedev/libs/llm/openai.py @@ -6,27 +6,29 @@ 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, format_chat_messages, prune_raw_prompt_from_top -from ...core.config import AzureInfo +from ...core.config import OpenAIServerInfo 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, write_log: Callable[[str], None] = None): + def __init__(self, api_key: str, default_model: str, system_message: str = None, openai_server_info: OpenAIServerInfo = 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.openai_server_info = openai_server_info self.write_log = write_log openai.api_key = api_key # Using an Azure OpenAI deployment - if azure_info is not None: - openai.api_type = "azure" - openai.api_base = azure_info.endpoint - openai.api_version = azure_info.api_version + if openai_server_info is not None: + openai.api_type = openai_server_info.api_type + if openai_server_info.api_base is not None: + openai.api_base = openai_server_info.api_base + if openai_server_info.api_version is not None: + openai.api_version = openai_server_info.api_version @cached_property def name(self): @@ -35,8 +37,8 @@ class OpenAI(LLM): @property def default_args(self): args = {**DEFAULT_ARGS, "model": self.default_model} - if self.azure_info is not None: - args["engine"] = self.azure_info.engine + if self.openai_server_info is not None: + args["engine"] = self.openai_server_info.engine return args def count_tokens(self, text: str): diff --git a/docs/docs/customization.md b/docs/docs/customization.md index f383de48..fa4d110e 100644 --- a/docs/docs/customization.md +++ b/docs/docs/customization.md @@ -11,6 +11,7 @@ Change the `default_model` field to any of "gpt-3.5-turbo", "gpt-3.5-turbo-16k", New users can try out Continue with GPT-4 using a proxy server that securely makes calls to OpenAI using our API key. Continue should just work the first time you install the extension in VS Code. Once you are using Continue regularly though, you will need to add an OpenAI API key that has access to GPT-4 by following these steps: + 1. Copy your API key from https://platform.openai.com/account/api-keys 2. Use the cmd+, (Mac) / ctrl+, (Windows) to open your VS Code settings 3. Type "Continue" in the search bar @@ -35,21 +36,24 @@ If by chance the provider has the exact same API interface as OpenAI, the `GGML` ### Azure OpenAI Service -If you'd like to use OpenAI models but are concerned about privacy, you can use the Azure OpenAI service, which is GDPR and HIPAA compliant. After applying for access [here](https://azure.microsoft.com/en-us/products/ai-services/openai-service), you will typically hear back within only a few days. Once you have access, set `default_model` to "gpt-4", and then set the `azure_openai_info` property in the `ContinueConfig` like so: +If you'd like to use OpenAI models but are concerned about privacy, you can use the Azure OpenAI service, which is GDPR and HIPAA compliant. After applying for access [here](https://azure.microsoft.com/en-us/products/ai-services/openai-service), you will typically hear back within only a few days. Once you have access, set `default_model` to "gpt-4", and then set the `openai_server_info` property in the `ContinueConfig` like so: ```python config = ContinueConfig( ... - azure_openai_info=AzureInfo( - endpoint="https://my-azure-openai-instance.openai.azure.com/", + openai_server_info=OpenAIServerInfo( + api_base="https://my-azure-openai-instance.openai.azure.com/", engine="my-azure-openai-deployment", - api_version="2023-03-15-preview" + api_version="2023-03-15-preview", + api_type="azure" ) ) ``` The easiest way to find this information is from the chat playground in the Azure OpenAI portal. Under the "Chat Session" section, click "View Code" to see each of these parameters. Finally, find one of your Azure OpenAI keys and enter it in the VS Code settings under `continue.OPENAI_API_KEY`. +Note that you can also use `OpenAIServerInfo` for uses other than Azure, such as self-hosting a model. + ## Customize System Message You can write your own system message, a set of instructions that will always be top-of-mind for the LLM, by setting the `system_message` property to any string. For example, you might request "Please make all responses as concise as possible and never repeat something you have already explained." diff --git a/extension/package-lock.json b/extension/package-lock.json index 34a8671b..088e114a 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.219", + "version": "0.0.220", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.219", + "version": "0.0.220", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index ceaff7d9..903cd6ec 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.219", + "version": "0.0.220", "publisher": "Continue", "engines": { "vscode": "^1.67.0" -- cgit v1.2.3-70-g09d2 From 3e811ea66bd79ff378488a83235409de91b2263d Mon Sep 17 00:00:00 2001 From: Ty Dunn Date: Fri, 28 Jul 2023 17:20:58 -0700 Subject: does this work? --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a958777f..14f295b5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Table of Contents -- [❤️ Ways to Contribute](#❤️-ways-to-contribute) +- [❤️ Ways to Contribute](#%EF%B8%8F-ways-to-contribute) - [🐛 Report Bugs](#🐛-report-bugs) - [✨ Suggest Enhancements](#✨-suggest-enhancements) - [📖 Updating / Improving Documentation](#📖-updating--improving-documentation) -- cgit v1.2.3-70-g09d2 From 6a808de4d5da07fea25552d3750bd694077a4eb0 Mon Sep 17 00:00:00 2001 From: Ty Dunn Date: Fri, 28 Jul 2023 17:24:22 -0700 Subject: adjusting anchor tags --- CONTRIBUTING.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14f295b5..e6dea5c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,18 +3,18 @@ ## Table of Contents - [❤️ Ways to Contribute](#%EF%B8%8F-ways-to-contribute) - - [🐛 Report Bugs](#🐛-report-bugs) - - [✨ Suggest Enhancements](#✨-suggest-enhancements) - - [📖 Updating / Improving Documentation](#📖-updating--improving-documentation) - - [🧑‍💻 Contributing Code](#🧑‍💻-contributing-code) + - [🐛 Report Bugs](#-report-bugs) + - [✨ Suggest Enhancements](#-suggest-enhancements) + - [📖 Updating / Improving Documentation](#-updating--improving-documentation) + - [🧑‍💻 Contributing Code](#-contributing-code) - [Setup Development Environment](#setting-up-the-development-environment) - [Writing Steps](#writing-steps) - [Writing Context Providers](#writing-context-providers) -- [📐 Continue Architecture](#📐-continue-architecture) +- [📐 Continue Architecture](#-continue-architecture) - [Continue VS Code Client](#continue-vs-code-client) - [Continue IDE Websockets Protocol](#continue-ide-websockets-protocol) - [Continue GUI Websockets Protocol](#continue-gui-websockets-protocol) -- [❇️ Core Concepts](#❇️-core-concepts) +- [❇️ Core Concepts](#%EF%B8%8F-core-concepts) - [Step](#step) - [Autopilot](#autopilot) - [Observation](#observation) -- cgit v1.2.3-70-g09d2 From 374bdd037792825bf984026da12d4100ffebcac2 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 17:38:13 -0700 Subject: fix: :bug: fix incorrect imports in default config file --- continuedev/src/continuedev/core/sdk.py | 36 ++++++++++++---------- .../libs/constants/default_config.py.txt | 10 +++--- continuedev/src/continuedev/libs/util/telemetry.py | 1 + extension/package-lock.json | 4 +-- extension/package.json | 2 +- 5 files changed, 28 insertions(+), 25 deletions(-) diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 9ee9ea06..5bb88b92 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -1,4 +1,5 @@ from functools import cached_property +import traceback from typing import Coroutine, Dict, Union import os @@ -162,11 +163,15 @@ class ContinueSDK(AbstractContinueSDK): config = sdk._load_config_dot_py() sdk.config = config except Exception as e: - logger.debug(e) - sdk.config = ContinueConfig() + logger.error(f"Failed to load config.py: {e}") + + sdk.config = ContinueConfig( + ) if sdk._last_valid_config is None else sdk._last_valid_config + + formatted_err = '\n'.join(traceback.format_exception(e)) msg_step = MessageStep( - name="Invalid Continue Config File", message=e.__repr__()) - msg_step.description = e.__repr__() + name="Invalid Continue Config File", message=formatted_err) + msg_step.description = f"Falling back to default config settings.\n```\n{formatted_err}\n```" sdk.history.add_node(HistoryNode( step=msg_step, observation=None, @@ -174,6 +179,10 @@ class ContinueSDK(AbstractContinueSDK): active=False )) + # When the config is loaded, setup posthog logger + posthog_logger.setup( + sdk.ide.unique_id, sdk.config.allow_anonymous_telemetry) + sdk.models = await Models.create(sdk) return sdk @@ -261,21 +270,14 @@ class ContinueSDK(AbstractContinueSDK): def _load_config_dot_py(self) -> ContinueConfig: # Use importlib to load the config file config.py at the given path path = getConfigFilePath() - try: - import importlib.util - spec = importlib.util.spec_from_file_location("config", path) - config = importlib.util.module_from_spec(spec) - spec.loader.exec_module(config) - self._last_valid_config = config.config - # When the config is loaded, setup posthog logger - posthog_logger.setup( - self.ide.unique_id, config.config.allow_anonymous_telemetry) + import importlib.util + spec = importlib.util.spec_from_file_location("config", path) + config = importlib.util.module_from_spec(spec) + spec.loader.exec_module(config) + self._last_valid_config = config.config - return config.config - except Exception as e: - logger.debug(f"Error loading config.py: {e}") - return ContinueConfig() if self._last_valid_config is None else self._last_valid_config + return config.config def get_code_context(self, only_editing: bool = False) -> List[RangeInFileWithContents]: highlighted_ranges = self.__autopilot.context_manager.context_providers[ diff --git a/continuedev/src/continuedev/libs/constants/default_config.py.txt b/continuedev/src/continuedev/libs/constants/default_config.py.txt index f80a9ff0..2210667d 100644 --- a/continuedev/src/continuedev/libs/constants/default_config.py.txt +++ b/continuedev/src/continuedev/libs/constants/default_config.py.txt @@ -7,11 +7,11 @@ be sure to select the Python interpreter in ~/.continue/server/env. import subprocess -from continuedev.src.continuedev.core.main import Step -from continuedev.src.continuedev.core.sdk import ContinueSDK -from continuedev.src.continuedev.core.config import CustomCommand, SlashCommand, ContinueConfig -from continuedev.src.continuedev.plugins.context_providers.github import GitHubIssuesContextProvider -from continuedev.src.continuedev.plugins.context_providers.google import GoogleContextProvider +from continuedev.core.main import Step +from continuedev.core.sdk import ContinueSDK +from continuedev.core.config import CustomCommand, SlashCommand, ContinueConfig +from continuedev.plugins.context_providers.github import GitHubIssuesContextProvider +from continuedev.plugins.context_providers.google import GoogleContextProvider class CommitMessageStep(Step): diff --git a/continuedev/src/continuedev/libs/util/telemetry.py b/continuedev/src/continuedev/libs/util/telemetry.py index 7e538c59..85aeda5e 100644 --- a/continuedev/src/continuedev/libs/util/telemetry.py +++ b/continuedev/src/continuedev/libs/util/telemetry.py @@ -21,6 +21,7 @@ class PostHogLogger: self.posthog = Posthog(self.api_key, host='https://app.posthog.com') def setup(self, unique_id: str, allow_anonymous_telemetry: bool): + logger.debug(f"Setting unique_id as {unique_id}") self.unique_id = unique_id or "NO_UNIQUE_ID" self.allow_anonymous_telemetry = allow_anonymous_telemetry or True diff --git a/extension/package-lock.json b/extension/package-lock.json index 088e114a..5bef9f73 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.220", + "version": "0.0.221", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.220", + "version": "0.0.221", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 903cd6ec..124402b5 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.220", + "version": "0.0.221", "publisher": "Continue", "engines": { "vscode": "^1.67.0" -- cgit v1.2.3-70-g09d2 From f7a3659381f839b890f2c53086f7fedecf23d9ab Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 17:55:45 -0700 Subject: fix: :zap: register vscode commands prior to server loading --- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- extension/src/activation/activate.ts | 8 ++++++++ extension/src/continueIdeClient.ts | 5 ----- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/extension/package-lock.json b/extension/package-lock.json index 5bef9f73..5d0b0c56 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.221", + "version": "0.0.222", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.221", + "version": "0.0.222", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 124402b5..942c844c 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.221", + "version": "0.0.222", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index 8bdc7e21..cbb840c0 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -7,6 +7,9 @@ import { startContinuePythonServer, } from "./environmentSetup"; import fetch from "node-fetch"; +import { registerAllCodeLensProviders } from "../lang-server/codeLens"; +import { registerAllCommands } from "../commands"; +import registerQuickFixProvider from "../lang-server/codeActions"; const PACKAGE_JSON_RAW_GITHUB_URL = "https://raw.githubusercontent.com/continuedev/continue/HEAD/extension/package.json"; @@ -46,6 +49,11 @@ export async function activateExtension(context: vscode.ExtensionContext) { }) .catch((e) => console.log("Error checking for extension updates: ", e)); + // Register commands and providers + registerAllCodeLensProviders(context); + registerAllCommands(context); + registerQuickFixProvider(); + // Start the server and display loader if taking > 2 seconds const sessionIdPromise = (async () => { await new Promise((resolve) => { diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 0b528054..498cf9de 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -93,11 +93,6 @@ class IdeProtocolClient { this._serverUrl = serverUrl; this._newWebsocketMessenger(); - // Register commands and providers - 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 9a9b7aae313c8ca3e69acc6b49327fef47a2644d Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 18:12:47 -0700 Subject: show "View Logs" popup during server loading --- extension/src/activation/activate.ts | 11 +++++++++++ extension/src/debugPanel.ts | 11 +---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index cbb840c0..a47d5e97 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -86,6 +86,17 @@ export async function activateExtension(context: vscode.ExtensionContext) { return Promise.resolve(); } ); + + vscode.window + .showInformationMessage( + "Click here to view the server logs, or use the 'continue.viewLogs' VS Code command.", + "View Logs" + ) + .then((selection) => { + if (selection === "View Logs") { + vscode.commands.executeCommand("continue.viewLogs"); + } + }); } }, 2000); }); diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index d3972189..643563a2 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -272,16 +272,7 @@ export function setupDebugPanel( }); } ); - vscode.window - .showInformationMessage( - "Click here to view the server logs, or use the 'continue.viewLogs' VS Code command.", - "View Logs" - ) - .then((selection) => { - if (selection === "View Logs") { - vscode.commands.executeCommand("continue.viewLogs"); - } - }); + break; } } -- cgit v1.2.3-70-g09d2 From 1e81182e7a0402f01ac2475b66fa1ccb1aa51353 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 19:32:45 -0700 Subject: refactor: :label: make HighlightedCodeContextProvider a subclass of ContextProvider --- .../plugins/context_providers/highlighted_code.py | 13 +++++++++++-- extension/src/activation/environmentSetup.ts | 3 ++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/continuedev/src/continuedev/plugins/context_providers/highlighted_code.py b/continuedev/src/continuedev/plugins/context_providers/highlighted_code.py index 1d040101..664e705e 100644 --- a/continuedev/src/continuedev/plugins/context_providers/highlighted_code.py +++ b/continuedev/src/continuedev/plugins/context_providers/highlighted_code.py @@ -3,7 +3,7 @@ from typing import Any, Dict, List from ...core.main import ChatMessage from ...models.filesystem import RangeInFile, RangeInFileWithContents -from ...core.context import ContextItem, ContextItemDescription, ContextItemId +from ...core.context import ContextItem, ContextItemDescription, ContextItemId, ContextProvider from pydantic import BaseModel @@ -12,7 +12,7 @@ class HighlightedRangeContextItem(BaseModel): item: ContextItem -class HighlightedCodeContextProvider(BaseModel): +class HighlightedCodeContextProvider(ContextProvider): """ The ContextProvider class is a plugin that lets you provide new information to the LLM by typing '@'. When you type '@', the context provider will be asked to populate a list of options. @@ -99,6 +99,15 @@ class HighlightedCodeContextProvider(BaseModel): async def provide_context_items(self) -> List[ContextItem]: return [] + async def get_item(self, id: ContextItemId, query: str) -> ContextItem: + raise NotImplementedError() + + async def clear_context(self): + self.highlighted_ranges = [] + self.adding_highlighted_code = False + self.should_get_fallback_context_item = True + self.last_added_fallback = False + async def delete_context_with_ids(self, ids: List[ContextItemId]) -> List[ContextItem]: indices_to_delete = [ int(id.item_id) for id in ids diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 04bcd0b6..50a2783a 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -470,7 +470,6 @@ export async function startContinuePythonServer() { shell: true, }); child.stderr.on("data", (data: any) => { - console.log(`stdout: ${data}`); if ( data.includes("Uvicorn running on") || // Successfully started the server data.includes("only one usage of each socket address") || // [windows] The server is already running (probably a simultaneously opened VS Code window) @@ -480,6 +479,8 @@ export async function startContinuePythonServer() { resolve(null); } else if (data.includes("ERROR") || data.includes("Traceback")) { console.log("Error starting Continue python server: ", data); + } else { + console.log(`stdout: ${data}`); } }); child.on("error", (error: any) => { -- cgit v1.2.3-70-g09d2 From 440f279734bc30bdbc3cecd7ee8fa81630044329 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 19:41:58 -0700 Subject: refactor: :loud_sound: log server version to posthog --- continuedev/src/continuedev/libs/constants/main.py | 2 +- continuedev/src/continuedev/libs/util/telemetry.py | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/continuedev/src/continuedev/libs/constants/main.py b/continuedev/src/continuedev/libs/constants/main.py index 96eb6e69..f5964df6 100644 --- a/continuedev/src/continuedev/libs/constants/main.py +++ b/continuedev/src/continuedev/libs/constants/main.py @@ -3,4 +3,4 @@ CONTINUE_GLOBAL_FOLDER = ".continue" CONTINUE_SESSIONS_FOLDER = "sessions" CONTINUE_SERVER_FOLDER = "server" - +CONTINUE_SERVER_VERSION_FILE = "server_version.txt" diff --git a/continuedev/src/continuedev/libs/util/telemetry.py b/continuedev/src/continuedev/libs/util/telemetry.py index 85aeda5e..60c910bb 100644 --- a/continuedev/src/continuedev/libs/util/telemetry.py +++ b/continuedev/src/continuedev/libs/util/telemetry.py @@ -4,6 +4,8 @@ import os from dotenv import load_dotenv from .commonregex import clean_pii_from_any from .logging import logger +from .paths import getServerFolderPath +from ..constants.main import CONTINUE_SERVER_VERSION_FILE load_dotenv() in_codespaces = os.getenv("CODESPACES") == "true" @@ -42,12 +44,21 @@ class PostHogLogger: if not self.allow_anonymous_telemetry: return + # Clean PII from event properties + event_properties = clean_pii_from_any(event_properties) + + # Add additional properties that are on every event if in_codespaces: event_properties['codespaces'] = True + server_version_file = os.path.join( + getServerFolderPath(), CONTINUE_SERVER_VERSION_FILE) + if os.path.exists(server_version_file): + with open(server_version_file, "r") as f: + event_properties['server_version'] = f.read() + # Send event to PostHog - self.posthog.capture(self.unique_id, event_name, - clean_pii_from_any(event_properties)) + self.posthog.capture(self.unique_id, event_name, event_properties) posthog_logger = PostHogLogger(api_key=POSTHOG_API_KEY) -- cgit v1.2.3-70-g09d2 From 7dbb8f8d789eb08a7c83cee02a6aac248afe9284 Mon Sep 17 00:00:00 2001 From: Nate Sesti <33237525+sestinj@users.noreply.github.com> Date: Fri, 28 Jul 2023 19:59:27 -0700 Subject: Update how-continue-works.md --- docs/docs/how-continue-works.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/docs/how-continue-works.md b/docs/docs/how-continue-works.md index 588b1308..06aada52 100644 --- a/docs/docs/how-continue-works.md +++ b/docs/docs/how-continue-works.md @@ -8,4 +8,24 @@ The `Continue` library consists of an **SDK**, a **GUI**, and a **Server** that 2. The **GUI** lets you transparently review every automated step, providing the opportunity to undo and rerun any that ran incorrectly. -3. The **Server** is responsible for connecting the GUI and SDK to the IDE as well as deciding which steps to take next. \ No newline at end of file +3. The **Server** is responsible for connecting the GUI and SDK to the IDE as well as deciding which steps to take next. + + +## Running the server manually + +If you would like to run the Continue server manually, rather than allowing the VS Code to set it up, you can follow these steps: + +1. `git clone https://github.com/continuedev/continue` +2. `cd continue/continuedev` +3. Make sure packages are installed with `poetry install` + - If poetry is not installed, you can install with + ```bash + curl -sSL https://install.python-poetry.org | python3 - + ``` + (official instructions [here](https://python-poetry.org/docs/#installing-with-the-official-installer)) +4. `poetry shell` to activate the virtual environment +5. Either: + + a) To run without the debugger: `cd ..` and `python3 -m continuedev.src.continuedev.server.main` + + b) To run with the debugger: Open a VS Code window with `continue` as the root folder. Ensure that you have selected the Python interpreter from virtual environment, then use the `.vscode/launch.json` we have provided to start the debugger. -- cgit v1.2.3-70-g09d2 From 41c1a32c88bfb82efb52630bae113b8d970272cc Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 20:01:12 -0700 Subject: docs: :memo: improved README for VS Code marketplace --- .github/workflows/main.yaml | 129 +++++++++++++++++++++++--------------------- extension/README.md | 4 ++ 2 files changed, 71 insertions(+), 62 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index f2fcf368..9d9eb186 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -1,77 +1,82 @@ -# name: Publish Extension +name: Publish Extension -# on: -# push: -# branches: -# - main +on: + push: + branches: + - main -# jobs: -# publish: -# runs-on: ubuntu-latest +jobs: + do-nothing: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + # publish: + # runs-on: ubuntu-latest -# steps: -# - name: Checkout -# uses: actions/checkout@v2 + # steps: + # - name: Checkout + # uses: actions/checkout@v2 -# - name: Set up Python -# uses: actions/setup-python@v2 -# with: -# python-version: "3.8" + # - name: Set up Python + # uses: actions/setup-python@v2 + # with: + # python-version: "3.8" -# - name: Install Poetry -# run: | -# curl -sSL https://install.python-poetry.org | python3 - + # - name: Install Poetry + # run: | + # curl -sSL https://install.python-poetry.org | python3 - -# - name: Install Python dependencies -# run: | -# cd continuedev -# poetry install + # - name: Install Python dependencies + # run: | + # cd continuedev + # poetry install -# - name: Cache extension node_modules -# uses: actions/cache@v2 -# with: -# path: extension/node_modules -# key: ${{ runner.os }}-node-${{ hashFiles('extension/package-lock.json') }} + # - name: Cache extension node_modules + # uses: actions/cache@v2 + # with: + # path: extension/node_modules + # key: ${{ runner.os }}-node-${{ hashFiles('extension/package-lock.json') }} -# - name: Cache react-app node_modules -# uses: actions/cache@v2 -# with: -# path: extension/react-app/node_modules -# key: ${{ runner.os }}-node-${{ hashFiles('extension/react-app/package-lock.json') }} + # - name: Cache react-app node_modules + # uses: actions/cache@v2 + # with: + # path: extension/react-app/node_modules + # key: ${{ runner.os }}-node-${{ hashFiles('extension/react-app/package-lock.json') }} -# - name: Set up Node.js -# uses: actions/setup-node@v2 -# with: -# node-version: "14" + # - name: Set up Node.js + # uses: actions/setup-node@v2 + # with: + # node-version: "14" -# - name: Install extension Dependencies -# run: | -# cd extension -# npm ci --legacy-peer-deps + # - name: Install extension Dependencies + # run: | + # cd extension + # npm ci --legacy-peer-deps -# - name: Install react-app Dependencies -# run: | -# cd extension/react-app -# npm ci --legacy-peer-deps + # - name: Install react-app Dependencies + # run: | + # cd extension/react-app + # npm ci --legacy-peer-deps -# - name: Build and Publish -# run: | -# cd extension -# npm run full-package + # - name: Build and Publish + # run: | + # cd extension + # npm run full-package -# - name: Commit changes -# run: | -# git config --local user.email "action@github.com" -# git config --local user.name "GitHub Action" -# git commit -am "Update package.json version [skip ci]" + # - name: Commit changes + # run: | + # git config --local user.email "action@github.com" + # git config --local user.name "GitHub Action" + # git commit -am "Update package.json version [skip ci]" -# - name: Push changes -# uses: ad-m/github-push-action@master -# with: -# github_token: ${{ secrets.GITHUB_TOKEN }} + # - name: Push changes + # uses: ad-m/github-push-action@master + # with: + # github_token: ${{ secrets.GITHUB_TOKEN }} -# - name: Upload .vsix artifact -# uses: actions/upload-artifact@v2 -# with: -# name: vsix-artifact -# path: extension/build/* + # - name: Upload .vsix artifact + # uses: actions/upload-artifact@v2 + # with: + # name: vsix-artifact + # path: extension/build/* diff --git a/extension/README.md b/extension/README.md index acb9e097..55f8eeaa 100644 --- a/extension/README.md +++ b/extension/README.md @@ -2,6 +2,8 @@ **[Continue](https://continue.dev/docs) is the open-source autopilot for software development—a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Continue.continue) that brings the power of ChatGPT to your IDE** +![Editing with Continue](https://github.com/continuedev/continue/blob/main/readme.gif?raw=true) + ## Task, not tab, auto-complete ### Get possible explainations @@ -41,6 +43,8 @@ If your Continue server is not setting up, please check the console logs: 3. Select `Console` 4. Read the console logs +\*The Continue VS Code extension is currently in beta. It will attempt to start the Continue Python server locally for you, but if this fails you can run the server manually as is explained here: [Running the Continue server manually](https://continue.dev/docs/how-continue-works) + ## License [Apache 2.0 © 2023 Continue Dev, Inc.](./LICENSE) -- cgit v1.2.3-70-g09d2 From 58e5dc4a5c4fcbed25170b61fbd88d479c5aebcf Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 20:12:56 -0700 Subject: fix: :children_crossing: add slash commands to default config --- continuedev/src/continuedev/core/config.py | 39 ---------------------- .../libs/constants/default_config.py.txt | 31 +++++++++++++++++ extension/package-lock.json | 4 +-- extension/package.json | 2 +- 4 files changed, 34 insertions(+), 42 deletions(-) diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py index e367e06c..9fbda824 100644 --- a/continuedev/src/continuedev/core/config.py +++ b/continuedev/src/continuedev/core/config.py @@ -54,45 +54,6 @@ class ContinueConfig(BaseModel): context_providers: List[ContextProvider] = [] - # Want to force these to be the slash commands for now - @validator('slash_commands', pre=True) - def default_slash_commands_validator(cls, v): - from ..plugins.steps.open_config import OpenConfigStep - from ..plugins.steps.clear_history import ClearHistoryStep - from ..plugins.steps.feedback import FeedbackStep - from ..plugins.steps.comment_code import CommentCodeStep - from ..plugins.steps.main import EditHighlightedCodeStep - - DEFAULT_SLASH_COMMANDS = [ - SlashCommand( - name="edit", - description="Edit code in the current file or the highlighted code", - step=EditHighlightedCodeStep, - ), - SlashCommand( - name="config", - description="Open the config file to create new and edit existing slash commands", - step=OpenConfigStep, - ), - SlashCommand( - name="comment", - description="Write comments for the current file or highlighted code", - step=CommentCodeStep, - ), - SlashCommand( - name="feedback", - description="Send feedback to improve Continue", - step=FeedbackStep, - ), - SlashCommand( - name="clear", - description="Clear step history", - step=ClearHistoryStep, - ) - ] - - return DEFAULT_SLASH_COMMANDS + v - @validator('temperature', pre=True) def temperature_validator(cls, v): return max(0.0, min(1.0, v)) diff --git a/continuedev/src/continuedev/libs/constants/default_config.py.txt b/continuedev/src/continuedev/libs/constants/default_config.py.txt index 2210667d..1a66c847 100644 --- a/continuedev/src/continuedev/libs/constants/default_config.py.txt +++ b/continuedev/src/continuedev/libs/constants/default_config.py.txt @@ -13,6 +13,12 @@ from continuedev.core.config import CustomCommand, SlashCommand, ContinueConfig from continuedev.plugins.context_providers.github import GitHubIssuesContextProvider from continuedev.plugins.context_providers.google import GoogleContextProvider +from continuedev.plugins.steps.open_config import OpenConfigStep +from continuedev.plugins.steps.clear_history import ClearHistoryStep +from continuedev.plugins.steps.feedback import FeedbackStep +from continuedev.plugins.steps.comment_code import CommentCodeStep +from continuedev.plugins.steps.main import EditHighlightedCodeStep + class CommitMessageStep(Step): """ @@ -69,6 +75,31 @@ config = ContinueConfig( # description="This is an example slash command. Use /config to edit it and create more", # step=CommitMessageStep, # ) + SlashCommand( + name="edit", + description="Edit code in the current file or the highlighted code", + step=EditHighlightedCodeStep, + ), + SlashCommand( + name="config", + description="Open the config file to create new and edit existing slash commands", + step=OpenConfigStep, + ), + SlashCommand( + name="comment", + description="Write comments for the current file or highlighted code", + step=CommentCodeStep, + ), + SlashCommand( + name="feedback", + description="Send feedback to improve Continue", + step=FeedbackStep, + ), + SlashCommand( + name="clear", + description="Clear step history", + step=ClearHistoryStep, + ) ], # Context providers let you quickly select context by typing '@' diff --git a/extension/package-lock.json b/extension/package-lock.json index 5d0b0c56..5e2a7e2a 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.222", + "version": "0.0.223", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.222", + "version": "0.0.223", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 942c844c..507e00b8 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.222", + "version": "0.0.223", "publisher": "Continue", "engines": { "vscode": "^1.67.0" -- cgit v1.2.3-70-g09d2 From 23167a51d959fed5e4be057ceb9fff50cf34c6c8 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 28 Jul 2023 20:28:53 -0700 Subject: fix: :children_crossing: clear the dropdown after text input cleared --- extension/package-lock.json | 4 +- extension/package.json | 2 +- extension/react-app/src/components/ComboBox.tsx | 58 +++++++++++++++---------- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/extension/package-lock.json b/extension/package-lock.json index 5e2a7e2a..30b9952c 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.223", + "version": "0.0.224", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.223", + "version": "0.0.224", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 507e00b8..9b9daef6 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.223", + "version": "0.0.224", "publisher": "Continue", "engines": { "vscode": "^1.67.0" diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx index 9aab4e93..da559383 100644 --- a/extension/react-app/src/components/ComboBox.tsx +++ b/extension/react-app/src/components/ComboBox.tsx @@ -162,33 +162,43 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { } }, onInputValueChange({ inputValue, highlightedIndex }) { - if (!inputValue) return; + if (!inputValue) { + setItems([]); + return; + } props.onInputValueChange(inputValue); if (inputValue.endsWith("@") || currentlyInContextQuery) { - setCurrentlyInContextQuery(true); - const segs = inputValue.split("@"); - const providerAndQuery = segs[segs.length - 1]; - const [provider, query] = providerAndQuery.split(" "); - searchClient - .index(SEARCH_INDEX_NAME) - .search(providerAndQuery) - .then((res) => { - setItems( - res.hits.map((hit) => { - return { - name: hit.name, - description: hit.description, - id: hit.id, - }; - }) - ); - }) - .catch(() => { - // Swallow errors, because this simply is not supported on Windows at the moment - }); - return; + + if (segs.length > 1) { + // Get search results and return + setCurrentlyInContextQuery(true); + const providerAndQuery = segs[segs.length - 1]; + const [provider, query] = providerAndQuery.split(" "); + searchClient + .index(SEARCH_INDEX_NAME) + .search(providerAndQuery) + .then((res) => { + setItems( + res.hits.map((hit) => { + return { + name: hit.name, + description: hit.description, + id: hit.id, + }; + }) + ); + }) + .catch(() => { + // Swallow errors, because this simply is not supported on Windows at the moment + }); + return; + } else { + // Exit the '@' context menu + setCurrentlyInContextQuery(false); + setItems; + } } setItems( props.items.filter((item) => @@ -262,7 +272,7 @@ const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => { key={`${item.description.id.item_id}${idx}`} item={item} warning={ - false && item.content.length > 4000 && item.editing + item.content.length > 4000 && item.editing ? "Editing such a large range may be slow" : undefined } -- cgit v1.2.3-70-g09d2 From daabebcc5d6df885a508582c0ca13e659305d2ff Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 29 Jul 2023 00:08:28 -0700 Subject: feat: :loud_sound: display any server errors to the GUI --- continuedev/src/continuedev/core/autopilot.py | 6 +- continuedev/src/continuedev/core/sdk.py | 2 +- .../src/continuedev/libs/util/create_async_task.py | 8 +- .../src/continuedev/plugins/steps/core/core.py | 33 +++++-- .../continuedev/plugins/steps/search_directory.py | 2 +- continuedev/src/continuedev/server/gui.py | 100 ++++++++++----------- continuedev/src/continuedev/server/ide.py | 20 +++-- .../src/continuedev/server/session_manager.py | 4 +- extension/package-lock.json | 4 +- extension/package.json | 2 +- 10 files changed, 108 insertions(+), 73 deletions(-) diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 931cfb75..3f25e64e 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -15,7 +15,7 @@ from ..server.ide_protocol import AbstractIdeProtocolServer from ..libs.util.queue import AsyncSubscriptionQueue from ..models.main import ContinueBaseModel from .main import Context, ContinueCustomException, Policy, History, FullState, Step, HistoryNode -from ..plugins.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep +from ..plugins.steps.core.core import DisplayErrorStep, ReversibleStep, ManualEditStep, UserInputStep from .sdk import ContinueSDK from ..libs.util.traceback_parsers import get_python_traceback, get_javascript_traceback from openai import error as openai_errors @@ -312,8 +312,8 @@ class Autopilot(ContinueBaseModel): # Update subscribers with new description await self.update_subscribers() - create_async_task(update_description(), - self.continue_sdk.ide.unique_id) + create_async_task(update_description( + ), on_error=lambda e: self.continue_sdk.run_step(DisplayErrorStep(e=e))) return observation diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 5bb88b92..4b76a121 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -1,6 +1,6 @@ from functools import cached_property import traceback -from typing import Coroutine, Dict, Union +from typing import Coroutine, Dict, Literal, Union import os from ..plugins.steps.core.core import DefaultModelEditCodeStep diff --git a/continuedev/src/continuedev/libs/util/create_async_task.py b/continuedev/src/continuedev/libs/util/create_async_task.py index 00e87445..4c6d3c95 100644 --- a/continuedev/src/continuedev/libs/util/create_async_task.py +++ b/continuedev/src/continuedev/libs/util/create_async_task.py @@ -1,4 +1,4 @@ -from typing import Coroutine, Union +from typing import Callable, Coroutine, Optional, Union import traceback from .telemetry import posthog_logger from .logging import logger @@ -7,7 +7,7 @@ import nest_asyncio nest_asyncio.apply() -def create_async_task(coro: Coroutine, unique_id: Union[str, None] = None): +def create_async_task(coro: Coroutine, on_error: Optional[Callable[[Exception], Coroutine]] = None): """asyncio.create_task and log errors by adding a callback""" task = asyncio.create_task(coro) @@ -22,5 +22,9 @@ def create_async_task(coro: Coroutine, unique_id: Union[str, None] = None): "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e)) }) + # Log the error to the GUI + if on_error is not None: + asyncio.create_task(on_error(e)) + task.add_done_callback(callback) return task diff --git a/continuedev/src/continuedev/plugins/steps/core/core.py b/continuedev/src/continuedev/plugins/steps/core/core.py index 5a81e5ee..c80cecc3 100644 --- a/continuedev/src/continuedev/plugins/steps/core/core.py +++ b/continuedev/src/continuedev/plugins/steps/core/core.py @@ -1,20 +1,22 @@ # These steps are depended upon by ContinueSDK import os -import subprocess +import json import difflib from textwrap import dedent -from typing import Coroutine, List, Literal, Union +import traceback +from typing import Any, Coroutine, List, Union +import difflib + +from pydantic import validator from ....libs.llm.ggml import GGML from ....models.main import Range -from ....libs.llm.prompt_utils import MarkdownStyleEncoderDecoder from ....models.filesystem_edit import EditDiff, FileEdit, FileEditWithFullContents, FileSystemEdit from ....models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents -from ....core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation -from ....core.main import ChatMessage, ContinueCustomException, Step, SequentialStep +from ....core.observation import Observation, TextObservation, UserInputObservation +from ....core.main import ChatMessage, ContinueCustomException, Step from ....libs.util.count_tokens import MAX_TOKENS_FOR_MODEL, DEFAULT_MAX_TOKENS from ....libs.util.strings import dedent_and_get_common_whitespace, remove_quotes_and_escapes -import difflib class ContinueSDK: @@ -41,6 +43,25 @@ class MessageStep(Step): return TextObservation(text=self.message) +class DisplayErrorStep(Step): + name: str = "Error in the Continue server" + e: Any + + class Config: + arbitrary_types_allowed = True + + @validator("e", pre=True, always=True) + def validate_e(cls, v): + if isinstance(v, Exception): + return '\n'.join(traceback.format_exception(v)) + + async def describe(self, models: Models) -> Coroutine[str, None, None]: + return self.e + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + raise ContinueCustomException(message=self.e, title=self.name) + + class FileSystemEditStep(ReversibleStep): edit: FileSystemEdit _diff: Union[EditDiff, None] = None diff --git a/continuedev/src/continuedev/plugins/steps/search_directory.py b/continuedev/src/continuedev/plugins/steps/search_directory.py index 7d02d6fa..07b50473 100644 --- a/continuedev/src/continuedev/plugins/steps/search_directory.py +++ b/continuedev/src/continuedev/plugins/steps/search_directory.py @@ -65,5 +65,5 @@ class EditAllMatchesStep(Step): range=range_in_file.range, filename=range_in_file.filepath, prompt=self.user_request - ), sdk.ide.unique_id) for range_in_file in range_in_files] + )) for range_in_file in range_in_files] await asyncio.gather(*tasks) diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 2adb680e..98a5aea0 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -8,6 +8,7 @@ import traceback from uvicorn.main import Server from .session_manager import session_manager, Session +from ..plugins.steps.core.core import DisplayErrorStep, MessageStep from .gui_protocol import AbstractGUIProtocolServer from ..libs.util.queue import AsyncSubscriptionQueue from ..libs.util.telemetry import posthog_logger @@ -70,94 +71,88 @@ class GUIProtocolServer(AbstractGUIProtocolServer): resp = await self._receive_json(message_type) return resp_model.parse_obj(resp) + def on_error(self, e: Exception): + return self.session.autopilot.continue_sdk.run_step(DisplayErrorStep(e=e)) + def handle_json(self, message_type: str, data: Any): - try: - if message_type == "main_input": - self.on_main_input(data["input"]) - elif message_type == "step_user_input": - self.on_step_user_input(data["input"], data["index"]) - elif message_type == "refinement_input": - self.on_refinement_input(data["input"], data["index"]) - elif message_type == "reverse_to_index": - self.on_reverse_to_index(data["index"]) - elif message_type == "retry_at_index": - self.on_retry_at_index(data["index"]) - elif message_type == "clear_history": - self.on_clear_history() - elif message_type == "delete_at_index": - self.on_delete_at_index(data["index"]) - elif message_type == "delete_context_with_ids": - self.on_delete_context_with_ids(data["ids"]) - elif message_type == "toggle_adding_highlighted_code": - self.on_toggle_adding_highlighted_code() - elif message_type == "set_editing_at_indices": - self.on_set_editing_at_indices(data["indices"]) - elif message_type == "show_logs_at_index": - self.on_show_logs_at_index(data["index"]) - elif message_type == "select_context_item": - self.select_context_item(data["id"], data["query"]) - except Exception as e: - logger.debug(e) + if message_type == "main_input": + self.on_main_input(data["input"]) + elif message_type == "step_user_input": + self.on_step_user_input(data["input"], data["index"]) + elif message_type == "refinement_input": + self.on_refinement_input(data["input"], data["index"]) + elif message_type == "reverse_to_index": + self.on_reverse_to_index(data["index"]) + elif message_type == "retry_at_index": + self.on_retry_at_index(data["index"]) + elif message_type == "clear_history": + self.on_clear_history() + elif message_type == "delete_at_index": + self.on_delete_at_index(data["index"]) + elif message_type == "delete_context_with_ids": + self.on_delete_context_with_ids(data["ids"]) + elif message_type == "toggle_adding_highlighted_code": + self.on_toggle_adding_highlighted_code() + elif message_type == "set_editing_at_indices": + self.on_set_editing_at_indices(data["indices"]) + elif message_type == "show_logs_at_index": + self.on_show_logs_at_index(data["index"]) + elif message_type == "select_context_item": + self.select_context_item(data["id"], data["query"]) def on_main_input(self, input: str): # Do something with user input - create_async_task(self.session.autopilot.accept_user_input( - input), self.session.autopilot.continue_sdk.ide.unique_id) + create_async_task( + self.session.autopilot.accept_user_input(input), self.on_error) def on_reverse_to_index(self, index: int): # Reverse the history to the given index - create_async_task(self.session.autopilot.reverse_to_index( - index), self.session.autopilot.continue_sdk.ide.unique_id) + create_async_task( + self.session.autopilot.reverse_to_index(index), self.on_error) def on_step_user_input(self, input: str, index: int): create_async_task( - self.session.autopilot.give_user_input(input, index), self.session.autopilot.continue_sdk.ide.unique_id) + self.session.autopilot.give_user_input(input, index), self.on_error) def on_refinement_input(self, input: str, index: int): create_async_task( - self.session.autopilot.accept_refinement_input(input, index), self.session.autopilot.continue_sdk.ide.unique_id) + self.session.autopilot.accept_refinement_input(input, index), self.on_error) def on_retry_at_index(self, index: int): create_async_task( - self.session.autopilot.retry_at_index(index), self.session.autopilot.continue_sdk.ide.unique_id) + self.session.autopilot.retry_at_index(index), self.on_error) def on_clear_history(self): - create_async_task(self.session.autopilot.clear_history( - ), self.session.autopilot.continue_sdk.ide.unique_id) + create_async_task( + self.session.autopilot.clear_history(), self.on_error) def on_delete_at_index(self, index: int): - create_async_task(self.session.autopilot.delete_at_index( - index), self.session.autopilot.continue_sdk.ide.unique_id) + create_async_task( + self.session.autopilot.delete_at_index(index), self.on_error) def on_delete_context_with_ids(self, ids: List[str]): create_async_task( - self.session.autopilot.delete_context_with_ids( - ids), self.session.autopilot.continue_sdk.ide.unique_id - ) + self.session.autopilot.delete_context_with_ids(ids), self.on_error) def on_toggle_adding_highlighted_code(self): create_async_task( - self.session.autopilot.toggle_adding_highlighted_code( - ), self.session.autopilot.continue_sdk.ide.unique_id - ) + self.session.autopilot.toggle_adding_highlighted_code(), self.on_error) def on_set_editing_at_indices(self, indices: List[int]): create_async_task( - self.session.autopilot.set_editing_at_indices( - indices), self.session.autopilot.continue_sdk.ide.unique_id - ) + self.session.autopilot.set_editing_at_indices(indices), self.on_error) 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), self.session.autopilot.continue_sdk.ide.unique_id) + self.session.autopilot.ide.showVirtualFile(name, logs), self.on_error) def select_context_item(self, id: str, query: str): """Called when user selects an item from the dropdown""" create_async_task( - self.session.autopilot.select_context_item(id, query), self.session.autopilot.continue_sdk.ide.unique_id) + self.session.autopilot.select_context_item(id, query), self.on_error) @router.websocket("/ws") @@ -189,9 +184,14 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we except WebSocketDisconnect as e: logger.debug("GUI websocket disconnected") except Exception as e: + # Log, send to PostHog, and send to GUI logger.debug(f"ERROR in gui websocket: {e}") + err_msg = '\n'.join(traceback.format_exception(e)) posthog_logger.capture_event("gui_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e))}) + "error_title": e.__str__() or e.__repr__(), "error_message": err_msg}) + + await protocol.session.autopilot.continue_sdk.run_step(DisplayErrorStep(e=e)) + raise e finally: logger.debug("Closing gui websocket") diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 8a269cb7..e4c07029 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -10,6 +10,7 @@ from pydantic import BaseModel import traceback import asyncio +from ..plugins.steps.core.core import DisplayErrorStep, MessageStep from .meilisearch_server import start_meilisearch from ..libs.util.telemetry import posthog_logger from ..libs.util.queue import AsyncSubscriptionQueue @@ -279,6 +280,9 @@ class IdeProtocolServer(AbstractIdeProtocolServer): # This is where you might have triggers: plugins can subscribe to certian events # like file changes, tracebacks, etc... + def on_error(self, e: Exception): + return self.session_manager.sessions[self.session_id].autopilot.continue_sdk.run_step(DisplayErrorStep(e=e)) + def onAcceptRejectSuggestion(self, accepted: bool): posthog_logger.capture_event("accept_reject_suggestion", { "accepted": accepted @@ -311,22 +315,22 @@ class IdeProtocolServer(AbstractIdeProtocolServer): def onDeleteAtIndex(self, index: int): if autopilot := self.__get_autopilot(): - create_async_task(autopilot.delete_at_index(index), self.unique_id) + create_async_task(autopilot.delete_at_index(index), self.on_error) def onCommandOutput(self, output: str): if autopilot := self.__get_autopilot(): create_async_task( - autopilot.handle_command_output(output), self.unique_id) + autopilot.handle_command_output(output), self.on_error) def onHighlightedCodeUpdate(self, range_in_files: List[RangeInFileWithContents]): if autopilot := self.__get_autopilot(): create_async_task(autopilot.handle_highlighted_code( - range_in_files), self.unique_id) + range_in_files), self.on_error) def onMainUserInput(self, input: str): if autopilot := self.__get_autopilot(): create_async_task( - autopilot.accept_user_input(input), self.unique_id) + autopilot.accept_user_input(input), self.on_error) # Request information. Session doesn't matter. async def getOpenFiles(self) -> List[str]: @@ -459,7 +463,7 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str = None): logger.debug(f"Received IDE message: {message_type}") create_async_task( - ideProtocolServer.handle_json(message_type, data)) + ideProtocolServer.handle_json(message_type, data), ideProtocolServer.on_error) ideProtocolServer = IdeProtocolServer(session_manager, websocket) if session_id is not None: @@ -480,8 +484,12 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str = None): logger.debug("IDE wbsocket disconnected") except Exception as e: logger.debug(f"Error in ide websocket: {e}") + err_msg = '\n'.join(traceback.format_exception(e)) posthog_logger.capture_event("gui_error", { - "error_title": e.__str__() or e.__repr__(), "error_message": '\n'.join(traceback.format_exception(e))}) + "error_title": e.__str__() or e.__repr__(), "error_message": err_msg}) + + await session_manager.sessions[session_id].autopilot.continue_sdk.run_step(DisplayErrorStep(e=e)) + raise e finally: logger.debug("Closing ide websocket") diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index d30411cd..cf46028f 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -6,6 +6,7 @@ import json from fastapi.websockets import WebSocketState +from ..plugins.steps.core.core import DisplayErrorStep from ..libs.util.paths import getSessionFilePath, getSessionsFolderPath from ..models.filesystem_edit import FileEditWithFullContents from ..libs.constants.main import CONTINUE_SESSIONS_FOLDER @@ -83,7 +84,8 @@ class SessionManager: }) autopilot.on_update(on_update) - create_async_task(autopilot.run_policy()) + create_async_task(autopilot.run_policy( + ), lambda e: autopilot.continue_sdk.run_step(DisplayErrorStep(e=e))) return session async def remove_session(self, session_id: str): diff --git a/extension/package-lock.json b/extension/package-lock.json index 30b9952c..18c80fed 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.224", + "version": "0.0.225", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.224", + "version": "0.0.225", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 9b9daef6..b2c94593 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.224", + "version": "0.0.225", "publisher": "Continue", "engines": { "vscode": "^1.67.0" -- cgit v1.2.3-70-g09d2 From 72784f6f1161f0c5b647889c26089a8247111dc9 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 29 Jul 2023 00:49:29 -0700 Subject: fix: :goal_net: display errors in SimpleChatStep --- continuedev/src/continuedev/plugins/steps/chat.py | 46 +++++++++++------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/continuedev/src/continuedev/plugins/steps/chat.py b/continuedev/src/continuedev/plugins/steps/chat.py index 2c662459..f72a8ec0 100644 --- a/continuedev/src/continuedev/plugins/steps/chat.py +++ b/continuedev/src/continuedev/plugins/steps/chat.py @@ -5,7 +5,7 @@ from pydantic import Field from ...libs.util.strings import remove_quotes_and_escapes from .main import EditHighlightedCodeStep -from .core.core import MessageStep +from .core.core import DisplayErrorStep, MessageStep from ...core.main import FunctionCall, Models from ...core.main import ChatMessage, Step, step_to_json_schema from ...core.sdk import ContinueSDK @@ -26,34 +26,32 @@ class SimpleChatStep(Step): messages: List[ChatMessage] = None async def run(self, sdk: ContinueSDK): - completion = "" messages = self.messages or await sdk.get_chat_context() generator = sdk.models.default.stream_chat( messages, temperature=sdk.config.temperature) - try: - async for chunk in generator: - if sdk.current_step_was_deleted(): - # So that the message doesn't disappear - self.hide = False - break - if "content" in chunk: - self.description += chunk["content"] - completion += chunk["content"] - await sdk.update_ui() - finally: - self.name = remove_quotes_and_escapes(await sdk.models.gpt35.complete( - f"Write a short title for the following chat message: {self.description}")) - - self.chat_context.append(ChatMessage( - role="assistant", - content=completion, - summary=self.name - )) - - # TODO: Never actually closing. - await generator.aclose() + async for chunk in generator: + if sdk.current_step_was_deleted(): + # So that the message doesn't disappear + self.hide = False + break + + if "content" in chunk: + self.description += chunk["content"] + await sdk.update_ui() + + self.name = remove_quotes_and_escapes(await sdk.models.gpt35.complete( + f"Write a short title for the following chat message: {self.description}")) + + self.chat_context.append(ChatMessage( + role="assistant", + content=self.description, + summary=self.name + )) + + # TODO: Never actually closing. + await generator.aclose() class AddFileStep(Step): -- cgit v1.2.3-70-g09d2 From ce3a5e9b6964d12b9f70b577a84d0f70115ac943 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 29 Jul 2023 10:12:28 -0700 Subject: chore: :bookmark: patch --- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/extension/package-lock.json b/extension/package-lock.json index 18c80fed..6a4c45de 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.225", + "version": "0.0.226", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.225", + "version": "0.0.226", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index b2c94593..8422773c 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.225", + "version": "0.0.226", "publisher": "Continue", "engines": { "vscode": "^1.67.0" -- cgit v1.2.3-70-g09d2 From 7eccf2ddb9308111686251474fb79fa03dfc87d6 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 29 Jul 2023 12:21:03 -0700 Subject: fix: serve to localhost --- continuedev/src/continuedev/server/main.py | 2 +- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index 13f6b840..468bc855 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -69,7 +69,7 @@ except Exception as e: def run_server(): - config = uvicorn.Config(app, host="0.0.0.0", port=args.port) + config = uvicorn.Config(app, host="127.0.0.1", port=args.port) server = uvicorn.Server(config) server.run() diff --git a/extension/package-lock.json b/extension/package-lock.json index 6a4c45de..6f289260 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.226", + "version": "0.0.227", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.226", + "version": "0.0.227", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 8422773c..5d0ccad2 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.226", + "version": "0.0.227", "publisher": "Continue", "engines": { "vscode": "^1.67.0" -- cgit v1.2.3-70-g09d2 From 3aa615a6c3243ecdd0334b94625b3d621b5d122b Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 29 Jul 2023 13:30:53 -0700 Subject: docs: :memo: improved /help prompt (don't hallucinate slash commands) --- continuedev/src/continuedev/plugins/steps/help.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/continuedev/src/continuedev/plugins/steps/help.py b/continuedev/src/continuedev/plugins/steps/help.py index d3807706..fef7f8e4 100644 --- a/continuedev/src/continuedev/plugins/steps/help.py +++ b/continuedev/src/continuedev/plugins/steps/help.py @@ -37,11 +37,16 @@ class HelpStep(Step): question = self.user_input - prompt = dedent(f"""Please us the information below to provide a succinct answer to the following quesiton: {question} - + prompt = dedent(f""" Information: + + {help} + + Instructions: + + Please us the information below to provide a succinct answer to the following question: {question} - {help}""") + Do not cite any slash commands other than those you've been told about, which are: /edit and /feedback.""") self.chat_context.append(ChatMessage( role="user", -- cgit v1.2.3-70-g09d2 From 17566c66e0a01ad3c38ece974e44c1c71a9188de Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 29 Jul 2023 13:33:53 -0700 Subject: docs: :memo: use hardcoded help info if no user_input --- continuedev/src/continuedev/plugins/steps/help.py | 48 ++++++++++++----------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/continuedev/src/continuedev/plugins/steps/help.py b/continuedev/src/continuedev/plugins/steps/help.py index fef7f8e4..6997a547 100644 --- a/continuedev/src/continuedev/plugins/steps/help.py +++ b/continuedev/src/continuedev/plugins/steps/help.py @@ -34,31 +34,33 @@ class HelpStep(Step): description: str = "" async def run(self, sdk: ContinueSDK): - question = self.user_input - prompt = dedent(f""" - Information: - - {help} - - Instructions: - - Please us the information below to provide a succinct answer to the following question: {question} - - Do not cite any slash commands other than those you've been told about, which are: /edit and /feedback.""") - - self.chat_context.append(ChatMessage( - role="user", - content=prompt, - summary="Help" - )) - messages = await sdk.get_chat_context() - generator = sdk.models.gpt4.stream_chat(messages) - async for chunk in generator: - if "content" in chunk: - self.description += chunk["content"] - await sdk.update_ui() + if question.strip() == "": + self.description = help + else: + prompt = dedent(f""" + Information: + + {help} + + Instructions: + + Please us the information below to provide a succinct answer to the following question: {question} + + Do not cite any slash commands other than those you've been told about, which are: /edit and /feedback.""") + + self.chat_context.append(ChatMessage( + role="user", + content=prompt, + summary="Help" + )) + messages = await sdk.get_chat_context() + generator = sdk.models.gpt4.stream_chat(messages) + async for chunk in generator: + if "content" in chunk: + self.description += chunk["content"] + await sdk.update_ui() posthog_logger.capture_event( "help", {"question": question, "answer": self.description}) -- cgit v1.2.3-70-g09d2 From 8bd76be6c0925e0d5e5f6d239e9c6907df3cfd23 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 29 Jul 2023 23:02:23 -0700 Subject: feat: :sparkles: FileTreeContextProvider also pass workspace_dir: str to provide_context_items --- continuedev/src/continuedev/core/autopilot.py | 2 +- continuedev/src/continuedev/core/context.py | 6 +-- .../continuedev/plugins/context_providers/file.py | 5 +-- .../plugins/context_providers/filetree.py | 49 ++++++++++++++++++++++ .../plugins/context_providers/github.py | 2 +- .../plugins/context_providers/google.py | 2 +- .../plugins/context_providers/highlighted_code.py | 2 +- 7 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 continuedev/src/continuedev/plugins/context_providers/filetree.py diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 3f25e64e..57e39d5c 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -75,7 +75,7 @@ class Autopilot(ContinueBaseModel): HighlightedCodeContextProvider(ide=ide), FileContextProvider(workspace_dir=ide.workspace_directory) ]) - await autopilot.context_manager.load_index() + await autopilot.context_manager.load_index(ide.workspace_directory) return autopilot diff --git a/continuedev/src/continuedev/core/context.py b/continuedev/src/continuedev/core/context.py index 78a747b2..86522ce1 100644 --- a/continuedev/src/continuedev/core/context.py +++ b/continuedev/src/continuedev/core/context.py @@ -35,7 +35,7 @@ class ContextProvider(BaseModel): return self.selected_items @abstractmethod - async def provide_context_items(self) -> List[ContextItem]: + async def provide_context_items(self, workspace_dir: str) -> List[ContextItem]: """ Provide documents for search index. This is run on startup. @@ -159,9 +159,9 @@ class ContextManager: return cls(context_providers) - async def load_index(self): + async def load_index(self, workspace_dir: str): for _, provider in self.context_providers.items(): - context_items = await provider.provide_context_items() + context_items = await provider.provide_context_items(workspace_dir) documents = [ { "id": item.description.id.to_string(), diff --git a/continuedev/src/continuedev/plugins/context_providers/file.py b/continuedev/src/continuedev/plugins/context_providers/file.py index 31c8e1d9..634774df 100644 --- a/continuedev/src/continuedev/plugins/context_providers/file.py +++ b/continuedev/src/continuedev/plugins/context_providers/file.py @@ -49,13 +49,12 @@ class FileContextProvider(ContextProvider): """ title = "file" - workspace_dir: str ignore_patterns: List[str] = DEFAULT_IGNORE_DIRS + \ list(filter(lambda d: f"**/{d}", DEFAULT_IGNORE_DIRS)) - async def provide_context_items(self) -> List[ContextItem]: + async def provide_context_items(self, workspace_dir: str) -> List[ContextItem]: filepaths = [] - for root, dir_names, file_names in os.walk(self.workspace_dir): + for root, dir_names, file_names in os.walk(workspace_dir): dir_names[:] = [d for d in dir_names if not any( fnmatch(d, pattern) for pattern in self.ignore_patterns)] for file_name in file_names: diff --git a/continuedev/src/continuedev/plugins/context_providers/filetree.py b/continuedev/src/continuedev/plugins/context_providers/filetree.py new file mode 100644 index 00000000..c7b4806b --- /dev/null +++ b/continuedev/src/continuedev/plugins/context_providers/filetree.py @@ -0,0 +1,49 @@ +import json +from typing import List +import os +import aiohttp + +from ...core.main import ContextItem, ContextItemDescription, ContextItemId +from ...core.context import ContextProvider + + +def format_file_tree(startpath) -> str: + result = "" + for root, dirs, files in os.walk(startpath): + level = root.replace(startpath, '').count(os.sep) + indent = ' ' * 4 * (level) + result += '{}{}/'.format(indent, os.path.basename(root)) + "\n" + subindent = ' ' * 4 * (level + 1) + for f in files: + result += '{}{}'.format(subindent, f) + "\n" + + return result + + +class FileTreeContextProvider(ContextProvider): + title = "tree" + + workspace_dir: str = None + + def _filetree_context_item(self): + return ContextItem( + content=format_file_tree(self.workspace_dir), + description=ContextItemDescription( + name="File Tree", + description="Add a formatted file tree of this directory to the context", + id=ContextItemId( + provider_title=self.title, + item_id=self.title + ) + ) + ) + + async def provide_context_items(self, workspace_dir: str) -> List[ContextItem]: + self.workspace_dir = workspace_dir + return [self._filetree_context_item()] + + async def get_item(self, id: ContextItemId, query: str) -> ContextItem: + if not id.item_id == self.title: + raise Exception("Invalid item id") + + return self._filetree_context_item() diff --git a/continuedev/src/continuedev/plugins/context_providers/github.py b/continuedev/src/continuedev/plugins/context_providers/github.py index 765a534d..2e7047f2 100644 --- a/continuedev/src/continuedev/plugins/context_providers/github.py +++ b/continuedev/src/continuedev/plugins/context_providers/github.py @@ -15,7 +15,7 @@ class GitHubIssuesContextProvider(ContextProvider): repo_name: str auth_token: str - async def provide_context_items(self) -> List[ContextItem]: + async def provide_context_items(self, workspace_dir: str) -> List[ContextItem]: auth = Auth.Token(self.auth_token) gh = Github(auth=auth) diff --git a/continuedev/src/continuedev/plugins/context_providers/google.py b/continuedev/src/continuedev/plugins/context_providers/google.py index 64954833..fc76fe67 100644 --- a/continuedev/src/continuedev/plugins/context_providers/google.py +++ b/continuedev/src/continuedev/plugins/context_providers/google.py @@ -42,7 +42,7 @@ class GoogleContextProvider(ContextProvider): async with session.post(url, headers=headers, data=payload) as response: return await response.text() - async def provide_context_items(self) -> List[ContextItem]: + async def provide_context_items(self, workspace_dir: str) -> List[ContextItem]: return [self.BASE_CONTEXT_ITEM] async def get_item(self, id: ContextItemId, query: str, _) -> ContextItem: diff --git a/continuedev/src/continuedev/plugins/context_providers/highlighted_code.py b/continuedev/src/continuedev/plugins/context_providers/highlighted_code.py index 664e705e..acd40dc7 100644 --- a/continuedev/src/continuedev/plugins/context_providers/highlighted_code.py +++ b/continuedev/src/continuedev/plugins/context_providers/highlighted_code.py @@ -96,7 +96,7 @@ class HighlightedCodeContextProvider(ContextProvider): hr.item.description.name = self._rif_to_name( hr.rif, display_filename=basename) - async def provide_context_items(self) -> List[ContextItem]: + async def provide_context_items(self, workspace_dir: str) -> List[ContextItem]: return [] async def get_item(self, id: ContextItemId, query: str) -> ContextItem: -- cgit v1.2.3-70-g09d2