From 1e9bd40415488760a52957cbb015a1c3c4e1383a Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 27 May 2023 00:11:28 -0400 Subject: Refactoring to deal with circularity --- continuedev/src/continuedev/libs/agent.py | 180 +++++++++++++ continuedev/src/continuedev/libs/core.py | 289 +-------------------- continuedev/src/continuedev/libs/env.py | 7 + continuedev/src/continuedev/libs/sdk.py | 39 +++ .../src/continuedev/libs/steps/core/core.py | 91 +++++++ continuedev/src/continuedev/libs/steps/main.py | 25 ++ continuedev/src/continuedev/models/main.py | 5 + continuedev/src/continuedev/server/notebook.py | 9 +- 8 files changed, 357 insertions(+), 288 deletions(-) create mode 100644 continuedev/src/continuedev/libs/agent.py create mode 100644 continuedev/src/continuedev/libs/env.py create mode 100644 continuedev/src/continuedev/libs/sdk.py create mode 100644 continuedev/src/continuedev/libs/steps/core/core.py diff --git a/continuedev/src/continuedev/libs/agent.py b/continuedev/src/continuedev/libs/agent.py new file mode 100644 index 00000000..466aa234 --- /dev/null +++ b/continuedev/src/continuedev/libs/agent.py @@ -0,0 +1,180 @@ +import traceback +import time +from typing import Callable, Coroutine, List +from ..models.filesystem_edit import FileEditWithFullContents +from .llm import LLM +from .observation import Observation +from ..server.ide_protocol import AbstractIdeProtocolServer +from .util.queue import AsyncSubscriptionQueue +from ..models.main import ContinueBaseModel +from .core import Policy, History, FullState, Step, HistoryNode +from .steps.core.core import ReversibleStep, ManualEditStep, UserInputStep +from .sdk import ContinueSDK + + +class Agent(ContinueBaseModel): + llm: LLM + policy: Policy + ide: AbstractIdeProtocolServer + history: History = History.from_empty() + continue_sdk: "ContinueSDK" + _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: Callable[["FullState"], None]): + """Subscribe to changes to state""" + self._on_update_callbacks.append(callback) + + def update_subscribers(self): + full_state = self.get_full_state() + for callback in self._on_update_callbacks: + callback(full_state) + + def __get_step_params(self, step: "Step"): + return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message)) + + def give_user_input(self, input: str, index: int): + self._user_input_queue.post(index, input) + + async def wait_for_user_input(self) -> str: + self._active = False + self.update_subscribers() + await self._user_input_queue.get(self.history.current_index) + self._active = True + self.update_subscribers() + + _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(self.__get_step_params(current_step)) + + 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]: + 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)) + + # Run step + self._step_depth += 1 + observation = await step(self.__get_step_params(step)) + self._step_depth -= 1 + + # Add observation to history + self.history.get_current().observation = observation + + # Update its description + step._set_description(await step.describe(self.llm)) + + # Call all subscribed callbacks + self.update_subscribers() + + return observation + + async def run_from_step(self, step: "Step"): + # if self._active: + # raise RuntimeError("Agent 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: + 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) + 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) + 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/libs/core.py b/continuedev/src/continuedev/libs/core.py index 6a8a83ba..9d432c4b 100644 --- a/continuedev/src/continuedev/libs/core.py +++ b/continuedev/src/continuedev/libs/core.py @@ -1,18 +1,9 @@ -import traceback -import time from typing import Callable, Coroutine, Dict, Generator, List, Tuple, Union -from ..models.filesystem_edit import EditDiff, FileEdit, FileEditWithFullContents, FileSystemEdit -from ..models.filesystem import FileSystem -from pydantic import BaseModel, parse_file_as, validator -from .llm import LLM -from .observation import Observation, UserInputObservation -from ..server.ide_protocol import AbstractIdeProtocolServer -from .util.queue import AsyncSubscriptionQueue - -class ContinueBaseModel(BaseModel): - class Config: - underscore_attrs_are_private = True +from ..models.main import ContinueBaseModel +from pydantic import validator +from .llm import LLM +from .observation import Observation class HistoryNode(ContinueBaseModel): @@ -86,198 +77,11 @@ class Policy(ContinueBaseModel): class ContinueSDK: - """The SDK provided as parameters to a step""" - llm: LLM - ide: AbstractIdeProtocolServer - __agent: "Agent" - - def __init__(self, agent: "Agent", llm: Union[LLM, None] = None): - if llm is None: - self.llm = agent.llm - else: - self.llm = llm - self.ide = agent.ide - self.__agent = agent - - @property - def history(self) -> History: - return self.__agent.history - - async def run_step(self, step: "Step") -> Coroutine[Observation, None, None]: - return await self.__agent._run_singular_step(step) + pass - async def apply_filesystem_edit(self, edit: FileSystemEdit): - await self.run_step(FileSystemEditStep(edit=edit)) - async def wait_for_user_input(self) -> str: - return await self.__agent.wait_for_user_input() - - -class Agent(ContinueBaseModel): - llm: LLM - 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: Callable[["FullState"], None]): - """Subscribe to changes to state""" - self._on_update_callbacks.append(callback) - - def update_subscribers(self): - full_state = self.get_full_state() - for callback in self._on_update_callbacks: - callback(full_state) - - def __get_step_params(self, step: "Step"): - return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message)) - - def give_user_input(self, input: str, index: int): - self._user_input_queue.post(index, input) - - async def wait_for_user_input(self) -> str: - self._active = False - self.update_subscribers() - await self._user_input_queue.get(self.history.current_index) - self._active = True - self.update_subscribers() - - _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(self.__get_step_params(current_step)) - - 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]: - 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)) - - # Run step - self._step_depth += 1 - observation = await step(self.__get_step_params(step)) - self._step_depth -= 1 - - # Add observation to history - self.history.get_current().observation = observation - - # Update its description - step._set_description(await step.describe(self.llm)) - - # Call all subscribed callbacks - self.update_subscribers() - - return observation - - async def run_from_step(self, step: "Step"): - # if self._active: - # raise RuntimeError("Agent 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: - 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) - 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) - 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)) +class SequentialStep: + pass class Step(ContinueBaseModel): @@ -331,85 +135,6 @@ class Step(ContinueBaseModel): return SequentialStep(steps=steps) -class SequentialStep(Step): - steps: list[Step] - hide: bool = True - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - for step in self.steps: - observation = await sdk.run_step(step) - return observation - - -class ReversibleStep(Step): - async def reverse(self, sdk: ContinueSDK): - raise NotImplementedError - - -class FileSystemEditStep(ReversibleStep): - edit: FileSystemEdit - _diff: Union[EditDiff, None] = None - - hide: bool = True - - async def run(self, sdk: "ContinueSDK") -> Coroutine[Observation, None, None]: - self._diff = await sdk.ide.applyFileSystemEdit(self.edit) - return None - - async def reverse(self, sdk: "ContinueSDK"): - await sdk.ide.applyFileSystemEdit(self._diff.backward) - # Where and when should file saves happen? - - -class ManualEditStep(ReversibleStep): - edit_diff: EditDiff - hide: bool = True - - hide: bool = True - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return "Manual edit step" - # TODO - only handling FileEdit here, but need all other types of FileSystemEdits - # Also requires the merge_file_edit function - # return llm.complete(dedent(f"""This code was replaced: - - # {self.edit_diff.backward.replacement} - - # With this code: - - # {self.edit_diff.forward.replacement} - - # Maximally concise summary of changes in bullet points (can use markdown): - # """)) - - @classmethod - def from_sequence(cls, edits: List[FileEditWithFullContents]) -> "ManualEditStep": - diffs = [] - for edit in edits: - _, diff = FileSystem.apply_edit_to_str( - edit.fileContents, edit.fileEdit) - diffs.append(diff) - return cls(edit_diff=EditDiff.from_sequence(diffs)) - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - return None - - async def reverse(self, sdk: ContinueSDK): - await sdk.ide.applyFileSystemEdit(self.edit_diff.backward) - - -class UserInputStep(Step): - user_input: str - name: str = "User Input" - hide: bool = True - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return self.user_input - - async def run(self, sdk: ContinueSDK) -> Coroutine[UserInputObservation, None, None]: - return UserInputObservation(user_input=self.user_input) - - class ValidatorObservation(Observation): passed: bool observation: Observation diff --git a/continuedev/src/continuedev/libs/env.py b/continuedev/src/continuedev/libs/env.py new file mode 100644 index 00000000..d7275b41 --- /dev/null +++ b/continuedev/src/continuedev/libs/env.py @@ -0,0 +1,7 @@ +from dotenv import load_dotenv +import os + +load_dotenv() + + +openai_api_key = os.getenv("OPENAI_API_KEY") diff --git a/continuedev/src/continuedev/libs/sdk.py b/continuedev/src/continuedev/libs/sdk.py new file mode 100644 index 00000000..0295bf35 --- /dev/null +++ b/continuedev/src/continuedev/libs/sdk.py @@ -0,0 +1,39 @@ +from typing import Coroutine, Union +from ..models.filesystem_edit import FileSystemEdit +from .llm import LLM +from .observation import Observation +from ..server.ide_protocol import AbstractIdeProtocolServer +from .core import History, Step +from .steps.core.core import * + + +class Agent: + pass + + +class ContinueSDK: + """The SDK provided as parameters to a step""" + llm: LLM + ide: AbstractIdeProtocolServer + __agent: Agent + + def __init__(self, agent: Agent, llm: Union[LLM, None] = None): + if llm is None: + self.llm = agent.llm + else: + self.llm = llm + self.ide = agent.ide + self.__agent = agent + + @property + def history(self) -> History: + return self.__agent.history + + async def run_step(self, step: Step) -> Coroutine[Observation, None, None]: + return await self.__agent._run_singular_step(step) + + async def apply_filesystem_edit(self, edit: FileSystemEdit): + await self.run_step(FileSystemEditStep(edit=edit)) + + async def wait_for_user_input(self) -> str: + return await self.__agent.wait_for_user_input() diff --git a/continuedev/src/continuedev/libs/steps/core/core.py b/continuedev/src/continuedev/libs/steps/core/core.py new file mode 100644 index 00000000..23086b53 --- /dev/null +++ b/continuedev/src/continuedev/libs/steps/core/core.py @@ -0,0 +1,91 @@ +# These steps are depended upon by ContinueSDK +from typing import Callable, Coroutine, Dict, Generator, List, Tuple, Union + +from ....models.filesystem_edit import EditDiff, FileEditWithFullContents, FileSystemEdit +from ....models.filesystem import FileSystem +from ...llm import LLM +from ...observation import Observation, UserInputObservation +from ...core import Step + + +class ContinueSDK: + pass + + +class SequentialStep(Step): + steps: list[Step] + hide: bool = True + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + for step in self.steps: + observation = await sdk.run_step(step) + return observation + + +class ReversibleStep(Step): + async def reverse(self, sdk: ContinueSDK): + raise NotImplementedError + + +class FileSystemEditStep(ReversibleStep): + edit: FileSystemEdit + _diff: Union[EditDiff, None] = None + + hide: bool = True + + async def run(self, sdk: "ContinueSDK") -> Coroutine[Observation, None, None]: + self._diff = await sdk.ide.applyFileSystemEdit(self.edit) + return None + + async def reverse(self, sdk: "ContinueSDK"): + await sdk.ide.applyFileSystemEdit(self._diff.backward) + # Where and when should file saves happen? + + +class ManualEditStep(ReversibleStep): + edit_diff: EditDiff + hide: bool = True + + hide: bool = True + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return "Manual edit step" + # TODO - only handling FileEdit here, but need all other types of FileSystemEdits + # Also requires the merge_file_edit function + # return llm.complete(dedent(f"""This code was replaced: + + # {self.edit_diff.backward.replacement} + + # With this code: + + # {self.edit_diff.forward.replacement} + + # Maximally concise summary of changes in bullet points (can use markdown): + # """)) + + @classmethod + def from_sequence(cls, edits: List[FileEditWithFullContents]) -> "ManualEditStep": + diffs = [] + for edit in edits: + _, diff = FileSystem.apply_edit_to_str( + edit.fileContents, edit.fileEdit) + diffs.append(diff) + return cls(edit_diff=EditDiff.from_sequence(diffs)) + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + return None + + async def reverse(self, sdk: ContinueSDK): + await sdk.ide.applyFileSystemEdit(self.edit_diff.backward) + + +class UserInputStep(Step): + user_input: str + name: str = "User Input" + hide: bool = True + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return self.user_input + + async def run(self, sdk: ContinueSDK) -> Coroutine[UserInputObservation, None, None]: + return UserInputObservation(user_input=self.user_input) diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 70953e95..555c1180 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -51,6 +51,31 @@ class RunCommandStep(Step): return TextObservation(text=stdout) +def ShellCommandsStep(Step): + cmds: List[str] + name: str = "Run Shell Commands" + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return "\n".join(self.cmds) + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + cwd = await sdk.ide.getWorkspaceDirectory() + + process = subprocess.Popen( + '/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd) + + stdin_input = "\n".join(self.cmds) + out, err = process.communicate(stdin_input.encode()) + + # TODO: How to await?? + + # If it fails, return the error + if err is not None and err != "": + return TextObservation(text=err) + + return None + + class WaitForUserInputStep(Step): prompt: str name: str = "Waiting for user input" diff --git a/continuedev/src/continuedev/models/main.py b/continuedev/src/continuedev/models/main.py index 081ec4af..7986b30c 100644 --- a/continuedev/src/continuedev/models/main.py +++ b/continuedev/src/continuedev/models/main.py @@ -4,6 +4,11 @@ from pydantic import BaseModel, root_validator from functools import total_ordering +class ContinueBaseModel(BaseModel): + class Config: + underscore_attrs_are_private = True + + @total_ordering class Position(BaseModel): line: int diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index c9d4edc5..5eb151d7 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -6,20 +6,17 @@ from uvicorn.main import Server from ..models.filesystem_edit import FileEditWithFullContents from ..libs.policy import DemoPolicy -from ..libs.core import Agent, FullState, History, Step +from ..libs.core import FullState, History, Step +from ..libs.agent import Agent from ..libs.steps.nate import ImplementAbstractMethodStep from ..libs.observation import Observation -from dotenv import load_dotenv from ..libs.llm.openai import OpenAI from .ide_protocol import AbstractIdeProtocolServer -import os +from ..libs.env import openai_api_key import asyncio import nest_asyncio nest_asyncio.apply() -load_dotenv() -openai_api_key = os.getenv("OPENAI_API_KEY") - router = APIRouter(prefix="/notebook", tags=["notebook"]) # Graceful shutdown by closing websockets -- cgit v1.2.3-70-g09d2 From 393eb93545567b9a3d1b6cd60a69e88bc15cf28c Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 27 May 2023 14:47:33 -0400 Subject: Refactoring and shortcut functions to common steps --- continuedev/src/continuedev/core/agent.py | 180 +++++++++++++++++++++ continuedev/src/continuedev/core/env.py | 7 + continuedev/src/continuedev/core/main.py | 148 +++++++++++++++++ continuedev/src/continuedev/core/observation.py | 35 ++++ continuedev/src/continuedev/core/policy.py | 93 +++++++++++ continuedev/src/continuedev/core/sdk.py | 62 +++++++ continuedev/src/continuedev/libs/agent.py | 180 --------------------- continuedev/src/continuedev/libs/core.py | 148 ----------------- continuedev/src/continuedev/libs/env.py | 7 - continuedev/src/continuedev/libs/observation.py | 35 ---- continuedev/src/continuedev/libs/policy.py | 91 ----------- continuedev/src/continuedev/libs/sdk.py | 39 ----- continuedev/src/continuedev/libs/steps/chroma.py | 9 +- .../src/continuedev/libs/steps/core/core.py | 133 ++++++++++++++- .../libs/steps/draft/abstract_method.py | 3 +- .../src/continuedev/libs/steps/draft/dlt.py | 3 +- .../src/continuedev/libs/steps/draft/redux.py | 7 +- .../src/continuedev/libs/steps/draft/typeorm.py | 3 +- continuedev/src/continuedev/libs/steps/main.py | 174 +++----------------- .../src/continuedev/libs/steps/migration.py | 2 +- continuedev/src/continuedev/libs/steps/nate.py | 9 +- continuedev/src/continuedev/libs/steps/pytest.py | 2 +- continuedev/src/continuedev/libs/steps/ty.py | 8 +- .../continuedev/plugins/policy/libs/alternate.py | 4 +- .../src/continuedev/plugins/step/hookspecs.py | 4 +- continuedev/src/continuedev/server/notebook.py | 10 +- 26 files changed, 713 insertions(+), 683 deletions(-) create mode 100644 continuedev/src/continuedev/core/agent.py create mode 100644 continuedev/src/continuedev/core/env.py create mode 100644 continuedev/src/continuedev/core/main.py create mode 100644 continuedev/src/continuedev/core/observation.py create mode 100644 continuedev/src/continuedev/core/policy.py create mode 100644 continuedev/src/continuedev/core/sdk.py delete mode 100644 continuedev/src/continuedev/libs/agent.py delete mode 100644 continuedev/src/continuedev/libs/core.py delete mode 100644 continuedev/src/continuedev/libs/env.py delete mode 100644 continuedev/src/continuedev/libs/observation.py delete mode 100644 continuedev/src/continuedev/libs/policy.py delete mode 100644 continuedev/src/continuedev/libs/sdk.py diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py new file mode 100644 index 00000000..509a54b3 --- /dev/null +++ b/continuedev/src/continuedev/core/agent.py @@ -0,0 +1,180 @@ +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 .sdk import ContinueSDK + + +class Agent(ContinueBaseModel): + llm: LLM + policy: Policy + ide: AbstractIdeProtocolServer + history: History = History.from_empty() + continue_sdk: "ContinueSDK" + _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: Callable[["FullState"], None]): + """Subscribe to changes to state""" + self._on_update_callbacks.append(callback) + + def update_subscribers(self): + full_state = self.get_full_state() + for callback in self._on_update_callbacks: + callback(full_state) + + def __get_step_params(self, step: "Step"): + return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message)) + + def give_user_input(self, input: str, index: int): + self._user_input_queue.post(index, input) + + async def wait_for_user_input(self) -> str: + self._active = False + self.update_subscribers() + await self._user_input_queue.get(self.history.current_index) + self._active = True + self.update_subscribers() + + _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(self.__get_step_params(current_step)) + + 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]: + 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)) + + # Run step + self._step_depth += 1 + observation = await step(self.__get_step_params(step)) + self._step_depth -= 1 + + # Add observation to history + self.history.get_current().observation = observation + + # Update its description + step._set_description(await step.describe(self.llm)) + + # Call all subscribed callbacks + self.update_subscribers() + + return observation + + async def run_from_step(self, step: "Step"): + # if self._active: + # raise RuntimeError("Agent 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: + 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) + 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) + 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/env.py b/continuedev/src/continuedev/core/env.py new file mode 100644 index 00000000..d7275b41 --- /dev/null +++ b/continuedev/src/continuedev/core/env.py @@ -0,0 +1,7 @@ +from dotenv import load_dotenv +import os + +load_dotenv() + + +openai_api_key = os.getenv("OPENAI_API_KEY") diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py new file mode 100644 index 00000000..51fcd299 --- /dev/null +++ b/continuedev/src/continuedev/core/main.py @@ -0,0 +1,148 @@ +from typing import Callable, Coroutine, Dict, Generator, List, Tuple, Union + +from ..models.main import ContinueBaseModel +from pydantic import validator +from ..libs.llm import LLM +from .observation import Observation + + +class HistoryNode(ContinueBaseModel): + """A point in history, a list of which make up History""" + step: "Step" + observation: Union[Observation, None] + depth: int + + +class History(ContinueBaseModel): + """A history of steps taken and their results""" + timeline: List[HistoryNode] + current_index: int + + def add_node(self, node: HistoryNode): + self.timeline.insert(self.current_index + 1, node) + self.current_index += 1 + + def get_current(self) -> Union[HistoryNode, None]: + if self.current_index < 0: + return None + return self.timeline[self.current_index] + + def remove_current_and_substeps(self): + self.timeline.pop(self.current_index) + while self.get_current() is not None and self.get_current().depth > 0: + self.timeline.pop(self.current_index) + + def take_next_step(self) -> Union["Step", None]: + if self.has_future(): + self.current_index += 1 + current_state = self.get_current() + if current_state is None: + return None + return current_state.step + return None + + def get_current_index(self) -> int: + return self.current_index + + def has_future(self) -> bool: + return self.current_index < len(self.timeline) - 1 + + def step_back(self): + self.current_index -= 1 + + def last_observation(self) -> Union[Observation, None]: + state = self.get_current() + if state is None: + return None + return state.observation + + @classmethod + def from_empty(cls): + return cls(timeline=[], current_index=-1) + + +class FullState(ContinueBaseModel): + """A full state of the program, including the history""" + history: History + active: bool + user_input_queue: List[str] + + +class ContinueSDK: + pass + + +class SequentialStep: + pass + + +class Policy(ContinueBaseModel): + """A rule that determines which step to take next""" + + # Note that history is mutable, kinda sus + def next(self, history: History = History.from_empty()) -> "Step": + raise NotImplementedError + + +class Step(ContinueBaseModel): + name: str = None + hide: bool = False + _description: Union[str, None] = None + + system_message: Union[str, None] = None + + class Config: + copy_on_model_validation = False + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + if self._description is not None: + return self._description + return "Running step: " + self.name + + def _set_description(self, description: str): + self._description = description + + def dict(self, *args, **kwargs): + d = super().dict(*args, **kwargs) + if self._description is not None: + d["description"] = self._description + else: + d["description"] = self.name + return d + + @validator("name", pre=True, always=True) + def name_is_class_name(cls, name): + if name is None: + return cls.__name__ + return name + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + raise NotImplementedError + + async def __call__(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + return await self.run(sdk) + + def __rshift__(self, other: "Step"): + steps = [] + if isinstance(self, SequentialStep): + steps = self.steps + else: + steps.append(self) + if isinstance(other, SequentialStep): + steps += other.steps + else: + steps.append(other) + return SequentialStep(steps=steps) + + +class ValidatorObservation(Observation): + passed: bool + observation: Observation + + +class Validator(Step): + def run(self, sdk: ContinueSDK) -> ValidatorObservation: + raise NotImplementedError + + +HistoryNode.update_forward_refs() diff --git a/continuedev/src/continuedev/core/observation.py b/continuedev/src/continuedev/core/observation.py new file mode 100644 index 00000000..fef04311 --- /dev/null +++ b/continuedev/src/continuedev/core/observation.py @@ -0,0 +1,35 @@ +from pydantic import BaseModel, validator +from ..models.main import Traceback + + +class Observation(BaseModel): + pass + + +class TracebackObservation(Observation): + traceback: Traceback + + +class ValidatorObservation(Observation): + passed: bool + + +class UserInputObservation(Observation): + user_input: str + + +class DictObservation(Observation): + values: dict + + def __getitem__(self, key): + return self.values[key] + + +class TextObservation(Observation): + text: str + + @validator("text", pre=True, always=True) + def text_not_none(cls, v): + if v is None: + return "" + return v diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py new file mode 100644 index 00000000..9e6abf14 --- /dev/null +++ b/continuedev/src/continuedev/core/policy.py @@ -0,0 +1,93 @@ +from typing import List, Tuple, Type + +from ..models.main import ContinueBaseModel + +from ..libs.steps.ty import CreatePipelineStep +from .main import Step, Validator, History, Policy +from .observation import Observation, TracebackObservation, UserInputObservation +from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep +from ..libs.steps.nate import WritePytestsStep, CreateTableStep +from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma + + +class DemoPolicy(Policy): + ran_code_last: bool = False + cmd: str + + def next(self, history: History) -> Step: + observation = history.last_observation() + if observation is not None and isinstance(observation, UserInputObservation): + # This could be defined with ObservationTypePolicy. Ergonomics not right though. + if " test" in observation.user_input.lower(): + return WritePytestsStep(instructions=observation.user_input) + elif "/dlt" in observation.user_input.lower() or " dlt" in observation.user_input.lower(): + return CreatePipelineStep() + elif "/table" in observation.user_input: + return CreateTableStep(sql_str=" ".join(observation.user_input.split(" ")[1:])) + elif "/ask" in observation.user_input: + return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) + elif "/edit" in observation.user_input: + return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) + return EditHighlightedCodeStep(user_input=observation.user_input) + + state = history.get_current() + if state is None or not self.ran_code_last: + self.ran_code_last = True + return RunCodeStep(cmd=self.cmd) + + if observation is not None and isinstance(observation, TracebackObservation): + self.ran_code_last = False + return SolveTracebackStep(traceback=observation.traceback) + else: + return None + + +class ObservationTypePolicy(Policy): + def __init__(self, base_policy: Policy, observation_type: Type[Observation], step_type: Type[Step]): + self.observation_type = observation_type + self.step_type = step_type + self.base_policy = base_policy + + def next(self, history: History) -> Step: + observation = history.last_observation() + if observation is not None and isinstance(observation, self.observation_type): + return self.step_type(observation) + return self.base_policy.next(history) + + +class PolicyWrappedWithValidators(Policy): + """Default is to stop, unless the validator tells what to do next""" + index: int + stage: int + + def __init__(self, base_policy: Policy, pairs: List[Tuple[Validator, Type[Step]]]): + # Want to pass Type[Validator], or just the Validator? Question of where params are coming from. + self.pairs = pairs + self.index = len(pairs) + self.validating = 0 + self.base_policy = base_policy + + def next(self, history: History) -> Step: + if self.index == len(self.pairs): + self.index = 0 + return self.base_policy.next(history) + + if self.stage == 0: + # Running the validator at the current index for the first time + validator, step = self.pairs[self.index] + self.stage = 1 + return validator + elif self.stage == 1: + # Previously ran the validator at the current index, now receiving its ValidatorObservation + observation = history.last_observation() + if observation.passed: + self.stage = 0 + self.index += 1 + if self.index == len(self.pairs): + self.index = 0 + return self.base_policy.next(history) + else: + return self.pairs[self.index][0] + else: + _, step_type = self.pairs[self.index] + return step_type(observation) diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py new file mode 100644 index 00000000..ff62a2b1 --- /dev/null +++ b/continuedev/src/continuedev/core/sdk.py @@ -0,0 +1,62 @@ +from typing import Coroutine, Union +from ..models.filesystem_edit import FileSystemEdit +from ..models.filesystem import RangeInFile +from ..libs.llm import LLM +from .observation import Observation +from ..server.ide_protocol import AbstractIdeProtocolServer +from .main import History, Step +from ..libs.steps.core.core import * + + +class Agent: + pass + + +class ContinueSDKSteps: + def __init__(self, sdk: "ContinueSDK"): + self.sdk = sdk + + +class ContinueSDK: + """The SDK provided as parameters to a step""" + llm: LLM + ide: AbstractIdeProtocolServer + steps: ContinueSDKSteps + __agent: Agent + + def __init__(self, agent: Agent, llm: Union[LLM, None] = None): + if llm is None: + self.llm = agent.llm + else: + self.llm = llm + self.ide = agent.ide + self.__agent = agent + self.steps = ContinueSDKSteps(self) + + @property + def history(self) -> History: + return self.__agent.history + + async def run_step(self, step: Step) -> Coroutine[Observation, None, None]: + return await self.__agent._run_singular_step(step) + + async def apply_filesystem_edit(self, edit: FileSystemEdit): + await self.run_step(FileSystemEditStep(edit=edit)) + + async def wait_for_user_input(self) -> str: + return await self.__agent.wait_for_user_input() + + async def wait_for_user_confirmation(self, prompt: str): + return await self.run_step(WaitForUserConfirmationStep(prompt=prompt)) + + async def run(self, commands: List[str] | str, cwd: str = None): + commands = commands if isinstance(commands, List) else [commands] + return self.run_step(ShellCommandsStep(commands=commands, cwd=cwd)) + + async def edit_file(self, filename: str, prompt: str): + await self.ide.setFileOpen(filename) + contents = await self.ide.readFile(filename) + await self.run_step(EditCodeStep( + range_in_files=[RangeInFile.from_entire_file(filename, contents)], + prompt=f'Here is the code before:\n\n{{code}}\n\nHere is the user request:\n\n{prompt}\n\nHere is the code edited to perfectly solve the user request:\n\n' + )) diff --git a/continuedev/src/continuedev/libs/agent.py b/continuedev/src/continuedev/libs/agent.py deleted file mode 100644 index 466aa234..00000000 --- a/continuedev/src/continuedev/libs/agent.py +++ /dev/null @@ -1,180 +0,0 @@ -import traceback -import time -from typing import Callable, Coroutine, List -from ..models.filesystem_edit import FileEditWithFullContents -from .llm import LLM -from .observation import Observation -from ..server.ide_protocol import AbstractIdeProtocolServer -from .util.queue import AsyncSubscriptionQueue -from ..models.main import ContinueBaseModel -from .core import Policy, History, FullState, Step, HistoryNode -from .steps.core.core import ReversibleStep, ManualEditStep, UserInputStep -from .sdk import ContinueSDK - - -class Agent(ContinueBaseModel): - llm: LLM - policy: Policy - ide: AbstractIdeProtocolServer - history: History = History.from_empty() - continue_sdk: "ContinueSDK" - _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: Callable[["FullState"], None]): - """Subscribe to changes to state""" - self._on_update_callbacks.append(callback) - - def update_subscribers(self): - full_state = self.get_full_state() - for callback in self._on_update_callbacks: - callback(full_state) - - def __get_step_params(self, step: "Step"): - return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message)) - - def give_user_input(self, input: str, index: int): - self._user_input_queue.post(index, input) - - async def wait_for_user_input(self) -> str: - self._active = False - self.update_subscribers() - await self._user_input_queue.get(self.history.current_index) - self._active = True - self.update_subscribers() - - _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(self.__get_step_params(current_step)) - - 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]: - 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)) - - # Run step - self._step_depth += 1 - observation = await step(self.__get_step_params(step)) - self._step_depth -= 1 - - # Add observation to history - self.history.get_current().observation = observation - - # Update its description - step._set_description(await step.describe(self.llm)) - - # Call all subscribed callbacks - self.update_subscribers() - - return observation - - async def run_from_step(self, step: "Step"): - # if self._active: - # raise RuntimeError("Agent 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: - 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) - 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) - 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/libs/core.py b/continuedev/src/continuedev/libs/core.py deleted file mode 100644 index 9d432c4b..00000000 --- a/continuedev/src/continuedev/libs/core.py +++ /dev/null @@ -1,148 +0,0 @@ -from typing import Callable, Coroutine, Dict, Generator, List, Tuple, Union - -from ..models.main import ContinueBaseModel -from pydantic import validator -from .llm import LLM -from .observation import Observation - - -class HistoryNode(ContinueBaseModel): - """A point in history, a list of which make up History""" - step: "Step" - observation: Union[Observation, None] - depth: int - - -class History(ContinueBaseModel): - """A history of steps taken and their results""" - timeline: List[HistoryNode] - current_index: int - - def add_node(self, node: HistoryNode): - self.timeline.insert(self.current_index + 1, node) - self.current_index += 1 - - def get_current(self) -> Union[HistoryNode, None]: - if self.current_index < 0: - return None - return self.timeline[self.current_index] - - def remove_current_and_substeps(self): - self.timeline.pop(self.current_index) - while self.get_current() is not None and self.get_current().depth > 0: - self.timeline.pop(self.current_index) - - def take_next_step(self) -> Union["Step", None]: - if self.has_future(): - self.current_index += 1 - current_state = self.get_current() - if current_state is None: - return None - return current_state.step - return None - - def get_current_index(self) -> int: - return self.current_index - - def has_future(self) -> bool: - return self.current_index < len(self.timeline) - 1 - - def step_back(self): - self.current_index -= 1 - - def last_observation(self) -> Union[Observation, None]: - state = self.get_current() - if state is None: - return None - return state.observation - - @classmethod - def from_empty(cls): - return cls(timeline=[], current_index=-1) - - -class FullState(ContinueBaseModel): - """A full state of the program, including the history""" - history: History - active: bool - user_input_queue: List[str] - - -class Policy(ContinueBaseModel): - """A rule that determines which step to take next""" - - # Note that history is mutable, kinda sus - def next(self, history: History = History.from_empty()) -> "Step": - raise NotImplementedError - - -class ContinueSDK: - pass - - -class SequentialStep: - pass - - -class Step(ContinueBaseModel): - name: str = None - hide: bool = False - _description: Union[str, None] = None - - system_message: Union[str, None] = None - - class Config: - copy_on_model_validation = False - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - if self._description is not None: - return self._description - return "Running step: " + self.name - - def _set_description(self, description: str): - self._description = description - - def dict(self, *args, **kwargs): - d = super().dict(*args, **kwargs) - if self._description is not None: - d["description"] = self._description - else: - d["description"] = self.name - return d - - @validator("name", pre=True, always=True) - def name_is_class_name(cls, name): - if name is None: - return cls.__name__ - return name - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - raise NotImplementedError - - async def __call__(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - return await self.run(sdk) - - def __rshift__(self, other: "Step"): - steps = [] - if isinstance(self, SequentialStep): - steps = self.steps - else: - steps.append(self) - if isinstance(other, SequentialStep): - steps += other.steps - else: - steps.append(other) - return SequentialStep(steps=steps) - - -class ValidatorObservation(Observation): - passed: bool - observation: Observation - - -class Validator(Step): - def run(self, sdk: ContinueSDK) -> ValidatorObservation: - raise NotImplementedError - - -HistoryNode.update_forward_refs() diff --git a/continuedev/src/continuedev/libs/env.py b/continuedev/src/continuedev/libs/env.py deleted file mode 100644 index d7275b41..00000000 --- a/continuedev/src/continuedev/libs/env.py +++ /dev/null @@ -1,7 +0,0 @@ -from dotenv import load_dotenv -import os - -load_dotenv() - - -openai_api_key = os.getenv("OPENAI_API_KEY") diff --git a/continuedev/src/continuedev/libs/observation.py b/continuedev/src/continuedev/libs/observation.py deleted file mode 100644 index fef04311..00000000 --- a/continuedev/src/continuedev/libs/observation.py +++ /dev/null @@ -1,35 +0,0 @@ -from pydantic import BaseModel, validator -from ..models.main import Traceback - - -class Observation(BaseModel): - pass - - -class TracebackObservation(Observation): - traceback: Traceback - - -class ValidatorObservation(Observation): - passed: bool - - -class UserInputObservation(Observation): - user_input: str - - -class DictObservation(Observation): - values: dict - - def __getitem__(self, key): - return self.values[key] - - -class TextObservation(Observation): - text: str - - @validator("text", pre=True, always=True) - def text_not_none(cls, v): - if v is None: - return "" - return v diff --git a/continuedev/src/continuedev/libs/policy.py b/continuedev/src/continuedev/libs/policy.py deleted file mode 100644 index 586eaebe..00000000 --- a/continuedev/src/continuedev/libs/policy.py +++ /dev/null @@ -1,91 +0,0 @@ -from typing import List, Tuple, Type - -from .steps.ty import CreatePipelineStep -from .core import Step, Validator, Policy, History -from .observation import Observation, TracebackObservation, UserInputObservation -from .steps.main import EditCodeStep, EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep -from .steps.nate import WritePytestsStep, CreateTableStep -from .steps.chroma import AnswerQuestionChroma, EditFileChroma - - -class DemoPolicy(Policy): - ran_code_last: bool = False - cmd: str - - def next(self, history: History) -> Step: - observation = history.last_observation() - if observation is not None and isinstance(observation, UserInputObservation): - # This could be defined with ObservationTypePolicy. Ergonomics not right though. - if " test" in observation.user_input.lower(): - return WritePytestsStep(instructions=observation.user_input) - elif "/dlt" in observation.user_input.lower() or " dlt" in observation.user_input.lower(): - return CreatePipelineStep() - elif "/table" in observation.user_input: - return CreateTableStep(sql_str=" ".join(observation.user_input.split(" ")[1:])) - elif "/ask" in observation.user_input: - return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) - elif "/edit" in observation.user_input: - return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) - return EditHighlightedCodeStep(user_input=observation.user_input) - - state = history.get_current() - if state is None or not self.ran_code_last: - self.ran_code_last = True - return RunCodeStep(cmd=self.cmd) - - if observation is not None and isinstance(observation, TracebackObservation): - self.ran_code_last = False - return SolveTracebackStep(traceback=observation.traceback) - else: - return None - - -class ObservationTypePolicy(Policy): - def __init__(self, base_policy: Policy, observation_type: Type[Observation], step_type: Type[Step]): - self.observation_type = observation_type - self.step_type = step_type - self.base_policy = base_policy - - def next(self, history: History) -> Step: - observation = history.last_observation() - if observation is not None and isinstance(observation, self.observation_type): - return self.step_type(observation) - return self.base_policy.next(history) - - -class PolicyWrappedWithValidators(Policy): - """Default is to stop, unless the validator tells what to do next""" - index: int - stage: int - - def __init__(self, base_policy: Policy, pairs: List[Tuple[Validator, Type[Step]]]): - # Want to pass Type[Validator], or just the Validator? Question of where params are coming from. - self.pairs = pairs - self.index = len(pairs) - self.validating = 0 - self.base_policy = base_policy - - def next(self, history: History) -> Step: - if self.index == len(self.pairs): - self.index = 0 - return self.base_policy.next(history) - - if self.stage == 0: - # Running the validator at the current index for the first time - validator, step = self.pairs[self.index] - self.stage = 1 - return validator - elif self.stage == 1: - # Previously ran the validator at the current index, now receiving its ValidatorObservation - observation = history.last_observation() - if observation.passed: - self.stage = 0 - self.index += 1 - if self.index == len(self.pairs): - self.index = 0 - return self.base_policy.next(history) - else: - return self.pairs[self.index][0] - else: - _, step_type = self.pairs[self.index] - return step_type(observation) diff --git a/continuedev/src/continuedev/libs/sdk.py b/continuedev/src/continuedev/libs/sdk.py deleted file mode 100644 index 0295bf35..00000000 --- a/continuedev/src/continuedev/libs/sdk.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Coroutine, Union -from ..models.filesystem_edit import FileSystemEdit -from .llm import LLM -from .observation import Observation -from ..server.ide_protocol import AbstractIdeProtocolServer -from .core import History, Step -from .steps.core.core import * - - -class Agent: - pass - - -class ContinueSDK: - """The SDK provided as parameters to a step""" - llm: LLM - ide: AbstractIdeProtocolServer - __agent: Agent - - def __init__(self, agent: Agent, llm: Union[LLM, None] = None): - if llm is None: - self.llm = agent.llm - else: - self.llm = llm - self.ide = agent.ide - self.__agent = agent - - @property - def history(self) -> History: - return self.__agent.history - - async def run_step(self, step: Step) -> Coroutine[Observation, None, None]: - return await self.__agent._run_singular_step(step) - - async def apply_filesystem_edit(self, edit: FileSystemEdit): - await self.run_step(FileSystemEditStep(edit=edit)) - - async def wait_for_user_input(self) -> str: - return await self.__agent.wait_for_user_input() diff --git a/continuedev/src/continuedev/libs/steps/chroma.py b/continuedev/src/continuedev/libs/steps/chroma.py index 2d8742e8..f13a2bab 100644 --- a/continuedev/src/continuedev/libs/steps/chroma.py +++ b/continuedev/src/continuedev/libs/steps/chroma.py @@ -1,11 +1,10 @@ from textwrap import dedent from typing import Coroutine, Union -from ...models.filesystem_edit import AddDirectory, AddFile -from ..observation import Observation, TextObservation -from ..core import Step, ContinueSDK -from .main import EditCodeStep, EditFileStep, RunCommandStep, WaitForUserConfirmationStep +from ...core.observation import Observation, TextObservation +from ...core.main import Step, ContinueSDK +from .core.core import EditFileStep from ..chroma.query import query_codebase_index -from .main import EditFileStep +from .core.core import EditFileStep class AnswerQuestionChroma(Step): diff --git a/continuedev/src/continuedev/libs/steps/core/core.py b/continuedev/src/continuedev/libs/steps/core/core.py index 23086b53..0338d635 100644 --- a/continuedev/src/continuedev/libs/steps/core/core.py +++ b/continuedev/src/continuedev/libs/steps/core/core.py @@ -1,11 +1,16 @@ # These steps are depended upon by ContinueSDK -from typing import Callable, Coroutine, Dict, Generator, List, Tuple, Union +import subprocess +from textwrap import dedent +from typing import Coroutine, List, Union +from ...llm.prompt_utils import MarkdownStyleEncoderDecoder + +from ...util.traceback_parsers import parse_python_traceback from ....models.filesystem_edit import EditDiff, FileEditWithFullContents, FileSystemEdit -from ....models.filesystem import FileSystem +from ....models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents from ...llm import LLM -from ...observation import Observation, UserInputObservation -from ...core import Step +from ....core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation +from ....core.main import Step class ContinueSDK: @@ -42,6 +47,98 @@ class FileSystemEditStep(ReversibleStep): # Where and when should file saves happen? +def ShellCommandsStep(Step): + cmds: List[str] + cwd: str | None = None + name: str = "Run Shell Commands" + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return "\n".join(self.cmds) + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + cwd = await sdk.ide.getWorkspaceDirectory() if self.cwd is None else self.cwd + + process = subprocess.Popen( + '/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd) + + stdin_input = "\n".join(self.cmds) + out, err = process.communicate(stdin_input.encode()) + + # If it fails, return the error + if err is not None and err != "": + return TextObservation(text=err) + + return None + + +class EditCodeStep(Step): + # Might make an even more specific atomic step, which is "apply file edit" + range_in_files: List[RangeInFile] + prompt: str # String with {code} somewhere + name: str = "Edit code" + + _edit_diffs: Union[List[EditDiff], None] = None + _prompt: Union[str, None] = None + _completion: Union[str, None] = None + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + if self._edit_diffs is None: + return "Editing files: " + ", ".join(map(lambda rif: rif.filepath, self.range_in_files)) + elif len(self._edit_diffs) == 0: + return "No edits made" + else: + return llm.complete(dedent(f"""{self._prompt}{self._completion} + + Maximally concise summary of changes in bullet points (can use markdown): + """)) + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + rif_with_contents = [] + for range_in_file in self.range_in_files: + file_contents = await sdk.ide.readRangeInFile(range_in_file) + rif_with_contents.append( + RangeInFileWithContents.from_range_in_file(range_in_file, file_contents)) + enc_dec = MarkdownStyleEncoderDecoder(rif_with_contents) + code_string = enc_dec.encode() + prompt = self.prompt.format(code=code_string) + + completion = sdk.llm.complete(prompt) + + # Temporarily doing this to generate description. + self._prompt = prompt + self._completion = completion + + file_edits = enc_dec.decode(completion) + + self._edit_diffs = [] + for file_edit in file_edits: + diff = await sdk.apply_filesystem_edit(file_edit) + self._edit_diffs.append(diff) + + for filepath in set([file_edit.filepath for file_edit in file_edits]): + await sdk.ide.saveFile(filepath) + await sdk.ide.setFileOpen(filepath) + + return None + + +class EditFileStep(Step): + filepath: str + prompt: str + hide: bool = True + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return "Editing file: " + self.filepath + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + file_contents = await sdk.ide.readFile(self.filepath) + await sdk.run_step(EditCodeStep( + range_in_files=[RangeInFile.from_entire_file( + self.filepath, file_contents)], + prompt=self.prompt + )) + + class ManualEditStep(ReversibleStep): edit_diff: EditDiff hide: bool = True @@ -89,3 +186,31 @@ class UserInputStep(Step): async def run(self, sdk: ContinueSDK) -> Coroutine[UserInputObservation, None, None]: return UserInputObservation(user_input=self.user_input) + + +class WaitForUserInputStep(Step): + prompt: str + name: str = "Waiting for user input" + + _description: Union[str, None] = None + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return self.prompt + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + self._description = self.prompt + resp = await sdk.wait_for_user_input() + return TextObservation(text=resp) + + +class WaitForUserConfirmationStep(Step): + prompt: str + name: str = "Waiting for user confirmation" + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return self.prompt + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + self._description = self.prompt + resp = await sdk.wait_for_user_input() + return TextObservation(text=resp) diff --git a/continuedev/src/continuedev/libs/steps/draft/abstract_method.py b/continuedev/src/continuedev/libs/steps/draft/abstract_method.py index 927d93fd..f3131c4b 100644 --- a/continuedev/src/continuedev/libs/steps/draft/abstract_method.py +++ b/continuedev/src/continuedev/libs/steps/draft/abstract_method.py @@ -1,4 +1,5 @@ -from ...core import ContinueSDK, Step +from ....core.sdk import ContinueSDK +from ....core.main import Step class ImplementAbstractMethodStep(Step): diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index 608f089a..5ba5692a 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -1,6 +1,7 @@ from textwrap import dedent from ....models.filesystem_edit import AddFile -from ...core import Step, ContinueSDK +from ....core.main import Step +from ....core.sdk import ContinueSDK from ..main import WaitForUserInputStep diff --git a/continuedev/src/continuedev/libs/steps/draft/redux.py b/continuedev/src/continuedev/libs/steps/draft/redux.py index 52a8fbd8..efaa9ba4 100644 --- a/continuedev/src/continuedev/libs/steps/draft/redux.py +++ b/continuedev/src/continuedev/libs/steps/draft/redux.py @@ -1,7 +1,6 @@ -from textwrap import dedent -from ....models.filesystem_edit import AddFile -from ...core import Step, ContinueSDK -from ..main import WaitForUserInputStep, EditFileStep +from ....core.main import Step +from ....core.sdk import ContinueSDK +from ..core.core import EditFileStep class EditReduxStateStep(Step): diff --git a/continuedev/src/continuedev/libs/steps/draft/typeorm.py b/continuedev/src/continuedev/libs/steps/draft/typeorm.py index 9d058f1e..d06a6fb4 100644 --- a/continuedev/src/continuedev/libs/steps/draft/typeorm.py +++ b/continuedev/src/continuedev/libs/steps/draft/typeorm.py @@ -1,5 +1,6 @@ from textwrap import dedent -from ...core import Step, ContinueSDK +from ....core.main import Step +from ....core.sdk import ContinueSDK class CreateTableStep(Step): diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 555c1180..f28cb23f 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -1,18 +1,39 @@ -import time from typing import Callable, Coroutine, List, Union +from ..util.traceback_parsers import parse_python_traceback from ..llm import LLM from ...models.main import Traceback, Range from ...models.filesystem_edit import EditDiff, FileEdit from ...models.filesystem import RangeInFile, RangeInFileWithContents -from ..observation import Observation, TextObservation +from ...core.observation import Observation, TextObservation, TracebackObservation from ..llm.prompt_utils import MarkdownStyleEncoderDecoder from textwrap import dedent -from ..core import History, Policy, Step, ContinueSDK, Observation +from ...core.main import History, Policy, Step, ContinueSDK, Observation import subprocess -from ..util.traceback_parsers import parse_python_traceback -from ..observation import TracebackObservation import json +from .core.core import EditCodeStep + + +class RunCodeStep(Step): + cmd: str + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return f"Ran command: `{self.cmd}`" + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + result = subprocess.run( + self.cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout = result.stdout.decode("utf-8") + stderr = result.stderr.decode("utf-8") + print(stdout, stderr) + + # If it fails, return the error + tb = parse_python_traceback(stdout) or parse_python_traceback(stderr) + if tb: + return TracebackObservation(traceback=tb) + else: + self.hide = True + return None class RunPolicyUntilDoneStep(Step): @@ -51,149 +72,6 @@ class RunCommandStep(Step): return TextObservation(text=stdout) -def ShellCommandsStep(Step): - cmds: List[str] - name: str = "Run Shell Commands" - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return "\n".join(self.cmds) - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - cwd = await sdk.ide.getWorkspaceDirectory() - - process = subprocess.Popen( - '/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd) - - stdin_input = "\n".join(self.cmds) - out, err = process.communicate(stdin_input.encode()) - - # TODO: How to await?? - - # If it fails, return the error - if err is not None and err != "": - return TextObservation(text=err) - - return None - - -class WaitForUserInputStep(Step): - prompt: str - name: str = "Waiting for user input" - - _description: Union[str, None] = None - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return self.prompt - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - self._description = self.prompt - resp = await sdk.wait_for_user_input() - return TextObservation(text=resp) - - -class WaitForUserConfirmationStep(Step): - prompt: str - name: str = "Waiting for user confirmation" - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return self.prompt - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - self._description = self.prompt - resp = await sdk.wait_for_user_input() - return TextObservation(text=resp) - - -class RunCodeStep(Step): - cmd: str - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return f"Ran command: `{self.cmd}`" - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - result = subprocess.run( - self.cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout = result.stdout.decode("utf-8") - stderr = result.stderr.decode("utf-8") - print(stdout, stderr) - - # If it fails, return the error - tb = parse_python_traceback(stdout) or parse_python_traceback(stderr) - if tb: - return TracebackObservation(traceback=tb) - else: - self.hide = True - return None - - -class EditCodeStep(Step): - # Might make an even more specific atomic step, which is "apply file edit" - range_in_files: List[RangeInFile] - prompt: str # String with {code} somewhere - name: str = "Edit code" - - _edit_diffs: Union[List[EditDiff], None] = None - _prompt: Union[str, None] = None - _completion: Union[str, None] = None - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - if self._edit_diffs is None: - return "Editing files: " + ", ".join(map(lambda rif: rif.filepath, self.range_in_files)) - elif len(self._edit_diffs) == 0: - return "No edits made" - else: - return llm.complete(dedent(f"""{self._prompt}{self._completion} - - Maximally concise summary of changes in bullet points (can use markdown): - """)) - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - rif_with_contents = [] - for range_in_file in self.range_in_files: - file_contents = await sdk.ide.readRangeInFile(range_in_file) - rif_with_contents.append( - RangeInFileWithContents.from_range_in_file(range_in_file, file_contents)) - enc_dec = MarkdownStyleEncoderDecoder(rif_with_contents) - code_string = enc_dec.encode() - prompt = self.prompt.format(code=code_string) - - completion = sdk.llm.complete(prompt) - - # Temporarily doing this to generate description. - self._prompt = prompt - self._completion = completion - - file_edits = enc_dec.decode(completion) - - self._edit_diffs = [] - for file_edit in file_edits: - diff = await sdk.apply_filesystem_edit(file_edit) - self._edit_diffs.append(diff) - - for filepath in set([file_edit.filepath for file_edit in file_edits]): - await sdk.ide.saveFile(filepath) - await sdk.ide.setFileOpen(filepath) - - return None - - -class EditFileStep(Step): - filepath: str - prompt: str - hide: bool = True - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return "Editing file: " + self.filepath - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - file_contents = await sdk.ide.readFile(self.filepath) - await sdk.run_step(EditCodeStep( - range_in_files=[RangeInFile.from_entire_file( - self.filepath, file_contents)], - prompt=self.prompt - )) - - class FasterEditHighlightedCodeStep(Step): user_input: str hide = True diff --git a/continuedev/src/continuedev/libs/steps/migration.py b/continuedev/src/continuedev/libs/steps/migration.py index 04296836..f044a60f 100644 --- a/continuedev/src/continuedev/libs/steps/migration.py +++ b/continuedev/src/continuedev/libs/steps/migration.py @@ -3,7 +3,7 @@ from ...models.filesystem import RangeInFile from .main import EditCodeStep, RunCommandStep -from ..core import Step +from ...core.main import Step class MigrationStep(Step): diff --git a/continuedev/src/continuedev/libs/steps/nate.py b/continuedev/src/continuedev/libs/steps/nate.py index 80436fa4..a0e728e5 100644 --- a/continuedev/src/continuedev/libs/steps/nate.py +++ b/continuedev/src/continuedev/libs/steps/nate.py @@ -1,14 +1,13 @@ -import asyncio from textwrap import dedent import time from typing import Coroutine, Union -from ...models.main import Range from ...models.filesystem import RangeInFile from ...models.filesystem_edit import AddDirectory, AddFile -from ..observation import Observation, TextObservation -from ..core import Step, ContinueSDK -from .main import EditCodeStep, EditFileStep, RunCommandStep, WaitForUserConfirmationStep +from ...core.observation import Observation, TextObservation +from ...core.main import Step, ContinueSDK +from .main import RunCommandStep +from .core.core import WaitForUserConfirmationStep, EditCodeStep, EditFileStep import os diff --git a/continuedev/src/continuedev/libs/steps/pytest.py b/continuedev/src/continuedev/libs/steps/pytest.py index e53eb465..b4e6dfd2 100644 --- a/continuedev/src/continuedev/libs/steps/pytest.py +++ b/continuedev/src/continuedev/libs/steps/pytest.py @@ -1,6 +1,6 @@ from textwrap import dedent from ...models.filesystem_edit import AddDirectory, AddFile -from ..core import Step, ContinueSDK +from ...core.main import Step, ContinueSDK import os diff --git a/continuedev/src/continuedev/libs/steps/ty.py b/continuedev/src/continuedev/libs/steps/ty.py index 1eb6271d..5ff03f04 100644 --- a/continuedev/src/continuedev/libs/steps/ty.py +++ b/continuedev/src/continuedev/libs/steps/ty.py @@ -2,9 +2,11 @@ import subprocess from ...models.main import Position, Range from ...models.filesystem import RangeInFile from ...models.filesystem_edit import AddDirectory, AddFile, FileEdit -from ..observation import DictObservation -from ..core import History, Step, ContinueSDK, Policy -from .main import EditCodeStep, RunCommandStep, WaitForUserInputStep, WaitForUserConfirmationStep +from ...core.observation import DictObservation +from ...core.main import History, Step, Policy +from ...core.sdk import ContinueSDK +from .main import RunCommandStep +from ..steps.core.core import EditCodeStep, WaitForUserConfirmationStep, WaitForUserInputStep source_name = "weather_api" diff --git a/continuedev/src/continuedev/plugins/policy/libs/alternate.py b/continuedev/src/continuedev/plugins/policy/libs/alternate.py index 5bfbb821..3087c059 100644 --- a/continuedev/src/continuedev/plugins/policy/libs/alternate.py +++ b/continuedev/src/continuedev/plugins/policy/libs/alternate.py @@ -1,7 +1,5 @@ from plugins import policy -from ....libs.observation import Observation -from ....libs.steps import Step -from ....libs.core import History +from ....core.main import History, Step class AlternatingPolicy: diff --git a/continuedev/src/continuedev/plugins/step/hookspecs.py b/continuedev/src/continuedev/plugins/step/hookspecs.py index 4309bad3..a5714fc5 100644 --- a/continuedev/src/continuedev/plugins/step/hookspecs.py +++ b/continuedev/src/continuedev/plugins/step/hookspecs.py @@ -1,6 +1,8 @@ from typing import Coroutine import pluggy -from ...libs.core import ContinueSDK, Step, Observation +from ...core.main import Step +from ...core.observation import Observation +from ...core.sdk import ContinueSDK hookspec = pluggy.HookspecMarker("continue.step") diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index 5eb151d7..bfd7a09c 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -5,14 +5,14 @@ from pydantic import BaseModel from uvicorn.main import Server from ..models.filesystem_edit import FileEditWithFullContents -from ..libs.policy import DemoPolicy -from ..libs.core import FullState, History, Step -from ..libs.agent import Agent +from ..core.policy import DemoPolicy +from ..core.main import FullState, History, Step +from ..core.agent import Agent from ..libs.steps.nate import ImplementAbstractMethodStep -from ..libs.observation import Observation +from ..core.observation import Observation from ..libs.llm.openai import OpenAI from .ide_protocol import AbstractIdeProtocolServer -from ..libs.env import openai_api_key +from ..core.env import openai_api_key import asyncio import nest_asyncio nest_asyncio.apply() -- cgit v1.2.3-70-g09d2 From e7fd1cd977c04643a11989f0bcefe6f573ab994a Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 27 May 2023 15:58:34 -0400 Subject: Added History classes for JSON Schema generation --- .../src/continuedev/models/generate_json_schema.py | 3 + extension/package.json | 2 +- extension/schema/History.d.ts | 41 + extension/schema/HistoryNode.d.ts | 31 + .../scripts/continuedev-0.1.0-py3-none-any.whl | Bin 61142 -> 51468 bytes schema/json/History.json | 73 ++ schema/json/HistoryNode.json | 51 + schema/openapi.json | 1027 ++++++++++++++++++++ 8 files changed, 1227 insertions(+), 1 deletion(-) create mode 100644 extension/schema/History.d.ts create mode 100644 extension/schema/HistoryNode.d.ts create mode 100644 schema/json/History.json create mode 100644 schema/json/HistoryNode.json create mode 100644 schema/openapi.json diff --git a/continuedev/src/continuedev/models/generate_json_schema.py b/continuedev/src/continuedev/models/generate_json_schema.py index da78dfac..07337029 100644 --- a/continuedev/src/continuedev/models/generate_json_schema.py +++ b/continuedev/src/continuedev/models/generate_json_schema.py @@ -1,6 +1,7 @@ from .main import * from .filesystem import RangeInFile, FileEdit from .filesystem_edit import FileEditWithFullContents +from ..core.main import History, HistoryNode from pydantic import schema_json_of import os @@ -10,6 +11,8 @@ MODELS_TO_GENERATE = [ RangeInFile, FileEdit ] + [ FileEditWithFullContents +] + [ + History, HistoryNode ] RENAMES = { diff --git a/extension/package.json b/extension/package.json index d957e071..3f9e59ff 100644 --- a/extension/package.json +++ b/extension/package.json @@ -166,7 +166,7 @@ "package": "cp ./config/prod_config.json ./config/config.json && npm run compile && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.1.vsix && cp ./config/dev_config.json ./config/config.json", "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.0-py3-none-any.whl ../extension/scripts/continuedev-0.1.0-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", "install-extension": "code --install-extension ./build/continue-0.0.1.vsix", - "uninstall": "code --uninstall-extension Continue.continue", + "uninstall": "code --uninstall-extension .continue", "reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension" }, "devDependencies": { diff --git a/extension/schema/History.d.ts b/extension/schema/History.d.ts new file mode 100644 index 00000000..508deaf0 --- /dev/null +++ b/extension/schema/History.d.ts @@ -0,0 +1,41 @@ +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +export type History = History1; +export type Name = string; +export type Hide = boolean; +export type SystemMessage = string; +export type Depth = number; +export type Timeline = HistoryNode[]; +export type CurrentIndex = number; + +/** + * A history of steps taken and their results + */ +export interface History1 { + timeline: Timeline; + current_index: CurrentIndex; + [k: string]: unknown; +} +/** + * A point in history, a list of which make up History + */ +export interface HistoryNode { + step: Step; + observation?: Observation; + depth: Depth; + [k: string]: unknown; +} +export interface Step { + name?: Name; + hide?: Hide; + system_message?: SystemMessage; + [k: string]: unknown; +} +export interface Observation { + [k: string]: unknown; +} diff --git a/extension/schema/HistoryNode.d.ts b/extension/schema/HistoryNode.d.ts new file mode 100644 index 00000000..c1507270 --- /dev/null +++ b/extension/schema/HistoryNode.d.ts @@ -0,0 +1,31 @@ +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +export type HistoryNode = HistoryNode1; +export type Name = string; +export type Hide = boolean; +export type SystemMessage = string; +export type Depth = number; + +/** + * A point in history, a list of which make up History + */ +export interface HistoryNode1 { + step: Step; + observation?: Observation; + depth: Depth; + [k: string]: unknown; +} +export interface Step { + name?: Name; + hide?: Hide; + system_message?: SystemMessage; + [k: string]: unknown; +} +export interface Observation { + [k: string]: unknown; +} diff --git a/extension/scripts/continuedev-0.1.0-py3-none-any.whl b/extension/scripts/continuedev-0.1.0-py3-none-any.whl index 15787c59..68457b3e 100644 Binary files a/extension/scripts/continuedev-0.1.0-py3-none-any.whl and b/extension/scripts/continuedev-0.1.0-py3-none-any.whl differ diff --git a/schema/json/History.json b/schema/json/History.json new file mode 100644 index 00000000..7691c7dd --- /dev/null +++ b/schema/json/History.json @@ -0,0 +1,73 @@ +{ + "title": "History", + "$ref": "#/definitions/continuedev__src__continuedev__core__main__History", + "definitions": { + "Step": { + "title": "Step", + "type": "object", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "hide": { + "title": "Hide", + "default": false, + "type": "boolean" + }, + "system_message": { + "title": "System Message", + "type": "string" + } + } + }, + "Observation": { + "title": "Observation", + "type": "object", + "properties": {} + }, + "HistoryNode": { + "title": "HistoryNode", + "description": "A point in history, a list of which make up History", + "type": "object", + "properties": { + "step": { + "$ref": "#/definitions/Step" + }, + "observation": { + "$ref": "#/definitions/Observation" + }, + "depth": { + "title": "Depth", + "type": "integer" + } + }, + "required": [ + "step", + "depth" + ] + }, + "continuedev__src__continuedev__core__main__History": { + "title": "History", + "description": "A history of steps taken and their results", + "type": "object", + "properties": { + "timeline": { + "title": "Timeline", + "type": "array", + "items": { + "$ref": "#/definitions/HistoryNode" + } + }, + "current_index": { + "title": "Current Index", + "type": "integer" + } + }, + "required": [ + "timeline", + "current_index" + ] + } + } +} \ No newline at end of file diff --git a/schema/json/HistoryNode.json b/schema/json/HistoryNode.json new file mode 100644 index 00000000..f58b8038 --- /dev/null +++ b/schema/json/HistoryNode.json @@ -0,0 +1,51 @@ +{ + "title": "HistoryNode", + "$ref": "#/definitions/continuedev__src__continuedev__core__main__HistoryNode", + "definitions": { + "Step": { + "title": "Step", + "type": "object", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "hide": { + "title": "Hide", + "default": false, + "type": "boolean" + }, + "system_message": { + "title": "System Message", + "type": "string" + } + } + }, + "Observation": { + "title": "Observation", + "type": "object", + "properties": {} + }, + "continuedev__src__continuedev__core__main__HistoryNode": { + "title": "HistoryNode", + "description": "A point in history, a list of which make up History", + "type": "object", + "properties": { + "step": { + "$ref": "#/definitions/Step" + }, + "observation": { + "$ref": "#/definitions/Observation" + }, + "depth": { + "title": "Depth", + "type": "integer" + } + }, + "required": [ + "step", + "depth" + ] + } + } +} \ No newline at end of file diff --git a/schema/openapi.json b/schema/openapi.json new file mode 100644 index 00000000..8880fd20 --- /dev/null +++ b/schema/openapi.json @@ -0,0 +1,1027 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "Continue API", + "description": "Continue API", + "version": "1.0" + }, + "paths": { + "/debug/run": { + "post": { + "tags": ["debug"], + "summary": "Run", + "description": "Returns boolean indicating whether error was found, edited, and solved, or not all of these.", + "operationId": "run_debug_run_post", + "parameters": [ + { + "required": true, + "schema": { + "title": "Filepath", + "type": "string" + }, + "name": "filepath", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Make Edit", + "type": "boolean", + "default": false + }, + "name": "make_edit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/inline": { + "post": { + "tags": ["debug"], + "summary": "Inline", + "operationId": "inline_debug_inline_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InlineBody" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompletionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/suggestion": { + "get": { + "tags": ["debug"], + "summary": "Suggestion", + "operationId": "suggestion_debug_suggestion_get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Traceback", + "type": "string" + }, + "name": "traceback", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompletionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/list": { + "post": { + "tags": ["debug"], + "summary": "Listten", + "operationId": "listten_debug_list_post", + "parameters": [ + { + "required": false, + "schema": { + "title": "X-Vsc-Machine-Id", + "type": "string", + "default": "anonymous" + }, + "name": "x-vsc-machine-id", + "in": "header" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SerializedDebugContext" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompletionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/explain": { + "post": { + "tags": ["debug"], + "summary": "Explain", + "operationId": "explain_debug_explain_post", + "parameters": [ + { + "required": false, + "schema": { + "title": "X-Vsc-Machine-Id", + "type": "string", + "default": "anonymous" + }, + "name": "x-vsc-machine-id", + "in": "header" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SerializedDebugContext" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExplainResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/edit": { + "post": { + "tags": ["debug"], + "summary": "Edit Endpoint", + "operationId": "edit_endpoint_debug_edit_post", + "parameters": [ + { + "required": false, + "schema": { + "title": "X-Vsc-Machine-Id", + "type": "string", + "default": "anonymous" + }, + "name": "x-vsc-machine-id", + "in": "header" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SerializedDebugContext" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditResp" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/find": { + "post": { + "tags": ["debug"], + "summary": "Find Sus Code Endpoint", + "operationId": "find_sus_code_endpoint_debug_find_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FindBody" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FindResp" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/parse_traceback": { + "get": { + "tags": ["debug"], + "summary": "Parse Traceback Endpoint", + "operationId": "parse_traceback_endpoint_debug_parse_traceback_get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Traceback", + "type": "string" + }, + "name": "traceback", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Traceback" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/find_docs": { + "get": { + "tags": ["debug"], + "summary": "Find Docs Endpoint", + "operationId": "find_docs_endpoint_debug_find_docs_get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Traceback", + "type": "string" + }, + "name": "traceback", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OptionalCompletionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/docstring/forline": { + "get": { + "tags": ["docstring"], + "summary": "Forline", + "description": "Write a docstring for a function at a line number", + "operationId": "forline_docstring_forline_get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Filecontents", + "type": "string" + }, + "name": "filecontents", + "in": "query" + }, + { + "required": true, + "schema": { + "title": "Lineno", + "type": "integer" + }, + "name": "lineno", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Format", + "type": "string", + "default": "google" + }, + "name": "format", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "X-Vsc-Machine-Id", + "type": "string", + "default": "anonymous" + }, + "name": "x-vsc-machine-id", + "in": "header" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/unittest/forline": { + "post": { + "tags": ["unittest"], + "summary": "Forline", + "description": "Write unit test for the function encapsulating the given line number.", + "operationId": "forline_unittest_forline_post", + "parameters": [ + { + "required": false, + "schema": { + "title": "X-Vsc-Machine-Id", + "type": "string", + "default": "anonymous" + }, + "name": "x-vsc-machine-id", + "in": "header" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FilePosition" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompletionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/unittest/failingtest": { + "post": { + "tags": ["unittest"], + "summary": "Failingtest", + "description": "Write a failing test for the function encapsulating the given line number.", + "operationId": "failingtest_unittest_failingtest_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailingTestBody" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompletionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/chat/test": { + "get": { + "tags": ["chat"], + "summary": "Test", + "operationId": "test_chat_test_get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Prompt", + "type": "string" + }, + "name": "prompt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/chat/complete": { + "post": { + "tags": ["chat"], + "summary": "Complete", + "operationId": "complete_chat_complete_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatHistory" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/test": { + "get": { + "summary": "Test", + "operationId": "test_test_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ChatHistory": { + "title": "ChatHistory", + "required": ["messages"], + "type": "object", + "properties": { + "messages": { + "title": "Messages", + "type": "array", + "items": { + "$ref": "#/components/schemas/ChatMessage" + } + } + } + }, + "ChatMessage": { + "title": "ChatMessage", + "required": ["role", "content"], + "type": "object", + "properties": { + "role": { + "title": "Role", + "type": "string" + }, + "content": { + "title": "Content", + "type": "string" + } + } + }, + "CompletionResponse": { + "title": "CompletionResponse", + "required": ["completion"], + "type": "object", + "properties": { + "completion": { + "title": "Completion", + "type": "string" + } + } + }, + "EditResp": { + "title": "EditResp", + "required": ["completion"], + "type": "object", + "properties": { + "completion": { + "title": "Completion", + "type": "array", + "items": { + "$ref": "#/components/schemas/FileEdit" + } + } + } + }, + "ExplainResponse": { + "title": "ExplainResponse", + "required": ["completion"], + "type": "object", + "properties": { + "completion": { + "title": "Completion", + "type": "string" + } + } + }, + "FailingTestBody": { + "title": "FailingTestBody", + "required": ["description", "fp"], + "type": "object", + "properties": { + "description": { + "title": "Description", + "type": "string" + }, + "fp": { + "$ref": "#/components/schemas/FilePosition" + } + }, + "description": "A failing test body." + }, + "FileEdit": { + "title": "FileEdit", + "required": ["filepath", "range", "replacement"], + "type": "object", + "properties": { + "filepath": { + "title": "Filepath", + "type": "string" + }, + "range": { + "$ref": "#/components/schemas/Range" + }, + "replacement": { + "title": "Replacement", + "type": "string" + } + }, + "additionalProperties": false + }, + "FilePosition": { + "title": "FilePosition", + "required": ["filecontents", "lineno"], + "type": "object", + "properties": { + "filecontents": { + "title": "Filecontents", + "type": "string" + }, + "lineno": { + "title": "Lineno", + "type": "integer" + } + }, + "description": "A position in a file." + }, + "FindBody": { + "title": "FindBody", + "required": ["traceback", "filesystem"], + "type": "object", + "properties": { + "traceback": { + "title": "Traceback", + "type": "string" + }, + "filesystem": { + "title": "Filesystem", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "description": { + "title": "Description", + "type": "string" + } + } + }, + "FindResp": { + "title": "FindResp", + "required": ["response"], + "type": "object", + "properties": { + "response": { + "title": "Response", + "type": "array", + "items": { + "$ref": "#/components/schemas/RangeInFile" + } + } + } + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + }, + "InlineBody": { + "title": "InlineBody", + "required": ["filecontents", "startline", "endline"], + "type": "object", + "properties": { + "filecontents": { + "title": "Filecontents", + "type": "string" + }, + "startline": { + "title": "Startline", + "type": "integer" + }, + "endline": { + "title": "Endline", + "type": "integer" + }, + "traceback": { + "title": "Traceback", + "type": "string", + "default": "" + } + } + }, + "OptionalCompletionResponse": { + "title": "OptionalCompletionResponse", + "type": "object", + "properties": { + "completion": { + "title": "Completion", + "type": "string" + } + } + }, + "Position": { + "title": "Position", + "required": ["line", "character"], + "type": "object", + "properties": { + "line": { + "title": "Line", + "type": "integer" + }, + "character": { + "title": "Character", + "type": "integer" + } + }, + "additionalProperties": false + }, + "ProgrammingLangauge": { + "title": "ProgrammingLangauge", + "enum": ["python", "javascript", "typescript"], + "type": "string", + "description": "An enumeration." + }, + "Range": { + "title": "Range", + "required": ["start", "end"], + "type": "object", + "properties": { + "start": { + "$ref": "#/components/schemas/Position" + }, + "end": { + "$ref": "#/components/schemas/Position" + } + }, + "additionalProperties": false, + "description": "A range in a file. 0-indexed." + }, + "RangeInFile": { + "title": "RangeInFile", + "required": ["filepath", "range"], + "type": "object", + "properties": { + "filepath": { + "title": "Filepath", + "type": "string" + }, + "range": { + "$ref": "#/components/schemas/Range" + } + }, + "additionalProperties": false + }, + "SerializedDebugContext": { + "title": "SerializedDebugContext", + "required": ["ranges_in_files", "filesystem"], + "type": "object", + "properties": { + "traceback": { + "title": "Traceback", + "type": "string" + }, + "ranges_in_files": { + "title": "Ranges In Files", + "type": "array", + "items": { + "$ref": "#/components/schemas/RangeInFile" + } + }, + "filesystem": { + "title": "Filesystem", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "description": { + "title": "Description", + "type": "string" + } + } + }, + "Traceback": { + "title": "Traceback", + "required": ["frames", "message", "error_type", "language"], + "type": "object", + "properties": { + "frames": { + "title": "Frames", + "type": "array", + "items": { + "$ref": "#/components/schemas/TracebackFrame" + } + }, + "message": { + "title": "Message", + "type": "string" + }, + "error_type": { + "title": "Error Type", + "type": "string" + }, + "language": { + "$ref": "#/components/schemas/ProgrammingLangauge" + }, + "full_traceback": { + "title": "Full Traceback", + "type": "string" + } + }, + "additionalProperties": false + }, + "TracebackFrame": { + "title": "TracebackFrame", + "required": ["filepath", "lineno", "function"], + "type": "object", + "properties": { + "filepath": { + "title": "Filepath", + "type": "string" + }, + "lineno": { + "title": "Lineno", + "type": "integer" + }, + "function": { + "title": "Function", + "type": "string" + }, + "code": { + "title": "Code", + "type": "string" + } + }, + "additionalProperties": false + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + } + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + } + } + } + } +} -- cgit v1.2.3-70-g09d2 From 36522ad6551bfcc51b1e923e7d50dfca1af1c697 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 27 May 2023 18:35:21 -0400 Subject: esbuild and preparing for publishing --- extension/.vscodeignore | 1 + extension/LICENSE.txt | 18 +- extension/README.md | 33 +- extension/media/continue-gradient.png | Bin 0 -> 13218 bytes extension/package-lock.json | 578 +++++++++++++++++++++++++++++++++- extension/package.json | 36 +-- 6 files changed, 614 insertions(+), 52 deletions(-) create mode 100644 extension/media/continue-gradient.png diff --git a/extension/.vscodeignore b/extension/.vscodeignore index 8fddc39a..f65d24a9 100644 --- a/extension/.vscodeignore +++ b/extension/.vscodeignore @@ -1,5 +1,6 @@ .vscode/** .vscode-test/** +build/** src/** .gitignore .yarnrc diff --git a/extension/LICENSE.txt b/extension/LICENSE.txt index eddcb4e6..a12f2a75 100644 --- a/extension/LICENSE.txt +++ b/extension/LICENSE.txt @@ -1,5 +1,13 @@ -/******************************************************* - * Copyright (C) 2023 Continue - * - * All rights reserved. - *******************************************************/ \ No newline at end of file +Copyright 2023 Continue + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/extension/README.md b/extension/README.md index 0a003145..55cf25d1 100644 --- a/extension/README.md +++ b/extension/README.md @@ -1,36 +1,17 @@ -# continue +# Continue -Bug report to fix in 1/10th the time. +Automate software development at the level of tasks, instead of tab-autocompletions. With Continue, you can make large edits with natural language, ask questions of your codebase, and run custom recipes built with our open-source Python library. -Our debugging assistant automatically handles tedious portions of the debugging process, such as: - -- Fault localization -- Enumerating potential error sources -- Generating fixes -- Pulling in outside context -- Following data flows -- Parsing tracebacks -- Generate unit tests -- Generate docstrings +Get started by opening the command pallet with `cmd+shift+p` and then selecting `Continue: Open Debug Panel`. # Features ### Ask a Question -`cmd+shift+j` to open up a universal search bar, where you can ask questions of your codebase in natural language. - -### Fault Localization - -Either manually highlight code snippets you think are suspicious, or let us find them for you. - -### Generate a Fix - -Once Continue has code snippets to work with, it can generate a fix. Just click to accept or reject, or make the tweak you need. - -### Stacktrace Parsing +Ask natural language questions of your codebase, like "Where is the entry point to the VS Code extension?" -Any stacktrace that appears in your VSCode terminal will be caught by us so we can immediately begin the debugging process. For small bugs that you might have quickly solved, we'll just speed up the process to be nearly instantaneous. +### Edit with Natural Language -### Generate Unit Tests and Docstrings +Request an edit to the currently open file, for example: "Add CORS headers to this FastAPI server". -Use `cmd+shift+i` to generate a unit test for the function surrounding your cursor position, and `cmd+shift+l` to generate a docstring. +### Generate Unit Tests diff --git a/extension/media/continue-gradient.png b/extension/media/continue-gradient.png new file mode 100644 index 00000000..2b382040 Binary files /dev/null and b/extension/media/continue-gradient.png differ diff --git a/extension/package-lock.json b/extension/package-lock.json index c8907210..a90827d8 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "@electron/rebuild": "^3.2.10", "@reduxjs/toolkit": "^1.9.3", @@ -30,6 +30,7 @@ "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "@vscode/test-electron": "^2.2.0", + "esbuild": "^0.17.19", "eslint": "^8.28.0", "glob": "^8.0.3", "json-schema-to-typescript": "^12.0.0", @@ -451,6 +452,358 @@ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", "peer": true }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", @@ -3217,6 +3570,43 @@ "es6-symbol": "^3.1.1" } }, + "node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -7734,6 +8124,160 @@ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", "peer": true }, + "@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "dev": true, + "optional": true + }, "@eslint/eslintrc": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", @@ -9817,6 +10361,36 @@ "es6-symbol": "^3.1.1" } }, + "esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", diff --git a/extension/package.json b/extension/package.json index 3f9e59ff..525ec075 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,12 +1,20 @@ { "name": "continue", + "icon": "media/continue-gradient.png", "repository": { "type": "git", - "url": "" + "url": "https://github.com/continuedev/continue" }, + "bugs": { + "url": "https://github.com/continuedev/continue/issues", + "email": "nate@continue.dev" + }, + "homepage": "https://continue.dev", + "license": "Apache-2.0", "displayName": "Continue", - "description": "Reduce debugging time by 10x", - "version": "0.0.1", + "pricing": "Free", + "description": "Refine code 10x faster", + "version": "0.0.2", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -107,21 +115,6 @@ } ], "keybindings": [ - { - "command": "continue.writeDocstring", - "key": "ctrl+alt+l", - "mac": "shift+cmd+l" - }, - { - "command": "continue.writeUnitTest", - "key": "ctrl+alt+i", - "mac": "shift+cmd+i" - }, - { - "command": "continue.askQuestionFromInput", - "key": "ctrl+alt+j", - "mac": "shift+cmd+j" - }, { "command": "continue.suggestionDown", "mac": "shift+ctrl+down", @@ -154,9 +147,13 @@ } }, "scripts": { + "vscode:prepublish": "npm run esbuild-base -- --minify", + "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node", + "esbuild": "npm run esbuild-base -- --sourcemap", + "esbuild-watch": "npm run esbuild-base -- --sourcemap --watch", + "test-compile": "tsc -p ./", "clientgen": "rm -rf src/client/ && npx @openapitools/openapi-generator-cli generate -i ../schema/openapi.json -g typescript-fetch -o src/client/ --additional-properties=supportsES6=true,npmVersion=8.19.2,typescriptThreePlus=true", "typegen": "node scripts/typegen.js", - "vscode:prepublish": "npm run compile", "rebuild": "electron-rebuild -v 19.1.8 node-pty", "compile": "tsc -p ./", "watch": "tsc -watch -p ./", @@ -179,6 +176,7 @@ "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "@vscode/test-electron": "^2.2.0", + "esbuild": "^0.17.19", "eslint": "^8.28.0", "glob": "^8.0.3", "json-schema-to-typescript": "^12.0.0", -- cgit v1.2.3-70-g09d2 From d3db75ee7e2483f86ba5eb8856dd59aaadfae285 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 27 May 2023 23:33:47 -0400 Subject: posthog setup in react app --- extension/react-app/package-lock.json | 39 ++++++++++++++++++++++++++++ extension/react-app/package.json | 1 + extension/react-app/src/main.tsx | 25 ++++++++++++------ extension/src/activation/environmentSetup.ts | 4 +-- 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json index 1ba8cfe8..dbcbc5cc 100644 --- a/extension/react-app/package-lock.json +++ b/extension/react-app/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@styled-icons/heroicons-outline": "^10.47.0", "@types/vscode-webview": "^1.57.1", + "posthog-js": "^1.58.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.5", @@ -1506,6 +1507,11 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2528,6 +2534,15 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/posthog-js": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.58.0.tgz", + "integrity": "sha512-PpH/MwjwV6UHDsv78xFvteEWYgY3O/HTKBnotzmkNCDWgsKzNr978B1AKzgtBU2GzBsnwUfuK+u2O6mxRzFSWw==", + "dependencies": { + "fflate": "^0.4.1", + "rrweb-snapshot": "^1.1.14" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -2778,6 +2793,11 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrweb-snapshot": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-1.1.14.tgz", + "integrity": "sha512-eP5pirNjP5+GewQfcOQY4uBiDnpqxNRc65yKPW0eSoU1XamDfc4M8oqpXGMyUyvLyxFDB0q0+DChuxxiU2FXBQ==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4196,6 +4216,11 @@ "reusify": "^1.0.4" } }, + "fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4803,6 +4828,15 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "posthog-js": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.58.0.tgz", + "integrity": "sha512-PpH/MwjwV6UHDsv78xFvteEWYgY3O/HTKBnotzmkNCDWgsKzNr978B1AKzgtBU2GzBsnwUfuK+u2O6mxRzFSWw==", + "requires": { + "fflate": "^0.4.1", + "rrweb-snapshot": "^1.1.14" + } + }, "prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -4964,6 +4998,11 @@ "fsevents": "~2.3.2" } }, + "rrweb-snapshot": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-1.1.14.tgz", + "integrity": "sha512-eP5pirNjP5+GewQfcOQY4uBiDnpqxNRc65yKPW0eSoU1XamDfc4M8oqpXGMyUyvLyxFDB0q0+DChuxxiU2FXBQ==" + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/extension/react-app/package.json b/extension/react-app/package.json index 3993b030..7d1211de 100644 --- a/extension/react-app/package.json +++ b/extension/react-app/package.json @@ -11,6 +11,7 @@ "dependencies": { "@styled-icons/heroicons-outline": "^10.47.0", "@types/vscode-webview": "^1.57.1", + "posthog-js": "^1.58.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.5", diff --git a/extension/react-app/src/main.tsx b/extension/react-app/src/main.tsx index 791f139e..1b94dc82 100644 --- a/extension/react-app/src/main.tsx +++ b/extension/react-app/src/main.tsx @@ -1,10 +1,19 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( +import posthog from "posthog-js"; +import { PostHogProvider } from "posthog-js/react"; + +posthog.init("phc_JS6XFROuNbhJtVCEdTSYk6gl5ArRrTNMpCcguAXlSPs", { + api_host: "https://app.posthog.com", +}); + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - , -) + + + + +); diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 4816b4b1..1e921c18 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -59,7 +59,7 @@ async function setupPythonEnv() { } let pipCmd = pythonCmd.endsWith("3") ? "pip3" : "pip"; - let activateCmd = "source env/bin/activate"; + let activateCmd = ". env/bin/activate"; let pipUpgradeCmd = `${pipCmd} install --upgrade pip`; if (process.platform == "win32") { activateCmd = ".\\env\\Scripts\\activate"; @@ -147,7 +147,7 @@ export async function startContinuePythonServer() { console.log("Error checking for existing server", e); } - let activateCmd = "source env/bin/activate"; + let activateCmd = ". env/bin/activate"; let pythonCmd = "python3"; if (process.platform == "win32") { activateCmd = ".\\env\\Scripts\\activate"; -- cgit v1.2.3-70-g09d2 From 33027dab0031b4f499dd200737f314dac143ef4f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 00:03:48 -0400 Subject: ContinueStepStep --- continuedev/src/continuedev/core/policy.py | 4 +++ .../src/continuedev/libs/steps/continue_step.py | 37 ++++++++++++++++++++++ .../src/continuedev/libs/steps/react_posthog.py | 0 3 files changed, 41 insertions(+) create mode 100644 continuedev/src/continuedev/libs/steps/continue_step.py create mode 100644 continuedev/src/continuedev/libs/steps/react_posthog.py diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 9e6abf14..5a6652f4 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -1,5 +1,6 @@ from typing import List, Tuple, Type + from ..models.main import ContinueBaseModel from ..libs.steps.ty import CreatePipelineStep @@ -8,6 +9,7 @@ from .observation import Observation, TracebackObservation, UserInputObservation from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep from ..libs.steps.nate import WritePytestsStep, CreateTableStep from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma +from ..libs.steps.continue_step import ContinueStepStep class DemoPolicy(Policy): @@ -28,6 +30,8 @@ class DemoPolicy(Policy): return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) elif "/edit" in observation.user_input: return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) + elif "/step" in observation.user_input: + return ContinueStepStep(prompt=" ".join(observation.user_input.split(" ")[1:])) return EditHighlightedCodeStep(user_input=observation.user_input) state = history.get_current() diff --git a/continuedev/src/continuedev/libs/steps/continue_step.py b/continuedev/src/continuedev/libs/steps/continue_step.py new file mode 100644 index 00000000..253bb490 --- /dev/null +++ b/continuedev/src/continuedev/libs/steps/continue_step.py @@ -0,0 +1,37 @@ +from textwrap import dedent +from ...models.filesystem import RangeInFile +from .main import EditHighlightedCodeStep +from ...core.main import Step +from ...core.sdk import ContinueSDK + + +class ContinueStepStep(Step): + name: str = "Write your own Continue Step." + prompt: str + + async def run(self, sdk: ContinueSDK): + await sdk.run_step(EditHighlightedCodeStep(user_input=dedent(f"""\ + Here is an example of a Step that runs a command and then edits a file. + + ```python + from ...core.main import Step + from ...core.sdk import ContinueSDK + + class RunCommandAndEditFileStep(Step): + name: str = "Run a command and then edit a file." + command: str + file_path: str + prompt: str + + async def run(self, sdk: ContinueSDK): + await sdk.run([command]) + await sdk.edit_file(filename=self.file_path, prompt=self.prompt) + ``` + + Please edit the code to write your own Step that does the following: + + {self.prommpt} + + It should be a subclass of Step as above, implementing the `run` method, and using pydantic attributes to define the parameters. + + """))) diff --git a/continuedev/src/continuedev/libs/steps/react_posthog.py b/continuedev/src/continuedev/libs/steps/react_posthog.py new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3-70-g09d2 From 056497801039c1b8f5d4600531f5c90dbd375a38 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 00:40:10 -0400 Subject: READMEs --- README.md | 7 +------ continuedev/README.md | 8 ++++++++ extension/DEV_README.md | 7 +++++++ extension/examples/README.md | 3 +++ extension/react-app/README.md | 3 +++ extension/schema/README.md | 3 +++ 6 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 extension/DEV_README.md create mode 100644 extension/examples/README.md create mode 100644 extension/react-app/README.md create mode 100644 extension/schema/README.md diff --git a/README.md b/README.md index 18525fcd..dc1ad7de 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,6 @@ # Continue -Try out latest version: - -- `cd extension` -- `npm run package` -- `cd build` -- `code --install-extension continue-0.0.1.vsix` +[Download for VS Code](https://marketplace.visualstudio.com/items?itemName=Continue.continue) Resources diff --git a/continuedev/README.md b/continuedev/README.md index 5af08e24..5658e8de 100644 --- a/continuedev/README.md +++ b/continuedev/README.md @@ -1,3 +1,7 @@ +# Continue + +Continue is a Python library for automating repetitive sequences of software development tasks using language models. Using our VS Code extension, you can build, run, and refine these recipes as they natively interact with your codebase. Download on [our GitHub](https://github.com/continuedev/continue). + ## Steps to start - `cd continue/continue` @@ -17,3 +21,7 @@ Same steps, then `uvicorn continue.src.server.main:app --reload`. ## To build Run `poetry build` and it will output wheel and tarball files in `./dist`. + +## Writing Steps + +See the `src/continuedev/libs/steps` folder for examples of writing a Continue step. diff --git a/extension/DEV_README.md b/extension/DEV_README.md new file mode 100644 index 00000000..7049da45 --- /dev/null +++ b/extension/DEV_README.md @@ -0,0 +1,7 @@ +# Continue VS Code Extension + +This is the Continue VS Code Extension. Its primary jobs are + +1. Implement the IDE side of the Continue IDE protocol, allowing a Continue server to interact natively in an IDE. This happens in `src/continueIdeClient.ts`. +2. Open the Continue React app in a side panel. The React app's source code lives in the `react-app` directory. The panel is opened by the `continue.openDebugPanel` command, as defined in `src/commands.ts`. +3. Run a Continue server in the background, which connects to both the IDE protocol and the React app. The server is launched in `src/activation/environmentSetup.ts` by calling Python code that lives in `scripts/` (unless extension settings define a server URL other than localhost:8000, in which case the extension will just connect to that). diff --git a/extension/examples/README.md b/extension/examples/README.md new file mode 100644 index 00000000..c95b9220 --- /dev/null +++ b/extension/examples/README.md @@ -0,0 +1,3 @@ +# Examples + +This folder contains example code used in Continue demos. diff --git a/extension/react-app/README.md b/extension/react-app/README.md new file mode 100644 index 00000000..006b6b11 --- /dev/null +++ b/extension/react-app/README.md @@ -0,0 +1,3 @@ +# Continue React App + +The Continue React app is a notebook-like interface to the Continue server. It allows the user to submit arbitrary text input, then communicates with the server to takes steps, which are displayed as a sequence of editable cells. The React app should sit beside an IDE, as in the VS Code extension. diff --git a/extension/schema/README.md b/extension/schema/README.md new file mode 100644 index 00000000..9c97c0eb --- /dev/null +++ b/extension/schema/README.md @@ -0,0 +1,3 @@ +# Schema + +These are files autogenerated by `npm run typegen`. They come originally from the `continuedev` Python package's Pydantic types. -- cgit v1.2.3-70-g09d2 From 306ab404ce687db5a67762d63b159118ab592837 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 12:05:32 -0400 Subject: bug fixes, build script, sdk methods --- continuedev/pyproject.toml | 3 ++ continuedev/src/continuedev/core/agent.py | 1 - continuedev/src/continuedev/core/sdk.py | 32 ++++++++++++--- continuedev/src/continuedev/libs/llm/openai.py | 10 +++-- .../src/continuedev/models/generate_json_schema.py | 8 +++- extension/package-lock.json | 4 +- extension/package.json | 6 +-- .../scripts/continuedev-0.1.0-py3-none-any.whl | Bin 51468 -> 53104 bytes extension/scripts/install_from_source.py | 45 +++++++++++++++++++++ schema/json/FileEdit.json | 4 +- schema/json/FileEditWithFullContents.json | 4 +- schema/json/History.json | 4 +- schema/json/HistoryNode.json | 4 +- schema/json/Position.json | 4 +- schema/json/Range.json | 4 +- schema/json/RangeInFile.json | 4 +- schema/json/Traceback.json | 4 +- schema/json/TracebackFrame.json | 4 +- 18 files changed, 109 insertions(+), 36 deletions(-) create mode 100644 extension/scripts/install_from_source.py diff --git a/continuedev/pyproject.toml b/continuedev/pyproject.toml index 5c224c9c..8ed8f268 100644 --- a/continuedev/pyproject.toml +++ b/continuedev/pyproject.toml @@ -20,6 +20,9 @@ urllib3 = "1.26.15" gpt-index = "^0.6.8" setuptools = "^67.7.2" +[tool.poetry.scripts] +typegen = "src.continuedev.models.generate_json_schema:main" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 509a54b3..6d1f542e 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -17,7 +17,6 @@ class Agent(ContinueBaseModel): policy: Policy ide: AbstractIdeProtocolServer history: History = History.from_empty() - continue_sdk: "ContinueSDK" _on_update_callbacks: List[Callable[[FullState], None]] = [] _active: bool = False diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index ff62a2b1..3559e9d7 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -1,5 +1,6 @@ +import os from typing import Coroutine, Union -from ..models.filesystem_edit import FileSystemEdit +from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory from ..models.filesystem import RangeInFile from ..libs.llm import LLM from .observation import Observation @@ -37,11 +38,16 @@ class ContinueSDK: def history(self) -> History: return self.__agent.history + async def _ensure_absolute_path(self, path: str) -> str: + if os.path.isabs(path): + return path + return os.path.join(await self.ide.getWorkspaceDirectory(), path) + async def run_step(self, step: Step) -> Coroutine[Observation, None, None]: return await self.__agent._run_singular_step(step) async def apply_filesystem_edit(self, edit: FileSystemEdit): - await self.run_step(FileSystemEditStep(edit=edit)) + return await self.run_step(FileSystemEditStep(edit=edit)) async def wait_for_user_input(self) -> str: return await self.__agent.wait_for_user_input() @@ -51,12 +57,26 @@ class ContinueSDK: async def run(self, commands: List[str] | str, cwd: str = None): commands = commands if isinstance(commands, List) else [commands] - return self.run_step(ShellCommandsStep(commands=commands, cwd=cwd)) + return await self.run_step(ShellCommandsStep(commands=commands, cwd=cwd)) async def edit_file(self, filename: str, prompt: str): - await self.ide.setFileOpen(filename) - contents = await self.ide.readFile(filename) + filepath = await self._ensure_absolute_path(filename) + + await self.ide.setFileOpen(filepath) + contents = await self.ide.readFile(filepath) await self.run_step(EditCodeStep( - range_in_files=[RangeInFile.from_entire_file(filename, contents)], + range_in_files=[RangeInFile.from_entire_file(filepath, contents)], prompt=f'Here is the code before:\n\n{{code}}\n\nHere is the user request:\n\n{prompt}\n\nHere is the code edited to perfectly solve the user request:\n\n' )) + + async def add_file(self, filename: str, content: str | None): + return await self.run_step(FileSystemEditStep(edit=AddFile(filename=filename, content=content))) + + async def delete_file(self, filename: str): + return await self.run_step(FileSystemEditStep(edit=DeleteFile(filepath=filename))) + + async def add_directory(self, path: str): + return await self.run_step(FileSystemEditStep(edit=AddDirectory(path=path))) + + async def delete_directory(self, path: str): + return await self.run_step(FileSystemEditStep(edit=DeleteDirectory(path=path))) diff --git a/continuedev/src/continuedev/libs/llm/openai.py b/continuedev/src/continuedev/libs/llm/openai.py index bb745e75..10801465 100644 --- a/continuedev/src/continuedev/libs/llm/openai.py +++ b/continuedev/src/continuedev/libs/llm/openai.py @@ -6,6 +6,8 @@ import aiohttp from ..llm import LLM from pydantic import BaseModel, validator +DEFAULT_MAX_TOKENS = 2048 + class OpenAI(LLM): api_key: str @@ -22,7 +24,7 @@ class OpenAI(LLM): def stream_chat(self, messages, **kwargs) -> Generator[Union[Any, List, Dict], None, None]: self.completion_count += 1 - args = {"max_tokens": 512, "temperature": 0.5, "top_p": 1, + args = {"max_tokens": DEFAULT_MAX_TOKENS, "temperature": 0.5, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0} | kwargs args["stream"] = True args["model"] = "gpt-3.5-turbo" @@ -38,7 +40,7 @@ class OpenAI(LLM): def stream_complete(self, prompt: str, **kwargs) -> Generator[Union[Any, List, Dict], None, None]: self.completion_count += 1 - args = {"model": self.default_model, "max_tokens": 512, "temperature": 0.5, + args = {"model": self.default_model, "max_tokens": DEFAULT_MAX_TOKENS, "temperature": 0.5, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0, "suffix": None} | kwargs args["stream"] = True @@ -64,7 +66,7 @@ class OpenAI(LLM): t1 = time.time() self.completion_count += 1 - args = {"model": self.default_model, "max_tokens": 512, "temperature": 0.5, "top_p": 1, + args = {"model": self.default_model, "max_tokens": DEFAULT_MAX_TOKENS, "temperature": 0.5, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0, "stream": False} | kwargs if args["model"] == "gpt-3.5-turbo": @@ -132,7 +134,7 @@ class OpenAI(LLM): def parallel_complete(self, prompts: list[str], suffixes: Union[list[str], None] = None, **kwargs) -> list[str]: self.completion_count += len(prompts) - args = {"model": self.default_model, "max_tokens": 512, "temperature": 0.5, + args = {"model": self.default_model, "max_tokens": DEFAULT_MAX_TOKENS, "temperature": 0.5, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0} | kwargs async def fn(): diff --git a/continuedev/src/continuedev/models/generate_json_schema.py b/continuedev/src/continuedev/models/generate_json_schema.py index 07337029..080787a5 100644 --- a/continuedev/src/continuedev/models/generate_json_schema.py +++ b/continuedev/src/continuedev/models/generate_json_schema.py @@ -19,7 +19,7 @@ RENAMES = { "ExampleClass": "RenamedName" } -SCHEMA_DIR = "schema/json" +SCHEMA_DIR = "../schema/json" def clear_schemas(): @@ -28,7 +28,7 @@ def clear_schemas(): os.remove(os.path.join(SCHEMA_DIR, filename)) -if __name__ == "__main__": +def main(): clear_schemas() for model in MODELS_TO_GENERATE: title = RENAMES.get(model.__name__, model.__name__) @@ -40,3 +40,7 @@ if __name__ == "__main__": with open(f"{SCHEMA_DIR}/{title}.json", "w") as f: f.write(json) + + +if __name__ == "__main__": + main() diff --git a/extension/package-lock.json b/extension/package-lock.json index a90827d8..faa07b57 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.2", + "version": "0.0.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.2", + "version": "0.0.3", "dependencies": { "@electron/rebuild": "^3.2.10", "@reduxjs/toolkit": "^1.9.3", diff --git a/extension/package.json b/extension/package.json index 525ec075..40d56fb7 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.2", + "version": "0.0.3", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -23,7 +23,7 @@ "Other" ], "activationEvents": [ - "*" + "onStartupFinished" ], "main": "./out/extension.js", "contributes": { @@ -160,7 +160,7 @@ "pretest": "npm run compile && npm run lint", "lint": "eslint src --ext ts", "test": "node ./out/test/runTest.js", - "package": "cp ./config/prod_config.json ./config/config.json && npm run compile && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.1.vsix && cp ./config/dev_config.json ./config/config.json", + "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.2.vsix && cp ./config/dev_config.json ./config/config.json", "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.0-py3-none-any.whl ../extension/scripts/continuedev-0.1.0-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", "install-extension": "code --install-extension ./build/continue-0.0.1.vsix", "uninstall": "code --uninstall-extension .continue", diff --git a/extension/scripts/continuedev-0.1.0-py3-none-any.whl b/extension/scripts/continuedev-0.1.0-py3-none-any.whl index 68457b3e..d1483db9 100644 Binary files a/extension/scripts/continuedev-0.1.0-py3-none-any.whl and b/extension/scripts/continuedev-0.1.0-py3-none-any.whl differ diff --git a/extension/scripts/install_from_source.py b/extension/scripts/install_from_source.py new file mode 100644 index 00000000..4fe903ed --- /dev/null +++ b/extension/scripts/install_from_source.py @@ -0,0 +1,45 @@ +import subprocess + + +def run(cmd: str): + return subprocess.run(cmd, shell=True, capture_output=True) + + +def main(): + # Check for Python and Node - we won't install them, but will warn + out, err1 = run("python --version") + out, err2 = run("python3 --version") + if err1 and err2: + print("Python is required for Continue but is not installed on your machine. See https://www.python.org/downloads/ to download the latest version, then try again.") + return + + out, err = run("node --version") + if err: + print("Node is required for Continue but is not installed on your machine. See https://nodejs.org/en/download/ to download the latest version, then try again.") + return + + out, err = run("npm --version") + if err: + print("NPM is required for Continue but is not installed on your machine. See https://nodejs.org/en/download/ to download the latest version, then try again.") + return + + out, err = run("poetry --version") + if err: + print("Poetry is required for Continue but is not installed on your machine. See https://python-poetry.org/docs/#installation to download the latest version, then try again.") + return + + out, err = run("cd ../../continuedev; poetry run typegen") + + out, err = run( + "cd ..; npm i; cd react-app; npm i; cd ..; npm run full-package\r y\r npm run install-extension") + + if err: + print("Error installing the extension. Please try again.") + print("This was the error: ", err) + return + + print("Continue VS Code extension installed successfully. Please restart VS Code to use it.") + + +if __name__ == "__main__": + main() diff --git a/schema/json/FileEdit.json b/schema/json/FileEdit.json index 1f7dcb64..011e0462 100644 --- a/schema/json/FileEdit.json +++ b/schema/json/FileEdit.json @@ -1,6 +1,6 @@ { "title": "FileEdit", - "$ref": "#/definitions/continuedev__src__continuedev__models__filesystem_edit__FileEdit", + "$ref": "#/definitions/src__continuedev__models__filesystem_edit__FileEdit", "definitions": { "Position": { "title": "Position", @@ -37,7 +37,7 @@ "end" ] }, - "continuedev__src__continuedev__models__filesystem_edit__FileEdit": { + "src__continuedev__models__filesystem_edit__FileEdit": { "title": "FileEdit", "type": "object", "properties": { diff --git a/schema/json/FileEditWithFullContents.json b/schema/json/FileEditWithFullContents.json index 571ea1d3..2ea75bab 100644 --- a/schema/json/FileEditWithFullContents.json +++ b/schema/json/FileEditWithFullContents.json @@ -1,6 +1,6 @@ { "title": "FileEditWithFullContents", - "$ref": "#/definitions/continuedev__src__continuedev__models__filesystem_edit__FileEditWithFullContents", + "$ref": "#/definitions/src__continuedev__models__filesystem_edit__FileEditWithFullContents", "definitions": { "Position": { "title": "Position", @@ -59,7 +59,7 @@ "replacement" ] }, - "continuedev__src__continuedev__models__filesystem_edit__FileEditWithFullContents": { + "src__continuedev__models__filesystem_edit__FileEditWithFullContents": { "title": "FileEditWithFullContents", "type": "object", "properties": { diff --git a/schema/json/History.json b/schema/json/History.json index 7691c7dd..14b82d6d 100644 --- a/schema/json/History.json +++ b/schema/json/History.json @@ -1,6 +1,6 @@ { "title": "History", - "$ref": "#/definitions/continuedev__src__continuedev__core__main__History", + "$ref": "#/definitions/src__continuedev__core__main__History", "definitions": { "Step": { "title": "Step", @@ -47,7 +47,7 @@ "depth" ] }, - "continuedev__src__continuedev__core__main__History": { + "src__continuedev__core__main__History": { "title": "History", "description": "A history of steps taken and their results", "type": "object", diff --git a/schema/json/HistoryNode.json b/schema/json/HistoryNode.json index f58b8038..87a8729f 100644 --- a/schema/json/HistoryNode.json +++ b/schema/json/HistoryNode.json @@ -1,6 +1,6 @@ { "title": "HistoryNode", - "$ref": "#/definitions/continuedev__src__continuedev__core__main__HistoryNode", + "$ref": "#/definitions/src__continuedev__core__main__HistoryNode", "definitions": { "Step": { "title": "Step", @@ -26,7 +26,7 @@ "type": "object", "properties": {} }, - "continuedev__src__continuedev__core__main__HistoryNode": { + "src__continuedev__core__main__HistoryNode": { "title": "HistoryNode", "description": "A point in history, a list of which make up History", "type": "object", diff --git a/schema/json/Position.json b/schema/json/Position.json index e550572e..6b272ce7 100644 --- a/schema/json/Position.json +++ b/schema/json/Position.json @@ -1,8 +1,8 @@ { "title": "Position", - "$ref": "#/definitions/continuedev__src__continuedev__models__main__Position", + "$ref": "#/definitions/src__continuedev__models__main__Position", "definitions": { - "continuedev__src__continuedev__models__main__Position": { + "src__continuedev__models__main__Position": { "title": "Position", "type": "object", "properties": { diff --git a/schema/json/Range.json b/schema/json/Range.json index 52953837..75675183 100644 --- a/schema/json/Range.json +++ b/schema/json/Range.json @@ -1,6 +1,6 @@ { "title": "Range", - "$ref": "#/definitions/continuedev__src__continuedev__models__main__Range", + "$ref": "#/definitions/src__continuedev__models__main__Range", "definitions": { "Position": { "title": "Position", @@ -20,7 +20,7 @@ "character" ] }, - "continuedev__src__continuedev__models__main__Range": { + "src__continuedev__models__main__Range": { "title": "Range", "description": "A range in a file. 0-indexed.", "type": "object", diff --git a/schema/json/RangeInFile.json b/schema/json/RangeInFile.json index 022ca1a8..1f5afaa3 100644 --- a/schema/json/RangeInFile.json +++ b/schema/json/RangeInFile.json @@ -1,6 +1,6 @@ { "title": "RangeInFile", - "$ref": "#/definitions/continuedev__src__continuedev__models__filesystem__RangeInFile", + "$ref": "#/definitions/src__continuedev__models__filesystem__RangeInFile", "definitions": { "Position": { "title": "Position", @@ -37,7 +37,7 @@ "end" ] }, - "continuedev__src__continuedev__models__filesystem__RangeInFile": { + "src__continuedev__models__filesystem__RangeInFile": { "title": "RangeInFile", "type": "object", "properties": { diff --git a/schema/json/Traceback.json b/schema/json/Traceback.json index c3e0e416..45606a2b 100644 --- a/schema/json/Traceback.json +++ b/schema/json/Traceback.json @@ -1,6 +1,6 @@ { "title": "Traceback", - "$ref": "#/definitions/continuedev__src__continuedev__models__main__Traceback", + "$ref": "#/definitions/src__continuedev__models__main__Traceback", "definitions": { "TracebackFrame": { "title": "TracebackFrame", @@ -29,7 +29,7 @@ "function" ] }, - "continuedev__src__continuedev__models__main__Traceback": { + "src__continuedev__models__main__Traceback": { "title": "Traceback", "type": "object", "properties": { diff --git a/schema/json/TracebackFrame.json b/schema/json/TracebackFrame.json index 6321e08c..1907430a 100644 --- a/schema/json/TracebackFrame.json +++ b/schema/json/TracebackFrame.json @@ -1,8 +1,8 @@ { "title": "TracebackFrame", - "$ref": "#/definitions/continuedev__src__continuedev__models__main__TracebackFrame", + "$ref": "#/definitions/src__continuedev__models__main__TracebackFrame", "definitions": { - "continuedev__src__continuedev__models__main__TracebackFrame": { + "src__continuedev__models__main__TracebackFrame": { "title": "TracebackFrame", "type": "object", "properties": { -- cgit v1.2.3-70-g09d2 From 64643e87e9af8b98945614119179e45fc95281e4 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 15:11:46 -0400 Subject: First load fail error fixed --- extension/package-lock.json | 5 +-- extension/package.json | 4 +-- extension/scripts/install_from_source.py | 50 +++++++++++++++++++--------- extension/src/activation/environmentSetup.ts | 5 ++- extension/src/continueIdeClient.ts | 26 +++++++++------ 5 files changed, 56 insertions(+), 34 deletions(-) diff --git a/extension/package-lock.json b/extension/package-lock.json index faa07b57..822b908e 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,13 @@ { "name": "continue", - "version": "0.0.3", + "version": "0.0.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.3", + "version": "0.0.4", + "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", "@reduxjs/toolkit": "^1.9.3", diff --git a/extension/package.json b/extension/package.json index 40d56fb7..f5771ee6 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.3", + "version": "0.0.4", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -149,7 +149,7 @@ "scripts": { "vscode:prepublish": "npm run esbuild-base -- --minify", "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node", - "esbuild": "npm run esbuild-base -- --sourcemap", + "esbuild": "rm -rf ./out && npm run esbuild-base -- --sourcemap", "esbuild-watch": "npm run esbuild-base -- --sourcemap --watch", "test-compile": "tsc -p ./", "clientgen": "rm -rf src/client/ && npx @openapitools/openapi-generator-cli generate -i ../schema/openapi.json -g typescript-fetch -o src/client/ --additional-properties=supportsES6=true,npmVersion=8.19.2,typescriptThreePlus=true", diff --git a/extension/scripts/install_from_source.py b/extension/scripts/install_from_source.py index 4fe903ed..28f382b0 100644 --- a/extension/scripts/install_from_source.py +++ b/extension/scripts/install_from_source.py @@ -1,43 +1,61 @@ +import os import subprocess def run(cmd: str): - return subprocess.run(cmd, shell=True, capture_output=True) + return subprocess.run(cmd, shell=True, capture_output=False) def main(): # Check for Python and Node - we won't install them, but will warn - out, err1 = run("python --version") - out, err2 = run("python3 --version") - if err1 and err2: + resp1 = run("python --version") + resp2 = run("python3 --version") + if resp1.stderr and resp2.stderr: print("Python is required for Continue but is not installed on your machine. See https://www.python.org/downloads/ to download the latest version, then try again.") return - out, err = run("node --version") - if err: + resp = run("node --version") + if resp.stderr: print("Node is required for Continue but is not installed on your machine. See https://nodejs.org/en/download/ to download the latest version, then try again.") return - out, err = run("npm --version") - if err: + resp = run("npm --version") + if resp.stderr: print("NPM is required for Continue but is not installed on your machine. See https://nodejs.org/en/download/ to download the latest version, then try again.") return - out, err = run("poetry --version") - if err: + resp = run("poetry --version") + if resp.stderr: print("Poetry is required for Continue but is not installed on your machine. See https://python-poetry.org/docs/#installation to download the latest version, then try again.") return - out, err = run("cd ../../continuedev; poetry run typegen") + resp = run("cd ../../continuedev; poetry run typegen") - out, err = run( - "cd ..; npm i; cd react-app; npm i; cd ..; npm run full-package\r y\r npm run install-extension") + resp = run( + "cd ..; npm i; cd react-app; npm i; cd ..; npm run full-package") - if err: - print("Error installing the extension. Please try again.") - print("This was the error: ", err) + if resp.stderr: + print("Error packaging the extension. Please try again.") + print("This was the error: ", resp.stderr) return + latest = None + latest_major = 0 + latest_minor = 0 + latest_patch = 0 + for file in os.listdir("../build"): + if file.endswith(".vsix"): + version = file.split("-")[1].split(".vsix")[0] + major, minor, patch = list( + map(lambda x: int(x), version.split("."))) + if latest is None or (major >= latest_major and minor >= latest_minor and patch > latest_patch): + latest = file + latest_major = major + latest_minor = minor + latest_patch = patch + + resp = run(f"cd ..; code --install-extension ./build/{latest}") + print("Continue VS Code extension installed successfully. Please restart VS Code to use it.") diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 1e921c18..93a471ff 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -143,9 +143,7 @@ export async function startContinuePythonServer() { console.log("Continue python server already running"); return; } - } catch (e) { - console.log("Error checking for existing server", e); - } + } catch (e) {} let activateCmd = ". env/bin/activate"; let pythonCmd = "python3"; @@ -162,6 +160,7 @@ export async function startContinuePythonServer() { // exec(command); let child = spawn(command, { shell: true, + detached: true, }); child.stdout.on("data", (data: any) => { console.log(`stdout: ${data}`); diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 6c65415f..35eb668d 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -66,18 +66,22 @@ class IdeProtocolClient { } async isConnected() { - if (this._ws === null) { - this._ws = new WebSocket(this._serverUrl); - } - // On open, return a promise - if (this._ws!.readyState === WebSocket.OPEN) { - return; - } - return new Promise((resolve, reject) => { - this._ws!.onopen = () => { - resolve(null); + if (this._ws === null || this._ws.readyState !== WebSocket.OPEN) { + let ws = new WebSocket(this._serverUrl); + ws.onclose = () => { + this._ws = null; }; - }); + ws.on("message", (data: any) => { + this.handleMessage(JSON.parse(data)); + }); + this._ws = ws; + + return new Promise((resolve, reject) => { + ws.addEventListener("open", () => { + resolve(null); + }); + }); + } } async startCore() { -- cgit v1.2.3-70-g09d2 From e02da4c8fada20b7e6bdd80d257a868bbd6b0d0f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 19:16:10 -0400 Subject: websockets through vscode messaging for codespaces --- extension/react-app/src/hooks/useWebsocket.ts | 133 ++++++++++++++++++++++---- extension/src/debugPanel.ts | 95 ++++++++++++++++++ 2 files changed, 207 insertions(+), 21 deletions(-) diff --git a/extension/react-app/src/hooks/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts index 147172bd..6e8e68fa 100644 --- a/extension/react-app/src/hooks/useWebsocket.ts +++ b/extension/react-app/src/hooks/useWebsocket.ts @@ -1,13 +1,100 @@ import React, { useEffect, useState } from "react"; import { RootStore } from "../redux/store"; import { useSelector } from "react-redux"; +import { postVscMessage } from "../vscode"; + +abstract class Messenger { + abstract send(data: string): void; +} + +class VscodeMessenger extends Messenger { + url: string; + + constructor( + url: string, + onMessage: (message: { data: any }) => void, + onOpen: (messenger: Messenger) => void, + onClose: (messenger: Messenger) => void + ) { + super(); + this.url = url; + window.addEventListener("message", (event: any) => { + switch (event.data.type) { + case "websocketForwardingMessage": + onMessage(event.data); + break; + case "websocketForwardingOpen": + onOpen(this); + break; + case "websocketForwardingClose": + onClose(this); + break; + } + }); + + postVscMessage("websocketForwardingOpen", { url: this.url }); + } + + send(data: string) { + postVscMessage("websocketForwardingMessage", { + message: data, + url: this.url, + }); + } +} + +class WebsocketMessenger extends Messenger { + websocket: WebSocket; + constructor( + websocket: WebSocket, + onMessage: (message: { data: any }) => void, + onOpen: (messenger: Messenger) => void, + onClose: (messenger: Messenger) => void + ) { + super(); + this.websocket = websocket; + + websocket.addEventListener("close", () => { + onClose(this); + }); + + websocket.addEventListener("open", () => { + onOpen(this); + }); + + websocket.addEventListener("message", (event) => { + onMessage(event.data); + }); + } + + static async connect( + url: string, + sessionId: string, + onMessage: (message: { data: any }) => void, + onOpen: (messenger: Messenger) => void, + onClose: (messenger: Messenger) => void + ): Promise { + const ws = new WebSocket(url); + + return new Promise((resolve, reject) => { + ws.addEventListener("open", () => { + resolve(new WebsocketMessenger(ws, onMessage, onOpen, onClose)); + }); + }); + } + + send(data: string) { + this.websocket.send(JSON.stringify(data)); + } +} function useContinueWebsocket( serverUrl: string, - onMessage: (message: { data: any }) => void + onMessage: (message: { data: any }) => void, + useVscodeMessagePassing: boolean = true ) { const sessionId = useSelector((state: RootStore) => state.config.sessionId); - const [websocket, setWebsocket] = useState(undefined); + const [websocket, setWebsocket] = useState(undefined); async function connect() { while (!sessionId) { @@ -15,32 +102,36 @@ function useContinueWebsocket( } console.log("Creating websocket", sessionId); + console.log("Using vscode message passing", useVscodeMessagePassing); - const wsUrl = - serverUrl.replace("http", "ws") + - "/notebook/ws?session_id=" + - encodeURIComponent(sessionId); - - const ws = new WebSocket(wsUrl); - setWebsocket(ws); + const onClose = (messenger: Messenger) => { + console.log("Websocket closed"); + setWebsocket(undefined); + }; - // Set up callbacks - ws.onopen = () => { + const onOpen = (messenger: Messenger) => { console.log("Websocket opened"); - ws.send(JSON.stringify({ sessionId })); + messenger.send(JSON.stringify({ sessionId })); }; - ws.onmessage = (msg) => { - onMessage(msg); - console.log("Got message", msg); - }; + const url = + serverUrl.replace("http", "ws") + + "/notebook/ws?session_id=" + + encodeURIComponent(sessionId); - ws.onclose = (msg) => { - console.log("Websocket closed"); - setWebsocket(undefined); - }; + const messenger: Messenger = useVscodeMessagePassing + ? new VscodeMessenger(url, onMessage, onOpen, onClose) + : await WebsocketMessenger.connect( + url, + sessionId, + onMessage, + onOpen, + onClose + ); + + setWebsocket(messenger); - return ws; + return messenger; } async function getConnection() { diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index 66829836..4192595c 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -16,6 +16,7 @@ import { import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; import { RangeInFile, SerializedDebugContext } from "./client"; import { addFileSystemToDebugContext } from "./util/util"; +const WebSocket = require("ws"); class StreamManager { private _fullText: string = ""; @@ -87,6 +88,46 @@ class StreamManager { } } +let websocketConnections: { [url: string]: WebsocketConnection | undefined } = + {}; + +class WebsocketConnection { + private _ws: WebSocket; + private _onMessage: (message: string) => void; + private _onOpen: () => void; + private _onClose: () => void; + + constructor( + url: string, + onMessage: (message: string) => void, + onOpen: () => void, + onClose: () => void + ) { + this._ws = new WebSocket(url); + this._onMessage = onMessage; + this._onOpen = onOpen; + this._onClose = onClose; + + this._ws.onmessage = (event) => { + this._onMessage(event.data); + }; + this._ws.onclose = () => { + this._onClose(); + }; + this._ws.onopen = () => { + this._onOpen(); + }; + } + + public send(message: string) { + this._ws.send(message); + } + + public close() { + this._ws.close(); + } +} + let streamManager = new StreamManager(); export let debugPanelWebview: vscode.Webview | undefined; @@ -147,6 +188,39 @@ export function setupDebugPanel( }); }); + async function connectWebsocket(url: string) { + return new Promise((resolve, reject) => { + const onMessage = (message: any) => { + panel.webview.postMessage({ + type: "websocketForwardingMessage", + url, + data: message, + }); + }; + const onOpen = () => { + panel.webview.postMessage({ + type: "websocketForwardingOpen", + url, + }); + resolve(null); + }; + const onClose = () => { + websocketConnections[url] = undefined; + panel.webview.postMessage({ + type: "websocketForwardingClose", + url, + }); + }; + const connection = new WebsocketConnection( + url, + onMessage, + onOpen, + onClose + ); + websocketConnections[url] = connection; + }); + } + panel.webview.onDidReceiveMessage(async (data) => { switch (data.type) { case "onLoad": { @@ -158,6 +232,27 @@ export function setupDebugPanel( }); break; } + + case "websocketForwardingOpen": { + let url = data.url; + if (typeof websocketConnections[url] === "undefined") { + await connectWebsocket(url); + } + break; + } + case "websocketForwardingMessage": { + let url = data.url; + let connection = websocketConnections[url]; + if (typeof connection === "undefined") { + await connectWebsocket(url); + } + connection = websocketConnections[url]; + if (typeof connection === "undefined") { + throw new Error("Failed to connect websocket in VS Code Extension"); + } + connection.send(data.message); + break; + } case "listTenThings": { sendTelemetryEvent(TelemetryEvent.GenerateIdeas); let resp = await debugApi.listtenDebugListPost({ -- cgit v1.2.3-70-g09d2 From d614719ed437cbce80fe937954e6ef647075a969 Mon Sep 17 00:00:00 2001 From: Nate Sesti <33237525+sestinj@users.noreply.github.com> Date: Sun, 28 May 2023 21:01:55 -0400 Subject: Update README.md --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index dc1ad7de..4c320a8b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,26 @@ # Continue +Continue is the open-source autopilot for software developers. Using our SDK you can write short scripts, called recipes, that automate sequences of tasks that are common within your codebase. This repository contains: + +- The Continue Python package, which offers an SDK to write recipes and the Continue server, which runs all automations +- The Continue VSCode extension, where you can natively run recipes and use natural language to refine code much faster + +## Getting Started + +The easiest way to start using Continue is to download our VS Code extension from the marketplace: + [Download for VS Code](https://marketplace.visualstudio.com/items?itemName=Continue.continue) +Alternatively, you can build from source. Just clone the repo and run a Python script: + +```bash +git clone https://github.com/continuedev/continue && cd continue/extension/scripts && python3 install_from_source.py +``` + +## Writing Recipes + +See our guides on writing recipes in the documentation. + Resources - [Continue Github Project](https://github.com/orgs/continuedev/projects/1/views/1) -- cgit v1.2.3-70-g09d2 From 03be7e716c0737bcfcadabb55be8260fd279d5ed Mon Sep 17 00:00:00 2001 From: Nate Sesti <33237525+sestinj@users.noreply.github.com> Date: Sun, 28 May 2023 21:06:47 -0400 Subject: Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 4c320a8b..cc95445a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +![GitHub issues](https://img.shields.io/github/issues-raw/continuedev/continue) +[![License: MIT](https://img.shields.io/badge/License-Apache-2.0-yellow.svg)](https://opensource.org/licenses/Apache-2.0) +![GitHub Repo stars](https://img.shields.io/github/stars/continuedev/continue?style=social) +![Twitter URL](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fgithub.com%2Fcontinuedev%2Fcontinue) + # Continue Continue is the open-source autopilot for software developers. Using our SDK you can write short scripts, called recipes, that automate sequences of tasks that are common within your codebase. This repository contains: -- cgit v1.2.3-70-g09d2 From d5777cf933b72507c90ffbf9aedc3f737771d615 Mon Sep 17 00:00:00 2001 From: Nate Sesti <33237525+sestinj@users.noreply.github.com> Date: Sun, 28 May 2023 21:08:20 -0400 Subject: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc95445a..fb061e2f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![GitHub issues](https://img.shields.io/github/issues-raw/continuedev/continue) -[![License: MIT](https://img.shields.io/badge/License-Apache-2.0-yellow.svg)](https://opensource.org/licenses/Apache-2.0) +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![GitHub Repo stars](https://img.shields.io/github/stars/continuedev/continue?style=social) ![Twitter URL](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fgithub.com%2Fcontinuedev%2Fcontinue) -- cgit v1.2.3-70-g09d2 From aa2ac903819ee6837d034b5f7ed8437323628191 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 21:11:49 -0400 Subject: vscodeignore --- extension/.vscodeignore | 4 +++- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/extension/.vscodeignore b/extension/.vscodeignore index f65d24a9..e9db3561 100644 --- a/extension/.vscodeignore +++ b/extension/.vscodeignore @@ -9,4 +9,6 @@ vsc-extension-quickstart.md **/.eslintrc.json **/*.map **/*.ts -scripts/env/** \ No newline at end of file +scripts/env/** +scripts/.env +**/.env \ No newline at end of file diff --git a/extension/package-lock.json b/extension/package-lock.json index 822b908e..20ac24be 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.4", + "version": "0.0.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.4", + "version": "0.0.5", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index f5771ee6..dc0192c3 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.4", + "version": "0.0.5", "publisher": "Continue", "engines": { "vscode": "^1.74.0" -- cgit v1.2.3-70-g09d2 From 61d9e3de9179ca9432689e3dd47009d39b1280e3 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 21:52:11 -0400 Subject: continuedev README improvements --- continuedev/README.md | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/continuedev/README.md b/continuedev/README.md index 5658e8de..a1ddb084 100644 --- a/continuedev/README.md +++ b/continuedev/README.md @@ -1,27 +1,23 @@ -# Continue +# Continue PyPI Package Continue is a Python library for automating repetitive sequences of software development tasks using language models. Using our VS Code extension, you can build, run, and refine these recipes as they natively interact with your codebase. Download on [our GitHub](https://github.com/continuedev/continue). -## Steps to start +## Continue Server -- `cd continue/continue` +The Continue server acts as a bridge between the Continue React app and your IDE, running your recipes and acting on the codebase. Start it by running the following commands: + +- `cd continuedev` - Make sure packages are installed with `poetry install` - `poetry shell` - `cd ..` -- `python3 -m continuedev.src.continuedev` - -## Steps to generate JSON Schema - -Same up until last step and then `python3 -m continuedev.src.scripts.gen_json_schema`. - -## Start the server +- `python3 -m continuedev.src.continuedev.server.main` -Same steps, then `uvicorn continue.src.server.main:app --reload`. +## Scripts -## To build +`poetry run typegen` to generate JSONSchema .json files from the Pydantic types defined in the `models` directory. -Run `poetry build` and it will output wheel and tarball files in `./dist`. +`poetry build` will output wheel and tarball files in `./dist`. ## Writing Steps -See the `src/continuedev/libs/steps` folder for examples of writing a Continue step. +See the `src/continuedev/libs/steps` folder for examples of writing a Continue step. See our documentation for tutorials. -- cgit v1.2.3-70-g09d2 From 327fb65f70fe4b86965a277bbd7cd75c76113eed Mon Sep 17 00:00:00 2001 From: Nate Sesti <33237525+sestinj@users.noreply.github.com> Date: Sun, 28 May 2023 21:54:49 -0400 Subject: Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fb061e2f..0e397b23 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,16 @@ git clone https://github.com/continuedev/continue && cd continue/extension/scrip See our guides on writing recipes in the documentation. -Resources +# Resources - [Continue Github Project](https://github.com/orgs/continuedev/projects/1/views/1) - [Continue User Guide](https://www.notion.so/continue-dev/Continue-User-Guide-1c6ad99887d0474d9e42206f6c98efa4) - [Continue - Mission & Vision](https://continue-dev.notion.site/Continue-a8e41af9801641f79d2c8565907bbd22) + +# Contributing + +Please do + +# License + +[Apache-2.0]([https://github.com/sestinj/the-x/blob/main/LICENSE](https://opensource.org/licenses/Apache-2.0)) © 2023 Continue -- cgit v1.2.3-70-g09d2 From 25770db64e613140f9c060f28f7e79e3ca593519 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 22:18:18 -0400 Subject: UI Cleanup --- extension/react-app/src/components/CodeBlock.tsx | 3 ++- extension/react-app/src/components/DebugPanel.tsx | 4 +++- extension/react-app/src/index.css | 2 +- extension/react-app/src/tabs/chat/MessageDiv.tsx | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/extension/react-app/src/components/CodeBlock.tsx b/extension/react-app/src/components/CodeBlock.tsx index 4c10ab23..e0336554 100644 --- a/extension/react-app/src/components/CodeBlock.tsx +++ b/extension/react-app/src/components/CodeBlock.tsx @@ -6,7 +6,8 @@ import { defaultBorderRadius, vscBackground } from "."; import { Clipboard } from "@styled-icons/heroicons-outline"; const StyledPre = styled.pre` - overflow: scroll; + overflow-y: scroll; + word-wrap: normal; border: 1px solid gray; border-radius: ${defaultBorderRadius}; background-color: ${vscBackground}; diff --git a/extension/react-app/src/components/DebugPanel.tsx b/extension/react-app/src/components/DebugPanel.tsx index ed00571b..9dacc624 100644 --- a/extension/react-app/src/components/DebugPanel.tsx +++ b/extension/react-app/src/components/DebugPanel.tsx @@ -36,7 +36,8 @@ const GradientContainer = styled.div` const MainDiv = styled.div` height: 100%; border-radius: ${defaultBorderRadius}; - overflow: scroll; + overflow-y: scroll; + scrollbar-gutter: stable both-edges; scrollbar-base-color: transparent; /* background: ${vscBackground}; */ background-color: #1e1e1ede; @@ -107,6 +108,7 @@ function DebugPanel(props: DebugPanelProps) { className={ tab.title === "Chat" ? "overflow-hidden" : "overflow-scroll" } + style={{ scrollbarGutter: "stable both-edges" }} > {tab.element} diff --git a/extension/react-app/src/index.css b/extension/react-app/src/index.css index dd38eec3..20599d30 100644 --- a/extension/react-app/src/index.css +++ b/extension/react-app/src/index.css @@ -21,7 +21,7 @@ html, body, #root { - height: calc(100% - 7px); + height: calc(100%); } body { diff --git a/extension/react-app/src/tabs/chat/MessageDiv.tsx b/extension/react-app/src/tabs/chat/MessageDiv.tsx index ab632220..ad81f5e9 100644 --- a/extension/react-app/src/tabs/chat/MessageDiv.tsx +++ b/extension/react-app/src/tabs/chat/MessageDiv.tsx @@ -20,7 +20,8 @@ const Container = styled.div` margin: 3px; width: fit-content; max-width: 75%; - overflow: scroll; + overflow-y: scroll; + scrollbar-gutter: stable both-edges; word-wrap: break-word; -ms-word-wrap: break-word; height: fit-content; -- cgit v1.2.3-70-g09d2 From 4d2aee55162ea5a5ac16f773fc573a555383d90a Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 29 May 2023 12:18:34 -0400 Subject: prompt engineering for faster code edits --- continuedev/src/continuedev/core/policy.py | 4 +- continuedev/src/continuedev/libs/steps/main.py | 108 ++++++++++++++++--------- continuedev/src/continuedev/models/main.py | 2 +- 3 files changed, 74 insertions(+), 40 deletions(-) diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 5a6652f4..504d5ff1 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -6,7 +6,7 @@ from ..models.main import ContinueBaseModel from ..libs.steps.ty import CreatePipelineStep from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation -from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep +from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep from ..libs.steps.nate import WritePytestsStep, CreateTableStep from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..libs.steps.continue_step import ContinueStepStep @@ -32,7 +32,7 @@ class DemoPolicy(Policy): return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) elif "/step" in observation.user_input: return ContinueStepStep(prompt=" ".join(observation.user_input.split(" ")[1:])) - return EditHighlightedCodeStep(user_input=observation.user_input) + return FasterEditHighlightedCodeStep(user_input=observation.user_input) state = history.get_current() if state is None or not self.ran_code_last: diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index f28cb23f..59df114e 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -77,41 +77,45 @@ class FasterEditHighlightedCodeStep(Step): hide = True _completion: str = "Edit Code" _edit_diffs: Union[List[EditDiff], None] = None - _prompt: str = dedent("""Below is the code before changes: - -{code} - -This is the user request: - -{user_input} - -Edit the code to perfectly satifsfy the user request. Format the changes you want to make as a comma-separated array of JSON objects of the form: -{{ - "edits": [{{ - "filepath": , - "replace_me": , - "replace_with": - }}] -}} - -For example, if you want to replace the code `x = 1` with `x = 2` in main.py, you would write: -{{ - "edits": [{{ - "filepath": "main.py", - "replace_me": "x = 1", - "replace_with": "x = 2" - }}] -}} -If you wanted to delete the code `def sum(a, b):\\n return a + b` in main.py, you would write: -{{ - "edits": [{{ - "filepath": "main.py", - "replace_me": "def sum(a, b):\\n return a + b", - "replace_with": "" - }}] -}} - -Respond with only as many edits as needed, and output only the list of json objects, no other text. + _prompt: str = dedent("""\ + You will be given code to edit in order to perfectly satisfy the user request. All the changes you make must be described as replacements, which you should format in the following way: + FILEPATH + + REPLACE_ME + + REPLACE_WITH + + + where and can be multiple lines, but should be the mininum needed to make the edit. Be sure to maintain existing whitespace at the start of lines. + + For example, if you want to replace the code `x = 1` with `x = 2` in main.py, you would write: + FILEPATH + main.py + REPLACE_ME + x = 1 + REPLACE_WITH + x = 2 + If you wanted to delete the code + ``` + def sum(a, b): + return a + b + ``` + in main.py, you would write: + FILEPATH + main.py + REPLACE_ME + def sum(a, b): + return a + b + REPLACE_WITH + + You may need to make multiple edits; respond with exactly as many as needed. + + Below is the code before changes: + + {code} + + This is the user request: "{user_input}" + Here is the description of changes to make: """) async def describe(self, llm: LLM) -> Coroutine[str, None, None]: @@ -148,11 +152,41 @@ Respond with only as many edits as needed, and output only the list of json obje # Temporarily doing this to generate description. self._prompt = prompt self._completion = completion + print(completion) # ALTERNATIVE DECODING STEP HERE + raw_file_edits = [] + lines = completion.split("\n") + current_edit = {} + status = "FILEPATH" + for i in range(0, len(lines)): + line = lines[i] + if line == "FILEPATH": + if "FILEPATH" in current_edit: + raw_file_edits.append(current_edit) + current_edit = {} + status = "FILEPATH" + elif line == "REPLACE_ME": + status = "REPLACE_ME" + elif line == "REPLACE_WITH": + status = "REPLACE_WITH" + elif status == "FILEPATH": + current_edit["filepath"] = line + elif status == "REPLACE_ME": + if "replace_me" in current_edit: + current_edit["replace_me"] += "\n" + line + else: + current_edit["replace_me"] = line + elif status == "REPLACE_WITH": + if "replace_with" in current_edit: + current_edit["replace_with"] += "\n" + line + else: + current_edit["replace_with"] = line + if "filepath" in current_edit: + raw_file_edits.append(current_edit) + file_edits = [] - obj = json.loads(completion.strip()) - for edit in obj["edits"]: + for edit in raw_file_edits: filepath = edit["filepath"] replace_me = edit["replace_me"] replace_with = edit["replace_with"] diff --git a/continuedev/src/continuedev/models/main.py b/continuedev/src/continuedev/models/main.py index 7986b30c..5bd65a76 100644 --- a/continuedev/src/continuedev/models/main.py +++ b/continuedev/src/continuedev/models/main.py @@ -32,7 +32,7 @@ class Position(BaseModel): def from_index(string: str, index: int) -> "Position": """Convert index in string to line and character""" line = string.count("\n", 0, index) - if line == 1: + if line == 0: character = index else: character = index - string.rindex("\n", 0, index) - 1 -- cgit v1.2.3-70-g09d2 From 9a221fda9b44d0dc7ab2637c9a25d1be226b2b32 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 29 May 2023 15:47:36 -0400 Subject: Snippet parsing for FasterEditHighlightedCodeStep --- continuedev/src/continuedev/libs/steps/main.py | 2 +- continuedev/src/continuedev/models/main.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 59df114e..4f4f80e3 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -191,7 +191,7 @@ class FasterEditHighlightedCodeStep(Step): replace_me = edit["replace_me"] replace_with = edit["replace_with"] file_edits.append( - FileEdit(filepath=filepath, range=Range.from_snippet_in_file(content=rif_dict[filepath], snippet=replace_me), replacement=replace_with)) + FileEdit(filepath=filepath, range=Range.from_lines_snippet_in_file(content=rif_dict[filepath], snippet=replace_me), replacement=replace_with)) # ------------------------------ self._edit_diffs = [] diff --git a/continuedev/src/continuedev/models/main.py b/continuedev/src/continuedev/models/main.py index 5bd65a76..1bc51ff1 100644 --- a/continuedev/src/continuedev/models/main.py +++ b/continuedev/src/continuedev/models/main.py @@ -93,6 +93,30 @@ class Range(BaseModel): end_index = start_index + len(snippet) return Range.from_indices(content, start_index, end_index) + @staticmethod + def from_lines_snippet_in_file(content: str, snippet: str) -> "Range": + # lines is a substring of the content modulo whitespace on each line + content_lines = content.splitlines() + snippet_lines = snippet.splitlines() + + start_line = -1 + end_line = -1 + looking_for_line = 0 + for i in range(len(content_lines)): + if content_lines[i].strip() == snippet_lines[looking_for_line].strip(): + if looking_for_line == len(snippet_lines) - 1: + start_line = i - len(snippet_lines) + 1 + end_line = i + break + looking_for_line += 1 + else: + looking_for_line = 0 + + if start_line == -1 or end_line == -1: + raise ValueError("Snippet not found in content") + + return Range.from_shorthand(start_line, 0, end_line, len(content_lines[end_line]) - 1) + class AbstractModel(ABC, BaseModel): @root_validator(pre=True) -- cgit v1.2.3-70-g09d2 From 22245d2cbf90daa9033d8551207aa986069d8b24 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 29 May 2023 18:31:25 -0400 Subject: (much!) faster inference with starcoder --- continuedev/src/continuedev/core/agent.py | 3 +- continuedev/src/continuedev/core/env.py | 19 +++++++- continuedev/src/continuedev/core/policy.py | 4 +- continuedev/src/continuedev/core/sdk.py | 25 ++++++++++ .../src/continuedev/libs/llm/hf_inference_api.py | 25 ++++++++++ continuedev/src/continuedev/libs/steps/main.py | 57 ++++++++++++++++++++-- continuedev/src/continuedev/server/notebook.py | 4 +- .../react-app/src/components/StepContainer.tsx | 21 ++------ 8 files changed, 130 insertions(+), 28 deletions(-) create mode 100644 continuedev/src/continuedev/libs/llm/hf_inference_api.py diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 6d1f542e..329e3d4c 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -49,9 +49,10 @@ class Agent(ContinueBaseModel): async def wait_for_user_input(self) -> str: self._active = False self.update_subscribers() - await self._user_input_queue.get(self.history.current_index) + user_input = await self._user_input_queue.get(self.history.current_index) self._active = True self.update_subscribers() + return user_input _manual_edits_buffer: List[FileEditWithFullContents] = [] diff --git a/continuedev/src/continuedev/core/env.py b/continuedev/src/continuedev/core/env.py index d7275b41..6267ed60 100644 --- a/continuedev/src/continuedev/core/env.py +++ b/continuedev/src/continuedev/core/env.py @@ -1,7 +1,22 @@ from dotenv import load_dotenv import os -load_dotenv() +def get_env_var(var_name: str): + load_dotenv() + return os.getenv(var_name) -openai_api_key = os.getenv("OPENAI_API_KEY") + +def save_env_var(var_name: str, var_value: str): + with open('.env', 'r') as f: + lines = f.readlines() + with open('.env', 'w') as f: + values = {} + for line in lines: + key, value = line.split('=') + value = value.replace('"', '') + values[key] = value + + values[var_name] = var_value + for key, value in values.items(): + f.write(f'{key}="{value}"\n') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 504d5ff1..07101576 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -6,7 +6,7 @@ from ..models.main import ContinueBaseModel from ..libs.steps.ty import CreatePipelineStep from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation -from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep +from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep from ..libs.steps.nate import WritePytestsStep, CreateTableStep from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..libs.steps.continue_step import ContinueStepStep @@ -32,7 +32,7 @@ class DemoPolicy(Policy): return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) elif "/step" in observation.user_input: return ContinueStepStep(prompt=" ".join(observation.user_input.split(" ")[1:])) - return FasterEditHighlightedCodeStep(user_input=observation.user_input) + return StarCoderEditHighlightedCodeStep(user_input=observation.user_input) state = history.get_current() if state is None or not self.ran_code_last: diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 3559e9d7..750b335d 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -3,10 +3,12 @@ from typing import Coroutine, Union from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory from ..models.filesystem import RangeInFile from ..libs.llm import LLM +from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step from ..libs.steps.core.core import * +from .env import get_env_var, save_env_var class Agent: @@ -18,11 +20,22 @@ class ContinueSDKSteps: self.sdk = sdk +class Models: + def __init__(self, sdk: "ContinueSDK"): + self.sdk = sdk + + async def starcoder(self): + api_key = await self.sdk.get_user_secret( + 'HUGGING_FACE_TOKEN', 'Please enter your Hugging Face token') + return HuggingFaceInferenceAPI(api_key=api_key) + + class ContinueSDK: """The SDK provided as parameters to a step""" llm: LLM ide: AbstractIdeProtocolServer steps: ContinueSDKSteps + models: Models __agent: Agent def __init__(self, agent: Agent, llm: Union[LLM, None] = None): @@ -33,6 +46,7 @@ class ContinueSDK: self.ide = agent.ide self.__agent = agent self.steps = ContinueSDKSteps(self) + self.models = Models(self) @property def history(self) -> History: @@ -80,3 +94,14 @@ class ContinueSDK: async def delete_directory(self, path: str): return await self.run_step(FileSystemEditStep(edit=DeleteDirectory(path=path))) + + async def get_user_secret(self, env_var: str, prompt: str) -> str: + try: + val = get_env_var(env_var) + if val is not None: + return val + except: + pass + val = (await self.run_step(WaitForUserInputStep(prompt=prompt))).text + save_env_var(env_var, val) + return val diff --git a/continuedev/src/continuedev/libs/llm/hf_inference_api.py b/continuedev/src/continuedev/libs/llm/hf_inference_api.py new file mode 100644 index 00000000..83852d27 --- /dev/null +++ b/continuedev/src/continuedev/libs/llm/hf_inference_api.py @@ -0,0 +1,25 @@ +from ..llm import LLM +import requests + +DEFAULT_MAX_TOKENS = 2048 +DEFAULT_MAX_TIME = 120. + + +class HuggingFaceInferenceAPI(LLM): + api_key: str + model: str = "bigcode/starcoder" + + def complete(self, prompt: str, **kwargs): + """Return the completion of the text with the given temperature.""" + API_URL = f"https://api-inference.huggingface.co/models/{self.model}" + headers = { + "Authorization": f"Bearer {self.api_key}"} + + response = requests.post(API_URL, headers=headers, json={ + "inputs": prompt, "parameters": { + "max_new_tokens": DEFAULT_MAX_TOKENS, + "max_time": DEFAULT_MAX_TIME, + "return_full_text": False, + } + }) + return response.json()[0]["generated_text"] diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 4f4f80e3..c8a85800 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -1,4 +1,6 @@ -from typing import Callable, Coroutine, List, Union +from typing import Coroutine, List, Union + +from pydantic import BaseModel from ..util.traceback_parsers import parse_python_traceback from ..llm import LLM @@ -8,9 +10,10 @@ from ...models.filesystem import RangeInFile, RangeInFileWithContents from ...core.observation import Observation, TextObservation, TracebackObservation from ..llm.prompt_utils import MarkdownStyleEncoderDecoder from textwrap import dedent -from ...core.main import History, Policy, Step, ContinueSDK, Observation +from ...core.main import Step +from ...core.sdk import ContinueSDK +from ...core.observation import Observation import subprocess -import json from .core.core import EditCodeStep @@ -36,6 +39,10 @@ class RunCodeStep(Step): return None +class Policy(BaseModel): + pass + + class RunPolicyUntilDoneStep(Step): policy: "Policy" @@ -206,6 +213,50 @@ class FasterEditHighlightedCodeStep(Step): return None +class StarCoderEditHighlightedCodeStep(Step): + user_input: str + hide = True + _prompt: str = "{code}{user_request}" + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return "Editing highlighted code" + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + range_in_files = await sdk.ide.getHighlightedCode() + if len(range_in_files) == 0: + # Get the full contents of all open files + files = await sdk.ide.getOpenFiles() + contents = {} + for file in files: + contents[file] = await sdk.ide.readFile(file) + + range_in_files = [RangeInFile.from_entire_file( + filepath, content) for filepath, content in contents.items()] + + rif_with_contents = [] + for range_in_file in range_in_files: + file_contents = await sdk.ide.readRangeInFile(range_in_file) + rif_with_contents.append( + RangeInFileWithContents.from_range_in_file(range_in_file, file_contents)) + + rif_dict = {} + for rif in rif_with_contents: + rif_dict[rif.filepath] = rif.contents + + for rif in rif_with_contents: + prompt = self._prompt.format( + code=rif.contents, user_request=self.user_input) + completion = str((await sdk.models.starcoder()).complete(prompt)) + eot_token = "<|endoftext|>" + if completion.endswith(eot_token): + completion = completion[:completion.rindex(eot_token)] + + await sdk.ide.applyFileSystemEdit( + FileEdit(filepath=rif.filepath, range=rif.range, replacement=completion)) + await sdk.ide.saveFile(rif.filepath) + await sdk.ide.setFileOpen(rif.filepath) + + class EditHighlightedCodeStep(Step): user_input: str hide = True diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index bfd7a09c..c5dcea31 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -12,7 +12,7 @@ from ..libs.steps.nate import ImplementAbstractMethodStep from ..core.observation import Observation from ..libs.llm.openai import OpenAI from .ide_protocol import AbstractIdeProtocolServer -from ..core.env import openai_api_key +from ..core.env import get_env_var import asyncio import nest_asyncio nest_asyncio.apply() @@ -75,7 +75,7 @@ class SessionManager: def new_session(self, ide: AbstractIdeProtocolServer) -> str: cmd = "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py" - agent = DemoAgent(llm=OpenAI(api_key=openai_api_key), + agent = DemoAgent(llm=OpenAI(api_key=get_env_var("OPENAI_API_KEY")), policy=DemoPolicy(cmd=cmd), ide=ide) session_id = str(uuid4()) session = Session(session_id=session_id, agent=agent) diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 03649b66..36b3d99a 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -144,6 +144,9 @@ function StepContainer(props: StepContainerProps) { onSubmit={(ev) => { props.onUserInput(ev.currentTarget.value); }} + onClick={(e) => { + e.stopPropagation(); + }} /> )} {props.historyNode.step.name === "Waiting for user confirmation" && ( @@ -165,24 +168,6 @@ function StepContainer(props: StepContainerProps) { /> )} - - {open && ( - <> - {/* {props.historyNode.observation && ( - - Error Here - - )} */} - {/* {props.iterationContext.suggestedChanges.map((sc) => { - return ( - - {sc.filepath} - {sc.replacement} - - ); - })} */} - - )} -- cgit v1.2.3-70-g09d2 From dd5b9f6b7f08f178d6034a57f97faea38442eb0a Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Wed, 31 May 2023 16:13:01 -0400 Subject: checkpoint! protocol reform and it works now --- continuedev/src/continuedev/core/agent.py | 34 ++-- continuedev/src/continuedev/core/env.py | 4 + continuedev/src/continuedev/core/main.py | 14 +- continuedev/src/continuedev/core/policy.py | 17 +- continuedev/src/continuedev/core/sdk.py | 13 +- continuedev/src/continuedev/libs/steps/chroma.py | 2 +- .../src/continuedev/libs/steps/core/core.py | 33 ++-- .../src/continuedev/libs/steps/draft/dlt.py | 2 +- continuedev/src/continuedev/libs/steps/main.py | 20 +- .../src/continuedev/libs/steps/migration.py | 2 +- continuedev/src/continuedev/libs/steps/nate.py | 6 +- continuedev/src/continuedev/libs/steps/pytest.py | 2 +- continuedev/src/continuedev/libs/steps/ty.py | 2 +- continuedev/src/continuedev/server/ide.py | 50 ++--- continuedev/src/continuedev/server/main.py | 2 +- continuedev/src/continuedev/server/notebook.py | 207 +++++++-------------- .../src/continuedev/server/notebook_protocol.py | 28 +++ .../src/continuedev/server/session_manager.py | 101 ++++++++++ extension/package-lock.json | 19 ++ extension/package.json | 7 +- .../src/hooks/ContinueNotebookClientProtocol.ts | 13 ++ extension/react-app/src/hooks/messenger.ts | 108 +++++++++++ .../src/hooks/useContinueNotebookProtocol.ts | 49 +++++ extension/react-app/src/hooks/useWebsocket.ts | 171 +++-------------- extension/react-app/src/hooks/vscodeMessenger.ts | 68 +++++++ extension/react-app/src/tabs/notebook.tsx | 70 +++---- extension/react-app/src/vscode/index.ts | 1 + extension/react-app/tsconfig.json | 2 +- .../scripts/continuedev-0.1.0-py3-none-any.whl | Bin 53104 -> 56070 bytes extension/src/activation/activate.ts | 4 +- extension/src/activation/environmentSetup.ts | 104 +++++++---- extension/src/commands.ts | 4 +- extension/src/continueIdeClient.ts | 146 ++++----------- extension/src/debugPanel.ts | 26 ++- extension/src/extension.ts | 7 +- extension/src/test/runTest.ts | 30 +-- extension/src/util/messenger.ts | 108 +++++++++++ 37 files changed, 864 insertions(+), 612 deletions(-) create mode 100644 continuedev/src/continuedev/server/notebook_protocol.py create mode 100644 continuedev/src/continuedev/server/session_manager.py create mode 100644 extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts create mode 100644 extension/react-app/src/hooks/messenger.ts create mode 100644 extension/react-app/src/hooks/useContinueNotebookProtocol.ts create mode 100644 extension/react-app/src/hooks/vscodeMessenger.ts create mode 100644 extension/src/util/messenger.ts diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 329e3d4c..7f7466a2 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -13,7 +13,6 @@ from .sdk import ContinueSDK class Agent(ContinueBaseModel): - llm: LLM policy: Policy ide: AbstractIdeProtocolServer history: History = History.from_empty() @@ -31,27 +30,24 @@ class Agent(ContinueBaseModel): 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: Callable[["FullState"], None]): + def on_update(self, callback: Coroutine["FullState", None, None]): """Subscribe to changes to state""" self._on_update_callbacks.append(callback) - def update_subscribers(self): + async def update_subscribers(self): full_state = self.get_full_state() for callback in self._on_update_callbacks: - callback(full_state) - - def __get_step_params(self, step: "Step"): - return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message)) + await callback(full_state) def give_user_input(self, input: str, index: int): - self._user_input_queue.post(index, input) + self._user_input_queue.post(str(index), input) async def wait_for_user_input(self) -> str: self._active = False - self.update_subscribers() - user_input = await self._user_input_queue.get(self.history.current_index) + await self.update_subscribers() + user_input = await self._user_input_queue.get(str(self.history.current_index)) self._active = True - self.update_subscribers() + await self.update_subscribers() return user_input _manual_edits_buffer: List[FileEditWithFullContents] = [] @@ -62,9 +58,9 @@ class Agent(ContinueBaseModel): current_step = self.history.get_current().step self.history.step_back() if issubclass(current_step.__class__, ReversibleStep): - await current_step.reverse(self.__get_step_params(current_step)) + await current_step.reverse(ContinueSDK(self)) - self.update_subscribers() + await self.update_subscribers() except Exception as e: print(e) @@ -94,17 +90,17 @@ class Agent(ContinueBaseModel): # Run step self._step_depth += 1 - observation = await step(self.__get_step_params(step)) + observation = await step(ContinueSDK(self)) self._step_depth -= 1 # Add observation to history self.history.get_current().observation = observation # Update its description - step._set_description(await step.describe(self.llm)) + step._set_description(await step.describe(ContinueSDK(self))) # Call all subscribed callbacks - self.update_subscribers() + await self.update_subscribers() return observation @@ -138,7 +134,7 @@ class Agent(ContinueBaseModel): # 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: - callback(None) + await callback(None) async def run_from_observation(self, observation: Observation): next_step = self.policy.next(self.history) @@ -158,7 +154,7 @@ class Agent(ContinueBaseModel): async def accept_user_input(self, user_input: str): self._main_user_input_queue.append(user_input) - self.update_subscribers() + await self.update_subscribers() if len(self._main_user_input_queue) > 1: return @@ -167,7 +163,7 @@ class Agent(ContinueBaseModel): # 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) - self.update_subscribers() + await self.update_subscribers() await self.run_from_step(UserInputStep(user_input=user_input)) while len(self._main_user_input_queue) > 0: diff --git a/continuedev/src/continuedev/core/env.py b/continuedev/src/continuedev/core/env.py index 6267ed60..edd3297c 100644 --- a/continuedev/src/continuedev/core/env.py +++ b/continuedev/src/continuedev/core/env.py @@ -8,6 +8,10 @@ def get_env_var(var_name: str): def save_env_var(var_name: str, var_value: str): + if not os.path.exists('.env'): + with open('.env', 'w') as f: + f.write(f'{var_name}="{var_value}"\n') + return with open('.env', 'r') as f: lines = f.readlines() with open('.env', 'w') as f: diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index 51fcd299..6be5139b 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -72,7 +72,7 @@ class ContinueSDK: pass -class SequentialStep: +class Models: pass @@ -94,7 +94,7 @@ class Step(ContinueBaseModel): class Config: copy_on_model_validation = False - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: if self._description is not None: return self._description return "Running step: " + self.name @@ -135,6 +135,16 @@ class Step(ContinueBaseModel): return SequentialStep(steps=steps) +class SequentialStep(Step): + steps: list[Step] + hide: bool = True + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + for step in self.steps: + observation = await sdk.run_step(step) + return observation + + class ValidatorObservation(Observation): passed: bool observation: Observation diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 07101576..c0ba0f4f 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -1,20 +1,16 @@ from typing import List, Tuple, Type - -from ..models.main import ContinueBaseModel - from ..libs.steps.ty import CreatePipelineStep from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep from ..libs.steps.nate import WritePytestsStep, CreateTableStep -from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma +# from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..libs.steps.continue_step import ContinueStepStep class DemoPolicy(Policy): ran_code_last: bool = False - cmd: str def next(self, history: History) -> Step: observation = history.last_observation() @@ -26,18 +22,15 @@ class DemoPolicy(Policy): return CreatePipelineStep() elif "/table" in observation.user_input: return CreateTableStep(sql_str=" ".join(observation.user_input.split(" ")[1:])) - elif "/ask" in observation.user_input: - return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) - elif "/edit" in observation.user_input: - return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) + # elif "/ask" in observation.user_input: + # return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) + # elif "/edit" in observation.user_input: + # return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) elif "/step" in observation.user_input: return ContinueStepStep(prompt=" ".join(observation.user_input.split(" ")[1:])) return StarCoderEditHighlightedCodeStep(user_input=observation.user_input) state = history.get_current() - if state is None or not self.ran_code_last: - self.ran_code_last = True - return RunCodeStep(cmd=self.cmd) if observation is not None and isinstance(observation, TracebackObservation): self.ran_code_last = False diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 750b335d..6ae0be04 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -4,6 +4,7 @@ from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDir from ..models.filesystem import RangeInFile from ..libs.llm import LLM from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI +from ..libs.llm.openai import OpenAI from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step @@ -29,20 +30,20 @@ class Models: 'HUGGING_FACE_TOKEN', 'Please enter your Hugging Face token') return HuggingFaceInferenceAPI(api_key=api_key) + async def gpt35(self): + api_key = await self.sdk.get_user_secret( + 'OPENAI_API_KEY', 'Please enter your OpenAI API key') + return OpenAI(api_key=api_key, default_model="gpt-3.5-turbo") + class ContinueSDK: """The SDK provided as parameters to a step""" - llm: LLM ide: AbstractIdeProtocolServer steps: ContinueSDKSteps models: Models __agent: Agent - def __init__(self, agent: Agent, llm: Union[LLM, None] = None): - if llm is None: - self.llm = agent.llm - else: - self.llm = llm + def __init__(self, agent: Agent): self.ide = agent.ide self.__agent = agent self.steps = ContinueSDKSteps(self) diff --git a/continuedev/src/continuedev/libs/steps/chroma.py b/continuedev/src/continuedev/libs/steps/chroma.py index f13a2bab..39424c5c 100644 --- a/continuedev/src/continuedev/libs/steps/chroma.py +++ b/continuedev/src/continuedev/libs/steps/chroma.py @@ -40,7 +40,7 @@ class AnswerQuestionChroma(Step): Here is the answer:""") - answer = sdk.llm.complete(prompt) + answer = (await sdk.models.gpt35()).complete(prompt) print(answer) self._answer = answer diff --git a/continuedev/src/continuedev/libs/steps/core/core.py b/continuedev/src/continuedev/libs/steps/core/core.py index 0338d635..14b3cb80 100644 --- a/continuedev/src/continuedev/libs/steps/core/core.py +++ b/continuedev/src/continuedev/libs/steps/core/core.py @@ -4,27 +4,18 @@ from textwrap import dedent from typing import Coroutine, List, Union from ...llm.prompt_utils import MarkdownStyleEncoderDecoder -from ...util.traceback_parsers import parse_python_traceback - from ....models.filesystem_edit import EditDiff, FileEditWithFullContents, FileSystemEdit from ....models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents -from ...llm import LLM from ....core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation -from ....core.main import Step +from ....core.main import Step, SequentialStep class ContinueSDK: pass -class SequentialStep(Step): - steps: list[Step] - hide: bool = True - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - for step in self.steps: - observation = await sdk.run_step(step) - return observation +class Models: + pass class ReversibleStep(Step): @@ -52,7 +43,7 @@ def ShellCommandsStep(Step): cwd: str | None = None name: str = "Run Shell Commands" - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "\n".join(self.cmds) async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -81,13 +72,13 @@ class EditCodeStep(Step): _prompt: Union[str, None] = None _completion: Union[str, None] = None - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: if self._edit_diffs is None: return "Editing files: " + ", ".join(map(lambda rif: rif.filepath, self.range_in_files)) elif len(self._edit_diffs) == 0: return "No edits made" else: - return llm.complete(dedent(f"""{self._prompt}{self._completion} + return (await models.gpt35()).complete(dedent(f"""{self._prompt}{self._completion} Maximally concise summary of changes in bullet points (can use markdown): """)) @@ -102,7 +93,7 @@ class EditCodeStep(Step): code_string = enc_dec.encode() prompt = self.prompt.format(code=code_string) - completion = sdk.llm.complete(prompt) + completion = (await sdk.models.gpt35()).complete(prompt) # Temporarily doing this to generate description. self._prompt = prompt @@ -127,7 +118,7 @@ class EditFileStep(Step): prompt: str hide: bool = True - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Editing file: " + self.filepath async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -145,7 +136,7 @@ class ManualEditStep(ReversibleStep): hide: bool = True - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Manual edit step" # TODO - only handling FileEdit here, but need all other types of FileSystemEdits # Also requires the merge_file_edit function @@ -181,7 +172,7 @@ class UserInputStep(Step): name: str = "User Input" hide: bool = True - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return self.user_input async def run(self, sdk: ContinueSDK) -> Coroutine[UserInputObservation, None, None]: @@ -194,7 +185,7 @@ class WaitForUserInputStep(Step): _description: Union[str, None] = None - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return self.prompt async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -207,7 +198,7 @@ class WaitForUserConfirmationStep(Step): prompt: str name: str = "Waiting for user confirmation" - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return self.prompt async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index 5ba5692a..460aa0cc 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -10,7 +10,7 @@ class SetupPipelineStep(Step): api_description: str # e.g. "I want to load data from the weatherapi.com API" async def run(self, sdk: ContinueSDK): - source_name = sdk.llm.complete( + source_name = (await sdk.models.gpt35()).complete( f"Write a snake_case name for the data source described by {self.api_description}: ").strip() filename = f'{source_name}.py' diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index c8a85800..70c0d4b8 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -11,7 +11,7 @@ from ...core.observation import Observation, TextObservation, TracebackObservati from ..llm.prompt_utils import MarkdownStyleEncoderDecoder from textwrap import dedent from ...core.main import Step -from ...core.sdk import ContinueSDK +from ...core.sdk import ContinueSDK, Models from ...core.observation import Observation import subprocess from .core.core import EditCodeStep @@ -20,7 +20,7 @@ from .core.core import EditCodeStep class RunCodeStep(Step): cmd: str - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return f"Ran command: `{self.cmd}`" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -59,7 +59,7 @@ class RunCommandStep(Step): name: str = "Run command" _description: str = None - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: if self._description is not None: return self._description return self.cmd @@ -125,7 +125,7 @@ class FasterEditHighlightedCodeStep(Step): Here is the description of changes to make: """) - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Editing highlighted code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -154,7 +154,7 @@ class FasterEditHighlightedCodeStep(Step): for rif in rif_with_contents: rif_dict[rif.filepath] = rif.contents - completion = sdk.llm.complete(prompt) + completion = (await sdk.models.gpt35()).complete(prompt) # Temporarily doing this to generate description. self._prompt = prompt @@ -215,10 +215,10 @@ class FasterEditHighlightedCodeStep(Step): class StarCoderEditHighlightedCodeStep(Step): user_input: str - hide = True + hide = False _prompt: str = "{code}{user_request}" - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Editing highlighted code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -271,7 +271,7 @@ This is the user request: This is the code after being changed to perfectly satisfy the user request: """) - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Editing highlighted code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -293,7 +293,7 @@ This is the code after being changed to perfectly satisfy the user request: class FindCodeStep(Step): prompt: str - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Finding code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -307,7 +307,7 @@ class UserInputStep(Step): class SolveTracebackStep(Step): traceback: Traceback - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return f"```\n{self.traceback.full_traceback}\n```" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: diff --git a/continuedev/src/continuedev/libs/steps/migration.py b/continuedev/src/continuedev/libs/steps/migration.py index f044a60f..7b70422d 100644 --- a/continuedev/src/continuedev/libs/steps/migration.py +++ b/continuedev/src/continuedev/libs/steps/migration.py @@ -15,7 +15,7 @@ class MigrationStep(Step): recent_edits = await sdk.ide.get_recent_edits(self.edited_file) recent_edits_string = "\n\n".join( map(lambda x: x.to_string(), recent_edits)) - description = await sdk.llm.complete(f"{recent_edits_string}\n\nGenerate a short description of the migration made in the above changes:\n") + description = await (await sdk.models.gpt35()).complete(f"{recent_edits_string}\n\nGenerate a short description of the migration made in the above changes:\n") await sdk.run_step(RunCommandStep(cmd=f"cd libs && poetry run alembic revision --autogenerate -m {description}")) migration_file = f"libs/alembic/versions/{?}.py" contents = await sdk.ide.readFile(migration_file) diff --git a/continuedev/src/continuedev/libs/steps/nate.py b/continuedev/src/continuedev/libs/steps/nate.py index a0e728e5..2f84e9d7 100644 --- a/continuedev/src/continuedev/libs/steps/nate.py +++ b/continuedev/src/continuedev/libs/steps/nate.py @@ -45,7 +45,7 @@ Here are additional instructions: Here is a complete set of pytest unit tests: """) - # tests = sdk.llm.complete(prompt) + # tests = (await sdk.models.gpt35()).complete(prompt) tests = ''' import pytest @@ -169,9 +169,9 @@ export class Order { tracking_number: string; }''' time.sleep(2) - # orm_entity = sdk.llm.complete( + # orm_entity = (await sdk.models.gpt35()).complete( # f"{self.sql_str}\n\nWrite a TypeORM entity called {entity_name} for this table, importing as necessary:") - # sdk.llm.complete("What is the name of the entity?") + # (await sdk.models.gpt35()).complete("What is the name of the entity?") await sdk.apply_filesystem_edit(AddFile(filepath=f"/Users/natesesti/Desktop/continue/extension/examples/python/MyProject/src/entity/{entity_name}.ts", content=orm_entity)) await sdk.ide.setFileOpen(f"/Users/natesesti/Desktop/continue/extension/examples/python/MyProject/src/entity/{entity_name}.ts", True) diff --git a/continuedev/src/continuedev/libs/steps/pytest.py b/continuedev/src/continuedev/libs/steps/pytest.py index b4e6dfd2..2e83ae2d 100644 --- a/continuedev/src/continuedev/libs/steps/pytest.py +++ b/continuedev/src/continuedev/libs/steps/pytest.py @@ -33,5 +33,5 @@ class WritePytestsStep(Step): Here is a complete set of pytest unit tests: """) - tests = sdk.llm.complete(prompt) + tests = (await sdk.models.gpt35()).complete(prompt) await sdk.apply_filesystem_edit(AddFile(filepath=path, content=tests)) diff --git a/continuedev/src/continuedev/libs/steps/ty.py b/continuedev/src/continuedev/libs/steps/ty.py index 5ff03f04..9dde7c86 100644 --- a/continuedev/src/continuedev/libs/steps/ty.py +++ b/continuedev/src/continuedev/libs/steps/ty.py @@ -18,7 +18,7 @@ class SetupPipelineStep(Step): api_description: str # e.g. "I want to load data from the weatherapi.com API" async def run(self, sdk: ContinueSDK): - # source_name = sdk.llm.complete( + # source_name = (await sdk.models.gpt35()).complete( # f"Write a snake_case name for the data source described by {self.api_description}: ").strip() filename = f'/Users/natesesti/Desktop/continue/extension/examples/python/{source_name}.py' diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index dd1dc463..50296841 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -1,5 +1,6 @@ # This is a separate server from server/main.py import asyncio +import json import os from typing import Any, Dict, List, Type, TypeVar, Union import uuid @@ -90,31 +91,33 @@ class IdeProtocolServer(AbstractIdeProtocolServer): def __init__(self, session_manager: SessionManager): self.session_manager = session_manager - async def _send_json(self, data: Any): - await self.websocket.send_json(data) + 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(data) + await self._send_json(message_type, data) resp = await self._receive_json(message_type) return resp_model.parse_obj(resp) - async def handle_json(self, data: Any): - t = data["messageType"] - if t == "openNotebook": + async def handle_json(self, message_type: str, data: Any): + if message_type == "openNotebook": await self.openNotebook() - elif t == "setFileOpen": + elif message_type == "setFileOpen": await self.setFileOpen(data["filepath"], data["open"]) - elif t == "fileEdits": + elif message_type == "fileEdits": fileEdits = list( map(lambda d: FileEditWithFullContents.parse_obj(d), data["fileEdits"])) self.onFileEdits(fileEdits) - elif t in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory"]: - self.sub_queue.post(t, data) + elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory"]: + self.sub_queue.post(message_type, data) else: - raise ValueError("Unknown message type", t) + raise ValueError("Unknown message type", message_type) # ------------------------------- # # Request actions in IDE, doesn't matter which Session @@ -123,24 +126,21 @@ class IdeProtocolServer(AbstractIdeProtocolServer): async def setFileOpen(self, filepath: str, open: bool = True): # Agent needs access to this. - await self.websocket.send_json({ - "messageType": "setFileOpen", + await self._send_json("setFileOpen", { "filepath": filepath, "open": open }) async def openNotebook(self): session_id = self.session_manager.new_session(self) - await self._send_json({ - "messageType": "openNotebook", + await self._send_json("openNotebook", { "sessionId": session_id }) async def showSuggestionsAndWait(self, suggestions: List[FileEdit]) -> bool: ids = [str(uuid.uuid4()) for _ in suggestions] for i in range(len(suggestions)): - self._send_json({ - "messageType": "showSuggestion", + self._send_json("showSuggestion", { "suggestion": suggestions[i], "suggestionId": ids[i] }) @@ -210,8 +210,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): async def saveFile(self, filepath: str): """Save a file""" - await self._send_json({ - "messageType": "saveFile", + await self._send_json("saveFile", { "filepath": filepath }) @@ -293,10 +292,17 @@ ideProtocolServer = IdeProtocolServer(session_manager) async def websocket_endpoint(websocket: WebSocket): await websocket.accept() print("Accepted websocket connection from, ", websocket.client) - await websocket.send_json({"messageType": "connected"}) + await websocket.send_json({"messageType": "connected", "data": {}}) ideProtocolServer.websocket = websocket while True: - data = await websocket.receive_json() - await ideProtocolServer.handle_json(data) + message = await websocket.receive_text() + message = json.loads(message) + + if "messageType" not in message or "data" not in message: + continue + message_type = message["messageType"] + data = message["data"] + + await ideProtocolServer.handle_json(message_type, data) await websocket.close() diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index 11ad1d8f..e87d5fa9 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -32,7 +32,7 @@ args = parser.parse_args() def run_server(): - uvicorn.run(app, host="0.0.0.0", port=args.port, log_config="logging.ini") + uvicorn.run(app, host="0.0.0.0", port=args.port) if __name__ == "__main__": diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index c5dcea31..edb61a45 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -1,18 +1,12 @@ -from fastapi import FastAPI, Depends, Header, WebSocket, APIRouter -from typing import Any, Dict, List, Union -from uuid import uuid4 +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 ..models.filesystem_edit import FileEditWithFullContents -from ..core.policy import DemoPolicy -from ..core.main import FullState, History, Step -from ..core.agent import Agent -from ..libs.steps.nate import ImplementAbstractMethodStep -from ..core.observation import Observation -from ..libs.llm.openai import OpenAI -from .ide_protocol import AbstractIdeProtocolServer -from ..core.env import get_env_var +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() @@ -36,160 +30,99 @@ class AppStatus: Server.handle_exit = AppStatus.handle_exit -class Session: - session_id: str - agent: Agent - ws: Union[WebSocket, None] - - def __init__(self, session_id: str, agent: Agent): - self.session_id = session_id - self.agent = agent - self.ws = None - - -class DemoAgent(Agent): - first_seen: bool = False - cumulative_edit_string = "" - - def handle_manual_edits(self, edits: List[FileEditWithFullContents]): - for edit in edits: - self.cumulative_edit_string += edit.fileEdit.replacement - self._manual_edits_buffer.append(edit) - # Note that 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) - # FOR DEMO PURPOSES - if edit.fileEdit.filepath.endswith("filesystem.py") and "List" in self.cumulative_edit_string and ":" in edit.fileEdit.replacement: - self.cumulative_edit_string = "" - asyncio.create_task(self.run_from_step( - ImplementAbstractMethodStep())) - - -class SessionManager: - sessions: Dict[str, Session] = {} - _event_loop: Union[asyncio.BaseEventLoop, None] = None - - def get_session(self, session_id: str) -> Session: - if session_id not in self.sessions: - raise KeyError("Session ID not recognized") - return self.sessions[session_id] - - def new_session(self, ide: AbstractIdeProtocolServer) -> str: - cmd = "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py" - agent = DemoAgent(llm=OpenAI(api_key=get_env_var("OPENAI_API_KEY")), - policy=DemoPolicy(cmd=cmd), ide=ide) - session_id = str(uuid4()) - session = Session(session_id=session_id, agent=agent) - self.sessions[session_id] = session +def session(x_continue_session_id: str = Header("anonymous")) -> Session: + return session_manager.get_session(x_continue_session_id) - def on_update(state: FullState): - session_manager.send_ws_data(session_id, { - "messageType": "state", - "state": agent.get_full_state().dict() - }) - agent.on_update(on_update) - asyncio.create_task(agent.run_policy()) - return session_id +def websocket_session(session_id: str) -> Session: + return session_manager.get_session(session_id) - def remove_session(self, session_id: str): - del self.sessions[session_id] - def register_websocket(self, session_id: str, ws: WebSocket): - self.sessions[session_id].ws = ws - print("Registered websocket for session", session_id) +T = TypeVar("T", bound=BaseModel) - def send_ws_data(self, session_id: str, data: Any): - if self.sessions[session_id].ws is None: - print(f"Session {session_id} has no websocket") - return +# You should probably abstract away the websocket stuff into a separate class - async def a(): - await self.sessions[session_id].ws.send_json(data) - # Run coroutine in background - if self._event_loop is None or self._event_loop.is_closed(): - self._event_loop = asyncio.new_event_loop() - self._event_loop.run_until_complete(a()) - self._event_loop.close() - else: - self._event_loop.run_until_complete(a()) - self._event_loop.close() +class NotebookProtocolServer(AbstractNotebookProtocolServer): + websocket: WebSocket + session: Session + sub_queue: AsyncSubscriptionQueue = AsyncSubscriptionQueue() + def __init__(self, session: Session): + self.session = session -session_manager = SessionManager() + async def _send_json(self, data: Any): + await self.websocket.send_json(data) + async def _receive_json(self, message_type: str) -> Any: + return await self.sub_queue.get(message_type) -def session(x_continue_session_id: str = Header("anonymous")) -> Session: - return session_manager.get_session(x_continue_session_id) + async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T: + await self._send_json(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) -def websocket_session(session_id: str) -> Session: - return session_manager.get_session(session_id) + async def send_state_update(self): + state = self.session.agent.get_full_state().dict() + await self._send_json({ + "messageType": "state_update", + "state": state + }) + def on_main_input(self, input: str): + # Do something with user input + asyncio.create_task(self.session.agent.accept_user_input(input)) -class StartSessionBody(BaseModel): - config_file_path: Union[str, None] + def on_reverse_to_index(self, index: int): + # Reverse the history to the given index + asyncio.create_task(self.session.agent.reverse_to_index(index)) + def on_step_user_input(self, input: str, index: int): + asyncio.create_task( + self.session.agent.give_user_input(input, index)) -class StartSessionResp(BaseModel): - session_id: str + def on_refinement_input(self, input: str, index: int): + asyncio.create_task( + self.session.agent.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) - data = await websocket.receive_text() + protocol = NotebookProtocolServer(session) + protocol.websocket = websocket + # Update any history that may have happened before connection - await websocket.send_json({ - "messageType": "state", - "state": session_manager.get_session(session.session_id).agent.get_full_state().dict() - }) - print("Session started", data) + await protocol.send_state_update() + while AppStatus.should_exit is False: - data = await websocket.receive_json() - print("Received data", data) + message = await websocket.receive_json() + print("Received message", message) + if type(message) is str: + message = json.loads(message) - if "messageType" not in data: + if "messageType" not in message or "data" not in message: continue - messageType = data["messageType"] + message_type = message["messageType"] + data = message["data"] - try: - if messageType == "main_input": - # Do something with user input - asyncio.create_task( - session.agent.accept_user_input(data["value"])) - elif messageType == "step_user_input": - asyncio.create_task( - session.agent.give_user_input(data["value"], data["index"])) - elif messageType == "refinement_input": - asyncio.create_task( - session.agent.accept_refinement_input(data["value"], data["index"])) - elif messageType == "reverse": - # Reverse the history to the given index - asyncio.create_task( - session.agent.reverse_to_index(data["index"])) - except Exception as e: - print(e) + protocol.handle_json(message_type, data) print("Closing websocket") await websocket.close() - - -@router.post("/run") -def request_run(step: Step, session=Depends(session)): - """Tell an agent to take a specific action.""" - asyncio.create_task(session.agent.run_from_step(step)) - return "Success" - - -@router.get("/history") -def get_history(session=Depends(session)) -> History: - return session.agent.history - - -@router.post("/observation") -def post_observation(observation: Observation, session=Depends(session)): - asyncio.create_task(session.agent.run_from_observation(observation)) - return "Success" diff --git a/continuedev/src/continuedev/server/notebook_protocol.py b/continuedev/src/continuedev/server/notebook_protocol.py new file mode 100644 index 00000000..c2be82e0 --- /dev/null +++ b/continuedev/src/continuedev/server/notebook_protocol.py @@ -0,0 +1,28 @@ +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""" diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py new file mode 100644 index 00000000..b48c21b7 --- /dev/null +++ b/continuedev/src/continuedev/server/session_manager.py @@ -0,0 +1,101 @@ +from fastapi import WebSocket +from typing import Any, Dict, List, Union +from uuid import uuid4 + +from ..models.filesystem_edit import FileEditWithFullContents +from ..core.policy import DemoPolicy +from ..core.main import FullState +from ..core.agent import Agent +from ..libs.steps.nate import ImplementAbstractMethodStep +from .ide_protocol import AbstractIdeProtocolServer +import asyncio +import nest_asyncio +nest_asyncio.apply() + + +class Session: + session_id: str + agent: Agent + ws: Union[WebSocket, None] + + def __init__(self, session_id: str, agent: Agent): + self.session_id = session_id + self.agent = agent + self.ws = None + + +class DemoAgent(Agent): + first_seen: bool = False + cumulative_edit_string = "" + + def handle_manual_edits(self, edits: List[FileEditWithFullContents]): + for edit in edits: + self.cumulative_edit_string += edit.fileEdit.replacement + self._manual_edits_buffer.append(edit) + # Note that 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) + # FOR DEMO PURPOSES + if edit.fileEdit.filepath.endswith("filesystem.py") and "List" in self.cumulative_edit_string and ":" in edit.fileEdit.replacement: + self.cumulative_edit_string = "" + asyncio.create_task(self.run_from_step( + ImplementAbstractMethodStep())) + + +class SessionManager: + sessions: Dict[str, Session] = {} + _event_loop: Union[asyncio.BaseEventLoop, None] = None + + def get_session(self, session_id: str) -> Session: + if session_id not in self.sessions: + raise KeyError("Session ID not recognized") + return self.sessions[session_id] + + def new_session(self, ide: AbstractIdeProtocolServer) -> str: + agent = DemoAgent(policy=DemoPolicy(), ide=ide) + session_id = str(uuid4()) + session = Session(session_id=session_id, agent=agent) + self.sessions[session_id] = session + + async def on_update(state: FullState): + await session_manager.send_ws_data(session_id, "state_update", { + "state": agent.get_full_state().dict() + }) + + agent.on_update(on_update) + asyncio.create_task(agent.run_policy()) + return session_id + + def remove_session(self, session_id: str): + del self.sessions[session_id] + + def register_websocket(self, session_id: str, ws: WebSocket): + self.sessions[session_id].ws = ws + print("Registered websocket for session", session_id) + + async def send_ws_data(self, session_id: str, message_type: str, data: Any): + if self.sessions[session_id].ws is None: + print(f"Session {session_id} has no websocket") + return + + async def a(): + await self.sessions[session_id].ws.send_json({ + "messageType": message_type, + "data": data + }) + + # Run coroutine in background + await self.sessions[session_id].ws.send_json({ + "messageType": message_type, + "data": data + }) + return + if self._event_loop is None or self._event_loop.is_closed(): + self._event_loop = asyncio.new_event_loop() + self._event_loop.run_until_complete(a()) + self._event_loop.close() + else: + self._event_loop.run_until_complete(a()) + self._event_loop.close() + + +session_manager = SessionManager() diff --git a/extension/package-lock.json b/extension/package-lock.json index 20ac24be..04af09d3 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -28,6 +28,7 @@ "@types/node": "16.x", "@types/node-fetch": "^2.6.2", "@types/vscode": "^1.74.0", + "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "@vscode/test-electron": "^2.2.0", @@ -2027,6 +2028,15 @@ "integrity": "sha512-LyeCIU3jb9d38w0MXFwta9r0Jx23ugujkAxdwLTNCyspdZTKUc43t7ppPbCiPoQ/Ivd/pnDFZrb4hWd45wrsgA==", "dev": true }, + "node_modules/@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.48.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", @@ -9246,6 +9256,15 @@ "integrity": "sha512-LyeCIU3jb9d38w0MXFwta9r0Jx23ugujkAxdwLTNCyspdZTKUc43t7ppPbCiPoQ/Ivd/pnDFZrb4hWd45wrsgA==", "dev": true }, + "@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "5.48.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", diff --git a/extension/package.json b/extension/package.json index dc0192c3..c96655a9 100644 --- a/extension/package.json +++ b/extension/package.json @@ -148,7 +148,7 @@ }, "scripts": { "vscode:prepublish": "npm run esbuild-base -- --minify", - "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node", + "esbuild-base": "rm -rf ./out && esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node", "esbuild": "rm -rf ./out && npm run esbuild-base -- --sourcemap", "esbuild-watch": "npm run esbuild-base -- --sourcemap --watch", "test-compile": "tsc -p ./", @@ -160,9 +160,9 @@ "pretest": "npm run compile && npm run lint", "lint": "eslint src --ext ts", "test": "node ./out/test/runTest.js", - "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.2.vsix && cp ./config/dev_config.json ./config/config.json", + "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.5.vsix && cp ./config/dev_config.json ./config/config.json", "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.0-py3-none-any.whl ../extension/scripts/continuedev-0.1.0-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", - "install-extension": "code --install-extension ./build/continue-0.0.1.vsix", + "install-extension": "code --install-extension ./build/continue-0.0.5.vsix", "uninstall": "code --uninstall-extension .continue", "reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension" }, @@ -173,6 +173,7 @@ "@types/node": "16.x", "@types/node-fetch": "^2.6.2", "@types/vscode": "^1.74.0", + "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "@vscode/test-electron": "^2.2.0", diff --git a/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts b/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts new file mode 100644 index 00000000..75fd7373 --- /dev/null +++ b/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts @@ -0,0 +1,13 @@ +abstract class AbstractContinueNotebookClientProtocol { + abstract sendMainInput(input: string): void; + + abstract reverseToIndex(index: number): void; + + abstract sendRefinementInput(input: string, index: number): void; + + abstract sendStepUserInput(input: string, index: number): void; + + abstract onStateUpdate(state: any): void; +} + +export default AbstractContinueNotebookClientProtocol; diff --git a/extension/react-app/src/hooks/messenger.ts b/extension/react-app/src/hooks/messenger.ts new file mode 100644 index 00000000..e2a0bab8 --- /dev/null +++ b/extension/react-app/src/hooks/messenger.ts @@ -0,0 +1,108 @@ +// console.log("Websocket import"); +// const WebSocket = require("ws"); + +export abstract class Messenger { + abstract send(messageType: string, data: object): void; + + abstract onMessageType( + messageType: string, + callback: (data: object) => void + ): void; + + abstract onMessage(callback: (messageType: string, data: any) => void): void; + + abstract onOpen(callback: () => void): void; + + abstract onClose(callback: () => void): void; + + abstract sendAndReceive(messageType: string, data: any): Promise; +} + +export class WebsocketMessenger extends Messenger { + websocket: WebSocket; + private onMessageListeners: { + [messageType: string]: ((data: object) => void)[]; + } = {}; + private onOpenListeners: (() => void)[] = []; + private onCloseListeners: (() => void)[] = []; + private serverUrl: string; + + _newWebsocket(): WebSocket { + // // Dynamic import, because WebSocket is builtin with browser, but not with node. And can't use require in browser. + // if (typeof process === "object") { + // console.log("Using node"); + // // process is only available in Node + // var WebSocket = require("ws"); + // } + + const newWebsocket = new WebSocket(this.serverUrl); + for (const listener of this.onOpenListeners) { + this.onOpen(listener); + } + for (const listener of this.onCloseListeners) { + this.onClose(listener); + } + for (const messageType in this.onMessageListeners) { + for (const listener of this.onMessageListeners[messageType]) { + this.onMessageType(messageType, listener); + } + } + return newWebsocket; + } + + constructor(serverUrl: string) { + super(); + this.serverUrl = serverUrl; + this.websocket = this._newWebsocket(); + } + + send(messageType: string, data: object) { + const payload = JSON.stringify({ messageType, data }); + if (this.websocket.readyState === this.websocket.OPEN) { + this.websocket.send(payload); + } else { + if (this.websocket.readyState !== this.websocket.CONNECTING) { + this.websocket = this._newWebsocket(); + } + this.websocket.addEventListener("open", () => { + this.websocket.send(payload); + }); + } + } + + sendAndReceive(messageType: string, data: any): Promise { + return new Promise((resolve, reject) => { + const eventListener = (data: any) => { + // THIS ISN"T GETTING CALLED + resolve(data); + this.websocket.removeEventListener("message", eventListener); + }; + this.onMessageType(messageType, eventListener); + this.send(messageType, data); + }); + } + + onMessageType(messageType: string, callback: (data: any) => void): void { + this.websocket.addEventListener("message", (event: any) => { + const msg = JSON.parse(event.data); + if (msg.messageType === messageType) { + callback(msg.data); + } + }); + } + + onMessage(callback: (messageType: string, data: any) => void): void { + this.websocket.addEventListener("message", (event) => { + const msg = JSON.parse(event.data); + callback(msg.messageType, msg.data); + }); + } + + onOpen(callback: () => void): void { + this.websocket.addEventListener("open", callback); + } + + onClose(callback: () => void): void { + this.websocket.addEventListener("close", callback); + } +} diff --git a/extension/react-app/src/hooks/useContinueNotebookProtocol.ts b/extension/react-app/src/hooks/useContinueNotebookProtocol.ts new file mode 100644 index 00000000..d5ffbf09 --- /dev/null +++ b/extension/react-app/src/hooks/useContinueNotebookProtocol.ts @@ -0,0 +1,49 @@ +import AbstractContinueNotebookClientProtocol from "./ContinueNotebookClientProtocol"; +// import { Messenger, WebsocketMessenger } from "../../../src/util/messenger"; +import { Messenger, WebsocketMessenger } from "./messenger"; +import { VscodeMessenger } from "./vscodeMessenger"; + +class ContinueNotebookClientProtocol extends AbstractContinueNotebookClientProtocol { + messenger: Messenger; + // Server URL must contain the session ID param + serverUrlWithSessionId: string; + + constructor( + serverUrlWithSessionId: string, + useVscodeMessagePassing: boolean = false + ) { + super(); + this.serverUrlWithSessionId = serverUrlWithSessionId; + if (useVscodeMessagePassing) { + this.messenger = new VscodeMessenger(serverUrlWithSessionId); + } else { + this.messenger = new WebsocketMessenger(serverUrlWithSessionId); + } + } + + sendMainInput(input: string) { + this.messenger.send("main_input", { input }); + } + + reverseToIndex(index: number) { + this.messenger.send("reverse_to_index", { index }); + } + + sendRefinementInput(input: string, index: number) { + this.messenger.send("refinement_input", { input, index }); + } + + sendStepUserInput(input: string, index: number) { + this.messenger.send("step_user_input", { input, index }); + } + + onStateUpdate(callback: (state: any) => void) { + this.messenger.onMessageType("state_update", (data: any) => { + if (data.state) { + callback(data.state); + } + }); + } +} + +export default ContinueNotebookClientProtocol; diff --git a/extension/react-app/src/hooks/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts index 6e8e68fa..b98be577 100644 --- a/extension/react-app/src/hooks/useWebsocket.ts +++ b/extension/react-app/src/hooks/useWebsocket.ts @@ -1,158 +1,39 @@ import React, { useEffect, useState } from "react"; import { RootStore } from "../redux/store"; import { useSelector } from "react-redux"; +import ContinueNotebookClientProtocol from "./useContinueNotebookProtocol"; import { postVscMessage } from "../vscode"; -abstract class Messenger { - abstract send(data: string): void; -} - -class VscodeMessenger extends Messenger { - url: string; - - constructor( - url: string, - onMessage: (message: { data: any }) => void, - onOpen: (messenger: Messenger) => void, - onClose: (messenger: Messenger) => void - ) { - super(); - this.url = url; - window.addEventListener("message", (event: any) => { - switch (event.data.type) { - case "websocketForwardingMessage": - onMessage(event.data); - break; - case "websocketForwardingOpen": - onOpen(this); - break; - case "websocketForwardingClose": - onClose(this); - break; - } - }); - - postVscMessage("websocketForwardingOpen", { url: this.url }); - } - - send(data: string) { - postVscMessage("websocketForwardingMessage", { - message: data, - url: this.url, - }); - } -} - -class WebsocketMessenger extends Messenger { - websocket: WebSocket; - constructor( - websocket: WebSocket, - onMessage: (message: { data: any }) => void, - onOpen: (messenger: Messenger) => void, - onClose: (messenger: Messenger) => void - ) { - super(); - this.websocket = websocket; - - websocket.addEventListener("close", () => { - onClose(this); - }); - - websocket.addEventListener("open", () => { - onOpen(this); - }); - - websocket.addEventListener("message", (event) => { - onMessage(event.data); - }); - } - - static async connect( - url: string, - sessionId: string, - onMessage: (message: { data: any }) => void, - onOpen: (messenger: Messenger) => void, - onClose: (messenger: Messenger) => void - ): Promise { - const ws = new WebSocket(url); - - return new Promise((resolve, reject) => { - ws.addEventListener("open", () => { - resolve(new WebsocketMessenger(ws, onMessage, onOpen, onClose)); - }); - }); - } - - send(data: string) { - this.websocket.send(JSON.stringify(data)); - } -} - -function useContinueWebsocket( - serverUrl: string, - onMessage: (message: { data: any }) => void, - useVscodeMessagePassing: boolean = true -) { +function useContinueNotebookProtocol(useVscodeMessagePassing: boolean = false) { const sessionId = useSelector((state: RootStore) => state.config.sessionId); - const [websocket, setWebsocket] = useState(undefined); + const serverHttpUrl = useSelector((state: RootStore) => state.config.apiUrl); + const [client, setClient] = useState< + ContinueNotebookClientProtocol | undefined + >(undefined); - async function connect() { - while (!sessionId) { - await new Promise((resolve) => setTimeout(resolve, 300)); + useEffect(() => { + if (!sessionId || !serverHttpUrl) { + if (useVscodeMessagePassing) { + postVscMessage("onLoad", {}); + } + setClient(undefined); + return; } - console.log("Creating websocket", sessionId); - console.log("Using vscode message passing", useVscodeMessagePassing); - - const onClose = (messenger: Messenger) => { - console.log("Websocket closed"); - setWebsocket(undefined); - }; - - const onOpen = (messenger: Messenger) => { - console.log("Websocket opened"); - messenger.send(JSON.stringify({ sessionId })); - }; - - const url = - serverUrl.replace("http", "ws") + + const serverUrlWithSessionId = + serverHttpUrl.replace("http", "ws") + "/notebook/ws?session_id=" + encodeURIComponent(sessionId); - const messenger: Messenger = useVscodeMessagePassing - ? new VscodeMessenger(url, onMessage, onOpen, onClose) - : await WebsocketMessenger.connect( - url, - sessionId, - onMessage, - onOpen, - onClose - ); - - setWebsocket(messenger); - - return messenger; - } - - async function getConnection() { - if (!websocket) { - return await connect(); - } - return websocket; - } - - async function send(message: object) { - let ws = await getConnection(); - ws.send(JSON.stringify(message)); - } - - useEffect(() => { - if (!sessionId) { - return; - } - connect(); - }, [sessionId]); - - return { send }; + console.log("Creating websocket", serverUrlWithSessionId); + console.log("Using vscode message passing", useVscodeMessagePassing); + const newClient = new ContinueNotebookClientProtocol( + serverUrlWithSessionId, + useVscodeMessagePassing + ); + setClient(newClient); + }, [sessionId, serverHttpUrl]); + + return client; } -export default useContinueWebsocket; +export default useContinueNotebookProtocol; diff --git a/extension/react-app/src/hooks/vscodeMessenger.ts b/extension/react-app/src/hooks/vscodeMessenger.ts new file mode 100644 index 00000000..746c4302 --- /dev/null +++ b/extension/react-app/src/hooks/vscodeMessenger.ts @@ -0,0 +1,68 @@ +import { postVscMessage } from "../vscode"; +// import { Messenger } from "../../../src/util/messenger"; +import { Messenger } from "./messenger"; + +export class VscodeMessenger extends Messenger { + serverUrl: string; + + constructor(serverUrl: string) { + super(); + this.serverUrl = serverUrl; + postVscMessage("websocketForwardingOpen", { url: this.serverUrl }); + } + + send(messageType: string, data: object) { + postVscMessage("websocketForwardingMessage", { + message: { messageType, data }, + url: this.serverUrl, + }); + } + + onMessageType(messageType: string, callback: (data: object) => void): void { + window.addEventListener("message", (event: any) => { + if (event.data.type === "websocketForwardingMessage") { + if (event.data.message.messageType === messageType) { + callback(event.data.message.data); + } + } + }); + } + + onMessage(callback: (messageType: string, data: any) => void): void { + window.addEventListener("message", (event: any) => { + if (event.data.type === "websocketForwardingMessage") { + callback(event.data.message.messageType, event.data.message.data); + } + }); + } + + sendAndReceive(messageType: string, data: any): Promise { + return new Promise((resolve) => { + const handler = (event: any) => { + if (event.data.type === "websocketForwardingMessage") { + if (event.data.message.messageType === messageType) { + window.removeEventListener("message", handler); + resolve(event.data.message.data); + } + } + }; + window.addEventListener("message", handler); + this.send(messageType, data); + }); + } + + onOpen(callback: () => void): void { + window.addEventListener("message", (event: any) => { + if (event.data.type === "websocketForwardingOpen") { + callback(); + } + }); + } + onClose(callback: () => void): void { + window.addEventListener("message", (event: any) => { + if (event.data.type === "websocketForwardingClose") { + callback(); + } + }); + } +} diff --git a/extension/react-app/src/tabs/notebook.tsx b/extension/react-app/src/tabs/notebook.tsx index a9c69c5b..02c9ff31 100644 --- a/extension/react-app/src/tabs/notebook.tsx +++ b/extension/react-app/src/tabs/notebook.tsx @@ -14,6 +14,7 @@ import StepContainer from "../components/StepContainer"; import { useSelector } from "react-redux"; import { RootStore } from "../redux/store"; import useContinueWebsocket from "../hooks/useWebsocket"; +import useContinueNotebookProtocol from "../hooks/useWebsocket"; let TopNotebookDiv = styled.div` display: grid; @@ -33,8 +34,6 @@ interface NotebookProps { } function Notebook(props: NotebookProps) { - const serverUrl = useSelector((state: RootStore) => state.config.apiUrl); - const [waitingForSteps, setWaitingForSteps] = useState(false); const [userInputQueue, setUserInputQueue] = useState([]); const [history, setHistory] = useState(); @@ -157,30 +156,17 @@ function Notebook(props: NotebookProps) { // } as any // ); - const { send: websocketSend } = useContinueWebsocket(serverUrl, (msg) => { - let data = JSON.parse(msg.data); - if (data.messageType === "state") { - setWaitingForSteps(data.state.active); - setHistory(data.state.history); - setUserInputQueue(data.state.user_input_queue); - } - }); + const client = useContinueNotebookProtocol(); - // useEffect(() => { - // (async () => { - // if (sessionId && props.firstObservation) { - // let resp = await fetch(serverUrl + "/observation", { - // method: "POST", - // headers: new Headers({ - // "x-continue-session-id": sessionId, - // }), - // body: JSON.stringify({ - // observation: props.firstObservation, - // }), - // }); - // } - // })(); - // }, [props.firstObservation]); + useEffect(() => { + console.log("CLIENT ON STATE UPDATE: ", client, client?.onStateUpdate); + client?.onStateUpdate((state) => { + console.log("Received state update: ", state); + setWaitingForSteps(state.active); + setHistory(state.history); + setUserInputQueue(state.user_input_queue); + }); + }, [client]); const mainTextInputRef = useRef(null); @@ -201,14 +187,12 @@ function Notebook(props: NotebookProps) { const onMainTextInput = () => { if (mainTextInputRef.current) { - let value = mainTextInputRef.current.value; + if (!client) return; + let input = mainTextInputRef.current.value; setWaitingForSteps(true); - websocketSend({ - messageType: "main_input", - value: value, - }); + client.sendMainInput(input); setUserInputQueue((queue) => { - return [...queue, value]; + return [...queue, input]; }); mainTextInputRef.current.value = ""; mainTextInputRef.current.style.height = ""; @@ -216,17 +200,20 @@ function Notebook(props: NotebookProps) { }; const onStepUserInput = (input: string, index: number) => { + if (!client) return; console.log("Sending step user input", input, index); - websocketSend({ - messageType: "step_user_input", - value: input, - index, - }); + client.sendStepUserInput(input, index); }; // const iterations = useSelector(selectIterations); return ( + {typeof client === "undefined" && ( + <> + +

Server disconnected

+ + )} {history?.timeline.map((node: HistoryNode, index: number) => { return ( history?.current_index} historyNode={node} onRefinement={(input: string) => { - websocketSend({ - messageType: "refinement_input", - value: input, - index, - }); + client?.sendRefinementInput(input, index); }} onReverse={() => { - websocketSend({ - messageType: "reverse", - index, - }); + client?.reverseToIndex(index); }} /> ); diff --git a/extension/react-app/src/vscode/index.ts b/extension/react-app/src/vscode/index.ts index 7e373cd9..0785aa4d 100644 --- a/extension/react-app/src/vscode/index.ts +++ b/extension/react-app/src/vscode/index.ts @@ -5,6 +5,7 @@ declare const vscode: any; export function postVscMessage(type: string, data: any) { if (typeof vscode === "undefined") { + console.log("Unable to send message: vscode is undefined"); return; } vscode.postMessage({ diff --git a/extension/react-app/tsconfig.json b/extension/react-app/tsconfig.json index 3d0a51a8..940a9359 100644 --- a/extension/react-app/tsconfig.json +++ b/extension/react-app/tsconfig.json @@ -16,6 +16,6 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": ["src"], + "include": ["src", "../src/util/messenger.ts"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/extension/scripts/continuedev-0.1.0-py3-none-any.whl b/extension/scripts/continuedev-0.1.0-py3-none-any.whl index d1483db9..2019c904 100644 Binary files a/extension/scripts/continuedev-0.1.0-py3-none-any.whl and b/extension/scripts/continuedev-0.1.0-py3-none-any.whl differ diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index a0aa560b..712ffe13 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -10,7 +10,7 @@ import { getContinueServerUrl } from "../bridge"; export let extensionContext: vscode.ExtensionContext | undefined = undefined; -export let ideProtocolClient: IdeProtocolClient | undefined = undefined; +export let ideProtocolClient: IdeProtocolClient; export function activateExtension( context: vscode.ExtensionContext, @@ -24,7 +24,7 @@ export function activateExtension( let serverUrl = getContinueServerUrl(); ideProtocolClient = new IdeProtocolClient( - serverUrl.replace("http", "ws") + "/ide/ws", + `${serverUrl.replace("http", "ws")}/ide/ws`, context ); diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 93a471ff..ad6ac71b 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -28,18 +28,7 @@ async function runCommand(cmd: string): Promise<[string, string | undefined]> { return [stdout, stderr]; } -async function getPythonCmdAssumingInstalled() { - const [, stderr] = await runCommand("python3 --version"); - if (stderr) { - return "python"; - } - return "python3"; -} - -async function setupPythonEnv() { - console.log("Setting up python env for Continue extension..."); - // First check that python3 is installed - +async function getPythonPipCommands() { var [stdout, stderr] = await runCommand("python3 --version"); let pythonCmd = "python3"; if (stderr) { @@ -58,28 +47,77 @@ async function setupPythonEnv() { } } let pipCmd = pythonCmd.endsWith("3") ? "pip3" : "pip"; + return [pythonCmd, pipCmd]; +} +function getActivateUpgradeCommands(pythonCmd: string, pipCmd: string) { let activateCmd = ". env/bin/activate"; let pipUpgradeCmd = `${pipCmd} install --upgrade pip`; if (process.platform == "win32") { activateCmd = ".\\env\\Scripts\\activate"; pipUpgradeCmd = `${pythonCmd} -m pip install --upgrade pip`; } + return [activateCmd, pipUpgradeCmd]; +} - let command = `cd ${path.join( +function checkEnvExists() { + const envBinActivatePath = path.join( getExtensionUri().fsPath, - "scripts" - )} && ${pythonCmd} -m venv env && ${activateCmd} && ${pipUpgradeCmd} && ${pipCmd} install -r requirements.txt`; - var [stdout, stderr] = await runCommand(command); - if (stderr) { - throw new Error(stderr); + "scripts", + "env", + "bin", + "activate" + ); + return fs.existsSync(envBinActivatePath); +} + +async function setupPythonEnv() { + console.log("Setting up python env for Continue extension..."); + + // Assemble the command to create the env + const [pythonCmd, pipCmd] = await getPythonPipCommands(); + const [activateCmd, pipUpgradeCmd] = getActivateUpgradeCommands( + pythonCmd, + pipCmd + ); + const createEnvCommand = [ + `cd ${path.join(getExtensionUri().fsPath, "scripts")}`, + `${pythonCmd} -m venv env`, + ].join(" && "); + + // Repeat until it is successfully created (sometimes it fails to generate the bin, need to try again) + while (true) { + const [, stderr] = await runCommand(createEnvCommand); + if (stderr) { + throw new Error(stderr); + } + if (checkEnvExists()) { + break; + } else { + // Remove the env and try again + const removeCommand = `rm -rf ${path.join( + getExtensionUri().fsPath, + "scripts", + "env" + )}`; + await runCommand(removeCommand); + } } console.log( "Successfully set up python env at ", getExtensionUri().fsPath + "/scripts/env" ); - await startContinuePythonServer(); + const installRequirementsCommand = [ + `cd ${path.join(getExtensionUri().fsPath, "scripts")}`, + activateCmd, + pipUpgradeCmd, + `${pipCmd} install -r requirements.txt`, + ].join(" && "); + const [, stderr] = await runCommand(installRequirementsCommand); + if (stderr) { + throw new Error(stderr); + } } function readEnvFile(path: string) { @@ -116,29 +154,19 @@ function writeEnvFile(path: string, key: string, value: string) { } export async function startContinuePythonServer() { + await setupPythonEnv(); + // Check vscode settings let serverUrl = getContinueServerUrl(); if (serverUrl !== "http://localhost:8000") { return; } - let envFile = path.join(getExtensionUri().fsPath, "scripts", ".env"); - let openai_api_key: string | undefined = - readEnvFile(envFile)["OPENAI_API_KEY"]; - while (typeof openai_api_key === "undefined" || openai_api_key === "") { - openai_api_key = await vscode.window.showInputBox({ - prompt: "Enter your OpenAI API key", - placeHolder: "Enter your OpenAI API key", - }); - // Write to .env file - } - writeEnvFile(envFile, "OPENAI_API_KEY", openai_api_key); - console.log("Starting Continue python server..."); // Check if already running by calling /health try { - let response = await fetch(serverUrl + "/health"); + const response = await fetch(serverUrl + "/health"); if (response.status === 200) { console.log("Continue python server already running"); return; @@ -152,15 +180,18 @@ export async function startContinuePythonServer() { pythonCmd = "python"; } + // let command = `cd ${path.join( + // getExtensionUri().fsPath, + // "scripts" + // )} && ${activateCmd} && cd env/lib/python3.11/site-packages && ${pythonCmd} -m continuedev.server.main`; let command = `cd ${path.join( getExtensionUri().fsPath, "scripts" )} && ${activateCmd} && cd .. && ${pythonCmd} -m scripts.run_continue_server`; try { // exec(command); - let child = spawn(command, { + const child = spawn(command, { shell: true, - detached: true, }); child.stdout.on("data", (data: any) => { console.log(`stdout: ${data}`); @@ -194,11 +225,6 @@ export function isPythonEnvSetup(): boolean { return fs.existsSync(path.join(pathToEnvCfg)); } -export async function setupExtensionEnvironment() { - console.log("Setting up environment for Continue extension..."); - await Promise.all([setupPythonEnv()]); -} - export async function downloadPython3() { // Download python3 and return the command to run it (python or python3) let os = process.platform; diff --git a/extension/src/commands.ts b/extension/src/commands.ts index 18f08e31..aeeb4b4f 100644 --- a/extension/src/commands.ts +++ b/extension/src/commands.ts @@ -62,11 +62,11 @@ const commandsMap: { [command: string]: (...args: any) => any } = { "continue.acceptSuggestion": acceptSuggestionCommand, "continue.rejectSuggestion": rejectSuggestionCommand, "continue.openDebugPanel": () => { - ideProtocolClient?.openNotebook(); + ideProtocolClient.openNotebook(); }, "continue.focusContinueInput": async () => { if (!debugPanelWebview) { - await ideProtocolClient?.openNotebook(); + await ideProtocolClient.openNotebook(); } debugPanelWebview?.postMessage({ type: "focusContinueInput", diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 35eb668d..477d1420 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -10,30 +10,28 @@ import { } from "./suggestions"; import { debugPanelWebview, setupDebugPanel } from "./debugPanel"; import { FileEditWithFullContents } from "../schema/FileEditWithFullContents"; -const util = require("util"); -const exec = util.promisify(require("child_process").exec); -const WebSocket = require("ws"); import fs = require("fs"); +import { WebsocketMessenger } from "./util/messenger"; class IdeProtocolClient { - private _ws: WebSocket | null = null; - private _panels: Map = new Map(); - private readonly _serverUrl: string; - private readonly _context: vscode.ExtensionContext; + private messenger: WebsocketMessenger | null = null; + private panels: Map = new Map(); + private readonly context: vscode.ExtensionContext; private _makingEdit = 0; constructor(serverUrl: string, context: vscode.ExtensionContext) { - this._context = context; - this._serverUrl = serverUrl; - let ws = new WebSocket(serverUrl); - this._ws = ws; - ws.onclose = () => { - this._ws = null; - }; - ws.on("message", (data: any) => { - this.handleMessage(JSON.parse(data)); + this.context = context; + + let messenger = new WebsocketMessenger(serverUrl); + this.messenger = messenger; + messenger.onClose(() => { + this.messenger = null; + }); + messenger.onMessage((messageType, data) => { + this.handleMessage(messageType, data); }); + // Setup listeners for any file changes in open editors vscode.workspace.onDidChangeTextDocument((event) => { if (this._makingEdit === 0) { @@ -58,125 +56,52 @@ class IdeProtocolClient { }; } ); - this.send("fileEdits", { fileEdits }); + this.messenger?.send("fileEdits", { fileEdits }); } else { this._makingEdit--; } }); } - async isConnected() { - if (this._ws === null || this._ws.readyState !== WebSocket.OPEN) { - let ws = new WebSocket(this._serverUrl); - ws.onclose = () => { - this._ws = null; - }; - ws.on("message", (data: any) => { - this.handleMessage(JSON.parse(data)); - }); - this._ws = ws; - - return new Promise((resolve, reject) => { - ws.addEventListener("open", () => { - resolve(null); - }); - }); - } - } - - async startCore() { - var { stdout, stderr } = await exec( - "cd /Users/natesesti/Desktop/continue/continue && poetry shell" - ); - if (stderr) { - throw new Error(stderr); - } - var { stdout, stderr } = await exec( - "cd .. && uvicorn continue.src.server.main:app --reload --reload-dir continue" - ); - if (stderr) { - throw new Error(stderr); - } - var { stdout, stderr } = await exec("python3 -m continue.src.libs.ide"); - if (stderr) { - throw new Error(stderr); - } - } - - async send(messageType: string, data: object) { - await this.isConnected(); - let msg = JSON.stringify({ messageType, ...data }); - this._ws!.send(msg); - console.log("Sent message", msg); - } - - async receiveMessage(messageType: string): Promise { - await this.isConnected(); - console.log("Connected to websocket"); - return await new Promise((resolve, reject) => { - if (!this._ws) { - reject("Not connected to websocket"); - } - this._ws!.onmessage = (event: any) => { - let message = JSON.parse(event.data); - console.log("RECEIVED MESSAGE", message); - if (message.messageType === messageType) { - resolve(message); - } - }; - }); - } - - async sendAndReceive(message: any, messageType: string): Promise { - try { - await this.send(messageType, message); - let msg = await this.receiveMessage(messageType); - console.log("Received message", msg); - return msg; - } catch (e) { - console.log("Error sending message", e); - } - } - - async handleMessage(message: any) { - switch (message.messageType) { + async handleMessage(messageType: string, data: any) { + switch (messageType) { case "highlightedCode": - this.send("highlightedCode", { + this.messenger?.send("highlightedCode", { highlightedCode: this.getHighlightedCode(), }); break; case "workspaceDirectory": - this.send("workspaceDirectory", { + this.messenger?.send("workspaceDirectory", { workspaceDirectory: this.getWorkspaceDirectory(), }); case "openFiles": - this.send("openFiles", { + this.messenger?.send("openFiles", { openFiles: this.getOpenFiles(), }); break; case "readFile": - this.send("readFile", { - contents: this.readFile(message.filepath), + this.messenger?.send("readFile", { + contents: this.readFile(data.filepath), }); break; case "editFile": - let fileEdit = await this.editFile(message.edit); - this.send("editFile", { + const fileEdit = await this.editFile(data.edit); + this.messenger?.send("editFile", { fileEdit, }); break; case "saveFile": - this.saveFile(message.filepath); + this.saveFile(data.filepath); break; case "setFileOpen": - this.openFile(message.filepath); + this.openFile(data.filepath); // TODO: Close file break; case "openNotebook": case "connected": break; default: - throw Error("Unknown message type:" + message.messageType); + throw Error("Unknown message type:" + messageType); } } getWorkspaceDirectory() { @@ -209,17 +134,20 @@ class IdeProtocolClient { // Initiate Request closeNotebook(sessionId: string) { - this._panels.get(sessionId)?.dispose(); - this._panels.delete(sessionId); + this.panels.get(sessionId)?.dispose(); + this.panels.delete(sessionId); } async openNotebook() { console.log("OPENING NOTEBOOK"); - let resp = await this.sendAndReceive({}, "openNotebook"); - let sessionId = resp.sessionId; + if (this.messenger === null) { + console.log("MESSENGER IS NULL"); + } + const resp = await this.messenger?.sendAndReceive("openNotebook", {}); + const sessionId = resp.sessionId; console.log("SESSION ID", sessionId); - let column = getRightViewColumn(); + const column = getRightViewColumn(); const panel = vscode.window.createWebviewPanel( "continue.debugPanelView", "Continue", @@ -231,9 +159,9 @@ class IdeProtocolClient { ); // And set its HTML content - panel.webview.html = setupDebugPanel(panel, this._context, sessionId); + panel.webview.html = setupDebugPanel(panel, this.context, sessionId); - this._panels.set(sessionId, panel); + this.panels.set(sessionId, panel); } acceptRejectSuggestion(accept: boolean, key: SuggestionRanges) { diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index 4192595c..a295085f 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -16,7 +16,6 @@ import { import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; import { RangeInFile, SerializedDebugContext } from "./client"; import { addFileSystemToDebugContext } from "./util/util"; -const WebSocket = require("ws"); class StreamManager { private _fullText: string = ""; @@ -108,15 +107,15 @@ class WebsocketConnection { this._onOpen = onOpen; this._onClose = onClose; - this._ws.onmessage = (event) => { + this._ws.addEventListener("message", (event) => { this._onMessage(event.data); - }; - this._ws.onclose = () => { + }); + this._ws.addEventListener("close", () => { this._onClose(); - }; - this._ws.onopen = () => { + }); + this._ws.addEventListener("open", () => { this._onOpen(); - }; + }); } public send(message: string) { @@ -230,6 +229,19 @@ export function setupDebugPanel( apiUrl: getContinueServerUrl(), sessionId, }); + + // // Listen for changes to server URL in settings + // vscode.workspace.onDidChangeConfiguration((event) => { + // if (event.affectsConfiguration("continue.serverUrl")) { + // debugPanelWebview?.postMessage({ + // type: "onLoad", + // vscMachineId: vscode.env.machineId, + // apiUrl: getContinueServerUrl(), + // sessionId, + // }); + // } + // }); + break; } diff --git a/extension/src/extension.ts b/extension/src/extension.ts index e0b94278..88af0d19 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -4,7 +4,6 @@ import * as vscode from "vscode"; import { - setupExtensionEnvironment, isPythonEnvSetup, startContinuePythonServer, } from "./activation/environmentSetup"; @@ -26,11 +25,7 @@ export function activate(context: vscode.ExtensionContext) { cancellable: false, }, async () => { - if (isPythonEnvSetup()) { - await startContinuePythonServer(); - } else { - await setupExtensionEnvironment(); - } + await startContinuePythonServer(); dynamicImportAndActivate(context, true); } ); diff --git a/extension/src/test/runTest.ts b/extension/src/test/runTest.ts index 27b3ceb2..e810ed5b 100644 --- a/extension/src/test/runTest.ts +++ b/extension/src/test/runTest.ts @@ -1,23 +1,23 @@ -import * as path from 'path'; +import * as path from "path"; -import { runTests } from '@vscode/test-electron'; +import { runTests } from "@vscode/test-electron"; async function main() { - try { - // The folder containing the Extension Manifest package.json - // Passed to `--extensionDevelopmentPath` - const extensionDevelopmentPath = path.resolve(__dirname, '../../'); + try { + // The folder containing the Extension Manifest package.json + // Passed to `--extensionDevelopmentPath` + const extensionDevelopmentPath = path.resolve(__dirname, "../../"); - // The path to test runner - // Passed to --extensionTestsPath - const extensionTestsPath = path.resolve(__dirname, './suite/index'); + // The path to test runner + // Passed to --extensionTestsPath + const extensionTestsPath = path.resolve(__dirname, "./suite/index"); - // Download VS Code, unzip it and run the integration test - await runTests({ extensionDevelopmentPath, extensionTestsPath }); - } catch (err) { - console.error('Failed to run tests'); - process.exit(1); - } + // Download VS Code, unzip it and run the integration test + await runTests({ extensionDevelopmentPath, extensionTestsPath }); + } catch (err) { + console.error("Failed to run tests"); + process.exit(1); + } } main(); diff --git a/extension/src/util/messenger.ts b/extension/src/util/messenger.ts new file mode 100644 index 00000000..6f8bb29d --- /dev/null +++ b/extension/src/util/messenger.ts @@ -0,0 +1,108 @@ +console.log("Websocket import"); +const WebSocket = require("ws"); + +export abstract class Messenger { + abstract send(messageType: string, data: object): void; + + abstract onMessageType( + messageType: string, + callback: (data: object) => void + ): void; + + abstract onMessage(callback: (messageType: string, data: any) => void): void; + + abstract onOpen(callback: () => void): void; + + abstract onClose(callback: () => void): void; + + abstract sendAndReceive(messageType: string, data: any): Promise; +} + +export class WebsocketMessenger extends Messenger { + websocket: WebSocket; + private onMessageListeners: { + [messageType: string]: ((data: object) => void)[]; + } = {}; + private onOpenListeners: (() => void)[] = []; + private onCloseListeners: (() => void)[] = []; + private serverUrl: string; + + _newWebsocket(): WebSocket { + // // Dynamic import, because WebSocket is builtin with browser, but not with node. And can't use require in browser. + // if (typeof process === "object") { + // console.log("Using node"); + // // process is only available in Node + // var WebSocket = require("ws"); + // } + + const newWebsocket = new WebSocket(this.serverUrl); + for (const listener of this.onOpenListeners) { + this.onOpen(listener); + } + for (const listener of this.onCloseListeners) { + this.onClose(listener); + } + for (const messageType in this.onMessageListeners) { + for (const listener of this.onMessageListeners[messageType]) { + this.onMessageType(messageType, listener); + } + } + return newWebsocket; + } + + constructor(serverUrl: string) { + super(); + this.serverUrl = serverUrl; + this.websocket = this._newWebsocket(); + } + + send(messageType: string, data: object) { + const payload = JSON.stringify({ messageType, data }); + if (this.websocket.readyState === this.websocket.OPEN) { + this.websocket.send(payload); + } else { + if (this.websocket.readyState !== this.websocket.CONNECTING) { + this.websocket = this._newWebsocket(); + } + this.websocket.addEventListener("open", () => { + this.websocket.send(payload); + }); + } + } + + sendAndReceive(messageType: string, data: any): Promise { + return new Promise((resolve, reject) => { + const eventListener = (data: any) => { + // THIS ISN"T GETTING CALLED + resolve(data); + this.websocket.removeEventListener("message", eventListener); + }; + this.onMessageType(messageType, eventListener); + this.send(messageType, data); + }); + } + + onMessageType(messageType: string, callback: (data: any) => void): void { + this.websocket.addEventListener("message", (event: any) => { + const msg = JSON.parse(event.data); + if (msg.messageType === messageType) { + callback(msg.data); + } + }); + } + + onMessage(callback: (messageType: string, data: any) => void): void { + this.websocket.addEventListener("message", (event) => { + const msg = JSON.parse(event.data); + callback(msg.messageType, msg.data); + }); + } + + onOpen(callback: () => void): void { + this.websocket.addEventListener("open", callback); + } + + onClose(callback: () => void): void { + this.websocket.addEventListener("close", callback); + } +} -- cgit v1.2.3-70-g09d2 From a4e9e7764ee42a743dbfbaedb520cc70daa23ec4 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Wed, 31 May 2023 20:27:52 -0400 Subject: fixing up vscodemessenger --- continuedev/src/continuedev/server/main.py | 3 +- continuedev/src/continuedev/server/notebook.py | 14 ++--- .../src/continuedev/server/session_manager.py | 15 ------ extension/package-lock.json | 4 +- extension/package.json | 4 +- .../src/hooks/useContinueNotebookProtocol.ts | 2 +- extension/react-app/src/hooks/useWebsocket.ts | 2 +- extension/react-app/src/hooks/vscodeMessenger.ts | 14 +++-- .../scripts/continuedev-0.1.0-py3-none-any.whl | Bin 56070 -> 56086 bytes extension/src/activation/environmentSetup.ts | 59 +++++++++++---------- extension/src/debugPanel.ts | 5 ++ logging.ini | 27 ---------- logging.yaml | 30 +++++++++++ 13 files changed, 91 insertions(+), 88 deletions(-) delete mode 100644 logging.ini create mode 100644 logging.yaml diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index e87d5fa9..1977bfdd 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -32,7 +32,8 @@ args = parser.parse_args() def run_server(): - uvicorn.run(app, host="0.0.0.0", port=args.port) + uvicorn.run(app, host="0.0.0.0", port=args.port, + log_config="logging.yaml") if __name__ == "__main__": diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index edb61a45..ab9211a8 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -51,14 +51,17 @@ class NotebookProtocolServer(AbstractNotebookProtocolServer): def __init__(self, session: Session): self.session = session - async def _send_json(self, data: Any): - await self.websocket.send_json(data) + async def _send_json(self, message_type: str, data: Any): + await self.websocket.send_json({ + "message_type": 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(data) + await self._send_json(message_type, data) resp = await self._receive_json(message_type) return resp_model.parse_obj(resp) @@ -77,8 +80,7 @@ class NotebookProtocolServer(AbstractNotebookProtocolServer): async def send_state_update(self): state = self.session.agent.get_full_state().dict() - await self._send_json({ - "messageType": "state_update", + await self._send_json("state_update", { "state": state }) @@ -112,7 +114,7 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we await protocol.send_state_update() while AppStatus.should_exit is False: - message = await websocket.receive_json() + message = await websocket.receive_text() print("Received message", message) if type(message) is str: message = json.loads(message) diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index b48c21b7..c5715034 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -77,25 +77,10 @@ class SessionManager: print(f"Session {session_id} has no websocket") return - async def a(): - await self.sessions[session_id].ws.send_json({ - "messageType": message_type, - "data": data - }) - - # Run coroutine in background await self.sessions[session_id].ws.send_json({ "messageType": message_type, "data": data }) - return - if self._event_loop is None or self._event_loop.is_closed(): - self._event_loop = asyncio.new_event_loop() - self._event_loop.run_until_complete(a()) - self._event_loop.close() - else: - self._event_loop.run_until_complete(a()) - self._event_loop.close() session_manager = SessionManager() diff --git a/extension/package-lock.json b/extension/package-lock.json index 04af09d3..cd956286 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.5", + "version": "0.0.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.5", + "version": "0.0.7", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index c96655a9..1a8d9004 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.5", + "version": "0.0.7", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -162,7 +162,7 @@ "test": "node ./out/test/runTest.js", "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.5.vsix && cp ./config/dev_config.json ./config/config.json", "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.0-py3-none-any.whl ../extension/scripts/continuedev-0.1.0-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", - "install-extension": "code --install-extension ./build/continue-0.0.5.vsix", + "install-extension": "code --install-extension ./build/continue-0.0.6.vsix", "uninstall": "code --uninstall-extension .continue", "reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension" }, diff --git a/extension/react-app/src/hooks/useContinueNotebookProtocol.ts b/extension/react-app/src/hooks/useContinueNotebookProtocol.ts index d5ffbf09..b785cc84 100644 --- a/extension/react-app/src/hooks/useContinueNotebookProtocol.ts +++ b/extension/react-app/src/hooks/useContinueNotebookProtocol.ts @@ -10,7 +10,7 @@ class ContinueNotebookClientProtocol extends AbstractContinueNotebookClientProto constructor( serverUrlWithSessionId: string, - useVscodeMessagePassing: boolean = false + useVscodeMessagePassing: boolean ) { super(); this.serverUrlWithSessionId = serverUrlWithSessionId; diff --git a/extension/react-app/src/hooks/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts index b98be577..016fa17d 100644 --- a/extension/react-app/src/hooks/useWebsocket.ts +++ b/extension/react-app/src/hooks/useWebsocket.ts @@ -4,7 +4,7 @@ import { useSelector } from "react-redux"; import ContinueNotebookClientProtocol from "./useContinueNotebookProtocol"; import { postVscMessage } from "../vscode"; -function useContinueNotebookProtocol(useVscodeMessagePassing: boolean = false) { +function useContinueNotebookProtocol(useVscodeMessagePassing: boolean = true) { const sessionId = useSelector((state: RootStore) => state.config.sessionId); const serverHttpUrl = useSelector((state: RootStore) => state.config.apiUrl); const [client, setClient] = useState< diff --git a/extension/react-app/src/hooks/vscodeMessenger.ts b/extension/react-app/src/hooks/vscodeMessenger.ts index 746c4302..e330db57 100644 --- a/extension/react-app/src/hooks/vscodeMessenger.ts +++ b/extension/react-app/src/hooks/vscodeMessenger.ts @@ -21,8 +21,10 @@ export class VscodeMessenger extends Messenger { onMessageType(messageType: string, callback: (data: object) => void): void { window.addEventListener("message", (event: any) => { if (event.data.type === "websocketForwardingMessage") { - if (event.data.message.messageType === messageType) { - callback(event.data.message.data); + console.log("VS CODE SENT DATA: ", event.data); + const data = JSON.parse(event.data.data); + if (data.messageType === messageType) { + callback(data.data); } } }); @@ -31,7 +33,8 @@ export class VscodeMessenger extends Messenger { onMessage(callback: (messageType: string, data: any) => void): void { window.addEventListener("message", (event: any) => { if (event.data.type === "websocketForwardingMessage") { - callback(event.data.message.messageType, event.data.message.data); + const data = JSON.parse(event.data.data); + callback(data.messageType, data.data); } }); } @@ -40,9 +43,10 @@ export class VscodeMessenger extends Messenger { return new Promise((resolve) => { const handler = (event: any) => { if (event.data.type === "websocketForwardingMessage") { - if (event.data.message.messageType === messageType) { + const data = JSON.parse(event.data.data); + if (data.messageType === messageType) { window.removeEventListener("message", handler); - resolve(event.data.message.data); + resolve(data.data); } } }; diff --git a/extension/scripts/continuedev-0.1.0-py3-none-any.whl b/extension/scripts/continuedev-0.1.0-py3-none-any.whl index 2019c904..8a35e0ec 100644 Binary files a/extension/scripts/continuedev-0.1.0-py3-none-any.whl and b/extension/scripts/continuedev-0.1.0-py3-none-any.whl differ diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index ad6ac71b..db2c5523 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -10,6 +10,7 @@ import { getContinueServerUrl } from "../bridge"; import fetch from "node-fetch"; async function runCommand(cmd: string): Promise<[string, string | undefined]> { + console.log("Running command: ", cmd); var stdout: any = ""; var stderr: any = ""; try { @@ -61,14 +62,16 @@ function getActivateUpgradeCommands(pythonCmd: string, pipCmd: string) { } function checkEnvExists() { - const envBinActivatePath = path.join( + const envBinPath = path.join( getExtensionUri().fsPath, "scripts", "env", - "bin", - "activate" + "bin" + ); + return ( + fs.existsSync(envBinPath + "/activate") && + fs.existsSync(envBinPath + "/pip") ); - return fs.existsSync(envBinActivatePath); } async function setupPythonEnv() { @@ -180,34 +183,34 @@ export async function startContinuePythonServer() { pythonCmd = "python"; } - // let command = `cd ${path.join( - // getExtensionUri().fsPath, - // "scripts" - // )} && ${activateCmd} && cd env/lib/python3.11/site-packages && ${pythonCmd} -m continuedev.server.main`; let command = `cd ${path.join( getExtensionUri().fsPath, "scripts" )} && ${activateCmd} && cd .. && ${pythonCmd} -m scripts.run_continue_server`; - try { - // exec(command); - const child = spawn(command, { - shell: true, - }); - child.stdout.on("data", (data: any) => { - console.log(`stdout: ${data}`); - }); - child.stderr.on("data", (data: any) => { - console.log(`stderr: ${data}`); - }); - child.on("error", (error: any) => { - console.log(`error: ${error.message}`); - }); - } catch (e) { - console.log("Failed to start Continue python server", e); - } - // Sleep for 3 seconds to give the server time to start - await new Promise((resolve) => setTimeout(resolve, 3000)); - console.log("Successfully started Continue python server"); + + return new Promise((resolve, reject) => { + try { + const child = spawn(command, { + shell: true, + }); + child.stdout.on("data", (data: any) => { + console.log(`stdout: ${data}`); + }); + child.stderr.on("data", (data: any) => { + console.log(`stderr: ${data}`); + if (data.includes("Uvicorn running on")) { + console.log("Successfully started Continue python server"); + resolve(null); + } + }); + child.on("error", (error: any) => { + console.log(`error: ${error.message}`); + }); + } catch (e) { + console.log("Failed to start Continue python server", e); + reject(); + } + }); } async function installNodeModules() { diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index a295085f..da29a52c 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -16,6 +16,7 @@ import { import { sendTelemetryEvent, TelemetryEvent } from "./telemetry"; import { RangeInFile, SerializedDebugContext } from "./client"; import { addFileSystemToDebugContext } from "./util/util"; +const WebSocket = require("ws"); class StreamManager { private _fullText: string = ""; @@ -119,6 +120,9 @@ class WebsocketConnection { } public send(message: string) { + if (typeof message !== "string") { + message = JSON.stringify(message); + } this._ws.send(message); } @@ -190,6 +194,7 @@ export function setupDebugPanel( async function connectWebsocket(url: string) { return new Promise((resolve, reject) => { const onMessage = (message: any) => { + console.log("websocket message", message); panel.webview.postMessage({ type: "websocketForwardingMessage", url, diff --git a/logging.ini b/logging.ini deleted file mode 100644 index 5b478619..00000000 --- a/logging.ini +++ /dev/null @@ -1,27 +0,0 @@ -[loggers] -keys=root - -[handlers] -keys=logfile,logconsole - -[formatters] -keys=logformatter - -[logger_root] -level=INFO -handlers=logfile, logconsole - -[formatter_logformatter] -format=[%(asctime)s.%(msecs)03d] %(levelname)s [%(thread)d] - %(message)s - -[handler_logfile] -class=handlers.RotatingFileHandler -level=INFO -args=('logfile.log','a') -formatter=logformatter - -[handler_logconsole] -class=handlers.logging.StreamHandler -level=INFO -args=() -formatter=logformatter \ No newline at end of file diff --git a/logging.yaml b/logging.yaml new file mode 100644 index 00000000..391041ef --- /dev/null +++ b/logging.yaml @@ -0,0 +1,30 @@ +version: 1 +disable_existing_loggers: False +formatters: + default: + (): 'uvicorn.logging.DefaultFormatter' + fmt: '%(asctime)s %(levelprefix)-9s %(name)s -: %(message)s' + access: + (): 'uvicorn.logging.AccessFormatter' + fmt: '%(asctime)s %(levelprefix)-9s %(name)s -: %(client_addr)s - "%(request_line)s" %(status_code)s' +handlers: + default: + class: logging.StreamHandler + formatter: default + stream: ext://sys.stderr + access: + class: logging.StreamHandler + formatter: access + stream: ext://sys.stdout +loggers: + uvicorn: + level: INFO + handlers: + - default + uvicorn.error: + level: INFO + uvicorn.access: + level: INFO + propagate: False + handlers: + - access \ No newline at end of file -- cgit v1.2.3-70-g09d2 From d6c78e357fb1010f4243a9a1fb53f6457209d192 Mon Sep 17 00:00:00 2001 From: Nate Sesti <33237525+sestinj@users.noreply.github.com> Date: Thu, 1 Jun 2023 00:45:38 +0000 Subject: logging.yaml in the right place --- extension/logging.yaml | 30 ++++++++++++++++++++++++++++++ logging.yaml | 30 ------------------------------ 2 files changed, 30 insertions(+), 30 deletions(-) create mode 100644 extension/logging.yaml delete mode 100644 logging.yaml diff --git a/extension/logging.yaml b/extension/logging.yaml new file mode 100644 index 00000000..391041ef --- /dev/null +++ b/extension/logging.yaml @@ -0,0 +1,30 @@ +version: 1 +disable_existing_loggers: False +formatters: + default: + (): 'uvicorn.logging.DefaultFormatter' + fmt: '%(asctime)s %(levelprefix)-9s %(name)s -: %(message)s' + access: + (): 'uvicorn.logging.AccessFormatter' + fmt: '%(asctime)s %(levelprefix)-9s %(name)s -: %(client_addr)s - "%(request_line)s" %(status_code)s' +handlers: + default: + class: logging.StreamHandler + formatter: default + stream: ext://sys.stderr + access: + class: logging.StreamHandler + formatter: access + stream: ext://sys.stdout +loggers: + uvicorn: + level: INFO + handlers: + - default + uvicorn.error: + level: INFO + uvicorn.access: + level: INFO + propagate: False + handlers: + - access \ No newline at end of file diff --git a/logging.yaml b/logging.yaml deleted file mode 100644 index 391041ef..00000000 --- a/logging.yaml +++ /dev/null @@ -1,30 +0,0 @@ -version: 1 -disable_existing_loggers: False -formatters: - default: - (): 'uvicorn.logging.DefaultFormatter' - fmt: '%(asctime)s %(levelprefix)-9s %(name)s -: %(message)s' - access: - (): 'uvicorn.logging.AccessFormatter' - fmt: '%(asctime)s %(levelprefix)-9s %(name)s -: %(client_addr)s - "%(request_line)s" %(status_code)s' -handlers: - default: - class: logging.StreamHandler - formatter: default - stream: ext://sys.stderr - access: - class: logging.StreamHandler - formatter: access - stream: ext://sys.stdout -loggers: - uvicorn: - level: INFO - handlers: - - default - uvicorn.error: - level: INFO - uvicorn.access: - level: INFO - propagate: False - handlers: - - access \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 3349f9726606e53e181e5c499106e78f4538ed53 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Wed, 31 May 2023 20:55:52 -0400 Subject: small script corrections --- extension/package.json | 2 +- extension/scripts/install_from_source.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/package.json b/extension/package.json index 1a8d9004..324ea8dd 100644 --- a/extension/package.json +++ b/extension/package.json @@ -160,7 +160,7 @@ "pretest": "npm run compile && npm run lint", "lint": "eslint src --ext ts", "test": "node ./out/test/runTest.js", - "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.5.vsix && cp ./config/dev_config.json ./config/config.json", + "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && cp ./config/dev_config.json ./config/config.json", "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.0-py3-none-any.whl ../extension/scripts/continuedev-0.1.0-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", "install-extension": "code --install-extension ./build/continue-0.0.6.vsix", "uninstall": "code --uninstall-extension .continue", diff --git a/extension/scripts/install_from_source.py b/extension/scripts/install_from_source.py index 28f382b0..bbb86797 100644 --- a/extension/scripts/install_from_source.py +++ b/extension/scripts/install_from_source.py @@ -29,7 +29,7 @@ def main(): print("Poetry is required for Continue but is not installed on your machine. See https://python-poetry.org/docs/#installation to download the latest version, then try again.") return - resp = run("cd ../../continuedev; poetry run typegen") + resp = run("cd ../../continuedev; poetry install; poetry run typegen") resp = run( "cd ..; npm i; cd react-app; npm i; cd ..; npm run full-package") -- cgit v1.2.3-70-g09d2 From 1fa14d27441962cf5ffc5d16ddc4c8aff035c36b Mon Sep 17 00:00:00 2001 From: Nate Sesti <33237525+sestinj@users.noreply.github.com> Date: Wed, 31 May 2023 21:22:46 -0400 Subject: Create devcontainer.json --- .devcontainer/devcontainer.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..be682b50 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,6 @@ +{ + "image": "mcr.microsoft.com/devcontainers/universal:2", + "features": { + "postCreateCommand": "curl -sSL https://install.python-poetry.org | python3 -" + } +} -- cgit v1.2.3-70-g09d2 From b5920fe18afd69992a2cbb77bac75fc25be544d9 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Wed, 31 May 2023 22:18:35 -0400 Subject: smaller vsix --- extension/.vscodeignore | 13 ++++++++++++- extension/package-lock.json | 4 ++-- extension/package.json | 4 ++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/extension/.vscodeignore b/extension/.vscodeignore index e9db3561..c1a09172 100644 --- a/extension/.vscodeignore +++ b/extension/.vscodeignore @@ -11,4 +11,15 @@ vsc-extension-quickstart.md **/*.ts scripts/env/** scripts/.env -**/.env \ No newline at end of file +**/.env +**/env +**/node_modules + +react-app/node_modules +react-app/public +react-app/src + +**/.pytest_cache +**/__pycache__ + +scripts/data \ No newline at end of file diff --git a/extension/package-lock.json b/extension/package-lock.json index cd956286..8662ef49 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.7", + "version": "0.0.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.7", + "version": "0.0.9", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 324ea8dd..f84585fb 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.7", + "version": "0.0.9", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -162,7 +162,7 @@ "test": "node ./out/test/runTest.js", "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && cp ./config/dev_config.json ./config/config.json", "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.0-py3-none-any.whl ../extension/scripts/continuedev-0.1.0-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", - "install-extension": "code --install-extension ./build/continue-0.0.6.vsix", + "install-extension": "code --install-extension ./build/continue-0.0.8.vsix", "uninstall": "code --uninstall-extension .continue", "reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension" }, -- cgit v1.2.3-70-g09d2 From ea5d50af9ba84242c25e82069d86c08ac039e543 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Thu, 1 Jun 2023 00:09:19 -0400 Subject: Polishing for dlt codespace and !config! --- continuedev/src/continuedev/core/config.py | 29 ++++++++++++++++++++ continuedev/src/continuedev/core/policy.py | 9 +++++-- continuedev/src/continuedev/core/sdk.py | 17 ++++++++++++ .../src/continuedev/libs/steps/draft/dlt.py | 4 +-- continuedev/src/continuedev/libs/steps/main.py | 20 ++++++++++++++ .../src/continuedev/libs/steps/steps_on_startup.py | 30 +++++++++++++++++++++ continuedev/src/continuedev/server/main.py | 8 ++++-- continuedev/src/continuedev/server/notebook.py | 2 +- extension/react-app/src/hooks/vscodeMessenger.ts | 1 - .../scripts/continuedev-0.1.0-py3-none-any.whl | Bin 56086 -> 57363 bytes extension/src/activation/activate.ts | 6 ++--- extension/src/activation/environmentSetup.ts | 2 ++ 12 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 continuedev/src/continuedev/core/config.py create mode 100644 continuedev/src/continuedev/libs/steps/steps_on_startup.py diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py new file mode 100644 index 00000000..e62f0e4f --- /dev/null +++ b/continuedev/src/continuedev/core/config.py @@ -0,0 +1,29 @@ +import json +import os +from pydantic import BaseModel +from typing import List, Optional, Dict +import yaml + + +class ContinueConfig(BaseModel): + """ + A pydantic class for the continue config file. + """ + steps_on_startup: Optional[Dict[str, Dict]] = {} + server_url: Optional[str] = None + + +def load_config(config_file: str) -> ContinueConfig: + """ + Load the config file and return a ContinueConfig object. + """ + _, ext = os.path.splitext(config_file) + if ext == '.yaml': + with open(config_file, 'r') as f: + config_dict = yaml.safe_load(f) + elif ext == '.json': + with open(config_file, 'r') as f: + config_dict = json.load(f) + else: + raise ValueError(f'Unknown config file extension: {ext}') + return ContinueConfig(**config_dict) diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index c0ba0f4f..9f68515f 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -1,9 +1,10 @@ from typing import List, Tuple, Type -from ..libs.steps.ty import CreatePipelineStep +from ..libs.steps.steps_on_startup import StepsOnStartupStep +from ..libs.steps.draft.dlt import CreatePipelineStep from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation -from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep +from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep, MessageStep, EmptyStep from ..libs.steps.nate import WritePytestsStep, CreateTableStep # from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..libs.steps.continue_step import ContinueStepStep @@ -13,6 +14,10 @@ class DemoPolicy(Policy): ran_code_last: bool = False def next(self, history: History) -> Step: + # At the very start, run initial Steps spcecified in the config + if history.get_current() is None: + return MessageStep(message="Welcome to Continue!") >> StepsOnStartupStep() + observation = history.last_observation() if observation is not None and isinstance(observation, UserInputObservation): # This could be defined with ObservationTypePolicy. Ergonomics not right though. diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 6ae0be04..4d82a1ae 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -1,5 +1,7 @@ import os from typing import Coroutine, Union + +from .config import ContinueConfig, load_config from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory from ..models.filesystem import RangeInFile from ..libs.llm import LLM @@ -106,3 +108,18 @@ class ContinueSDK: val = (await self.run_step(WaitForUserInputStep(prompt=prompt))).text save_env_var(env_var, val) return val + + async def get_config(self) -> ContinueConfig: + dir = await self.ide.getWorkspaceDirectory() + yaml_path = os.path.join(dir, 'continue.yaml') + json_path = os.path.join(dir, 'continue.json') + if os.path.exists(yaml_path): + return load_config(yaml_path) + elif os.path.exists(json_path): + return load_config(json_path) + else: + return ContinueConfig() + + def set_loading_message(self, message: str): + # self.__agent.set_loading_message(message) + raise NotImplementedError() diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index 460aa0cc..778ced1d 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -2,7 +2,7 @@ from textwrap import dedent from ....models.filesystem_edit import AddFile from ....core.main import Step from ....core.sdk import ContinueSDK -from ..main import WaitForUserInputStep +from ..core.core import WaitForUserInputStep class SetupPipelineStep(Step): @@ -77,6 +77,6 @@ class CreatePipelineStep(Step): async def run(self, sdk: ContinueSDK): await sdk.run_step( WaitForUserInputStep(prompt="What API do you want to load data from?") >> - SetupPipelineStep() >> + SetupPipelineStep(api_description="WeatherAPI.com API") >> ValidatePipelineStep() ) diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 70c0d4b8..d31db0eb 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -331,3 +331,23 @@ class SolveTracebackStep(Step): await sdk.run_step(EditCodeStep( range_in_files=range_in_files, prompt=prompt)) return None + + +class MessageStep(Step): + message: str + + async def describe(self, models: Models) -> Coroutine[str, None, None]: + return self.message + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + pass + + +class EmptyStep(Step): + hide: bool = True + + async def describe(self, models: Models) -> Coroutine[str, None, None]: + return "" + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + pass diff --git a/continuedev/src/continuedev/libs/steps/steps_on_startup.py b/continuedev/src/continuedev/libs/steps/steps_on_startup.py new file mode 100644 index 00000000..fd1eb8f0 --- /dev/null +++ b/continuedev/src/continuedev/libs/steps/steps_on_startup.py @@ -0,0 +1,30 @@ + + +from ...core.main import ContinueSDK, Models, Step +from .main import UserInputStep +from .draft.dlt import CreatePipelineStep + + +step_name_to_step_class = { + "UserInputStep": UserInputStep, + "CreatePipelineStep": CreatePipelineStep +} + + +class StepsOnStartupStep(Step): + hide: bool = True + + async def describe(self, models: Models): + return "Running steps on startup" + + async def run(self, sdk: ContinueSDK): + steps_descriptions = (await sdk.get_config()).steps_on_startup + + for step_name, step_params in steps_descriptions.items(): + try: + step = step_name_to_step_class[step_name](**step_params) + except: + print( + f"Incorrect parameters for step {step_name}. Parameters provided were: {step_params}") + continue + await sdk.run_step(step) diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index 1977bfdd..1ffe1450 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -1,3 +1,4 @@ +import os from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from .ide import router as ide_router @@ -32,8 +33,11 @@ args = parser.parse_args() def run_server(): - uvicorn.run(app, host="0.0.0.0", port=args.port, - log_config="logging.yaml") + if os.path.exists("logging.yaml"): + uvicorn.run(app, host="0.0.0.0", port=args.port, + log_config="logging.yaml") + else: + uvicorn.run(app, host="0.0.0.0", port=args.port) if __name__ == "__main__": diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index ab9211a8..9ca510dd 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -53,7 +53,7 @@ class NotebookProtocolServer(AbstractNotebookProtocolServer): async def _send_json(self, message_type: str, data: Any): await self.websocket.send_json({ - "message_type": message_type, + "messageType": message_type, "data": data }) diff --git a/extension/react-app/src/hooks/vscodeMessenger.ts b/extension/react-app/src/hooks/vscodeMessenger.ts index e330db57..ba19586b 100644 --- a/extension/react-app/src/hooks/vscodeMessenger.ts +++ b/extension/react-app/src/hooks/vscodeMessenger.ts @@ -21,7 +21,6 @@ export class VscodeMessenger extends Messenger { onMessageType(messageType: string, callback: (data: object) => void): void { window.addEventListener("message", (event: any) => { if (event.data.type === "websocketForwardingMessage") { - console.log("VS CODE SENT DATA: ", event.data); const data = JSON.parse(event.data.data); if (data.messageType === messageType) { callback(data.data); diff --git a/extension/scripts/continuedev-0.1.0-py3-none-any.whl b/extension/scripts/continuedev-0.1.0-py3-none-any.whl index 8a35e0ec..6cda6f2d 100644 Binary files a/extension/scripts/continuedev-0.1.0-py3-none-any.whl and b/extension/scripts/continuedev-0.1.0-py3-none-any.whl differ diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index 712ffe13..f8f3c65a 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -62,9 +62,9 @@ export function activateExtension( ideProtocolClient?.openNotebook(); }); } else { - // ideProtocolClient?.openNotebook().then(() => { - // // openCapturedTerminal(); - // }); + ideProtocolClient.openNotebook().then(() => { + // openCapturedTerminal(); + }); } extensionContext = context; diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index db2c5523..419d32ed 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -77,6 +77,8 @@ function checkEnvExists() { async function setupPythonEnv() { console.log("Setting up python env for Continue extension..."); + if (checkEnvExists()) return; + // Assemble the command to create the env const [pythonCmd, pipCmd] = await getPythonPipCommands(); const [activateCmd, pipUpgradeCmd] = getActivateUpgradeCommands( -- cgit v1.2.3-70-g09d2 From da4647f2788a7b6d1e3b6ec76665052210311385 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Thu, 1 Jun 2023 16:04:03 -0400 Subject: fixing up CreatePipelineStep --- continuedev/src/continuedev/core/agent.py | 5 +++-- continuedev/src/continuedev/core/main.py | 13 ++++++++++++- continuedev/src/continuedev/core/sdk.py | 2 +- continuedev/src/continuedev/libs/steps/core/core.py | 2 +- continuedev/src/continuedev/libs/steps/draft/dlt.py | 14 +++++++++----- continuedev/src/continuedev/libs/steps/main.py | 2 +- extension/package-lock.json | 4 ++-- extension/package.json | 2 +- extension/src/debugPanel.ts | 1 - 9 files changed, 30 insertions(+), 15 deletions(-) diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 7f7466a2..0dba6122 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -94,10 +94,11 @@ class Agent(ContinueBaseModel): self._step_depth -= 1 # Add observation to history - self.history.get_current().observation = observation + self.history.get_last_at_depth( + self._step_depth, include_current=True).observation = observation # Update its description - step._set_description(await step.describe(ContinueSDK(self))) + step._set_description(await step.describe(ContinueSDK(self).models)) # Call all subscribed callbacks await self.update_subscribers() diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index 6be5139b..a2336671 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -27,6 +27,17 @@ class History(ContinueBaseModel): return None return self.timeline[self.current_index] + def get_last_at_depth(self, depth: int, include_current: bool = False) -> Union[HistoryNode, None]: + i = self.current_index if include_current else self.current_index - 1 + while i >= 0: + if self.timeline[i].depth == depth and type(self.timeline[i].step).__name__ != "ManualEditStep": + return self.timeline[i] + i -= 1 + return None + + def get_last_at_same_depth(self) -> Union[HistoryNode, None]: + return self.get_last_at_depth(self.get_current().depth) + def remove_current_and_substeps(self): self.timeline.pop(self.current_index) while self.get_current() is not None and self.get_current().depth > 0: @@ -51,7 +62,7 @@ class History(ContinueBaseModel): self.current_index -= 1 def last_observation(self) -> Union[Observation, None]: - state = self.get_current() + state = self.get_last_at_same_depth() if state is None: return None return state.observation diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 4d82a1ae..b9b20422 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -74,7 +74,7 @@ class ContinueSDK: async def run(self, commands: List[str] | str, cwd: str = None): commands = commands if isinstance(commands, List) else [commands] - return await self.run_step(ShellCommandsStep(commands=commands, cwd=cwd)) + return await self.run_step(ShellCommandsStep(cmds=commands, cwd=cwd)) async def edit_file(self, filename: str, prompt: str): filepath = await self._ensure_absolute_path(filename) diff --git a/continuedev/src/continuedev/libs/steps/core/core.py b/continuedev/src/continuedev/libs/steps/core/core.py index 14b3cb80..1761cbfd 100644 --- a/continuedev/src/continuedev/libs/steps/core/core.py +++ b/continuedev/src/continuedev/libs/steps/core/core.py @@ -38,7 +38,7 @@ class FileSystemEditStep(ReversibleStep): # Where and when should file saves happen? -def ShellCommandsStep(Step): +class ShellCommandsStep(Step): cmds: List[str] cwd: str | None = None name: str = "Run Shell Commands" diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index 778ced1d..94ca2323 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -1,4 +1,6 @@ from textwrap import dedent + +from ....core.observation import DictObservation from ....models.filesystem_edit import AddFile from ....core.main import Step from ....core.sdk import ContinueSDK @@ -19,7 +21,7 @@ class SetupPipelineStep(Step): 'python3 -m venv env', 'source env/bin/activate', 'pip install dlt', - 'dlt init {source_name} duckdb', + f'dlt init {source_name} duckdb', 'Y', 'pip install -r requirements.txt' ]) @@ -31,15 +33,15 @@ class SetupPipelineStep(Step): ) # wait for user to put API key in secrets.toml - await sdk.ide.setFileOpen(".dlt/secrets.toml") + await sdk.ide.setFileOpen(await sdk.ide.getWorkspaceDirectory() + "/.dlt/secrets.toml") await sdk.wait_for_user_confirmation("Please add the API key to the `secrets.toml` file and then press `Continue`") - return {"source_name": source_name} + return DictObservation(values={"source_name": source_name}) class ValidatePipelineStep(Step): async def run(self, sdk: ContinueSDK): - source_name = sdk.history.last_observation()["source_name"] + source_name = sdk.history.last_observation().values["source_name"] filename = f'{source_name}.py' # test that the API call works @@ -68,7 +70,9 @@ class ValidatePipelineStep(Step): for row in rows: print(row) ''') - await sdk.apply_filesystem_edit(AddFile(filepath='query.py', content=tables_query_code)) + + query_filename = (await sdk.ide.getWorkspaceDirectory()) + "/query.py" + await sdk.apply_filesystem_edit(AddFile(filepath=query_filename, content=tables_query_code)) await sdk.run('env/bin/python3 query.py') diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index d31db0eb..6a7f14c7 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -340,7 +340,7 @@ class MessageStep(Step): return self.message async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - pass + return TextObservation(text=self.message) class EmptyStep(Step): diff --git a/extension/package-lock.json b/extension/package-lock.json index 8662ef49..ef3a619b 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.9", + "version": "0.0.10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.9", + "version": "0.0.10", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index f84585fb..6658f77a 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.9", + "version": "0.0.10", "publisher": "Continue", "engines": { "vscode": "^1.74.0" diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index da29a52c..87c33da1 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -194,7 +194,6 @@ export function setupDebugPanel( async function connectWebsocket(url: string) { return new Promise((resolve, reject) => { const onMessage = (message: any) => { - console.log("websocket message", message); panel.webview.postMessage({ type: "websocketForwardingMessage", url, -- cgit v1.2.3-70-g09d2 From 5ce90853586fcfaa7d795e2593aaaecce4bbed49 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Thu, 1 Jun 2023 19:03:10 -0400 Subject: polishing dlt recipe --- continuedev/src/continuedev/core/agent.py | 13 ++++++++---- .../src/continuedev/libs/steps/core/core.py | 3 ++- .../src/continuedev/libs/steps/draft/dlt.py | 24 ++++++++++++++++++++-- continuedev/src/continuedev/libs/steps/main.py | 1 + .../react-app/src/components/StepContainer.tsx | 2 ++ extension/react-app/src/components/index.ts | 2 +- extension/src/activation/environmentSetup.ts | 6 +++--- 7 files changed, 40 insertions(+), 11 deletions(-) diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 0dba6122..cf5c9781 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -10,6 +10,7 @@ from ..models.main import ContinueBaseModel from .main import Policy, History, FullState, Step, HistoryNode from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep from .sdk import ContinueSDK +import asyncio class Agent(ContinueBaseModel): @@ -88,6 +89,9 @@ class Agent(ContinueBaseModel): 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)) @@ -98,10 +102,11 @@ class Agent(ContinueBaseModel): self._step_depth, include_current=True).observation = observation # Update its description - step._set_description(await step.describe(ContinueSDK(self).models)) - - # Call all subscribed callbacks - await self.update_subscribers() + 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 diff --git a/continuedev/src/continuedev/libs/steps/core/core.py b/continuedev/src/continuedev/libs/steps/core/core.py index 1761cbfd..9a5d54f0 100644 --- a/continuedev/src/continuedev/libs/steps/core/core.py +++ b/continuedev/src/continuedev/libs/steps/core/core.py @@ -44,7 +44,8 @@ class ShellCommandsStep(Step): name: str = "Run Shell Commands" async def describe(self, models: Models) -> Coroutine[str, None, None]: - return "\n".join(self.cmds) + cmds_str = "\n".join(self.cmds) + return (await models.gpt35()).complete(f"{cmds_str}\n\nSummarize what was done in these shell commands, using markdown bullet points:") async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: cwd = await sdk.ide.getWorkspaceDirectory() if self.cwd is None else self.cwd diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index 94ca2323..73762327 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -1,16 +1,27 @@ from textwrap import dedent +from ....core.sdk import Models + from ....core.observation import DictObservation from ....models.filesystem_edit import AddFile from ....core.main import Step from ....core.sdk import ContinueSDK from ..core.core import WaitForUserInputStep +from ..main import MessageStep class SetupPipelineStep(Step): + hide: bool = True + name: str = "Setup dlt Pipeline" api_description: str # e.g. "I want to load data from the weatherapi.com API" + async def describe(self, models: Models): + return dedent(f"""\ + This step will create a new dlt pipeline that loads data from an API, as per your request: + {self.api_description} + """) + async def run(self, sdk: ContinueSDK): source_name = (await sdk.models.gpt35()).complete( f"Write a snake_case name for the data source described by {self.api_description}: ").strip() @@ -34,12 +45,11 @@ class SetupPipelineStep(Step): # wait for user to put API key in secrets.toml await sdk.ide.setFileOpen(await sdk.ide.getWorkspaceDirectory() + "/.dlt/secrets.toml") - await sdk.wait_for_user_confirmation("Please add the API key to the `secrets.toml` file and then press `Continue`") + await sdk.wait_for_user_confirmation("If this service requires an API key, please add it to the `secrets.toml` file and then press `Continue`") return DictObservation(values={"source_name": source_name}) class ValidatePipelineStep(Step): - async def run(self, sdk: ContinueSDK): source_name = sdk.history.last_observation().values["source_name"] filename = f'{source_name}.py' @@ -77,9 +87,19 @@ class ValidatePipelineStep(Step): class CreatePipelineStep(Step): + hide: bool = True async def run(self, sdk: ContinueSDK): await sdk.run_step( + MessageStep(message=dedent("""\ + This recipe will walk you through the process of creating a dlt pipeline for your chosen data source. With the help of Continue, you will: + - Create a Python virtual environment with dlt installed + - Run `dlt init` to generate a pipeline template + - Write the code to call the API + - Add any required API keys to the `secrets.toml` file + - Test that the API call works + - Load the data into a local DuckDB instance + - Write a query to view the data""")) >> WaitForUserInputStep(prompt="What API do you want to load data from?") >> SetupPipelineStep(api_description="WeatherAPI.com API") >> ValidatePipelineStep() diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 6a7f14c7..c70d5c2c 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -334,6 +334,7 @@ class SolveTracebackStep(Step): class MessageStep(Step): + name: str = "Message" message: str async def describe(self, models: Models) -> Coroutine[str, None, None]: diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 36b3d99a..ec601ea7 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -36,6 +36,8 @@ const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>` animation: ${appear} 0.3s ease-in-out; /* padding-left: ${(props) => props.stepDepth * 20}px; */ overflow: hidden; + margin-left: 0px; + margin-right: 0px; `; const StepContainerDiv = styled.div<{ open: boolean }>` diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts index e37c97f3..7ba60467 100644 --- a/extension/react-app/src/components/index.ts +++ b/extension/react-app/src/components/index.ts @@ -45,7 +45,7 @@ export const Pre = styled.pre` border-radius: ${defaultBorderRadius}; padding: 8px; max-height: 150px; - overflow: scroll; + overflow-y: scroll; margin: 0; background-color: ${secondaryDark}; border: none; diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 419d32ed..170426e1 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -66,11 +66,11 @@ function checkEnvExists() { getExtensionUri().fsPath, "scripts", "env", - "bin" + process.platform == "win32" ? "Scripts" : "bin" ); return ( - fs.existsSync(envBinPath + "/activate") && - fs.existsSync(envBinPath + "/pip") + fs.existsSync(path.join(envBinPath, "activate")) && + fs.existsSync(path.join(envBinPath, "pip")) ); } -- cgit v1.2.3-70-g09d2 From ef0fcedffc53b5ba455b230b112306a46ee0235f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Thu, 1 Jun 2023 20:59:10 -0400 Subject: enter secrets directly into .env file --- continuedev/src/continuedev/core/env.py | 10 ++++-- continuedev/src/continuedev/core/sdk.py | 37 +++++++++++++++------- .../src/continuedev/models/filesystem_edit.py | 4 +++ continuedev/src/continuedev/models/main.py | 8 +++++ .../react-app/src/components/StepContainer.tsx | 8 +++++ 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/continuedev/src/continuedev/core/env.py b/continuedev/src/continuedev/core/env.py index edd3297c..2692c348 100644 --- a/continuedev/src/continuedev/core/env.py +++ b/continuedev/src/continuedev/core/env.py @@ -7,11 +7,15 @@ def get_env_var(var_name: str): return os.getenv(var_name) -def save_env_var(var_name: str, var_value: str): +def make_sure_env_exists(): if not os.path.exists('.env'): with open('.env', 'w') as f: - f.write(f'{var_name}="{var_value}"\n') - return + f.write('') + + +def save_env_var(var_name: str, var_value: str): + make_sure_env_exists() + with open('.env', 'r') as f: lines = f.readlines() with open('.env', 'w') as f: diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index b9b20422..ce0c53fd 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -2,7 +2,7 @@ import os from typing import Coroutine, Union from .config import ContinueConfig, load_config -from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory +from ..models.filesystem_edit import FileEdit, FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory from ..models.filesystem import RangeInFile from ..libs.llm import LLM from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI @@ -11,7 +11,7 @@ from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step from ..libs.steps.core.core import * -from .env import get_env_var, save_env_var +from .env import get_env_var, make_sure_env_exists class Agent: @@ -29,12 +29,12 @@ class Models: async def starcoder(self): api_key = await self.sdk.get_user_secret( - 'HUGGING_FACE_TOKEN', 'Please enter your Hugging Face token') + 'HUGGING_FACE_TOKEN', 'Please add your Hugging Face token to the .env file') return HuggingFaceInferenceAPI(api_key=api_key) async def gpt35(self): api_key = await self.sdk.get_user_secret( - 'OPENAI_API_KEY', 'Please enter your OpenAI API key') + 'OPENAI_API_KEY', 'Please add your OpenAI API key to the .env file') return OpenAI(api_key=api_key, default_model="gpt-3.5-turbo") @@ -86,6 +86,12 @@ class ContinueSDK: prompt=f'Here is the code before:\n\n{{code}}\n\nHere is the user request:\n\n{prompt}\n\nHere is the code edited to perfectly solve the user request:\n\n' )) + async def append_to_file(self, filename: str, content: str): + filepath = await self._ensure_absolute_path(filename) + previous_content = await self.ide.readFile(filepath) + file_edit = FileEdit.from_append(filepath, previous_content, content) + await self.ide.applyFileSystemEdit(file_edit) + async def add_file(self, filename: str, content: str | None): return await self.run_step(FileSystemEditStep(edit=AddFile(filename=filename, content=content))) @@ -99,14 +105,23 @@ class ContinueSDK: return await self.run_step(FileSystemEditStep(edit=DeleteDirectory(path=path))) async def get_user_secret(self, env_var: str, prompt: str) -> str: - try: + make_sure_env_exists() + + val = None + while val is None: + try: + val = get_env_var(env_var) + if val is not None: + return val + except: + pass + server_dir = os.getcwd() + env_path = os.path.join(server_dir, ".env") + await self.ide.setFileOpen(env_path) + await self.append_to_file(env_path, f'\n{env_var}=""') + await self.run_step(WaitForUserConfirmationStep(prompt=prompt)) val = get_env_var(env_var) - if val is not None: - return val - except: - pass - val = (await self.run_step(WaitForUserInputStep(prompt=prompt))).text - save_env_var(env_var, val) + return val async def get_config(self) -> ContinueConfig: diff --git a/continuedev/src/continuedev/models/filesystem_edit.py b/continuedev/src/continuedev/models/filesystem_edit.py index 7526d4c9..8e74b819 100644 --- a/continuedev/src/continuedev/models/filesystem_edit.py +++ b/continuedev/src/continuedev/models/filesystem_edit.py @@ -37,6 +37,10 @@ class FileEdit(AtomicFileSystemEdit): def from_insertion(filepath: str, position: Position, content: str) -> "FileEdit": return FileEdit(filepath=filepath, range=Range.from_shorthand(position.line, position.character, position.line, position.character), replacement=content) + @staticmethod + def from_append(filepath: str, previous_content: str, appended_content: str) -> "FileEdit": + return FileEdit(filepath=filepath, range=Range.from_position(Position.from_end_of_file(previous_content)), replacement=appended_content) + class FileEditWithFullContents(BaseModel): fileEdit: FileEdit diff --git a/continuedev/src/continuedev/models/main.py b/continuedev/src/continuedev/models/main.py index 1bc51ff1..02c44aae 100644 --- a/continuedev/src/continuedev/models/main.py +++ b/continuedev/src/continuedev/models/main.py @@ -39,6 +39,10 @@ class Position(BaseModel): return Position(line=line, character=character) + @staticmethod + def from_end_of_file(contents: str) -> "Position": + return Position.from_index(contents, len(contents)) + class Range(BaseModel): """A range in a file. 0-indexed.""" @@ -117,6 +121,10 @@ class Range(BaseModel): return Range.from_shorthand(start_line, 0, end_line, len(content_lines[end_line]) - 1) + @staticmethod + def from_position(position: Position) -> "Range": + return Range(start=position, end=position) + class AbstractModel(ABC, BaseModel): @root_validator(pre=True) diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index ec601ea7..5e979b34 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -80,6 +80,13 @@ function StepContainer(props: StepContainerProps) { const [open, setOpen] = useState(false); const [isHovered, setIsHovered] = useState(false); const naturalLanguageInputRef = useRef(null); + const userInputRef = useRef(null); + + useEffect(() => { + if (userInputRef?.current) { + userInputRef.current.focus(); + } + }, [userInputRef]); useEffect(() => { if (isHovered) { @@ -136,6 +143,7 @@ function StepContainer(props: StepContainerProps) { {props.historyNode.step.name === "Waiting for user input" && ( { if (e.key === "Enter") { -- cgit v1.2.3-70-g09d2 From 37dbfc0102b6d67b47b8886e78e57c5998655fc9 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Thu, 1 Jun 2023 21:15:36 -0400 Subject: 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 ef3a619b..ff4dbfa1 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.10", + "version": "0.0.11", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.10", + "version": "0.0.11", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 6658f77a..8f1eabde 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.10", + "version": "0.0.11", "publisher": "Continue", "engines": { "vscode": "^1.74.0" -- cgit v1.2.3-70-g09d2 From 7bab929db01c0e585297c88747ef0f7620620343 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 2 Jun 2023 00:51:01 -0400 Subject: posthog telemetry --- continuedev/poetry.lock | 50 +++++++++++++++++++++- continuedev/pyproject.toml | 1 + continuedev/src/continuedev/core/agent.py | 4 ++ continuedev/src/continuedev/core/config.py | 4 ++ continuedev/src/continuedev/libs/util/telemetry.py | 12 ++++++ 5 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 continuedev/src/continuedev/libs/util/telemetry.py diff --git a/continuedev/poetry.lock b/continuedev/poetry.lock index 810560b6..857a7c99 100644 --- a/continuedev/poetry.lock +++ b/continuedev/poetry.lock @@ -176,6 +176,18 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +[[package]] +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, + {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, +] + [[package]] name = "boltons" version = "23.0.0" @@ -631,6 +643,18 @@ files = [ [package.dependencies] marshmallow = ">=2.0.0" +[[package]] +name = "monotonic" +version = "1.6" +description = "An implementation of time.monotonic() for Python 2 & < 3.3" +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c"}, + {file = "monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7"}, +] + [[package]] name = "multidict" version = "6.0.4" @@ -938,6 +962,30 @@ sql-other = ["SQLAlchemy (>=1.4.16)"] test = ["hypothesis (>=6.34.2)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.6.3)"] +[[package]] +name = "posthog" +version = "3.0.1" +description = "Integrate PostHog into any python application." +category = "main" +optional = false +python-versions = "*" +files = [ + {file = "posthog-3.0.1-py2.py3-none-any.whl", hash = "sha256:9c7f92fecc713257d4b2710d05b456569c9156fbdd3e85655ba7ba5ba6c7b3ae"}, + {file = "posthog-3.0.1.tar.gz", hash = "sha256:57d2791ff5752ce56ba0f9bb8876faf3ca9208f1c2c6ceaeb5a2504c34493767"}, +] + +[package.dependencies] +backoff = ">=1.10.0" +monotonic = ">=1.5" +python-dateutil = ">2.1" +requests = ">=2.7,<3.0" +six = ">=1.5" + +[package.extras] +dev = ["black", "flake8", "flake8-print", "isort", "pre-commit"] +sentry = ["django", "sentry-sdk"] +test = ["coverage", "flake8", "freezegun (==0.3.15)", "mock (>=2.0.0)", "pylint", "pytest"] + [[package]] name = "pydantic" version = "1.10.7" @@ -1691,4 +1739,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "2eb1f7b5bda2352b6b96ff4e139c0c92694b6f16d4c2fffc0e89c5c098396884" +content-hash = "9f9254c954b7948c49debba86bc81a4a9c3f50694424f5940d0058725b1bf0fb" diff --git a/continuedev/pyproject.toml b/continuedev/pyproject.toml index 8ed8f268..0dc4d267 100644 --- a/continuedev/pyproject.toml +++ b/continuedev/pyproject.toml @@ -19,6 +19,7 @@ websockets = "^11.0.2" urllib3 = "1.26.15" gpt-index = "^0.6.8" setuptools = "^67.7.2" +posthog = "^3.0.1" [tool.poetry.scripts] typegen = "src.continuedev.models.generate_json_schema:main" diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index cf5c9781..29c695af 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -9,6 +9,7 @@ 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 @@ -77,6 +78,9 @@ class Agent(ContinueBaseModel): _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: diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py index e62f0e4f..8ed41a82 100644 --- a/continuedev/src/continuedev/core/config.py +++ b/continuedev/src/continuedev/core/config.py @@ -11,12 +11,16 @@ class ContinueConfig(BaseModel): """ steps_on_startup: Optional[Dict[str, Dict]] = {} server_url: Optional[str] = None + allow_anonymous_telemetry: Optional[bool] = True def load_config(config_file: str) -> ContinueConfig: """ Load the config file and return a ContinueConfig object. """ + if not os.path.exists(config_file): + return ContinueConfig() + _, ext = os.path.splitext(config_file) if ext == '.yaml': with open(config_file, 'r') as f: diff --git a/continuedev/src/continuedev/libs/util/telemetry.py b/continuedev/src/continuedev/libs/util/telemetry.py new file mode 100644 index 00000000..4bff3970 --- /dev/null +++ b/continuedev/src/continuedev/libs/util/telemetry.py @@ -0,0 +1,12 @@ +from posthog import Posthog +from ...core.config import load_config + +# The personal API key is necessary only if you want to use local evaluation of feature flags. +posthog = Posthog('phc_JS6XFROuNbhJtVCEdTSYk6gl5ArRrTNMpCcguAXlSPs', + host='https://app.posthog.com') + + +def capture_event(event_name, event_properties): + config = load_config('~/.continue/continue.json') + if config.allow_anonymous_telemetry: + posthog.capture("not distinct :(", event_name, event_properties) -- cgit v1.2.3-70-g09d2 From 3ff98317831d80436b266b424a2613fcf1d7205c Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 2 Jun 2023 00:57:43 -0400 Subject: agent -> autopilot --- docs/docs/concepts/agent.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/docs/concepts/agent.md b/docs/docs/concepts/agent.md index 0528b305..bbcc6f57 100644 --- a/docs/docs/concepts/agent.md +++ b/docs/docs/concepts/agent.md @@ -1,9 +1,8 @@ -# Agent +# Autopilot + +`Autopilot` contains the -`Agent` contains the - History - LLM - Policy - IDE - -**Q: should we really call this abstraction agent?** \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 769bf31da03f4c5d3145d885cff897109d63f246 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 2 Jun 2023 00:57:57 -0400 Subject: agent -> autopilot --- continuedev/src/continuedev/core/agent.py | 4 +-- continuedev/src/continuedev/core/sdk.py | 18 ++++++------- .../src/continuedev/libs/util/copy_codebase.py | 16 +++++------ continuedev/src/continuedev/server/ide.py | 16 +++++------ continuedev/src/continuedev/server/notebook.py | 10 +++---- .../src/continuedev/server/session_manager.py | 20 +++++++------- docs/docs/concepts/core.md | 5 ++-- docs/docs/concepts/llm.md | 2 +- docs/sidebars.js | 31 +++++++++++----------- 9 files changed, 61 insertions(+), 61 deletions(-) diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 29c695af..6e920ab4 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -14,7 +14,7 @@ from .sdk import ContinueSDK import asyncio -class Agent(ContinueBaseModel): +class Autopilot(ContinueBaseModel): policy: Policy ide: AbstractIdeProtocolServer history: History = History.from_empty() @@ -116,7 +116,7 @@ class Agent(ContinueBaseModel): async def run_from_step(self, step: "Step"): # if self._active: - # raise RuntimeError("Agent is already running") + # raise RuntimeError("Autopilot is already running") self._active = True next_step = step diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index ce0c53fd..af7754cc 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -14,7 +14,7 @@ from ..libs.steps.core.core import * from .env import get_env_var, make_sure_env_exists -class Agent: +class Autopilot: pass @@ -43,17 +43,17 @@ class ContinueSDK: ide: AbstractIdeProtocolServer steps: ContinueSDKSteps models: Models - __agent: Agent + __autopilot: Autopilot - def __init__(self, agent: Agent): - self.ide = agent.ide - self.__agent = agent + def __init__(self, autopilot: Autopilot): + self.ide = autopilot.ide + self.__autopilot = autopilot self.steps = ContinueSDKSteps(self) self.models = Models(self) @property def history(self) -> History: - return self.__agent.history + return self.__autopilot.history async def _ensure_absolute_path(self, path: str) -> str: if os.path.isabs(path): @@ -61,13 +61,13 @@ class ContinueSDK: return os.path.join(await self.ide.getWorkspaceDirectory(), path) async def run_step(self, step: Step) -> Coroutine[Observation, None, None]: - return await self.__agent._run_singular_step(step) + return await self.__autopilot._run_singular_step(step) async def apply_filesystem_edit(self, edit: FileSystemEdit): return await self.run_step(FileSystemEditStep(edit=edit)) async def wait_for_user_input(self) -> str: - return await self.__agent.wait_for_user_input() + return await self.__autopilot.wait_for_user_input() async def wait_for_user_confirmation(self, prompt: str): return await self.run_step(WaitForUserConfirmationStep(prompt=prompt)) @@ -136,5 +136,5 @@ class ContinueSDK: return ContinueConfig() def set_loading_message(self, message: str): - # self.__agent.set_loading_message(message) + # self.__autopilot.set_loading_message(message) raise NotImplementedError() diff --git a/continuedev/src/continuedev/libs/util/copy_codebase.py b/continuedev/src/continuedev/libs/util/copy_codebase.py index ef1db72b..af957a34 100644 --- a/continuedev/src/continuedev/libs/util/copy_codebase.py +++ b/continuedev/src/continuedev/libs/util/copy_codebase.py @@ -5,7 +5,7 @@ from watchdog.observers import Observer from watchdog.events import PatternMatchingEventHandler from ..models.main import FileEdit, DeleteDirectory, DeleteFile, AddDirectory, AddFile, FileSystemEdit, Position, Range, RenameFile, RenameDirectory, SequentialFileSystemEdit from ..models.filesystem import FileSystem -from ..libs.main import Agent +from ..libs.main import Autopilot from ..libs.map_path import map_path from ..libs.steps.main import ManualEditAction import shutil @@ -65,15 +65,15 @@ def calculate_diff(filepath: str, original: str, updated: str) -> List[FileEdit] # The whole usage of watchdog here should only be specific to RealFileSystem, you want to have a different "Observer" class for VirtualFileSystem, which would depend on being sent notifications class CopyCodebaseEventHandler(PatternMatchingEventHandler): - def __init__(self, ignore_directories: List[str], ignore_patterns: List[str], agent: Agent, orig_root: str, copy_root: str, filesystem: FileSystem): + def __init__(self, ignore_directories: List[str], ignore_patterns: List[str], autopilot: Autopilot, orig_root: str, copy_root: str, filesystem: FileSystem): super().__init__(ignore_directories=ignore_directories, ignore_patterns=ignore_patterns) - self.agent = agent + self.autopilot = autopilot self.orig_root = orig_root self.copy_root = copy_root self.filesystem = filesystem - # For now, we'll just make the update immediately, but eventually need to sync with agent. - # It should be the agent that makes the update right? It's just another action, everything comes from a single stream. + # For now, we'll just make the update immediately, but eventually need to sync with autopilot. + # It should be the autopilot that makes the update right? It's just another action, everything comes from a single stream. def _event_to_edit(self, event) -> Union[FileSystemEdit, None]: # NOTE: You'll need to map paths to create both an action within the copy filesystem (the one you take) and one in the original fileystem (the one you'll record and allow the user to accept). Basically just need a converter built in to the FileSystemEdit class @@ -110,13 +110,13 @@ class CopyCodebaseEventHandler(PatternMatchingEventHandler): return edit = edit.with_mapped_paths(self.orig_root, self.copy_root) action = ManualEditAction(edit) - self.agent.act(action) + self.autopilot.act(action) -def maintain_copy_workspace(agent: Agent, filesystem: FileSystem, orig_root: str, copy_root: str): +def maintain_copy_workspace(autopilot: Autopilot, filesystem: FileSystem, orig_root: str, copy_root: str): observer = Observer() event_handler = CopyCodebaseEventHandler( - [".git"], [], agent, orig_root, copy_root, filesystem) + [".git"], [], autopilot, orig_root, copy_root, filesystem) observer.schedule(event_handler, orig_root, recursive=True) observer.start() try: diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 50296841..32f0b3ba 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -125,7 +125,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): pass async def setFileOpen(self, filepath: str, open: bool = True): - # Agent needs access to this. + # Autopilot needs access to this. await self._send_json("setFileOpen", { "filepath": filepath, "open": open @@ -147,12 +147,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer): responses = await asyncio.gather(*[ 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 agent. + ]) # 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) return any([r.accepted for r in responses]) # ------------------------------- # - # Here needs to pass message onto the Agent OR Agent just subscribes. + # Here needs to pass message onto the Autopilot OR Autopilot just subscribes. # This is where you might have triggers: plugins can subscribe to certian events # like file changes, tracebacks, etc... @@ -160,12 +160,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer): pass def onTraceback(self, traceback: Traceback): - # Same as below, maybe not every agent? + # Same as below, maybe not every autopilot? for _, session in self.session_manager.sessions.items(): - session.agent.handle_traceback(traceback) + session.autopilot.handle_traceback(traceback) def onFileSystemUpdate(self, update: FileSystemEdit): - # Access to Agent (so SessionManager) + # Access to Autopilot (so SessionManager) pass def onCloseNotebook(self, session_id: str): @@ -176,10 +176,10 @@ class IdeProtocolServer(AbstractIdeProtocolServer): pass def onFileEdits(self, edits: List[FileEditWithFullContents]): - # Send the file edits to ALL agents. + # Send the file edits to ALL autopilots. # Maybe not ideal behavior for _, session in self.session_manager.sessions.items(): - session.agent.handle_manual_edits(edits) + session.autopilot.handle_manual_edits(edits) # Request information. Session doesn't matter. async def getOpenFiles(self) -> List[str]: diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index 9ca510dd..8ebe2853 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -79,26 +79,26 @@ class NotebookProtocolServer(AbstractNotebookProtocolServer): print(e) async def send_state_update(self): - state = self.session.agent.get_full_state().dict() + 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.agent.accept_user_input(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.agent.reverse_to_index(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.agent.give_user_input(input, index)) + self.session.autopilot.give_user_input(input, index)) def on_refinement_input(self, input: str, index: int): asyncio.create_task( - self.session.agent.accept_refinement_input(input, index)) + self.session.autopilot.accept_refinement_input(input, index)) @router.websocket("/ws") diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index c5715034..5598e140 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -5,7 +5,7 @@ from uuid import uuid4 from ..models.filesystem_edit import FileEditWithFullContents from ..core.policy import DemoPolicy from ..core.main import FullState -from ..core.agent import Agent +from ..core.autopilot import Autopilot from ..libs.steps.nate import ImplementAbstractMethodStep from .ide_protocol import AbstractIdeProtocolServer import asyncio @@ -15,16 +15,16 @@ nest_asyncio.apply() class Session: session_id: str - agent: Agent + autopilot: Autopilot ws: Union[WebSocket, None] - def __init__(self, session_id: str, agent: Agent): + def __init__(self, session_id: str, autopilot: Autopilot): self.session_id = session_id - self.agent = agent + self.autopilot = autopilot self.ws = None -class DemoAgent(Agent): +class DemoAutopilot(Autopilot): first_seen: bool = False cumulative_edit_string = "" @@ -51,18 +51,18 @@ class SessionManager: return self.sessions[session_id] def new_session(self, ide: AbstractIdeProtocolServer) -> str: - agent = DemoAgent(policy=DemoPolicy(), ide=ide) + autopilot = DemoAutopilot(policy=DemoPolicy(), ide=ide) session_id = str(uuid4()) - session = Session(session_id=session_id, agent=agent) + session = Session(session_id=session_id, autopilot=autopilot) self.sessions[session_id] = session async def on_update(state: FullState): await session_manager.send_ws_data(session_id, "state_update", { - "state": agent.get_full_state().dict() + "state": autopilot.get_full_state().dict() }) - agent.on_update(on_update) - asyncio.create_task(agent.run_policy()) + autopilot.on_update(on_update) + asyncio.create_task(autopilot.run_policy()) return session_id def remove_session(self, session_id: str): diff --git a/docs/docs/concepts/core.md b/docs/docs/concepts/core.md index ee58cbb2..d60f46ac 100644 --- a/docs/docs/concepts/core.md +++ b/docs/docs/concepts/core.md @@ -3,11 +3,12 @@ The `Core` connects the SDK and GUI with the IDE (i.e. in VS Code, a web browser, etc), enabling the steps to make changes to your code and accelerate your software development workflows. The `Core` includes + - IDE protocol - GUI protocol - SDK -- Agent +- Autopilot There is a two-way sync between an IDE and the GUI that happens through Core. -**Q: does this make sense as a concept?** \ No newline at end of file +**Q: does this make sense as a concept?** diff --git a/docs/docs/concepts/llm.md b/docs/docs/concepts/llm.md index 11bbacc7..8c2dbcba 100644 --- a/docs/docs/concepts/llm.md +++ b/docs/docs/concepts/llm.md @@ -4,4 +4,4 @@ **Q: should we call this LLM? Perhaps just model?** -**Q: should this abstraction be connected to agent?** \ No newline at end of file +**Q: should this abstraction be connected to autopilot?** diff --git a/docs/sidebars.js b/docs/sidebars.js index db9ae662..29e35a02 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -13,27 +13,26 @@ /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { - docsSidebar: [ - 'intro', - 'create-a-recipe', + "intro", + "create-a-recipe", { - type: 'category', - label: 'Concepts', + type: "category", + label: "Concepts", items: [ - 'concepts/agent', - 'concepts/core', - 'concepts/gui', - 'concepts/history', - 'concepts/ide', - 'concepts/llm', - 'concepts/policy', - 'concepts/recipes', - 'concepts/sdk', - 'concepts/step', + "concepts/autopilot", + "concepts/core", + "concepts/gui", + "concepts/history", + "concepts/ide", + "concepts/llm", + "concepts/policy", + "concepts/recipes", + "concepts/sdk", + "concepts/step", ], }, ], }; -module.exports = sidebars; \ No newline at end of file +module.exports = sidebars; -- cgit v1.2.3-70-g09d2 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 --- docs/docs/concepts/agent.md | 8 - docs/docs/concepts/autopilot.md | 30 +++ docs/docs/concepts/gui.md | 4 +- docs/docs/concepts/ide.md | 14 +- docs/docs/how-continue-works.md | 37 +++ extension/react-app/src/App.tsx | 6 +- .../src/hooks/ContinueGUIClientProtocol.ts | 13 + .../src/hooks/ContinueNotebookClientProtocol.ts | 13 - .../react-app/src/hooks/useContinueGUIProtocol.ts | 49 ++++ .../src/hooks/useContinueNotebookProtocol.ts | 49 ---- extension/react-app/src/hooks/useWebsocket.ts | 16 +- extension/react-app/src/tabs/gui.tsx | 265 +++++++++++++++++++++ extension/react-app/src/tabs/notebook.tsx | 265 --------------------- extension/src/activation/activate.ts | 4 +- extension/src/commands.ts | 4 +- extension/src/continueIdeClient.ts | 8 +- 35 files changed, 782 insertions(+), 832 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 delete mode 100644 docs/docs/concepts/agent.md create mode 100644 docs/docs/concepts/autopilot.md create mode 100644 docs/docs/how-continue-works.md create mode 100644 extension/react-app/src/hooks/ContinueGUIClientProtocol.ts delete mode 100644 extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts create mode 100644 extension/react-app/src/hooks/useContinueGUIProtocol.ts delete mode 100644 extension/react-app/src/hooks/useContinueNotebookProtocol.ts create mode 100644 extension/react-app/src/tabs/gui.tsx delete mode 100644 extension/react-app/src/tabs/notebook.tsx 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""" diff --git a/docs/docs/concepts/agent.md b/docs/docs/concepts/agent.md deleted file mode 100644 index bbcc6f57..00000000 --- a/docs/docs/concepts/agent.md +++ /dev/null @@ -1,8 +0,0 @@ -# Autopilot - -`Autopilot` contains the - -- History -- LLM -- Policy -- IDE diff --git a/docs/docs/concepts/autopilot.md b/docs/docs/concepts/autopilot.md new file mode 100644 index 00000000..71090eb0 --- /dev/null +++ b/docs/docs/concepts/autopilot.md @@ -0,0 +1,30 @@ +# Autopilot + +**TODO: Better explain in one sentence what this is and what its purpose is** + +:::info +The **autopilot** is the main loop, completing steps and then deciding the next step and repeating +::: + +## Details + +The Autopilot class is the center of Continue. Every step is initiated from the Autopilot, which provides it with a ContinueSDK. + +- Records history +- Allows reversal +- Injects SDK +- Has policy to decide what step to take next +- Accepts user input and acts on it +- Main event loop +- Contains main classes that are provided through the SDK, including LLM, History, IDE + +--- + +- An autopilot takes user input from the React app +- You can see this happening in `server/gui.py` +- It basically queues user inputs, pops off the most recent, runs that as a "UserInputStep", uses its Policy to run other steps until the next step is None, and then pops off the next user input. When nothing left, just waits for more +- `Autopilot` contains the + - History + - LLM + - Policy + - IDE diff --git a/docs/docs/concepts/gui.md b/docs/docs/concepts/gui.md index dfdc2a7a..f9cff697 100644 --- a/docs/docs/concepts/gui.md +++ b/docs/docs/concepts/gui.md @@ -3,11 +3,11 @@ The `GUI` enables you to guide steps and makes everything transparent, so you can review all steps that were automated, giving you the opportunity to undo and rerun any that ran incorrectly. **From GUI to Core** + - Natural language instructions from the developer - Hover / clicked on a step - Other user input **From Core to GUI** -- Updates to state (e.g. a new step) -**Q: do we call this the Continue GUI or Notebook?** \ No newline at end of file +- Updates to state (e.g. a new step) diff --git a/docs/docs/concepts/ide.md b/docs/docs/concepts/ide.md index 980b589d..4684c362 100644 --- a/docs/docs/concepts/ide.md +++ b/docs/docs/concepts/ide.md @@ -24,9 +24,9 @@ Get the workspace directory Set whether a file is open -### openNotebook +### openGUI -Open a notebook +Open a gui ### showSuggestionsAndWait @@ -44,13 +44,13 @@ Called when a traceback is received Called when a file system update is received -### onCloseNotebook +### onCloseGUI -Called when a notebook is closed +Called when a gui is closed -### onOpenNotebookRequest +### onOpenGUIRequest -Called when a notebook is requested to be opened +Called when a gui is requested to be opened ### getOpenFiles @@ -78,4 +78,4 @@ Apply a file edit ### saveFile -Save a file \ No newline at end of file +Save a file diff --git a/docs/docs/how-continue-works.md b/docs/docs/how-continue-works.md new file mode 100644 index 00000000..e6648cbc --- /dev/null +++ b/docs/docs/how-continue-works.md @@ -0,0 +1,37 @@ +# How `Continue` works + +![Continue Architecture Diagram](/img/continue-architecture.png) + +## Overview + +The `Continue` library consists of an [SDK](./concepts/sdk.md), a [GUI](./concepts/gui.md), and a [Core](./concepts/core.md) that brings everything together. + +The [SDK](./concepts/sdk.md) gives you access to the tools (e.g. open a directory, edit a file, call a model, etc.) needed to define steps that integrate LLMs into your IDE. + +The [GUI](./concepts/gui.md) lets you transparently review every automated step, providing the opportunity to undo and rerun any that ran incorrectly. + +The [Core](./concepts/core.md) holds the main event loop, responsible for connecting IDE, SDK, and GUI and deciding which steps to take next. + +## Details + +**TODO: Refactor all of this and make it fit with language above** + +- Continue connects any code editor (primarily VS Code right now) to a server (the Continue server) that can take actions in the editor in accordance with defined recipes at the request of a user through the GUI +- What this looks like: + - The Continue VS Code extension runs the ContinueIdeProtocol, launches the Continue Python server in the background, and opens the Continue GUI in a side-panel. + - The Continue server is the brain, communication center, and source of truth, interacting with VS Code through the ContinueIdeProtocol and with the GUI through the GUIProtocol. + - Communication between the extension and GUI happens through the Continue server. + - When you type a natural language command in the GUI, this is sent to the Continue server, where the `Autopilot` class takes action, potentially using the ContinueIdeProtocol to request actions be taken in the IDE, and then updates the GUI to display the new history. +- `core` directory contains major concepts + - This includes Autopilot, Policy, SDK (all in their own files so far) + - It also includes `main.py`, which contains History, HistoryNode, Step, and others + - You'll find `env.py` here too, which is a common place to load environment variables, which can then be imported from here +- `libs` contains misc. stuff +- `llm` for language model utilities +- `steps` for builtin Continue steps +- `util` for very misc. stuff +- `chroma` for chroma code that deals with codebase embeddings +- `models` contains all the Pydantic models and `generate_json_schema.py`, a script that converts them to JSONSchema .json files in `schema/json` +- `server` runs the servers that communicate with a) the React app (`gui.py`) and b) the IDE (`ide.py`) +- `ide_protocol.py` is just the abstract version of what is implemented in `ide.py`, and `main.py` runs both `gui.py` and `ide.py` as a single FastAPI server. This is the entry point to the Continue server, and acts as a bridge between IDE and React app +- We use OpenAPI/JSONSchema to define types so that it's really easy to bring them across language barriers. Use Pydantic types, then run `poetry run typegen` from the root of continuedev folder to generate JSONSchema json files in the `schema/json` folder. Then `npm run typegen` from the extension folder generates the types that are used within the extension. diff --git a/extension/react-app/src/App.tsx b/extension/react-app/src/App.tsx index 0c40ced1..a51541d0 100644 --- a/extension/react-app/src/App.tsx +++ b/extension/react-app/src/App.tsx @@ -4,7 +4,7 @@ import { Provider } from "react-redux"; import store from "./redux/store"; import WelcomeTab from "./tabs/welcome"; import ChatTab from "./tabs/chat"; -import Notebook from "./tabs/notebook"; +import GUI from "./tabs/gui"; function App() { return ( @@ -13,8 +13,8 @@ function App() { , - title: "Notebook", + element: , + title: "GUI", }, // { element: , title: "Debug Panel" }, // { element: , title: "Welcome" }, diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts new file mode 100644 index 00000000..18a91de7 --- /dev/null +++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts @@ -0,0 +1,13 @@ +abstract class AbstractContinueGUIClientProtocol { + abstract sendMainInput(input: string): void; + + abstract reverseToIndex(index: number): void; + + abstract sendRefinementInput(input: string, index: number): void; + + abstract sendStepUserInput(input: string, index: number): void; + + abstract onStateUpdate(state: any): void; +} + +export default AbstractContinueGUIClientProtocol; diff --git a/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts b/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts deleted file mode 100644 index 75fd7373..00000000 --- a/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts +++ /dev/null @@ -1,13 +0,0 @@ -abstract class AbstractContinueNotebookClientProtocol { - abstract sendMainInput(input: string): void; - - abstract reverseToIndex(index: number): void; - - abstract sendRefinementInput(input: string, index: number): void; - - abstract sendStepUserInput(input: string, index: number): void; - - abstract onStateUpdate(state: any): void; -} - -export default AbstractContinueNotebookClientProtocol; diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts new file mode 100644 index 00000000..a3a1d0c9 --- /dev/null +++ b/extension/react-app/src/hooks/useContinueGUIProtocol.ts @@ -0,0 +1,49 @@ +import AbstractContinueGUIClientProtocol from "./ContinueGUIClientProtocol"; +// import { Messenger, WebsocketMessenger } from "../../../src/util/messenger"; +import { Messenger, WebsocketMessenger } from "./messenger"; +import { VscodeMessenger } from "./vscodeMessenger"; + +class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol { + messenger: Messenger; + // Server URL must contain the session ID param + serverUrlWithSessionId: string; + + constructor( + serverUrlWithSessionId: string, + useVscodeMessagePassing: boolean + ) { + super(); + this.serverUrlWithSessionId = serverUrlWithSessionId; + if (useVscodeMessagePassing) { + this.messenger = new VscodeMessenger(serverUrlWithSessionId); + } else { + this.messenger = new WebsocketMessenger(serverUrlWithSessionId); + } + } + + sendMainInput(input: string) { + this.messenger.send("main_input", { input }); + } + + reverseToIndex(index: number) { + this.messenger.send("reverse_to_index", { index }); + } + + sendRefinementInput(input: string, index: number) { + this.messenger.send("refinement_input", { input, index }); + } + + sendStepUserInput(input: string, index: number) { + this.messenger.send("step_user_input", { input, index }); + } + + onStateUpdate(callback: (state: any) => void) { + this.messenger.onMessageType("state_update", (data: any) => { + if (data.state) { + callback(data.state); + } + }); + } +} + +export default ContinueGUIClientProtocol; diff --git a/extension/react-app/src/hooks/useContinueNotebookProtocol.ts b/extension/react-app/src/hooks/useContinueNotebookProtocol.ts deleted file mode 100644 index b785cc84..00000000 --- a/extension/react-app/src/hooks/useContinueNotebookProtocol.ts +++ /dev/null @@ -1,49 +0,0 @@ -import AbstractContinueNotebookClientProtocol from "./ContinueNotebookClientProtocol"; -// import { Messenger, WebsocketMessenger } from "../../../src/util/messenger"; -import { Messenger, WebsocketMessenger } from "./messenger"; -import { VscodeMessenger } from "./vscodeMessenger"; - -class ContinueNotebookClientProtocol extends AbstractContinueNotebookClientProtocol { - messenger: Messenger; - // Server URL must contain the session ID param - serverUrlWithSessionId: string; - - constructor( - serverUrlWithSessionId: string, - useVscodeMessagePassing: boolean - ) { - super(); - this.serverUrlWithSessionId = serverUrlWithSessionId; - if (useVscodeMessagePassing) { - this.messenger = new VscodeMessenger(serverUrlWithSessionId); - } else { - this.messenger = new WebsocketMessenger(serverUrlWithSessionId); - } - } - - sendMainInput(input: string) { - this.messenger.send("main_input", { input }); - } - - reverseToIndex(index: number) { - this.messenger.send("reverse_to_index", { index }); - } - - sendRefinementInput(input: string, index: number) { - this.messenger.send("refinement_input", { input, index }); - } - - sendStepUserInput(input: string, index: number) { - this.messenger.send("step_user_input", { input, index }); - } - - onStateUpdate(callback: (state: any) => void) { - this.messenger.onMessageType("state_update", (data: any) => { - if (data.state) { - callback(data.state); - } - }); - } -} - -export default ContinueNotebookClientProtocol; diff --git a/extension/react-app/src/hooks/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts index 016fa17d..e762666f 100644 --- a/extension/react-app/src/hooks/useWebsocket.ts +++ b/extension/react-app/src/hooks/useWebsocket.ts @@ -1,15 +1,15 @@ import React, { useEffect, useState } from "react"; import { RootStore } from "../redux/store"; import { useSelector } from "react-redux"; -import ContinueNotebookClientProtocol from "./useContinueNotebookProtocol"; +import ContinueGUIClientProtocol from "./useContinueGUIProtocol"; import { postVscMessage } from "../vscode"; -function useContinueNotebookProtocol(useVscodeMessagePassing: boolean = true) { +function useContinueGUIProtocol(useVscodeMessagePassing: boolean = true) { const sessionId = useSelector((state: RootStore) => state.config.sessionId); const serverHttpUrl = useSelector((state: RootStore) => state.config.apiUrl); - const [client, setClient] = useState< - ContinueNotebookClientProtocol | undefined - >(undefined); + const [client, setClient] = useState( + undefined + ); useEffect(() => { if (!sessionId || !serverHttpUrl) { @@ -22,12 +22,12 @@ function useContinueNotebookProtocol(useVscodeMessagePassing: boolean = true) { const serverUrlWithSessionId = serverHttpUrl.replace("http", "ws") + - "/notebook/ws?session_id=" + + "/gui/ws?session_id=" + encodeURIComponent(sessionId); console.log("Creating websocket", serverUrlWithSessionId); console.log("Using vscode message passing", useVscodeMessagePassing); - const newClient = new ContinueNotebookClientProtocol( + const newClient = new ContinueGUIClientProtocol( serverUrlWithSessionId, useVscodeMessagePassing ); @@ -36,4 +36,4 @@ function useContinueNotebookProtocol(useVscodeMessagePassing: boolean = true) { return client; } -export default useContinueNotebookProtocol; +export default useContinueGUIProtocol; diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx new file mode 100644 index 00000000..5ddddbfc --- /dev/null +++ b/extension/react-app/src/tabs/gui.tsx @@ -0,0 +1,265 @@ +import styled from "styled-components"; +import { + Button, + defaultBorderRadius, + vscBackground, + MainTextInput, + Loader, +} from "../components"; +import ContinueButton from "../components/ContinueButton"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { History } from "../../../schema/History"; +import { HistoryNode } from "../../../schema/HistoryNode"; +import StepContainer from "../components/StepContainer"; +import { useSelector } from "react-redux"; +import { RootStore } from "../redux/store"; +import useContinueWebsocket from "../hooks/useWebsocket"; +import useContinueGUIProtocol from "../hooks/useWebsocket"; + +let TopGUIDiv = styled.div` + display: grid; + grid-template-columns: 1fr; +`; + +let UserInputQueueItem = styled.div` + border-radius: ${defaultBorderRadius}; + color: gray; + padding: 8px; + margin: 8px; + text-align: center; +`; + +interface GUIProps { + firstObservation?: any; +} + +function GUI(props: GUIProps) { + const [waitingForSteps, setWaitingForSteps] = useState(false); + const [userInputQueue, setUserInputQueue] = useState([]); + const [history, setHistory] = useState(); + // { + // timeline: [ + // { + // step: { + // name: "RunCodeStep", + // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // description: + // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", + // }, + // output: [ + // { + // traceback: { + // frames: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // lineno: 7, + // function: "", + // code: "print(sum(first, second))", + // }, + // ], + // message: "unsupported operand type(s) for +: 'int' and 'str'", + // error_type: + // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', + // full_traceback: + // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", + // }, + // }, + // null, + // ], + // }, + // { + // step: { + // name: "EditCodeStep", + // range_in_files: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // range: { + // start: { + // line: 0, + // character: 0, + // }, + // end: { + // line: 6, + // character: 25, + // }, + // }, + // }, + // ], + // prompt: + // "I ran into this problem with my Python code:\n\n Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n Below are the files that might need to be fixed:\n\n {code}\n\n This is what the code should be in order to avoid the problem:\n", + // description: + // "Editing files: /Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // }, + // output: [ + // null, + // { + // reversible: true, + // actions: [ + // { + // reversible: true, + // filesystem: {}, + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // range: { + // start: { + // line: 0, + // character: 0, + // }, + // end: { + // line: 6, + // character: 25, + // }, + // }, + // replacement: + // "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))", + // }, + // ], + // }, + // ], + // }, + // { + // step: { + // name: "SolveTracebackStep", + // traceback: { + // frames: [ + // { + // filepath: + // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // lineno: 7, + // function: "", + // code: "print(sum(first, second))", + // }, + // ], + // message: "unsupported operand type(s) for +: 'int' and 'str'", + // error_type: + // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', + // full_traceback: + // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", + // }, + // description: "Running step: SolveTracebackStep", + // }, + // output: [null, null], + // }, + // { + // step: { + // name: "RunCodeStep", + // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", + // description: + // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", + // }, + // output: [null, null], + // }, + // ], + // current_index: 0, + // } as any + // ); + + const client = useContinueGUIProtocol(); + + useEffect(() => { + console.log("CLIENT ON STATE UPDATE: ", client, client?.onStateUpdate); + client?.onStateUpdate((state) => { + console.log("Received state update: ", state); + setWaitingForSteps(state.active); + setHistory(state.history); + setUserInputQueue(state.user_input_queue); + }); + }, [client]); + + const mainTextInputRef = useRef(null); + + useEffect(() => { + if (mainTextInputRef.current) { + mainTextInputRef.current.focus(); + let handler = (event: any) => { + if (event.data.type === "focusContinueInput") { + mainTextInputRef.current?.focus(); + } + }; + window.addEventListener("message", handler); + return () => { + window.removeEventListener("message", handler); + }; + } + }, [mainTextInputRef]); + + const onMainTextInput = () => { + if (mainTextInputRef.current) { + if (!client) return; + let input = mainTextInputRef.current.value; + setWaitingForSteps(true); + client.sendMainInput(input); + setUserInputQueue((queue) => { + return [...queue, input]; + }); + mainTextInputRef.current.value = ""; + mainTextInputRef.current.style.height = ""; + } + }; + + const onStepUserInput = (input: string, index: number) => { + if (!client) return; + console.log("Sending step user input", input, index); + client.sendStepUserInput(input, index); + }; + + // const iterations = useSelector(selectIterations); + return ( + + {typeof client === "undefined" && ( + <> + +

Server disconnected

+ + )} + {history?.timeline.map((node: HistoryNode, index: number) => { + return ( + { + onStepUserInput(input, index); + }} + inFuture={index > history?.current_index} + historyNode={node} + onRefinement={(input: string) => { + client?.sendRefinementInput(input, index); + }} + onReverse={() => { + client?.reverseToIndex(index); + }} + /> + ); + })} + {waitingForSteps && } + +
+ {userInputQueue.map((input) => { + return {input}; + })} +
+ + { + if (e.key === "Enter") { + onMainTextInput(); + e.stopPropagation(); + e.preventDefault(); + } + }} + rows={1} + onChange={() => { + let textarea = mainTextInputRef.current!; + textarea.style.height = ""; /* Reset the height*/ + textarea.style.height = + Math.min(textarea.scrollHeight - 15, 500) + "px"; + }} + > + +
+ ); +} + +export default GUI; diff --git a/extension/react-app/src/tabs/notebook.tsx b/extension/react-app/src/tabs/notebook.tsx deleted file mode 100644 index 02c9ff31..00000000 --- a/extension/react-app/src/tabs/notebook.tsx +++ /dev/null @@ -1,265 +0,0 @@ -import styled from "styled-components"; -import { - Button, - defaultBorderRadius, - vscBackground, - MainTextInput, - Loader, -} from "../components"; -import ContinueButton from "../components/ContinueButton"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { History } from "../../../schema/History"; -import { HistoryNode } from "../../../schema/HistoryNode"; -import StepContainer from "../components/StepContainer"; -import { useSelector } from "react-redux"; -import { RootStore } from "../redux/store"; -import useContinueWebsocket from "../hooks/useWebsocket"; -import useContinueNotebookProtocol from "../hooks/useWebsocket"; - -let TopNotebookDiv = styled.div` - display: grid; - grid-template-columns: 1fr; -`; - -let UserInputQueueItem = styled.div` - border-radius: ${defaultBorderRadius}; - color: gray; - padding: 8px; - margin: 8px; - text-align: center; -`; - -interface NotebookProps { - firstObservation?: any; -} - -function Notebook(props: NotebookProps) { - const [waitingForSteps, setWaitingForSteps] = useState(false); - const [userInputQueue, setUserInputQueue] = useState([]); - const [history, setHistory] = useState(); - // { - // timeline: [ - // { - // step: { - // name: "RunCodeStep", - // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // description: - // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", - // }, - // output: [ - // { - // traceback: { - // frames: [ - // { - // filepath: - // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // lineno: 7, - // function: "", - // code: "print(sum(first, second))", - // }, - // ], - // message: "unsupported operand type(s) for +: 'int' and 'str'", - // error_type: - // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', - // full_traceback: - // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", - // }, - // }, - // null, - // ], - // }, - // { - // step: { - // name: "EditCodeStep", - // range_in_files: [ - // { - // filepath: - // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // range: { - // start: { - // line: 0, - // character: 0, - // }, - // end: { - // line: 6, - // character: 25, - // }, - // }, - // }, - // ], - // prompt: - // "I ran into this problem with my Python code:\n\n Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n Below are the files that might need to be fixed:\n\n {code}\n\n This is what the code should be in order to avoid the problem:\n", - // description: - // "Editing files: /Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // }, - // output: [ - // null, - // { - // reversible: true, - // actions: [ - // { - // reversible: true, - // filesystem: {}, - // filepath: - // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // range: { - // start: { - // line: 0, - // character: 0, - // }, - // end: { - // line: 6, - // character: 25, - // }, - // }, - // replacement: - // "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))", - // }, - // ], - // }, - // ], - // }, - // { - // step: { - // name: "SolveTracebackStep", - // traceback: { - // frames: [ - // { - // filepath: - // "/Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // lineno: 7, - // function: "", - // code: "print(sum(first, second))", - // }, - // ], - // message: "unsupported operand type(s) for +: 'int' and 'str'", - // error_type: - // ' ^^^^^^^^^^^^^^^^^^\n File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n return a + b\n ~~^~~\nTypeError', - // full_traceback: - // "Traceback (most recent call last):\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in \n print(sum(first, second))\n ^^^^^^^^^^^^^^^^^^\n File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n return a + b\n ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'", - // }, - // description: "Running step: SolveTracebackStep", - // }, - // output: [null, null], - // }, - // { - // step: { - // name: "RunCodeStep", - // cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py", - // description: - // "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`", - // }, - // output: [null, null], - // }, - // ], - // current_index: 0, - // } as any - // ); - - const client = useContinueNotebookProtocol(); - - useEffect(() => { - console.log("CLIENT ON STATE UPDATE: ", client, client?.onStateUpdate); - client?.onStateUpdate((state) => { - console.log("Received state update: ", state); - setWaitingForSteps(state.active); - setHistory(state.history); - setUserInputQueue(state.user_input_queue); - }); - }, [client]); - - const mainTextInputRef = useRef(null); - - useEffect(() => { - if (mainTextInputRef.current) { - mainTextInputRef.current.focus(); - let handler = (event: any) => { - if (event.data.type === "focusContinueInput") { - mainTextInputRef.current?.focus(); - } - }; - window.addEventListener("message", handler); - return () => { - window.removeEventListener("message", handler); - }; - } - }, [mainTextInputRef]); - - const onMainTextInput = () => { - if (mainTextInputRef.current) { - if (!client) return; - let input = mainTextInputRef.current.value; - setWaitingForSteps(true); - client.sendMainInput(input); - setUserInputQueue((queue) => { - return [...queue, input]; - }); - mainTextInputRef.current.value = ""; - mainTextInputRef.current.style.height = ""; - } - }; - - const onStepUserInput = (input: string, index: number) => { - if (!client) return; - console.log("Sending step user input", input, index); - client.sendStepUserInput(input, index); - }; - - // const iterations = useSelector(selectIterations); - return ( - - {typeof client === "undefined" && ( - <> - -

Server disconnected

- - )} - {history?.timeline.map((node: HistoryNode, index: number) => { - return ( - { - onStepUserInput(input, index); - }} - inFuture={index > history?.current_index} - historyNode={node} - onRefinement={(input: string) => { - client?.sendRefinementInput(input, index); - }} - onReverse={() => { - client?.reverseToIndex(index); - }} - /> - ); - })} - {waitingForSteps && } - -
- {userInputQueue.map((input) => { - return {input}; - })} -
- - { - if (e.key === "Enter") { - onMainTextInput(); - e.stopPropagation(); - e.preventDefault(); - } - }} - rows={1} - onChange={() => { - let textarea = mainTextInputRef.current!; - textarea.style.height = ""; /* Reset the height*/ - textarea.style.height = - Math.min(textarea.scrollHeight - 15, 500) + "px"; - }} - > - -
- ); -} - -export default Notebook; diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index f8f3c65a..40def480 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -59,10 +59,10 @@ export function activateExtension( }) ), ]).then(() => { - ideProtocolClient?.openNotebook(); + ideProtocolClient?.openGUI(); }); } else { - ideProtocolClient.openNotebook().then(() => { + ideProtocolClient.openGUI().then(() => { // openCapturedTerminal(); }); } diff --git a/extension/src/commands.ts b/extension/src/commands.ts index aeeb4b4f..f0c1744b 100644 --- a/extension/src/commands.ts +++ b/extension/src/commands.ts @@ -62,11 +62,11 @@ const commandsMap: { [command: string]: (...args: any) => any } = { "continue.acceptSuggestion": acceptSuggestionCommand, "continue.rejectSuggestion": rejectSuggestionCommand, "continue.openDebugPanel": () => { - ideProtocolClient.openNotebook(); + ideProtocolClient.openGUI(); }, "continue.focusContinueInput": async () => { if (!debugPanelWebview) { - await ideProtocolClient.openNotebook(); + await ideProtocolClient.openGUI(); } debugPanelWebview?.postMessage({ type: "focusContinueInput", diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 477d1420..ab890801 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -97,7 +97,7 @@ class IdeProtocolClient { this.openFile(data.filepath); // TODO: Close file break; - case "openNotebook": + case "openGUI": case "connected": break; default: @@ -133,17 +133,17 @@ class IdeProtocolClient { // ------------------------------------ // // Initiate Request - closeNotebook(sessionId: string) { + closeGUI(sessionId: string) { this.panels.get(sessionId)?.dispose(); this.panels.delete(sessionId); } - async openNotebook() { + async openGUI() { console.log("OPENING NOTEBOOK"); if (this.messenger === null) { console.log("MESSENGER IS NULL"); } - const resp = await this.messenger?.sendAndReceive("openNotebook", {}); + const resp = await this.messenger?.sendAndReceive("openGUI", {}); const sessionId = resp.sessionId; console.log("SESSION ID", sessionId); -- cgit v1.2.3-70-g09d2 From 5e1216968b4bb1d67438e9d1b329932c5d55daab Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 2 Jun 2023 12:40:06 -0400 Subject: minor --- continuedev/src/continuedev/libs/steps/draft/dlt.py | 9 +++++++++ continuedev/src/continuedev/libs/steps/main.py | 4 +++- extension/src/continueIdeClient.ts | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index 73762327..f3417c9d 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -50,6 +50,15 @@ class SetupPipelineStep(Step): class ValidatePipelineStep(Step): + + async def describe(self, models: Models): + return dedent("""\ + This step will validate that your dlt pipeline is working as expected: + - Test that the API call works + - Load the data into a local DuckDB instance + - Write a query to view the data + """) + async def run(self, sdk: ContinueSDK): source_name = sdk.history.last_observation().values["source_name"] filename = f'{source_name}.py' diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index c70d5c2c..aefbe084 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -218,8 +218,10 @@ class StarCoderEditHighlightedCodeStep(Step): hide = False _prompt: str = "{code}{user_request}" + _prompt_and_completion: str = "" + async def describe(self, models: Models) -> Coroutine[str, None, None]: - return "Editing highlighted code" + return (await models.gpt35()).complete(f"{self._prompt_and_completion}\n\nPlease give brief a description of the changes made above using markdown bullet points:") async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: range_in_files = await sdk.ide.getHighlightedCode() diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index ab890801..03e5fbc5 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -139,7 +139,7 @@ class IdeProtocolClient { } async openGUI() { - console.log("OPENING NOTEBOOK"); + console.log("OPENING GUI"); if (this.messenger === null) { console.log("MESSENGER IS NULL"); } -- cgit v1.2.3-70-g09d2 From aea318b48dd7e15df16eca12ba59c677671869aa Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 2 Jun 2023 13:44:33 -0400 Subject: policy + starcoder description fixes --- continuedev/src/continuedev/core/policy.py | 2 +- continuedev/src/continuedev/libs/steps/draft/dlt.py | 15 +++++++-------- continuedev/src/continuedev/libs/steps/main.py | 2 ++ extension/package-lock.json | 4 ++-- extension/package.json | 2 +- extension/react-app/src/tabs/gui.tsx | 10 ++++++---- .../scripts/continuedev-0.1.0-py3-none-any.whl | Bin 57363 -> 55662 bytes 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 9f68515f..6e264bab 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -18,7 +18,7 @@ class DemoPolicy(Policy): if history.get_current() is None: return MessageStep(message="Welcome to Continue!") >> StepsOnStartupStep() - observation = history.last_observation() + observation = history.get_current().observation if observation is not None and isinstance(observation, UserInputObservation): # This could be defined with ObservationTypePolicy. Ergonomics not right though. if " test" in observation.user_input.lower(): diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index f3417c9d..9cec5014 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -50,14 +50,7 @@ class SetupPipelineStep(Step): class ValidatePipelineStep(Step): - - async def describe(self, models: Models): - return dedent("""\ - This step will validate that your dlt pipeline is working as expected: - - Test that the API call works - - Load the data into a local DuckDB instance - - Write a query to view the data - """) + hide: bool = True async def run(self, sdk: ContinueSDK): source_name = sdk.history.last_observation().values["source_name"] @@ -111,5 +104,11 @@ class CreatePipelineStep(Step): - Write a query to view the data""")) >> WaitForUserInputStep(prompt="What API do you want to load data from?") >> SetupPipelineStep(api_description="WeatherAPI.com API") >> + MessageStep(message=dedent("""\ + This step will validate that your dlt pipeline is working as expected: + - Test that the API call works + - Load the data into a local DuckDB instance + - Write a query to view the data + """)) >> ValidatePipelineStep() ) diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index aefbe084..73ad3352 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -253,6 +253,8 @@ class StarCoderEditHighlightedCodeStep(Step): if completion.endswith(eot_token): completion = completion[:completion.rindex(eot_token)] + self._prompt_and_completion += prompt + completion + await sdk.ide.applyFileSystemEdit( FileEdit(filepath=rif.filepath, range=rif.range, replacement=completion)) await sdk.ide.saveFile(rif.filepath) diff --git a/extension/package-lock.json b/extension/package-lock.json index ff4dbfa1..647e3aa2 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.11", + "version": "0.0.13", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.11", + "version": "0.0.13", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index 8f1eabde..1219ca8e 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.11", + "version": "0.0.13", "publisher": "Continue", "engines": { "vscode": "^1.74.0" diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx index 5ddddbfc..42ad4ed5 100644 --- a/extension/react-app/src/tabs/gui.tsx +++ b/extension/react-app/src/tabs/gui.tsx @@ -19,6 +19,7 @@ import useContinueGUIProtocol from "../hooks/useWebsocket"; let TopGUIDiv = styled.div` display: grid; grid-template-columns: 1fr; + overflow: scroll; `; let UserInputQueueItem = styled.div` @@ -37,7 +38,7 @@ function GUI(props: GUIProps) { const [waitingForSteps, setWaitingForSteps] = useState(false); const [userInputQueue, setUserInputQueue] = useState([]); const [history, setHistory] = useState(); - // { + // { // timeline: [ // { // step: { @@ -153,8 +154,7 @@ function GUI(props: GUIProps) { // }, // ], // current_index: 0, - // } as any - // ); + // } as any); const client = useContinueGUIProtocol(); @@ -211,7 +211,9 @@ function GUI(props: GUIProps) { {typeof client === "undefined" && ( <> -

Server disconnected

+

+ Trying to reconnect with server... +

)} {history?.timeline.map((node: HistoryNode, index: number) => { diff --git a/extension/scripts/continuedev-0.1.0-py3-none-any.whl b/extension/scripts/continuedev-0.1.0-py3-none-any.whl index 6cda6f2d..fd3f48d1 100644 Binary files a/extension/scripts/continuedev-0.1.0-py3-none-any.whl and b/extension/scripts/continuedev-0.1.0-py3-none-any.whl differ -- cgit v1.2.3-70-g09d2