summaryrefslogtreecommitdiff
path: root/continuedev/src
diff options
context:
space:
mode:
Diffstat (limited to 'continuedev/src')
-rw-r--r--continuedev/src/continuedev/core/autopilot.py56
-rw-r--r--continuedev/src/continuedev/core/main.py47
-rw-r--r--continuedev/src/continuedev/core/observation.py1
-rw-r--r--continuedev/src/continuedev/core/policy.py8
-rw-r--r--continuedev/src/continuedev/core/sdk.py29
-rw-r--r--continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py11
-rw-r--r--continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py110
-rw-r--r--continuedev/src/continuedev/server/ide.py15
-rw-r--r--continuedev/src/continuedev/server/ide_protocol.py8
-rw-r--r--continuedev/src/continuedev/steps/core/core.py129
-rw-r--r--continuedev/src/continuedev/steps/main.py17
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):