diff options
Diffstat (limited to 'continuedev/src')
-rw-r--r-- | continuedev/src/continuedev/core/autopilot.py | 56 | ||||
-rw-r--r-- | continuedev/src/continuedev/core/main.py | 47 | ||||
-rw-r--r-- | continuedev/src/continuedev/core/observation.py | 1 | ||||
-rw-r--r-- | continuedev/src/continuedev/core/policy.py | 8 | ||||
-rw-r--r-- | continuedev/src/continuedev/core/sdk.py | 29 | ||||
-rw-r--r-- | continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py | 11 | ||||
-rw-r--r-- | continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py | 110 | ||||
-rw-r--r-- | continuedev/src/continuedev/server/ide.py | 15 | ||||
-rw-r--r-- | continuedev/src/continuedev/server/ide_protocol.py | 8 | ||||
-rw-r--r-- | continuedev/src/continuedev/steps/core/core.py | 129 | ||||
-rw-r--r-- | continuedev/src/continuedev/steps/main.py | 17 |
11 files changed, 302 insertions, 129 deletions
diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index db06c975..5a6bd2e7 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -1,13 +1,13 @@ import traceback import time -from typing import Callable, Coroutine, List +from typing import Any, Callable, Coroutine, Dict, List from ..models.filesystem_edit import FileEditWithFullContents from ..libs.llm import LLM from .observation import Observation, InternalErrorObservation 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 .main import Context, ContinueCustomException, Policy, History, FullState, Step, HistoryNode from ..steps.core.core import ReversibleStep, ManualEditStep, UserInputStep from ..libs.util.telemetry import capture_event from .sdk import ContinueSDK @@ -18,6 +18,7 @@ class Autopilot(ContinueBaseModel): policy: Policy ide: AbstractIdeProtocolServer history: History = History.from_empty() + context: Context = Context() _on_update_callbacks: List[Callable[[FullState], None]] = [] _active: bool = False @@ -26,6 +27,10 @@ class Autopilot(ContinueBaseModel): _user_input_queue = AsyncSubscriptionQueue() + @property + def continue_sdk(self) -> ContinueSDK: + return ContinueSDK(self) + class Config: arbitrary_types_allowed = True @@ -60,7 +65,7 @@ class Autopilot(ContinueBaseModel): current_step = self.history.get_current().step self.history.step_back() if issubclass(current_step.__class__, ReversibleStep): - await current_step.reverse(ContinueSDK(self)) + await current_step.reverse(self.continue_sdk) await self.update_subscribers() except Exception as e: @@ -78,9 +83,9 @@ class Autopilot(ContinueBaseModel): _step_depth: int = 0 async def retry_at_index(self, index: int): - last_step = self.history.pop_last_step() + step = self.history.timeline[index].step.copy() await self.update_subscribers() - await self._run_singular_step(last_step) + await self._run_singular_step(step) async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: capture_event( @@ -105,7 +110,26 @@ class Autopilot(ContinueBaseModel): self._step_depth += 1 try: - observation = await step(ContinueSDK(self)) + observation = await step(self.continue_sdk) + except ContinueCustomException as e: + # Attach an InternalErrorObservation to the step and unhide it. + error_string = e.message + print( + f"\n{error_string}\n{e}") + + observation = InternalErrorObservation( + error=error_string, title=e.title) + + # Reveal this step, but hide all of the following steps (its substeps) + step.hide = False + i = self.history.get_current_index() + while self.history.timeline[i].step.name != step.name: + self.history.timeline[i].step.hide = True + i -= 1 + + if e.with_step is not None: + await self._run_singular_step(e.with_step) + except Exception as e: # Attach an InternalErrorObservation to the step and unhide it. error_string = '\n\n'.join( @@ -114,8 +138,14 @@ class Autopilot(ContinueBaseModel): f"Error while running step: \n{error_string}\n{e}") observation = InternalErrorObservation( - error=error_string) + error=error_string, title=e.__repr__()) + + # Reveal this step, but hide all of the following steps (its substeps) step.hide = False + i = self.history.get_current_index() + while self.history.timeline[i].step.name != step.name: + self.history.timeline[i].step.hide = True + i -= 1 self._step_depth -= 1 @@ -125,11 +155,13 @@ class Autopilot(ContinueBaseModel): await self.update_subscribers() # 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()) + if step.description is None: + async def update_description(): + step.description = await step.describe(self.continue_sdk.models) + # Update subscribers with new description + await self.update_subscribers() + + asyncio.create_task(update_description()) return observation diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index b2b97bae..37d80de3 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -67,11 +67,16 @@ class History(ContinueBaseModel): return None return state.observation - def pop_last_step(self) -> Union[HistoryNode, None]: - if self.current_index < 0: + def pop_step(self, index: int = None) -> Union[HistoryNode, None]: + index = index if index is not None else self.current_index + if index < 0 or self.current_index < 0: return None - node = self.timeline.pop(self.current_index) - self.current_index -= 1 + + node = self.timeline.pop(index) + + if index <= self.current_index: + self.current_index -= 1 + return node.step @classmethod @@ -105,7 +110,7 @@ class Policy(ContinueBaseModel): class Step(ContinueBaseModel): name: str = None hide: bool = False - _description: Union[str, None] = None + description: Union[str, None] = None system_message: Union[str, None] = None @@ -113,17 +118,14 @@ class Step(ContinueBaseModel): copy_on_model_validation = False async def describe(self, models: Models) -> Coroutine[str, None, None]: - if self._description is not None: - return self._description + 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 + if self.description is not None: + d["description"] = self.description else: d["description"] = "`Description loading...`" return d @@ -173,4 +175,25 @@ class Validator(Step): raise NotImplementedError +class Context: + key_value: Dict[str, str] = {} + + def set(self, key: str, value: str): + self.key_value[key] = value + + def get(self, key: str) -> str: + return self.key_value[key] + + +class ContinueCustomException(Exception): + title: str + message: str + with_step: Union[Step, None] + + def __init__(self, message: str, title: str = "Error while running step:", with_step: Union[Step, None] = None): + self.message = message + self.title = title + self.with_step = with_step + + HistoryNode.update_forward_refs() diff --git a/continuedev/src/continuedev/core/observation.py b/continuedev/src/continuedev/core/observation.py index b6117236..126cf19e 100644 --- a/continuedev/src/continuedev/core/observation.py +++ b/continuedev/src/continuedev/core/observation.py @@ -36,4 +36,5 @@ class TextObservation(Observation): class InternalErrorObservation(Observation): + title: str error: str diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 4287bb6e..8aea8de7 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -17,7 +17,11 @@ class DemoPolicy(Policy): 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!") >> SetupContinueWorkspaceStep() >> CreateCodebaseIndexChroma() >> StepsOnStartupStep() + return ( + # MessageStep(name="Welcome to Continue!", message="") >> + # SetupContinueWorkspaceStep() >> + # CreateCodebaseIndexChroma() >> + StepsOnStartupStep()) observation = history.get_current().observation if observation is not None and isinstance(observation, UserInputObservation): @@ -34,7 +38,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 StarCoderEditHighlightedCodeStep(user_input=observation.user_input) + return EditHighlightedCodeStep(user_input=observation.user_input) state = history.get_current() diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 8317a3d1..51faadf2 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -2,16 +2,17 @@ from abc import ABC, abstractmethod from typing import Coroutine, Union import os +from ..steps.core.core import Gpt35EditCodeStep +from ..models.main import Range from .abstract_sdk import AbstractContinueSDK from .config import ContinueConfig, load_config 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 from ..libs.llm.openai import OpenAI from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer -from .main import History, Step +from .main import Context, ContinueCustomException, History, Step from ..steps.core.core import * @@ -44,6 +45,7 @@ class ContinueSDK(AbstractContinueSDK): ide: AbstractIdeProtocolServer steps: ContinueSDKSteps models: Models + context: Context __autopilot: Autopilot def __init__(self, autopilot: Autopilot): @@ -51,6 +53,7 @@ class ContinueSDK(AbstractContinueSDK): self.__autopilot = autopilot self.steps = ContinueSDKSteps(self) self.models = Models(self) + self.context = autopilot.context @property def history(self) -> History: @@ -64,8 +67,8 @@ class ContinueSDK(AbstractContinueSDK): async def run_step(self, step: Step) -> Coroutine[Observation, None, None]: 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 apply_filesystem_edit(self, edit: FileSystemEdit, name: str = None, description: str = None): + return await self.run_step(FileSystemEditStep(edit=edit, description=description, **({'name': name} if name else {}))) async def wait_for_user_input(self) -> str: return await self.__autopilot.wait_for_user_input() @@ -73,18 +76,21 @@ class ContinueSDK(AbstractContinueSDK): async def wait_for_user_confirmation(self, prompt: str): return await self.run_step(WaitForUserConfirmationStep(prompt=prompt)) - async def run(self, commands: Union[List[str], str], cwd: str = None): + async def run(self, commands: Union[List[str], str], cwd: str = None, name: str = None, description: str = None) -> Coroutine[str, None, None]: commands = commands if isinstance(commands, List) else [commands] - return await self.run_step(ShellCommandsStep(cmds=commands, cwd=cwd)) + return (await self.run_step(ShellCommandsStep(cmds=commands, cwd=cwd, description=description, **({'name': name} if name else {})))).text - async def edit_file(self, filename: str, prompt: str): + async def edit_file(self, filename: str, prompt: str, name: str = None, description: str = None, range: Range = None): 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(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' + await self.run_step(Gpt35EditCodeStep( + range_in_files=[RangeInFile(filepath=filename, range=range) if range is not None else RangeInFile.from_entire_file( + filepath, contents)], + user_input=prompt, + description=description, + **({'name': name} if name else {}) )) async def append_to_file(self, filename: str, content: str): @@ -126,3 +132,6 @@ class ContinueSDK(AbstractContinueSDK): def set_loading_message(self, message: str): # self.__autopilot.set_loading_message(message) raise NotImplementedError() + + def raise_exception(self, message: str, title: str, with_step: Union[Step, None] = None): + raise ContinueCustomException(message, title, with_step) diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py index 4a4604d6..428ac9cc 100644 --- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py @@ -11,8 +11,8 @@ class CreatePipelineRecipe(Step): hide: bool = True async def run(self, sdk: ContinueSDK): - await sdk.run_step( - MessageStep(message=dedent("""\ + text_observation = await sdk.run_step( + MessageStep(name="Building your first dlt pipeline", 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 @@ -21,7 +21,10 @@ class CreatePipelineRecipe(Step): - 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") >> + WaitForUserInputStep( + prompt="What API do you want to load data from? (e.g. weatherapi.com, chess.com)") + ) + await sdk.run_step( + SetupPipelineStep(api_description=text_observation.text) >> ValidatePipelineStep() ) diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py index ef5e3b43..511abd1f 100644 --- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py @@ -1,12 +1,19 @@ +import os +import subprocess from textwrap import dedent +import time +from ...models.main import Range +from ...models.filesystem import RangeInFile from ...steps.main import MessageStep from ...core.sdk import Models -from ...core.observation import DictObservation -from ...models.filesystem_edit import AddFile +from ...core.observation import DictObservation, InternalErrorObservation +from ...models.filesystem_edit import AddFile, FileEdit from ...core.main import Step from ...core.sdk import ContinueSDK +AI_ASSISTED_STRING = "(✨ AI-Assisted ✨)" + class SetupPipelineStep(Step): hide: bool = True @@ -21,6 +28,8 @@ class SetupPipelineStep(Step): """) async def run(self, sdk: ContinueSDK): + sdk.context.set("api_description", self.api_description) + 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' @@ -30,48 +39,100 @@ class SetupPipelineStep(Step): 'python3 -m venv env', 'source env/bin/activate', 'pip install dlt', - f'dlt init {source_name} duckdb', - 'Y', + f'dlt --non-interactive init {source_name} duckdb', 'pip install -r requirements.txt' - ]) + ], description=dedent(f"""\ + Running the following commands: + - `python3 -m venv env`: Create a Python virtual environment + - `source env/bin/activate`: Activate the virtual environment + - `pip install dlt`: Install dlt + - `dlt init {source_name} duckdb`: Create a new dlt pipeline called {source_name} that loads data into a local DuckDB instance + - `pip install -r requirements.txt`: Install the Python dependencies for the pipeline"""), name="Setup Python environment") # editing the resource function to call the requested API + resource_function_range = Range.from_shorthand(15, 0, 29, 0) + await sdk.ide.highlightCode(RangeInFile(filepath=os.path.join(await sdk.ide.getWorkspaceDirectory(), filename), range=resource_function_range), "#00ff0022") + + # sdk.set_loading_message("Writing code to call the API...") await sdk.edit_file( + range=resource_function_range, filename=filename, - prompt=f'Edit the resource function to call the API described by this: {self.api_description}' + prompt=f'Edit the resource function to call the API described by this: {self.api_description}. Do not move or remove the exit() call in __main__.', + name=f"Edit the resource function to call the API {AI_ASSISTED_STRING}" ) + time.sleep(1) + # 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("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}) + + sdk.context.set("source_name", source_name) class ValidatePipelineStep(Step): hide: bool = True async def run(self, sdk: ContinueSDK): - source_name = sdk.history.last_observation().values["source_name"] + workspace_dir = await sdk.ide.getWorkspaceDirectory() + source_name = sdk.context.get("source_name") filename = f'{source_name}.py' - await sdk.run_step(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 - """))) + # await sdk.run_step(MessageStep(name="Validate the pipeline", message=dedent("""\ + # Next, we 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 + # """))) # test that the API call works - await sdk.run(f'python3 {filename}') + output = await sdk.run(f'python3 {filename}', name="Test the pipeline", description=f"Running `python3 {filename}` to test loading data from the API") + + # If it fails, return the error + if "Traceback" in output: + output = "Traceback" + output.split("Traceback")[-1] + file_content = await sdk.ide.readFile(os.path.join(workspace_dir, filename)) + suggestion = (await sdk.models.gpt35()).complete(dedent(f"""\ + ```python + {file_content} + ``` + This above code is a dlt pipeline that loads data from an API. The function with the @resource decorator is responsible for calling the API and returning the data. While attempting to run the pipeline, the following error occurred: + + ```ascii + {output} + ``` + + This is a brief summary of the error followed by a suggestion on how it can be fixed by editing the resource function:""")) + + api_documentation_url = (await sdk.models.gpt35()).complete(dedent(f"""\ + The API I am trying to call is the '{sdk.context.get('api_description')}'. I tried calling it in the @resource function like this: + ```python + {file_content} + ``` + What is the URL for the API documentation that will help me learn how to make this call? Please format in markdown so I can click the link.""")) + + sdk.raise_exception( + title=f"Error while running pipeline.\nFix the resource function in {filename} and rerun this step", message=output, with_step=MessageStep(name=f"Suggestion to solve error {AI_ASSISTED_STRING}", message=dedent(f"""\ + {suggestion} + + {api_documentation_url} + + After you've fixed the code, click the retry button at the top of the Validate Pipeline step above."""))) # remove exit() from the main main function - await sdk.edit_file( - filename=filename, - prompt='Remove exit() from the main function' - ) + await sdk.run_step(MessageStep(name="Remove early exit() from main function", message="Remove the early exit() from the main function now that we are done testing and want the pipeline to load the data into DuckDB.")) + + contents = await sdk.ide.readFile(os.path.join(workspace_dir, filename)) + replacement = "\n".join( + list(filter(lambda line: line.strip() != "exit()", contents.split("\n")))) + await sdk.ide.applyFileSystemEdit(FileEdit( + filepath=os.path.join(workspace_dir, filename), + replacement=replacement, + range=Range.from_entire_file(contents) + )) # load the data into the DuckDB instance - await sdk.run(f'python3 {filename}') + await sdk.run(f'python3 {filename}', name="Load data into DuckDB", description=f"Running python3 {filename} to load data into DuckDB") table_name = f"{source_name}.{source_name}_resource" tables_query_code = dedent(f'''\ @@ -85,9 +146,8 @@ class ValidatePipelineStep(Step): # print table names for row in rows: - print(row) - ''') + print(row)''') - 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') + query_filename = os.path.join(workspace_dir, "query.py") + await sdk.apply_filesystem_edit(AddFile(filepath=query_filename, content=tables_query_code), name="Add query.py file", description="Adding a file called `query.py` to the workspace that will run a test query on the DuckDB instance") + await sdk.run('env/bin/python3 query.py', name="Run test query", description="Running `env/bin/python3 query.py` to test that the data was loaded into DuckDB as expected") diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index eec5b607..007eb2b4 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -76,6 +76,10 @@ class GetUserSecretResponse(BaseModel): value: str +class RunCommandResponse(BaseModel): + output: str + + T = TypeVar("T", bound=BaseModel) @@ -110,7 +114,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): fileEdits = list( map(lambda d: FileEditWithFullContents.parse_obj(d), data["fileEdits"])) self.onFileEdits(fileEdits) - elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory", "getUserSecret"]: + elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory", "getUserSecret", "runCommand"]: self.sub_queue.post(message_type, data) else: raise ValueError("Unknown message type", message_type) @@ -133,6 +137,15 @@ class IdeProtocolServer(AbstractIdeProtocolServer): "sessionId": session_id }) + async def highlightCode(self, range_in_file: RangeInFile, color: str): + await self._send_json("highlightCode", { + "rangeInFile": range_in_file.dict(), + "color": color + }) + + async def runCommand(self, command: str) -> str: + return (await self._send_and_receive_json({"command": command}, RunCommandResponse, "runCommand")).output + async def showSuggestionsAndWait(self, suggestions: List[FileEdit]) -> bool: ids = [str(uuid.uuid4()) for _ in suggestions] for i in range(len(suggestions)): diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py index 8f155415..4622d6ff 100644 --- a/continuedev/src/continuedev/server/ide_protocol.py +++ b/continuedev/src/continuedev/server/ide_protocol.py @@ -82,3 +82,11 @@ class AbstractIdeProtocolServer(ABC): @abstractmethod async def getUserSecret(self, key: str): """Get a user secret""" + + @abstractmethod + async def highlightCode(self, range_in_file: RangeInFile, color: str): + """Highlight code""" + + @abstractmethod + async def runCommand(self, command: str) -> str: + """Run a command""" diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index fdcd9837..413bc195 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -4,7 +4,7 @@ from textwrap import dedent from typing import Coroutine, List, Union from ...libs.llm.prompt_utils import MarkdownStyleEncoderDecoder -from ...models.filesystem_edit import EditDiff, FileEditWithFullContents, FileSystemEdit +from ...models.filesystem_edit import EditDiff, FileEdit, FileEditWithFullContents, FileSystemEdit from ...models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents from ...core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation from ...core.main import Step, SequentialStep @@ -55,40 +55,67 @@ class ShellCommandsStep(Step): 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) + for cmd in self.cmds: + output = await sdk.ide.runCommand(cmd) - stdin_input = "\n".join(self.cmds) - out, err = process.communicate(stdin_input.encode()) + return TextObservation(text=output) - # If it fails, return the error - if err is not None and err != "": - self._err_text = err - return TextObservation(text=err) + # process = subprocess.Popen( + # '/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd) - return None + # 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 != "": + # self._err_text = err + # return TextObservation(text=err) -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" + # return None - _edit_diffs: Union[List[EditDiff], None] = None - _prompt: Union[str, None] = None - _completion: Union[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 (await models.gpt35()).complete(dedent(f"""{self._prompt}{self._completion} +class Gpt35EditCodeStep(Step): + user_input: str + range_in_files: List[RangeInFile] + name: str = "Editing Code" + hide = False + _prompt: str = dedent("""\ + Take the file prefix and suffix into account, but only rewrite the commit before as specified in the commit message. Here's an example: + + <file_prefix> + a = 5 + b = 4 + + <file_suffix> + + def mul(a, b): + return a * b + <commit_before> + def sum(): + return a + b + <commit_msg> + Make a and b parameters of sum + <commit_after> + def sum(a, b): + return a + b + <|endoftext|> + + Now complete the real thing: + + <file_prefix> + {file_prefix} + <file_suffix> + {file_suffix} + <commit_before> + {code} + <commit_msg> + {user_request} + <commit_after>""") + + _prompt_and_completion: str = "" - Maximally concise summary of changes in bullet points (can use markdown): - """)) + async def describe(self, models: Models) -> Coroutine[str, None, None]: + 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]: rif_with_contents = [] @@ -96,28 +123,27 @@ class EditCodeStep(Step): 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 = (await sdk.models.gpt35()).complete(prompt) - # Temporarily doing this to generate description. - self._prompt = prompt - self._completion = completion + rif_dict = {} + for rif in rif_with_contents: + rif_dict[rif.filepath] = rif.contents - file_edits = enc_dec.decode(completion) + for rif in rif_with_contents: + full_file_contents = await sdk.ide.readFile(rif.filepath) + segs = full_file_contents.split(rif.contents) + prompt = self._prompt.format( + code=rif.contents, user_request=self.user_input, file_prefix=segs[0], file_suffix=segs[1]) - self._edit_diffs = [] - for file_edit in file_edits: - diff = await sdk.apply_filesystem_edit(file_edit) - self._edit_diffs.append(diff) + completion = str((await sdk.models.gpt35()).complete(prompt)) + eot_token = "<|endoftext|>" + completion = completion.removesuffix(eot_token) - for filepath in set([file_edit.filepath for file_edit in file_edits]): - await sdk.ide.saveFile(filepath) - await sdk.ide.setFileOpen(filepath) + self._prompt_and_completion += prompt + completion - return None + 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 EditFileStep(Step): @@ -130,10 +156,10 @@ class EditFileStep(Step): async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: file_contents = await sdk.ide.readFile(self.filepath) - await sdk.run_step(EditCodeStep( + await sdk.run_step(Gpt35EditCodeStep( range_in_files=[RangeInFile.from_entire_file( self.filepath, file_contents)], - prompt=self.prompt + user_input=self.prompt )) @@ -191,13 +217,18 @@ class WaitForUserInputStep(Step): name: str = "Waiting for user input" _description: Union[str, None] = None + _response: Union[str, None] = None async def describe(self, models: Models) -> Coroutine[str, None, None]: - return self.prompt + if self._response is None: + return self.prompt + else: + return f"{self.prompt}\n\n`{self._response}`" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - self._description = self.prompt + self.description = self.prompt resp = await sdk.wait_for_user_input() + self.description = f"{self.prompt}\n\n`{resp}`" return TextObservation(text=resp) @@ -209,7 +240,7 @@ class WaitForUserConfirmationStep(Step): return self.prompt async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - self._description = self.prompt + self.description = self.prompt resp = await sdk.wait_for_user_input() self.hide = True return TextObservation(text=resp) diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index da0fc8d2..69c98bd4 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -15,7 +15,7 @@ from ..core.main import Step from ..core.sdk import ContinueSDK, Models from ..core.observation import Observation import subprocess -from .core.core import EditCodeStep +from .core.core import Gpt35EditCodeStep class SetupContinueWorkspaceStep(Step): @@ -255,19 +255,9 @@ class StarCoderEditHighlightedCodeStep(Step): class EditHighlightedCodeStep(Step): user_input: str hide = True - _prompt: str = dedent("""Below is the code before changes: - -{code} - -This is the user request: - -{user_input} - -This is the code after being changed to perfectly satisfy the user request: - """) async def describe(self, models: Models) -> Coroutine[str, None, None]: - return "Editing highlighted code" + return "Editing code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: range_in_files = await sdk.ide.getHighlightedCode() @@ -281,8 +271,7 @@ This is the code after being changed to perfectly satisfy the user request: range_in_files = [RangeInFile.from_entire_file( filepath, content) for filepath, content in contents.items()] - await sdk.run_step(EditCodeStep( - range_in_files=range_in_files, prompt=self._prompt.format(code="{code}", user_input=self.user_input))) + await sdk.run_step(Gpt35EditCodeStep(user_input=self.user_input, range_in_files=range_in_files)) class FindCodeStep(Step): |