From ad86eff7b06f0bfbed3b1cb362d83ec6a4348e45 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 2 Jun 2023 01:05:59 -0400 Subject: notebook -> gui --- continuedev/src/continuedev/core/agent.py | 186 --------------------- continuedev/src/continuedev/core/autopilot.py | 186 +++++++++++++++++++++ continuedev/src/continuedev/plugins/__init__.py | 26 --- continuedev/src/continuedev/plugins/load.py | 21 --- .../src/continuedev/plugins/policy/__init__.py | 4 - .../src/continuedev/plugins/policy/hookspecs.py | 10 -- .../continuedev/plugins/policy/libs/__init__.py | 0 .../continuedev/plugins/policy/libs/alternate.py | 20 --- .../src/continuedev/plugins/step/__init__.py | 4 - .../src/continuedev/plugins/step/hookspecs.py | 15 -- .../src/continuedev/plugins/step/libs/__init__.py | 0 .../continuedev/plugins/step/libs/hello_world.py | 9 - continuedev/src/continuedev/server/gui.py | 130 ++++++++++++++ continuedev/src/continuedev/server/gui_protocol.py | 28 ++++ continuedev/src/continuedev/server/ide.py | 16 +- continuedev/src/continuedev/server/ide_protocol.py | 12 +- continuedev/src/continuedev/server/main.py | 4 +- continuedev/src/continuedev/server/notebook.py | 130 -------------- .../src/continuedev/server/notebook_protocol.py | 28 ---- 19 files changed, 360 insertions(+), 469 deletions(-) delete mode 100644 continuedev/src/continuedev/core/agent.py create mode 100644 continuedev/src/continuedev/core/autopilot.py delete mode 100644 continuedev/src/continuedev/plugins/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/load.py delete mode 100644 continuedev/src/continuedev/plugins/policy/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/policy/hookspecs.py delete mode 100644 continuedev/src/continuedev/plugins/policy/libs/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/policy/libs/alternate.py delete mode 100644 continuedev/src/continuedev/plugins/step/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/step/hookspecs.py delete mode 100644 continuedev/src/continuedev/plugins/step/libs/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/step/libs/hello_world.py create mode 100644 continuedev/src/continuedev/server/gui.py create mode 100644 continuedev/src/continuedev/server/gui_protocol.py delete mode 100644 continuedev/src/continuedev/server/notebook.py delete mode 100644 continuedev/src/continuedev/server/notebook_protocol.py (limited to 'continuedev') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py deleted file mode 100644 index 6e920ab4..00000000 --- a/continuedev/src/continuedev/core/agent.py +++ /dev/null @@ -1,186 +0,0 @@ -import traceback -import time -from typing import Callable, Coroutine, List -from ..models.filesystem_edit import FileEditWithFullContents -from ..libs.llm import LLM -from .observation import Observation -from ..server.ide_protocol import AbstractIdeProtocolServer -from ..libs.util.queue import AsyncSubscriptionQueue -from ..models.main import ContinueBaseModel -from .main import Policy, History, FullState, Step, HistoryNode -from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep -from ..libs.util.telemetry import capture_event -from .sdk import ContinueSDK -import asyncio - - -class Autopilot(ContinueBaseModel): - policy: Policy - ide: AbstractIdeProtocolServer - history: History = History.from_empty() - _on_update_callbacks: List[Callable[[FullState], None]] = [] - - _active: bool = False - _should_halt: bool = False - _main_user_input_queue: List[str] = [] - - _user_input_queue = AsyncSubscriptionQueue() - - class Config: - arbitrary_types_allowed = True - - def get_full_state(self) -> FullState: - return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) - - def on_update(self, callback: Coroutine["FullState", None, None]): - """Subscribe to changes to state""" - self._on_update_callbacks.append(callback) - - async def update_subscribers(self): - full_state = self.get_full_state() - for callback in self._on_update_callbacks: - await callback(full_state) - - def give_user_input(self, input: str, index: int): - self._user_input_queue.post(str(index), input) - - async def wait_for_user_input(self) -> str: - self._active = False - await self.update_subscribers() - user_input = await self._user_input_queue.get(str(self.history.current_index)) - self._active = True - await self.update_subscribers() - return user_input - - _manual_edits_buffer: List[FileEditWithFullContents] = [] - - async def reverse_to_index(self, index: int): - try: - while self.history.get_current_index() >= index: - current_step = self.history.get_current().step - self.history.step_back() - if issubclass(current_step.__class__, ReversibleStep): - await current_step.reverse(ContinueSDK(self)) - - await self.update_subscribers() - except Exception as e: - print(e) - - def handle_manual_edits(self, edits: List[FileEditWithFullContents]): - for edit in edits: - self._manual_edits_buffer.append(edit) - # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. - # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) - - def handle_traceback(self, traceback: str): - raise NotImplementedError - - _step_depth: int = 0 - - async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: - capture_event( - 'step run', {'step_name': step.name, 'params': step.dict()}) - - if not is_future_step: - # Check manual edits buffer, clear out if needed by creating a ManualEditStep - if len(self._manual_edits_buffer) > 0: - manualEditsStep = ManualEditStep.from_sequence( - self._manual_edits_buffer) - self._manual_edits_buffer = [] - await self._run_singular_step(manualEditsStep) - - # Update history - do this first so we get top-first tree ordering - self.history.add_node(HistoryNode( - step=step, observation=None, depth=self._step_depth)) - - # Call all subscribed callbacks - await self.update_subscribers() - - # Run step - self._step_depth += 1 - observation = await step(ContinueSDK(self)) - self._step_depth -= 1 - - # Add observation to history - self.history.get_last_at_depth( - self._step_depth, include_current=True).observation = observation - - # Update its description - async def update_description(): - step._set_description(await step.describe(ContinueSDK(self).models)) - # Update subscribers with new description - await self.update_subscribers() - asyncio.create_task(update_description()) - - return observation - - async def run_from_step(self, step: "Step"): - # if self._active: - # raise RuntimeError("Autopilot is already running") - self._active = True - - next_step = step - is_future_step = False - while not (next_step is None or self._should_halt): - try: - if is_future_step: - # If future step, then we are replaying and need to delete the step from history so it can be replaced - self.history.remove_current_and_substeps() - - observation = await self._run_singular_step(next_step, is_future_step) - if next_step := self.policy.next(self.history): - is_future_step = False - elif next_step := self.history.take_next_step(): - is_future_step = True - else: - next_step = None - - except Exception as e: - print( - f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") - next_step = None - - self._active = False - - # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools - for callback in self._on_update_callbacks: - await callback(None) - - async def run_from_observation(self, observation: Observation): - next_step = self.policy.next(self.history) - await self.run_from_step(next_step) - - async def run_policy(self): - first_step = self.policy.next(self.history) - await self.run_from_step(first_step) - - async def _request_halt(self): - if self._active: - self._should_halt = True - while self._active: - time.sleep(0.1) - self._should_halt = False - return None - - async def accept_user_input(self, user_input: str): - self._main_user_input_queue.append(user_input) - await self.update_subscribers() - - if len(self._main_user_input_queue) > 1: - return - - # await self._request_halt() - # Just run the step that takes user input, and - # then up to the policy to decide how to deal with it. - self._main_user_input_queue.pop(0) - await self.update_subscribers() - await self.run_from_step(UserInputStep(user_input=user_input)) - - while len(self._main_user_input_queue) > 0: - await self.run_from_step(UserInputStep( - user_input=self._main_user_input_queue.pop(0))) - - async def accept_refinement_input(self, user_input: str, index: int): - await self._request_halt() - await self.reverse_to_index(index) - await self.run_from_step(UserInputStep(user_input=user_input)) diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py new file mode 100644 index 00000000..6e920ab4 --- /dev/null +++ b/continuedev/src/continuedev/core/autopilot.py @@ -0,0 +1,186 @@ +import traceback +import time +from typing import Callable, Coroutine, List +from ..models.filesystem_edit import FileEditWithFullContents +from ..libs.llm import LLM +from .observation import Observation +from ..server.ide_protocol import AbstractIdeProtocolServer +from ..libs.util.queue import AsyncSubscriptionQueue +from ..models.main import ContinueBaseModel +from .main import Policy, History, FullState, Step, HistoryNode +from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep +from ..libs.util.telemetry import capture_event +from .sdk import ContinueSDK +import asyncio + + +class Autopilot(ContinueBaseModel): + policy: Policy + ide: AbstractIdeProtocolServer + history: History = History.from_empty() + _on_update_callbacks: List[Callable[[FullState], None]] = [] + + _active: bool = False + _should_halt: bool = False + _main_user_input_queue: List[str] = [] + + _user_input_queue = AsyncSubscriptionQueue() + + class Config: + arbitrary_types_allowed = True + + def get_full_state(self) -> FullState: + return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) + + def on_update(self, callback: Coroutine["FullState", None, None]): + """Subscribe to changes to state""" + self._on_update_callbacks.append(callback) + + async def update_subscribers(self): + full_state = self.get_full_state() + for callback in self._on_update_callbacks: + await callback(full_state) + + def give_user_input(self, input: str, index: int): + self._user_input_queue.post(str(index), input) + + async def wait_for_user_input(self) -> str: + self._active = False + await self.update_subscribers() + user_input = await self._user_input_queue.get(str(self.history.current_index)) + self._active = True + await self.update_subscribers() + return user_input + + _manual_edits_buffer: List[FileEditWithFullContents] = [] + + async def reverse_to_index(self, index: int): + try: + while self.history.get_current_index() >= index: + current_step = self.history.get_current().step + self.history.step_back() + if issubclass(current_step.__class__, ReversibleStep): + await current_step.reverse(ContinueSDK(self)) + + await self.update_subscribers() + except Exception as e: + print(e) + + def handle_manual_edits(self, edits: List[FileEditWithFullContents]): + for edit in edits: + self._manual_edits_buffer.append(edit) + # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. + # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) + + def handle_traceback(self, traceback: str): + raise NotImplementedError + + _step_depth: int = 0 + + async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: + capture_event( + 'step run', {'step_name': step.name, 'params': step.dict()}) + + if not is_future_step: + # Check manual edits buffer, clear out if needed by creating a ManualEditStep + if len(self._manual_edits_buffer) > 0: + manualEditsStep = ManualEditStep.from_sequence( + self._manual_edits_buffer) + self._manual_edits_buffer = [] + await self._run_singular_step(manualEditsStep) + + # Update history - do this first so we get top-first tree ordering + self.history.add_node(HistoryNode( + step=step, observation=None, depth=self._step_depth)) + + # Call all subscribed callbacks + await self.update_subscribers() + + # Run step + self._step_depth += 1 + observation = await step(ContinueSDK(self)) + self._step_depth -= 1 + + # Add observation to history + self.history.get_last_at_depth( + self._step_depth, include_current=True).observation = observation + + # Update its description + async def update_description(): + step._set_description(await step.describe(ContinueSDK(self).models)) + # Update subscribers with new description + await self.update_subscribers() + asyncio.create_task(update_description()) + + return observation + + async def run_from_step(self, step: "Step"): + # if self._active: + # raise RuntimeError("Autopilot is already running") + self._active = True + + next_step = step + is_future_step = False + while not (next_step is None or self._should_halt): + try: + if is_future_step: + # If future step, then we are replaying and need to delete the step from history so it can be replaced + self.history.remove_current_and_substeps() + + observation = await self._run_singular_step(next_step, is_future_step) + if next_step := self.policy.next(self.history): + is_future_step = False + elif next_step := self.history.take_next_step(): + is_future_step = True + else: + next_step = None + + except Exception as e: + print( + f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") + next_step = None + + self._active = False + + # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools + for callback in self._on_update_callbacks: + await callback(None) + + async def run_from_observation(self, observation: Observation): + next_step = self.policy.next(self.history) + await self.run_from_step(next_step) + + async def run_policy(self): + first_step = self.policy.next(self.history) + await self.run_from_step(first_step) + + async def _request_halt(self): + if self._active: + self._should_halt = True + while self._active: + time.sleep(0.1) + self._should_halt = False + return None + + async def accept_user_input(self, user_input: str): + self._main_user_input_queue.append(user_input) + await self.update_subscribers() + + if len(self._main_user_input_queue) > 1: + return + + # await self._request_halt() + # Just run the step that takes user input, and + # then up to the policy to decide how to deal with it. + self._main_user_input_queue.pop(0) + await self.update_subscribers() + await self.run_from_step(UserInputStep(user_input=user_input)) + + while len(self._main_user_input_queue) > 0: + await self.run_from_step(UserInputStep( + user_input=self._main_user_input_queue.pop(0))) + + async def accept_refinement_input(self, user_input: str, index: int): + await self._request_halt() + await self.reverse_to_index(index) + await self.run_from_step(UserInputStep(user_input=user_input)) diff --git a/continuedev/src/continuedev/plugins/__init__.py b/continuedev/src/continuedev/plugins/__init__.py deleted file mode 100644 index 0ce6d079..00000000 --- a/continuedev/src/continuedev/plugins/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import List -import pluggy -from .step import hookspecs -from .step.libs import hello_world - -builtin_libs = [hello_world] - -def get_plugin_manager(use_plugins: List[str]) -> pluggy.PluginManager: - pm = pluggy.PluginManager("continue.step") - pm.add_hookspecs(hookspecs) - pm.load_setuptools_entrypoints("continue.step") - - # Only use plugins that are specified in the config file - for plugin, name in pm.list_name_plugin(): - if name not in use_plugins: - pm.set_blocked(plugin) - - # Print warning if plugin not found - for name in use_plugins: - if not pm.has_plugin(name): - print(f"Plugin {name} not found.") - - for lib in builtin_libs: - pm.register(lib) - - return pm \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/load.py b/continuedev/src/continuedev/plugins/load.py deleted file mode 100644 index adbaad09..00000000 --- a/continuedev/src/continuedev/plugins/load.py +++ /dev/null @@ -1,21 +0,0 @@ -def load_validator_plugin(config: ValidatorPluginConfig) -> Validator: - if config.name == "continue.tb_validator": - return PythonTracebackValidator(config.cmd, config.cwd) - elif config.name == "continue.pytest_validator": - return PytestValidator(cwd=config.cwd) - else: - raise KeyError("Unknown validator plugin name") - -def load_llm_plugin(config: LLMPluginConfig) -> LLM: - if config.provider == "openai": - return OpenAI(api_key=config.api_key) - else: - raise KeyError("Unknown LLM provider: " + config.provider) - -def load_policy_plugin(config: PolicyPluginConfig) -> Policy: - if config.name == "continue.random_policy": - return RandomPolicy() - elif config.name == "continue.dfs_policy": - return DFSPolicy() - else: - raise KeyError("Unknown policy plugin name") \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/policy/__init__.py b/continuedev/src/continuedev/plugins/policy/__init__.py deleted file mode 100644 index b9722bae..00000000 --- a/continuedev/src/continuedev/plugins/policy/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import pluggy - -hookimpl = pluggy.HookimplMarker("continue.policy") -"""Marker to be imported and used in plugins (and for own implementations)""" \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/policy/hookspecs.py b/continuedev/src/continuedev/plugins/policy/hookspecs.py deleted file mode 100644 index abe932d3..00000000 --- a/continuedev/src/continuedev/plugins/policy/hookspecs.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import List, Tuple -import pluggy -from ...libs.policy import Policy, Step - -hookspec = pluggy.HookspecMarker("continue.policy") - -class PolicyPlugin(Policy): - @hookspec - def next(self) -> Step: - """Get the next step to run""" \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/policy/libs/__init__.py b/continuedev/src/continuedev/plugins/policy/libs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/continuedev/src/continuedev/plugins/policy/libs/alternate.py b/continuedev/src/continuedev/plugins/policy/libs/alternate.py deleted file mode 100644 index 3087c059..00000000 --- a/continuedev/src/continuedev/plugins/policy/libs/alternate.py +++ /dev/null @@ -1,20 +0,0 @@ -from plugins import policy -from ....core.main import History, Step - - -class AlternatingPolicy: - """A Policy that alternates between two steps.""" - - def __init__(self, first: Step, second: Step): - self.first = first - self.second = second - self.last_was_first = False - - @policy.hookimpl - def next(self, history: History) -> Step: - if self.last_was_first: - self.last_was_first = False - return self.second - else: - self.last_was_first = True - return self.first diff --git a/continuedev/src/continuedev/plugins/step/__init__.py b/continuedev/src/continuedev/plugins/step/__init__.py deleted file mode 100644 index e6d8cd3b..00000000 --- a/continuedev/src/continuedev/plugins/step/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import pluggy - -hookimpl = pluggy.HookimplMarker("continue.step") -"""Marker to be imported and used in plugins (and for own implementations)""" \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/step/hookspecs.py b/continuedev/src/continuedev/plugins/step/hookspecs.py deleted file mode 100644 index a5714fc5..00000000 --- a/continuedev/src/continuedev/plugins/step/hookspecs.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Coroutine -import pluggy -from ...core.main import Step -from ...core.observation import Observation -from ...core.sdk import ContinueSDK - -hookspec = pluggy.HookspecMarker("continue.step") - -# Perhaps Actions should be generic about what their inputs must be. - - -class StepPlugin(Step): - @hookspec - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - """Run""" diff --git a/continuedev/src/continuedev/plugins/step/libs/__init__.py b/continuedev/src/continuedev/plugins/step/libs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/continuedev/src/continuedev/plugins/step/libs/hello_world.py b/continuedev/src/continuedev/plugins/step/libs/hello_world.py deleted file mode 100644 index 72255bfd..00000000 --- a/continuedev/src/continuedev/plugins/step/libs/hello_world.py +++ /dev/null @@ -1,9 +0,0 @@ -from ....plugins import step -from ....libs.steps import ContinueSDK - - -class HelloWorldStep: - """A Step that prints "Hello World!".""" - @step.hookimpl - def run(sdk: ContinueSDK): - print("Hello World!") diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py new file mode 100644 index 00000000..3d1a5a82 --- /dev/null +++ b/continuedev/src/continuedev/server/gui.py @@ -0,0 +1,130 @@ +import json +from fastapi import Depends, Header, WebSocket, APIRouter +from typing import Any, Type, TypeVar, Union +from pydantic import BaseModel +from uvicorn.main import Server + +from .session_manager import SessionManager, session_manager, Session +from .gui_protocol import AbstractGUIProtocolServer +from ..libs.util.queue import AsyncSubscriptionQueue +import asyncio +import nest_asyncio +nest_asyncio.apply() + +router = APIRouter(prefix="/gui", tags=["gui"]) + +# Graceful shutdown by closing websockets +original_handler = Server.handle_exit + + +class AppStatus: + should_exit = False + + @staticmethod + def handle_exit(*args, **kwargs): + AppStatus.should_exit = True + print("Shutting down") + original_handler(*args, **kwargs) + + +Server.handle_exit = AppStatus.handle_exit + + +def session(x_continue_session_id: str = Header("anonymous")) -> Session: + return session_manager.get_session(x_continue_session_id) + + +def websocket_session(session_id: str) -> Session: + return session_manager.get_session(session_id) + + +T = TypeVar("T", bound=BaseModel) + +# You should probably abstract away the websocket stuff into a separate class + + +class GUIProtocolServer(AbstractGUIProtocolServer): + websocket: WebSocket + session: Session + sub_queue: AsyncSubscriptionQueue = AsyncSubscriptionQueue() + + def __init__(self, session: Session): + self.session = session + + async def _send_json(self, message_type: str, data: Any): + await self.websocket.send_json({ + "messageType": message_type, + "data": data + }) + + async def _receive_json(self, message_type: str) -> Any: + return await self.sub_queue.get(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) + resp = await self._receive_json(message_type) + return resp_model.parse_obj(resp) + + 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"]) + except Exception as e: + print(e) + + async def send_state_update(self): + state = self.session.autopilot.get_full_state().dict() + await self._send_json("state_update", { + "state": state + }) + + def on_main_input(self, input: str): + # Do something with user input + asyncio.create_task(self.session.autopilot.accept_user_input(input)) + + def on_reverse_to_index(self, index: int): + # Reverse the history to the given index + asyncio.create_task(self.session.autopilot.reverse_to_index(index)) + + def on_step_user_input(self, input: str, index: int): + asyncio.create_task( + self.session.autopilot.give_user_input(input, index)) + + def on_refinement_input(self, input: str, index: int): + asyncio.create_task( + self.session.autopilot.accept_refinement_input(input, index)) + + +@router.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)): + await websocket.accept() + + print("Session started") + session_manager.register_websocket(session.session_id, websocket) + protocol = GUIProtocolServer(session) + protocol.websocket = websocket + + # Update any history that may have happened before connection + await protocol.send_state_update() + + while AppStatus.should_exit is False: + message = await websocket.receive_text() + print("Received message", message) + if type(message) is str: + message = json.loads(message) + + if "messageType" not in message or "data" not in message: + continue + message_type = message["messageType"] + data = message["data"] + + protocol.handle_json(message_type, data) + + print("Closing websocket") + await websocket.close() diff --git a/continuedev/src/continuedev/server/gui_protocol.py b/continuedev/src/continuedev/server/gui_protocol.py new file mode 100644 index 00000000..e32d80ef --- /dev/null +++ b/continuedev/src/continuedev/server/gui_protocol.py @@ -0,0 +1,28 @@ +from typing import Any +from abc import ABC, abstractmethod + + +class AbstractGUIProtocolServer(ABC): + @abstractmethod + async def handle_json(self, data: Any): + """Handle a json message""" + + @abstractmethod + def on_main_input(self, input: str): + """Called when the user inputs something""" + + @abstractmethod + def on_reverse_to_index(self, index: int): + """Called when the user requests reverse to a previous index""" + + @abstractmethod + def on_refinement_input(self, input: str, index: int): + """Called when the user inputs a refinement""" + + @abstractmethod + def on_step_user_input(self, input: str, index: int): + """Called when the user inputs a step""" + + @abstractmethod + async def send_state_update(self, state: dict): + """Send a state update to the client""" diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 32f0b3ba..71017ce0 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -12,7 +12,7 @@ from ..models.filesystem import FileSystem, RangeInFile, EditDiff, RealFileSyste from ..models.main import Traceback from ..models.filesystem_edit import AddDirectory, AddFile, DeleteDirectory, DeleteFile, FileSystemEdit, FileEdit, FileEditWithFullContents, RenameDirectory, RenameFile, SequentialFileSystemEdit from pydantic import BaseModel -from .notebook import SessionManager, session_manager +from .gui import SessionManager, session_manager from .ide_protocol import AbstractIdeProtocolServer @@ -106,8 +106,8 @@ class IdeProtocolServer(AbstractIdeProtocolServer): return resp_model.parse_obj(resp) async def handle_json(self, message_type: str, data: Any): - if message_type == "openNotebook": - await self.openNotebook() + if message_type == "openGUI": + await self.openGUI() elif message_type == "setFileOpen": await self.setFileOpen(data["filepath"], data["open"]) elif message_type == "fileEdits": @@ -131,9 +131,9 @@ class IdeProtocolServer(AbstractIdeProtocolServer): "open": open }) - async def openNotebook(self): + async def openGUI(self): session_id = self.session_manager.new_session(self) - await self._send_json("openNotebook", { + await self._send_json("openGUI", { "sessionId": session_id }) @@ -148,7 +148,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): self._receive_json(ShowSuggestionResponse) for i in range(len(suggestions)) ]) # WORKING ON THIS FLOW HERE. Fine now to just await for response, instead of doing something fancy with a "waiting" state on the autopilot. - # Just need connect the suggestionId to the IDE (and the notebook) + # Just need connect the suggestionId to the IDE (and the gui) return any([r.accepted for r in responses]) # ------------------------------- # @@ -168,11 +168,11 @@ class IdeProtocolServer(AbstractIdeProtocolServer): # Access to Autopilot (so SessionManager) pass - def onCloseNotebook(self, session_id: str): + def onCloseGUI(self, session_id: str): # Accesss to SessionManager pass - def onOpenNotebookRequest(self): + def onOpenGUIRequest(self): pass def onFileEdits(self, edits: List[FileEditWithFullContents]): diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py index 15d019b4..4f505e80 100644 --- a/continuedev/src/continuedev/server/ide_protocol.py +++ b/continuedev/src/continuedev/server/ide_protocol.py @@ -24,8 +24,8 @@ class AbstractIdeProtocolServer(ABC): """Set whether a file is open""" @abstractmethod - async def openNotebook(self): - """Open a notebook""" + async def openGUI(self): + """Open a GUI""" @abstractmethod async def showSuggestionsAndWait(self, suggestions: List[FileEdit]) -> bool: @@ -44,12 +44,12 @@ class AbstractIdeProtocolServer(ABC): """Called when a file system update is received""" @abstractmethod - def onCloseNotebook(self, session_id: str): - """Called when a notebook is closed""" + def onCloseGUI(self, session_id: str): + """Called when a GUI is closed""" @abstractmethod - def onOpenNotebookRequest(self): - """Called when a notebook is requested to be opened""" + def onOpenGUIRequest(self): + """Called when a GUI is requested to be opened""" @abstractmethod async def getOpenFiles(self) -> List[str]: diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index 1ffe1450..7b7124de 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -2,14 +2,14 @@ import os from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from .ide import router as ide_router -from .notebook import router as notebook_router +from .gui import router as gui_router import uvicorn import argparse app = FastAPI() app.include_router(ide_router) -app.include_router(notebook_router) +app.include_router(gui_router) # Add CORS support app.add_middleware( diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py deleted file mode 100644 index 8ebe2853..00000000 --- a/continuedev/src/continuedev/server/notebook.py +++ /dev/null @@ -1,130 +0,0 @@ -import json -from fastapi import Depends, Header, WebSocket, APIRouter -from typing import Any, Type, TypeVar, Union -from pydantic import BaseModel -from uvicorn.main import Server - -from .session_manager import SessionManager, session_manager, Session -from .notebook_protocol import AbstractNotebookProtocolServer -from ..libs.util.queue import AsyncSubscriptionQueue -import asyncio -import nest_asyncio -nest_asyncio.apply() - -router = APIRouter(prefix="/notebook", tags=["notebook"]) - -# Graceful shutdown by closing websockets -original_handler = Server.handle_exit - - -class AppStatus: - should_exit = False - - @staticmethod - def handle_exit(*args, **kwargs): - AppStatus.should_exit = True - print("Shutting down") - original_handler(*args, **kwargs) - - -Server.handle_exit = AppStatus.handle_exit - - -def session(x_continue_session_id: str = Header("anonymous")) -> Session: - return session_manager.get_session(x_continue_session_id) - - -def websocket_session(session_id: str) -> Session: - return session_manager.get_session(session_id) - - -T = TypeVar("T", bound=BaseModel) - -# You should probably abstract away the websocket stuff into a separate class - - -class NotebookProtocolServer(AbstractNotebookProtocolServer): - websocket: WebSocket - session: Session - sub_queue: AsyncSubscriptionQueue = AsyncSubscriptionQueue() - - def __init__(self, session: Session): - self.session = session - - async def _send_json(self, message_type: str, data: Any): - await self.websocket.send_json({ - "messageType": message_type, - "data": data - }) - - async def _receive_json(self, message_type: str) -> Any: - return await self.sub_queue.get(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) - resp = await self._receive_json(message_type) - return resp_model.parse_obj(resp) - - 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"]) - except Exception as e: - print(e) - - async def send_state_update(self): - state = self.session.autopilot.get_full_state().dict() - await self._send_json("state_update", { - "state": state - }) - - def on_main_input(self, input: str): - # Do something with user input - asyncio.create_task(self.session.autopilot.accept_user_input(input)) - - def on_reverse_to_index(self, index: int): - # Reverse the history to the given index - asyncio.create_task(self.session.autopilot.reverse_to_index(index)) - - def on_step_user_input(self, input: str, index: int): - asyncio.create_task( - self.session.autopilot.give_user_input(input, index)) - - def on_refinement_input(self, input: str, index: int): - asyncio.create_task( - self.session.autopilot.accept_refinement_input(input, index)) - - -@router.websocket("/ws") -async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)): - await websocket.accept() - - print("Session started") - session_manager.register_websocket(session.session_id, websocket) - protocol = NotebookProtocolServer(session) - protocol.websocket = websocket - - # Update any history that may have happened before connection - await protocol.send_state_update() - - while AppStatus.should_exit is False: - message = await websocket.receive_text() - print("Received message", message) - if type(message) is str: - message = json.loads(message) - - if "messageType" not in message or "data" not in message: - continue - message_type = message["messageType"] - data = message["data"] - - protocol.handle_json(message_type, data) - - print("Closing websocket") - await websocket.close() diff --git a/continuedev/src/continuedev/server/notebook_protocol.py b/continuedev/src/continuedev/server/notebook_protocol.py deleted file mode 100644 index c2be82e0..00000000 --- a/continuedev/src/continuedev/server/notebook_protocol.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Any -from abc import ABC, abstractmethod - - -class AbstractNotebookProtocolServer(ABC): - @abstractmethod - async def handle_json(self, data: Any): - """Handle a json message""" - - @abstractmethod - def on_main_input(self, input: str): - """Called when the user inputs something""" - - @abstractmethod - def on_reverse_to_index(self, index: int): - """Called when the user requests reverse to a previous index""" - - @abstractmethod - def on_refinement_input(self, input: str, index: int): - """Called when the user inputs a refinement""" - - @abstractmethod - def on_step_user_input(self, input: str, index: int): - """Called when the user inputs a step""" - - @abstractmethod - async def send_state_update(self, state: dict): - """Send a state update to the client""" -- cgit v1.2.3-70-g09d2