diff options
Diffstat (limited to 'continuedev/src/continuedev/libs/steps')
-rw-r--r-- | continuedev/src/continuedev/libs/steps/__init__.py | 1 | ||||
-rw-r--r-- | continuedev/src/continuedev/libs/steps/chroma.py | 62 | ||||
-rw-r--r-- | continuedev/src/continuedev/libs/steps/draft/abstract_method.py | 18 | ||||
-rw-r--r-- | continuedev/src/continuedev/libs/steps/draft/dlt.py | 81 | ||||
-rw-r--r-- | continuedev/src/continuedev/libs/steps/draft/redux.py | 48 | ||||
-rw-r--r-- | continuedev/src/continuedev/libs/steps/draft/typeorm.py | 42 | ||||
-rw-r--r-- | continuedev/src/continuedev/libs/steps/main.py | 345 | ||||
-rw-r--r-- | continuedev/src/continuedev/libs/steps/migration.py | 26 | ||||
-rw-r--r-- | continuedev/src/continuedev/libs/steps/nate.py | 215 | ||||
-rw-r--r-- | continuedev/src/continuedev/libs/steps/pytest.py | 37 | ||||
-rw-r--r-- | continuedev/src/continuedev/libs/steps/ty.py | 153 |
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() + ) |