summaryrefslogtreecommitdiff
path: root/continuedev/src/continuedev/libs/steps
diff options
context:
space:
mode:
Diffstat (limited to 'continuedev/src/continuedev/libs/steps')
-rw-r--r--continuedev/src/continuedev/libs/steps/__init__.py1
-rw-r--r--continuedev/src/continuedev/libs/steps/chroma.py62
-rw-r--r--continuedev/src/continuedev/libs/steps/draft/abstract_method.py18
-rw-r--r--continuedev/src/continuedev/libs/steps/draft/dlt.py81
-rw-r--r--continuedev/src/continuedev/libs/steps/draft/redux.py48
-rw-r--r--continuedev/src/continuedev/libs/steps/draft/typeorm.py42
-rw-r--r--continuedev/src/continuedev/libs/steps/main.py345
-rw-r--r--continuedev/src/continuedev/libs/steps/migration.py26
-rw-r--r--continuedev/src/continuedev/libs/steps/nate.py215
-rw-r--r--continuedev/src/continuedev/libs/steps/pytest.py37
-rw-r--r--continuedev/src/continuedev/libs/steps/ty.py153
11 files changed, 1028 insertions, 0 deletions
diff --git a/continuedev/src/continuedev/libs/steps/__init__.py b/continuedev/src/continuedev/libs/steps/__init__.py
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/continuedev/src/continuedev/libs/steps/__init__.py
@@ -0,0 +1 @@
+
diff --git a/continuedev/src/continuedev/libs/steps/chroma.py b/continuedev/src/continuedev/libs/steps/chroma.py
new file mode 100644
index 00000000..2d8742e8
--- /dev/null
+++ b/continuedev/src/continuedev/libs/steps/chroma.py
@@ -0,0 +1,62 @@
+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 ..chroma.query import query_codebase_index
+from .main import EditFileStep
+
+
+class AnswerQuestionChroma(Step):
+ question: str
+ _answer: Union[str, None] = None
+ name: str = "Answer Question"
+
+ async def describe(self, llm) -> Coroutine[str, None, None]:
+ if self._answer is None:
+ return f"Answering the question: {self.question}"
+ else:
+ return self._answer
+
+ async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
+ results = query_codebase_index(self.question)
+
+ code_snippets = ""
+
+ files = []
+ for node in results.source_nodes:
+ resource_name = list(node.node.relationships.values())[0]
+ filepath = resource_name[:resource_name.index("::")]
+ files.append(filepath)
+ code_snippets += f"""{filepath}```\n{node.node.text}\n```\n\n"""
+
+ prompt = dedent(f"""Here are a few snippets of code that might be useful in answering the question:
+
+ {code_snippets}
+
+ Here is the question to answer:
+
+ {self.question}
+
+ Here is the answer:""")
+
+ answer = sdk.llm.complete(prompt)
+ print(answer)
+ self._answer = answer
+
+ await sdk.ide.setFileOpen(files[0])
+
+
+class EditFileChroma(Step):
+ request: str
+ hide: bool = True
+
+ async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
+ results = query_codebase_index(self.request)
+
+ resource_name = list(
+ results.source_nodes[0].node.relationships.values())[0]
+ filepath = resource_name[:resource_name.index("::")]
+
+ await sdk.run_step(EditFileStep(filepath=filepath, prompt=f"Here is the code:\n\n{{code}}\n\nHere is the user request:\n\n{self.request}\n\nHere is the code after making the requested changes:\n"))
diff --git a/continuedev/src/continuedev/libs/steps/draft/abstract_method.py b/continuedev/src/continuedev/libs/steps/draft/abstract_method.py
new file mode 100644
index 00000000..927d93fd
--- /dev/null
+++ b/continuedev/src/continuedev/libs/steps/draft/abstract_method.py
@@ -0,0 +1,18 @@
+from ...core import ContinueSDK, Step
+
+
+class ImplementAbstractMethodStep(Step):
+ name: str = "Implement abstract method for all subclasses"
+ method_name: str
+ class_name: str
+
+ async def run(self, sdk: ContinueSDK):
+
+ implementations = await sdk.lsp.go_to_implementations(self.class_name)
+
+ for implementation in implementations:
+
+ await sdk.edit_file(
+ range_in_files=[implementation.range_in_file],
+ prompt=f"Implement method `{self.method_name}` for this subclass of `{self.class_name}`",
+ )
diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py
new file mode 100644
index 00000000..608f089a
--- /dev/null
+++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py
@@ -0,0 +1,81 @@
+from textwrap import dedent
+from ....models.filesystem_edit import AddFile
+from ...core import Step, ContinueSDK
+from ..main import WaitForUserInputStep
+
+
+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(
+ f"Write a snake_case name for the data source described by {self.api_description}: ").strip()
+ filename = f'{source_name}.py'
+
+ # running commands to get started when creating a new dlt pipeline
+ await sdk.run([
+ 'python3 -m venv env',
+ 'source env/bin/activate',
+ 'pip install dlt',
+ 'dlt init {source_name} duckdb',
+ 'Y',
+ 'pip install -r requirements.txt'
+ ])
+
+ # editing the resource function to call the requested API
+ await sdk.edit_file(
+ filename=filename,
+ prompt=f'Edit the resource function to call the API described by this: {self.api_description}'
+ )
+
+ # wait for user to put API key in secrets.toml
+ await sdk.ide.setFileOpen(".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}
+
+
+class ValidatePipelineStep(Step):
+
+ async def run(self, sdk: ContinueSDK):
+ source_name = sdk.history.last_observation()["source_name"]
+ filename = f'{source_name}.py'
+
+ # test that the API call works
+ await sdk.run(f'python3 {filename}')
+
+ # remove exit() from the main main function
+ await sdk.edit_file(
+ filename=filename,
+ prompt='Remove exit() from the main function'
+ )
+
+ # load the data into the DuckDB instance
+ await sdk.run(f'python3 {filename}')
+
+ table_name = f"{source_name}.{source_name}_resource"
+ tables_query_code = dedent(f'''\
+ import duckdb
+
+ # connect to DuckDB instance
+ conn = duckdb.connect(database="{source_name}.duckdb")
+
+ # get table names
+ rows = conn.execute("SELECT * FROM {table_name};").fetchall()
+
+ # print table names
+ for row in rows:
+ print(row)
+ ''')
+ await sdk.apply_filesystem_edit(AddFile(filepath='query.py', content=tables_query_code))
+ await sdk.run('env/bin/python3 query.py')
+
+
+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() >>
+ ValidatePipelineStep()
+ )
diff --git a/continuedev/src/continuedev/libs/steps/draft/redux.py b/continuedev/src/continuedev/libs/steps/draft/redux.py
new file mode 100644
index 00000000..52a8fbd8
--- /dev/null
+++ b/continuedev/src/continuedev/libs/steps/draft/redux.py
@@ -0,0 +1,48 @@
+from textwrap import dedent
+from ....models.filesystem_edit import AddFile
+from ...core import Step, ContinueSDK
+from ..main import WaitForUserInputStep, EditFileStep
+
+
+class EditReduxStateStep(Step):
+
+ description: str # e.g. "I want to load data from the weatherapi.com API"
+
+ async def run(self, sdk: ContinueSDK):
+ # Find the right file to edit
+
+ # RootStore
+ store_filename = ""
+ sdk.run_step(
+ EditFileStep(
+ filename=store_filename,
+ prompt=f"Edit the root store to add a new slice for {self.description}"
+ )
+ )
+ store_file_contents = await sdk.ide.readFile(store_filename)
+
+ # Selector
+ selector_filename = ""
+ sdk.run_step(EditFileStep(
+ filepath=selector_filename,
+ prompt=f"Edit the selector to add a new property for {self.description}. The store looks like this: {store_file_contents}"
+ )
+
+ # Reducer
+ reducer_filename = ""
+ sdk.run_step(EditFileStep(
+ filepath=reducer_filename,
+ prompt=f"Edit the reducer to add a new property for {self.description}. The store looks like this: {store_file_contents}"
+
+ """
+ Starts with implementing selector
+ 1. RootStore
+ 2. Selector
+ 3. Reducer or entire slice
+
+ Need to first determine whether this is an:
+ 1. edit
+ 2. add new reducer and property in existing slice
+ 3. add whole new slice
+ 4. build redux from scratch
+ """
diff --git a/continuedev/src/continuedev/libs/steps/draft/typeorm.py b/continuedev/src/continuedev/libs/steps/draft/typeorm.py
new file mode 100644
index 00000000..9d058f1e
--- /dev/null
+++ b/continuedev/src/continuedev/libs/steps/draft/typeorm.py
@@ -0,0 +1,42 @@
+from textwrap import dedent
+from ...core import Step, ContinueSDK
+
+
+class CreateTableStep(Step):
+ sql_str: str
+ name: str = "Create a table in TypeORM"
+
+ async def run(self, sdk: ContinueSDK):
+ # Write TypeORM entity
+ entity_name = self.sql_str.split(" ")[2].capitalize()
+ await sdk.edit_file(
+ f"src/entity/{entity_name}.ts",
+ dedent(f"""\
+ {self.sql_str}
+
+ Write a TypeORM entity called {entity_name} for this table, importing as necessary:""")
+ )
+
+ # Add entity to data-source.ts
+ await sdk.edit_file(filepath="src/data-source.ts", prompt=f"Add the {entity_name} entity:")
+
+ # Generate blank migration for the entity
+ out = await sdk.run(f"npx typeorm migration:create ./src/migration/Create{entity_name}Table")
+ migration_filepath = out.text.split(" ")[1]
+
+ # Wait for user input
+ await sdk.wait_for_user_confirmation("Fill in the migration?")
+
+ # Fill in the migration
+ await sdk.edit_file(
+ migration_filepath,
+ dedent(f"""\
+ This is the table that was created:
+
+ {self.sql_str}
+
+ Fill in the migration for the table:"""),
+ )
+
+ # Run the migration
+ await sdk.run("npx typeorm-ts-node-commonjs migration:run -d ./src/data-source.ts")
diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py
new file mode 100644
index 00000000..70953e95
--- /dev/null
+++ b/continuedev/src/continuedev/libs/steps/main.py
@@ -0,0 +1,345 @@
+import time
+from typing import Callable, Coroutine, List, Union
+
+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 ..llm.prompt_utils import MarkdownStyleEncoderDecoder
+from textwrap import dedent
+from ..core import History, Policy, Step, ContinueSDK, Observation
+import subprocess
+from ..util.traceback_parsers import parse_python_traceback
+from ..observation import TracebackObservation
+import json
+
+
+class RunPolicyUntilDoneStep(Step):
+ policy: "Policy"
+
+ async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
+ next_step = self.policy.next(sdk.history)
+ while next_step is not None:
+ observation = await sdk.run_step(next_step)
+ next_step = self.policy.next(sdk.history)
+ return observation
+
+
+class RunCommandStep(Step):
+ cmd: str
+ name: str = "Run command"
+ _description: str = None
+
+ async def describe(self, llm: LLM) -> Coroutine[str, None, None]:
+ if self._description is not None:
+ return self._description
+ return self.cmd
+
+ async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
+ cwd = await sdk.ide.getWorkspaceDirectory()
+ result = subprocess.run(
+ self.cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
+ stdout = result.stdout.decode("utf-8")
+ stderr = result.stderr.decode("utf-8")
+ print(stdout, stderr)
+
+ # If it fails, return the error
+ if result.returncode != 0:
+ return TextObservation(text=stderr)
+ else:
+ return TextObservation(text=stdout)
+
+
+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
+ _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": <FILEPATH>,
+ "replace_me": <CODE_TO_REPLACE>,
+ "replace_with": <CODE_TO_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.
+""")
+
+ 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))
+ enc_dec = MarkdownStyleEncoderDecoder(rif_with_contents)
+ code_string = enc_dec.encode()
+ prompt = self._prompt.format(
+ code=code_string, user_input=self.user_input)
+
+ rif_dict = {}
+ for rif in rif_with_contents:
+ rif_dict[rif.filepath] = rif.contents
+
+ completion = sdk.llm.complete(prompt)
+
+ # Temporarily doing this to generate description.
+ self._prompt = prompt
+ self._completion = completion
+
+ # ALTERNATIVE DECODING STEP HERE
+ file_edits = []
+ obj = json.loads(completion.strip())
+ for edit in obj["edits"]:
+ filepath = edit["filepath"]
+ 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))
+ # ------------------------------
+
+ 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 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, 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()]
+
+ await sdk.run_step(EditCodeStep(
+ range_in_files=range_in_files, prompt=self._prompt.format(code="{code}", user_input=self.user_input)))
+
+
+class FindCodeStep(Step):
+ prompt: str
+
+ async def describe(self, llm: LLM) -> Coroutine[str, None, None]:
+ return "Finding code"
+
+ async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
+ return await sdk.ide.getOpenFiles()
+
+
+class UserInputStep(Step):
+ user_input: str
+
+
+class SolveTracebackStep(Step):
+ traceback: Traceback
+
+ async def describe(self, llm: LLM) -> Coroutine[str, None, None]:
+ return f"```\n{self.traceback.full_traceback}\n```"
+
+ async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
+ prompt = dedent("""I ran into this problem with my Python code:
+
+ {traceback}
+
+ Below are the files that might need to be fixed:
+
+ {code}
+
+ This is what the code should be in order to avoid the problem:
+ """).format(traceback=self.traceback.full_traceback, code="{code}")
+
+ range_in_files = []
+ for frame in self.traceback.frames:
+ content = await sdk.ide.readFile(frame.filepath)
+ range_in_files.append(
+ RangeInFile.from_entire_file(frame.filepath, content))
+
+ await sdk.run_step(EditCodeStep(
+ range_in_files=range_in_files, prompt=prompt))
+ return None
diff --git a/continuedev/src/continuedev/libs/steps/migration.py b/continuedev/src/continuedev/libs/steps/migration.py
new file mode 100644
index 00000000..04296836
--- /dev/null
+++ b/continuedev/src/continuedev/libs/steps/migration.py
@@ -0,0 +1,26 @@
+# When an edit is made to an existing class or a new sqlalchemy class is created,
+# this should be kicked off.
+
+from ...models.filesystem import RangeInFile
+from .main import EditCodeStep, RunCommandStep
+from ..core import Step
+
+
+class MigrationStep(Step):
+ name: str = "Create and run an alembic migration."
+
+ edited_file: str
+
+ async def run(self, sdk):
+ 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")
+ 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)
+ await sdk.run_step(EditCodeStep(
+ range_in_files=[RangeInFile.from_entire_file(migration_file, contents)],
+ prompt=f"Here are the changes made to the sqlalchemy classes:\n\n{recent_edits_string}\n\nThis is the generated migration file:\n\n{{code}}\n\nReview the migration file to make sure it correctly reflects the changes made to the sqlalchemy classes.",
+ ))
+ await sdk.run_step(RunCommandStep(cmd="cd libs && poetry run alembic upgrade head"))
diff --git a/continuedev/src/continuedev/libs/steps/nate.py b/continuedev/src/continuedev/libs/steps/nate.py
new file mode 100644
index 00000000..80436fa4
--- /dev/null
+++ b/continuedev/src/continuedev/libs/steps/nate.py
@@ -0,0 +1,215 @@
+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
+import os
+
+
+class WritePytestsStep(Step):
+ for_filepath: Union[str, None] = None
+ instructions: str = "Write unit tests for this file."
+
+ async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
+ if self.for_filepath is None:
+ self.for_filepath = (await sdk.ide.getOpenFiles())[0]
+
+ filename = os.path.basename(self.for_filepath)
+ dirname = os.path.dirname(self.for_filepath)
+
+ path_dir = os.path.join(dirname, "tests")
+ if not os.path.exists(path_dir):
+ await sdk.apply_filesystem_edit(AddDirectory(path=path_dir))
+
+ path = os.path.join(path_dir, f"test_{filename}")
+ if os.path.exists(path):
+ return None
+
+ for_file_contents = await sdk.ide.readFile(self.for_filepath)
+
+ prompt = dedent(f"""This is the file you will write unit tests for:
+
+```python
+{for_file_contents}
+```
+
+Here are additional instructions:
+
+"{self.instructions}"
+
+Here is a complete set of pytest unit tests:
+
+ """)
+ # tests = sdk.llm.complete(prompt)
+ tests = '''
+import pytest
+
+from ..calculator import Calculator
+
+
+@pytest.fixture
+def calculator():
+ return Calculator()
+
+
+def test_add(calculator):
+ assert calculator.add(2, 3) == 5
+ assert calculator.add(10, -2) == 8
+ assert calculator.add(0, 0) == 0
+
+
+def test_sub(calculator):
+ assert calculator.sub(2, 3) == -1
+ assert calculator.sub(10, -2) == 12
+ assert calculator.sub(0, 0) == 0
+
+
+def test_mul(calculator):
+ assert calculator.mul(2, 3) == 6
+ assert calculator.mul(10, -2) == -20
+ assert calculator.mul(0, 0) == 0
+
+
+def test_div(calculator):
+ assert calculator.div(2, 3) == 0.6666666666666666
+ assert calculator.div(10, -2) == -5
+ assert calculator.div(0, 1) == 0
+
+
+def test_exp(calculator):
+ assert calculator.exp(2, 3) == 8
+ assert calculator.exp(10, -2) == 0.01
+ assert calculator.exp(0, 0) == 1
+'''
+ time.sleep(3.5)
+ await sdk.apply_filesystem_edit(AddFile(filepath=path, content=tests))
+
+ return None
+
+
+class CreatePyplot(Step):
+ # Wish there was a way to add import, specify dependency
+ name: str = "Create a pyplot"
+
+ async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
+ code = dedent("""import matplotlib.pyplot as plt
+import numpy as np
+
+{instructions}
+
+plt.xlabel("{x_label}")
+plt.ylabel("{y_label}")
+plt.title("{title}")
+plt.show()
+ """)
+
+
+class ImplementAbstractMethodStep(Step):
+ name: str = "Implement abstract method for all subclasses"
+ method_name: str = "def walk(self, path: str) -> List[str]"
+ class_name: str = "FileSystem"
+
+ async def run(self, sdk: ContinueSDK):
+ await sdk.run_step(WaitForUserConfirmationStep(prompt="Detected new abstract method. Implement in all subclasses?"))
+ implementations = []
+ for filepath in ["/Users/natesesti/Desktop/continue/extension/examples/python/filesystem/real.py", "/Users/natesesti/Desktop/continue/extension/examples/python/filesystem/virtual.py"]:
+ contents = await sdk.ide.readFile(filepath)
+ implementations.append(
+ RangeInFile.from_entire_file(filepath, contents))
+
+ for implementation in implementations:
+ await sdk.run_step(EditCodeStep(
+ range_in_files=[implementation],
+ prompt=f"{{code}}\nRewrite the class, implementing the method `{self.method_name}`.\n",
+ ))
+
+
+class CreateTableStep(Step):
+ sql_str: str
+ name: str = "Create a table"
+ hide = True
+
+ async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
+ # Write the TypeORM entity
+ entity_name = "Order"
+ orm_entity = '''import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+
+@Entity()
+export class Order {
+ @PrimaryGeneratedColumn()
+ order_id: number;
+
+ @Column()
+ customer_id: number;
+
+ @Column()
+ order_date: Date;
+
+ @Column()
+ order_total: number;
+
+ @Column()
+ shipping_address: string;
+
+ @Column()
+ billing_address: string;
+
+ @Column()
+ payment_method: string;
+
+ @Column()
+ order_status: string;
+
+ @Column()
+ tracking_number: string;
+}'''
+ time.sleep(2)
+ # orm_entity = sdk.llm.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.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)
+
+ # Add entity to data-source.ts
+ await sdk.run_step(EditFileStep(
+ filepath=f"/Users/natesesti/Desktop/continue/extension/examples/python/MyProject/src/data-source.ts",
+ prompt=f"{{code}}\nAdd the {entity_name} entity:\n",
+ ))
+
+ # Generate blank migration for the entity
+ obs: TextObservation = await sdk.run_step(RunCommandStep(
+ cmd=f"npx typeorm migration:create ./src/migration/Create{entity_name}Table"
+ ))
+ migration_filepath = obs.text.split(" ")[1]
+
+ # Wait for user input
+ await sdk.run_step(WaitForUserConfirmationStep(prompt="Fill in the migration?"))
+
+ # Fill in the migration
+ await sdk.run_step(EditFileStep(
+ filepath=migration_filepath,
+ prompt=f"{{code}}\nThis is the table that was created:\n{self.sql_str}\n\nFill in the migration for the table:\n",
+ ))
+
+ # Run the migration
+ command_step = RunCommandStep(
+ cmd=f"""sqlite3 database.sqlite 'CREATE TABLE orders (
+ order_id SERIAL PRIMARY KEY,
+ customer_id INTEGER,
+ order_date DATE,
+ order_total NUMERIC,
+ shipping_address TEXT,
+ billing_address TEXT,
+ payment_method TEXT,
+ order_status TEXT,
+ tracking_number TEXT
+);'"""
+ )
+ command_step._description = "npx typeorm-ts-node-commonjs migration:run -d ./src/data-source.ts"
+ await sdk.run_step(command_step)
diff --git a/continuedev/src/continuedev/libs/steps/pytest.py b/continuedev/src/continuedev/libs/steps/pytest.py
new file mode 100644
index 00000000..e53eb465
--- /dev/null
+++ b/continuedev/src/continuedev/libs/steps/pytest.py
@@ -0,0 +1,37 @@
+from textwrap import dedent
+from ...models.filesystem_edit import AddDirectory, AddFile
+from ..core import Step, ContinueSDK
+import os
+
+
+class WritePytestsStep(Step):
+ for_filepath: str
+
+ async def run(self, sdk: ContinueSDK):
+ filename, dirname = os.path.split(self.for_filepath)
+
+ path_dir = os.path.join(dirname, "tests")
+ if not os.path.exists(path_dir):
+ await sdk.apply_filesystem_edit(AddDirectory(path=path_dir))
+
+ path = os.path.join(path_dir, f"test_{filename}")
+ if os.path.exists(path):
+ return
+
+ for_file_contents = await sdk.ide.readFile(self.for_filepath)
+
+ prompt = dedent(f"""\
+ This is the file you will write unit tests for:
+
+ ```python
+ {for_file_contents}
+ ```
+
+ Here are additional instructions:
+
+ "{self.instructions}"
+
+ Here is a complete set of pytest unit tests:
+ """)
+ tests = sdk.llm.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
new file mode 100644
index 00000000..1eb6271d
--- /dev/null
+++ b/continuedev/src/continuedev/libs/steps/ty.py
@@ -0,0 +1,153 @@
+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
+
+source_name = "weather_api"
+
+
+class SetupPipelineStep(Step):
+
+ name = "Setup Pipeline"
+
+ 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(
+ # 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'
+
+ # running commands to get started when creating a new dlt pipeline
+ process = subprocess.Popen(
+ '/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ out, err = process.communicate(f'''
+ cd /Users/natesesti/Desktop/continue/extension/examples/python && python3 -m venv env && source env/bin/activate && pip install dlt && dlt init {source_name} duckdb
+Y
+pip install -r requirements.txt && pip install dlt[duckdb]'''.encode())
+ process = subprocess.Popen(
+ '/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ out, err = process.communicate(
+ f'''cd /Users/natesesti/Desktop/continue/extension/examples/python && source env/bin/activate && pip install -r requirements.txt'''.encode())
+ # await sdk.run_step(
+ # RunCommandStep(cmd="cd /Users/natesesti/Desktop/continue/extension/examples/python") >>
+ # RunCommandStep(cmd=f'python3 -m venv env') >>
+ # RunCommandStep(cmd=f'source env/bin/activate') >>
+ # RunCommandStep(cmd=f'pip install dlt') >>
+ # RunCommandStep(cmd=f'dlt init {source_name} duckdb') >>
+ # RunCommandStep(cmd=f'pip install -r requirements')
+ # )
+
+ # editing the resource function to call the requested API
+ await sdk.ide.setFileOpen(filename)
+ contents = await sdk.ide.readFile(filename)
+ await sdk.run_step(EditCodeStep(
+ range_in_files=[RangeInFile.from_entire_file(filename, contents)],
+ prompt=f'{{code}}\n\nRewrite the entire file, editing the resource function to call the API described by this: {self.api_description}'
+ ))
+
+ # wait for user to put API key in secrets.toml
+ await sdk.ide.setFileOpen("/Users/natesesti/Desktop/continue/extension/examples/python/.dlt/secrets.toml")
+ await sdk.run_step(WaitForUserConfirmationStep(prompt=f"Please add the API key to the `secrets.toml` file and then press `Continue`"))
+ return DictObservation(values={"source_name": source_name})
+
+
+class ValidatePipelineStep(Step):
+
+ name = "Validate Pipeline"
+
+ async def run(self, sdk: ContinueSDK):
+ # source_name = sdk.history.last_observation()["source_name"]
+ filename = f'/Users/natesesti/Desktop/continue/extension/examples/python/{source_name}.py'
+
+ # test that the API call works
+ await sdk.run_step(RunCommandStep(cmd=f'env/bin/python3 weather_api.py'))
+ # TODO: validate that the response code is 200 (i.e. successful) else loop
+
+ # remove exit() from the main main function
+ await sdk.ide.setFileOpen(filename)
+ contents = await sdk.ide.readFile(filename)
+ new_contents = contents.replace('exit()', '')
+ await sdk.apply_filesystem_edit(FileEdit(filepath=filename, range=Range.from_entire_file(contents), replacement=new_contents))
+ await sdk.ide.saveFile(filename)
+ # await sdk.run_step(EditCodeStep(
+ # range_in_files=[RangeInFile.from_entire_file(filename)],
+ # prompt=f'Remove exit() from the main function'
+ # ))
+
+ # test that dlt loads the data into the DuckDB instance
+ await sdk.run_step(RunCommandStep(cmd=f'env/bin/python3 weather_api.py'))
+ # TODO: validate that `dlt` outputted success via print(load_info) else loop
+
+ # write Python code in `query.py` that queries the DuckDB instance to validate it worked
+ query_filename = '/Users/natesesti/Desktop/continue/extension/examples/python/query.py'
+
+ names_query_code = '''
+ import duckdb
+
+ # Connect to the DuckDB instance
+ con = duckdb.connect('weather.duckdb')
+
+ # Query the schema_name.table_name
+ result = conn.execute("SELECT table_schema || '.' || table_name FROM information_schema.tables WHERE table_schema NOT IN ('information_schema', 'pg_catalog')").fetchall()
+
+ # Print the schema_name.table_name(s) to stdout
+ for r in result:
+ print(r[0])
+ '''
+ # await sdk.apply_filesystem_edit(FileEdit.from_insertion(
+ # filepath=query_filename,
+ # position=Position(line=0, character=0),
+ # content=names_query_code
+ # ))
+ # await sdk.run_step(RunCommandStep(cmd=f'env/bin/python3 query.py'))
+ # TODO: replace with code that grabs all non-dlt `schema_name.table_name`s outputted by previous query
+ table_name = "weather_api.weather_api_resource"
+ tables_query_code = f'''
+import duckdb
+
+# connect to DuckDB instance
+conn = duckdb.connect(database="weather.duckdb")
+
+# get table names
+rows = conn.execute("SELECT * FROM {table_name};").fetchall()
+
+# print table names
+for row in rows:
+ print(row)
+ '''
+ await sdk.apply_filesystem_edit(AddFile(filepath=query_filename, content=tables_query_code))
+ await sdk.ide.setFileOpen(query_filename)
+ # await sdk.apply_filesystem_edit(FileEdit(filepath=query_filename, replacement=tables_query_code,
+ # range=Range.from_entire_file(content=names_query_code)))
+ await sdk.run_step(RunCommandStep(cmd=f'env/bin/python3 query.py'))
+
+
+class CreatePipelinePolicy(Policy):
+
+ _current_state: str = "init"
+
+ def next(self, history: History = History.from_empty()) -> "Step":
+ if self._current_state == "init":
+ self._current_state = "setup"
+ return WaitForUserInputStep(prompt="What API do you want to load data from?")
+ elif self._current_state == "setup":
+ self._current_state = "validate"
+ return SetupPipelineStep()
+ elif self._current_state == "validate":
+ self._current_state = "done"
+ return ValidatePipelineStep()
+ else:
+ return None
+
+
+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(api_description="Load data from the WeatherAPI.com API") >>
+ ValidatePipelineStep()
+ )