From f53768612b1e2268697b5444e502032ef9f3fb3c Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 23 May 2023 23:45:12 -0400 Subject: copying from old repo --- continuedev/src/__init__.py | 0 continuedev/src/continuedev/__init__.py | 0 continuedev/src/continuedev/libs/__init__.py | 0 continuedev/src/continuedev/libs/chroma/.gitignore | 1 + continuedev/src/continuedev/libs/chroma/query.py | 78 ++++ continuedev/src/continuedev/libs/chroma/replace.py | 19 + continuedev/src/continuedev/libs/chroma/update.py | 213 +++++++++++ continuedev/src/continuedev/libs/core.py | 423 +++++++++++++++++++++ continuedev/src/continuedev/libs/llm/__init__.py | 22 ++ .../src/continuedev/libs/llm/hugging_face.py | 14 + continuedev/src/continuedev/libs/llm/openai.py | 159 ++++++++ .../src/continuedev/libs/llm/prompt_utils.py | 71 ++++ continuedev/src/continuedev/libs/llm/prompters.py | 112 ++++++ continuedev/src/continuedev/libs/llm/utils.py | 34 ++ continuedev/src/continuedev/libs/observation.py | 35 ++ continuedev/src/continuedev/libs/policy.py | 91 +++++ continuedev/src/continuedev/libs/steps/__init__.py | 1 + continuedev/src/continuedev/libs/steps/chroma.py | 62 +++ .../libs/steps/draft/abstract_method.py | 18 + .../src/continuedev/libs/steps/draft/dlt.py | 81 ++++ .../src/continuedev/libs/steps/draft/redux.py | 48 +++ .../src/continuedev/libs/steps/draft/typeorm.py | 42 ++ continuedev/src/continuedev/libs/steps/main.py | 345 +++++++++++++++++ .../src/continuedev/libs/steps/migration.py | 26 ++ continuedev/src/continuedev/libs/steps/nate.py | 215 +++++++++++ continuedev/src/continuedev/libs/steps/pytest.py | 37 ++ continuedev/src/continuedev/libs/steps/ty.py | 153 ++++++++ .../src/continuedev/libs/util/copy_codebase.py | 127 +++++++ continuedev/src/continuedev/libs/util/map_path.py | 16 + continuedev/src/continuedev/libs/util/queue.py | 17 + .../src/continuedev/libs/util/traceback_parsers.py | 24 ++ continuedev/src/continuedev/models/__init__.py | 0 continuedev/src/continuedev/models/filesystem.py | 328 ++++++++++++++++ .../src/continuedev/models/filesystem_edit.py | 136 +++++++ .../src/continuedev/models/generate_json_schema.py | 39 ++ continuedev/src/continuedev/models/main.py | 131 +++++++ continuedev/src/continuedev/plugins/__init__.py | 26 ++ continuedev/src/continuedev/plugins/load.py | 21 + .../src/continuedev/plugins/policy/__init__.py | 4 + .../src/continuedev/plugins/policy/hookspecs.py | 10 + .../continuedev/plugins/policy/libs/__init__.py | 0 .../continuedev/plugins/policy/libs/alternate.py | 22 ++ .../src/continuedev/plugins/step/__init__.py | 4 + .../src/continuedev/plugins/step/hookspecs.py | 13 + .../src/continuedev/plugins/step/libs/__init__.py | 0 .../continuedev/plugins/step/libs/hello_world.py | 9 + continuedev/src/continuedev/server/ide.py | 302 +++++++++++++++ continuedev/src/continuedev/server/ide_protocol.py | 80 ++++ continuedev/src/continuedev/server/main.py | 39 ++ continuedev/src/continuedev/server/notebook.py | 198 ++++++++++ 50 files changed, 3846 insertions(+) create mode 100644 continuedev/src/__init__.py create mode 100644 continuedev/src/continuedev/__init__.py create mode 100644 continuedev/src/continuedev/libs/__init__.py create mode 100644 continuedev/src/continuedev/libs/chroma/.gitignore create mode 100644 continuedev/src/continuedev/libs/chroma/query.py create mode 100644 continuedev/src/continuedev/libs/chroma/replace.py create mode 100644 continuedev/src/continuedev/libs/chroma/update.py create mode 100644 continuedev/src/continuedev/libs/core.py create mode 100644 continuedev/src/continuedev/libs/llm/__init__.py create mode 100644 continuedev/src/continuedev/libs/llm/hugging_face.py create mode 100644 continuedev/src/continuedev/libs/llm/openai.py create mode 100644 continuedev/src/continuedev/libs/llm/prompt_utils.py create mode 100644 continuedev/src/continuedev/libs/llm/prompters.py create mode 100644 continuedev/src/continuedev/libs/llm/utils.py create mode 100644 continuedev/src/continuedev/libs/observation.py create mode 100644 continuedev/src/continuedev/libs/policy.py create mode 100644 continuedev/src/continuedev/libs/steps/__init__.py create mode 100644 continuedev/src/continuedev/libs/steps/chroma.py create mode 100644 continuedev/src/continuedev/libs/steps/draft/abstract_method.py create mode 100644 continuedev/src/continuedev/libs/steps/draft/dlt.py create mode 100644 continuedev/src/continuedev/libs/steps/draft/redux.py create mode 100644 continuedev/src/continuedev/libs/steps/draft/typeorm.py create mode 100644 continuedev/src/continuedev/libs/steps/main.py create mode 100644 continuedev/src/continuedev/libs/steps/migration.py create mode 100644 continuedev/src/continuedev/libs/steps/nate.py create mode 100644 continuedev/src/continuedev/libs/steps/pytest.py create mode 100644 continuedev/src/continuedev/libs/steps/ty.py create mode 100644 continuedev/src/continuedev/libs/util/copy_codebase.py create mode 100644 continuedev/src/continuedev/libs/util/map_path.py create mode 100644 continuedev/src/continuedev/libs/util/queue.py create mode 100644 continuedev/src/continuedev/libs/util/traceback_parsers.py create mode 100644 continuedev/src/continuedev/models/__init__.py create mode 100644 continuedev/src/continuedev/models/filesystem.py create mode 100644 continuedev/src/continuedev/models/filesystem_edit.py create mode 100644 continuedev/src/continuedev/models/generate_json_schema.py create mode 100644 continuedev/src/continuedev/models/main.py create mode 100644 continuedev/src/continuedev/plugins/__init__.py create mode 100644 continuedev/src/continuedev/plugins/load.py create mode 100644 continuedev/src/continuedev/plugins/policy/__init__.py create mode 100644 continuedev/src/continuedev/plugins/policy/hookspecs.py create mode 100644 continuedev/src/continuedev/plugins/policy/libs/__init__.py create mode 100644 continuedev/src/continuedev/plugins/policy/libs/alternate.py create mode 100644 continuedev/src/continuedev/plugins/step/__init__.py create mode 100644 continuedev/src/continuedev/plugins/step/hookspecs.py create mode 100644 continuedev/src/continuedev/plugins/step/libs/__init__.py create mode 100644 continuedev/src/continuedev/plugins/step/libs/hello_world.py create mode 100644 continuedev/src/continuedev/server/ide.py create mode 100644 continuedev/src/continuedev/server/ide_protocol.py create mode 100644 continuedev/src/continuedev/server/main.py create mode 100644 continuedev/src/continuedev/server/notebook.py (limited to 'continuedev/src') diff --git a/continuedev/src/__init__.py b/continuedev/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/continuedev/src/continuedev/__init__.py b/continuedev/src/continuedev/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/continuedev/src/continuedev/libs/__init__.py b/continuedev/src/continuedev/libs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/continuedev/src/continuedev/libs/chroma/.gitignore b/continuedev/src/continuedev/libs/chroma/.gitignore new file mode 100644 index 00000000..6320cd24 --- /dev/null +++ b/continuedev/src/continuedev/libs/chroma/.gitignore @@ -0,0 +1 @@ +data \ No newline at end of file diff --git a/continuedev/src/continuedev/libs/chroma/query.py b/continuedev/src/continuedev/libs/chroma/query.py new file mode 100644 index 00000000..5a1c89b3 --- /dev/null +++ b/continuedev/src/continuedev/libs/chroma/query.py @@ -0,0 +1,78 @@ +import subprocess +import sys +from llama_index import GPTVectorStoreIndex, StorageContext, load_index_from_storage +import os +from typer import Typer +from enum import Enum +from .update import update_codebase_index, create_codebase_index, index_dir_for, get_current_branch +from .replace import replace_additional_index + +app = Typer() + + +def query_codebase_index(query: str) -> str: + """Query the codebase index.""" + branch = subprocess.check_output( + ["git", "rev-parse", "--abbrev-ref", "HEAD"]).decode("utf-8").strip() + path = index_dir_for(branch) + if not os.path.exists(path): + print("No index found for the codebase at ", path) + return "" + + storage_context = StorageContext.from_defaults( + persist_dir=index_dir_for(branch)) + index = load_index_from_storage(storage_context) + # index = GPTVectorStoreIndex.load_from_disk(path) + engine = index.as_query_engine() + return engine.query(query) + + +def query_additional_index(query: str) -> str: + """Query the additional index.""" + index = GPTVectorStoreIndex.load_from_disk('data/additional_index.json') + return index.query(query) + + +class IndexTypeOption(str, Enum): + codebase = "codebase" + additional = "additional" + + +@app.command() +def query(context: IndexTypeOption, query: str): + if context == IndexTypeOption.additional: + response = query_additional_index(query) + elif context == IndexTypeOption.codebase: + response = query_codebase_index(query) + else: + print("Error: unknown context") + print({"response": response}) + + +@app.command() +def check_index_exists(root_path: str): + branch = get_current_branch() + exists = os.path.exists(index_dir_for(branch)) + print({"exists": exists}) + + +@app.command() +def update(): + update_codebase_index() + print("Updated codebase index") + + +@app.command("create") +def create_index(): + create_codebase_index() + print("Created file index") + + +@app.command() +def replace_additional_index(info: str): + replace_additional_index() + print("Replaced additional index") + + +if __name__ == '__main__': + app() diff --git a/continuedev/src/continuedev/libs/chroma/replace.py b/continuedev/src/continuedev/libs/chroma/replace.py new file mode 100644 index 00000000..1868b152 --- /dev/null +++ b/continuedev/src/continuedev/libs/chroma/replace.py @@ -0,0 +1,19 @@ +import sys +from llama_index import GPTVectorStoreIndex, Document + + +def replace_additional_index(info: str): + """Replace the additional index.""" + with open('data/additional_context.txt', 'w') as f: + f.write(info) + documents = [Document(info)] + index = GPTVectorStoreIndex(documents) + index.save_to_disk('data/additional_index.json') + print("Additional index replaced") + + +if __name__ == "__main__": + """python3 replace.py """ + info = sys.argv[1] if len(sys.argv) > 1 else None + if info: + replace_additional_index(info) diff --git a/continuedev/src/continuedev/libs/chroma/update.py b/continuedev/src/continuedev/libs/chroma/update.py new file mode 100644 index 00000000..3b9eb743 --- /dev/null +++ b/continuedev/src/continuedev/libs/chroma/update.py @@ -0,0 +1,213 @@ +# import faiss +import json +import os +import subprocess + +from llama_index.langchain_helpers.text_splitter import TokenTextSplitter +from llama_index import GPTVectorStoreIndex, SimpleDirectoryReader, Document +from typing import List, Generator, Tuple +from dotenv import load_dotenv + +load_dotenv() + +FILE_TYPES_TO_IGNORE = [ + '.pyc', + '.png', + '.jpg', + '.jpeg', + '.gif', + '.svg', + '.ico' +] + + +def further_filter(files: List[str], root_dir: str): + """Further filter files before indexing.""" + for file in files: + if file.endswith(tuple(FILE_TYPES_TO_IGNORE)) or file.startswith('.git') or file.startswith('archive'): + continue + yield root_dir + "/" + file + + +def get_git_root_dir(path: str): + """Get the root directory of a Git repository.""" + try: + return subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], cwd=path).strip().decode() + except subprocess.CalledProcessError: + return None + + +def get_git_ignored_files(root_dir: str): + """Get the list of ignored files in a Git repository.""" + try: + output = subprocess.check_output( + ['git', 'ls-files', '--ignored', '--others', '--exclude-standard'], cwd=root_dir).strip().decode() + return output.split('\n') + except subprocess.CalledProcessError: + return [] + + +def get_all_files(root_dir: str): + """Get a list of all files in a directory.""" + for dir_path, _, file_names in os.walk(root_dir): + for file_name in file_names: + yield os.path.join(os.path.relpath(dir_path, root_dir), file_name) + + +def get_input_files(root_dir: str): + """Get a list of all files in a Git repository that are not ignored.""" + ignored_files = set(get_git_ignored_files(root_dir)) + all_files = set(get_all_files(root_dir)) + nonignored_files = all_files - ignored_files + return further_filter(nonignored_files, root_dir) + + +def load_gpt_index_documents(root: str) -> List[Document]: + """Loads a list of GPTIndex Documents, respecting .gitignore files.""" + # Get input files + input_files = get_input_files(root) + # Use SimpleDirectoryReader to load the files into Documents + return SimpleDirectoryReader(root, input_files=input_files, file_metadata=lambda filename: {"filename": filename}).load_data() + + +def index_dir_for(branch: str) -> str: + return f"/Users/natesesti/Desktop/continue/continuedev/src/continuedev/libs/data/{branch}" + + +def get_git_root_dir(): + return "/Users/natesesti/Desktop/continue/extension/examples/python" + result = subprocess.run(['git', 'rev-parse', '--show-toplevel'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return result.stdout.decode().strip() + + +def get_current_branch() -> str: + return subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).decode("utf-8").strip() + + +def get_current_commit() -> str: + return subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("utf-8").strip() + + +def create_codebase_index(): + """Create a new index for the current branch.""" + branch = get_current_branch() + if not os.path.exists(index_dir_for(branch)): + os.makedirs(index_dir_for(branch)) + + print("ROOT DIRECTORY: ", get_git_root_dir()) + documents = load_gpt_index_documents(get_git_root_dir()) + + chunks = {} + doc_chunks = [] + for doc in documents: + text_splitter = TokenTextSplitter() + text_chunks = text_splitter.split_text(doc.text) + filename = doc.extra_info["filename"] + chunks[filename] = len(text_chunks) + for i, text in enumerate(text_chunks): + doc_chunks.append(Document(text, doc_id=f"{filename}::{i}")) + + with open(f"{index_dir_for(branch)}/metadata.json", "w") as f: + json.dump({"commit": get_current_commit(), + "chunks": chunks}, f, indent=4) + + index = GPTVectorStoreIndex([]) + + for chunk in doc_chunks: + index.insert(chunk) + + # d = 1536 # Dimension of text-ada-embedding-002 + # faiss_index = faiss.IndexFlatL2(d) + # index = GPTFaissIndex(documents, faiss_index=faiss_index) + # index.save_to_disk(f"{index_dir_for(branch)}/index.json", faiss_index_save_path=f"{index_dir_for(branch)}/index_faiss_core.index") + + index.storage_context.persist( + persist_dir=index_dir_for(branch)) + + print("Codebase index created") + + +def get_modified_deleted_files() -> Tuple[List[str], List[str]]: + """Get a list of all files that have been modified since the last commit.""" + branch = get_current_branch() + current_commit = get_current_commit() + + metadata = f"{index_dir_for(branch)}/metadata.json" + with open(metadata, "r") as f: + previous_commit = json.load(f)["commit"] + + modified_deleted_files = subprocess.check_output( + ["git", "diff", "--name-only", previous_commit, current_commit]).decode("utf-8").strip() + modified_deleted_files = modified_deleted_files.split("\n") + modified_deleted_files = [f for f in modified_deleted_files if f] + + root = get_git_root_dir() + deleted_files = [ + f for f in modified_deleted_files if not os.path.exists(root + "/" + f)] + modified_files = [ + f for f in modified_deleted_files if os.path.exists(root + "/" + f)] + + return further_filter(modified_files, index_dir_for(branch)), further_filter(deleted_files, index_dir_for(branch)) + + +def update_codebase_index(): + """Update the index with a list of files.""" + branch = get_current_branch() + + if not os.path.exists(index_dir_for(branch)): + create_codebase_index() + else: + # index = GPTFaissIndex.load_from_disk(f"{index_dir_for(branch)}/index.json", faiss_index_save_path=f"{index_dir_for(branch)}/index_faiss_core.index") + index = GPTVectorStoreIndex.load_from_disk( + f"{index_dir_for(branch)}/index.json") + modified_files, deleted_files = get_modified_deleted_files() + + with open(f"{index_dir_for(branch)}/metadata.json", "r") as f: + metadata = json.load(f) + + for file in deleted_files: + + num_chunks = metadata["chunks"][file] + for i in range(num_chunks): + index.delete(f"{file}::{i}") + + del metadata["chunks"][file] + + print(f"Deleted {file}") + + for file in modified_files: + + if file in metadata["chunks"]: + + num_chunks = metadata["chunks"][file] + + for i in range(num_chunks): + index.delete(f"{file}::{i}") + + print(f"Deleted old version of {file}") + + with open(file, "r") as f: + text = f.read() + + text_splitter = TokenTextSplitter() + text_chunks = text_splitter.split_text(text) + + for i, text in enumerate(text_chunks): + index.insert(Document(text, doc_id=f"{file}::{i}")) + + metadata["chunks"][file] = len(text_chunks) + + print(f"Inserted new version of {file}") + + metadata["commit"] = get_current_commit() + + with open(f"{index_dir_for(branch)}/metadata.json", "w") as f: + json.dump(metadata, f, indent=4) + + print("Codebase index updated") + + +if __name__ == "__main__": + """python3 update.py""" + update_codebase_index() diff --git a/continuedev/src/continuedev/libs/core.py b/continuedev/src/continuedev/libs/core.py new file mode 100644 index 00000000..6a8a83ba --- /dev/null +++ b/continuedev/src/continuedev/libs/core.py @@ -0,0 +1,423 @@ +import traceback +import time +from typing import Callable, Coroutine, Dict, Generator, List, Tuple, Union +from ..models.filesystem_edit import EditDiff, FileEdit, FileEditWithFullContents, FileSystemEdit +from ..models.filesystem import FileSystem +from pydantic import BaseModel, parse_file_as, validator +from .llm import LLM +from .observation import Observation, UserInputObservation +from ..server.ide_protocol import AbstractIdeProtocolServer +from .util.queue import AsyncSubscriptionQueue + + +class ContinueBaseModel(BaseModel): + class Config: + underscore_attrs_are_private = True + + +class HistoryNode(ContinueBaseModel): + """A point in history, a list of which make up History""" + step: "Step" + observation: Union[Observation, None] + depth: int + + +class History(ContinueBaseModel): + """A history of steps taken and their results""" + timeline: List[HistoryNode] + current_index: int + + def add_node(self, node: HistoryNode): + self.timeline.insert(self.current_index + 1, node) + self.current_index += 1 + + def get_current(self) -> Union[HistoryNode, None]: + if self.current_index < 0: + return None + return self.timeline[self.current_index] + + def remove_current_and_substeps(self): + self.timeline.pop(self.current_index) + while self.get_current() is not None and self.get_current().depth > 0: + self.timeline.pop(self.current_index) + + def take_next_step(self) -> Union["Step", None]: + if self.has_future(): + self.current_index += 1 + current_state = self.get_current() + if current_state is None: + return None + return current_state.step + return None + + def get_current_index(self) -> int: + return self.current_index + + def has_future(self) -> bool: + return self.current_index < len(self.timeline) - 1 + + def step_back(self): + self.current_index -= 1 + + def last_observation(self) -> Union[Observation, None]: + state = self.get_current() + if state is None: + return None + return state.observation + + @classmethod + def from_empty(cls): + return cls(timeline=[], current_index=-1) + + +class FullState(ContinueBaseModel): + """A full state of the program, including the history""" + history: History + active: bool + user_input_queue: List[str] + + +class Policy(ContinueBaseModel): + """A rule that determines which step to take next""" + + # Note that history is mutable, kinda sus + def next(self, history: History = History.from_empty()) -> "Step": + raise NotImplementedError + + +class ContinueSDK: + """The SDK provided as parameters to a step""" + llm: LLM + ide: AbstractIdeProtocolServer + __agent: "Agent" + + def __init__(self, agent: "Agent", llm: Union[LLM, None] = None): + if llm is None: + self.llm = agent.llm + else: + self.llm = llm + self.ide = agent.ide + self.__agent = agent + + @property + def history(self) -> History: + return self.__agent.history + + async def run_step(self, step: "Step") -> Coroutine[Observation, None, None]: + return await self.__agent._run_singular_step(step) + + async def apply_filesystem_edit(self, edit: FileSystemEdit): + await self.run_step(FileSystemEditStep(edit=edit)) + + async def wait_for_user_input(self) -> str: + return await self.__agent.wait_for_user_input() + + +class Agent(ContinueBaseModel): + llm: LLM + policy: Policy + ide: AbstractIdeProtocolServer + history: History = History.from_empty() + _on_update_callbacks: List[Callable[["FullState"], None]] = [] + + _active: bool = False + _should_halt: bool = False + _main_user_input_queue: List[str] = [] + + _user_input_queue = AsyncSubscriptionQueue() + + class Config: + arbitrary_types_allowed = True + + def get_full_state(self) -> FullState: + return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) + + def on_update(self, callback: Callable[["FullState"], None]): + """Subscribe to changes to state""" + self._on_update_callbacks.append(callback) + + def update_subscribers(self): + full_state = self.get_full_state() + for callback in self._on_update_callbacks: + callback(full_state) + + def __get_step_params(self, step: "Step"): + return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message)) + + def give_user_input(self, input: str, index: int): + self._user_input_queue.post(index, input) + + async def wait_for_user_input(self) -> str: + self._active = False + self.update_subscribers() + await self._user_input_queue.get(self.history.current_index) + self._active = True + self.update_subscribers() + + _manual_edits_buffer: List[FileEditWithFullContents] = [] + + async def reverse_to_index(self, index: int): + try: + while self.history.get_current_index() >= index: + current_step = self.history.get_current().step + self.history.step_back() + if issubclass(current_step.__class__, ReversibleStep): + await current_step.reverse(self.__get_step_params(current_step)) + + self.update_subscribers() + except Exception as e: + print(e) + + def handle_manual_edits(self, edits: List[FileEditWithFullContents]): + for edit in edits: + self._manual_edits_buffer.append(edit) + # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. + # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) + + def handle_traceback(self, traceback: str): + raise NotImplementedError + + _step_depth: int = 0 + + async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: + if not is_future_step: + # Check manual edits buffer, clear out if needed by creating a ManualEditStep + if len(self._manual_edits_buffer) > 0: + manualEditsStep = ManualEditStep.from_sequence( + self._manual_edits_buffer) + self._manual_edits_buffer = [] + await self._run_singular_step(manualEditsStep) + + # Update history - do this first so we get top-first tree ordering + self.history.add_node(HistoryNode( + step=step, observation=None, depth=self._step_depth)) + + # Run step + self._step_depth += 1 + observation = await step(self.__get_step_params(step)) + self._step_depth -= 1 + + # Add observation to history + self.history.get_current().observation = observation + + # Update its description + step._set_description(await step.describe(self.llm)) + + # Call all subscribed callbacks + self.update_subscribers() + + return observation + + async def run_from_step(self, step: "Step"): + # if self._active: + # raise RuntimeError("Agent is already running") + self._active = True + + next_step = step + is_future_step = False + while not (next_step is None or self._should_halt): + try: + if is_future_step: + # If future step, then we are replaying and need to delete the step from history so it can be replaced + self.history.remove_current_and_substeps() + + observation = await self._run_singular_step(next_step, is_future_step) + if next_step := self.policy.next(self.history): + is_future_step = False + elif next_step := self.history.take_next_step(): + is_future_step = True + else: + next_step = None + + except Exception as e: + print( + f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") + next_step = None + + self._active = False + + # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools + for callback in self._on_update_callbacks: + callback(None) + + async def run_from_observation(self, observation: Observation): + next_step = self.policy.next(self.history) + await self.run_from_step(next_step) + + async def run_policy(self): + first_step = self.policy.next(self.history) + await self.run_from_step(first_step) + + async def _request_halt(self): + if self._active: + self._should_halt = True + while self._active: + time.sleep(0.1) + self._should_halt = False + return None + + async def accept_user_input(self, user_input: str): + self._main_user_input_queue.append(user_input) + self.update_subscribers() + + if len(self._main_user_input_queue) > 1: + return + + # await self._request_halt() + # Just run the step that takes user input, and + # then up to the policy to decide how to deal with it. + self._main_user_input_queue.pop(0) + self.update_subscribers() + await self.run_from_step(UserInputStep(user_input=user_input)) + + while len(self._main_user_input_queue) > 0: + await self.run_from_step(UserInputStep( + user_input=self._main_user_input_queue.pop(0))) + + async def accept_refinement_input(self, user_input: str, index: int): + await self._request_halt() + await self.reverse_to_index(index) + await self.run_from_step(UserInputStep(user_input=user_input)) + + +class Step(ContinueBaseModel): + name: str = None + hide: bool = False + _description: Union[str, None] = None + + system_message: Union[str, None] = None + + class Config: + copy_on_model_validation = False + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + if self._description is not None: + return self._description + return "Running step: " + self.name + + def _set_description(self, description: str): + self._description = description + + def dict(self, *args, **kwargs): + d = super().dict(*args, **kwargs) + if self._description is not None: + d["description"] = self._description + else: + d["description"] = self.name + return d + + @validator("name", pre=True, always=True) + def name_is_class_name(cls, name): + if name is None: + return cls.__name__ + return name + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + raise NotImplementedError + + async def __call__(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + return await self.run(sdk) + + def __rshift__(self, other: "Step"): + steps = [] + if isinstance(self, SequentialStep): + steps = self.steps + else: + steps.append(self) + if isinstance(other, SequentialStep): + steps += other.steps + else: + steps.append(other) + return SequentialStep(steps=steps) + + +class SequentialStep(Step): + steps: list[Step] + hide: bool = True + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + for step in self.steps: + observation = await sdk.run_step(step) + return observation + + +class ReversibleStep(Step): + async def reverse(self, sdk: ContinueSDK): + raise NotImplementedError + + +class FileSystemEditStep(ReversibleStep): + edit: FileSystemEdit + _diff: Union[EditDiff, None] = None + + hide: bool = True + + async def run(self, sdk: "ContinueSDK") -> Coroutine[Observation, None, None]: + self._diff = await sdk.ide.applyFileSystemEdit(self.edit) + return None + + async def reverse(self, sdk: "ContinueSDK"): + await sdk.ide.applyFileSystemEdit(self._diff.backward) + # Where and when should file saves happen? + + +class ManualEditStep(ReversibleStep): + edit_diff: EditDiff + hide: bool = True + + hide: bool = True + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return "Manual edit step" + # TODO - only handling FileEdit here, but need all other types of FileSystemEdits + # Also requires the merge_file_edit function + # return llm.complete(dedent(f"""This code was replaced: + + # {self.edit_diff.backward.replacement} + + # With this code: + + # {self.edit_diff.forward.replacement} + + # Maximally concise summary of changes in bullet points (can use markdown): + # """)) + + @classmethod + def from_sequence(cls, edits: List[FileEditWithFullContents]) -> "ManualEditStep": + diffs = [] + for edit in edits: + _, diff = FileSystem.apply_edit_to_str( + edit.fileContents, edit.fileEdit) + diffs.append(diff) + return cls(edit_diff=EditDiff.from_sequence(diffs)) + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + return None + + async def reverse(self, sdk: ContinueSDK): + await sdk.ide.applyFileSystemEdit(self.edit_diff.backward) + + +class UserInputStep(Step): + user_input: str + name: str = "User Input" + hide: bool = True + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return self.user_input + + async def run(self, sdk: ContinueSDK) -> Coroutine[UserInputObservation, None, None]: + return UserInputObservation(user_input=self.user_input) + + +class ValidatorObservation(Observation): + passed: bool + observation: Observation + + +class Validator(Step): + def run(self, sdk: ContinueSDK) -> ValidatorObservation: + raise NotImplementedError + + +HistoryNode.update_forward_refs() diff --git a/continuedev/src/continuedev/libs/llm/__init__.py b/continuedev/src/continuedev/libs/llm/__init__.py new file mode 100644 index 00000000..6bae2222 --- /dev/null +++ b/continuedev/src/continuedev/libs/llm/__init__.py @@ -0,0 +1,22 @@ +from typing import Union +from ...models.main import AbstractModel +from pydantic import BaseModel + + +class LLM(BaseModel): + system_message: Union[str, None] = None + + def complete(self, prompt: str, **kwargs): + """Return the completion of the text with the given temperature.""" + raise + + def __call__(self, prompt: str, **kwargs): + return self.complete(prompt, **kwargs) + + def fine_tune(self, pairs: list): + """Fine tune the model on the given prompt/completion pairs.""" + raise NotImplementedError + + def with_system_message(self, system_message: Union[str, None]): + """Return a new model with the given system message.""" + raise NotImplementedError diff --git a/continuedev/src/continuedev/libs/llm/hugging_face.py b/continuedev/src/continuedev/libs/llm/hugging_face.py new file mode 100644 index 00000000..868cb560 --- /dev/null +++ b/continuedev/src/continuedev/libs/llm/hugging_face.py @@ -0,0 +1,14 @@ +from .llm import LLM +from transformers import AutoTokenizer, AutoModelForCausalLM + +class HuggingFace(LLM): + def __init__(self, model_path: str = "Salesforce/codegen-2B-mono"): + self.model_path = model_path + self.tokenizer = AutoTokenizer.from_pretrained(model_path) + self.model = AutoModelForCausalLM.from_pretrained(model_path) + + def complete(self, prompt: str, **kwargs): + args = { "max_tokens": 100 } | kwargs + input_ids = self.tokenizer(prompt, return_tensors="pt").input_ids + generated_ids = self.model.generate(input_ids, max_length=args["max_tokens"]) + return self.tokenizer.decode(generated_ids[0], skip_special_tokens=True) \ No newline at end of file diff --git a/continuedev/src/continuedev/libs/llm/openai.py b/continuedev/src/continuedev/libs/llm/openai.py new file mode 100644 index 00000000..bb745e75 --- /dev/null +++ b/continuedev/src/continuedev/libs/llm/openai.py @@ -0,0 +1,159 @@ +import asyncio +import time +from typing import Any, Dict, Generator, List, Union +import openai +import aiohttp +from ..llm import LLM +from pydantic import BaseModel, validator + + +class OpenAI(LLM): + api_key: str + completion_count: int = 0 + default_model: str = "text-davinci-003" + + @validator("api_key", pre=True, always=True) + def validate_api_key(cls, v): + openai.api_key = v + return v + + def with_system_message(self, system_message: Union[str, None]): + return OpenAI(api_key=self.api_key, system_message=system_message) + + def stream_chat(self, messages, **kwargs) -> Generator[Union[Any, List, Dict], None, None]: + self.completion_count += 1 + args = {"max_tokens": 512, "temperature": 0.5, "top_p": 1, + "frequency_penalty": 0, "presence_penalty": 0} | kwargs + args["stream"] = True + args["model"] = "gpt-3.5-turbo" + + for chunk in openai.ChatCompletion.create( + messages=messages, + **args, + ): + if "content" in chunk.choices[0].delta: + yield chunk.choices[0].delta.content + else: + continue + + def stream_complete(self, prompt: str, **kwargs) -> Generator[Union[Any, List, Dict], None, None]: + self.completion_count += 1 + args = {"model": self.default_model, "max_tokens": 512, "temperature": 0.5, + "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0, "suffix": None} | kwargs + args["stream"] = True + + if args["model"] == "gpt-3.5-turbo": + generator = openai.ChatCompletion.create( + messages=[{ + "role": "user", + "content": prompt + }], + **args, + ) + for chunk in generator: + yield chunk.choices[0].message.content + else: + generator = openai.Completion.create( + prompt=prompt, + **args, + ) + for chunk in generator: + yield chunk.choices[0].text + + def complete(self, prompt: str, **kwargs) -> str: + t1 = time.time() + + self.completion_count += 1 + args = {"model": self.default_model, "max_tokens": 512, "temperature": 0.5, "top_p": 1, + "frequency_penalty": 0, "presence_penalty": 0, "stream": False} | kwargs + + if args["model"] == "gpt-3.5-turbo": + messages = [{ + "role": "user", + "content": prompt + }] + if self.system_message: + messages.insert(0, { + "role": "system", + "content": self.system_message + }) + resp = openai.ChatCompletion.create( + messages=messages, + **args, + ).choices[0].message.content + else: + resp = openai.Completion.create( + prompt=prompt, + **args, + ).choices[0].text + + t2 = time.time() + print("Completion time:", t2 - t1) + return resp + + def edit(self, inp: str, instruction: str) -> str: + try: + resp = openai.Edit.create( + input=inp, + instruction=instruction, + model='text-davinci-edit-001' + ).choices[0].text + return resp + except Exception as e: + print("OpenAI error:", e) + raise e + + def parallel_edit(self, inputs: list[str], instructions: Union[List[str], str], **kwargs) -> list[str]: + args = {"temperature": 0.5, "top_p": 1} | kwargs + args['model'] = 'text-davinci-edit-001' + + async def fn(): + async with aiohttp.ClientSession() as session: + tasks = [] + + async def get(input, instruction): + async with session.post("https://api.openai.com/v1/edits", headers={ + "Content-Type": "application/json", + "Authorization": "Bearer " + self.api_key + }, json={"model": args["model"], "input": input, "instruction": instruction, "temperature": args["temperature"], "max_tokens": args["max_tokens"], "suffix": args["suffix"]}) as resp: + json = await resp.json() + if "error" in json: + print("ERROR IN GPT-3 RESPONSE: ", json) + return None + return json["choices"][0]["text"] + + for i in range(len(inputs)): + tasks.append(get(inputs[i], instructions[i] if isinstance( + instructions, list) else instructions)) + + return await asyncio.gather(*tasks) + + return asyncio.run(fn()) + + def parallel_complete(self, prompts: list[str], suffixes: Union[list[str], None] = None, **kwargs) -> list[str]: + self.completion_count += len(prompts) + args = {"model": self.default_model, "max_tokens": 512, "temperature": 0.5, + "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0} | kwargs + + async def fn(): + async with aiohttp.ClientSession() as session: + tasks = [] + + async def get(prompt, suffix): + async with session.post("https://api.openai.com/v1/completions", headers={ + "Content-Type": "application/json", + "Authorization": "Bearer " + self.api_key + }, json={"model": args["model"], "prompt": prompt, "temperature": args["temperature"], "max_tokens": args["max_tokens"], "suffix": suffix}) as resp: + json = await resp.json() + if "error" in json: + print("ERROR IN GPT-3 RESPONSE: ", json) + return None + return json["choices"][0]["text"] + + for i in range(len(prompts)): + tasks.append(asyncio.ensure_future( + get(prompts[i], suffixes[i] if suffixes else None))) + + return await asyncio.gather(*tasks) + + return asyncio.run(fn()) diff --git a/continuedev/src/continuedev/libs/llm/prompt_utils.py b/continuedev/src/continuedev/libs/llm/prompt_utils.py new file mode 100644 index 00000000..51b64732 --- /dev/null +++ b/continuedev/src/continuedev/libs/llm/prompt_utils.py @@ -0,0 +1,71 @@ +from typing import Dict, List, Union +from ...models.filesystem import RangeInFileWithContents +from ...models.filesystem_edit import FileEdit + + +class MarkdownStyleEncoderDecoder: + # Filename -> the part of the file you care about + range_in_files: List[RangeInFileWithContents] + + def __init__(self, range_in_files: List[RangeInFileWithContents]): + self.range_in_files = range_in_files + + def encode(self) -> str: + return "\n\n".join([ + f"File ({rif.filepath})\n```\n{rif.contents}\n```" + for rif in self.range_in_files + ]) + + def _suggestions_to_file_edits(self, suggestions: Dict[str, str]) -> List[FileEdit]: + file_edits: List[FileEdit] = [] + for suggestion_filepath, suggestion in suggestions.items(): + matching_rifs = list( + filter(lambda r: r.filepath == suggestion_filepath, self.range_in_files)) + if (len(matching_rifs) > 0): + range_in_file = matching_rifs[0] + file_edits.append(FileEdit( + range=range_in_file.range, + filepath=range_in_file.filepath, + replacement=suggestion + )) + + return file_edits + + def _decode_to_suggestions(self, completion: str) -> Dict[str, str]: + if len(self.range_in_files) == 0: + return {} + + if not '```' in completion: + completion = "```\n" + completion + "\n```" + if completion.strip().splitlines()[0].strip() == '```': + first_filepath = self.range_in_files[0].filepath + completion = f"File ({first_filepath})\n" + completion + + suggestions: Dict[str, str] = {} + current_file_lines: List[str] = [] + current_filepath: Union[str, None] = None + last_was_file = False + inside_file = False + for line in completion.splitlines(): + if line.strip().startswith("File ("): + last_was_file = True + current_filepath = line.strip()[6:-1] + elif last_was_file and line.startswith("```"): + last_was_file = False + inside_file = True + elif inside_file: + if line.startswith("```"): + inside_file = False + suggestions[current_filepath] = "\n".join( + current_file_lines) + current_file_lines = [] + current_filepath = None + else: + current_file_lines.append(line) + + return suggestions + + def decode(self, completion: str) -> List[FileEdit]: + suggestions = self._decode_to_suggestions(completion) + file_edits = self._suggestions_to_file_edits(suggestions) + return file_edits diff --git a/continuedev/src/continuedev/libs/llm/prompters.py b/continuedev/src/continuedev/libs/llm/prompters.py new file mode 100644 index 00000000..04e9885a --- /dev/null +++ b/continuedev/src/continuedev/libs/llm/prompters.py @@ -0,0 +1,112 @@ +from typing import Any, Callable, List, Tuple, Union +from ..llm import LLM +from .openai import OpenAI + + +def cls_method_to_str(cls_name: str, init: str, method: str) -> str: + """Convert class and method info to formatted code""" + return f"""class {cls_name}: +{init} +{method}""" + + +# Prompter classes +class Prompter: + def __init__(self, llm: LLM = None): + if llm is None: + self.llm = OpenAI() + else: + self.llm = llm + + def _compile_prompt(self, inp: Any) -> Tuple[str, str, Union[str, None]]: + "Takes input and returns prompt, prefix, suffix" + raise NotImplementedError + + def complete(self, inp: Any, **kwargs) -> str: + prompt, prefix, suffix = self._compile_prompt(inp) + resp = self.llm.complete(prompt + prefix, suffix=suffix, **kwargs) + return prefix + resp + (suffix or "") + + def __call__(self, inp: Any, **kwargs) -> str: + return self.complete(inp, **kwargs) + + def parallel_complete(self, inps: List[Any]) -> List[str]: + prompts = [] + prefixes = [] + suffixes = [] + for inp in inps: + prompt, prefix, suffix = self._compile_prompt(inp) + prompts.append(prompt) + prefixes.append(prefix) + suffixes.append(suffix) + + resps = self.llm.parallel_complete( + [prompt + prefix for prompt, prefix in zip(prompts, prefixes)], suffixes=suffixes) + return [prefix + resp + (suffix or "") for prefix, resp, suffix in zip(prefixes, resps, suffixes)] + + +class MixedPrompter(Prompter): + def __init__(self, prompters: List[Prompter], router: Callable[[Any], int], llm: LLM = None): + super().__init__(llm=llm) + self.prompters = prompters + self.router = router + + def _compile_prompt(self, inp: Any) -> Tuple[str, str, Union[str, None]]: + prompter = self.prompters[self.router(inp)] + return prompter._compile_prompt(inp) + + def complete(self, inp: Any, **kwargs) -> str: + prompter = self.prompters[self.router(inp)] + return prompter.complete(inp, **kwargs) + + +class SimplePrompter(Prompter): + def __init__(self, prompt_fn: Callable[[Any], str], llm: LLM = None): + super().__init__(llm=llm) + self.prompt_fn = prompt_fn + + def _compile_prompt(self, inp: Any) -> Tuple[str, str, Union[str, None]]: + return self.prompt_fn(inp), "", None + + +class FormatStringPrompter(SimplePrompter): + """Pass a formatted string, and the input should be a dict with the keys to format""" + + def __init__(self, prompt: str, llm: LLM = None): + super().__init__(lambda inp: prompt.format(**inp), llm=llm) + + +class BasicCommentPrompter(SimplePrompter): + def __init__(self, comment: str, llm: LLM = None): + super().__init__(lambda inp: f"""{inp} + +# {comment}""", llm=llm) + + +class EditPrompter(Prompter): + def __init__(self, prompt_fn: Callable[[Any], Tuple[str, str]], llm: LLM = None): + super().__init__(llm=llm) + self.prompt_fn = prompt_fn + + def complete(self, inp: str, **kwargs) -> str: + inp, instruction = self.prompt_fn(inp) + return self.llm.edit(inp, instruction, **kwargs) + + def parallel_complete(self, inps: List[Any]) -> List[str]: + prompts = [] + instructions = [] + for inp in inps: + prompt, instruction = self.prompt_fn(inp) + prompts.append(prompt) + instructions.append(instruction) + + return self.llm.parallel_edit(prompts, instructions) + + +class InsertPrompter(Prompter): + def __init__(self, prompt_fn: Callable[[Any], Tuple[str, str, str]], llm: LLM = None): + super().__init__(llm=llm) + self.prompt_fn = prompt_fn + + def _compile_prompt(self, inp: Any) -> Tuple[str, str, Union[str, None]]: + return self.prompt_fn(inp) diff --git a/continuedev/src/continuedev/libs/llm/utils.py b/continuedev/src/continuedev/libs/llm/utils.py new file mode 100644 index 00000000..76240d4e --- /dev/null +++ b/continuedev/src/continuedev/libs/llm/utils.py @@ -0,0 +1,34 @@ +from transformers import AutoTokenizer, AutoModelForCausalLM +from transformers import GPT2TokenizerFast + +gpt2_tokenizer = GPT2TokenizerFast.from_pretrained("gpt2") +def count_tokens(text: str) -> int: + return len(gpt2_tokenizer.encode(text)) + +prices = { + # All prices are per 1k tokens + "fine-tune-train": { + "davinci": 0.03, + "curie": 0.03, + "babbage": 0.0006, + "ada": 0.0004, + }, + "completion": { + "davinci": 0.02, + "curie": 0.002, + "babbage": 0.0005, + "ada": 0.0004, + }, + "fine-tune-completion": { + "davinci": 0.12, + "curie": 0.012, + "babbage": 0.0024, + "ada": 0.0016, + }, + "embedding": { + "ada": 0.0004 + } +} + +def get_price(text: str, model: str="davinci", task: str="completion") -> float: + return count_tokens(text) * prices[task][model] / 1000 \ No newline at end of file diff --git a/continuedev/src/continuedev/libs/observation.py b/continuedev/src/continuedev/libs/observation.py new file mode 100644 index 00000000..fef04311 --- /dev/null +++ b/continuedev/src/continuedev/libs/observation.py @@ -0,0 +1,35 @@ +from pydantic import BaseModel, validator +from ..models.main import Traceback + + +class Observation(BaseModel): + pass + + +class TracebackObservation(Observation): + traceback: Traceback + + +class ValidatorObservation(Observation): + passed: bool + + +class UserInputObservation(Observation): + user_input: str + + +class DictObservation(Observation): + values: dict + + def __getitem__(self, key): + return self.values[key] + + +class TextObservation(Observation): + text: str + + @validator("text", pre=True, always=True) + def text_not_none(cls, v): + if v is None: + return "" + return v diff --git a/continuedev/src/continuedev/libs/policy.py b/continuedev/src/continuedev/libs/policy.py new file mode 100644 index 00000000..586eaebe --- /dev/null +++ b/continuedev/src/continuedev/libs/policy.py @@ -0,0 +1,91 @@ +from typing import List, Tuple, Type + +from .steps.ty import CreatePipelineStep +from .core import Step, Validator, Policy, History +from .observation import Observation, TracebackObservation, UserInputObservation +from .steps.main import EditCodeStep, EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep +from .steps.nate import WritePytestsStep, CreateTableStep +from .steps.chroma import AnswerQuestionChroma, EditFileChroma + + +class DemoPolicy(Policy): + ran_code_last: bool = False + cmd: str + + def next(self, history: History) -> Step: + observation = history.last_observation() + if observation is not None and isinstance(observation, UserInputObservation): + # This could be defined with ObservationTypePolicy. Ergonomics not right though. + if " test" in observation.user_input.lower(): + return WritePytestsStep(instructions=observation.user_input) + elif "/dlt" in observation.user_input.lower() or " dlt" in observation.user_input.lower(): + return CreatePipelineStep() + elif "/table" in observation.user_input: + return CreateTableStep(sql_str=" ".join(observation.user_input.split(" ")[1:])) + elif "/ask" in observation.user_input: + return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) + elif "/edit" in observation.user_input: + return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) + return EditHighlightedCodeStep(user_input=observation.user_input) + + state = history.get_current() + if state is None or not self.ran_code_last: + self.ran_code_last = True + return RunCodeStep(cmd=self.cmd) + + if observation is not None and isinstance(observation, TracebackObservation): + self.ran_code_last = False + return SolveTracebackStep(traceback=observation.traceback) + else: + return None + + +class ObservationTypePolicy(Policy): + def __init__(self, base_policy: Policy, observation_type: Type[Observation], step_type: Type[Step]): + self.observation_type = observation_type + self.step_type = step_type + self.base_policy = base_policy + + def next(self, history: History) -> Step: + observation = history.last_observation() + if observation is not None and isinstance(observation, self.observation_type): + return self.step_type(observation) + return self.base_policy.next(history) + + +class PolicyWrappedWithValidators(Policy): + """Default is to stop, unless the validator tells what to do next""" + index: int + stage: int + + def __init__(self, base_policy: Policy, pairs: List[Tuple[Validator, Type[Step]]]): + # Want to pass Type[Validator], or just the Validator? Question of where params are coming from. + self.pairs = pairs + self.index = len(pairs) + self.validating = 0 + self.base_policy = base_policy + + def next(self, history: History) -> Step: + if self.index == len(self.pairs): + self.index = 0 + return self.base_policy.next(history) + + if self.stage == 0: + # Running the validator at the current index for the first time + validator, step = self.pairs[self.index] + self.stage = 1 + return validator + elif self.stage == 1: + # Previously ran the validator at the current index, now receiving its ValidatorObservation + observation = history.last_observation() + if observation.passed: + self.stage = 0 + self.index += 1 + if self.index == len(self.pairs): + self.index = 0 + return self.base_policy.next(history) + else: + return self.pairs[self.index][0] + else: + _, step_type = self.pairs[self.index] + return step_type(observation) diff --git a/continuedev/src/continuedev/libs/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": , + "replace_me": , + "replace_with": + }}] +}} + +For example, if you want to replace the code `x = 1` with `x = 2` in main.py, you would write: +{{ + "edits": [{{ + "filepath": "main.py", + "replace_me": "x = 1", + "replace_with": "x = 2" + }}] +}} +If you wanted to delete the code `def sum(a, b):\\n return a + b` in main.py, you would write: +{{ + "edits": [{{ + "filepath": "main.py", + "replace_me": "def sum(a, b):\\n return a + b", + "replace_with": "" + }}] +}} + +Respond with only as many edits as needed, and output only the list of json objects, no other text. +""") + + 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() + ) diff --git a/continuedev/src/continuedev/libs/util/copy_codebase.py b/continuedev/src/continuedev/libs/util/copy_codebase.py new file mode 100644 index 00000000..ef1db72b --- /dev/null +++ b/continuedev/src/continuedev/libs/util/copy_codebase.py @@ -0,0 +1,127 @@ +import os +from pathlib import Path +from typing import Iterable, List, Union +from watchdog.observers import Observer +from watchdog.events import PatternMatchingEventHandler +from ..models.main import FileEdit, DeleteDirectory, DeleteFile, AddDirectory, AddFile, FileSystemEdit, Position, Range, RenameFile, RenameDirectory, SequentialFileSystemEdit +from ..models.filesystem import FileSystem +from ..libs.main import Agent +from ..libs.map_path import map_path +from ..libs.steps.main import ManualEditAction +import shutil +import difflib + + +def create_copy(orig_root: str, copy_root: str = None, ignore: Iterable[str] = []): + # TODO: Make ignore a spec, like .gitignore + if copy_root is None: + copy_root = Path(orig_root) / ".continue-copy" + ignore.append(str(copy_root)) + ignore = set(ignore) + + os.mkdir(copy_root) + # I think you're messing up a lot of absolute paths here + for child in os.listdir(): + if os.path.isdir(child): + if child not in ignore: + os.mkdir(map_path(child)) + create_copy(Path(orig_root) / child, + Path(copy_root) / child, ignore) + else: + os.symlink(child, map_path(child)) + else: + if child not in ignore: + shutil.copyfile(child, map_path(child)) + else: + os.symlink(child, map_path(child)) + + +def calculate_diff(filepath: str, original: str, updated: str) -> List[FileEdit]: + s = difflib.SequenceMatcher(None, original, updated) + offset = 0 # The indices are offset by previous deletions/insertions + edits = [] + for tag, i1, i2, j1, j2 in s.get_opcodes(): + i1, i2, j1, j2 = i1 + offset, i2 + offset, j1 + offset, j2 + offset + replacement = updated[j1:j2] + if tag == "equal": + pass + elif tag == "delete": + edits.append(FileEdit.from_deletion( + filepath, Range.from_indices(original, i1, i2))) + offset -= i2 - i1 + elif tag == "insert": + edits.append(FileEdit.from_insertion( + filepath, Position.from_index(original, i1), replacement)) + offset += j2 - j1 + elif tag == "replace": + edits.append(FileEdit(filepath, Range.from_indices( + original, i1, i2), replacement)) + offset += (j2 - j1) - (i2 + i1) + else: + raise Exception("Unexpected difflib.SequenceMatcher tag: " + tag) + + return edits + + +# The whole usage of watchdog here should only be specific to RealFileSystem, you want to have a different "Observer" class for VirtualFileSystem, which would depend on being sent notifications +class CopyCodebaseEventHandler(PatternMatchingEventHandler): + def __init__(self, ignore_directories: List[str], ignore_patterns: List[str], agent: Agent, orig_root: str, copy_root: str, filesystem: FileSystem): + super().__init__(ignore_directories=ignore_directories, ignore_patterns=ignore_patterns) + self.agent = agent + self.orig_root = orig_root + self.copy_root = copy_root + self.filesystem = filesystem + + # For now, we'll just make the update immediately, but eventually need to sync with agent. + # It should be the agent that makes the update right? It's just another action, everything comes from a single stream. + + def _event_to_edit(self, event) -> Union[FileSystemEdit, None]: + # NOTE: You'll need to map paths to create both an action within the copy filesystem (the one you take) and one in the original fileystem (the one you'll record and allow the user to accept). Basically just need a converter built in to the FileSystemEdit class + src = event.src_path() + if event.is_directory: + if event.event_type == "moved": + return RenameDirectory(src, event.dest_path()) + elif event.event_type == "deleted": + return DeleteDirectory(src) + elif event.event_type == "created": + return AddDirectory(src) + else: + if event.event_type == "moved": + return RenameFile(src, event.dest_path()) + elif event.event_type == "deleted": + return DeleteFile(src) + elif event.event_type == "created": + contents = self.filesystem.read(src) + # Unclear whether it will always pass a "modified" event right after if something like echo "abc" > newfile.txt happens + return AddFile(src, contents) + elif event.event_type == "modified": + # Watchdog doesn't pass the contents or edit, so have to get it myself and diff + updated = self.filesystem.read(src) + copy_filepath = map_path(src, self.orig_root, self.copy_root) + old = self.filesystem.read(copy_filepath) + + edits = calculate_diff(src, updated, old) + return SequentialFileSystemEdit(edits) + return None + + def on_any_event(self, event): + edit = self._event_to_edit(event) + if edit is None: + return + edit = edit.with_mapped_paths(self.orig_root, self.copy_root) + action = ManualEditAction(edit) + self.agent.act(action) + + +def maintain_copy_workspace(agent: Agent, filesystem: FileSystem, orig_root: str, copy_root: str): + observer = Observer() + event_handler = CopyCodebaseEventHandler( + [".git"], [], agent, orig_root, copy_root, filesystem) + observer.schedule(event_handler, orig_root, recursive=True) + observer.start() + try: + while observer.isAlive(): + observer.join(1) + finally: + observer.stop() + observer.join() diff --git a/continuedev/src/continuedev/libs/util/map_path.py b/continuedev/src/continuedev/libs/util/map_path.py new file mode 100644 index 00000000..8eb57712 --- /dev/null +++ b/continuedev/src/continuedev/libs/util/map_path.py @@ -0,0 +1,16 @@ +from pathlib import Path + +def map_path(path: str, orig_root: str, copy_root: str) -> Path: + path = Path(path) + if path.is_relative_to(orig_root): + if path.is_absolute(): + return Path(copy_root) / path.relative_to(orig_root) + else: + return path + else: + if path.is_absolute(): + return path + else: + # For this one, you need to know the directory from which the relative path is being used. + return Path(orig_root) / path + diff --git a/continuedev/src/continuedev/libs/util/queue.py b/continuedev/src/continuedev/libs/util/queue.py new file mode 100644 index 00000000..e1f98cc6 --- /dev/null +++ b/continuedev/src/continuedev/libs/util/queue.py @@ -0,0 +1,17 @@ +import asyncio +from typing import Dict + + +class AsyncSubscriptionQueue: + # The correct way to do this is probably to keep request IDs + queues: Dict[str, asyncio.Queue] = {} + + def post(self, messageType: str, data: any): + if messageType not in self.queues: + self.queues.update({messageType: asyncio.Queue()}) + self.queues[messageType].put_nowait(data) + + async def get(self, message_type: str) -> any: + if message_type not in self.queues: + self.queues.update({message_type: asyncio.Queue()}) + return await self.queues[message_type].get() diff --git a/continuedev/src/continuedev/libs/util/traceback_parsers.py b/continuedev/src/continuedev/libs/util/traceback_parsers.py new file mode 100644 index 00000000..c31929c1 --- /dev/null +++ b/continuedev/src/continuedev/libs/util/traceback_parsers.py @@ -0,0 +1,24 @@ +from typing import Union +from ...models.main import Traceback +from boltons import tbutils + + +def sort_func(items): + """Sort a list of items.""" + return sorted(items) + + +def parse_python_traceback(stdout: str) -> Union[Traceback, None]: + """Parse a python traceback from stdout.""" + + # Sometimes paths are not quoted, but they need to be + if "File \"" not in stdout: + stdout = stdout.replace("File ", "File \"").replace( + ", line ", "\", line ") + + try: + tbutil_parsed_exc = tbutils.ParsedException.from_string(stdout) + return Traceback.from_tbutil_parsed_exc(tbutil_parsed_exc) + + except Exception: + return None diff --git a/continuedev/src/continuedev/models/__init__.py b/continuedev/src/continuedev/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/continuedev/src/continuedev/models/filesystem.py b/continuedev/src/continuedev/models/filesystem.py new file mode 100644 index 00000000..ede636c5 --- /dev/null +++ b/continuedev/src/continuedev/models/filesystem.py @@ -0,0 +1,328 @@ +from abc import ABC, abstractmethod +from typing import Dict, List, Tuple +import os +from ..models.main import Position, Range, AbstractModel +from pydantic import BaseModel +from .filesystem_edit import FileSystemEdit, FileEdit, AddFile, DeleteFile, RenameDirectory, RenameFile, AddDirectory, DeleteDirectory, EditDiff, SequentialFileSystemEdit + + +class RangeInFile(BaseModel): + filepath: str + range: Range + + def __hash__(self): + return hash((self.filepath, self.range)) + + @staticmethod + def from_entire_file(filepath: str, content: str) -> "RangeInFile": + range = Range.from_entire_file(content) + return RangeInFile( + filepath=filepath, + range=range + ) + + +class RangeInFileWithContents(RangeInFile): + contents: str + + def __hash__(self): + return hash((self.filepath, self.range, self.contents)) + + @staticmethod + def from_entire_file(filepath: str, content: str) -> "RangeInFileWithContents": + lines = content.splitlines() + return RangeInFileWithContents( + filepath=filepath, + range=Range.from_shorthand( + 0, 0, len(lines) - 1, len(lines[-1]) - 1), + contents=content + ) + + @staticmethod + def from_range_in_file(rif: RangeInFile, content: str) -> "RangeInFileWithContents": + return RangeInFileWithContents( + filepath=rif.filepath, + range=rif.range, + contents=content + ) + + +class FileSystem(AbstractModel): + """An abstract filesystem that can read/write from a set of files.""" + @abstractmethod + def read(self, path) -> str: + raise NotImplementedError + + @abstractmethod + def readlines(self, path) -> List[str]: + raise NotImplementedError + + @abstractmethod + def write(self, path, content): + raise NotImplementedError + + @abstractmethod + def exists(self, path) -> bool: + raise NotImplementedError + + @abstractmethod + def read_range_in_file(self, r: RangeInFile) -> str: + raise NotImplementedError + + @abstractmethod + def rename_file(self, filepath: str, new_filepath: str): + raise NotImplementedError + + @abstractmethod + def rename_directory(self, path: str, new_path: str): + raise NotImplementedError + + @abstractmethod + def delete_file(self, filepath: str): + raise NotImplementedError + + @abstractmethod + def delete_directory(self, path: str): + raise NotImplementedError + + @abstractmethod + def add_directory(self, path: str): + raise NotImplementedError + + @abstractmethod + def apply_file_edit(self, edit: FileEdit) -> EditDiff: + raise NotImplementedError + + @abstractmethod + def apply_edit(self, edit: FileSystemEdit) -> EditDiff: + """Apply edit to filesystem, calculate the reverse edit, and return and EditDiff""" + raise NotImplementedError + + @classmethod + def read_range_in_str(self, s: str, r: Range) -> str: + lines = s.splitlines()[r.start.line:r.end.line + 1] + if len(lines) == 0: + return "" + + lines[0] = lines[0][r.start.character:] + lines[-1] = lines[-1][:r.end.character + 1] + return "\n".join(lines) + + @classmethod + def apply_edit_to_str(cls, s: str, edit: FileEdit) -> Tuple[str, EditDiff]: + original = cls.read_range_in_str(s, edit.range) + + # Split lines and deal with some edge cases (could obviously be nicer) + lines = s.splitlines() + if s.startswith("\n"): + lines.insert(0, "") + if s.endswith("\n"): + lines.append("") + + if len(lines) == 0: + lines = [""] + + end = Position(line=edit.range.end.line, + character=edit.range.end.character) + if edit.range.end.line == len(lines) and edit.range.end.character == 0: + end = Position(line=edit.range.end.line - 1, + character=len(lines[min(len(lines) - 1, edit.range.end.line - 1)])) + + before_lines = lines[:edit.range.start.line] + after_lines = lines[end.line + 1:] + between_str = lines[min(len(lines) - 1, edit.range.start.line)][:edit.range.start.character] + \ + edit.replacement + \ + lines[min(len(lines) - 1, end.line)][end.character + 1:] + + new_range = Range( + start=edit.range.start, + end=Position( + line=edit.range.start.line + + len(edit.replacement.splitlines()) - 1, + character=edit.range.start.character + + len(edit.replacement.splitlines() + [-1]) if edit.replacement != "" else 0 + ) + ) + + lines = before_lines + between_str.splitlines() + after_lines + return "\n".join(lines), EditDiff( + forward=edit, + backward=FileEdit( + filepath=edit.filepath, + range=new_range, + replacement=original + ) + ) + + def reverse_edit_on_str(self, s: str, diff: EditDiff) -> str: + lines = s.splitlines() + + replacement_lines = diff.replacement.splitlines() + replacement_d_lines = len(replacement_lines) + replacement_d_chars = len(replacement_lines[-1]) + replacement_range = Range( + start=diff.edit.range.start, + end=Position( + line=diff.edit.range.start + replacement_d_lines, + character=diff.edit.range.start.character + replacement_d_chars + ) + ) + + before_lines = lines[:replacement_range.start.line] + after_lines = lines[replacement_range.end.line + 1:] + between_str = lines[replacement_range.start.line][:replacement_range.start.character] + \ + diff.original + \ + lines[replacement_range.end.line][replacement_range.end.character + 1:] + + lines = before_lines + between_str.splitlines() + after_lines + return "\n".join(lines) + + def apply_edit(self, edit: FileSystemEdit) -> EditDiff: + backward = None + if isinstance(edit, FileEdit): + diff = self.apply_file_edit(edit) + backward = diff.backward + elif isinstance(edit, AddFile): + self.write(edit.filepath, edit.content) + backward = DeleteFile(edit.filepath) + elif isinstance(edit, DeleteFile): + contents = self.read(edit.filepath) + backward = AddFile(edit.filepath, contents) + self.delete_file(edit.filepath) + elif isinstance(edit, RenameFile): + self.rename_file(edit.filepath, edit.new_filepath) + backward = RenameFile(filepath=edit.new_filepath, + new_filepath=edit.filepath) + elif isinstance(edit, AddDirectory): + self.add_directory(edit.path) + backward = DeleteDirectory(edit.path) + elif isinstance(edit, DeleteDirectory): + # This isn't atomic! + backward_edits = [] + for root, dirs, files in os.walk(edit.path, topdown=False): + for f in files: + path = os.path.join(root, f) + backward_edits.append(self.apply_edit(DeleteFile(path))) + for d in dirs: + path = os.path.join(root, d) + backward_edits.append( + self.apply_edit(DeleteDirectory(path))) + + backward_edits.append(self.apply_edit(DeleteDirectory(edit.path))) + backward_edits.reverse() + backward = SequentialFileSystemEdit(edits=backward_edits) + elif isinstance(edit, RenameDirectory): + self.rename_directory(edit.path, edit.new_path) + backward = RenameDirectory(path=edit.new_path, new_path=edit.path) + elif isinstance(edit, FileSystemEdit): + backward_edits = [] + for edit in edit.next_edit(): + backward_edits.append(self.apply_edit(edit)) + backward_edits.reverse() + backward = SequentialFileSystemEdit(edits=backward_edits) + else: + raise TypeError("Unknown FileSystemEdit type: " + str(type(edit))) + + return EditDiff( + forward=edit, + backward=backward + ) + + +class RealFileSystem(FileSystem): + """A filesystem that reads/writes from the actual filesystem.""" + + def read(self, path) -> str: + with open(path, "r") as f: + return f.read() + + def readlines(self, path) -> List[str]: + with open(path, "r") as f: + return f.readlines() + + def write(self, path, content): + with open(path, "w") as f: + f.write(content) + + def exists(self, path) -> bool: + return os.path.exists(path) + + def read_range_in_file(self, r: RangeInFile) -> str: + return FileSystem.read_range_in_str(self.read(r.filepath), r.range) + + def rename_file(self, filepath: str, new_filepath: str): + os.rename(filepath, new_filepath) + + def rename_directory(self, path: str, new_path: str): + os.rename(path, new_path) + + def delete_file(self, filepath: str): + os.remove(filepath) + + def delete_directory(self, path: str): + raise NotImplementedError + + def add_directory(self, path: str): + os.makedirs(path) + + def apply_file_edit(self, edit: FileEdit) -> EditDiff: + old_content = self.read(edit.filepath) + new_content, diff = FileSystem.apply_edit_to_str(old_content, edit) + self.write(edit.filepath, new_content) + return diff + + +class VirtualFileSystem(FileSystem): + """A simulated filesystem from a mapping of filepath to file contents.""" + files: Dict[str, str] + + def __init__(self, files: Dict[str, str]): + self.files = files + + def read(self, path) -> str: + return self.files[path] + + def readlines(self, path) -> List[str]: + return self.files[path].splitlines() + + def write(self, path, content): + self.files[path] = content + + def exists(self, path) -> bool: + return path in self.files + + def read_range_in_file(self, r: RangeInFile) -> str: + return FileSystem.read_range_in_str(self.read(r.filepath), r.range) + + def rename_file(self, filepath: str, new_filepath: str): + self.files[new_filepath] = self.files[filepath] + del self.files[filepath] + + def rename_directory(self, path: str, new_path: str): + for filepath in self.files: + if filepath.startswith(path): + new_filepath = new_path + filepath[len(path):] + self.files[new_filepath] = self.files[filepath] + del self.files[filepath] + + def delete_file(self, filepath: str): + del self.files[filepath] + + def delete_directory(self, path: str): + raise NotImplementedError + + def add_directory(self, path: str): + # For reasons as seen here and in delete_directory, a Dict[str, str] might not be the best represntation. Could just preprocess to something better upon __init__ + pass + + def apply_file_edit(self, edit: FileEdit) -> EditDiff: + old_content = self.read(edit.filepath) + new_content, original = FileSystem.apply_edit_to_str(old_content, edit) + self.write(edit.filepath, new_content) + return EditDiff( + edit=edit, + original=original + ) + +# TODO: Uniform errors thrown by any FileSystem subclass. diff --git a/continuedev/src/continuedev/models/filesystem_edit.py b/continuedev/src/continuedev/models/filesystem_edit.py new file mode 100644 index 00000000..7526d4c9 --- /dev/null +++ b/continuedev/src/continuedev/models/filesystem_edit.py @@ -0,0 +1,136 @@ +from abc import abstractmethod +from typing import Generator, List +from pydantic import BaseModel +from .main import Position, Range +from ..libs.util.map_path import map_path +import os + + +class FileSystemEdit(BaseModel): + @abstractmethod + def next_edit(self) -> Generator["FileSystemEdit", None, None]: + raise NotImplementedError + + @abstractmethod + def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": + raise NotImplementedError + + +class AtomicFileSystemEdit(FileSystemEdit): + def next_edit(self) -> Generator["FileSystemEdit", None, None]: + yield self + + +class FileEdit(AtomicFileSystemEdit): + filepath: str + range: Range + replacement: str + + def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": + return FileEdit(map_path(self.filepath, orig_root, copy_root), self.range, self.replacement) + + @staticmethod + def from_deletion(filepath: str, start: Position, end: Position) -> "FileEdit": + return FileEdit(filepath, Range(start, end), "") + + @staticmethod + def from_insertion(filepath: str, position: Position, content: str) -> "FileEdit": + return FileEdit(filepath=filepath, range=Range.from_shorthand(position.line, position.character, position.line, position.character), replacement=content) + + +class FileEditWithFullContents(BaseModel): + fileEdit: FileEdit + fileContents: str + + +class AddFile(AtomicFileSystemEdit): + filepath: str + content: str + + def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": + return AddFile(self, map_path(self.filepath, orig_root, copy_root), self.content) + + +class DeleteFile(AtomicFileSystemEdit): + filepath: str + + def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": + return DeleteFile(map_path(self.filepath, orig_root, copy_root)) + + +class RenameFile(AtomicFileSystemEdit): + filepath: str + new_filepath: str + + def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": + return RenameFile(map_path(self.filepath, orig_root, copy_root), map_path(self.new_filepath, orig_root, copy_root)) + + +class AddDirectory(AtomicFileSystemEdit): + path: str + + def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": + return AddDirectory(map_path(self.path, orig_root, copy_root)) + + +class DeleteDirectory(AtomicFileSystemEdit): + path: str + + def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": + return DeleteDirectory(map_path(self.path, orig_root, copy_root)) + + +class RenameDirectory(AtomicFileSystemEdit): + path: str + new_path: str + + def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": + return RenameDirectory(map_path(self.filepath, orig_root, copy_root), map_path(self.new_path, orig_root, copy_root)) + + +class DeleteDirectoryRecursive(FileSystemEdit): + path: str + + def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": + return DeleteDirectoryRecursive(map_path(self.path, orig_root, copy_root)) + + def next_edit(self) -> Generator[FileSystemEdit, None, None]: + yield DeleteDirectory(path=self.path) + for child in os.listdir(self.path): + child_path = os.path.join(self.path, child) + if os.path.isdir(child_path): + yield DeleteDirectoryRecursive(path=child_path) + else: + yield DeleteFile(filepath=child_path) + + +class SequentialFileSystemEdit(FileSystemEdit): + edits: List[FileSystemEdit] + + def with_mapped_paths(self, orig_root: str, copy_root: str) -> "FileSystemEdit": + return SequentialFileSystemEdit([ + edit.with_mapped_paths(orig_root, copy_root) + for edit in self.edits + ]) + + def next_edit(self) -> Generator["FileSystemEdit", None, None]: + for edit in self.edits: + yield from edit.next_edit() + + +class EditDiff(BaseModel): + """A reversible edit that can be applied to a file.""" + forward: FileSystemEdit + backward: FileSystemEdit + + @classmethod + def from_sequence(cls, diffs: List["EditDiff"]) -> "EditDiff": + forwards = [] + backwards = [] + for diff in diffs: + forwards.append(diff.forward) + backwards.insert(0, diff.backward) + return cls( + forward=SequentialFileSystemEdit(edits=forwards), + backward=SequentialFileSystemEdit(edits=backwards) + ) diff --git a/continuedev/src/continuedev/models/generate_json_schema.py b/continuedev/src/continuedev/models/generate_json_schema.py new file mode 100644 index 00000000..da78dfac --- /dev/null +++ b/continuedev/src/continuedev/models/generate_json_schema.py @@ -0,0 +1,39 @@ +from .main import * +from .filesystem import RangeInFile, FileEdit +from .filesystem_edit import FileEditWithFullContents +from pydantic import schema_json_of +import os + +MODELS_TO_GENERATE = [ + Position, Range, Traceback, TracebackFrame +] + [ + RangeInFile, FileEdit +] + [ + FileEditWithFullContents +] + +RENAMES = { + "ExampleClass": "RenamedName" +} + +SCHEMA_DIR = "schema/json" + + +def clear_schemas(): + for filename in os.listdir(SCHEMA_DIR): + if filename.endswith(".json"): + os.remove(os.path.join(SCHEMA_DIR, filename)) + + +if __name__ == "__main__": + clear_schemas() + for model in MODELS_TO_GENERATE: + title = RENAMES.get(model.__name__, model.__name__) + try: + json = schema_json_of(model, indent=2, title=title) + except Exception as e: + print(f"Failed to generate json schema for {title}: ", e) + continue + + with open(f"{SCHEMA_DIR}/{title}.json", "w") as f: + f.write(json) diff --git a/continuedev/src/continuedev/models/main.py b/continuedev/src/continuedev/models/main.py new file mode 100644 index 00000000..081ec4af --- /dev/null +++ b/continuedev/src/continuedev/models/main.py @@ -0,0 +1,131 @@ +from abc import ABC +from typing import List, Union +from pydantic import BaseModel, root_validator +from functools import total_ordering + + +@total_ordering +class Position(BaseModel): + line: int + character: int + + def __hash__(self): + return hash((self.line, self.character)) + + def __eq__(self, other: "Position") -> bool: + return self.line == other.line and self.character == other.character + + def __lt__(self, other: "Position") -> bool: + if self.line < other.line: + return True + elif self.line == other.line: + return self.character < other.character + else: + return False + + @staticmethod + def from_index(string: str, index: int) -> "Position": + """Convert index in string to line and character""" + line = string.count("\n", 0, index) + if line == 1: + character = index + else: + character = index - string.rindex("\n", 0, index) - 1 + + return Position(line=line, character=character) + + +class Range(BaseModel): + """A range in a file. 0-indexed.""" + start: Position + end: Position + + def __hash__(self): + return hash((self.start, self.end)) + + def union(self, other: "Range") -> "Range": + return Range( + start=min(self.start, other.start), + end=max(self.end, other.end), + ) + + def is_empty(self) -> bool: + return self.start == self.end + + def overlaps_with(self, other: "Range") -> bool: + return not (self.end < other.start or self.start > other.end) + + @staticmethod + def from_indices(string: str, start_index: int, end_index: int) -> "Range": + return Range( + start=Position.from_index(string, start_index), + end=Position.from_index(string, end_index) + ) + + @staticmethod + def from_shorthand(start_line: int, start_char: int, end_line: int, end_char: int) -> "Range": + return Range( + start=Position( + line=start_line, + character=start_char + ), + end=Position( + line=end_line, + character=end_char + ) + ) + + @staticmethod + def from_entire_file(content: str) -> "Range": + lines = content.splitlines() + if len(lines) == 0: + return Range.from_shorthand(0, 0, 0, 0) + return Range.from_shorthand(0, 0, len(lines) - 1, len(lines[-1]) - 1) + + @staticmethod + def from_snippet_in_file(content: str, snippet: str) -> "Range": + start_index = content.index(snippet) + end_index = start_index + len(snippet) + return Range.from_indices(content, start_index, end_index) + + +class AbstractModel(ABC, BaseModel): + @root_validator(pre=True) + def check_is_subclass(cls, values): + if not issubclass(cls, AbstractModel): + raise TypeError( + "AbstractModel subclasses must be subclasses of AbstractModel") + + +class TracebackFrame(BaseModel): + filepath: str + lineno: int + function: str + code: Union[str, None] + + def __eq__(self, other): + return self.filepath == other.filepath and self.lineno == other.lineno and self.function == other.function + + +class Traceback(BaseModel): + frames: List[TracebackFrame] + message: str + error_type: str + full_traceback: Union[str, None] + + @classmethod + def from_tbutil_parsed_exc(cls, tbutil_parsed_exc): + return cls( + frames=[ + TracebackFrame( + filepath=frame["filepath"], + lineno=frame["lineno"], + function=frame["funcname"], + code=frame["source_line"], + ) + for frame in tbutil_parsed_exc.frames + ], + message=tbutil_parsed_exc.exc_msg, + error_type=tbutil_parsed_exc.exc_type, + full_traceback=tbutil_parsed_exc.to_string(), + ) diff --git a/continuedev/src/continuedev/plugins/__init__.py b/continuedev/src/continuedev/plugins/__init__.py new file mode 100644 index 00000000..0ce6d079 --- /dev/null +++ b/continuedev/src/continuedev/plugins/__init__.py @@ -0,0 +1,26 @@ +from typing import List +import pluggy +from .step import hookspecs +from .step.libs import hello_world + +builtin_libs = [hello_world] + +def get_plugin_manager(use_plugins: List[str]) -> pluggy.PluginManager: + pm = pluggy.PluginManager("continue.step") + pm.add_hookspecs(hookspecs) + pm.load_setuptools_entrypoints("continue.step") + + # Only use plugins that are specified in the config file + for plugin, name in pm.list_name_plugin(): + if name not in use_plugins: + pm.set_blocked(plugin) + + # Print warning if plugin not found + for name in use_plugins: + if not pm.has_plugin(name): + print(f"Plugin {name} not found.") + + for lib in builtin_libs: + pm.register(lib) + + return pm \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/load.py b/continuedev/src/continuedev/plugins/load.py new file mode 100644 index 00000000..adbaad09 --- /dev/null +++ b/continuedev/src/continuedev/plugins/load.py @@ -0,0 +1,21 @@ +def load_validator_plugin(config: ValidatorPluginConfig) -> Validator: + if config.name == "continue.tb_validator": + return PythonTracebackValidator(config.cmd, config.cwd) + elif config.name == "continue.pytest_validator": + return PytestValidator(cwd=config.cwd) + else: + raise KeyError("Unknown validator plugin name") + +def load_llm_plugin(config: LLMPluginConfig) -> LLM: + if config.provider == "openai": + return OpenAI(api_key=config.api_key) + else: + raise KeyError("Unknown LLM provider: " + config.provider) + +def load_policy_plugin(config: PolicyPluginConfig) -> Policy: + if config.name == "continue.random_policy": + return RandomPolicy() + elif config.name == "continue.dfs_policy": + return DFSPolicy() + else: + raise KeyError("Unknown policy plugin name") \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/policy/__init__.py b/continuedev/src/continuedev/plugins/policy/__init__.py new file mode 100644 index 00000000..b9722bae --- /dev/null +++ b/continuedev/src/continuedev/plugins/policy/__init__.py @@ -0,0 +1,4 @@ +import pluggy + +hookimpl = pluggy.HookimplMarker("continue.policy") +"""Marker to be imported and used in plugins (and for own implementations)""" \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/policy/hookspecs.py b/continuedev/src/continuedev/plugins/policy/hookspecs.py new file mode 100644 index 00000000..abe932d3 --- /dev/null +++ b/continuedev/src/continuedev/plugins/policy/hookspecs.py @@ -0,0 +1,10 @@ +from typing import List, Tuple +import pluggy +from ...libs.policy import Policy, Step + +hookspec = pluggy.HookspecMarker("continue.policy") + +class PolicyPlugin(Policy): + @hookspec + def next(self) -> Step: + """Get the next step to run""" \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/policy/libs/__init__.py b/continuedev/src/continuedev/plugins/policy/libs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/continuedev/src/continuedev/plugins/policy/libs/alternate.py b/continuedev/src/continuedev/plugins/policy/libs/alternate.py new file mode 100644 index 00000000..5bfbb821 --- /dev/null +++ b/continuedev/src/continuedev/plugins/policy/libs/alternate.py @@ -0,0 +1,22 @@ +from plugins import policy +from ....libs.observation import Observation +from ....libs.steps import Step +from ....libs.core import History + + +class AlternatingPolicy: + """A Policy that alternates between two steps.""" + + def __init__(self, first: Step, second: Step): + self.first = first + self.second = second + self.last_was_first = False + + @policy.hookimpl + def next(self, history: History) -> Step: + if self.last_was_first: + self.last_was_first = False + return self.second + else: + self.last_was_first = True + return self.first diff --git a/continuedev/src/continuedev/plugins/step/__init__.py b/continuedev/src/continuedev/plugins/step/__init__.py new file mode 100644 index 00000000..e6d8cd3b --- /dev/null +++ b/continuedev/src/continuedev/plugins/step/__init__.py @@ -0,0 +1,4 @@ +import pluggy + +hookimpl = pluggy.HookimplMarker("continue.step") +"""Marker to be imported and used in plugins (and for own implementations)""" \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/step/hookspecs.py b/continuedev/src/continuedev/plugins/step/hookspecs.py new file mode 100644 index 00000000..4309bad3 --- /dev/null +++ b/continuedev/src/continuedev/plugins/step/hookspecs.py @@ -0,0 +1,13 @@ +from typing import Coroutine +import pluggy +from ...libs.core import ContinueSDK, Step, Observation + +hookspec = pluggy.HookspecMarker("continue.step") + +# Perhaps Actions should be generic about what their inputs must be. + + +class StepPlugin(Step): + @hookspec + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + """Run""" diff --git a/continuedev/src/continuedev/plugins/step/libs/__init__.py b/continuedev/src/continuedev/plugins/step/libs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/continuedev/src/continuedev/plugins/step/libs/hello_world.py b/continuedev/src/continuedev/plugins/step/libs/hello_world.py new file mode 100644 index 00000000..72255bfd --- /dev/null +++ b/continuedev/src/continuedev/plugins/step/libs/hello_world.py @@ -0,0 +1,9 @@ +from ....plugins import step +from ....libs.steps import ContinueSDK + + +class HelloWorldStep: + """A Step that prints "Hello World!".""" + @step.hookimpl + def run(sdk: ContinueSDK): + print("Hello World!") diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py new file mode 100644 index 00000000..dd1dc463 --- /dev/null +++ b/continuedev/src/continuedev/server/ide.py @@ -0,0 +1,302 @@ +# This is a separate server from server/main.py +import asyncio +import os +from typing import Any, Dict, List, Type, TypeVar, Union +import uuid +from fastapi import WebSocket, Body, APIRouter +from uvicorn.main import Server + +from ..libs.util.queue import AsyncSubscriptionQueue +from ..models.filesystem import FileSystem, RangeInFile, EditDiff, RealFileSystem +from ..models.main import Traceback +from ..models.filesystem_edit import AddDirectory, AddFile, DeleteDirectory, DeleteFile, FileSystemEdit, FileEdit, FileEditWithFullContents, RenameDirectory, RenameFile, SequentialFileSystemEdit +from pydantic import BaseModel +from .notebook import SessionManager, session_manager +from .ide_protocol import AbstractIdeProtocolServer + + +router = APIRouter(prefix="/ide", tags=["ide"]) + + +# Graceful shutdown by closing websockets +original_handler = Server.handle_exit + + +class AppStatus: + should_exit = False + + @staticmethod + def handle_exit(*args, **kwargs): + AppStatus.should_exit = True + print("Shutting down") + original_handler(*args, **kwargs) + + +Server.handle_exit = AppStatus.handle_exit + + +# TYPES # + + +class FileEditsUpdate(BaseModel): + messageType: str = "fileEdits" + fileEdits: List[FileEditWithFullContents] + + +class OpenFilesResponse(BaseModel): + messageType: str = "openFiles" + openFiles: List[str] + + +class HighlightedCodeResponse(BaseModel): + messageType: str = "highlightedCode" + highlightedCode: List[RangeInFile] + + +class ShowSuggestionRequest(BaseModel): + messageType: str = "showSuggestion" + suggestion: FileEdit + + +class ShowSuggestionResponse(BaseModel): + messageType: str = "showSuggestion" + suggestion: FileEdit + accepted: bool + + +class ReadFileResponse(BaseModel): + messageType: str = "readFile" + contents: str + + +class EditFileResponse(BaseModel): + messageType: str = "editFile" + fileEdit: FileEditWithFullContents + + +class WorkspaceDirectoryResponse(BaseModel): + messageType: str = "workspaceDirectory" + workspaceDirectory: str + + +T = TypeVar("T", bound=BaseModel) + + +class IdeProtocolServer(AbstractIdeProtocolServer): + websocket: WebSocket + session_manager: SessionManager + sub_queue: AsyncSubscriptionQueue = AsyncSubscriptionQueue() + + def __init__(self, session_manager: SessionManager): + self.session_manager = session_manager + + async def _send_json(self, data: Any): + await self.websocket.send_json(data) + + async def _receive_json(self, message_type: str) -> Any: + return await self.sub_queue.get(message_type) + + async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T: + await self._send_json(data) + resp = await self._receive_json(message_type) + return resp_model.parse_obj(resp) + + async def handle_json(self, data: Any): + t = data["messageType"] + if t == "openNotebook": + await self.openNotebook() + elif t == "setFileOpen": + await self.setFileOpen(data["filepath"], data["open"]) + elif t == "fileEdits": + fileEdits = list( + map(lambda d: FileEditWithFullContents.parse_obj(d), data["fileEdits"])) + self.onFileEdits(fileEdits) + elif t in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory"]: + self.sub_queue.post(t, data) + else: + raise ValueError("Unknown message type", t) + + # ------------------------------- # + # Request actions in IDE, doesn't matter which Session + def showSuggestion(): + pass + + async def setFileOpen(self, filepath: str, open: bool = True): + # Agent needs access to this. + await self.websocket.send_json({ + "messageType": "setFileOpen", + "filepath": filepath, + "open": open + }) + + async def openNotebook(self): + session_id = self.session_manager.new_session(self) + await self._send_json({ + "messageType": "openNotebook", + "sessionId": session_id + }) + + async def showSuggestionsAndWait(self, suggestions: List[FileEdit]) -> bool: + ids = [str(uuid.uuid4()) for _ in suggestions] + for i in range(len(suggestions)): + self._send_json({ + "messageType": "showSuggestion", + "suggestion": suggestions[i], + "suggestionId": ids[i] + }) + responses = await asyncio.gather(*[ + self._receive_json(ShowSuggestionResponse) + for i in range(len(suggestions)) + ]) # WORKING ON THIS FLOW HERE. Fine now to just await for response, instead of doing something fancy with a "waiting" state on the agent. + # Just need connect the suggestionId to the IDE (and the notebook) + return any([r.accepted for r in responses]) + + # ------------------------------- # + # Here needs to pass message onto the Agent OR Agent just subscribes. + # This is where you might have triggers: plugins can subscribe to certian events + # like file changes, tracebacks, etc... + + def onAcceptRejectSuggestion(self, suggestionId: str, accepted: bool): + pass + + def onTraceback(self, traceback: Traceback): + # Same as below, maybe not every agent? + for _, session in self.session_manager.sessions.items(): + session.agent.handle_traceback(traceback) + + def onFileSystemUpdate(self, update: FileSystemEdit): + # Access to Agent (so SessionManager) + pass + + def onCloseNotebook(self, session_id: str): + # Accesss to SessionManager + pass + + def onOpenNotebookRequest(self): + pass + + def onFileEdits(self, edits: List[FileEditWithFullContents]): + # Send the file edits to ALL agents. + # Maybe not ideal behavior + for _, session in self.session_manager.sessions.items(): + session.agent.handle_manual_edits(edits) + + # Request information. Session doesn't matter. + async def getOpenFiles(self) -> List[str]: + resp = await self._send_and_receive_json({ + "messageType": "openFiles" + }, OpenFilesResponse, "openFiles") + return resp.openFiles + + async def getWorkspaceDirectory(self) -> str: + resp = await self._send_and_receive_json({ + "messageType": "workspaceDirectory" + }, WorkspaceDirectoryResponse, "workspaceDirectory") + return resp.workspaceDirectory + + async def getHighlightedCode(self) -> List[RangeInFile]: + resp = await self._send_and_receive_json({ + "messageType": "highlightedCode" + }, HighlightedCodeResponse, "highlightedCode") + return resp.highlightedCode + + async def readFile(self, filepath: str) -> str: + """Read a file""" + resp = await self._send_and_receive_json({ + "messageType": "readFile", + "filepath": filepath + }, ReadFileResponse, "readFile") + return resp.contents + + async def saveFile(self, filepath: str): + """Save a file""" + await self._send_json({ + "messageType": "saveFile", + "filepath": filepath + }) + + async def readRangeInFile(self, range_in_file: RangeInFile) -> str: + """Read a range in a file""" + full_contents = await self.readFile(range_in_file.filepath) + return FileSystem.read_range_in_str(full_contents, range_in_file.range) + + async def editFile(self, edit: FileEdit) -> FileEditWithFullContents: + """Edit a file""" + resp = await self._send_and_receive_json({ + "messageType": "editFile", + "edit": edit.dict() + }, EditFileResponse, "editFile") + return resp.fileEdit + + async def applyFileSystemEdit(self, edit: FileSystemEdit) -> EditDiff: + """Apply a file edit""" + backward = None + fs = RealFileSystem() + if isinstance(edit, FileEdit): + file_edit = await self.editFile(edit) + _, diff = FileSystem.apply_edit_to_str( + file_edit.fileContents, file_edit.fileEdit) + backward = diff.backward + elif isinstance(edit, AddFile): + fs.write(edit.filepath, edit.content) + backward = DeleteFile(filepath=edit.filepath) + elif isinstance(edit, DeleteFile): + contents = await self.readFile(edit.filepath) + backward = AddFile(filepath=edit.filepath, content=contents) + fs.delete_file(edit.filepath) + elif isinstance(edit, RenameFile): + fs.rename_file(edit.filepath, edit.new_filepath) + backward = RenameFile(filepath=edit.new_filepath, + new_filepath=edit.filepath) + elif isinstance(edit, AddDirectory): + fs.add_directory(edit.path) + backward = DeleteDirectory(path=edit.path) + elif isinstance(edit, DeleteDirectory): + # This isn't atomic! + backward_edits = [] + for root, dirs, files in os.walk(edit.path, topdown=False): + for f in files: + path = os.path.join(root, f) + edit_diff = await self.applyFileSystemEdit(DeleteFile(filepath=path)) + backward_edits.append(edit_diff) + for d in dirs: + path = os.path.join(root, d) + edit_diff = await self.applyFileSystemEdit(DeleteDirectory(path=path)) + backward_edits.append(edit_diff) + + edit_diff = await self.applyFileSystemEdit(DeleteDirectory(path=edit.path)) + backward_edits.append(edit_diff) + backward_edits.reverse() + backward = SequentialFileSystemEdit(edits=backward_edits) + elif isinstance(edit, RenameDirectory): + fs.rename_directory(edit.path, edit.new_path) + backward = RenameDirectory(path=edit.new_path, new_path=edit.path) + elif isinstance(edit, FileSystemEdit): + diffs = [] + for edit in edit.next_edit(): + edit_diff = await self.applyFileSystemEdit(edit) + diffs.append(edit_diff) + backward = EditDiff.from_sequence(diffs=diffs).backward + else: + raise TypeError("Unknown FileSystemEdit type: " + str(type(edit))) + + return EditDiff( + forward=edit, + backward=backward + ) + + +ideProtocolServer = IdeProtocolServer(session_manager) + + +@router.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket): + await websocket.accept() + print("Accepted websocket connection from, ", websocket.client) + await websocket.send_json({"messageType": "connected"}) + ideProtocolServer.websocket = websocket + while True: + data = await websocket.receive_json() + await ideProtocolServer.handle_json(data) + + await websocket.close() diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py new file mode 100644 index 00000000..15d019b4 --- /dev/null +++ b/continuedev/src/continuedev/server/ide_protocol.py @@ -0,0 +1,80 @@ +from typing import Any, List +from abc import ABC, abstractmethod + +from ..models.main import Traceback +from ..models.filesystem_edit import FileEdit, FileSystemEdit, EditDiff +from ..models.filesystem import RangeInFile + + +class AbstractIdeProtocolServer(ABC): + @abstractmethod + async def handle_json(self, data: Any): + """Handle a json message""" + + @abstractmethod + def showSuggestion(): + """Show a suggestion to the user""" + + @abstractmethod + async def getWorkspaceDirectory(self): + """Get the workspace directory""" + + @abstractmethod + async def setFileOpen(self, filepath: str, open: bool = True): + """Set whether a file is open""" + + @abstractmethod + async def openNotebook(self): + """Open a notebook""" + + @abstractmethod + async def showSuggestionsAndWait(self, suggestions: List[FileEdit]) -> bool: + """Show suggestions to the user and wait for a response""" + + @abstractmethod + def onAcceptRejectSuggestion(self, suggestionId: str, accepted: bool): + """Called when the user accepts or rejects a suggestion""" + + @abstractmethod + def onTraceback(self, traceback: Traceback): + """Called when a traceback is received""" + + @abstractmethod + def onFileSystemUpdate(self, update: FileSystemEdit): + """Called when a file system update is received""" + + @abstractmethod + def onCloseNotebook(self, session_id: str): + """Called when a notebook is closed""" + + @abstractmethod + def onOpenNotebookRequest(self): + """Called when a notebook is requested to be opened""" + + @abstractmethod + async def getOpenFiles(self) -> List[str]: + """Get a list of open files""" + + @abstractmethod + async def getHighlightedCode(self) -> List[RangeInFile]: + """Get a list of highlighted code""" + + @abstractmethod + async def readFile(self, filepath: str) -> str: + """Read a file""" + + @abstractmethod + async def readRangeInFile(self, range_in_file: RangeInFile) -> str: + """Read a range in a file""" + + @abstractmethod + async def editFile(self, edit: FileEdit): + """Edit a file""" + + @abstractmethod + async def applyFileSystemEdit(self, edit: FileSystemEdit) -> EditDiff: + """Apply a file edit""" + + @abstractmethod + async def saveFile(self, filepath: str): + """Save a file""" diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py new file mode 100644 index 00000000..11ad1d8f --- /dev/null +++ b/continuedev/src/continuedev/server/main.py @@ -0,0 +1,39 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from .ide import router as ide_router +from .notebook import router as notebook_router +import uvicorn +import argparse + +app = FastAPI() + +app.include_router(ide_router) +app.include_router(notebook_router) + +# Add CORS support +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/health") +def health(): + return {"status": "ok"} + + +# add cli arg for server port +parser = argparse.ArgumentParser() +parser.add_argument("-p", "--port", help="server port", type=int, default=8000) +args = parser.parse_args() + + +def run_server(): + uvicorn.run(app, host="0.0.0.0", port=args.port, log_config="logging.ini") + + +if __name__ == "__main__": + run_server() diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py new file mode 100644 index 00000000..c9d4edc5 --- /dev/null +++ b/continuedev/src/continuedev/server/notebook.py @@ -0,0 +1,198 @@ +from fastapi import FastAPI, Depends, Header, WebSocket, APIRouter +from typing import Any, Dict, List, Union +from uuid import uuid4 +from pydantic import BaseModel +from uvicorn.main import Server + +from ..models.filesystem_edit import FileEditWithFullContents +from ..libs.policy import DemoPolicy +from ..libs.core import Agent, FullState, History, Step +from ..libs.steps.nate import ImplementAbstractMethodStep +from ..libs.observation import Observation +from dotenv import load_dotenv +from ..libs.llm.openai import OpenAI +from .ide_protocol import AbstractIdeProtocolServer +import os +import asyncio +import nest_asyncio +nest_asyncio.apply() + +load_dotenv() +openai_api_key = os.getenv("OPENAI_API_KEY") + +router = APIRouter(prefix="/notebook", tags=["notebook"]) + +# Graceful shutdown by closing websockets +original_handler = Server.handle_exit + + +class AppStatus: + should_exit = False + + @staticmethod + def handle_exit(*args, **kwargs): + AppStatus.should_exit = True + print("Shutting down") + original_handler(*args, **kwargs) + + +Server.handle_exit = AppStatus.handle_exit + + +class Session: + session_id: str + agent: Agent + ws: Union[WebSocket, None] + + def __init__(self, session_id: str, agent: Agent): + self.session_id = session_id + self.agent = agent + self.ws = None + + +class DemoAgent(Agent): + first_seen: bool = False + cumulative_edit_string = "" + + def handle_manual_edits(self, edits: List[FileEditWithFullContents]): + for edit in edits: + self.cumulative_edit_string += edit.fileEdit.replacement + self._manual_edits_buffer.append(edit) + # Note that you're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. + # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) + # FOR DEMO PURPOSES + if edit.fileEdit.filepath.endswith("filesystem.py") and "List" in self.cumulative_edit_string and ":" in edit.fileEdit.replacement: + self.cumulative_edit_string = "" + asyncio.create_task(self.run_from_step( + ImplementAbstractMethodStep())) + + +class SessionManager: + sessions: Dict[str, Session] = {} + _event_loop: Union[asyncio.BaseEventLoop, None] = None + + def get_session(self, session_id: str) -> Session: + if session_id not in self.sessions: + raise KeyError("Session ID not recognized") + return self.sessions[session_id] + + def new_session(self, ide: AbstractIdeProtocolServer) -> str: + cmd = "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py" + agent = DemoAgent(llm=OpenAI(api_key=openai_api_key), + policy=DemoPolicy(cmd=cmd), ide=ide) + session_id = str(uuid4()) + session = Session(session_id=session_id, agent=agent) + self.sessions[session_id] = session + + def on_update(state: FullState): + session_manager.send_ws_data(session_id, { + "messageType": "state", + "state": agent.get_full_state().dict() + }) + + agent.on_update(on_update) + asyncio.create_task(agent.run_policy()) + return session_id + + def remove_session(self, session_id: str): + del self.sessions[session_id] + + def register_websocket(self, session_id: str, ws: WebSocket): + self.sessions[session_id].ws = ws + print("Registered websocket for session", session_id) + + def send_ws_data(self, session_id: str, data: Any): + if self.sessions[session_id].ws is None: + print(f"Session {session_id} has no websocket") + return + + async def a(): + await self.sessions[session_id].ws.send_json(data) + + # Run coroutine in background + if self._event_loop is None or self._event_loop.is_closed(): + self._event_loop = asyncio.new_event_loop() + self._event_loop.run_until_complete(a()) + self._event_loop.close() + else: + self._event_loop.run_until_complete(a()) + self._event_loop.close() + + +session_manager = SessionManager() + + +def session(x_continue_session_id: str = Header("anonymous")) -> Session: + return session_manager.get_session(x_continue_session_id) + + +def websocket_session(session_id: str) -> Session: + return session_manager.get_session(session_id) + + +class StartSessionBody(BaseModel): + config_file_path: Union[str, None] + + +class StartSessionResp(BaseModel): + session_id: str + + +@router.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)): + await websocket.accept() + + session_manager.register_websocket(session.session_id, websocket) + data = await websocket.receive_text() + # Update any history that may have happened before connection + await websocket.send_json({ + "messageType": "state", + "state": session_manager.get_session(session.session_id).agent.get_full_state().dict() + }) + print("Session started", data) + while AppStatus.should_exit is False: + data = await websocket.receive_json() + print("Received data", data) + + if "messageType" not in data: + continue + messageType = data["messageType"] + + try: + if messageType == "main_input": + # Do something with user input + asyncio.create_task( + session.agent.accept_user_input(data["value"])) + elif messageType == "step_user_input": + asyncio.create_task( + session.agent.give_user_input(data["value"], data["index"])) + elif messageType == "refinement_input": + asyncio.create_task( + session.agent.accept_refinement_input(data["value"], data["index"])) + elif messageType == "reverse": + # Reverse the history to the given index + asyncio.create_task( + session.agent.reverse_to_index(data["index"])) + except Exception as e: + print(e) + + print("Closing websocket") + await websocket.close() + + +@router.post("/run") +def request_run(step: Step, session=Depends(session)): + """Tell an agent to take a specific action.""" + asyncio.create_task(session.agent.run_from_step(step)) + return "Success" + + +@router.get("/history") +def get_history(session=Depends(session)) -> History: + return session.agent.history + + +@router.post("/observation") +def post_observation(observation: Observation, session=Depends(session)): + asyncio.create_task(session.agent.run_from_observation(observation)) + return "Success" -- cgit v1.2.3-70-g09d2 From 1e9bd40415488760a52957cbb015a1c3c4e1383a Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 27 May 2023 00:11:28 -0400 Subject: Refactoring to deal with circularity --- continuedev/src/continuedev/libs/agent.py | 180 +++++++++++++ continuedev/src/continuedev/libs/core.py | 289 +-------------------- continuedev/src/continuedev/libs/env.py | 7 + continuedev/src/continuedev/libs/sdk.py | 39 +++ .../src/continuedev/libs/steps/core/core.py | 91 +++++++ continuedev/src/continuedev/libs/steps/main.py | 25 ++ continuedev/src/continuedev/models/main.py | 5 + continuedev/src/continuedev/server/notebook.py | 9 +- 8 files changed, 357 insertions(+), 288 deletions(-) create mode 100644 continuedev/src/continuedev/libs/agent.py create mode 100644 continuedev/src/continuedev/libs/env.py create mode 100644 continuedev/src/continuedev/libs/sdk.py create mode 100644 continuedev/src/continuedev/libs/steps/core/core.py (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/libs/agent.py b/continuedev/src/continuedev/libs/agent.py new file mode 100644 index 00000000..466aa234 --- /dev/null +++ b/continuedev/src/continuedev/libs/agent.py @@ -0,0 +1,180 @@ +import traceback +import time +from typing import Callable, Coroutine, List +from ..models.filesystem_edit import FileEditWithFullContents +from .llm import LLM +from .observation import Observation +from ..server.ide_protocol import AbstractIdeProtocolServer +from .util.queue import AsyncSubscriptionQueue +from ..models.main import ContinueBaseModel +from .core import Policy, History, FullState, Step, HistoryNode +from .steps.core.core import ReversibleStep, ManualEditStep, UserInputStep +from .sdk import ContinueSDK + + +class Agent(ContinueBaseModel): + llm: LLM + policy: Policy + ide: AbstractIdeProtocolServer + history: History = History.from_empty() + continue_sdk: "ContinueSDK" + _on_update_callbacks: List[Callable[[FullState], None]] = [] + + _active: bool = False + _should_halt: bool = False + _main_user_input_queue: List[str] = [] + + _user_input_queue = AsyncSubscriptionQueue() + + class Config: + arbitrary_types_allowed = True + + def get_full_state(self) -> FullState: + return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) + + def on_update(self, callback: Callable[["FullState"], None]): + """Subscribe to changes to state""" + self._on_update_callbacks.append(callback) + + def update_subscribers(self): + full_state = self.get_full_state() + for callback in self._on_update_callbacks: + callback(full_state) + + def __get_step_params(self, step: "Step"): + return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message)) + + def give_user_input(self, input: str, index: int): + self._user_input_queue.post(index, input) + + async def wait_for_user_input(self) -> str: + self._active = False + self.update_subscribers() + await self._user_input_queue.get(self.history.current_index) + self._active = True + self.update_subscribers() + + _manual_edits_buffer: List[FileEditWithFullContents] = [] + + async def reverse_to_index(self, index: int): + try: + while self.history.get_current_index() >= index: + current_step = self.history.get_current().step + self.history.step_back() + if issubclass(current_step.__class__, ReversibleStep): + await current_step.reverse(self.__get_step_params(current_step)) + + self.update_subscribers() + except Exception as e: + print(e) + + def handle_manual_edits(self, edits: List[FileEditWithFullContents]): + for edit in edits: + self._manual_edits_buffer.append(edit) + # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. + # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) + + def handle_traceback(self, traceback: str): + raise NotImplementedError + + _step_depth: int = 0 + + async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: + if not is_future_step: + # Check manual edits buffer, clear out if needed by creating a ManualEditStep + if len(self._manual_edits_buffer) > 0: + manualEditsStep = ManualEditStep.from_sequence( + self._manual_edits_buffer) + self._manual_edits_buffer = [] + await self._run_singular_step(manualEditsStep) + + # Update history - do this first so we get top-first tree ordering + self.history.add_node(HistoryNode( + step=step, observation=None, depth=self._step_depth)) + + # Run step + self._step_depth += 1 + observation = await step(self.__get_step_params(step)) + self._step_depth -= 1 + + # Add observation to history + self.history.get_current().observation = observation + + # Update its description + step._set_description(await step.describe(self.llm)) + + # Call all subscribed callbacks + self.update_subscribers() + + return observation + + async def run_from_step(self, step: "Step"): + # if self._active: + # raise RuntimeError("Agent is already running") + self._active = True + + next_step = step + is_future_step = False + while not (next_step is None or self._should_halt): + try: + if is_future_step: + # If future step, then we are replaying and need to delete the step from history so it can be replaced + self.history.remove_current_and_substeps() + + observation = await self._run_singular_step(next_step, is_future_step) + if next_step := self.policy.next(self.history): + is_future_step = False + elif next_step := self.history.take_next_step(): + is_future_step = True + else: + next_step = None + + except Exception as e: + print( + f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") + next_step = None + + self._active = False + + # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools + for callback in self._on_update_callbacks: + callback(None) + + async def run_from_observation(self, observation: Observation): + next_step = self.policy.next(self.history) + await self.run_from_step(next_step) + + async def run_policy(self): + first_step = self.policy.next(self.history) + await self.run_from_step(first_step) + + async def _request_halt(self): + if self._active: + self._should_halt = True + while self._active: + time.sleep(0.1) + self._should_halt = False + return None + + async def accept_user_input(self, user_input: str): + self._main_user_input_queue.append(user_input) + self.update_subscribers() + + if len(self._main_user_input_queue) > 1: + return + + # await self._request_halt() + # Just run the step that takes user input, and + # then up to the policy to decide how to deal with it. + self._main_user_input_queue.pop(0) + self.update_subscribers() + await self.run_from_step(UserInputStep(user_input=user_input)) + + while len(self._main_user_input_queue) > 0: + await self.run_from_step(UserInputStep( + user_input=self._main_user_input_queue.pop(0))) + + async def accept_refinement_input(self, user_input: str, index: int): + await self._request_halt() + await self.reverse_to_index(index) + await self.run_from_step(UserInputStep(user_input=user_input)) diff --git a/continuedev/src/continuedev/libs/core.py b/continuedev/src/continuedev/libs/core.py index 6a8a83ba..9d432c4b 100644 --- a/continuedev/src/continuedev/libs/core.py +++ b/continuedev/src/continuedev/libs/core.py @@ -1,18 +1,9 @@ -import traceback -import time from typing import Callable, Coroutine, Dict, Generator, List, Tuple, Union -from ..models.filesystem_edit import EditDiff, FileEdit, FileEditWithFullContents, FileSystemEdit -from ..models.filesystem import FileSystem -from pydantic import BaseModel, parse_file_as, validator -from .llm import LLM -from .observation import Observation, UserInputObservation -from ..server.ide_protocol import AbstractIdeProtocolServer -from .util.queue import AsyncSubscriptionQueue - -class ContinueBaseModel(BaseModel): - class Config: - underscore_attrs_are_private = True +from ..models.main import ContinueBaseModel +from pydantic import validator +from .llm import LLM +from .observation import Observation class HistoryNode(ContinueBaseModel): @@ -86,198 +77,11 @@ class Policy(ContinueBaseModel): class ContinueSDK: - """The SDK provided as parameters to a step""" - llm: LLM - ide: AbstractIdeProtocolServer - __agent: "Agent" - - def __init__(self, agent: "Agent", llm: Union[LLM, None] = None): - if llm is None: - self.llm = agent.llm - else: - self.llm = llm - self.ide = agent.ide - self.__agent = agent - - @property - def history(self) -> History: - return self.__agent.history - - async def run_step(self, step: "Step") -> Coroutine[Observation, None, None]: - return await self.__agent._run_singular_step(step) + pass - async def apply_filesystem_edit(self, edit: FileSystemEdit): - await self.run_step(FileSystemEditStep(edit=edit)) - async def wait_for_user_input(self) -> str: - return await self.__agent.wait_for_user_input() - - -class Agent(ContinueBaseModel): - llm: LLM - policy: Policy - ide: AbstractIdeProtocolServer - history: History = History.from_empty() - _on_update_callbacks: List[Callable[["FullState"], None]] = [] - - _active: bool = False - _should_halt: bool = False - _main_user_input_queue: List[str] = [] - - _user_input_queue = AsyncSubscriptionQueue() - - class Config: - arbitrary_types_allowed = True - - def get_full_state(self) -> FullState: - return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) - - def on_update(self, callback: Callable[["FullState"], None]): - """Subscribe to changes to state""" - self._on_update_callbacks.append(callback) - - def update_subscribers(self): - full_state = self.get_full_state() - for callback in self._on_update_callbacks: - callback(full_state) - - def __get_step_params(self, step: "Step"): - return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message)) - - def give_user_input(self, input: str, index: int): - self._user_input_queue.post(index, input) - - async def wait_for_user_input(self) -> str: - self._active = False - self.update_subscribers() - await self._user_input_queue.get(self.history.current_index) - self._active = True - self.update_subscribers() - - _manual_edits_buffer: List[FileEditWithFullContents] = [] - - async def reverse_to_index(self, index: int): - try: - while self.history.get_current_index() >= index: - current_step = self.history.get_current().step - self.history.step_back() - if issubclass(current_step.__class__, ReversibleStep): - await current_step.reverse(self.__get_step_params(current_step)) - - self.update_subscribers() - except Exception as e: - print(e) - - def handle_manual_edits(self, edits: List[FileEditWithFullContents]): - for edit in edits: - self._manual_edits_buffer.append(edit) - # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. - # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) - - def handle_traceback(self, traceback: str): - raise NotImplementedError - - _step_depth: int = 0 - - async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: - if not is_future_step: - # Check manual edits buffer, clear out if needed by creating a ManualEditStep - if len(self._manual_edits_buffer) > 0: - manualEditsStep = ManualEditStep.from_sequence( - self._manual_edits_buffer) - self._manual_edits_buffer = [] - await self._run_singular_step(manualEditsStep) - - # Update history - do this first so we get top-first tree ordering - self.history.add_node(HistoryNode( - step=step, observation=None, depth=self._step_depth)) - - # Run step - self._step_depth += 1 - observation = await step(self.__get_step_params(step)) - self._step_depth -= 1 - - # Add observation to history - self.history.get_current().observation = observation - - # Update its description - step._set_description(await step.describe(self.llm)) - - # Call all subscribed callbacks - self.update_subscribers() - - return observation - - async def run_from_step(self, step: "Step"): - # if self._active: - # raise RuntimeError("Agent is already running") - self._active = True - - next_step = step - is_future_step = False - while not (next_step is None or self._should_halt): - try: - if is_future_step: - # If future step, then we are replaying and need to delete the step from history so it can be replaced - self.history.remove_current_and_substeps() - - observation = await self._run_singular_step(next_step, is_future_step) - if next_step := self.policy.next(self.history): - is_future_step = False - elif next_step := self.history.take_next_step(): - is_future_step = True - else: - next_step = None - - except Exception as e: - print( - f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") - next_step = None - - self._active = False - - # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools - for callback in self._on_update_callbacks: - callback(None) - - async def run_from_observation(self, observation: Observation): - next_step = self.policy.next(self.history) - await self.run_from_step(next_step) - - async def run_policy(self): - first_step = self.policy.next(self.history) - await self.run_from_step(first_step) - - async def _request_halt(self): - if self._active: - self._should_halt = True - while self._active: - time.sleep(0.1) - self._should_halt = False - return None - - async def accept_user_input(self, user_input: str): - self._main_user_input_queue.append(user_input) - self.update_subscribers() - - if len(self._main_user_input_queue) > 1: - return - - # await self._request_halt() - # Just run the step that takes user input, and - # then up to the policy to decide how to deal with it. - self._main_user_input_queue.pop(0) - self.update_subscribers() - await self.run_from_step(UserInputStep(user_input=user_input)) - - while len(self._main_user_input_queue) > 0: - await self.run_from_step(UserInputStep( - user_input=self._main_user_input_queue.pop(0))) - - async def accept_refinement_input(self, user_input: str, index: int): - await self._request_halt() - await self.reverse_to_index(index) - await self.run_from_step(UserInputStep(user_input=user_input)) +class SequentialStep: + pass class Step(ContinueBaseModel): @@ -331,85 +135,6 @@ class Step(ContinueBaseModel): return SequentialStep(steps=steps) -class SequentialStep(Step): - steps: list[Step] - hide: bool = True - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - for step in self.steps: - observation = await sdk.run_step(step) - return observation - - -class ReversibleStep(Step): - async def reverse(self, sdk: ContinueSDK): - raise NotImplementedError - - -class FileSystemEditStep(ReversibleStep): - edit: FileSystemEdit - _diff: Union[EditDiff, None] = None - - hide: bool = True - - async def run(self, sdk: "ContinueSDK") -> Coroutine[Observation, None, None]: - self._diff = await sdk.ide.applyFileSystemEdit(self.edit) - return None - - async def reverse(self, sdk: "ContinueSDK"): - await sdk.ide.applyFileSystemEdit(self._diff.backward) - # Where and when should file saves happen? - - -class ManualEditStep(ReversibleStep): - edit_diff: EditDiff - hide: bool = True - - hide: bool = True - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return "Manual edit step" - # TODO - only handling FileEdit here, but need all other types of FileSystemEdits - # Also requires the merge_file_edit function - # return llm.complete(dedent(f"""This code was replaced: - - # {self.edit_diff.backward.replacement} - - # With this code: - - # {self.edit_diff.forward.replacement} - - # Maximally concise summary of changes in bullet points (can use markdown): - # """)) - - @classmethod - def from_sequence(cls, edits: List[FileEditWithFullContents]) -> "ManualEditStep": - diffs = [] - for edit in edits: - _, diff = FileSystem.apply_edit_to_str( - edit.fileContents, edit.fileEdit) - diffs.append(diff) - return cls(edit_diff=EditDiff.from_sequence(diffs)) - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - return None - - async def reverse(self, sdk: ContinueSDK): - await sdk.ide.applyFileSystemEdit(self.edit_diff.backward) - - -class UserInputStep(Step): - user_input: str - name: str = "User Input" - hide: bool = True - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return self.user_input - - async def run(self, sdk: ContinueSDK) -> Coroutine[UserInputObservation, None, None]: - return UserInputObservation(user_input=self.user_input) - - class ValidatorObservation(Observation): passed: bool observation: Observation diff --git a/continuedev/src/continuedev/libs/env.py b/continuedev/src/continuedev/libs/env.py new file mode 100644 index 00000000..d7275b41 --- /dev/null +++ b/continuedev/src/continuedev/libs/env.py @@ -0,0 +1,7 @@ +from dotenv import load_dotenv +import os + +load_dotenv() + + +openai_api_key = os.getenv("OPENAI_API_KEY") diff --git a/continuedev/src/continuedev/libs/sdk.py b/continuedev/src/continuedev/libs/sdk.py new file mode 100644 index 00000000..0295bf35 --- /dev/null +++ b/continuedev/src/continuedev/libs/sdk.py @@ -0,0 +1,39 @@ +from typing import Coroutine, Union +from ..models.filesystem_edit import FileSystemEdit +from .llm import LLM +from .observation import Observation +from ..server.ide_protocol import AbstractIdeProtocolServer +from .core import History, Step +from .steps.core.core import * + + +class Agent: + pass + + +class ContinueSDK: + """The SDK provided as parameters to a step""" + llm: LLM + ide: AbstractIdeProtocolServer + __agent: Agent + + def __init__(self, agent: Agent, llm: Union[LLM, None] = None): + if llm is None: + self.llm = agent.llm + else: + self.llm = llm + self.ide = agent.ide + self.__agent = agent + + @property + def history(self) -> History: + return self.__agent.history + + async def run_step(self, step: Step) -> Coroutine[Observation, None, None]: + return await self.__agent._run_singular_step(step) + + async def apply_filesystem_edit(self, edit: FileSystemEdit): + await self.run_step(FileSystemEditStep(edit=edit)) + + async def wait_for_user_input(self) -> str: + return await self.__agent.wait_for_user_input() diff --git a/continuedev/src/continuedev/libs/steps/core/core.py b/continuedev/src/continuedev/libs/steps/core/core.py new file mode 100644 index 00000000..23086b53 --- /dev/null +++ b/continuedev/src/continuedev/libs/steps/core/core.py @@ -0,0 +1,91 @@ +# These steps are depended upon by ContinueSDK +from typing import Callable, Coroutine, Dict, Generator, List, Tuple, Union + +from ....models.filesystem_edit import EditDiff, FileEditWithFullContents, FileSystemEdit +from ....models.filesystem import FileSystem +from ...llm import LLM +from ...observation import Observation, UserInputObservation +from ...core import Step + + +class ContinueSDK: + pass + + +class SequentialStep(Step): + steps: list[Step] + hide: bool = True + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + for step in self.steps: + observation = await sdk.run_step(step) + return observation + + +class ReversibleStep(Step): + async def reverse(self, sdk: ContinueSDK): + raise NotImplementedError + + +class FileSystemEditStep(ReversibleStep): + edit: FileSystemEdit + _diff: Union[EditDiff, None] = None + + hide: bool = True + + async def run(self, sdk: "ContinueSDK") -> Coroutine[Observation, None, None]: + self._diff = await sdk.ide.applyFileSystemEdit(self.edit) + return None + + async def reverse(self, sdk: "ContinueSDK"): + await sdk.ide.applyFileSystemEdit(self._diff.backward) + # Where and when should file saves happen? + + +class ManualEditStep(ReversibleStep): + edit_diff: EditDiff + hide: bool = True + + hide: bool = True + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return "Manual edit step" + # TODO - only handling FileEdit here, but need all other types of FileSystemEdits + # Also requires the merge_file_edit function + # return llm.complete(dedent(f"""This code was replaced: + + # {self.edit_diff.backward.replacement} + + # With this code: + + # {self.edit_diff.forward.replacement} + + # Maximally concise summary of changes in bullet points (can use markdown): + # """)) + + @classmethod + def from_sequence(cls, edits: List[FileEditWithFullContents]) -> "ManualEditStep": + diffs = [] + for edit in edits: + _, diff = FileSystem.apply_edit_to_str( + edit.fileContents, edit.fileEdit) + diffs.append(diff) + return cls(edit_diff=EditDiff.from_sequence(diffs)) + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + return None + + async def reverse(self, sdk: ContinueSDK): + await sdk.ide.applyFileSystemEdit(self.edit_diff.backward) + + +class UserInputStep(Step): + user_input: str + name: str = "User Input" + hide: bool = True + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return self.user_input + + async def run(self, sdk: ContinueSDK) -> Coroutine[UserInputObservation, None, None]: + return UserInputObservation(user_input=self.user_input) diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 70953e95..555c1180 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -51,6 +51,31 @@ class RunCommandStep(Step): return TextObservation(text=stdout) +def ShellCommandsStep(Step): + cmds: List[str] + name: str = "Run Shell Commands" + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return "\n".join(self.cmds) + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + cwd = await sdk.ide.getWorkspaceDirectory() + + process = subprocess.Popen( + '/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd) + + stdin_input = "\n".join(self.cmds) + out, err = process.communicate(stdin_input.encode()) + + # TODO: How to await?? + + # If it fails, return the error + if err is not None and err != "": + return TextObservation(text=err) + + return None + + class WaitForUserInputStep(Step): prompt: str name: str = "Waiting for user input" diff --git a/continuedev/src/continuedev/models/main.py b/continuedev/src/continuedev/models/main.py index 081ec4af..7986b30c 100644 --- a/continuedev/src/continuedev/models/main.py +++ b/continuedev/src/continuedev/models/main.py @@ -4,6 +4,11 @@ from pydantic import BaseModel, root_validator from functools import total_ordering +class ContinueBaseModel(BaseModel): + class Config: + underscore_attrs_are_private = True + + @total_ordering class Position(BaseModel): line: int diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index c9d4edc5..5eb151d7 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -6,20 +6,17 @@ from uvicorn.main import Server from ..models.filesystem_edit import FileEditWithFullContents from ..libs.policy import DemoPolicy -from ..libs.core import Agent, FullState, History, Step +from ..libs.core import FullState, History, Step +from ..libs.agent import Agent from ..libs.steps.nate import ImplementAbstractMethodStep from ..libs.observation import Observation -from dotenv import load_dotenv from ..libs.llm.openai import OpenAI from .ide_protocol import AbstractIdeProtocolServer -import os +from ..libs.env import openai_api_key import asyncio import nest_asyncio nest_asyncio.apply() -load_dotenv() -openai_api_key = os.getenv("OPENAI_API_KEY") - router = APIRouter(prefix="/notebook", tags=["notebook"]) # Graceful shutdown by closing websockets -- cgit v1.2.3-70-g09d2 From 393eb93545567b9a3d1b6cd60a69e88bc15cf28c Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 27 May 2023 14:47:33 -0400 Subject: Refactoring and shortcut functions to common steps --- continuedev/src/continuedev/core/agent.py | 180 +++++++++++++++++++++ continuedev/src/continuedev/core/env.py | 7 + continuedev/src/continuedev/core/main.py | 148 +++++++++++++++++ continuedev/src/continuedev/core/observation.py | 35 ++++ continuedev/src/continuedev/core/policy.py | 93 +++++++++++ continuedev/src/continuedev/core/sdk.py | 62 +++++++ continuedev/src/continuedev/libs/agent.py | 180 --------------------- continuedev/src/continuedev/libs/core.py | 148 ----------------- continuedev/src/continuedev/libs/env.py | 7 - continuedev/src/continuedev/libs/observation.py | 35 ---- continuedev/src/continuedev/libs/policy.py | 91 ----------- continuedev/src/continuedev/libs/sdk.py | 39 ----- continuedev/src/continuedev/libs/steps/chroma.py | 9 +- .../src/continuedev/libs/steps/core/core.py | 133 ++++++++++++++- .../libs/steps/draft/abstract_method.py | 3 +- .../src/continuedev/libs/steps/draft/dlt.py | 3 +- .../src/continuedev/libs/steps/draft/redux.py | 7 +- .../src/continuedev/libs/steps/draft/typeorm.py | 3 +- continuedev/src/continuedev/libs/steps/main.py | 174 +++----------------- .../src/continuedev/libs/steps/migration.py | 2 +- continuedev/src/continuedev/libs/steps/nate.py | 9 +- continuedev/src/continuedev/libs/steps/pytest.py | 2 +- continuedev/src/continuedev/libs/steps/ty.py | 8 +- .../continuedev/plugins/policy/libs/alternate.py | 4 +- .../src/continuedev/plugins/step/hookspecs.py | 4 +- continuedev/src/continuedev/server/notebook.py | 10 +- 26 files changed, 713 insertions(+), 683 deletions(-) create mode 100644 continuedev/src/continuedev/core/agent.py create mode 100644 continuedev/src/continuedev/core/env.py create mode 100644 continuedev/src/continuedev/core/main.py create mode 100644 continuedev/src/continuedev/core/observation.py create mode 100644 continuedev/src/continuedev/core/policy.py create mode 100644 continuedev/src/continuedev/core/sdk.py delete mode 100644 continuedev/src/continuedev/libs/agent.py delete mode 100644 continuedev/src/continuedev/libs/core.py delete mode 100644 continuedev/src/continuedev/libs/env.py delete mode 100644 continuedev/src/continuedev/libs/observation.py delete mode 100644 continuedev/src/continuedev/libs/policy.py delete mode 100644 continuedev/src/continuedev/libs/sdk.py (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py new file mode 100644 index 00000000..509a54b3 --- /dev/null +++ b/continuedev/src/continuedev/core/agent.py @@ -0,0 +1,180 @@ +import traceback +import time +from typing import Callable, Coroutine, List +from ..models.filesystem_edit import FileEditWithFullContents +from ..libs.llm import LLM +from .observation import Observation +from ..server.ide_protocol import AbstractIdeProtocolServer +from ..libs.util.queue import AsyncSubscriptionQueue +from ..models.main import ContinueBaseModel +from .main import Policy, History, FullState, Step, HistoryNode +from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep +from .sdk import ContinueSDK + + +class Agent(ContinueBaseModel): + llm: LLM + policy: Policy + ide: AbstractIdeProtocolServer + history: History = History.from_empty() + continue_sdk: "ContinueSDK" + _on_update_callbacks: List[Callable[[FullState], None]] = [] + + _active: bool = False + _should_halt: bool = False + _main_user_input_queue: List[str] = [] + + _user_input_queue = AsyncSubscriptionQueue() + + class Config: + arbitrary_types_allowed = True + + def get_full_state(self) -> FullState: + return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) + + def on_update(self, callback: Callable[["FullState"], None]): + """Subscribe to changes to state""" + self._on_update_callbacks.append(callback) + + def update_subscribers(self): + full_state = self.get_full_state() + for callback in self._on_update_callbacks: + callback(full_state) + + def __get_step_params(self, step: "Step"): + return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message)) + + def give_user_input(self, input: str, index: int): + self._user_input_queue.post(index, input) + + async def wait_for_user_input(self) -> str: + self._active = False + self.update_subscribers() + await self._user_input_queue.get(self.history.current_index) + self._active = True + self.update_subscribers() + + _manual_edits_buffer: List[FileEditWithFullContents] = [] + + async def reverse_to_index(self, index: int): + try: + while self.history.get_current_index() >= index: + current_step = self.history.get_current().step + self.history.step_back() + if issubclass(current_step.__class__, ReversibleStep): + await current_step.reverse(self.__get_step_params(current_step)) + + self.update_subscribers() + except Exception as e: + print(e) + + def handle_manual_edits(self, edits: List[FileEditWithFullContents]): + for edit in edits: + self._manual_edits_buffer.append(edit) + # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. + # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) + + def handle_traceback(self, traceback: str): + raise NotImplementedError + + _step_depth: int = 0 + + async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: + if not is_future_step: + # Check manual edits buffer, clear out if needed by creating a ManualEditStep + if len(self._manual_edits_buffer) > 0: + manualEditsStep = ManualEditStep.from_sequence( + self._manual_edits_buffer) + self._manual_edits_buffer = [] + await self._run_singular_step(manualEditsStep) + + # Update history - do this first so we get top-first tree ordering + self.history.add_node(HistoryNode( + step=step, observation=None, depth=self._step_depth)) + + # Run step + self._step_depth += 1 + observation = await step(self.__get_step_params(step)) + self._step_depth -= 1 + + # Add observation to history + self.history.get_current().observation = observation + + # Update its description + step._set_description(await step.describe(self.llm)) + + # Call all subscribed callbacks + self.update_subscribers() + + return observation + + async def run_from_step(self, step: "Step"): + # if self._active: + # raise RuntimeError("Agent is already running") + self._active = True + + next_step = step + is_future_step = False + while not (next_step is None or self._should_halt): + try: + if is_future_step: + # If future step, then we are replaying and need to delete the step from history so it can be replaced + self.history.remove_current_and_substeps() + + observation = await self._run_singular_step(next_step, is_future_step) + if next_step := self.policy.next(self.history): + is_future_step = False + elif next_step := self.history.take_next_step(): + is_future_step = True + else: + next_step = None + + except Exception as e: + print( + f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") + next_step = None + + self._active = False + + # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools + for callback in self._on_update_callbacks: + callback(None) + + async def run_from_observation(self, observation: Observation): + next_step = self.policy.next(self.history) + await self.run_from_step(next_step) + + async def run_policy(self): + first_step = self.policy.next(self.history) + await self.run_from_step(first_step) + + async def _request_halt(self): + if self._active: + self._should_halt = True + while self._active: + time.sleep(0.1) + self._should_halt = False + return None + + async def accept_user_input(self, user_input: str): + self._main_user_input_queue.append(user_input) + self.update_subscribers() + + if len(self._main_user_input_queue) > 1: + return + + # await self._request_halt() + # Just run the step that takes user input, and + # then up to the policy to decide how to deal with it. + self._main_user_input_queue.pop(0) + self.update_subscribers() + await self.run_from_step(UserInputStep(user_input=user_input)) + + while len(self._main_user_input_queue) > 0: + await self.run_from_step(UserInputStep( + user_input=self._main_user_input_queue.pop(0))) + + async def accept_refinement_input(self, user_input: str, index: int): + await self._request_halt() + await self.reverse_to_index(index) + await self.run_from_step(UserInputStep(user_input=user_input)) diff --git a/continuedev/src/continuedev/core/env.py b/continuedev/src/continuedev/core/env.py new file mode 100644 index 00000000..d7275b41 --- /dev/null +++ b/continuedev/src/continuedev/core/env.py @@ -0,0 +1,7 @@ +from dotenv import load_dotenv +import os + +load_dotenv() + + +openai_api_key = os.getenv("OPENAI_API_KEY") diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py new file mode 100644 index 00000000..51fcd299 --- /dev/null +++ b/continuedev/src/continuedev/core/main.py @@ -0,0 +1,148 @@ +from typing import Callable, Coroutine, Dict, Generator, List, Tuple, Union + +from ..models.main import ContinueBaseModel +from pydantic import validator +from ..libs.llm import LLM +from .observation import Observation + + +class HistoryNode(ContinueBaseModel): + """A point in history, a list of which make up History""" + step: "Step" + observation: Union[Observation, None] + depth: int + + +class History(ContinueBaseModel): + """A history of steps taken and their results""" + timeline: List[HistoryNode] + current_index: int + + def add_node(self, node: HistoryNode): + self.timeline.insert(self.current_index + 1, node) + self.current_index += 1 + + def get_current(self) -> Union[HistoryNode, None]: + if self.current_index < 0: + return None + return self.timeline[self.current_index] + + def remove_current_and_substeps(self): + self.timeline.pop(self.current_index) + while self.get_current() is not None and self.get_current().depth > 0: + self.timeline.pop(self.current_index) + + def take_next_step(self) -> Union["Step", None]: + if self.has_future(): + self.current_index += 1 + current_state = self.get_current() + if current_state is None: + return None + return current_state.step + return None + + def get_current_index(self) -> int: + return self.current_index + + def has_future(self) -> bool: + return self.current_index < len(self.timeline) - 1 + + def step_back(self): + self.current_index -= 1 + + def last_observation(self) -> Union[Observation, None]: + state = self.get_current() + if state is None: + return None + return state.observation + + @classmethod + def from_empty(cls): + return cls(timeline=[], current_index=-1) + + +class FullState(ContinueBaseModel): + """A full state of the program, including the history""" + history: History + active: bool + user_input_queue: List[str] + + +class ContinueSDK: + pass + + +class SequentialStep: + pass + + +class Policy(ContinueBaseModel): + """A rule that determines which step to take next""" + + # Note that history is mutable, kinda sus + def next(self, history: History = History.from_empty()) -> "Step": + raise NotImplementedError + + +class Step(ContinueBaseModel): + name: str = None + hide: bool = False + _description: Union[str, None] = None + + system_message: Union[str, None] = None + + class Config: + copy_on_model_validation = False + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + if self._description is not None: + return self._description + return "Running step: " + self.name + + def _set_description(self, description: str): + self._description = description + + def dict(self, *args, **kwargs): + d = super().dict(*args, **kwargs) + if self._description is not None: + d["description"] = self._description + else: + d["description"] = self.name + return d + + @validator("name", pre=True, always=True) + def name_is_class_name(cls, name): + if name is None: + return cls.__name__ + return name + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + raise NotImplementedError + + async def __call__(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + return await self.run(sdk) + + def __rshift__(self, other: "Step"): + steps = [] + if isinstance(self, SequentialStep): + steps = self.steps + else: + steps.append(self) + if isinstance(other, SequentialStep): + steps += other.steps + else: + steps.append(other) + return SequentialStep(steps=steps) + + +class ValidatorObservation(Observation): + passed: bool + observation: Observation + + +class Validator(Step): + def run(self, sdk: ContinueSDK) -> ValidatorObservation: + raise NotImplementedError + + +HistoryNode.update_forward_refs() diff --git a/continuedev/src/continuedev/core/observation.py b/continuedev/src/continuedev/core/observation.py new file mode 100644 index 00000000..fef04311 --- /dev/null +++ b/continuedev/src/continuedev/core/observation.py @@ -0,0 +1,35 @@ +from pydantic import BaseModel, validator +from ..models.main import Traceback + + +class Observation(BaseModel): + pass + + +class TracebackObservation(Observation): + traceback: Traceback + + +class ValidatorObservation(Observation): + passed: bool + + +class UserInputObservation(Observation): + user_input: str + + +class DictObservation(Observation): + values: dict + + def __getitem__(self, key): + return self.values[key] + + +class TextObservation(Observation): + text: str + + @validator("text", pre=True, always=True) + def text_not_none(cls, v): + if v is None: + return "" + return v diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py new file mode 100644 index 00000000..9e6abf14 --- /dev/null +++ b/continuedev/src/continuedev/core/policy.py @@ -0,0 +1,93 @@ +from typing import List, Tuple, Type + +from ..models.main import ContinueBaseModel + +from ..libs.steps.ty import CreatePipelineStep +from .main import Step, Validator, History, Policy +from .observation import Observation, TracebackObservation, UserInputObservation +from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep +from ..libs.steps.nate import WritePytestsStep, CreateTableStep +from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma + + +class DemoPolicy(Policy): + ran_code_last: bool = False + cmd: str + + def next(self, history: History) -> Step: + observation = history.last_observation() + if observation is not None and isinstance(observation, UserInputObservation): + # This could be defined with ObservationTypePolicy. Ergonomics not right though. + if " test" in observation.user_input.lower(): + return WritePytestsStep(instructions=observation.user_input) + elif "/dlt" in observation.user_input.lower() or " dlt" in observation.user_input.lower(): + return CreatePipelineStep() + elif "/table" in observation.user_input: + return CreateTableStep(sql_str=" ".join(observation.user_input.split(" ")[1:])) + elif "/ask" in observation.user_input: + return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) + elif "/edit" in observation.user_input: + return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) + return EditHighlightedCodeStep(user_input=observation.user_input) + + state = history.get_current() + if state is None or not self.ran_code_last: + self.ran_code_last = True + return RunCodeStep(cmd=self.cmd) + + if observation is not None and isinstance(observation, TracebackObservation): + self.ran_code_last = False + return SolveTracebackStep(traceback=observation.traceback) + else: + return None + + +class ObservationTypePolicy(Policy): + def __init__(self, base_policy: Policy, observation_type: Type[Observation], step_type: Type[Step]): + self.observation_type = observation_type + self.step_type = step_type + self.base_policy = base_policy + + def next(self, history: History) -> Step: + observation = history.last_observation() + if observation is not None and isinstance(observation, self.observation_type): + return self.step_type(observation) + return self.base_policy.next(history) + + +class PolicyWrappedWithValidators(Policy): + """Default is to stop, unless the validator tells what to do next""" + index: int + stage: int + + def __init__(self, base_policy: Policy, pairs: List[Tuple[Validator, Type[Step]]]): + # Want to pass Type[Validator], or just the Validator? Question of where params are coming from. + self.pairs = pairs + self.index = len(pairs) + self.validating = 0 + self.base_policy = base_policy + + def next(self, history: History) -> Step: + if self.index == len(self.pairs): + self.index = 0 + return self.base_policy.next(history) + + if self.stage == 0: + # Running the validator at the current index for the first time + validator, step = self.pairs[self.index] + self.stage = 1 + return validator + elif self.stage == 1: + # Previously ran the validator at the current index, now receiving its ValidatorObservation + observation = history.last_observation() + if observation.passed: + self.stage = 0 + self.index += 1 + if self.index == len(self.pairs): + self.index = 0 + return self.base_policy.next(history) + else: + return self.pairs[self.index][0] + else: + _, step_type = self.pairs[self.index] + return step_type(observation) diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py new file mode 100644 index 00000000..ff62a2b1 --- /dev/null +++ b/continuedev/src/continuedev/core/sdk.py @@ -0,0 +1,62 @@ +from typing import Coroutine, Union +from ..models.filesystem_edit import FileSystemEdit +from ..models.filesystem import RangeInFile +from ..libs.llm import LLM +from .observation import Observation +from ..server.ide_protocol import AbstractIdeProtocolServer +from .main import History, Step +from ..libs.steps.core.core import * + + +class Agent: + pass + + +class ContinueSDKSteps: + def __init__(self, sdk: "ContinueSDK"): + self.sdk = sdk + + +class ContinueSDK: + """The SDK provided as parameters to a step""" + llm: LLM + ide: AbstractIdeProtocolServer + steps: ContinueSDKSteps + __agent: Agent + + def __init__(self, agent: Agent, llm: Union[LLM, None] = None): + if llm is None: + self.llm = agent.llm + else: + self.llm = llm + self.ide = agent.ide + self.__agent = agent + self.steps = ContinueSDKSteps(self) + + @property + def history(self) -> History: + return self.__agent.history + + async def run_step(self, step: Step) -> Coroutine[Observation, None, None]: + return await self.__agent._run_singular_step(step) + + async def apply_filesystem_edit(self, edit: FileSystemEdit): + await self.run_step(FileSystemEditStep(edit=edit)) + + async def wait_for_user_input(self) -> str: + return await self.__agent.wait_for_user_input() + + async def wait_for_user_confirmation(self, prompt: str): + return await self.run_step(WaitForUserConfirmationStep(prompt=prompt)) + + async def run(self, commands: List[str] | str, cwd: str = None): + commands = commands if isinstance(commands, List) else [commands] + return self.run_step(ShellCommandsStep(commands=commands, cwd=cwd)) + + async def edit_file(self, filename: str, prompt: str): + await self.ide.setFileOpen(filename) + contents = await self.ide.readFile(filename) + await self.run_step(EditCodeStep( + range_in_files=[RangeInFile.from_entire_file(filename, contents)], + prompt=f'Here is the code before:\n\n{{code}}\n\nHere is the user request:\n\n{prompt}\n\nHere is the code edited to perfectly solve the user request:\n\n' + )) diff --git a/continuedev/src/continuedev/libs/agent.py b/continuedev/src/continuedev/libs/agent.py deleted file mode 100644 index 466aa234..00000000 --- a/continuedev/src/continuedev/libs/agent.py +++ /dev/null @@ -1,180 +0,0 @@ -import traceback -import time -from typing import Callable, Coroutine, List -from ..models.filesystem_edit import FileEditWithFullContents -from .llm import LLM -from .observation import Observation -from ..server.ide_protocol import AbstractIdeProtocolServer -from .util.queue import AsyncSubscriptionQueue -from ..models.main import ContinueBaseModel -from .core import Policy, History, FullState, Step, HistoryNode -from .steps.core.core import ReversibleStep, ManualEditStep, UserInputStep -from .sdk import ContinueSDK - - -class Agent(ContinueBaseModel): - llm: LLM - policy: Policy - ide: AbstractIdeProtocolServer - history: History = History.from_empty() - continue_sdk: "ContinueSDK" - _on_update_callbacks: List[Callable[[FullState], None]] = [] - - _active: bool = False - _should_halt: bool = False - _main_user_input_queue: List[str] = [] - - _user_input_queue = AsyncSubscriptionQueue() - - class Config: - arbitrary_types_allowed = True - - def get_full_state(self) -> FullState: - return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) - - def on_update(self, callback: Callable[["FullState"], None]): - """Subscribe to changes to state""" - self._on_update_callbacks.append(callback) - - def update_subscribers(self): - full_state = self.get_full_state() - for callback in self._on_update_callbacks: - callback(full_state) - - def __get_step_params(self, step: "Step"): - return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message)) - - def give_user_input(self, input: str, index: int): - self._user_input_queue.post(index, input) - - async def wait_for_user_input(self) -> str: - self._active = False - self.update_subscribers() - await self._user_input_queue.get(self.history.current_index) - self._active = True - self.update_subscribers() - - _manual_edits_buffer: List[FileEditWithFullContents] = [] - - async def reverse_to_index(self, index: int): - try: - while self.history.get_current_index() >= index: - current_step = self.history.get_current().step - self.history.step_back() - if issubclass(current_step.__class__, ReversibleStep): - await current_step.reverse(self.__get_step_params(current_step)) - - self.update_subscribers() - except Exception as e: - print(e) - - def handle_manual_edits(self, edits: List[FileEditWithFullContents]): - for edit in edits: - self._manual_edits_buffer.append(edit) - # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. - # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) - - def handle_traceback(self, traceback: str): - raise NotImplementedError - - _step_depth: int = 0 - - async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: - if not is_future_step: - # Check manual edits buffer, clear out if needed by creating a ManualEditStep - if len(self._manual_edits_buffer) > 0: - manualEditsStep = ManualEditStep.from_sequence( - self._manual_edits_buffer) - self._manual_edits_buffer = [] - await self._run_singular_step(manualEditsStep) - - # Update history - do this first so we get top-first tree ordering - self.history.add_node(HistoryNode( - step=step, observation=None, depth=self._step_depth)) - - # Run step - self._step_depth += 1 - observation = await step(self.__get_step_params(step)) - self._step_depth -= 1 - - # Add observation to history - self.history.get_current().observation = observation - - # Update its description - step._set_description(await step.describe(self.llm)) - - # Call all subscribed callbacks - self.update_subscribers() - - return observation - - async def run_from_step(self, step: "Step"): - # if self._active: - # raise RuntimeError("Agent is already running") - self._active = True - - next_step = step - is_future_step = False - while not (next_step is None or self._should_halt): - try: - if is_future_step: - # If future step, then we are replaying and need to delete the step from history so it can be replaced - self.history.remove_current_and_substeps() - - observation = await self._run_singular_step(next_step, is_future_step) - if next_step := self.policy.next(self.history): - is_future_step = False - elif next_step := self.history.take_next_step(): - is_future_step = True - else: - next_step = None - - except Exception as e: - print( - f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") - next_step = None - - self._active = False - - # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools - for callback in self._on_update_callbacks: - callback(None) - - async def run_from_observation(self, observation: Observation): - next_step = self.policy.next(self.history) - await self.run_from_step(next_step) - - async def run_policy(self): - first_step = self.policy.next(self.history) - await self.run_from_step(first_step) - - async def _request_halt(self): - if self._active: - self._should_halt = True - while self._active: - time.sleep(0.1) - self._should_halt = False - return None - - async def accept_user_input(self, user_input: str): - self._main_user_input_queue.append(user_input) - self.update_subscribers() - - if len(self._main_user_input_queue) > 1: - return - - # await self._request_halt() - # Just run the step that takes user input, and - # then up to the policy to decide how to deal with it. - self._main_user_input_queue.pop(0) - self.update_subscribers() - await self.run_from_step(UserInputStep(user_input=user_input)) - - while len(self._main_user_input_queue) > 0: - await self.run_from_step(UserInputStep( - user_input=self._main_user_input_queue.pop(0))) - - async def accept_refinement_input(self, user_input: str, index: int): - await self._request_halt() - await self.reverse_to_index(index) - await self.run_from_step(UserInputStep(user_input=user_input)) diff --git a/continuedev/src/continuedev/libs/core.py b/continuedev/src/continuedev/libs/core.py deleted file mode 100644 index 9d432c4b..00000000 --- a/continuedev/src/continuedev/libs/core.py +++ /dev/null @@ -1,148 +0,0 @@ -from typing import Callable, Coroutine, Dict, Generator, List, Tuple, Union - -from ..models.main import ContinueBaseModel -from pydantic import validator -from .llm import LLM -from .observation import Observation - - -class HistoryNode(ContinueBaseModel): - """A point in history, a list of which make up History""" - step: "Step" - observation: Union[Observation, None] - depth: int - - -class History(ContinueBaseModel): - """A history of steps taken and their results""" - timeline: List[HistoryNode] - current_index: int - - def add_node(self, node: HistoryNode): - self.timeline.insert(self.current_index + 1, node) - self.current_index += 1 - - def get_current(self) -> Union[HistoryNode, None]: - if self.current_index < 0: - return None - return self.timeline[self.current_index] - - def remove_current_and_substeps(self): - self.timeline.pop(self.current_index) - while self.get_current() is not None and self.get_current().depth > 0: - self.timeline.pop(self.current_index) - - def take_next_step(self) -> Union["Step", None]: - if self.has_future(): - self.current_index += 1 - current_state = self.get_current() - if current_state is None: - return None - return current_state.step - return None - - def get_current_index(self) -> int: - return self.current_index - - def has_future(self) -> bool: - return self.current_index < len(self.timeline) - 1 - - def step_back(self): - self.current_index -= 1 - - def last_observation(self) -> Union[Observation, None]: - state = self.get_current() - if state is None: - return None - return state.observation - - @classmethod - def from_empty(cls): - return cls(timeline=[], current_index=-1) - - -class FullState(ContinueBaseModel): - """A full state of the program, including the history""" - history: History - active: bool - user_input_queue: List[str] - - -class Policy(ContinueBaseModel): - """A rule that determines which step to take next""" - - # Note that history is mutable, kinda sus - def next(self, history: History = History.from_empty()) -> "Step": - raise NotImplementedError - - -class ContinueSDK: - pass - - -class SequentialStep: - pass - - -class Step(ContinueBaseModel): - name: str = None - hide: bool = False - _description: Union[str, None] = None - - system_message: Union[str, None] = None - - class Config: - copy_on_model_validation = False - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - if self._description is not None: - return self._description - return "Running step: " + self.name - - def _set_description(self, description: str): - self._description = description - - def dict(self, *args, **kwargs): - d = super().dict(*args, **kwargs) - if self._description is not None: - d["description"] = self._description - else: - d["description"] = self.name - return d - - @validator("name", pre=True, always=True) - def name_is_class_name(cls, name): - if name is None: - return cls.__name__ - return name - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - raise NotImplementedError - - async def __call__(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - return await self.run(sdk) - - def __rshift__(self, other: "Step"): - steps = [] - if isinstance(self, SequentialStep): - steps = self.steps - else: - steps.append(self) - if isinstance(other, SequentialStep): - steps += other.steps - else: - steps.append(other) - return SequentialStep(steps=steps) - - -class ValidatorObservation(Observation): - passed: bool - observation: Observation - - -class Validator(Step): - def run(self, sdk: ContinueSDK) -> ValidatorObservation: - raise NotImplementedError - - -HistoryNode.update_forward_refs() diff --git a/continuedev/src/continuedev/libs/env.py b/continuedev/src/continuedev/libs/env.py deleted file mode 100644 index d7275b41..00000000 --- a/continuedev/src/continuedev/libs/env.py +++ /dev/null @@ -1,7 +0,0 @@ -from dotenv import load_dotenv -import os - -load_dotenv() - - -openai_api_key = os.getenv("OPENAI_API_KEY") diff --git a/continuedev/src/continuedev/libs/observation.py b/continuedev/src/continuedev/libs/observation.py deleted file mode 100644 index fef04311..00000000 --- a/continuedev/src/continuedev/libs/observation.py +++ /dev/null @@ -1,35 +0,0 @@ -from pydantic import BaseModel, validator -from ..models.main import Traceback - - -class Observation(BaseModel): - pass - - -class TracebackObservation(Observation): - traceback: Traceback - - -class ValidatorObservation(Observation): - passed: bool - - -class UserInputObservation(Observation): - user_input: str - - -class DictObservation(Observation): - values: dict - - def __getitem__(self, key): - return self.values[key] - - -class TextObservation(Observation): - text: str - - @validator("text", pre=True, always=True) - def text_not_none(cls, v): - if v is None: - return "" - return v diff --git a/continuedev/src/continuedev/libs/policy.py b/continuedev/src/continuedev/libs/policy.py deleted file mode 100644 index 586eaebe..00000000 --- a/continuedev/src/continuedev/libs/policy.py +++ /dev/null @@ -1,91 +0,0 @@ -from typing import List, Tuple, Type - -from .steps.ty import CreatePipelineStep -from .core import Step, Validator, Policy, History -from .observation import Observation, TracebackObservation, UserInputObservation -from .steps.main import EditCodeStep, EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep -from .steps.nate import WritePytestsStep, CreateTableStep -from .steps.chroma import AnswerQuestionChroma, EditFileChroma - - -class DemoPolicy(Policy): - ran_code_last: bool = False - cmd: str - - def next(self, history: History) -> Step: - observation = history.last_observation() - if observation is not None and isinstance(observation, UserInputObservation): - # This could be defined with ObservationTypePolicy. Ergonomics not right though. - if " test" in observation.user_input.lower(): - return WritePytestsStep(instructions=observation.user_input) - elif "/dlt" in observation.user_input.lower() or " dlt" in observation.user_input.lower(): - return CreatePipelineStep() - elif "/table" in observation.user_input: - return CreateTableStep(sql_str=" ".join(observation.user_input.split(" ")[1:])) - elif "/ask" in observation.user_input: - return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) - elif "/edit" in observation.user_input: - return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) - return EditHighlightedCodeStep(user_input=observation.user_input) - - state = history.get_current() - if state is None or not self.ran_code_last: - self.ran_code_last = True - return RunCodeStep(cmd=self.cmd) - - if observation is not None and isinstance(observation, TracebackObservation): - self.ran_code_last = False - return SolveTracebackStep(traceback=observation.traceback) - else: - return None - - -class ObservationTypePolicy(Policy): - def __init__(self, base_policy: Policy, observation_type: Type[Observation], step_type: Type[Step]): - self.observation_type = observation_type - self.step_type = step_type - self.base_policy = base_policy - - def next(self, history: History) -> Step: - observation = history.last_observation() - if observation is not None and isinstance(observation, self.observation_type): - return self.step_type(observation) - return self.base_policy.next(history) - - -class PolicyWrappedWithValidators(Policy): - """Default is to stop, unless the validator tells what to do next""" - index: int - stage: int - - def __init__(self, base_policy: Policy, pairs: List[Tuple[Validator, Type[Step]]]): - # Want to pass Type[Validator], or just the Validator? Question of where params are coming from. - self.pairs = pairs - self.index = len(pairs) - self.validating = 0 - self.base_policy = base_policy - - def next(self, history: History) -> Step: - if self.index == len(self.pairs): - self.index = 0 - return self.base_policy.next(history) - - if self.stage == 0: - # Running the validator at the current index for the first time - validator, step = self.pairs[self.index] - self.stage = 1 - return validator - elif self.stage == 1: - # Previously ran the validator at the current index, now receiving its ValidatorObservation - observation = history.last_observation() - if observation.passed: - self.stage = 0 - self.index += 1 - if self.index == len(self.pairs): - self.index = 0 - return self.base_policy.next(history) - else: - return self.pairs[self.index][0] - else: - _, step_type = self.pairs[self.index] - return step_type(observation) diff --git a/continuedev/src/continuedev/libs/sdk.py b/continuedev/src/continuedev/libs/sdk.py deleted file mode 100644 index 0295bf35..00000000 --- a/continuedev/src/continuedev/libs/sdk.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Coroutine, Union -from ..models.filesystem_edit import FileSystemEdit -from .llm import LLM -from .observation import Observation -from ..server.ide_protocol import AbstractIdeProtocolServer -from .core import History, Step -from .steps.core.core import * - - -class Agent: - pass - - -class ContinueSDK: - """The SDK provided as parameters to a step""" - llm: LLM - ide: AbstractIdeProtocolServer - __agent: Agent - - def __init__(self, agent: Agent, llm: Union[LLM, None] = None): - if llm is None: - self.llm = agent.llm - else: - self.llm = llm - self.ide = agent.ide - self.__agent = agent - - @property - def history(self) -> History: - return self.__agent.history - - async def run_step(self, step: Step) -> Coroutine[Observation, None, None]: - return await self.__agent._run_singular_step(step) - - async def apply_filesystem_edit(self, edit: FileSystemEdit): - await self.run_step(FileSystemEditStep(edit=edit)) - - async def wait_for_user_input(self) -> str: - return await self.__agent.wait_for_user_input() diff --git a/continuedev/src/continuedev/libs/steps/chroma.py b/continuedev/src/continuedev/libs/steps/chroma.py index 2d8742e8..f13a2bab 100644 --- a/continuedev/src/continuedev/libs/steps/chroma.py +++ b/continuedev/src/continuedev/libs/steps/chroma.py @@ -1,11 +1,10 @@ from textwrap import dedent from typing import Coroutine, Union -from ...models.filesystem_edit import AddDirectory, AddFile -from ..observation import Observation, TextObservation -from ..core import Step, ContinueSDK -from .main import EditCodeStep, EditFileStep, RunCommandStep, WaitForUserConfirmationStep +from ...core.observation import Observation, TextObservation +from ...core.main import Step, ContinueSDK +from .core.core import EditFileStep from ..chroma.query import query_codebase_index -from .main import EditFileStep +from .core.core import EditFileStep class AnswerQuestionChroma(Step): diff --git a/continuedev/src/continuedev/libs/steps/core/core.py b/continuedev/src/continuedev/libs/steps/core/core.py index 23086b53..0338d635 100644 --- a/continuedev/src/continuedev/libs/steps/core/core.py +++ b/continuedev/src/continuedev/libs/steps/core/core.py @@ -1,11 +1,16 @@ # These steps are depended upon by ContinueSDK -from typing import Callable, Coroutine, Dict, Generator, List, Tuple, Union +import subprocess +from textwrap import dedent +from typing import Coroutine, List, Union +from ...llm.prompt_utils import MarkdownStyleEncoderDecoder + +from ...util.traceback_parsers import parse_python_traceback from ....models.filesystem_edit import EditDiff, FileEditWithFullContents, FileSystemEdit -from ....models.filesystem import FileSystem +from ....models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents from ...llm import LLM -from ...observation import Observation, UserInputObservation -from ...core import Step +from ....core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation +from ....core.main import Step class ContinueSDK: @@ -42,6 +47,98 @@ class FileSystemEditStep(ReversibleStep): # Where and when should file saves happen? +def ShellCommandsStep(Step): + cmds: List[str] + cwd: str | None = None + name: str = "Run Shell Commands" + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return "\n".join(self.cmds) + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + cwd = await sdk.ide.getWorkspaceDirectory() if self.cwd is None else self.cwd + + process = subprocess.Popen( + '/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd) + + stdin_input = "\n".join(self.cmds) + out, err = process.communicate(stdin_input.encode()) + + # If it fails, return the error + if err is not None and err != "": + return TextObservation(text=err) + + return None + + +class EditCodeStep(Step): + # Might make an even more specific atomic step, which is "apply file edit" + range_in_files: List[RangeInFile] + prompt: str # String with {code} somewhere + name: str = "Edit code" + + _edit_diffs: Union[List[EditDiff], None] = None + _prompt: Union[str, None] = None + _completion: Union[str, None] = None + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + if self._edit_diffs is None: + return "Editing files: " + ", ".join(map(lambda rif: rif.filepath, self.range_in_files)) + elif len(self._edit_diffs) == 0: + return "No edits made" + else: + return llm.complete(dedent(f"""{self._prompt}{self._completion} + + Maximally concise summary of changes in bullet points (can use markdown): + """)) + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + rif_with_contents = [] + for range_in_file in self.range_in_files: + file_contents = await sdk.ide.readRangeInFile(range_in_file) + rif_with_contents.append( + RangeInFileWithContents.from_range_in_file(range_in_file, file_contents)) + enc_dec = MarkdownStyleEncoderDecoder(rif_with_contents) + code_string = enc_dec.encode() + prompt = self.prompt.format(code=code_string) + + completion = sdk.llm.complete(prompt) + + # Temporarily doing this to generate description. + self._prompt = prompt + self._completion = completion + + file_edits = enc_dec.decode(completion) + + self._edit_diffs = [] + for file_edit in file_edits: + diff = await sdk.apply_filesystem_edit(file_edit) + self._edit_diffs.append(diff) + + for filepath in set([file_edit.filepath for file_edit in file_edits]): + await sdk.ide.saveFile(filepath) + await sdk.ide.setFileOpen(filepath) + + return None + + +class EditFileStep(Step): + filepath: str + prompt: str + hide: bool = True + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return "Editing file: " + self.filepath + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + file_contents = await sdk.ide.readFile(self.filepath) + await sdk.run_step(EditCodeStep( + range_in_files=[RangeInFile.from_entire_file( + self.filepath, file_contents)], + prompt=self.prompt + )) + + class ManualEditStep(ReversibleStep): edit_diff: EditDiff hide: bool = True @@ -89,3 +186,31 @@ class UserInputStep(Step): async def run(self, sdk: ContinueSDK) -> Coroutine[UserInputObservation, None, None]: return UserInputObservation(user_input=self.user_input) + + +class WaitForUserInputStep(Step): + prompt: str + name: str = "Waiting for user input" + + _description: Union[str, None] = None + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return self.prompt + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + self._description = self.prompt + resp = await sdk.wait_for_user_input() + return TextObservation(text=resp) + + +class WaitForUserConfirmationStep(Step): + prompt: str + name: str = "Waiting for user confirmation" + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return self.prompt + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + self._description = self.prompt + resp = await sdk.wait_for_user_input() + return TextObservation(text=resp) diff --git a/continuedev/src/continuedev/libs/steps/draft/abstract_method.py b/continuedev/src/continuedev/libs/steps/draft/abstract_method.py index 927d93fd..f3131c4b 100644 --- a/continuedev/src/continuedev/libs/steps/draft/abstract_method.py +++ b/continuedev/src/continuedev/libs/steps/draft/abstract_method.py @@ -1,4 +1,5 @@ -from ...core import ContinueSDK, Step +from ....core.sdk import ContinueSDK +from ....core.main import Step class ImplementAbstractMethodStep(Step): diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index 608f089a..5ba5692a 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -1,6 +1,7 @@ from textwrap import dedent from ....models.filesystem_edit import AddFile -from ...core import Step, ContinueSDK +from ....core.main import Step +from ....core.sdk import ContinueSDK from ..main import WaitForUserInputStep diff --git a/continuedev/src/continuedev/libs/steps/draft/redux.py b/continuedev/src/continuedev/libs/steps/draft/redux.py index 52a8fbd8..efaa9ba4 100644 --- a/continuedev/src/continuedev/libs/steps/draft/redux.py +++ b/continuedev/src/continuedev/libs/steps/draft/redux.py @@ -1,7 +1,6 @@ -from textwrap import dedent -from ....models.filesystem_edit import AddFile -from ...core import Step, ContinueSDK -from ..main import WaitForUserInputStep, EditFileStep +from ....core.main import Step +from ....core.sdk import ContinueSDK +from ..core.core import EditFileStep class EditReduxStateStep(Step): diff --git a/continuedev/src/continuedev/libs/steps/draft/typeorm.py b/continuedev/src/continuedev/libs/steps/draft/typeorm.py index 9d058f1e..d06a6fb4 100644 --- a/continuedev/src/continuedev/libs/steps/draft/typeorm.py +++ b/continuedev/src/continuedev/libs/steps/draft/typeorm.py @@ -1,5 +1,6 @@ from textwrap import dedent -from ...core import Step, ContinueSDK +from ....core.main import Step +from ....core.sdk import ContinueSDK class CreateTableStep(Step): diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 555c1180..f28cb23f 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -1,18 +1,39 @@ -import time from typing import Callable, Coroutine, List, Union +from ..util.traceback_parsers import parse_python_traceback from ..llm import LLM from ...models.main import Traceback, Range from ...models.filesystem_edit import EditDiff, FileEdit from ...models.filesystem import RangeInFile, RangeInFileWithContents -from ..observation import Observation, TextObservation +from ...core.observation import Observation, TextObservation, TracebackObservation from ..llm.prompt_utils import MarkdownStyleEncoderDecoder from textwrap import dedent -from ..core import History, Policy, Step, ContinueSDK, Observation +from ...core.main import History, Policy, Step, ContinueSDK, Observation import subprocess -from ..util.traceback_parsers import parse_python_traceback -from ..observation import TracebackObservation import json +from .core.core import EditCodeStep + + +class RunCodeStep(Step): + cmd: str + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return f"Ran command: `{self.cmd}`" + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + result = subprocess.run( + self.cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout = result.stdout.decode("utf-8") + stderr = result.stderr.decode("utf-8") + print(stdout, stderr) + + # If it fails, return the error + tb = parse_python_traceback(stdout) or parse_python_traceback(stderr) + if tb: + return TracebackObservation(traceback=tb) + else: + self.hide = True + return None class RunPolicyUntilDoneStep(Step): @@ -51,149 +72,6 @@ class RunCommandStep(Step): return TextObservation(text=stdout) -def ShellCommandsStep(Step): - cmds: List[str] - name: str = "Run Shell Commands" - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return "\n".join(self.cmds) - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - cwd = await sdk.ide.getWorkspaceDirectory() - - process = subprocess.Popen( - '/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd) - - stdin_input = "\n".join(self.cmds) - out, err = process.communicate(stdin_input.encode()) - - # TODO: How to await?? - - # If it fails, return the error - if err is not None and err != "": - return TextObservation(text=err) - - return None - - -class WaitForUserInputStep(Step): - prompt: str - name: str = "Waiting for user input" - - _description: Union[str, None] = None - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return self.prompt - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - self._description = self.prompt - resp = await sdk.wait_for_user_input() - return TextObservation(text=resp) - - -class WaitForUserConfirmationStep(Step): - prompt: str - name: str = "Waiting for user confirmation" - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return self.prompt - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - self._description = self.prompt - resp = await sdk.wait_for_user_input() - return TextObservation(text=resp) - - -class RunCodeStep(Step): - cmd: str - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return f"Ran command: `{self.cmd}`" - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - result = subprocess.run( - self.cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout = result.stdout.decode("utf-8") - stderr = result.stderr.decode("utf-8") - print(stdout, stderr) - - # If it fails, return the error - tb = parse_python_traceback(stdout) or parse_python_traceback(stderr) - if tb: - return TracebackObservation(traceback=tb) - else: - self.hide = True - return None - - -class EditCodeStep(Step): - # Might make an even more specific atomic step, which is "apply file edit" - range_in_files: List[RangeInFile] - prompt: str # String with {code} somewhere - name: str = "Edit code" - - _edit_diffs: Union[List[EditDiff], None] = None - _prompt: Union[str, None] = None - _completion: Union[str, None] = None - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - if self._edit_diffs is None: - return "Editing files: " + ", ".join(map(lambda rif: rif.filepath, self.range_in_files)) - elif len(self._edit_diffs) == 0: - return "No edits made" - else: - return llm.complete(dedent(f"""{self._prompt}{self._completion} - - Maximally concise summary of changes in bullet points (can use markdown): - """)) - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - rif_with_contents = [] - for range_in_file in self.range_in_files: - file_contents = await sdk.ide.readRangeInFile(range_in_file) - rif_with_contents.append( - RangeInFileWithContents.from_range_in_file(range_in_file, file_contents)) - enc_dec = MarkdownStyleEncoderDecoder(rif_with_contents) - code_string = enc_dec.encode() - prompt = self.prompt.format(code=code_string) - - completion = sdk.llm.complete(prompt) - - # Temporarily doing this to generate description. - self._prompt = prompt - self._completion = completion - - file_edits = enc_dec.decode(completion) - - self._edit_diffs = [] - for file_edit in file_edits: - diff = await sdk.apply_filesystem_edit(file_edit) - self._edit_diffs.append(diff) - - for filepath in set([file_edit.filepath for file_edit in file_edits]): - await sdk.ide.saveFile(filepath) - await sdk.ide.setFileOpen(filepath) - - return None - - -class EditFileStep(Step): - filepath: str - prompt: str - hide: bool = True - - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: - return "Editing file: " + self.filepath - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - file_contents = await sdk.ide.readFile(self.filepath) - await sdk.run_step(EditCodeStep( - range_in_files=[RangeInFile.from_entire_file( - self.filepath, file_contents)], - prompt=self.prompt - )) - - class FasterEditHighlightedCodeStep(Step): user_input: str hide = True diff --git a/continuedev/src/continuedev/libs/steps/migration.py b/continuedev/src/continuedev/libs/steps/migration.py index 04296836..f044a60f 100644 --- a/continuedev/src/continuedev/libs/steps/migration.py +++ b/continuedev/src/continuedev/libs/steps/migration.py @@ -3,7 +3,7 @@ from ...models.filesystem import RangeInFile from .main import EditCodeStep, RunCommandStep -from ..core import Step +from ...core.main import Step class MigrationStep(Step): diff --git a/continuedev/src/continuedev/libs/steps/nate.py b/continuedev/src/continuedev/libs/steps/nate.py index 80436fa4..a0e728e5 100644 --- a/continuedev/src/continuedev/libs/steps/nate.py +++ b/continuedev/src/continuedev/libs/steps/nate.py @@ -1,14 +1,13 @@ -import asyncio from textwrap import dedent import time from typing import Coroutine, Union -from ...models.main import Range from ...models.filesystem import RangeInFile from ...models.filesystem_edit import AddDirectory, AddFile -from ..observation import Observation, TextObservation -from ..core import Step, ContinueSDK -from .main import EditCodeStep, EditFileStep, RunCommandStep, WaitForUserConfirmationStep +from ...core.observation import Observation, TextObservation +from ...core.main import Step, ContinueSDK +from .main import RunCommandStep +from .core.core import WaitForUserConfirmationStep, EditCodeStep, EditFileStep import os diff --git a/continuedev/src/continuedev/libs/steps/pytest.py b/continuedev/src/continuedev/libs/steps/pytest.py index e53eb465..b4e6dfd2 100644 --- a/continuedev/src/continuedev/libs/steps/pytest.py +++ b/continuedev/src/continuedev/libs/steps/pytest.py @@ -1,6 +1,6 @@ from textwrap import dedent from ...models.filesystem_edit import AddDirectory, AddFile -from ..core import Step, ContinueSDK +from ...core.main import Step, ContinueSDK import os diff --git a/continuedev/src/continuedev/libs/steps/ty.py b/continuedev/src/continuedev/libs/steps/ty.py index 1eb6271d..5ff03f04 100644 --- a/continuedev/src/continuedev/libs/steps/ty.py +++ b/continuedev/src/continuedev/libs/steps/ty.py @@ -2,9 +2,11 @@ import subprocess from ...models.main import Position, Range from ...models.filesystem import RangeInFile from ...models.filesystem_edit import AddDirectory, AddFile, FileEdit -from ..observation import DictObservation -from ..core import History, Step, ContinueSDK, Policy -from .main import EditCodeStep, RunCommandStep, WaitForUserInputStep, WaitForUserConfirmationStep +from ...core.observation import DictObservation +from ...core.main import History, Step, Policy +from ...core.sdk import ContinueSDK +from .main import RunCommandStep +from ..steps.core.core import EditCodeStep, WaitForUserConfirmationStep, WaitForUserInputStep source_name = "weather_api" diff --git a/continuedev/src/continuedev/plugins/policy/libs/alternate.py b/continuedev/src/continuedev/plugins/policy/libs/alternate.py index 5bfbb821..3087c059 100644 --- a/continuedev/src/continuedev/plugins/policy/libs/alternate.py +++ b/continuedev/src/continuedev/plugins/policy/libs/alternate.py @@ -1,7 +1,5 @@ from plugins import policy -from ....libs.observation import Observation -from ....libs.steps import Step -from ....libs.core import History +from ....core.main import History, Step class AlternatingPolicy: diff --git a/continuedev/src/continuedev/plugins/step/hookspecs.py b/continuedev/src/continuedev/plugins/step/hookspecs.py index 4309bad3..a5714fc5 100644 --- a/continuedev/src/continuedev/plugins/step/hookspecs.py +++ b/continuedev/src/continuedev/plugins/step/hookspecs.py @@ -1,6 +1,8 @@ from typing import Coroutine import pluggy -from ...libs.core import ContinueSDK, Step, Observation +from ...core.main import Step +from ...core.observation import Observation +from ...core.sdk import ContinueSDK hookspec = pluggy.HookspecMarker("continue.step") diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index 5eb151d7..bfd7a09c 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -5,14 +5,14 @@ from pydantic import BaseModel from uvicorn.main import Server from ..models.filesystem_edit import FileEditWithFullContents -from ..libs.policy import DemoPolicy -from ..libs.core import FullState, History, Step -from ..libs.agent import Agent +from ..core.policy import DemoPolicy +from ..core.main import FullState, History, Step +from ..core.agent import Agent from ..libs.steps.nate import ImplementAbstractMethodStep -from ..libs.observation import Observation +from ..core.observation import Observation from ..libs.llm.openai import OpenAI from .ide_protocol import AbstractIdeProtocolServer -from ..libs.env import openai_api_key +from ..core.env import openai_api_key import asyncio import nest_asyncio nest_asyncio.apply() -- cgit v1.2.3-70-g09d2 From e7fd1cd977c04643a11989f0bcefe6f573ab994a Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 27 May 2023 15:58:34 -0400 Subject: Added History classes for JSON Schema generation --- .../src/continuedev/models/generate_json_schema.py | 3 + extension/package.json | 2 +- extension/schema/History.d.ts | 41 + extension/schema/HistoryNode.d.ts | 31 + .../scripts/continuedev-0.1.0-py3-none-any.whl | Bin 61142 -> 51468 bytes schema/json/History.json | 73 ++ schema/json/HistoryNode.json | 51 + schema/openapi.json | 1027 ++++++++++++++++++++ 8 files changed, 1227 insertions(+), 1 deletion(-) create mode 100644 extension/schema/History.d.ts create mode 100644 extension/schema/HistoryNode.d.ts create mode 100644 schema/json/History.json create mode 100644 schema/json/HistoryNode.json create mode 100644 schema/openapi.json (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/models/generate_json_schema.py b/continuedev/src/continuedev/models/generate_json_schema.py index da78dfac..07337029 100644 --- a/continuedev/src/continuedev/models/generate_json_schema.py +++ b/continuedev/src/continuedev/models/generate_json_schema.py @@ -1,6 +1,7 @@ from .main import * from .filesystem import RangeInFile, FileEdit from .filesystem_edit import FileEditWithFullContents +from ..core.main import History, HistoryNode from pydantic import schema_json_of import os @@ -10,6 +11,8 @@ MODELS_TO_GENERATE = [ RangeInFile, FileEdit ] + [ FileEditWithFullContents +] + [ + History, HistoryNode ] RENAMES = { diff --git a/extension/package.json b/extension/package.json index d957e071..3f9e59ff 100644 --- a/extension/package.json +++ b/extension/package.json @@ -166,7 +166,7 @@ "package": "cp ./config/prod_config.json ./config/config.json && npm run compile && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.1.vsix && cp ./config/dev_config.json ./config/config.json", "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.0-py3-none-any.whl ../extension/scripts/continuedev-0.1.0-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", "install-extension": "code --install-extension ./build/continue-0.0.1.vsix", - "uninstall": "code --uninstall-extension Continue.continue", + "uninstall": "code --uninstall-extension .continue", "reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension" }, "devDependencies": { diff --git a/extension/schema/History.d.ts b/extension/schema/History.d.ts new file mode 100644 index 00000000..508deaf0 --- /dev/null +++ b/extension/schema/History.d.ts @@ -0,0 +1,41 @@ +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +export type History = History1; +export type Name = string; +export type Hide = boolean; +export type SystemMessage = string; +export type Depth = number; +export type Timeline = HistoryNode[]; +export type CurrentIndex = number; + +/** + * A history of steps taken and their results + */ +export interface History1 { + timeline: Timeline; + current_index: CurrentIndex; + [k: string]: unknown; +} +/** + * A point in history, a list of which make up History + */ +export interface HistoryNode { + step: Step; + observation?: Observation; + depth: Depth; + [k: string]: unknown; +} +export interface Step { + name?: Name; + hide?: Hide; + system_message?: SystemMessage; + [k: string]: unknown; +} +export interface Observation { + [k: string]: unknown; +} diff --git a/extension/schema/HistoryNode.d.ts b/extension/schema/HistoryNode.d.ts new file mode 100644 index 00000000..c1507270 --- /dev/null +++ b/extension/schema/HistoryNode.d.ts @@ -0,0 +1,31 @@ +/* eslint-disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +export type HistoryNode = HistoryNode1; +export type Name = string; +export type Hide = boolean; +export type SystemMessage = string; +export type Depth = number; + +/** + * A point in history, a list of which make up History + */ +export interface HistoryNode1 { + step: Step; + observation?: Observation; + depth: Depth; + [k: string]: unknown; +} +export interface Step { + name?: Name; + hide?: Hide; + system_message?: SystemMessage; + [k: string]: unknown; +} +export interface Observation { + [k: string]: unknown; +} diff --git a/extension/scripts/continuedev-0.1.0-py3-none-any.whl b/extension/scripts/continuedev-0.1.0-py3-none-any.whl index 15787c59..68457b3e 100644 Binary files a/extension/scripts/continuedev-0.1.0-py3-none-any.whl and b/extension/scripts/continuedev-0.1.0-py3-none-any.whl differ diff --git a/schema/json/History.json b/schema/json/History.json new file mode 100644 index 00000000..7691c7dd --- /dev/null +++ b/schema/json/History.json @@ -0,0 +1,73 @@ +{ + "title": "History", + "$ref": "#/definitions/continuedev__src__continuedev__core__main__History", + "definitions": { + "Step": { + "title": "Step", + "type": "object", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "hide": { + "title": "Hide", + "default": false, + "type": "boolean" + }, + "system_message": { + "title": "System Message", + "type": "string" + } + } + }, + "Observation": { + "title": "Observation", + "type": "object", + "properties": {} + }, + "HistoryNode": { + "title": "HistoryNode", + "description": "A point in history, a list of which make up History", + "type": "object", + "properties": { + "step": { + "$ref": "#/definitions/Step" + }, + "observation": { + "$ref": "#/definitions/Observation" + }, + "depth": { + "title": "Depth", + "type": "integer" + } + }, + "required": [ + "step", + "depth" + ] + }, + "continuedev__src__continuedev__core__main__History": { + "title": "History", + "description": "A history of steps taken and their results", + "type": "object", + "properties": { + "timeline": { + "title": "Timeline", + "type": "array", + "items": { + "$ref": "#/definitions/HistoryNode" + } + }, + "current_index": { + "title": "Current Index", + "type": "integer" + } + }, + "required": [ + "timeline", + "current_index" + ] + } + } +} \ No newline at end of file diff --git a/schema/json/HistoryNode.json b/schema/json/HistoryNode.json new file mode 100644 index 00000000..f58b8038 --- /dev/null +++ b/schema/json/HistoryNode.json @@ -0,0 +1,51 @@ +{ + "title": "HistoryNode", + "$ref": "#/definitions/continuedev__src__continuedev__core__main__HistoryNode", + "definitions": { + "Step": { + "title": "Step", + "type": "object", + "properties": { + "name": { + "title": "Name", + "type": "string" + }, + "hide": { + "title": "Hide", + "default": false, + "type": "boolean" + }, + "system_message": { + "title": "System Message", + "type": "string" + } + } + }, + "Observation": { + "title": "Observation", + "type": "object", + "properties": {} + }, + "continuedev__src__continuedev__core__main__HistoryNode": { + "title": "HistoryNode", + "description": "A point in history, a list of which make up History", + "type": "object", + "properties": { + "step": { + "$ref": "#/definitions/Step" + }, + "observation": { + "$ref": "#/definitions/Observation" + }, + "depth": { + "title": "Depth", + "type": "integer" + } + }, + "required": [ + "step", + "depth" + ] + } + } +} \ No newline at end of file diff --git a/schema/openapi.json b/schema/openapi.json new file mode 100644 index 00000000..8880fd20 --- /dev/null +++ b/schema/openapi.json @@ -0,0 +1,1027 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "Continue API", + "description": "Continue API", + "version": "1.0" + }, + "paths": { + "/debug/run": { + "post": { + "tags": ["debug"], + "summary": "Run", + "description": "Returns boolean indicating whether error was found, edited, and solved, or not all of these.", + "operationId": "run_debug_run_post", + "parameters": [ + { + "required": true, + "schema": { + "title": "Filepath", + "type": "string" + }, + "name": "filepath", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Make Edit", + "type": "boolean", + "default": false + }, + "name": "make_edit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/inline": { + "post": { + "tags": ["debug"], + "summary": "Inline", + "operationId": "inline_debug_inline_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InlineBody" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompletionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/suggestion": { + "get": { + "tags": ["debug"], + "summary": "Suggestion", + "operationId": "suggestion_debug_suggestion_get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Traceback", + "type": "string" + }, + "name": "traceback", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompletionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/list": { + "post": { + "tags": ["debug"], + "summary": "Listten", + "operationId": "listten_debug_list_post", + "parameters": [ + { + "required": false, + "schema": { + "title": "X-Vsc-Machine-Id", + "type": "string", + "default": "anonymous" + }, + "name": "x-vsc-machine-id", + "in": "header" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SerializedDebugContext" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompletionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/explain": { + "post": { + "tags": ["debug"], + "summary": "Explain", + "operationId": "explain_debug_explain_post", + "parameters": [ + { + "required": false, + "schema": { + "title": "X-Vsc-Machine-Id", + "type": "string", + "default": "anonymous" + }, + "name": "x-vsc-machine-id", + "in": "header" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SerializedDebugContext" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ExplainResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/edit": { + "post": { + "tags": ["debug"], + "summary": "Edit Endpoint", + "operationId": "edit_endpoint_debug_edit_post", + "parameters": [ + { + "required": false, + "schema": { + "title": "X-Vsc-Machine-Id", + "type": "string", + "default": "anonymous" + }, + "name": "x-vsc-machine-id", + "in": "header" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SerializedDebugContext" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EditResp" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/find": { + "post": { + "tags": ["debug"], + "summary": "Find Sus Code Endpoint", + "operationId": "find_sus_code_endpoint_debug_find_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FindBody" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FindResp" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/parse_traceback": { + "get": { + "tags": ["debug"], + "summary": "Parse Traceback Endpoint", + "operationId": "parse_traceback_endpoint_debug_parse_traceback_get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Traceback", + "type": "string" + }, + "name": "traceback", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Traceback" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/debug/find_docs": { + "get": { + "tags": ["debug"], + "summary": "Find Docs Endpoint", + "operationId": "find_docs_endpoint_debug_find_docs_get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Traceback", + "type": "string" + }, + "name": "traceback", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OptionalCompletionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/docstring/forline": { + "get": { + "tags": ["docstring"], + "summary": "Forline", + "description": "Write a docstring for a function at a line number", + "operationId": "forline_docstring_forline_get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Filecontents", + "type": "string" + }, + "name": "filecontents", + "in": "query" + }, + { + "required": true, + "schema": { + "title": "Lineno", + "type": "integer" + }, + "name": "lineno", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "Format", + "type": "string", + "default": "google" + }, + "name": "format", + "in": "query" + }, + { + "required": false, + "schema": { + "title": "X-Vsc-Machine-Id", + "type": "string", + "default": "anonymous" + }, + "name": "x-vsc-machine-id", + "in": "header" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/unittest/forline": { + "post": { + "tags": ["unittest"], + "summary": "Forline", + "description": "Write unit test for the function encapsulating the given line number.", + "operationId": "forline_unittest_forline_post", + "parameters": [ + { + "required": false, + "schema": { + "title": "X-Vsc-Machine-Id", + "type": "string", + "default": "anonymous" + }, + "name": "x-vsc-machine-id", + "in": "header" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FilePosition" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompletionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/unittest/failingtest": { + "post": { + "tags": ["unittest"], + "summary": "Failingtest", + "description": "Write a failing test for the function encapsulating the given line number.", + "operationId": "failingtest_unittest_failingtest_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailingTestBody" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompletionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/chat/test": { + "get": { + "tags": ["chat"], + "summary": "Test", + "operationId": "test_chat_test_get", + "parameters": [ + { + "required": true, + "schema": { + "title": "Prompt", + "type": "string" + }, + "name": "prompt", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/chat/complete": { + "post": { + "tags": ["chat"], + "summary": "Complete", + "operationId": "complete_chat_complete_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChatHistory" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/test": { + "get": { + "summary": "Test", + "operationId": "test_test_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ChatHistory": { + "title": "ChatHistory", + "required": ["messages"], + "type": "object", + "properties": { + "messages": { + "title": "Messages", + "type": "array", + "items": { + "$ref": "#/components/schemas/ChatMessage" + } + } + } + }, + "ChatMessage": { + "title": "ChatMessage", + "required": ["role", "content"], + "type": "object", + "properties": { + "role": { + "title": "Role", + "type": "string" + }, + "content": { + "title": "Content", + "type": "string" + } + } + }, + "CompletionResponse": { + "title": "CompletionResponse", + "required": ["completion"], + "type": "object", + "properties": { + "completion": { + "title": "Completion", + "type": "string" + } + } + }, + "EditResp": { + "title": "EditResp", + "required": ["completion"], + "type": "object", + "properties": { + "completion": { + "title": "Completion", + "type": "array", + "items": { + "$ref": "#/components/schemas/FileEdit" + } + } + } + }, + "ExplainResponse": { + "title": "ExplainResponse", + "required": ["completion"], + "type": "object", + "properties": { + "completion": { + "title": "Completion", + "type": "string" + } + } + }, + "FailingTestBody": { + "title": "FailingTestBody", + "required": ["description", "fp"], + "type": "object", + "properties": { + "description": { + "title": "Description", + "type": "string" + }, + "fp": { + "$ref": "#/components/schemas/FilePosition" + } + }, + "description": "A failing test body." + }, + "FileEdit": { + "title": "FileEdit", + "required": ["filepath", "range", "replacement"], + "type": "object", + "properties": { + "filepath": { + "title": "Filepath", + "type": "string" + }, + "range": { + "$ref": "#/components/schemas/Range" + }, + "replacement": { + "title": "Replacement", + "type": "string" + } + }, + "additionalProperties": false + }, + "FilePosition": { + "title": "FilePosition", + "required": ["filecontents", "lineno"], + "type": "object", + "properties": { + "filecontents": { + "title": "Filecontents", + "type": "string" + }, + "lineno": { + "title": "Lineno", + "type": "integer" + } + }, + "description": "A position in a file." + }, + "FindBody": { + "title": "FindBody", + "required": ["traceback", "filesystem"], + "type": "object", + "properties": { + "traceback": { + "title": "Traceback", + "type": "string" + }, + "filesystem": { + "title": "Filesystem", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "description": { + "title": "Description", + "type": "string" + } + } + }, + "FindResp": { + "title": "FindResp", + "required": ["response"], + "type": "object", + "properties": { + "response": { + "title": "Response", + "type": "array", + "items": { + "$ref": "#/components/schemas/RangeInFile" + } + } + } + }, + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + } + } + } + }, + "InlineBody": { + "title": "InlineBody", + "required": ["filecontents", "startline", "endline"], + "type": "object", + "properties": { + "filecontents": { + "title": "Filecontents", + "type": "string" + }, + "startline": { + "title": "Startline", + "type": "integer" + }, + "endline": { + "title": "Endline", + "type": "integer" + }, + "traceback": { + "title": "Traceback", + "type": "string", + "default": "" + } + } + }, + "OptionalCompletionResponse": { + "title": "OptionalCompletionResponse", + "type": "object", + "properties": { + "completion": { + "title": "Completion", + "type": "string" + } + } + }, + "Position": { + "title": "Position", + "required": ["line", "character"], + "type": "object", + "properties": { + "line": { + "title": "Line", + "type": "integer" + }, + "character": { + "title": "Character", + "type": "integer" + } + }, + "additionalProperties": false + }, + "ProgrammingLangauge": { + "title": "ProgrammingLangauge", + "enum": ["python", "javascript", "typescript"], + "type": "string", + "description": "An enumeration." + }, + "Range": { + "title": "Range", + "required": ["start", "end"], + "type": "object", + "properties": { + "start": { + "$ref": "#/components/schemas/Position" + }, + "end": { + "$ref": "#/components/schemas/Position" + } + }, + "additionalProperties": false, + "description": "A range in a file. 0-indexed." + }, + "RangeInFile": { + "title": "RangeInFile", + "required": ["filepath", "range"], + "type": "object", + "properties": { + "filepath": { + "title": "Filepath", + "type": "string" + }, + "range": { + "$ref": "#/components/schemas/Range" + } + }, + "additionalProperties": false + }, + "SerializedDebugContext": { + "title": "SerializedDebugContext", + "required": ["ranges_in_files", "filesystem"], + "type": "object", + "properties": { + "traceback": { + "title": "Traceback", + "type": "string" + }, + "ranges_in_files": { + "title": "Ranges In Files", + "type": "array", + "items": { + "$ref": "#/components/schemas/RangeInFile" + } + }, + "filesystem": { + "title": "Filesystem", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "description": { + "title": "Description", + "type": "string" + } + } + }, + "Traceback": { + "title": "Traceback", + "required": ["frames", "message", "error_type", "language"], + "type": "object", + "properties": { + "frames": { + "title": "Frames", + "type": "array", + "items": { + "$ref": "#/components/schemas/TracebackFrame" + } + }, + "message": { + "title": "Message", + "type": "string" + }, + "error_type": { + "title": "Error Type", + "type": "string" + }, + "language": { + "$ref": "#/components/schemas/ProgrammingLangauge" + }, + "full_traceback": { + "title": "Full Traceback", + "type": "string" + } + }, + "additionalProperties": false + }, + "TracebackFrame": { + "title": "TracebackFrame", + "required": ["filepath", "lineno", "function"], + "type": "object", + "properties": { + "filepath": { + "title": "Filepath", + "type": "string" + }, + "lineno": { + "title": "Lineno", + "type": "integer" + }, + "function": { + "title": "Function", + "type": "string" + }, + "code": { + "title": "Code", + "type": "string" + } + }, + "additionalProperties": false + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + } + }, + "msg": { + "title": "Message", + "type": "string" + }, + "type": { + "title": "Error Type", + "type": "string" + } + } + } + } + } +} -- cgit v1.2.3-70-g09d2 From 33027dab0031b4f499dd200737f314dac143ef4f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 00:03:48 -0400 Subject: ContinueStepStep --- continuedev/src/continuedev/core/policy.py | 4 +++ .../src/continuedev/libs/steps/continue_step.py | 37 ++++++++++++++++++++++ .../src/continuedev/libs/steps/react_posthog.py | 0 3 files changed, 41 insertions(+) create mode 100644 continuedev/src/continuedev/libs/steps/continue_step.py create mode 100644 continuedev/src/continuedev/libs/steps/react_posthog.py (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 9e6abf14..5a6652f4 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -1,5 +1,6 @@ from typing import List, Tuple, Type + from ..models.main import ContinueBaseModel from ..libs.steps.ty import CreatePipelineStep @@ -8,6 +9,7 @@ from .observation import Observation, TracebackObservation, UserInputObservation from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep from ..libs.steps.nate import WritePytestsStep, CreateTableStep from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma +from ..libs.steps.continue_step import ContinueStepStep class DemoPolicy(Policy): @@ -28,6 +30,8 @@ class DemoPolicy(Policy): return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) elif "/edit" in observation.user_input: return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) + elif "/step" in observation.user_input: + return ContinueStepStep(prompt=" ".join(observation.user_input.split(" ")[1:])) return EditHighlightedCodeStep(user_input=observation.user_input) state = history.get_current() diff --git a/continuedev/src/continuedev/libs/steps/continue_step.py b/continuedev/src/continuedev/libs/steps/continue_step.py new file mode 100644 index 00000000..253bb490 --- /dev/null +++ b/continuedev/src/continuedev/libs/steps/continue_step.py @@ -0,0 +1,37 @@ +from textwrap import dedent +from ...models.filesystem import RangeInFile +from .main import EditHighlightedCodeStep +from ...core.main import Step +from ...core.sdk import ContinueSDK + + +class ContinueStepStep(Step): + name: str = "Write your own Continue Step." + prompt: str + + async def run(self, sdk: ContinueSDK): + await sdk.run_step(EditHighlightedCodeStep(user_input=dedent(f"""\ + Here is an example of a Step that runs a command and then edits a file. + + ```python + from ...core.main import Step + from ...core.sdk import ContinueSDK + + class RunCommandAndEditFileStep(Step): + name: str = "Run a command and then edit a file." + command: str + file_path: str + prompt: str + + async def run(self, sdk: ContinueSDK): + await sdk.run([command]) + await sdk.edit_file(filename=self.file_path, prompt=self.prompt) + ``` + + Please edit the code to write your own Step that does the following: + + {self.prommpt} + + It should be a subclass of Step as above, implementing the `run` method, and using pydantic attributes to define the parameters. + + """))) diff --git a/continuedev/src/continuedev/libs/steps/react_posthog.py b/continuedev/src/continuedev/libs/steps/react_posthog.py new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3-70-g09d2 From 306ab404ce687db5a67762d63b159118ab592837 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 28 May 2023 12:05:32 -0400 Subject: bug fixes, build script, sdk methods --- continuedev/pyproject.toml | 3 ++ continuedev/src/continuedev/core/agent.py | 1 - continuedev/src/continuedev/core/sdk.py | 32 ++++++++++++--- continuedev/src/continuedev/libs/llm/openai.py | 10 +++-- .../src/continuedev/models/generate_json_schema.py | 8 +++- extension/package-lock.json | 4 +- extension/package.json | 6 +-- .../scripts/continuedev-0.1.0-py3-none-any.whl | Bin 51468 -> 53104 bytes extension/scripts/install_from_source.py | 45 +++++++++++++++++++++ schema/json/FileEdit.json | 4 +- schema/json/FileEditWithFullContents.json | 4 +- schema/json/History.json | 4 +- schema/json/HistoryNode.json | 4 +- schema/json/Position.json | 4 +- schema/json/Range.json | 4 +- schema/json/RangeInFile.json | 4 +- schema/json/Traceback.json | 4 +- schema/json/TracebackFrame.json | 4 +- 18 files changed, 109 insertions(+), 36 deletions(-) create mode 100644 extension/scripts/install_from_source.py (limited to 'continuedev/src') diff --git a/continuedev/pyproject.toml b/continuedev/pyproject.toml index 5c224c9c..8ed8f268 100644 --- a/continuedev/pyproject.toml +++ b/continuedev/pyproject.toml @@ -20,6 +20,9 @@ urllib3 = "1.26.15" gpt-index = "^0.6.8" setuptools = "^67.7.2" +[tool.poetry.scripts] +typegen = "src.continuedev.models.generate_json_schema:main" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 509a54b3..6d1f542e 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -17,7 +17,6 @@ class Agent(ContinueBaseModel): policy: Policy ide: AbstractIdeProtocolServer history: History = History.from_empty() - continue_sdk: "ContinueSDK" _on_update_callbacks: List[Callable[[FullState], None]] = [] _active: bool = False diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index ff62a2b1..3559e9d7 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -1,5 +1,6 @@ +import os from typing import Coroutine, Union -from ..models.filesystem_edit import FileSystemEdit +from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory from ..models.filesystem import RangeInFile from ..libs.llm import LLM from .observation import Observation @@ -37,11 +38,16 @@ class ContinueSDK: def history(self) -> History: return self.__agent.history + async def _ensure_absolute_path(self, path: str) -> str: + if os.path.isabs(path): + return path + return os.path.join(await self.ide.getWorkspaceDirectory(), path) + async def run_step(self, step: Step) -> Coroutine[Observation, None, None]: return await self.__agent._run_singular_step(step) async def apply_filesystem_edit(self, edit: FileSystemEdit): - await self.run_step(FileSystemEditStep(edit=edit)) + return await self.run_step(FileSystemEditStep(edit=edit)) async def wait_for_user_input(self) -> str: return await self.__agent.wait_for_user_input() @@ -51,12 +57,26 @@ class ContinueSDK: async def run(self, commands: List[str] | str, cwd: str = None): commands = commands if isinstance(commands, List) else [commands] - return self.run_step(ShellCommandsStep(commands=commands, cwd=cwd)) + return await self.run_step(ShellCommandsStep(commands=commands, cwd=cwd)) async def edit_file(self, filename: str, prompt: str): - await self.ide.setFileOpen(filename) - contents = await self.ide.readFile(filename) + filepath = await self._ensure_absolute_path(filename) + + await self.ide.setFileOpen(filepath) + contents = await self.ide.readFile(filepath) await self.run_step(EditCodeStep( - range_in_files=[RangeInFile.from_entire_file(filename, contents)], + range_in_files=[RangeInFile.from_entire_file(filepath, contents)], prompt=f'Here is the code before:\n\n{{code}}\n\nHere is the user request:\n\n{prompt}\n\nHere is the code edited to perfectly solve the user request:\n\n' )) + + async def add_file(self, filename: str, content: str | None): + return await self.run_step(FileSystemEditStep(edit=AddFile(filename=filename, content=content))) + + async def delete_file(self, filename: str): + return await self.run_step(FileSystemEditStep(edit=DeleteFile(filepath=filename))) + + async def add_directory(self, path: str): + return await self.run_step(FileSystemEditStep(edit=AddDirectory(path=path))) + + async def delete_directory(self, path: str): + return await self.run_step(FileSystemEditStep(edit=DeleteDirectory(path=path))) diff --git a/continuedev/src/continuedev/libs/llm/openai.py b/continuedev/src/continuedev/libs/llm/openai.py index bb745e75..10801465 100644 --- a/continuedev/src/continuedev/libs/llm/openai.py +++ b/continuedev/src/continuedev/libs/llm/openai.py @@ -6,6 +6,8 @@ import aiohttp from ..llm import LLM from pydantic import BaseModel, validator +DEFAULT_MAX_TOKENS = 2048 + class OpenAI(LLM): api_key: str @@ -22,7 +24,7 @@ class OpenAI(LLM): def stream_chat(self, messages, **kwargs) -> Generator[Union[Any, List, Dict], None, None]: self.completion_count += 1 - args = {"max_tokens": 512, "temperature": 0.5, "top_p": 1, + args = {"max_tokens": DEFAULT_MAX_TOKENS, "temperature": 0.5, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0} | kwargs args["stream"] = True args["model"] = "gpt-3.5-turbo" @@ -38,7 +40,7 @@ class OpenAI(LLM): def stream_complete(self, prompt: str, **kwargs) -> Generator[Union[Any, List, Dict], None, None]: self.completion_count += 1 - args = {"model": self.default_model, "max_tokens": 512, "temperature": 0.5, + args = {"model": self.default_model, "max_tokens": DEFAULT_MAX_TOKENS, "temperature": 0.5, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0, "suffix": None} | kwargs args["stream"] = True @@ -64,7 +66,7 @@ class OpenAI(LLM): t1 = time.time() self.completion_count += 1 - args = {"model": self.default_model, "max_tokens": 512, "temperature": 0.5, "top_p": 1, + args = {"model": self.default_model, "max_tokens": DEFAULT_MAX_TOKENS, "temperature": 0.5, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0, "stream": False} | kwargs if args["model"] == "gpt-3.5-turbo": @@ -132,7 +134,7 @@ class OpenAI(LLM): def parallel_complete(self, prompts: list[str], suffixes: Union[list[str], None] = None, **kwargs) -> list[str]: self.completion_count += len(prompts) - args = {"model": self.default_model, "max_tokens": 512, "temperature": 0.5, + args = {"model": self.default_model, "max_tokens": DEFAULT_MAX_TOKENS, "temperature": 0.5, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0} | kwargs async def fn(): diff --git a/continuedev/src/continuedev/models/generate_json_schema.py b/continuedev/src/continuedev/models/generate_json_schema.py index 07337029..080787a5 100644 --- a/continuedev/src/continuedev/models/generate_json_schema.py +++ b/continuedev/src/continuedev/models/generate_json_schema.py @@ -19,7 +19,7 @@ RENAMES = { "ExampleClass": "RenamedName" } -SCHEMA_DIR = "schema/json" +SCHEMA_DIR = "../schema/json" def clear_schemas(): @@ -28,7 +28,7 @@ def clear_schemas(): os.remove(os.path.join(SCHEMA_DIR, filename)) -if __name__ == "__main__": +def main(): clear_schemas() for model in MODELS_TO_GENERATE: title = RENAMES.get(model.__name__, model.__name__) @@ -40,3 +40,7 @@ if __name__ == "__main__": with open(f"{SCHEMA_DIR}/{title}.json", "w") as f: f.write(json) + + +if __name__ == "__main__": + main() diff --git a/extension/package-lock.json b/extension/package-lock.json index a90827d8..faa07b57 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.2", + "version": "0.0.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.2", + "version": "0.0.3", "dependencies": { "@electron/rebuild": "^3.2.10", "@reduxjs/toolkit": "^1.9.3", diff --git a/extension/package.json b/extension/package.json index 525ec075..40d56fb7 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.2", + "version": "0.0.3", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -23,7 +23,7 @@ "Other" ], "activationEvents": [ - "*" + "onStartupFinished" ], "main": "./out/extension.js", "contributes": { @@ -160,7 +160,7 @@ "pretest": "npm run compile && npm run lint", "lint": "eslint src --ext ts", "test": "node ./out/test/runTest.js", - "package": "cp ./config/prod_config.json ./config/config.json && npm run compile && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.1.vsix && cp ./config/dev_config.json ./config/config.json", + "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.2.vsix && cp ./config/dev_config.json ./config/config.json", "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.0-py3-none-any.whl ../extension/scripts/continuedev-0.1.0-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", "install-extension": "code --install-extension ./build/continue-0.0.1.vsix", "uninstall": "code --uninstall-extension .continue", diff --git a/extension/scripts/continuedev-0.1.0-py3-none-any.whl b/extension/scripts/continuedev-0.1.0-py3-none-any.whl index 68457b3e..d1483db9 100644 Binary files a/extension/scripts/continuedev-0.1.0-py3-none-any.whl and b/extension/scripts/continuedev-0.1.0-py3-none-any.whl differ diff --git a/extension/scripts/install_from_source.py b/extension/scripts/install_from_source.py new file mode 100644 index 00000000..4fe903ed --- /dev/null +++ b/extension/scripts/install_from_source.py @@ -0,0 +1,45 @@ +import subprocess + + +def run(cmd: str): + return subprocess.run(cmd, shell=True, capture_output=True) + + +def main(): + # Check for Python and Node - we won't install them, but will warn + out, err1 = run("python --version") + out, err2 = run("python3 --version") + if err1 and err2: + print("Python is required for Continue but is not installed on your machine. See https://www.python.org/downloads/ to download the latest version, then try again.") + return + + out, err = run("node --version") + if err: + print("Node is required for Continue but is not installed on your machine. See https://nodejs.org/en/download/ to download the latest version, then try again.") + return + + out, err = run("npm --version") + if err: + print("NPM is required for Continue but is not installed on your machine. See https://nodejs.org/en/download/ to download the latest version, then try again.") + return + + out, err = run("poetry --version") + if err: + print("Poetry is required for Continue but is not installed on your machine. See https://python-poetry.org/docs/#installation to download the latest version, then try again.") + return + + out, err = run("cd ../../continuedev; poetry run typegen") + + out, err = run( + "cd ..; npm i; cd react-app; npm i; cd ..; npm run full-package\r y\r npm run install-extension") + + if err: + print("Error installing the extension. Please try again.") + print("This was the error: ", err) + return + + print("Continue VS Code extension installed successfully. Please restart VS Code to use it.") + + +if __name__ == "__main__": + main() diff --git a/schema/json/FileEdit.json b/schema/json/FileEdit.json index 1f7dcb64..011e0462 100644 --- a/schema/json/FileEdit.json +++ b/schema/json/FileEdit.json @@ -1,6 +1,6 @@ { "title": "FileEdit", - "$ref": "#/definitions/continuedev__src__continuedev__models__filesystem_edit__FileEdit", + "$ref": "#/definitions/src__continuedev__models__filesystem_edit__FileEdit", "definitions": { "Position": { "title": "Position", @@ -37,7 +37,7 @@ "end" ] }, - "continuedev__src__continuedev__models__filesystem_edit__FileEdit": { + "src__continuedev__models__filesystem_edit__FileEdit": { "title": "FileEdit", "type": "object", "properties": { diff --git a/schema/json/FileEditWithFullContents.json b/schema/json/FileEditWithFullContents.json index 571ea1d3..2ea75bab 100644 --- a/schema/json/FileEditWithFullContents.json +++ b/schema/json/FileEditWithFullContents.json @@ -1,6 +1,6 @@ { "title": "FileEditWithFullContents", - "$ref": "#/definitions/continuedev__src__continuedev__models__filesystem_edit__FileEditWithFullContents", + "$ref": "#/definitions/src__continuedev__models__filesystem_edit__FileEditWithFullContents", "definitions": { "Position": { "title": "Position", @@ -59,7 +59,7 @@ "replacement" ] }, - "continuedev__src__continuedev__models__filesystem_edit__FileEditWithFullContents": { + "src__continuedev__models__filesystem_edit__FileEditWithFullContents": { "title": "FileEditWithFullContents", "type": "object", "properties": { diff --git a/schema/json/History.json b/schema/json/History.json index 7691c7dd..14b82d6d 100644 --- a/schema/json/History.json +++ b/schema/json/History.json @@ -1,6 +1,6 @@ { "title": "History", - "$ref": "#/definitions/continuedev__src__continuedev__core__main__History", + "$ref": "#/definitions/src__continuedev__core__main__History", "definitions": { "Step": { "title": "Step", @@ -47,7 +47,7 @@ "depth" ] }, - "continuedev__src__continuedev__core__main__History": { + "src__continuedev__core__main__History": { "title": "History", "description": "A history of steps taken and their results", "type": "object", diff --git a/schema/json/HistoryNode.json b/schema/json/HistoryNode.json index f58b8038..87a8729f 100644 --- a/schema/json/HistoryNode.json +++ b/schema/json/HistoryNode.json @@ -1,6 +1,6 @@ { "title": "HistoryNode", - "$ref": "#/definitions/continuedev__src__continuedev__core__main__HistoryNode", + "$ref": "#/definitions/src__continuedev__core__main__HistoryNode", "definitions": { "Step": { "title": "Step", @@ -26,7 +26,7 @@ "type": "object", "properties": {} }, - "continuedev__src__continuedev__core__main__HistoryNode": { + "src__continuedev__core__main__HistoryNode": { "title": "HistoryNode", "description": "A point in history, a list of which make up History", "type": "object", diff --git a/schema/json/Position.json b/schema/json/Position.json index e550572e..6b272ce7 100644 --- a/schema/json/Position.json +++ b/schema/json/Position.json @@ -1,8 +1,8 @@ { "title": "Position", - "$ref": "#/definitions/continuedev__src__continuedev__models__main__Position", + "$ref": "#/definitions/src__continuedev__models__main__Position", "definitions": { - "continuedev__src__continuedev__models__main__Position": { + "src__continuedev__models__main__Position": { "title": "Position", "type": "object", "properties": { diff --git a/schema/json/Range.json b/schema/json/Range.json index 52953837..75675183 100644 --- a/schema/json/Range.json +++ b/schema/json/Range.json @@ -1,6 +1,6 @@ { "title": "Range", - "$ref": "#/definitions/continuedev__src__continuedev__models__main__Range", + "$ref": "#/definitions/src__continuedev__models__main__Range", "definitions": { "Position": { "title": "Position", @@ -20,7 +20,7 @@ "character" ] }, - "continuedev__src__continuedev__models__main__Range": { + "src__continuedev__models__main__Range": { "title": "Range", "description": "A range in a file. 0-indexed.", "type": "object", diff --git a/schema/json/RangeInFile.json b/schema/json/RangeInFile.json index 022ca1a8..1f5afaa3 100644 --- a/schema/json/RangeInFile.json +++ b/schema/json/RangeInFile.json @@ -1,6 +1,6 @@ { "title": "RangeInFile", - "$ref": "#/definitions/continuedev__src__continuedev__models__filesystem__RangeInFile", + "$ref": "#/definitions/src__continuedev__models__filesystem__RangeInFile", "definitions": { "Position": { "title": "Position", @@ -37,7 +37,7 @@ "end" ] }, - "continuedev__src__continuedev__models__filesystem__RangeInFile": { + "src__continuedev__models__filesystem__RangeInFile": { "title": "RangeInFile", "type": "object", "properties": { diff --git a/schema/json/Traceback.json b/schema/json/Traceback.json index c3e0e416..45606a2b 100644 --- a/schema/json/Traceback.json +++ b/schema/json/Traceback.json @@ -1,6 +1,6 @@ { "title": "Traceback", - "$ref": "#/definitions/continuedev__src__continuedev__models__main__Traceback", + "$ref": "#/definitions/src__continuedev__models__main__Traceback", "definitions": { "TracebackFrame": { "title": "TracebackFrame", @@ -29,7 +29,7 @@ "function" ] }, - "continuedev__src__continuedev__models__main__Traceback": { + "src__continuedev__models__main__Traceback": { "title": "Traceback", "type": "object", "properties": { diff --git a/schema/json/TracebackFrame.json b/schema/json/TracebackFrame.json index 6321e08c..1907430a 100644 --- a/schema/json/TracebackFrame.json +++ b/schema/json/TracebackFrame.json @@ -1,8 +1,8 @@ { "title": "TracebackFrame", - "$ref": "#/definitions/continuedev__src__continuedev__models__main__TracebackFrame", + "$ref": "#/definitions/src__continuedev__models__main__TracebackFrame", "definitions": { - "continuedev__src__continuedev__models__main__TracebackFrame": { + "src__continuedev__models__main__TracebackFrame": { "title": "TracebackFrame", "type": "object", "properties": { -- cgit v1.2.3-70-g09d2 From 4d2aee55162ea5a5ac16f773fc573a555383d90a Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 29 May 2023 12:18:34 -0400 Subject: prompt engineering for faster code edits --- continuedev/src/continuedev/core/policy.py | 4 +- continuedev/src/continuedev/libs/steps/main.py | 108 ++++++++++++++++--------- continuedev/src/continuedev/models/main.py | 2 +- 3 files changed, 74 insertions(+), 40 deletions(-) (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 5a6652f4..504d5ff1 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -6,7 +6,7 @@ from ..models.main import ContinueBaseModel from ..libs.steps.ty import CreatePipelineStep from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation -from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep +from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep from ..libs.steps.nate import WritePytestsStep, CreateTableStep from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..libs.steps.continue_step import ContinueStepStep @@ -32,7 +32,7 @@ class DemoPolicy(Policy): return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) elif "/step" in observation.user_input: return ContinueStepStep(prompt=" ".join(observation.user_input.split(" ")[1:])) - return EditHighlightedCodeStep(user_input=observation.user_input) + return FasterEditHighlightedCodeStep(user_input=observation.user_input) state = history.get_current() if state is None or not self.ran_code_last: diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index f28cb23f..59df114e 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -77,41 +77,45 @@ class FasterEditHighlightedCodeStep(Step): hide = True _completion: str = "Edit Code" _edit_diffs: Union[List[EditDiff], None] = None - _prompt: str = dedent("""Below is the code before changes: - -{code} - -This is the user request: - -{user_input} - -Edit the code to perfectly satifsfy the user request. Format the changes you want to make as a comma-separated array of JSON objects of the form: -{{ - "edits": [{{ - "filepath": , - "replace_me": , - "replace_with": - }}] -}} - -For example, if you want to replace the code `x = 1` with `x = 2` in main.py, you would write: -{{ - "edits": [{{ - "filepath": "main.py", - "replace_me": "x = 1", - "replace_with": "x = 2" - }}] -}} -If you wanted to delete the code `def sum(a, b):\\n return a + b` in main.py, you would write: -{{ - "edits": [{{ - "filepath": "main.py", - "replace_me": "def sum(a, b):\\n return a + b", - "replace_with": "" - }}] -}} - -Respond with only as many edits as needed, and output only the list of json objects, no other text. + _prompt: str = dedent("""\ + You will be given code to edit in order to perfectly satisfy the user request. All the changes you make must be described as replacements, which you should format in the following way: + FILEPATH + + REPLACE_ME + + REPLACE_WITH + + + where and can be multiple lines, but should be the mininum needed to make the edit. Be sure to maintain existing whitespace at the start of lines. + + For example, if you want to replace the code `x = 1` with `x = 2` in main.py, you would write: + FILEPATH + main.py + REPLACE_ME + x = 1 + REPLACE_WITH + x = 2 + If you wanted to delete the code + ``` + def sum(a, b): + return a + b + ``` + in main.py, you would write: + FILEPATH + main.py + REPLACE_ME + def sum(a, b): + return a + b + REPLACE_WITH + + You may need to make multiple edits; respond with exactly as many as needed. + + Below is the code before changes: + + {code} + + This is the user request: "{user_input}" + Here is the description of changes to make: """) async def describe(self, llm: LLM) -> Coroutine[str, None, None]: @@ -148,11 +152,41 @@ Respond with only as many edits as needed, and output only the list of json obje # Temporarily doing this to generate description. self._prompt = prompt self._completion = completion + print(completion) # ALTERNATIVE DECODING STEP HERE + raw_file_edits = [] + lines = completion.split("\n") + current_edit = {} + status = "FILEPATH" + for i in range(0, len(lines)): + line = lines[i] + if line == "FILEPATH": + if "FILEPATH" in current_edit: + raw_file_edits.append(current_edit) + current_edit = {} + status = "FILEPATH" + elif line == "REPLACE_ME": + status = "REPLACE_ME" + elif line == "REPLACE_WITH": + status = "REPLACE_WITH" + elif status == "FILEPATH": + current_edit["filepath"] = line + elif status == "REPLACE_ME": + if "replace_me" in current_edit: + current_edit["replace_me"] += "\n" + line + else: + current_edit["replace_me"] = line + elif status == "REPLACE_WITH": + if "replace_with" in current_edit: + current_edit["replace_with"] += "\n" + line + else: + current_edit["replace_with"] = line + if "filepath" in current_edit: + raw_file_edits.append(current_edit) + file_edits = [] - obj = json.loads(completion.strip()) - for edit in obj["edits"]: + for edit in raw_file_edits: filepath = edit["filepath"] replace_me = edit["replace_me"] replace_with = edit["replace_with"] diff --git a/continuedev/src/continuedev/models/main.py b/continuedev/src/continuedev/models/main.py index 7986b30c..5bd65a76 100644 --- a/continuedev/src/continuedev/models/main.py +++ b/continuedev/src/continuedev/models/main.py @@ -32,7 +32,7 @@ class Position(BaseModel): def from_index(string: str, index: int) -> "Position": """Convert index in string to line and character""" line = string.count("\n", 0, index) - if line == 1: + if line == 0: character = index else: character = index - string.rindex("\n", 0, index) - 1 -- cgit v1.2.3-70-g09d2 From 9a221fda9b44d0dc7ab2637c9a25d1be226b2b32 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 29 May 2023 15:47:36 -0400 Subject: Snippet parsing for FasterEditHighlightedCodeStep --- continuedev/src/continuedev/libs/steps/main.py | 2 +- continuedev/src/continuedev/models/main.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 59df114e..4f4f80e3 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -191,7 +191,7 @@ class FasterEditHighlightedCodeStep(Step): replace_me = edit["replace_me"] replace_with = edit["replace_with"] file_edits.append( - FileEdit(filepath=filepath, range=Range.from_snippet_in_file(content=rif_dict[filepath], snippet=replace_me), replacement=replace_with)) + FileEdit(filepath=filepath, range=Range.from_lines_snippet_in_file(content=rif_dict[filepath], snippet=replace_me), replacement=replace_with)) # ------------------------------ self._edit_diffs = [] diff --git a/continuedev/src/continuedev/models/main.py b/continuedev/src/continuedev/models/main.py index 5bd65a76..1bc51ff1 100644 --- a/continuedev/src/continuedev/models/main.py +++ b/continuedev/src/continuedev/models/main.py @@ -93,6 +93,30 @@ class Range(BaseModel): end_index = start_index + len(snippet) return Range.from_indices(content, start_index, end_index) + @staticmethod + def from_lines_snippet_in_file(content: str, snippet: str) -> "Range": + # lines is a substring of the content modulo whitespace on each line + content_lines = content.splitlines() + snippet_lines = snippet.splitlines() + + start_line = -1 + end_line = -1 + looking_for_line = 0 + for i in range(len(content_lines)): + if content_lines[i].strip() == snippet_lines[looking_for_line].strip(): + if looking_for_line == len(snippet_lines) - 1: + start_line = i - len(snippet_lines) + 1 + end_line = i + break + looking_for_line += 1 + else: + looking_for_line = 0 + + if start_line == -1 or end_line == -1: + raise ValueError("Snippet not found in content") + + return Range.from_shorthand(start_line, 0, end_line, len(content_lines[end_line]) - 1) + class AbstractModel(ABC, BaseModel): @root_validator(pre=True) -- cgit v1.2.3-70-g09d2 From 22245d2cbf90daa9033d8551207aa986069d8b24 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 29 May 2023 18:31:25 -0400 Subject: (much!) faster inference with starcoder --- continuedev/src/continuedev/core/agent.py | 3 +- continuedev/src/continuedev/core/env.py | 19 +++++++- continuedev/src/continuedev/core/policy.py | 4 +- continuedev/src/continuedev/core/sdk.py | 25 ++++++++++ .../src/continuedev/libs/llm/hf_inference_api.py | 25 ++++++++++ continuedev/src/continuedev/libs/steps/main.py | 57 ++++++++++++++++++++-- continuedev/src/continuedev/server/notebook.py | 4 +- .../react-app/src/components/StepContainer.tsx | 21 ++------ 8 files changed, 130 insertions(+), 28 deletions(-) create mode 100644 continuedev/src/continuedev/libs/llm/hf_inference_api.py (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 6d1f542e..329e3d4c 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -49,9 +49,10 @@ class Agent(ContinueBaseModel): async def wait_for_user_input(self) -> str: self._active = False self.update_subscribers() - await self._user_input_queue.get(self.history.current_index) + user_input = await self._user_input_queue.get(self.history.current_index) self._active = True self.update_subscribers() + return user_input _manual_edits_buffer: List[FileEditWithFullContents] = [] diff --git a/continuedev/src/continuedev/core/env.py b/continuedev/src/continuedev/core/env.py index d7275b41..6267ed60 100644 --- a/continuedev/src/continuedev/core/env.py +++ b/continuedev/src/continuedev/core/env.py @@ -1,7 +1,22 @@ from dotenv import load_dotenv import os -load_dotenv() +def get_env_var(var_name: str): + load_dotenv() + return os.getenv(var_name) -openai_api_key = os.getenv("OPENAI_API_KEY") + +def save_env_var(var_name: str, var_value: str): + with open('.env', 'r') as f: + lines = f.readlines() + with open('.env', 'w') as f: + values = {} + for line in lines: + key, value = line.split('=') + value = value.replace('"', '') + values[key] = value + + values[var_name] = var_value + for key, value in values.items(): + f.write(f'{key}="{value}"\n') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 504d5ff1..07101576 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -6,7 +6,7 @@ from ..models.main import ContinueBaseModel from ..libs.steps.ty import CreatePipelineStep from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation -from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep +from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep from ..libs.steps.nate import WritePytestsStep, CreateTableStep from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..libs.steps.continue_step import ContinueStepStep @@ -32,7 +32,7 @@ class DemoPolicy(Policy): return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) elif "/step" in observation.user_input: return ContinueStepStep(prompt=" ".join(observation.user_input.split(" ")[1:])) - return FasterEditHighlightedCodeStep(user_input=observation.user_input) + return StarCoderEditHighlightedCodeStep(user_input=observation.user_input) state = history.get_current() if state is None or not self.ran_code_last: diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 3559e9d7..750b335d 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -3,10 +3,12 @@ from typing import Coroutine, Union from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory from ..models.filesystem import RangeInFile from ..libs.llm import LLM +from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step from ..libs.steps.core.core import * +from .env import get_env_var, save_env_var class Agent: @@ -18,11 +20,22 @@ class ContinueSDKSteps: self.sdk = sdk +class Models: + def __init__(self, sdk: "ContinueSDK"): + self.sdk = sdk + + async def starcoder(self): + api_key = await self.sdk.get_user_secret( + 'HUGGING_FACE_TOKEN', 'Please enter your Hugging Face token') + return HuggingFaceInferenceAPI(api_key=api_key) + + class ContinueSDK: """The SDK provided as parameters to a step""" llm: LLM ide: AbstractIdeProtocolServer steps: ContinueSDKSteps + models: Models __agent: Agent def __init__(self, agent: Agent, llm: Union[LLM, None] = None): @@ -33,6 +46,7 @@ class ContinueSDK: self.ide = agent.ide self.__agent = agent self.steps = ContinueSDKSteps(self) + self.models = Models(self) @property def history(self) -> History: @@ -80,3 +94,14 @@ class ContinueSDK: async def delete_directory(self, path: str): return await self.run_step(FileSystemEditStep(edit=DeleteDirectory(path=path))) + + async def get_user_secret(self, env_var: str, prompt: str) -> str: + try: + val = get_env_var(env_var) + if val is not None: + return val + except: + pass + val = (await self.run_step(WaitForUserInputStep(prompt=prompt))).text + save_env_var(env_var, val) + return val diff --git a/continuedev/src/continuedev/libs/llm/hf_inference_api.py b/continuedev/src/continuedev/libs/llm/hf_inference_api.py new file mode 100644 index 00000000..83852d27 --- /dev/null +++ b/continuedev/src/continuedev/libs/llm/hf_inference_api.py @@ -0,0 +1,25 @@ +from ..llm import LLM +import requests + +DEFAULT_MAX_TOKENS = 2048 +DEFAULT_MAX_TIME = 120. + + +class HuggingFaceInferenceAPI(LLM): + api_key: str + model: str = "bigcode/starcoder" + + def complete(self, prompt: str, **kwargs): + """Return the completion of the text with the given temperature.""" + API_URL = f"https://api-inference.huggingface.co/models/{self.model}" + headers = { + "Authorization": f"Bearer {self.api_key}"} + + response = requests.post(API_URL, headers=headers, json={ + "inputs": prompt, "parameters": { + "max_new_tokens": DEFAULT_MAX_TOKENS, + "max_time": DEFAULT_MAX_TIME, + "return_full_text": False, + } + }) + return response.json()[0]["generated_text"] diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index 4f4f80e3..c8a85800 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -1,4 +1,6 @@ -from typing import Callable, Coroutine, List, Union +from typing import Coroutine, List, Union + +from pydantic import BaseModel from ..util.traceback_parsers import parse_python_traceback from ..llm import LLM @@ -8,9 +10,10 @@ from ...models.filesystem import RangeInFile, RangeInFileWithContents from ...core.observation import Observation, TextObservation, TracebackObservation from ..llm.prompt_utils import MarkdownStyleEncoderDecoder from textwrap import dedent -from ...core.main import History, Policy, Step, ContinueSDK, Observation +from ...core.main import Step +from ...core.sdk import ContinueSDK +from ...core.observation import Observation import subprocess -import json from .core.core import EditCodeStep @@ -36,6 +39,10 @@ class RunCodeStep(Step): return None +class Policy(BaseModel): + pass + + class RunPolicyUntilDoneStep(Step): policy: "Policy" @@ -206,6 +213,50 @@ class FasterEditHighlightedCodeStep(Step): return None +class StarCoderEditHighlightedCodeStep(Step): + user_input: str + hide = True + _prompt: str = "{code}{user_request}" + + async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + return "Editing highlighted code" + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + range_in_files = await sdk.ide.getHighlightedCode() + if len(range_in_files) == 0: + # Get the full contents of all open files + files = await sdk.ide.getOpenFiles() + contents = {} + for file in files: + contents[file] = await sdk.ide.readFile(file) + + range_in_files = [RangeInFile.from_entire_file( + filepath, content) for filepath, content in contents.items()] + + rif_with_contents = [] + for range_in_file in range_in_files: + file_contents = await sdk.ide.readRangeInFile(range_in_file) + rif_with_contents.append( + RangeInFileWithContents.from_range_in_file(range_in_file, file_contents)) + + rif_dict = {} + for rif in rif_with_contents: + rif_dict[rif.filepath] = rif.contents + + for rif in rif_with_contents: + prompt = self._prompt.format( + code=rif.contents, user_request=self.user_input) + completion = str((await sdk.models.starcoder()).complete(prompt)) + eot_token = "<|endoftext|>" + if completion.endswith(eot_token): + completion = completion[:completion.rindex(eot_token)] + + await sdk.ide.applyFileSystemEdit( + FileEdit(filepath=rif.filepath, range=rif.range, replacement=completion)) + await sdk.ide.saveFile(rif.filepath) + await sdk.ide.setFileOpen(rif.filepath) + + class EditHighlightedCodeStep(Step): user_input: str hide = True diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index bfd7a09c..c5dcea31 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -12,7 +12,7 @@ from ..libs.steps.nate import ImplementAbstractMethodStep from ..core.observation import Observation from ..libs.llm.openai import OpenAI from .ide_protocol import AbstractIdeProtocolServer -from ..core.env import openai_api_key +from ..core.env import get_env_var import asyncio import nest_asyncio nest_asyncio.apply() @@ -75,7 +75,7 @@ class SessionManager: def new_session(self, ide: AbstractIdeProtocolServer) -> str: cmd = "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py" - agent = DemoAgent(llm=OpenAI(api_key=openai_api_key), + agent = DemoAgent(llm=OpenAI(api_key=get_env_var("OPENAI_API_KEY")), policy=DemoPolicy(cmd=cmd), ide=ide) session_id = str(uuid4()) session = Session(session_id=session_id, agent=agent) diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 03649b66..36b3d99a 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -144,6 +144,9 @@ function StepContainer(props: StepContainerProps) { onSubmit={(ev) => { props.onUserInput(ev.currentTarget.value); }} + onClick={(e) => { + e.stopPropagation(); + }} /> )} {props.historyNode.step.name === "Waiting for user confirmation" && ( @@ -165,24 +168,6 @@ function StepContainer(props: StepContainerProps) { /> )} - - {open && ( - <> - {/* {props.historyNode.observation && ( - - Error Here - - )} */} - {/* {props.iterationContext.suggestedChanges.map((sc) => { - return ( - - {sc.filepath} - {sc.replacement} - - ); - })} */} - - )} -- cgit v1.2.3-70-g09d2 From 9171567ebf2c0ddf7c430a160a50d0d47c0c991b Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 30 May 2023 08:41:56 -0400 Subject: rename agent to autopilot --- continuedev/src/continuedev/core/agent.py | 180 +++++++++++++++++++++ continuedev/src/continuedev/libs/core.py | 22 +-- .../src/continuedev/libs/util/copy_codebase.py | 16 +- continuedev/src/continuedev/server/ide.py | 16 +- continuedev/src/continuedev/server/notebook.py | 40 ++--- docs/docs/concepts/agent.md | 7 +- docs/docs/concepts/core.md | 5 +- docs/docs/concepts/llm.md | 2 +- docs/sidebars.js | 51 +++--- 9 files changed, 260 insertions(+), 79 deletions(-) create mode 100644 continuedev/src/continuedev/core/agent.py (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py new file mode 100644 index 00000000..1996abb1 --- /dev/null +++ b/continuedev/src/continuedev/core/agent.py @@ -0,0 +1,180 @@ +import traceback +import time +from typing import Callable, Coroutine, List +from ..models.filesystem_edit import FileEditWithFullContents +from ..libs.llm import LLM +from .observation import Observation +from ..server.ide_protocol import AbstractIdeProtocolServer +from ..libs.util.queue import AsyncSubscriptionQueue +from ..models.main import ContinueBaseModel +from .main import Policy, History, FullState, Step, HistoryNode +from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep +from .sdk import ContinueSDK + + +class Autopilot(ContinueBaseModel): + llm: LLM + policy: Policy + ide: AbstractIdeProtocolServer + history: History = History.from_empty() + _on_update_callbacks: List[Callable[[FullState], None]] = [] + + _active: bool = False + _should_halt: bool = False + _main_user_input_queue: List[str] = [] + + _user_input_queue = AsyncSubscriptionQueue() + + class Config: + arbitrary_types_allowed = True + + def get_full_state(self) -> FullState: + return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) + + def on_update(self, callback: Callable[["FullState"], None]): + """Subscribe to changes to state""" + self._on_update_callbacks.append(callback) + + def update_subscribers(self): + full_state = self.get_full_state() + for callback in self._on_update_callbacks: + callback(full_state) + + def __get_step_params(self, step: "Step"): + return ContinueSDK(autopilot=self, llm=self.llm.with_system_message(step.system_message)) + + def give_user_input(self, input: str, index: int): + self._user_input_queue.post(index, input) + + async def wait_for_user_input(self) -> str: + self._active = False + self.update_subscribers() + user_input = await self._user_input_queue.get(self.history.current_index) + self._active = True + self.update_subscribers() + return user_input + + _manual_edits_buffer: List[FileEditWithFullContents] = [] + + async def reverse_to_index(self, index: int): + try: + while self.history.get_current_index() >= index: + current_step = self.history.get_current().step + self.history.step_back() + if issubclass(current_step.__class__, ReversibleStep): + await current_step.reverse(self.__get_step_params(current_step)) + + self.update_subscribers() + except Exception as e: + print(e) + + def handle_manual_edits(self, edits: List[FileEditWithFullContents]): + for edit in edits: + self._manual_edits_buffer.append(edit) + # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. + # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) + + def handle_traceback(self, traceback: str): + raise NotImplementedError + + _step_depth: int = 0 + + async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: + if not is_future_step: + # Check manual edits buffer, clear out if needed by creating a ManualEditStep + if len(self._manual_edits_buffer) > 0: + manualEditsStep = ManualEditStep.from_sequence( + self._manual_edits_buffer) + self._manual_edits_buffer = [] + await self._run_singular_step(manualEditsStep) + + # Update history - do this first so we get top-first tree ordering + self.history.add_node(HistoryNode( + step=step, observation=None, depth=self._step_depth)) + + # Run step + self._step_depth += 1 + observation = await step(self.__get_step_params(step)) + self._step_depth -= 1 + + # Add observation to history + self.history.get_current().observation = observation + + # Update its description + step._set_description(await step.describe(self.llm)) + + # Call all subscribed callbacks + self.update_subscribers() + + return observation + + async def run_from_step(self, step: "Step"): + # if self._active: + # raise RuntimeError("Autopilot is already running") + self._active = True + + next_step = step + is_future_step = False + while not (next_step is None or self._should_halt): + try: + if is_future_step: + # If future step, then we are replaying and need to delete the step from history so it can be replaced + self.history.remove_current_and_substeps() + + observation = await self._run_singular_step(next_step, is_future_step) + if next_step := self.policy.next(self.history): + is_future_step = False + elif next_step := self.history.take_next_step(): + is_future_step = True + else: + next_step = None + + except Exception as e: + print( + f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") + next_step = None + + self._active = False + + # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools + for callback in self._on_update_callbacks: + callback(None) + + async def run_from_observation(self, observation: Observation): + next_step = self.policy.next(self.history) + await self.run_from_step(next_step) + + async def run_policy(self): + first_step = self.policy.next(self.history) + await self.run_from_step(first_step) + + async def _request_halt(self): + if self._active: + self._should_halt = True + while self._active: + time.sleep(0.1) + self._should_halt = False + return None + + async def accept_user_input(self, user_input: str): + self._main_user_input_queue.append(user_input) + self.update_subscribers() + + if len(self._main_user_input_queue) > 1: + return + + # await self._request_halt() + # Just run the step that takes user input, and + # then up to the policy to decide how to deal with it. + self._main_user_input_queue.pop(0) + self.update_subscribers() + await self.run_from_step(UserInputStep(user_input=user_input)) + + while len(self._main_user_input_queue) > 0: + await self.run_from_step(UserInputStep( + user_input=self._main_user_input_queue.pop(0))) + + async def accept_refinement_input(self, user_input: str, index: int): + await self._request_halt() + await self.reverse_to_index(index) + await self.run_from_step(UserInputStep(user_input=user_input)) diff --git a/continuedev/src/continuedev/libs/core.py b/continuedev/src/continuedev/libs/core.py index 6a8a83ba..36d94ad1 100644 --- a/continuedev/src/continuedev/libs/core.py +++ b/continuedev/src/continuedev/libs/core.py @@ -89,31 +89,31 @@ class ContinueSDK: """The SDK provided as parameters to a step""" llm: LLM ide: AbstractIdeProtocolServer - __agent: "Agent" + __autopilot: "Autopilot" - def __init__(self, agent: "Agent", llm: Union[LLM, None] = None): + def __init__(self, autopilot: "Autopilot", llm: Union[LLM, None] = None): if llm is None: - self.llm = agent.llm + self.llm = autopilot.llm else: self.llm = llm - self.ide = agent.ide - self.__agent = agent + self.ide = autopilot.ide + self.__autopilot = autopilot @property def history(self) -> History: - return self.__agent.history + return self.__autopilot.history async def run_step(self, step: "Step") -> Coroutine[Observation, None, None]: - return await self.__agent._run_singular_step(step) + return await self.__autopilot._run_singular_step(step) async def apply_filesystem_edit(self, edit: FileSystemEdit): await self.run_step(FileSystemEditStep(edit=edit)) async def wait_for_user_input(self) -> str: - return await self.__agent.wait_for_user_input() + return await self.__autopilot.wait_for_user_input() -class Agent(ContinueBaseModel): +class Autopilot(ContinueBaseModel): llm: LLM policy: Policy ide: AbstractIdeProtocolServer @@ -142,7 +142,7 @@ class Agent(ContinueBaseModel): callback(full_state) def __get_step_params(self, step: "Step"): - return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message)) + return ContinueSDK(autopilot=self, llm=self.llm.with_system_message(step.system_message)) def give_user_input(self, input: str, index: int): self._user_input_queue.post(index, input) @@ -210,7 +210,7 @@ class Agent(ContinueBaseModel): async def run_from_step(self, step: "Step"): # if self._active: - # raise RuntimeError("Agent is already running") + # raise RuntimeError("Autopilot is already running") self._active = True next_step = step diff --git a/continuedev/src/continuedev/libs/util/copy_codebase.py b/continuedev/src/continuedev/libs/util/copy_codebase.py index ef1db72b..af957a34 100644 --- a/continuedev/src/continuedev/libs/util/copy_codebase.py +++ b/continuedev/src/continuedev/libs/util/copy_codebase.py @@ -5,7 +5,7 @@ from watchdog.observers import Observer from watchdog.events import PatternMatchingEventHandler from ..models.main import FileEdit, DeleteDirectory, DeleteFile, AddDirectory, AddFile, FileSystemEdit, Position, Range, RenameFile, RenameDirectory, SequentialFileSystemEdit from ..models.filesystem import FileSystem -from ..libs.main import Agent +from ..libs.main import Autopilot from ..libs.map_path import map_path from ..libs.steps.main import ManualEditAction import shutil @@ -65,15 +65,15 @@ def calculate_diff(filepath: str, original: str, updated: str) -> List[FileEdit] # The whole usage of watchdog here should only be specific to RealFileSystem, you want to have a different "Observer" class for VirtualFileSystem, which would depend on being sent notifications class CopyCodebaseEventHandler(PatternMatchingEventHandler): - def __init__(self, ignore_directories: List[str], ignore_patterns: List[str], agent: Agent, orig_root: str, copy_root: str, filesystem: FileSystem): + def __init__(self, ignore_directories: List[str], ignore_patterns: List[str], autopilot: Autopilot, orig_root: str, copy_root: str, filesystem: FileSystem): super().__init__(ignore_directories=ignore_directories, ignore_patterns=ignore_patterns) - self.agent = agent + self.autopilot = autopilot self.orig_root = orig_root self.copy_root = copy_root self.filesystem = filesystem - # For now, we'll just make the update immediately, but eventually need to sync with agent. - # It should be the agent that makes the update right? It's just another action, everything comes from a single stream. + # For now, we'll just make the update immediately, but eventually need to sync with autopilot. + # It should be the autopilot that makes the update right? It's just another action, everything comes from a single stream. def _event_to_edit(self, event) -> Union[FileSystemEdit, None]: # NOTE: You'll need to map paths to create both an action within the copy filesystem (the one you take) and one in the original fileystem (the one you'll record and allow the user to accept). Basically just need a converter built in to the FileSystemEdit class @@ -110,13 +110,13 @@ class CopyCodebaseEventHandler(PatternMatchingEventHandler): return edit = edit.with_mapped_paths(self.orig_root, self.copy_root) action = ManualEditAction(edit) - self.agent.act(action) + self.autopilot.act(action) -def maintain_copy_workspace(agent: Agent, filesystem: FileSystem, orig_root: str, copy_root: str): +def maintain_copy_workspace(autopilot: Autopilot, filesystem: FileSystem, orig_root: str, copy_root: str): observer = Observer() event_handler = CopyCodebaseEventHandler( - [".git"], [], agent, orig_root, copy_root, filesystem) + [".git"], [], autopilot, orig_root, copy_root, filesystem) observer.schedule(event_handler, orig_root, recursive=True) observer.start() try: diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index dd1dc463..167d9483 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -122,7 +122,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): pass async def setFileOpen(self, filepath: str, open: bool = True): - # Agent needs access to this. + # Autopilot needs access to this. await self.websocket.send_json({ "messageType": "setFileOpen", "filepath": filepath, @@ -147,12 +147,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer): responses = await asyncio.gather(*[ self._receive_json(ShowSuggestionResponse) for i in range(len(suggestions)) - ]) # WORKING ON THIS FLOW HERE. Fine now to just await for response, instead of doing something fancy with a "waiting" state on the agent. + ]) # WORKING ON THIS FLOW HERE. Fine now to just await for response, instead of doing something fancy with a "waiting" state on the autopilot. # Just need connect the suggestionId to the IDE (and the notebook) return any([r.accepted for r in responses]) # ------------------------------- # - # Here needs to pass message onto the Agent OR Agent just subscribes. + # Here needs to pass message onto the Autopilot OR Autopilot just subscribes. # This is where you might have triggers: plugins can subscribe to certian events # like file changes, tracebacks, etc... @@ -160,12 +160,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer): pass def onTraceback(self, traceback: Traceback): - # Same as below, maybe not every agent? + # Same as below, maybe not every autopilot? for _, session in self.session_manager.sessions.items(): - session.agent.handle_traceback(traceback) + session.autopilot.handle_traceback(traceback) def onFileSystemUpdate(self, update: FileSystemEdit): - # Access to Agent (so SessionManager) + # Access to Autopilot (so SessionManager) pass def onCloseNotebook(self, session_id: str): @@ -176,10 +176,10 @@ class IdeProtocolServer(AbstractIdeProtocolServer): pass def onFileEdits(self, edits: List[FileEditWithFullContents]): - # Send the file edits to ALL agents. + # Send the file edits to ALL autopilots. # Maybe not ideal behavior for _, session in self.session_manager.sessions.items(): - session.agent.handle_manual_edits(edits) + session.autopilot.handle_manual_edits(edits) # Request information. Session doesn't matter. async def getOpenFiles(self) -> List[str]: diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index c9d4edc5..c26920f5 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -6,7 +6,7 @@ from uvicorn.main import Server from ..models.filesystem_edit import FileEditWithFullContents from ..libs.policy import DemoPolicy -from ..libs.core import Agent, FullState, History, Step +from ..libs.core import Autopilot, FullState, History, Step from ..libs.steps.nate import ImplementAbstractMethodStep from ..libs.observation import Observation from dotenv import load_dotenv @@ -41,16 +41,16 @@ Server.handle_exit = AppStatus.handle_exit class Session: session_id: str - agent: Agent + autopilot: Autopilot ws: Union[WebSocket, None] - def __init__(self, session_id: str, agent: Agent): + def __init__(self, session_id: str, autopilot: Autopilot): self.session_id = session_id - self.agent = agent + self.autopilot = autopilot self.ws = None -class DemoAgent(Agent): +class DemoAutopilot(Autopilot): first_seen: bool = False cumulative_edit_string = "" @@ -78,20 +78,20 @@ class SessionManager: def new_session(self, ide: AbstractIdeProtocolServer) -> str: cmd = "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py" - agent = DemoAgent(llm=OpenAI(api_key=openai_api_key), - policy=DemoPolicy(cmd=cmd), ide=ide) + autopilot = DemoAutopilot(llm=OpenAI(api_key=openai_api_key), + policy=DemoPolicy(cmd=cmd), ide=ide) session_id = str(uuid4()) - session = Session(session_id=session_id, agent=agent) + session = Session(session_id=session_id, autopilot=autopilot) self.sessions[session_id] = session def on_update(state: FullState): session_manager.send_ws_data(session_id, { "messageType": "state", - "state": agent.get_full_state().dict() + "state": autopilot.get_full_state().dict() }) - agent.on_update(on_update) - asyncio.create_task(agent.run_policy()) + autopilot.on_update(on_update) + asyncio.create_task(autopilot.run_policy()) return session_id def remove_session(self, session_id: str): @@ -147,7 +147,7 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we # Update any history that may have happened before connection await websocket.send_json({ "messageType": "state", - "state": session_manager.get_session(session.session_id).agent.get_full_state().dict() + "state": session_manager.get_session(session.session_id).autopilot.get_full_state().dict() }) print("Session started", data) while AppStatus.should_exit is False: @@ -162,17 +162,17 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we if messageType == "main_input": # Do something with user input asyncio.create_task( - session.agent.accept_user_input(data["value"])) + session.autopilot.accept_user_input(data["value"])) elif messageType == "step_user_input": asyncio.create_task( - session.agent.give_user_input(data["value"], data["index"])) + session.autopilot.give_user_input(data["value"], data["index"])) elif messageType == "refinement_input": asyncio.create_task( - session.agent.accept_refinement_input(data["value"], data["index"])) + session.autopilot.accept_refinement_input(data["value"], data["index"])) elif messageType == "reverse": # Reverse the history to the given index asyncio.create_task( - session.agent.reverse_to_index(data["index"])) + session.autopilot.reverse_to_index(data["index"])) except Exception as e: print(e) @@ -182,17 +182,17 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we @router.post("/run") def request_run(step: Step, session=Depends(session)): - """Tell an agent to take a specific action.""" - asyncio.create_task(session.agent.run_from_step(step)) + """Tell an autopilot to take a specific action.""" + asyncio.create_task(session.autopilot.run_from_step(step)) return "Success" @router.get("/history") def get_history(session=Depends(session)) -> History: - return session.agent.history + return session.autopilot.history @router.post("/observation") def post_observation(observation: Observation, session=Depends(session)): - asyncio.create_task(session.agent.run_from_observation(observation)) + asyncio.create_task(session.autopilot.run_from_observation(observation)) return "Success" diff --git a/docs/docs/concepts/agent.md b/docs/docs/concepts/agent.md index 0528b305..e2fa6832 100644 --- a/docs/docs/concepts/agent.md +++ b/docs/docs/concepts/agent.md @@ -1,9 +1,10 @@ -# Agent +# Autopilot + +`Autopilot` contains the -`Agent` contains the - History - LLM - Policy - IDE -**Q: should we really call this abstraction agent?** \ No newline at end of file +**Q: should we really call this abstraction autopilot?** diff --git a/docs/docs/concepts/core.md b/docs/docs/concepts/core.md index ee58cbb2..d60f46ac 100644 --- a/docs/docs/concepts/core.md +++ b/docs/docs/concepts/core.md @@ -3,11 +3,12 @@ The `Core` connects the SDK and GUI with the IDE (i.e. in VS Code, a web browser, etc), enabling the steps to make changes to your code and accelerate your software development workflows. The `Core` includes + - IDE protocol - GUI protocol - SDK -- Agent +- Autopilot There is a two-way sync between an IDE and the GUI that happens through Core. -**Q: does this make sense as a concept?** \ No newline at end of file +**Q: does this make sense as a concept?** diff --git a/docs/docs/concepts/llm.md b/docs/docs/concepts/llm.md index 11bbacc7..8c2dbcba 100644 --- a/docs/docs/concepts/llm.md +++ b/docs/docs/concepts/llm.md @@ -4,4 +4,4 @@ **Q: should we call this LLM? Perhaps just model?** -**Q: should this abstraction be connected to agent?** \ No newline at end of file +**Q: should this abstraction be connected to autopilot?** diff --git a/docs/sidebars.js b/docs/sidebars.js index befe4feb..d2c91388 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -13,41 +13,40 @@ /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { - docsSidebar: [ - 'intro', - 'getting-started', - 'install', - 'how-continue-works', + "intro", + "getting-started", + "install", + "how-continue-works", { - type: 'category', - label: 'Walkthroughs', + type: "category", + label: "Walkthroughs", items: [ - 'walkthroughs/use-the-gui', - 'walkthroughs/use-a-recipe', - 'walkthroughs/create-a-recipe', - 'walkthroughs/share-a-recipe', + "walkthroughs/use-the-gui", + "walkthroughs/use-a-recipe", + "walkthroughs/create-a-recipe", + "walkthroughs/share-a-recipe", ], }, { - type: 'category', - label: 'Concepts', + type: "category", + label: "Concepts", items: [ - 'concepts/agent', - 'concepts/core', - 'concepts/gui', - 'concepts/history', - 'concepts/ide', - 'concepts/llm', - 'concepts/policy', - 'concepts/recipe', - 'concepts/sdk', - 'concepts/step', + "concepts/autopilot", + "concepts/core", + "concepts/gui", + "concepts/history", + "concepts/ide", + "concepts/llm", + "concepts/policy", + "concepts/recipe", + "concepts/sdk", + "concepts/step", ], }, - 'sdk', - 'telemetry', + "sdk", + "telemetry", ], }; -module.exports = sidebars; \ No newline at end of file +module.exports = sidebars; -- cgit v1.2.3-70-g09d2 From 64333e3694e8de0f0681d076ec5f7a54a291bf68 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Tue, 30 May 2023 08:48:27 -0400 Subject: delete plugin directory --- continuedev/src/continuedev/plugins/__init__.py | 26 ---------------------- continuedev/src/continuedev/plugins/load.py | 21 ----------------- .../src/continuedev/plugins/policy/__init__.py | 4 ---- .../src/continuedev/plugins/policy/hookspecs.py | 10 --------- .../continuedev/plugins/policy/libs/__init__.py | 0 .../continuedev/plugins/policy/libs/alternate.py | 22 ------------------ .../src/continuedev/plugins/step/__init__.py | 4 ---- .../src/continuedev/plugins/step/hookspecs.py | 13 ----------- .../src/continuedev/plugins/step/libs/__init__.py | 0 .../continuedev/plugins/step/libs/hello_world.py | 9 -------- 10 files changed, 109 deletions(-) delete mode 100644 continuedev/src/continuedev/plugins/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/load.py delete mode 100644 continuedev/src/continuedev/plugins/policy/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/policy/hookspecs.py delete mode 100644 continuedev/src/continuedev/plugins/policy/libs/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/policy/libs/alternate.py delete mode 100644 continuedev/src/continuedev/plugins/step/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/step/hookspecs.py delete mode 100644 continuedev/src/continuedev/plugins/step/libs/__init__.py delete mode 100644 continuedev/src/continuedev/plugins/step/libs/hello_world.py (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/plugins/__init__.py b/continuedev/src/continuedev/plugins/__init__.py deleted file mode 100644 index 0ce6d079..00000000 --- a/continuedev/src/continuedev/plugins/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import List -import pluggy -from .step import hookspecs -from .step.libs import hello_world - -builtin_libs = [hello_world] - -def get_plugin_manager(use_plugins: List[str]) -> pluggy.PluginManager: - pm = pluggy.PluginManager("continue.step") - pm.add_hookspecs(hookspecs) - pm.load_setuptools_entrypoints("continue.step") - - # Only use plugins that are specified in the config file - for plugin, name in pm.list_name_plugin(): - if name not in use_plugins: - pm.set_blocked(plugin) - - # Print warning if plugin not found - for name in use_plugins: - if not pm.has_plugin(name): - print(f"Plugin {name} not found.") - - for lib in builtin_libs: - pm.register(lib) - - return pm \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/load.py b/continuedev/src/continuedev/plugins/load.py deleted file mode 100644 index adbaad09..00000000 --- a/continuedev/src/continuedev/plugins/load.py +++ /dev/null @@ -1,21 +0,0 @@ -def load_validator_plugin(config: ValidatorPluginConfig) -> Validator: - if config.name == "continue.tb_validator": - return PythonTracebackValidator(config.cmd, config.cwd) - elif config.name == "continue.pytest_validator": - return PytestValidator(cwd=config.cwd) - else: - raise KeyError("Unknown validator plugin name") - -def load_llm_plugin(config: LLMPluginConfig) -> LLM: - if config.provider == "openai": - return OpenAI(api_key=config.api_key) - else: - raise KeyError("Unknown LLM provider: " + config.provider) - -def load_policy_plugin(config: PolicyPluginConfig) -> Policy: - if config.name == "continue.random_policy": - return RandomPolicy() - elif config.name == "continue.dfs_policy": - return DFSPolicy() - else: - raise KeyError("Unknown policy plugin name") \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/policy/__init__.py b/continuedev/src/continuedev/plugins/policy/__init__.py deleted file mode 100644 index b9722bae..00000000 --- a/continuedev/src/continuedev/plugins/policy/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import pluggy - -hookimpl = pluggy.HookimplMarker("continue.policy") -"""Marker to be imported and used in plugins (and for own implementations)""" \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/policy/hookspecs.py b/continuedev/src/continuedev/plugins/policy/hookspecs.py deleted file mode 100644 index abe932d3..00000000 --- a/continuedev/src/continuedev/plugins/policy/hookspecs.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import List, Tuple -import pluggy -from ...libs.policy import Policy, Step - -hookspec = pluggy.HookspecMarker("continue.policy") - -class PolicyPlugin(Policy): - @hookspec - def next(self) -> Step: - """Get the next step to run""" \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/policy/libs/__init__.py b/continuedev/src/continuedev/plugins/policy/libs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/continuedev/src/continuedev/plugins/policy/libs/alternate.py b/continuedev/src/continuedev/plugins/policy/libs/alternate.py deleted file mode 100644 index 5bfbb821..00000000 --- a/continuedev/src/continuedev/plugins/policy/libs/alternate.py +++ /dev/null @@ -1,22 +0,0 @@ -from plugins import policy -from ....libs.observation import Observation -from ....libs.steps import Step -from ....libs.core import History - - -class AlternatingPolicy: - """A Policy that alternates between two steps.""" - - def __init__(self, first: Step, second: Step): - self.first = first - self.second = second - self.last_was_first = False - - @policy.hookimpl - def next(self, history: History) -> Step: - if self.last_was_first: - self.last_was_first = False - return self.second - else: - self.last_was_first = True - return self.first diff --git a/continuedev/src/continuedev/plugins/step/__init__.py b/continuedev/src/continuedev/plugins/step/__init__.py deleted file mode 100644 index e6d8cd3b..00000000 --- a/continuedev/src/continuedev/plugins/step/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import pluggy - -hookimpl = pluggy.HookimplMarker("continue.step") -"""Marker to be imported and used in plugins (and for own implementations)""" \ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/step/hookspecs.py b/continuedev/src/continuedev/plugins/step/hookspecs.py deleted file mode 100644 index 4309bad3..00000000 --- a/continuedev/src/continuedev/plugins/step/hookspecs.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Coroutine -import pluggy -from ...libs.core import ContinueSDK, Step, Observation - -hookspec = pluggy.HookspecMarker("continue.step") - -# Perhaps Actions should be generic about what their inputs must be. - - -class StepPlugin(Step): - @hookspec - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - """Run""" diff --git a/continuedev/src/continuedev/plugins/step/libs/__init__.py b/continuedev/src/continuedev/plugins/step/libs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/continuedev/src/continuedev/plugins/step/libs/hello_world.py b/continuedev/src/continuedev/plugins/step/libs/hello_world.py deleted file mode 100644 index 72255bfd..00000000 --- a/continuedev/src/continuedev/plugins/step/libs/hello_world.py +++ /dev/null @@ -1,9 +0,0 @@ -from ....plugins import step -from ....libs.steps import ContinueSDK - - -class HelloWorldStep: - """A Step that prints "Hello World!".""" - @step.hookimpl - def run(sdk: ContinueSDK): - print("Hello World!") -- cgit v1.2.3-70-g09d2 From dd5b9f6b7f08f178d6034a57f97faea38442eb0a Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Wed, 31 May 2023 16:13:01 -0400 Subject: checkpoint! protocol reform and it works now --- continuedev/src/continuedev/core/agent.py | 34 ++-- continuedev/src/continuedev/core/env.py | 4 + continuedev/src/continuedev/core/main.py | 14 +- continuedev/src/continuedev/core/policy.py | 17 +- continuedev/src/continuedev/core/sdk.py | 13 +- continuedev/src/continuedev/libs/steps/chroma.py | 2 +- .../src/continuedev/libs/steps/core/core.py | 33 ++-- .../src/continuedev/libs/steps/draft/dlt.py | 2 +- continuedev/src/continuedev/libs/steps/main.py | 20 +- .../src/continuedev/libs/steps/migration.py | 2 +- continuedev/src/continuedev/libs/steps/nate.py | 6 +- continuedev/src/continuedev/libs/steps/pytest.py | 2 +- continuedev/src/continuedev/libs/steps/ty.py | 2 +- continuedev/src/continuedev/server/ide.py | 50 ++--- continuedev/src/continuedev/server/main.py | 2 +- continuedev/src/continuedev/server/notebook.py | 207 +++++++-------------- .../src/continuedev/server/notebook_protocol.py | 28 +++ .../src/continuedev/server/session_manager.py | 101 ++++++++++ extension/package-lock.json | 19 ++ extension/package.json | 7 +- .../src/hooks/ContinueNotebookClientProtocol.ts | 13 ++ extension/react-app/src/hooks/messenger.ts | 108 +++++++++++ .../src/hooks/useContinueNotebookProtocol.ts | 49 +++++ extension/react-app/src/hooks/useWebsocket.ts | 171 +++-------------- extension/react-app/src/hooks/vscodeMessenger.ts | 68 +++++++ extension/react-app/src/tabs/notebook.tsx | 70 +++---- extension/react-app/src/vscode/index.ts | 1 + extension/react-app/tsconfig.json | 2 +- .../scripts/continuedev-0.1.0-py3-none-any.whl | Bin 53104 -> 56070 bytes extension/src/activation/activate.ts | 4 +- extension/src/activation/environmentSetup.ts | 104 +++++++---- extension/src/commands.ts | 4 +- extension/src/continueIdeClient.ts | 146 ++++----------- extension/src/debugPanel.ts | 26 ++- extension/src/extension.ts | 7 +- extension/src/test/runTest.ts | 30 +-- extension/src/util/messenger.ts | 108 +++++++++++ 37 files changed, 864 insertions(+), 612 deletions(-) create mode 100644 continuedev/src/continuedev/server/notebook_protocol.py create mode 100644 continuedev/src/continuedev/server/session_manager.py create mode 100644 extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts create mode 100644 extension/react-app/src/hooks/messenger.ts create mode 100644 extension/react-app/src/hooks/useContinueNotebookProtocol.ts create mode 100644 extension/react-app/src/hooks/vscodeMessenger.ts create mode 100644 extension/src/util/messenger.ts (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py index 329e3d4c..7f7466a2 100644 --- a/continuedev/src/continuedev/core/agent.py +++ b/continuedev/src/continuedev/core/agent.py @@ -13,7 +13,6 @@ from .sdk import ContinueSDK class Agent(ContinueBaseModel): - llm: LLM policy: Policy ide: AbstractIdeProtocolServer history: History = History.from_empty() @@ -31,27 +30,24 @@ class Agent(ContinueBaseModel): def get_full_state(self) -> FullState: return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) - def on_update(self, callback: Callable[["FullState"], None]): + def on_update(self, callback: Coroutine["FullState", None, None]): """Subscribe to changes to state""" self._on_update_callbacks.append(callback) - def update_subscribers(self): + async def update_subscribers(self): full_state = self.get_full_state() for callback in self._on_update_callbacks: - callback(full_state) - - def __get_step_params(self, step: "Step"): - return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message)) + await callback(full_state) def give_user_input(self, input: str, index: int): - self._user_input_queue.post(index, input) + self._user_input_queue.post(str(index), input) async def wait_for_user_input(self) -> str: self._active = False - self.update_subscribers() - user_input = await self._user_input_queue.get(self.history.current_index) + await self.update_subscribers() + user_input = await self._user_input_queue.get(str(self.history.current_index)) self._active = True - self.update_subscribers() + await self.update_subscribers() return user_input _manual_edits_buffer: List[FileEditWithFullContents] = [] @@ -62,9 +58,9 @@ class Agent(ContinueBaseModel): current_step = self.history.get_current().step self.history.step_back() if issubclass(current_step.__class__, ReversibleStep): - await current_step.reverse(self.__get_step_params(current_step)) + await current_step.reverse(ContinueSDK(self)) - self.update_subscribers() + await self.update_subscribers() except Exception as e: print(e) @@ -94,17 +90,17 @@ class Agent(ContinueBaseModel): # Run step self._step_depth += 1 - observation = await step(self.__get_step_params(step)) + observation = await step(ContinueSDK(self)) self._step_depth -= 1 # Add observation to history self.history.get_current().observation = observation # Update its description - step._set_description(await step.describe(self.llm)) + step._set_description(await step.describe(ContinueSDK(self))) # Call all subscribed callbacks - self.update_subscribers() + await self.update_subscribers() return observation @@ -138,7 +134,7 @@ class Agent(ContinueBaseModel): # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools for callback in self._on_update_callbacks: - callback(None) + await callback(None) async def run_from_observation(self, observation: Observation): next_step = self.policy.next(self.history) @@ -158,7 +154,7 @@ class Agent(ContinueBaseModel): async def accept_user_input(self, user_input: str): self._main_user_input_queue.append(user_input) - self.update_subscribers() + await self.update_subscribers() if len(self._main_user_input_queue) > 1: return @@ -167,7 +163,7 @@ class Agent(ContinueBaseModel): # Just run the step that takes user input, and # then up to the policy to decide how to deal with it. self._main_user_input_queue.pop(0) - self.update_subscribers() + await self.update_subscribers() await self.run_from_step(UserInputStep(user_input=user_input)) while len(self._main_user_input_queue) > 0: diff --git a/continuedev/src/continuedev/core/env.py b/continuedev/src/continuedev/core/env.py index 6267ed60..edd3297c 100644 --- a/continuedev/src/continuedev/core/env.py +++ b/continuedev/src/continuedev/core/env.py @@ -8,6 +8,10 @@ def get_env_var(var_name: str): def save_env_var(var_name: str, var_value: str): + if not os.path.exists('.env'): + with open('.env', 'w') as f: + f.write(f'{var_name}="{var_value}"\n') + return with open('.env', 'r') as f: lines = f.readlines() with open('.env', 'w') as f: diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index 51fcd299..6be5139b 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -72,7 +72,7 @@ class ContinueSDK: pass -class SequentialStep: +class Models: pass @@ -94,7 +94,7 @@ class Step(ContinueBaseModel): class Config: copy_on_model_validation = False - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: if self._description is not None: return self._description return "Running step: " + self.name @@ -135,6 +135,16 @@ class Step(ContinueBaseModel): return SequentialStep(steps=steps) +class SequentialStep(Step): + steps: list[Step] + hide: bool = True + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + for step in self.steps: + observation = await sdk.run_step(step) + return observation + + class ValidatorObservation(Observation): passed: bool observation: Observation diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 07101576..c0ba0f4f 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -1,20 +1,16 @@ from typing import List, Tuple, Type - -from ..models.main import ContinueBaseModel - from ..libs.steps.ty import CreatePipelineStep from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep from ..libs.steps.nate import WritePytestsStep, CreateTableStep -from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma +# from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..libs.steps.continue_step import ContinueStepStep class DemoPolicy(Policy): ran_code_last: bool = False - cmd: str def next(self, history: History) -> Step: observation = history.last_observation() @@ -26,18 +22,15 @@ class DemoPolicy(Policy): return CreatePipelineStep() elif "/table" in observation.user_input: return CreateTableStep(sql_str=" ".join(observation.user_input.split(" ")[1:])) - elif "/ask" in observation.user_input: - return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) - elif "/edit" in observation.user_input: - return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) + # elif "/ask" in observation.user_input: + # return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) + # elif "/edit" in observation.user_input: + # return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) elif "/step" in observation.user_input: return ContinueStepStep(prompt=" ".join(observation.user_input.split(" ")[1:])) return StarCoderEditHighlightedCodeStep(user_input=observation.user_input) state = history.get_current() - if state is None or not self.ran_code_last: - self.ran_code_last = True - return RunCodeStep(cmd=self.cmd) if observation is not None and isinstance(observation, TracebackObservation): self.ran_code_last = False diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 750b335d..6ae0be04 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -4,6 +4,7 @@ from ..models.filesystem_edit import FileSystemEdit, AddFile, DeleteFile, AddDir from ..models.filesystem import RangeInFile from ..libs.llm import LLM from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI +from ..libs.llm.openai import OpenAI from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step @@ -29,20 +30,20 @@ class Models: 'HUGGING_FACE_TOKEN', 'Please enter your Hugging Face token') return HuggingFaceInferenceAPI(api_key=api_key) + async def gpt35(self): + api_key = await self.sdk.get_user_secret( + 'OPENAI_API_KEY', 'Please enter your OpenAI API key') + return OpenAI(api_key=api_key, default_model="gpt-3.5-turbo") + class ContinueSDK: """The SDK provided as parameters to a step""" - llm: LLM ide: AbstractIdeProtocolServer steps: ContinueSDKSteps models: Models __agent: Agent - def __init__(self, agent: Agent, llm: Union[LLM, None] = None): - if llm is None: - self.llm = agent.llm - else: - self.llm = llm + def __init__(self, agent: Agent): self.ide = agent.ide self.__agent = agent self.steps = ContinueSDKSteps(self) diff --git a/continuedev/src/continuedev/libs/steps/chroma.py b/continuedev/src/continuedev/libs/steps/chroma.py index f13a2bab..39424c5c 100644 --- a/continuedev/src/continuedev/libs/steps/chroma.py +++ b/continuedev/src/continuedev/libs/steps/chroma.py @@ -40,7 +40,7 @@ class AnswerQuestionChroma(Step): Here is the answer:""") - answer = sdk.llm.complete(prompt) + answer = (await sdk.models.gpt35()).complete(prompt) print(answer) self._answer = answer diff --git a/continuedev/src/continuedev/libs/steps/core/core.py b/continuedev/src/continuedev/libs/steps/core/core.py index 0338d635..14b3cb80 100644 --- a/continuedev/src/continuedev/libs/steps/core/core.py +++ b/continuedev/src/continuedev/libs/steps/core/core.py @@ -4,27 +4,18 @@ from textwrap import dedent from typing import Coroutine, List, Union from ...llm.prompt_utils import MarkdownStyleEncoderDecoder -from ...util.traceback_parsers import parse_python_traceback - from ....models.filesystem_edit import EditDiff, FileEditWithFullContents, FileSystemEdit from ....models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents -from ...llm import LLM from ....core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation -from ....core.main import Step +from ....core.main import Step, SequentialStep class ContinueSDK: pass -class SequentialStep(Step): - steps: list[Step] - hide: bool = True - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - for step in self.steps: - observation = await sdk.run_step(step) - return observation +class Models: + pass class ReversibleStep(Step): @@ -52,7 +43,7 @@ def ShellCommandsStep(Step): cwd: str | None = None name: str = "Run Shell Commands" - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "\n".join(self.cmds) async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -81,13 +72,13 @@ class EditCodeStep(Step): _prompt: Union[str, None] = None _completion: Union[str, None] = None - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: if self._edit_diffs is None: return "Editing files: " + ", ".join(map(lambda rif: rif.filepath, self.range_in_files)) elif len(self._edit_diffs) == 0: return "No edits made" else: - return llm.complete(dedent(f"""{self._prompt}{self._completion} + return (await models.gpt35()).complete(dedent(f"""{self._prompt}{self._completion} Maximally concise summary of changes in bullet points (can use markdown): """)) @@ -102,7 +93,7 @@ class EditCodeStep(Step): code_string = enc_dec.encode() prompt = self.prompt.format(code=code_string) - completion = sdk.llm.complete(prompt) + completion = (await sdk.models.gpt35()).complete(prompt) # Temporarily doing this to generate description. self._prompt = prompt @@ -127,7 +118,7 @@ class EditFileStep(Step): prompt: str hide: bool = True - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Editing file: " + self.filepath async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -145,7 +136,7 @@ class ManualEditStep(ReversibleStep): hide: bool = True - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Manual edit step" # TODO - only handling FileEdit here, but need all other types of FileSystemEdits # Also requires the merge_file_edit function @@ -181,7 +172,7 @@ class UserInputStep(Step): name: str = "User Input" hide: bool = True - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return self.user_input async def run(self, sdk: ContinueSDK) -> Coroutine[UserInputObservation, None, None]: @@ -194,7 +185,7 @@ class WaitForUserInputStep(Step): _description: Union[str, None] = None - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return self.prompt async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -207,7 +198,7 @@ class WaitForUserConfirmationStep(Step): prompt: str name: str = "Waiting for user confirmation" - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return self.prompt async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: diff --git a/continuedev/src/continuedev/libs/steps/draft/dlt.py b/continuedev/src/continuedev/libs/steps/draft/dlt.py index 5ba5692a..460aa0cc 100644 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py @@ -10,7 +10,7 @@ class SetupPipelineStep(Step): api_description: str # e.g. "I want to load data from the weatherapi.com API" async def run(self, sdk: ContinueSDK): - source_name = sdk.llm.complete( + source_name = (await sdk.models.gpt35()).complete( f"Write a snake_case name for the data source described by {self.api_description}: ").strip() filename = f'{source_name}.py' diff --git a/continuedev/src/continuedev/libs/steps/main.py b/continuedev/src/continuedev/libs/steps/main.py index c8a85800..70c0d4b8 100644 --- a/continuedev/src/continuedev/libs/steps/main.py +++ b/continuedev/src/continuedev/libs/steps/main.py @@ -11,7 +11,7 @@ from ...core.observation import Observation, TextObservation, TracebackObservati from ..llm.prompt_utils import MarkdownStyleEncoderDecoder from textwrap import dedent from ...core.main import Step -from ...core.sdk import ContinueSDK +from ...core.sdk import ContinueSDK, Models from ...core.observation import Observation import subprocess from .core.core import EditCodeStep @@ -20,7 +20,7 @@ from .core.core import EditCodeStep class RunCodeStep(Step): cmd: str - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return f"Ran command: `{self.cmd}`" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -59,7 +59,7 @@ class RunCommandStep(Step): name: str = "Run command" _description: str = None - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: if self._description is not None: return self._description return self.cmd @@ -125,7 +125,7 @@ class FasterEditHighlightedCodeStep(Step): Here is the description of changes to make: """) - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Editing highlighted code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -154,7 +154,7 @@ class FasterEditHighlightedCodeStep(Step): for rif in rif_with_contents: rif_dict[rif.filepath] = rif.contents - completion = sdk.llm.complete(prompt) + completion = (await sdk.models.gpt35()).complete(prompt) # Temporarily doing this to generate description. self._prompt = prompt @@ -215,10 +215,10 @@ class FasterEditHighlightedCodeStep(Step): class StarCoderEditHighlightedCodeStep(Step): user_input: str - hide = True + hide = False _prompt: str = "{code}{user_request}" - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Editing highlighted code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -271,7 +271,7 @@ This is the user request: This is the code after being changed to perfectly satisfy the user request: """) - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Editing highlighted code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -293,7 +293,7 @@ This is the code after being changed to perfectly satisfy the user request: class FindCodeStep(Step): prompt: str - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return "Finding code" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: @@ -307,7 +307,7 @@ class UserInputStep(Step): class SolveTracebackStep(Step): traceback: Traceback - async def describe(self, llm: LLM) -> Coroutine[str, None, None]: + async def describe(self, models: Models) -> Coroutine[str, None, None]: return f"```\n{self.traceback.full_traceback}\n```" async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: diff --git a/continuedev/src/continuedev/libs/steps/migration.py b/continuedev/src/continuedev/libs/steps/migration.py index f044a60f..7b70422d 100644 --- a/continuedev/src/continuedev/libs/steps/migration.py +++ b/continuedev/src/continuedev/libs/steps/migration.py @@ -15,7 +15,7 @@ class MigrationStep(Step): recent_edits = await sdk.ide.get_recent_edits(self.edited_file) recent_edits_string = "\n\n".join( map(lambda x: x.to_string(), recent_edits)) - description = await sdk.llm.complete(f"{recent_edits_string}\n\nGenerate a short description of the migration made in the above changes:\n") + description = await (await sdk.models.gpt35()).complete(f"{recent_edits_string}\n\nGenerate a short description of the migration made in the above changes:\n") await sdk.run_step(RunCommandStep(cmd=f"cd libs && poetry run alembic revision --autogenerate -m {description}")) migration_file = f"libs/alembic/versions/{?}.py" contents = await sdk.ide.readFile(migration_file) diff --git a/continuedev/src/continuedev/libs/steps/nate.py b/continuedev/src/continuedev/libs/steps/nate.py index a0e728e5..2f84e9d7 100644 --- a/continuedev/src/continuedev/libs/steps/nate.py +++ b/continuedev/src/continuedev/libs/steps/nate.py @@ -45,7 +45,7 @@ Here are additional instructions: Here is a complete set of pytest unit tests: """) - # tests = sdk.llm.complete(prompt) + # tests = (await sdk.models.gpt35()).complete(prompt) tests = ''' import pytest @@ -169,9 +169,9 @@ export class Order { tracking_number: string; }''' time.sleep(2) - # orm_entity = sdk.llm.complete( + # orm_entity = (await sdk.models.gpt35()).complete( # f"{self.sql_str}\n\nWrite a TypeORM entity called {entity_name} for this table, importing as necessary:") - # sdk.llm.complete("What is the name of the entity?") + # (await sdk.models.gpt35()).complete("What is the name of the entity?") await sdk.apply_filesystem_edit(AddFile(filepath=f"/Users/natesesti/Desktop/continue/extension/examples/python/MyProject/src/entity/{entity_name}.ts", content=orm_entity)) await sdk.ide.setFileOpen(f"/Users/natesesti/Desktop/continue/extension/examples/python/MyProject/src/entity/{entity_name}.ts", True) diff --git a/continuedev/src/continuedev/libs/steps/pytest.py b/continuedev/src/continuedev/libs/steps/pytest.py index b4e6dfd2..2e83ae2d 100644 --- a/continuedev/src/continuedev/libs/steps/pytest.py +++ b/continuedev/src/continuedev/libs/steps/pytest.py @@ -33,5 +33,5 @@ class WritePytestsStep(Step): Here is a complete set of pytest unit tests: """) - tests = sdk.llm.complete(prompt) + tests = (await sdk.models.gpt35()).complete(prompt) await sdk.apply_filesystem_edit(AddFile(filepath=path, content=tests)) diff --git a/continuedev/src/continuedev/libs/steps/ty.py b/continuedev/src/continuedev/libs/steps/ty.py index 5ff03f04..9dde7c86 100644 --- a/continuedev/src/continuedev/libs/steps/ty.py +++ b/continuedev/src/continuedev/libs/steps/ty.py @@ -18,7 +18,7 @@ class SetupPipelineStep(Step): api_description: str # e.g. "I want to load data from the weatherapi.com API" async def run(self, sdk: ContinueSDK): - # source_name = sdk.llm.complete( + # source_name = (await sdk.models.gpt35()).complete( # f"Write a snake_case name for the data source described by {self.api_description}: ").strip() filename = f'/Users/natesesti/Desktop/continue/extension/examples/python/{source_name}.py' diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index dd1dc463..50296841 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -1,5 +1,6 @@ # This is a separate server from server/main.py import asyncio +import json import os from typing import Any, Dict, List, Type, TypeVar, Union import uuid @@ -90,31 +91,33 @@ class IdeProtocolServer(AbstractIdeProtocolServer): def __init__(self, session_manager: SessionManager): self.session_manager = session_manager - async def _send_json(self, data: Any): - await self.websocket.send_json(data) + async def _send_json(self, message_type: str, data: Any): + await self.websocket.send_json({ + "messageType": message_type, + "data": data + }) async def _receive_json(self, message_type: str) -> Any: return await self.sub_queue.get(message_type) async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T: - await self._send_json(data) + await self._send_json(message_type, data) resp = await self._receive_json(message_type) return resp_model.parse_obj(resp) - async def handle_json(self, data: Any): - t = data["messageType"] - if t == "openNotebook": + async def handle_json(self, message_type: str, data: Any): + if message_type == "openNotebook": await self.openNotebook() - elif t == "setFileOpen": + elif message_type == "setFileOpen": await self.setFileOpen(data["filepath"], data["open"]) - elif t == "fileEdits": + elif message_type == "fileEdits": fileEdits = list( map(lambda d: FileEditWithFullContents.parse_obj(d), data["fileEdits"])) self.onFileEdits(fileEdits) - elif t in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory"]: - self.sub_queue.post(t, data) + elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory"]: + self.sub_queue.post(message_type, data) else: - raise ValueError("Unknown message type", t) + raise ValueError("Unknown message type", message_type) # ------------------------------- # # Request actions in IDE, doesn't matter which Session @@ -123,24 +126,21 @@ class IdeProtocolServer(AbstractIdeProtocolServer): async def setFileOpen(self, filepath: str, open: bool = True): # Agent needs access to this. - await self.websocket.send_json({ - "messageType": "setFileOpen", + await self._send_json("setFileOpen", { "filepath": filepath, "open": open }) async def openNotebook(self): session_id = self.session_manager.new_session(self) - await self._send_json({ - "messageType": "openNotebook", + await self._send_json("openNotebook", { "sessionId": session_id }) async def showSuggestionsAndWait(self, suggestions: List[FileEdit]) -> bool: ids = [str(uuid.uuid4()) for _ in suggestions] for i in range(len(suggestions)): - self._send_json({ - "messageType": "showSuggestion", + self._send_json("showSuggestion", { "suggestion": suggestions[i], "suggestionId": ids[i] }) @@ -210,8 +210,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer): async def saveFile(self, filepath: str): """Save a file""" - await self._send_json({ - "messageType": "saveFile", + await self._send_json("saveFile", { "filepath": filepath }) @@ -293,10 +292,17 @@ ideProtocolServer = IdeProtocolServer(session_manager) async def websocket_endpoint(websocket: WebSocket): await websocket.accept() print("Accepted websocket connection from, ", websocket.client) - await websocket.send_json({"messageType": "connected"}) + await websocket.send_json({"messageType": "connected", "data": {}}) ideProtocolServer.websocket = websocket while True: - data = await websocket.receive_json() - await ideProtocolServer.handle_json(data) + message = await websocket.receive_text() + message = json.loads(message) + + if "messageType" not in message or "data" not in message: + continue + message_type = message["messageType"] + data = message["data"] + + await ideProtocolServer.handle_json(message_type, data) await websocket.close() diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index 11ad1d8f..e87d5fa9 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -32,7 +32,7 @@ args = parser.parse_args() def run_server(): - uvicorn.run(app, host="0.0.0.0", port=args.port, log_config="logging.ini") + uvicorn.run(app, host="0.0.0.0", port=args.port) if __name__ == "__main__": diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py index c5dcea31..edb61a45 100644 --- a/continuedev/src/continuedev/server/notebook.py +++ b/continuedev/src/continuedev/server/notebook.py @@ -1,18 +1,12 @@ -from fastapi import FastAPI, Depends, Header, WebSocket, APIRouter -from typing import Any, Dict, List, Union -from uuid import uuid4 +import json +from fastapi import Depends, Header, WebSocket, APIRouter +from typing import Any, Type, TypeVar, Union from pydantic import BaseModel from uvicorn.main import Server -from ..models.filesystem_edit import FileEditWithFullContents -from ..core.policy import DemoPolicy -from ..core.main import FullState, History, Step -from ..core.agent import Agent -from ..libs.steps.nate import ImplementAbstractMethodStep -from ..core.observation import Observation -from ..libs.llm.openai import OpenAI -from .ide_protocol import AbstractIdeProtocolServer -from ..core.env import get_env_var +from .session_manager import SessionManager, session_manager, Session +from .notebook_protocol import AbstractNotebookProtocolServer +from ..libs.util.queue import AsyncSubscriptionQueue import asyncio import nest_asyncio nest_asyncio.apply() @@ -36,160 +30,99 @@ class AppStatus: Server.handle_exit = AppStatus.handle_exit -class Session: - session_id: str - agent: Agent - ws: Union[WebSocket, None] - - def __init__(self, session_id: str, agent: Agent): - self.session_id = session_id - self.agent = agent - self.ws = None - - -class DemoAgent(Agent): - first_seen: bool = False - cumulative_edit_string = "" - - def handle_manual_edits(self, edits: List[FileEditWithFullContents]): - for edit in edits: - self.cumulative_edit_string += edit.fileEdit.replacement - self._manual_edits_buffer.append(edit) - # Note that you're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. - # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) - # FOR DEMO PURPOSES - if edit.fileEdit.filepath.endswith("filesystem.py") and "List" in self.cumulative_edit_string and ":" in edit.fileEdit.replacement: - self.cumulative_edit_string = "" - asyncio.create_task(self.run_from_step( - ImplementAbstractMethodStep())) - - -class SessionManager: - sessions: Dict[str, Session] = {} - _event_loop: Union[asyncio.BaseEventLoop, None] = None - - def get_session(self, session_id: str) -> Session: - if session_id not in self.sessions: - raise KeyError("Session ID not recognized") - return self.sessions[session_id] - - def new_session(self, ide: AbstractIdeProtocolServer) -> str: - cmd = "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py" - agent = DemoAgent(llm=OpenAI(api_key=get_env_var("OPENAI_API_KEY")), - policy=DemoPolicy(cmd=cmd), ide=ide) - session_id = str(uuid4()) - session = Session(session_id=session_id, agent=agent) - self.sessions[session_id] = session +def session(x_continue_session_id: str = Header("anonymous")) -> Session: + return session_manager.get_session(x_continue_session_id) - def on_update(state: FullState): - session_manager.send_ws_data(session_id, { - "messageType": "state", - "state": agent.get_full_state().dict() - }) - agent.on_update(on_update) - asyncio.create_task(agent.run_policy()) - return session_id +def websocket_session(session_id: str) -> Session: + return session_manager.get_session(session_id) - def remove_session(self, session_id: str): - del self.sessions[session_id] - def register_websocket(self, session_id: str, ws: WebSocket): - self.sessions[session_id].ws = ws - print("Registered websocket for session", session_id) +T = TypeVar("T", bound=BaseModel) - def send_ws_data(self, session_id: str, data: Any): - if self.sessions[session_id].ws is None: - print(f"Session {session_id} has no websocket") - return +# You should probably abstract away the websocket stuff into a separate class - async def a(): - await self.sessions[session_id].ws.send_json(data) - # Run coroutine in background - if self._event_loop is None or self._event_loop.is_closed(): - self._event_loop = asyncio.new_event_loop() - self._event_loop.run_until_complete(a()) - self._event_loop.close() - else: - self._event_loop.run_until_complete(a()) - self._event_loop.close() +class NotebookProtocolServer(AbstractNotebookProtocolServer): + websocket: WebSocket + session: Session + sub_queue: AsyncSubscriptionQueue = AsyncSubscriptionQueue() + def __init__(self, session: Session): + self.session = session -session_manager = SessionManager() + async def _send_json(self, data: Any): + await self.websocket.send_json(data) + async def _receive_json(self, message_type: str) -> Any: + return await self.sub_queue.get(message_type) -def session(x_continue_session_id: str = Header("anonymous")) -> Session: - return session_manager.get_session(x_continue_session_id) + async def _send_and_receive_json(self, data: Any, resp_model: Type[T], message_type: str) -> T: + await self._send_json(data) + resp = await self._receive_json(message_type) + return resp_model.parse_obj(resp) + def handle_json(self, message_type: str, data: Any): + try: + if message_type == "main_input": + self.on_main_input(data["input"]) + elif message_type == "step_user_input": + self.on_step_user_input(data["input"], data["index"]) + elif message_type == "refinement_input": + self.on_refinement_input(data["input"], data["index"]) + elif message_type == "reverse_to_index": + self.on_reverse_to_index(data["index"]) + except Exception as e: + print(e) -def websocket_session(session_id: str) -> Session: - return session_manager.get_session(session_id) + async def send_state_update(self): + state = self.session.agent.get_full_state().dict() + await self._send_json({ + "messageType": "state_update", + "state": state + }) + def on_main_input(self, input: str): + # Do something with user input + asyncio.create_task(self.session.agent.accept_user_input(input)) -class StartSessionBody(BaseModel): - config_file_path: Union[str, None] + def on_reverse_to_index(self, index: int): + # Reverse the history to the given index + asyncio.create_task(self.session.agent.reverse_to_index(index)) + def on_step_user_input(self, input: str, index: int): + asyncio.create_task( + self.session.agent.give_user_input(input, index)) -class StartSessionResp(BaseModel): - session_id: str + def on_refinement_input(self, input: str, index: int): + asyncio.create_task( + self.session.agent.accept_refinement_input(input, index)) @router.websocket("/ws") async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)): await websocket.accept() + print("Session started") session_manager.register_websocket(session.session_id, websocket) - data = await websocket.receive_text() + protocol = NotebookProtocolServer(session) + protocol.websocket = websocket + # Update any history that may have happened before connection - await websocket.send_json({ - "messageType": "state", - "state": session_manager.get_session(session.session_id).agent.get_full_state().dict() - }) - print("Session started", data) + await protocol.send_state_update() + while AppStatus.should_exit is False: - data = await websocket.receive_json() - print("Received data", data) + message = await websocket.receive_json() + print("Received message", message) + if type(message) is str: + message = json.loads(message) - if "messageType" not in data: + if "messageType" not in message or "data" not in message: continue - messageType = data["messageType"] + message_type = message["messageType"] + data = message["data"] - try: - if messageType == "main_input": - # Do something with user input - asyncio.create_task( - session.agent.accept_user_input(data["value"])) - elif messageType == "step_user_input": - asyncio.create_task( - session.agent.give_user_input(data["value"], data["index"])) - elif messageType == "refinement_input": - asyncio.create_task( - session.agent.accept_refinement_input(data["value"], data["index"])) - elif messageType == "reverse": - # Reverse the history to the given index - asyncio.create_task( - session.agent.reverse_to_index(data["index"])) - except Exception as e: - print(e) + protocol.handle_json(message_type, data) print("Closing websocket") await websocket.close() - - -@router.post("/run") -def request_run(step: Step, session=Depends(session)): - """Tell an agent to take a specific action.""" - asyncio.create_task(session.agent.run_from_step(step)) - return "Success" - - -@router.get("/history") -def get_history(session=Depends(session)) -> History: - return session.agent.history - - -@router.post("/observation") -def post_observation(observation: Observation, session=Depends(session)): - asyncio.create_task(session.agent.run_from_observation(observation)) - return "Success" diff --git a/continuedev/src/continuedev/server/notebook_protocol.py b/continuedev/src/continuedev/server/notebook_protocol.py new file mode 100644 index 00000000..c2be82e0 --- /dev/null +++ b/continuedev/src/continuedev/server/notebook_protocol.py @@ -0,0 +1,28 @@ +from typing import Any +from abc import ABC, abstractmethod + + +class AbstractNotebookProtocolServer(ABC): + @abstractmethod + async def handle_json(self, data: Any): + """Handle a json message""" + + @abstractmethod + def on_main_input(self, input: str): + """Called when the user inputs something""" + + @abstractmethod + def on_reverse_to_index(self, index: int): + """Called when the user requests reverse to a previous index""" + + @abstractmethod + def on_refinement_input(self, input: str, index: int): + """Called when the user inputs a refinement""" + + @abstractmethod + def on_step_user_input(self, input: str, index: int): + """Called when the user inputs a step""" + + @abstractmethod + async def send_state_update(self, state: dict): + """Send a state update to the client""" diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py new file mode 100644 index 00000000..b48c21b7 --- /dev/null +++ b/continuedev/src/continuedev/server/session_manager.py @@ -0,0 +1,101 @@ +from fastapi import WebSocket +from typing import Any, Dict, List, Union +from uuid import uuid4 + +from ..models.filesystem_edit import FileEditWithFullContents +from ..core.policy import DemoPolicy +from ..core.main import FullState +from ..core.agent import Agent +from ..libs.steps.nate import ImplementAbstractMethodStep +from .ide_protocol import AbstractIdeProtocolServer +import asyncio +import nest_asyncio +nest_asyncio.apply() + + +class Session: + session_id: str + agent: Agent + ws: Union[WebSocket, None] + + def __init__(self, session_id: str, agent: Agent): + self.session_id = session_id + self.agent = agent + self.ws = None + + +class DemoAgent(Agent): + first_seen: bool = False + cumulative_edit_string = "" + + def handle_manual_edits(self, edits: List[FileEditWithFullContents]): + for edit in edits: + self.cumulative_edit_string += edit.fileEdit.replacement + self._manual_edits_buffer.append(edit) + # Note that you're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. + # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) + # FOR DEMO PURPOSES + if edit.fileEdit.filepath.endswith("filesystem.py") and "List" in self.cumulative_edit_string and ":" in edit.fileEdit.replacement: + self.cumulative_edit_string = "" + asyncio.create_task(self.run_from_step( + ImplementAbstractMethodStep())) + + +class SessionManager: + sessions: Dict[str, Session] = {} + _event_loop: Union[asyncio.BaseEventLoop, None] = None + + def get_session(self, session_id: str) -> Session: + if session_id not in self.sessions: + raise KeyError("Session ID not recognized") + return self.sessions[session_id] + + def new_session(self, ide: AbstractIdeProtocolServer) -> str: + agent = DemoAgent(policy=DemoPolicy(), ide=ide) + session_id = str(uuid4()) + session = Session(session_id=session_id, agent=agent) + self.sessions[session_id] = session + + async def on_update(state: FullState): + await session_manager.send_ws_data(session_id, "state_update", { + "state": agent.get_full_state().dict() + }) + + agent.on_update(on_update) + asyncio.create_task(agent.run_policy()) + return session_id + + def remove_session(self, session_id: str): + del self.sessions[session_id] + + def register_websocket(self, session_id: str, ws: WebSocket): + self.sessions[session_id].ws = ws + print("Registered websocket for session", session_id) + + async def send_ws_data(self, session_id: str, message_type: str, data: Any): + if self.sessions[session_id].ws is None: + print(f"Session {session_id} has no websocket") + return + + async def a(): + await self.sessions[session_id].ws.send_json({ + "messageType": message_type, + "data": data + }) + + # Run coroutine in background + await self.sessions[session_id].ws.send_json({ + "messageType": message_type, + "data": data + }) + return + if self._event_loop is None or self._event_loop.is_closed(): + self._event_loop = asyncio.new_event_loop() + self._event_loop.run_until_complete(a()) + self._event_loop.close() + else: + self._event_loop.run_until_complete(a()) + self._event_loop.close() + + +session_manager = SessionManager() diff --git a/extension/package-lock.json b/extension/package-lock.json index 20ac24be..04af09d3 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -28,6 +28,7 @@ "@types/node": "16.x", "@types/node-fetch": "^2.6.2", "@types/vscode": "^1.74.0", + "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "@vscode/test-electron": "^2.2.0", @@ -2027,6 +2028,15 @@ "integrity": "sha512-LyeCIU3jb9d38w0MXFwta9r0Jx23ugujkAxdwLTNCyspdZTKUc43t7ppPbCiPoQ/Ivd/pnDFZrb4hWd45wrsgA==", "dev": true }, + "node_modules/@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.48.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", @@ -9246,6 +9256,15 @@ "integrity": "sha512-LyeCIU3jb9d38w0MXFwta9r0Jx23ugujkAxdwLTNCyspdZTKUc43t7ppPbCiPoQ/Ivd/pnDFZrb4hWd45wrsgA==", "dev": true }, + "@types/ws": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", + "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "5.48.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz", diff --git a/extension/package.json b/extension/package.json index dc0192c3..c96655a9 100644 --- a/extension/package.json +++ b/extension/package.json @@ -148,7 +148,7 @@ }, "scripts": { "vscode:prepublish": "npm run esbuild-base -- --minify", - "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node", + "esbuild-base": "rm -rf ./out && esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node", "esbuild": "rm -rf ./out && npm run esbuild-base -- --sourcemap", "esbuild-watch": "npm run esbuild-base -- --sourcemap --watch", "test-compile": "tsc -p ./", @@ -160,9 +160,9 @@ "pretest": "npm run compile && npm run lint", "lint": "eslint src --ext ts", "test": "node ./out/test/runTest.js", - "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.2.vsix && cp ./config/dev_config.json ./config/config.json", + "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && chmod 777 ./build/continue-0.0.5.vsix && cp ./config/dev_config.json ./config/config.json", "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.0-py3-none-any.whl ../extension/scripts/continuedev-0.1.0-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", - "install-extension": "code --install-extension ./build/continue-0.0.1.vsix", + "install-extension": "code --install-extension ./build/continue-0.0.5.vsix", "uninstall": "code --uninstall-extension .continue", "reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension" }, @@ -173,6 +173,7 @@ "@types/node": "16.x", "@types/node-fetch": "^2.6.2", "@types/vscode": "^1.74.0", + "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "@vscode/test-electron": "^2.2.0", diff --git a/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts b/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts new file mode 100644 index 00000000..75fd7373 --- /dev/null +++ b/extension/react-app/src/hooks/ContinueNotebookClientProtocol.ts @@ -0,0 +1,13 @@ +abstract class AbstractContinueNotebookClientProtocol { + abstract sendMainInput(input: string): void; + + abstract reverseToIndex(index: number): void; + + abstract sendRefinementInput(input: string, index: number): void; + + abstract sendStepUserInput(input: string, index: number): void; + + abstract onStateUpdate(state: any): void; +} + +export default AbstractContinueNotebookClientProtocol; diff --git a/extension/react-app/src/hooks/messenger.ts b/extension/react-app/src/hooks/messenger.ts new file mode 100644 index 00000000..e2a0bab8 --- /dev/null +++ b/extension/react-app/src/hooks/messenger.ts @@ -0,0 +1,108 @@ +// console.log("Websocket import"); +// const WebSocket = require("ws"); + +export abstract class Messenger { + abstract send(messageType: string, data: object): void; + + abstract onMessageType( + messageType: string, + callback: (data: object) => void + ): void; + + abstract onMessage(callback: (messageType: string, data: any) => void): void; + + abstract onOpen(callback: () => void): void; + + abstract onClose(callback: () => void): void; + + abstract sendAndReceive(messageType: string, data: any): Promise; +} + +export class WebsocketMessenger extends Messenger { + websocket: WebSocket; + private onMessageListeners: { + [messageType: string]: ((data: object) => void)[]; + } = {}; + private onOpenListeners: (() => void)[] = []; + private onCloseListeners: (() => void)[] = []; + private serverUrl: string; + + _newWebsocket(): WebSocket { + // // Dynamic import, because WebSocket is builtin with browser, but not with node. And can't use require in browser. + // if (typeof process === "object") { + // console.log("Using node"); + // // process is only available in Node + // var WebSocket = require("ws"); + // } + + const newWebsocket = new WebSocket(this.serverUrl); + for (const listener of this.onOpenListeners) { + this.onOpen(listener); + } + for (const listener of this.onCloseListeners) { + this.onClose(listener); + } + for (const messageType in this.onMessageListeners) { + for (const listener of this.onMessageListeners[messageType]) { + this.onMessageType(messageType, listener); + } + } + return newWebsocket; + } + + constructor(serverUrl: string) { + super(); + this.serverUrl = serverUrl; + this.websocket = this._newWebsocket(); + } + + send(messageType: string, data: object) { + const payload = JSON.stringify({ messageType, data }); + if (this.websocket.readyState === this.websocket.OPEN) { + this.websocket.send(payload); + } else { + if (this.websocket.readyState !== this.websocket.CONNECTING) { + this.websocket = this._newWebsocket(); + } + this.websocket.addEventListener("open", () => { + this.websocket.send(payload); + }); + } + } + + sendAndReceive(messageType: string, data: any): Promise { + return new Promise((resolve, reject) => { + const eventListener = (data: any) => { + // THIS ISN"T GETTING CALLED + resolve(data); + this.websocket.removeEventListener("message", eventListener); + }; + this.onMessageType(messageType, eventListener); + this.send(messageType, data); + }); + } + + onMessageType(messageType: string, callback: (data: any) => void): void { + this.websocket.addEventListener("message", (event: any) => { + const msg = JSON.parse(event.data); + if (msg.messageType === messageType) { + callback(msg.data); + } + }); + } + + onMessage(callback: (messageType: string, data: any) => void): void { + this.websocket.addEventListener("message", (event) => { + const msg = JSON.parse(event.data); + callback(msg.messageType, msg.data); + }); + } + + onOpen(callback: () => void): void { + this.websocket.addEventListener("open", callback); + } + + onClose(callback: () => void): void { + this.websocket.addEventListener("close", callback); + } +} diff --git a/extension/react-app/src/hooks/useContinueNotebookProtocol.ts b/extension/react-app/src/hooks/useContinueNotebookProtocol.ts new file mode 100644 index 00000000..d5ffbf09 --- /dev/null +++ b/extension/react-app/src/hooks/useContinueNotebookProtocol.ts @@ -0,0 +1,49 @@ +import AbstractContinueNotebookClientProtocol from "./ContinueNotebookClientProtocol"; +// import { Messenger, WebsocketMessenger } from "../../../src/util/messenger"; +import { Messenger, WebsocketMessenger } from "./messenger"; +import { VscodeMessenger } from "./vscodeMessenger"; + +class ContinueNotebookClientProtocol extends AbstractContinueNotebookClientProtocol { + messenger: Messenger; + // Server URL must contain the session ID param + serverUrlWithSessionId: string; + + constructor( + serverUrlWithSessionId: string, + useVscodeMessagePassing: boolean = false + ) { + super(); + this.serverUrlWithSessionId = serverUrlWithSessionId; + if (useVscodeMessagePassing) { + this.messenger = new VscodeMessenger(serverUrlWithSessionId); + } else { + this.messenger = new WebsocketMessenger(serverUrlWithSessionId); + } + } + + sendMainInput(input: string) { + this.messenger.send("main_input", { input }); + } + + reverseToIndex(index: number) { + this.messenger.send("reverse_to_index", { index }); + } + + sendRefinementInput(input: string, index: number) { + this.messenger.send("refinement_input", { input, index }); + } + + sendStepUserInput(input: string, index: number) { + this.messenger.send("step_user_input", { input, index }); + } + + onStateUpdate(callback: (state: any) => void) { + this.messenger.onMessageType("state_update", (data: any) => { + if (data.state) { + callback(data.state); + } + }); + } +} + +export default ContinueNotebookClientProtocol; diff --git a/extension/react-app/src/hooks/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts index 6e8e68fa..b98be577 100644 --- a/extension/react-app/src/hooks/useWebsocket.ts +++ b/extension/react-app/src/hooks/useWebsocket.ts @@ -1,158 +1,39 @@ import React, { useEffect, useState } from "react"; import { RootStore } from "../redux/store"; import { useSelector } from "react-redux"; +import ContinueNotebookClientProtocol from "./useContinueNotebookProtocol"; import { postVscMessage } from "../vscode"; -abstract class Messenger { - abstract send(data: string): void; -} - -class VscodeMessenger extends Messenger { - url: string; - - constructor( - url: string, - onMessage: (message: { data: any }) => void, - onOpen: (messenger: Messenger) => void, - onClose: (messenger: Messenger) => void - ) { - super(); - this.url = url; - window.addEventListener("message", (event: any) => { - switch (event.data.type) { - case "websocketForwardingMessage": - onMessage(event.data); - break; - case "websocketForwardingOpen": - onOpen(this); - break; - case "websocketForwardingClose": - onClose(this); - break; - } - }); - - postVscMessage("websocketForwardingOpen", { url: this.url }); - } - - send(data: string) { - postVscMessage("websocketForwardingMessage", { - message: data, - url: this.url, - }); - } -} - -class WebsocketMessenger extends Messenger { - websocket: WebSocket; - constructor( - websocket: WebSocket, - onMessage: (message: { data: any }) => void, - onOpen: (messenger: Messenger) => void, - onClose: (messenger: Messenger) => void - ) { - super(); - this.websocket = websocket; - - websocket.addEventListener("close", () => { - onClose(this); - }); - - websocket.addEventListener("open", () => { - onOpen(this); - }); - - websocket.addEventListener("message", (event) => { - onMessage(event.data); - }); - } - - static async connect( - url: string, - sessionId: string, - onMessage: (message: { data: any }) => void, - onOpen: (messenger: Messenger) => void, - onClose: (messenger: Messenger) => void - ): Promise { - const ws = new WebSocket(url); - - return new Promise((resolve, reject) => { - ws.addEventListener("open", () => { - resolve(new WebsocketMessenger(ws, onMessage, onOpen, onClose)); - }); - }); - } - - send(data: string) { - this.websocket.send(JSON.stringify(data)); - } -} - -function useContinueWebsocket( - serverUrl: string, - onMessage: (message: { data: any }) => void, - useVscodeMessagePassing: boolean = true -) { +function useContinueNotebookProtocol(useVscodeMessagePassing: boolean = false) { const sessionId = useSelector((state: RootStore) => state.config.sessionId); - const [websocket, setWebsocket] = useState(undefined); + const serverHttpUrl = useSelector((state: RootStore) => state.config.apiUrl); + const [client, setClient] = useState< + ContinueNotebookClientProtocol | undefined + >(undefined); - async function connect() { - while (!sessionId) { - await new Promise((resolve) => setTimeout(resolve, 300)); + useEffect(() => { + if (!sessionId || !serverHttpUrl) { + if (useVscodeMessagePassing) { + postVscMessage("onLoad", {}); + } + setClient(undefined); + return; } - console.log("Creating websocket", sessionId); - console.log("Using vscode message passing", useVscodeMessagePassing); - - const onClose = (messenger: Messenger) => { - console.log("Websocket closed"); - setWebsocket(undefined); - }; - - const onOpen = (messenger: Messenger) => { - console.log("Websocket opened"); - messenger.send(JSON.stringify({ sessionId })); - }; - - const url = - serverUrl.replace("http", "ws") + + const serverUrlWithSessionId = + serverHttpUrl.replace("http", "ws") + "/notebook/ws?session_id=" + encodeURIComponent(sessionId); - const messenger: Messenger = useVscodeMessagePassing - ? new VscodeMessenger(url, onMessage, onOpen, onClose) - : await WebsocketMessenger.connect( - url, - sessionId, - onMessage, - onOpen, - onClose - ); - - setWebsocket(messenger); - - return messenger; - } - - async function getConnection() { - if (!websocket) { - return await connect(); - } - return websocket; - } - - async function send(message: object) { - let ws = await getConnection(); - ws.send(JSON.stringify(message)); - } - - useEffect(() => { - if (!sessionId) { - return; - } - connect(); - }, [sessionId]); - - return { send }; + console.log("Creating websocket", serverUrlWithSessionId); + console.log("Using vscode message passing", useVscodeMessagePassing); + const newClient = new ContinueNotebookClientProtocol( + serverUrlWithSessionId, + useVscodeMessagePassing + ); + setClient(newClient); + }, [sessionId, serverHttpUrl]); + + return client; } -export default useContinueWebsocket; +export default useContinueNotebookProtocol; diff --git a/extension/react-app/src/hooks/vscodeMessenger.ts b/extension/react-app/src/hooks/vscodeMessenger.ts new file mode 100644 index 00000000..746c4302 --- /dev/null +++ b/extension/react-app/src/hooks/vscodeMessenger.ts @@ -0,0 +1,68 @@ +import { postVscMessage } from "../vscode"; +// import { Messenger } from "../../../src/util/messenger"; +import { Messenger } from "./messenger"; + +export class VscodeMessenger extends Messenger { + serverUrl: string; + + constructor(serverUrl: string) { + super(); + this.serverUrl = serverUrl; + postVscMessage("websocketForwardingOpen", { url: this.serverUrl }); + } + + send(messageType: string, data: object) { + postVscMessage("websocketForwardingMessage", { + message: { messageType, data }, + url: this.serverUrl, + }); + } + + onMessageType(messageType: string, callback: (data: object) => void): void { + window.addEventListener("message", (event: any) => { + if (event.data.type === "websocketForwardingMessage") { + if (event.data.message.messageType === messageType) { + callback(event.data.message.data); + } + } + }); + } + + onMessage(callback: (messageType: string, data: any) => void): void { + window.addEventListener("message", (event: any) => { + if (event.data.type === "websocketForwardingMessage") { + callback(event.data.message.messageType, event.data.message.data); + } + }); + } + + sendAndReceive(messageType: string, data: any): Promise { + return new Promise((resolve) => { + const handler = (event: any) => { + if (event.data.type === "websocketForwardingMessage") { + if (event.data.message.messageType === messageType) { + window.removeEventListener("message", handler); + resolve(event.data.message.data); + } + } + }; + window.addEventListener("message", handler); + this.send(messageType, data); + }); + } + + onOpen(callback: () => void): void { + window.addEventListener("message", (event: any) => { + if (event.data.type === "websocketForwardingOpen") { + callback(); + } + }); + } + onClose(callback: () => void): void { + window.addEventListener("message", (event: any) => { + if (event.data.type === "websocketForwardingClose") { + callback(); + } + }); + } +} diff --git a/extension/react-app/src/tabs/notebook.tsx b/extension/react-app/src/tabs/notebook.tsx index a9c69c5b..02c9ff31 100644 --- a/extension/react-app/src/tabs/notebook.tsx +++ b/extension/react-app/src/tabs/notebook.tsx @@ -14,6 +14,7 @@ import StepContainer from "../components/StepContainer"; import { useSelector } from "react-redux"; import { RootStore } from "../redux/store"; import useContinueWebsocket from "../hooks/useWebsocket"; +import useContinueNotebookProtocol from "../hooks/useWebsocket"; let TopNotebookDiv = styled.div` display: grid; @@ -33,8 +34,6 @@ interface NotebookProps { } function Notebook(props: NotebookProps) { - const serverUrl = useSelector((state: RootStore) => state.config.apiUrl); - const [waitingForSteps, setWaitingForSteps] = useState(false); const [userInputQueue, setUserInputQueue] = useState([]); const [history, setHistory] = useState(); @@ -157,30 +156,17 @@ function Notebook(props: NotebookProps) { // } as any // ); - const { send: websocketSend } = useContinueWebsocket(serverUrl, (msg) => { - let data = JSON.parse(msg.data); - if (data.messageType === "state") { - setWaitingForSteps(data.state.active); - setHistory(data.state.history); - setUserInputQueue(data.state.user_input_queue); - } - }); + const client = useContinueNotebookProtocol(); - // useEffect(() => { - // (async () => { - // if (sessionId && props.firstObservation) { - // let resp = await fetch(serverUrl + "/observation", { - // method: "POST", - // headers: new Headers({ - // "x-continue-session-id": sessionId, - // }), - // body: JSON.stringify({ - // observation: props.firstObservation, - // }), - // }); - // } - // })(); - // }, [props.firstObservation]); + useEffect(() => { + console.log("CLIENT ON STATE UPDATE: ", client, client?.onStateUpdate); + client?.onStateUpdate((state) => { + console.log("Received state update: ", state); + setWaitingForSteps(state.active); + setHistory(state.history); + setUserInputQueue(state.user_input_queue); + }); + }, [client]); const mainTextInputRef = useRef(null); @@ -201,14 +187,12 @@ function Notebook(props: NotebookProps) { const onMainTextInput = () => { if (mainTextInputRef.current) { - let value = mainTextInputRef.current.value; + if (!client) return; + let input = mainTextInputRef.current.value; setWaitingForSteps(true); - websocketSend({ - messageType: "main_input", - value: value, - }); + client.sendMainInput(input); setUserInputQueue((queue) => { - return [...queue, value]; + return [...queue, input]; }); mainTextInputRef.current.value = ""; mainTextInputRef.current.style.height = ""; @@ -216,17 +200,20 @@ function Notebook(props: NotebookProps) { }; const onStepUserInput = (input: string, index: number) => { + if (!client) return; console.log("Sending step user input", input, index); - websocketSend({ - messageType: "step_user_input", - value: input, - index, - }); + client.sendStepUserInput(input, index); }; // const iterations = useSelector(selectIterations); return ( + {typeof client === "undefined" && ( + <> + +

Server disconnected

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

Server disconnected

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

Server disconnected

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

Server disconnected

+

+ Trying to reconnect with server... +

)} {history?.timeline.map((node: HistoryNode, index: number) => { diff --git a/extension/scripts/continuedev-0.1.0-py3-none-any.whl b/extension/scripts/continuedev-0.1.0-py3-none-any.whl index 6cda6f2d..fd3f48d1 100644 Binary files a/extension/scripts/continuedev-0.1.0-py3-none-any.whl and b/extension/scripts/continuedev-0.1.0-py3-none-any.whl differ -- cgit v1.2.3-70-g09d2 From 6acf607cac52f475f54312968afcf03022f7868f Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 2 Jun 2023 16:59:33 -0400 Subject: send server print statements to logfile --- continuedev/src/continuedev/server/main.py | 12 +++++++++--- logging.ini | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 logging.ini (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/server/main.py b/continuedev/src/continuedev/server/main.py index 7b7124de..a8597c8a 100644 --- a/continuedev/src/continuedev/server/main.py +++ b/continuedev/src/continuedev/server/main.py @@ -1,8 +1,10 @@ import os +import sys from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from .ide import router as ide_router from .gui import router as gui_router +import logging import uvicorn import argparse @@ -23,6 +25,7 @@ app.add_middleware( @app.get("/health") def health(): + print("Testing") return {"status": "ok"} @@ -32,12 +35,15 @@ parser.add_argument("-p", "--port", help="server port", type=int, default=8000) args = parser.parse_args() +log_file = open('output.log', 'a') +sys.stdout = log_file + + def run_server(): if os.path.exists("logging.yaml"): - uvicorn.run(app, host="0.0.0.0", port=args.port, - log_config="logging.yaml") + uvicorn.run(app, host="0.0.0.0", port=args.port, log_level="info") else: - uvicorn.run(app, host="0.0.0.0", port=args.port) + uvicorn.run(app, host="0.0.0.0", port=args.port, log_level="info") if __name__ == "__main__": diff --git a/logging.ini b/logging.ini new file mode 100644 index 00000000..5b478619 --- /dev/null +++ b/logging.ini @@ -0,0 +1,27 @@ +[loggers] +keys=root + +[handlers] +keys=logfile,logconsole + +[formatters] +keys=logformatter + +[logger_root] +level=INFO +handlers=logfile, logconsole + +[formatter_logformatter] +format=[%(asctime)s.%(msecs)03d] %(levelname)s [%(thread)d] - %(message)s + +[handler_logfile] +class=handlers.RotatingFileHandler +level=INFO +args=('logfile.log','a') +formatter=logformatter + +[handler_logconsole] +class=handlers.logging.StreamHandler +level=INFO +args=() +formatter=logformatter \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 9c00ece5b8c11d5aeaafefdae6be51c98c807f14 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Fri, 2 Jun 2023 23:21:42 -0400 Subject: recipes folder --- continuedev/src/continuedev/core/autopilot.py | 2 +- continuedev/src/continuedev/core/policy.py | 16 +- continuedev/src/continuedev/core/sdk.py | 2 +- continuedev/src/continuedev/libs/steps/__init__.py | 1 - continuedev/src/continuedev/libs/steps/chroma.py | 61 ---- .../src/continuedev/libs/steps/continue_step.py | 37 --- .../src/continuedev/libs/steps/core/core.py | 208 ------------ .../libs/steps/draft/abstract_method.py | 19 -- .../src/continuedev/libs/steps/draft/dlt.py | 114 ------- .../src/continuedev/libs/steps/draft/redux.py | 47 --- .../src/continuedev/libs/steps/draft/typeorm.py | 43 --- continuedev/src/continuedev/libs/steps/main.py | 358 --------------------- .../src/continuedev/libs/steps/migration.py | 26 -- continuedev/src/continuedev/libs/steps/nate.py | 214 ------------ continuedev/src/continuedev/libs/steps/pytest.py | 37 --- .../src/continuedev/libs/steps/react_posthog.py | 0 .../src/continuedev/libs/steps/steps_on_startup.py | 30 -- continuedev/src/continuedev/libs/steps/ty.py | 155 --------- .../recipes/ContinueRecipeRecipe/README.md | 7 + .../recipes/ContinueRecipeRecipe/main.py | 37 +++ .../recipes/CreatePipelineRecipe/README.md | 0 .../recipes/CreatePipelineRecipe/main.py | 33 ++ .../recipes/CreatePipelineRecipe/steps.py | 85 +++++ continuedev/src/continuedev/recipes/README.md | 9 + .../continuedev/recipes/TemplateRecipe/README.md | 7 + .../src/continuedev/recipes/TemplateRecipe/main.py | 27 ++ .../recipes/WritePytestsRecipe/README.md | 7 + .../continuedev/recipes/WritePytestsRecipe/main.py | 37 +++ .../src/continuedev/server/session_manager.py | 6 - continuedev/src/continuedev/steps/__init__.py | 1 + continuedev/src/continuedev/steps/chroma.py | 61 ++++ continuedev/src/continuedev/steps/core/core.py | 208 ++++++++++++ .../src/continuedev/steps/draft/abstract_method.py | 19 ++ .../src/continuedev/steps/draft/migration.py | 25 ++ continuedev/src/continuedev/steps/draft/redux.py | 47 +++ continuedev/src/continuedev/steps/draft/typeorm.py | 43 +++ continuedev/src/continuedev/steps/main.py | 358 +++++++++++++++++++++ .../src/continuedev/steps/steps_on_startup.py | 28 ++ docs/docs/concepts/step.md | 4 +- docs/docs/walkthroughs/create-a-recipe.md | 2 +- 40 files changed, 1051 insertions(+), 1370 deletions(-) delete mode 100644 continuedev/src/continuedev/libs/steps/__init__.py delete mode 100644 continuedev/src/continuedev/libs/steps/chroma.py delete mode 100644 continuedev/src/continuedev/libs/steps/continue_step.py delete mode 100644 continuedev/src/continuedev/libs/steps/core/core.py delete mode 100644 continuedev/src/continuedev/libs/steps/draft/abstract_method.py delete mode 100644 continuedev/src/continuedev/libs/steps/draft/dlt.py delete mode 100644 continuedev/src/continuedev/libs/steps/draft/redux.py delete mode 100644 continuedev/src/continuedev/libs/steps/draft/typeorm.py delete mode 100644 continuedev/src/continuedev/libs/steps/main.py delete mode 100644 continuedev/src/continuedev/libs/steps/migration.py delete mode 100644 continuedev/src/continuedev/libs/steps/nate.py delete mode 100644 continuedev/src/continuedev/libs/steps/pytest.py delete mode 100644 continuedev/src/continuedev/libs/steps/react_posthog.py delete mode 100644 continuedev/src/continuedev/libs/steps/steps_on_startup.py delete mode 100644 continuedev/src/continuedev/libs/steps/ty.py create mode 100644 continuedev/src/continuedev/recipes/ContinueRecipeRecipe/README.md create mode 100644 continuedev/src/continuedev/recipes/ContinueRecipeRecipe/main.py create mode 100644 continuedev/src/continuedev/recipes/CreatePipelineRecipe/README.md create mode 100644 continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py create mode 100644 continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py create mode 100644 continuedev/src/continuedev/recipes/README.md create mode 100644 continuedev/src/continuedev/recipes/TemplateRecipe/README.md create mode 100644 continuedev/src/continuedev/recipes/TemplateRecipe/main.py create mode 100644 continuedev/src/continuedev/recipes/WritePytestsRecipe/README.md create mode 100644 continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py create mode 100644 continuedev/src/continuedev/steps/__init__.py create mode 100644 continuedev/src/continuedev/steps/chroma.py create mode 100644 continuedev/src/continuedev/steps/core/core.py create mode 100644 continuedev/src/continuedev/steps/draft/abstract_method.py create mode 100644 continuedev/src/continuedev/steps/draft/migration.py create mode 100644 continuedev/src/continuedev/steps/draft/redux.py create mode 100644 continuedev/src/continuedev/steps/draft/typeorm.py create mode 100644 continuedev/src/continuedev/steps/main.py create mode 100644 continuedev/src/continuedev/steps/steps_on_startup.py (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 6e920ab4..85f65dc3 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -8,7 +8,7 @@ from ..server.ide_protocol import AbstractIdeProtocolServer from ..libs.util.queue import AsyncSubscriptionQueue from ..models.main import ContinueBaseModel from .main import Policy, History, FullState, Step, HistoryNode -from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep +from ..steps.core.core import ReversibleStep, ManualEditStep, UserInputStep from ..libs.util.telemetry import capture_event from .sdk import ContinueSDK import asyncio diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 6e264bab..f7fcf21a 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -1,13 +1,13 @@ from typing import List, Tuple, Type -from ..libs.steps.steps_on_startup import StepsOnStartupStep -from ..libs.steps.draft.dlt import CreatePipelineStep +from ..steps.steps_on_startup import StepsOnStartupStep +from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation -from ..libs.steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep, MessageStep, EmptyStep -from ..libs.steps.nate import WritePytestsStep, CreateTableStep +from ..steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep, MessageStep, EmptyStep +from ..recipes.WritePytestsRecipe.main import WritePytestsRecipe # from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma -from ..libs.steps.continue_step import ContinueStepStep +from ..recipes.ContinueRecipeRecipe.main import ContinueStepStep class DemoPolicy(Policy): @@ -22,11 +22,9 @@ class DemoPolicy(Policy): if observation is not None and isinstance(observation, UserInputObservation): # This could be defined with ObservationTypePolicy. Ergonomics not right though. if " test" in observation.user_input.lower(): - return WritePytestsStep(instructions=observation.user_input) + return WritePytestsRecipe(instructions=observation.user_input) elif "/dlt" in observation.user_input.lower() or " dlt" in observation.user_input.lower(): - return CreatePipelineStep() - elif "/table" in observation.user_input: - return CreateTableStep(sql_str=" ".join(observation.user_input.split(" ")[1:])) + return CreatePipelineRecipe() # elif "/ask" in observation.user_input: # return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) # elif "/edit" in observation.user_input: diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index af7754cc..5d0f03fe 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -10,7 +10,7 @@ from ..libs.llm.openai import OpenAI from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step -from ..libs.steps.core.core import * +from ..steps.core.core import * from .env import get_env_var, make_sure_env_exists diff --git a/continuedev/src/continuedev/libs/steps/__init__.py b/continuedev/src/continuedev/libs/steps/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/continuedev/src/continuedev/libs/steps/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/continuedev/src/continuedev/libs/steps/chroma.py b/continuedev/src/continuedev/libs/steps/chroma.py deleted file mode 100644 index 39424c5c..00000000 --- a/continuedev/src/continuedev/libs/steps/chroma.py +++ /dev/null @@ -1,61 +0,0 @@ -from textwrap import dedent -from typing import Coroutine, Union -from ...core.observation import Observation, TextObservation -from ...core.main import Step, ContinueSDK -from .core.core import EditFileStep -from ..chroma.query import query_codebase_index -from .core.core 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 = (await sdk.models.gpt35()).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/continue_step.py b/continuedev/src/continuedev/libs/steps/continue_step.py deleted file mode 100644 index 253bb490..00000000 --- a/continuedev/src/continuedev/libs/steps/continue_step.py +++ /dev/null @@ -1,37 +0,0 @@ -from textwrap import dedent -from ...models.filesystem import RangeInFile -from .main import EditHighlightedCodeStep -from ...core.main import Step -from ...core.sdk import ContinueSDK - - -class ContinueStepStep(Step): - name: str = "Write your own Continue Step." - prompt: str - - async def run(self, sdk: ContinueSDK): - await sdk.run_step(EditHighlightedCodeStep(user_input=dedent(f"""\ - Here is an example of a Step that runs a command and then edits a file. - - ```python - from ...core.main import Step - from ...core.sdk import ContinueSDK - - class RunCommandAndEditFileStep(Step): - name: str = "Run a command and then edit a file." - command: str - file_path: str - prompt: str - - async def run(self, sdk: ContinueSDK): - await sdk.run([command]) - await sdk.edit_file(filename=self.file_path, prompt=self.prompt) - ``` - - Please edit the code to write your own Step that does the following: - - {self.prommpt} - - It should be a subclass of Step as above, implementing the `run` method, and using pydantic attributes to define the parameters. - - """))) diff --git a/continuedev/src/continuedev/libs/steps/core/core.py b/continuedev/src/continuedev/libs/steps/core/core.py deleted file mode 100644 index 9a5d54f0..00000000 --- a/continuedev/src/continuedev/libs/steps/core/core.py +++ /dev/null @@ -1,208 +0,0 @@ -# These steps are depended upon by ContinueSDK -import subprocess -from textwrap import dedent -from typing import Coroutine, List, Union -from ...llm.prompt_utils import MarkdownStyleEncoderDecoder - -from ....models.filesystem_edit import EditDiff, FileEditWithFullContents, FileSystemEdit -from ....models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents -from ....core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation -from ....core.main import Step, SequentialStep - - -class ContinueSDK: - pass - - -class Models: - pass - - -class ReversibleStep(Step): - async def reverse(self, sdk: ContinueSDK): - raise NotImplementedError - - -class FileSystemEditStep(ReversibleStep): - edit: FileSystemEdit - _diff: Union[EditDiff, None] = None - - hide: bool = True - - async def run(self, sdk: "ContinueSDK") -> Coroutine[Observation, None, None]: - self._diff = await sdk.ide.applyFileSystemEdit(self.edit) - return None - - async def reverse(self, sdk: "ContinueSDK"): - await sdk.ide.applyFileSystemEdit(self._diff.backward) - # Where and when should file saves happen? - - -class ShellCommandsStep(Step): - cmds: List[str] - cwd: str | None = None - name: str = "Run Shell Commands" - - async def describe(self, models: Models) -> Coroutine[str, None, None]: - cmds_str = "\n".join(self.cmds) - return (await models.gpt35()).complete(f"{cmds_str}\n\nSummarize what was done in these shell commands, using markdown bullet points:") - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - cwd = await sdk.ide.getWorkspaceDirectory() if self.cwd is None else self.cwd - - process = subprocess.Popen( - '/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd) - - stdin_input = "\n".join(self.cmds) - out, err = process.communicate(stdin_input.encode()) - - # If it fails, return the error - if err is not None and err != "": - return TextObservation(text=err) - - return None - - -class EditCodeStep(Step): - # Might make an even more specific atomic step, which is "apply file edit" - range_in_files: List[RangeInFile] - prompt: str # String with {code} somewhere - name: str = "Edit code" - - _edit_diffs: Union[List[EditDiff], None] = None - _prompt: Union[str, None] = None - _completion: Union[str, None] = None - - async def describe(self, 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} - - 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 = (await sdk.models.gpt35()).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, models: Models) -> Coroutine[str, None, None]: - return "Editing file: " + self.filepath - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - file_contents = await sdk.ide.readFile(self.filepath) - await sdk.run_step(EditCodeStep( - range_in_files=[RangeInFile.from_entire_file( - self.filepath, file_contents)], - prompt=self.prompt - )) - - -class ManualEditStep(ReversibleStep): - edit_diff: EditDiff - hide: bool = True - - hide: bool = True - - async def describe(self, models: Models) -> Coroutine[str, None, None]: - return "Manual edit step" - # TODO - only handling FileEdit here, but need all other types of FileSystemEdits - # Also requires the merge_file_edit function - # return llm.complete(dedent(f"""This code was replaced: - - # {self.edit_diff.backward.replacement} - - # With this code: - - # {self.edit_diff.forward.replacement} - - # Maximally concise summary of changes in bullet points (can use markdown): - # """)) - - @classmethod - def from_sequence(cls, edits: List[FileEditWithFullContents]) -> "ManualEditStep": - diffs = [] - for edit in edits: - _, diff = FileSystem.apply_edit_to_str( - edit.fileContents, edit.fileEdit) - diffs.append(diff) - return cls(edit_diff=EditDiff.from_sequence(diffs)) - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - return None - - async def reverse(self, sdk: ContinueSDK): - await sdk.ide.applyFileSystemEdit(self.edit_diff.backward) - - -class UserInputStep(Step): - user_input: str - name: str = "User Input" - hide: bool = True - - async def describe(self, models: Models) -> Coroutine[str, None, None]: - return self.user_input - - async def run(self, sdk: ContinueSDK) -> Coroutine[UserInputObservation, None, None]: - return UserInputObservation(user_input=self.user_input) - - -class WaitForUserInputStep(Step): - prompt: str - name: str = "Waiting for user input" - - _description: Union[str, None] = None - - async def describe(self, models: Models) -> Coroutine[str, None, None]: - return self.prompt - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - 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, models: Models) -> Coroutine[str, None, None]: - return self.prompt - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - self._description = self.prompt - resp = await sdk.wait_for_user_input() - return TextObservation(text=resp) diff --git a/continuedev/src/continuedev/libs/steps/draft/abstract_method.py b/continuedev/src/continuedev/libs/steps/draft/abstract_method.py deleted file mode 100644 index f3131c4b..00000000 --- a/continuedev/src/continuedev/libs/steps/draft/abstract_method.py +++ /dev/null @@ -1,19 +0,0 @@ -from ....core.sdk import ContinueSDK -from ....core.main import 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 deleted file mode 100644 index 9cec5014..00000000 --- a/continuedev/src/continuedev/libs/steps/draft/dlt.py +++ /dev/null @@ -1,114 +0,0 @@ -from textwrap import dedent - -from ....core.sdk import Models - -from ....core.observation import DictObservation -from ....models.filesystem_edit import AddFile -from ....core.main import Step -from ....core.sdk import ContinueSDK -from ..core.core import WaitForUserInputStep -from ..main import MessageStep - - -class SetupPipelineStep(Step): - hide: bool = True - name: str = "Setup dlt Pipeline" - - api_description: str # e.g. "I want to load data from the weatherapi.com API" - - async def describe(self, models: Models): - return dedent(f"""\ - This step will create a new dlt pipeline that loads data from an API, as per your request: - {self.api_description} - """) - - async def run(self, sdk: ContinueSDK): - source_name = (await sdk.models.gpt35()).complete( - f"Write a snake_case name for the data source described by {self.api_description}: ").strip() - 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', - f'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(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}) - - -class ValidatePipelineStep(Step): - hide: bool = True - - async def run(self, sdk: ContinueSDK): - source_name = sdk.history.last_observation().values["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) - ''') - - 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') - - -class CreatePipelineStep(Step): - hide: bool = True - - async def run(self, sdk: ContinueSDK): - await sdk.run_step( - MessageStep(message=dedent("""\ - This recipe will walk you through the process of creating a dlt pipeline for your chosen data source. With the help of Continue, you will: - - Create a Python virtual environment with dlt installed - - Run `dlt init` to generate a pipeline template - - Write the code to call the API - - Add any required API keys to the `secrets.toml` file - - Test that the API call works - - Load the data into a local DuckDB instance - - Write a query to view the data""")) >> - WaitForUserInputStep(prompt="What API do you want to load data from?") >> - SetupPipelineStep(api_description="WeatherAPI.com API") >> - MessageStep(message=dedent("""\ - This step will validate that your dlt pipeline is working as expected: - - Test that the API call works - - Load the data into a local DuckDB instance - - Write a query to view the data - """)) >> - ValidatePipelineStep() - ) diff --git a/continuedev/src/continuedev/libs/steps/draft/redux.py b/continuedev/src/continuedev/libs/steps/draft/redux.py deleted file mode 100644 index efaa9ba4..00000000 --- a/continuedev/src/continuedev/libs/steps/draft/redux.py +++ /dev/null @@ -1,47 +0,0 @@ -from ....core.main import Step -from ....core.sdk import ContinueSDK -from ..core.core import 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 deleted file mode 100644 index d06a6fb4..00000000 --- a/continuedev/src/continuedev/libs/steps/draft/typeorm.py +++ /dev/null @@ -1,43 +0,0 @@ -from textwrap import dedent -from ....core.main import Step -from ....core.sdk import 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 deleted file mode 100644 index 73ad3352..00000000 --- a/continuedev/src/continuedev/libs/steps/main.py +++ /dev/null @@ -1,358 +0,0 @@ -from typing import Coroutine, List, Union - -from pydantic import BaseModel - -from ..util.traceback_parsers import parse_python_traceback -from ..llm import LLM -from ...models.main import Traceback, Range -from ...models.filesystem_edit import EditDiff, FileEdit -from ...models.filesystem import RangeInFile, RangeInFileWithContents -from ...core.observation import Observation, TextObservation, TracebackObservation -from ..llm.prompt_utils import MarkdownStyleEncoderDecoder -from textwrap import dedent -from ...core.main import Step -from ...core.sdk import ContinueSDK, Models -from ...core.observation import Observation -import subprocess -from .core.core import EditCodeStep - - -class RunCodeStep(Step): - cmd: str - - async def describe(self, models: Models) -> Coroutine[str, None, None]: - return f"Ran command: `{self.cmd}`" - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - 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 Policy(BaseModel): - pass - - -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, models: Models) -> 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 FasterEditHighlightedCodeStep(Step): - user_input: str - hide = True - _completion: str = "Edit Code" - _edit_diffs: Union[List[EditDiff], None] = None - _prompt: str = dedent("""\ - You will be given code to edit in order to perfectly satisfy the user request. All the changes you make must be described as replacements, which you should format in the following way: - FILEPATH - - REPLACE_ME - - REPLACE_WITH - - - where and can be multiple lines, but should be the mininum needed to make the edit. Be sure to maintain existing whitespace at the start of lines. - - For example, if you want to replace the code `x = 1` with `x = 2` in main.py, you would write: - FILEPATH - main.py - REPLACE_ME - x = 1 - REPLACE_WITH - x = 2 - If you wanted to delete the code - ``` - def sum(a, b): - return a + b - ``` - in main.py, you would write: - FILEPATH - main.py - REPLACE_ME - def sum(a, b): - return a + b - REPLACE_WITH - - You may need to make multiple edits; respond with exactly as many as needed. - - Below is the code before changes: - - {code} - - This is the user request: "{user_input}" - Here is the description of changes to make: -""") - - async def describe(self, models: Models) -> 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 = (await sdk.models.gpt35()).complete(prompt) - - # Temporarily doing this to generate description. - self._prompt = prompt - self._completion = completion - print(completion) - - # ALTERNATIVE DECODING STEP HERE - raw_file_edits = [] - lines = completion.split("\n") - current_edit = {} - status = "FILEPATH" - for i in range(0, len(lines)): - line = lines[i] - if line == "FILEPATH": - if "FILEPATH" in current_edit: - raw_file_edits.append(current_edit) - current_edit = {} - status = "FILEPATH" - elif line == "REPLACE_ME": - status = "REPLACE_ME" - elif line == "REPLACE_WITH": - status = "REPLACE_WITH" - elif status == "FILEPATH": - current_edit["filepath"] = line - elif status == "REPLACE_ME": - if "replace_me" in current_edit: - current_edit["replace_me"] += "\n" + line - else: - current_edit["replace_me"] = line - elif status == "REPLACE_WITH": - if "replace_with" in current_edit: - current_edit["replace_with"] += "\n" + line - else: - current_edit["replace_with"] = line - if "filepath" in current_edit: - raw_file_edits.append(current_edit) - - file_edits = [] - for edit in raw_file_edits: - filepath = edit["filepath"] - replace_me = edit["replace_me"] - replace_with = edit["replace_with"] - file_edits.append( - FileEdit(filepath=filepath, range=Range.from_lines_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 StarCoderEditHighlightedCodeStep(Step): - user_input: str - hide = False - _prompt: str = "{code}{user_request}" - - _prompt_and_completion: str = "" - - 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]: - range_in_files = await sdk.ide.getHighlightedCode() - if len(range_in_files) == 0: - # Get the full contents of all open files - files = await sdk.ide.getOpenFiles() - contents = {} - for file in files: - contents[file] = await sdk.ide.readFile(file) - - range_in_files = [RangeInFile.from_entire_file( - filepath, content) for filepath, content in contents.items()] - - rif_with_contents = [] - for range_in_file in range_in_files: - file_contents = await sdk.ide.readRangeInFile(range_in_file) - rif_with_contents.append( - RangeInFileWithContents.from_range_in_file(range_in_file, file_contents)) - - rif_dict = {} - for rif in rif_with_contents: - rif_dict[rif.filepath] = rif.contents - - for rif in rif_with_contents: - prompt = self._prompt.format( - code=rif.contents, user_request=self.user_input) - completion = str((await sdk.models.starcoder()).complete(prompt)) - eot_token = "<|endoftext|>" - if completion.endswith(eot_token): - completion = completion[:completion.rindex(eot_token)] - - self._prompt_and_completion += prompt + completion - - await sdk.ide.applyFileSystemEdit( - FileEdit(filepath=rif.filepath, range=rif.range, replacement=completion)) - await sdk.ide.saveFile(rif.filepath) - await sdk.ide.setFileOpen(rif.filepath) - - -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" - - 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, models: Models) -> 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, models: Models) -> 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 - - -class MessageStep(Step): - name: str = "Message" - message: str - - async def describe(self, models: Models) -> Coroutine[str, None, None]: - return self.message - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - return TextObservation(text=self.message) - - -class EmptyStep(Step): - hide: bool = True - - async def describe(self, models: Models) -> Coroutine[str, None, None]: - return "" - - async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - pass diff --git a/continuedev/src/continuedev/libs/steps/migration.py b/continuedev/src/continuedev/libs/steps/migration.py deleted file mode 100644 index 7b70422d..00000000 --- a/continuedev/src/continuedev/libs/steps/migration.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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.main 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 (await sdk.models.gpt35()).complete(f"{recent_edits_string}\n\nGenerate a short description of the migration made in the above changes:\n") - await sdk.run_step(RunCommandStep(cmd=f"cd libs && poetry run alembic revision --autogenerate -m {description}")) - migration_file = f"libs/alembic/versions/{?}.py" - contents = await sdk.ide.readFile(migration_file) - 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 deleted file mode 100644 index 2f84e9d7..00000000 --- a/continuedev/src/continuedev/libs/steps/nate.py +++ /dev/null @@ -1,214 +0,0 @@ -from textwrap import dedent -import time -from typing import Coroutine, Union - -from ...models.filesystem import RangeInFile -from ...models.filesystem_edit import AddDirectory, AddFile -from ...core.observation import Observation, TextObservation -from ...core.main import Step, ContinueSDK -from .main import RunCommandStep -from .core.core import WaitForUserConfirmationStep, EditCodeStep, EditFileStep -import os - - -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 = (await sdk.models.gpt35()).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 = (await sdk.models.gpt35()).complete( - # f"{self.sql_str}\n\nWrite a TypeORM entity called {entity_name} for this table, importing as necessary:") - # (await sdk.models.gpt35()).complete("What is the name of the entity?") - await sdk.apply_filesystem_edit(AddFile(filepath=f"/Users/natesesti/Desktop/continue/extension/examples/python/MyProject/src/entity/{entity_name}.ts", content=orm_entity)) - await sdk.ide.setFileOpen(f"/Users/natesesti/Desktop/continue/extension/examples/python/MyProject/src/entity/{entity_name}.ts", True) - - # 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 deleted file mode 100644 index 2e83ae2d..00000000 --- a/continuedev/src/continuedev/libs/steps/pytest.py +++ /dev/null @@ -1,37 +0,0 @@ -from textwrap import dedent -from ...models.filesystem_edit import AddDirectory, AddFile -from ...core.main 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 = (await sdk.models.gpt35()).complete(prompt) - await sdk.apply_filesystem_edit(AddFile(filepath=path, content=tests)) diff --git a/continuedev/src/continuedev/libs/steps/react_posthog.py b/continuedev/src/continuedev/libs/steps/react_posthog.py deleted file mode 100644 index e69de29b..00000000 diff --git a/continuedev/src/continuedev/libs/steps/steps_on_startup.py b/continuedev/src/continuedev/libs/steps/steps_on_startup.py deleted file mode 100644 index fd1eb8f0..00000000 --- a/continuedev/src/continuedev/libs/steps/steps_on_startup.py +++ /dev/null @@ -1,30 +0,0 @@ - - -from ...core.main import ContinueSDK, Models, Step -from .main import UserInputStep -from .draft.dlt import CreatePipelineStep - - -step_name_to_step_class = { - "UserInputStep": UserInputStep, - "CreatePipelineStep": CreatePipelineStep -} - - -class StepsOnStartupStep(Step): - hide: bool = True - - async def describe(self, models: Models): - return "Running steps on startup" - - async def run(self, sdk: ContinueSDK): - steps_descriptions = (await sdk.get_config()).steps_on_startup - - for step_name, step_params in steps_descriptions.items(): - try: - step = step_name_to_step_class[step_name](**step_params) - except: - print( - f"Incorrect parameters for step {step_name}. Parameters provided were: {step_params}") - continue - await sdk.run_step(step) diff --git a/continuedev/src/continuedev/libs/steps/ty.py b/continuedev/src/continuedev/libs/steps/ty.py deleted file mode 100644 index 9dde7c86..00000000 --- a/continuedev/src/continuedev/libs/steps/ty.py +++ /dev/null @@ -1,155 +0,0 @@ -import subprocess -from ...models.main import Position, Range -from ...models.filesystem import RangeInFile -from ...models.filesystem_edit import AddDirectory, AddFile, FileEdit -from ...core.observation import DictObservation -from ...core.main import History, Step, Policy -from ...core.sdk import ContinueSDK -from .main import RunCommandStep -from ..steps.core.core import EditCodeStep, WaitForUserConfirmationStep, WaitForUserInputStep - -source_name = "weather_api" - - -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 = (await sdk.models.gpt35()).complete( - # f"Write a snake_case name for the data source described by {self.api_description}: ").strip() - filename = f'/Users/natesesti/Desktop/continue/extension/examples/python/{source_name}.py' - - # 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() - ) diff --git a/continuedev/src/continuedev/recipes/ContinueRecipeRecipe/README.md b/continuedev/src/continuedev/recipes/ContinueRecipeRecipe/README.md new file mode 100644 index 00000000..df66104f --- /dev/null +++ b/continuedev/src/continuedev/recipes/ContinueRecipeRecipe/README.md @@ -0,0 +1,7 @@ +# ContinueRecipeRecipe + +A recipe for building recipes! + +## How to use this recipe + +This recipe takes a single input, a description of the recipe to be built. diff --git a/continuedev/src/continuedev/recipes/ContinueRecipeRecipe/main.py b/continuedev/src/continuedev/recipes/ContinueRecipeRecipe/main.py new file mode 100644 index 00000000..953fb0c2 --- /dev/null +++ b/continuedev/src/continuedev/recipes/ContinueRecipeRecipe/main.py @@ -0,0 +1,37 @@ +from textwrap import dedent +from ...models.filesystem import RangeInFile +from ...steps.main import EditHighlightedCodeStep +from ...core.main import Step +from ...core.sdk import ContinueSDK + + +class ContinueStepStep(Step): + name: str = "Write your own Continue Step." + prompt: str + + async def run(self, sdk: ContinueSDK): + await sdk.run_step(EditHighlightedCodeStep(user_input=dedent(f"""\ + Here is an example of a Step that runs a command and then edits a file. + + ```python + from ...core.main import Step + from ...core.sdk import ContinueSDK + + class RunCommandAndEditFileStep(Step): + name: str = "Run a command and then edit a file." + command: str + file_path: str + prompt: str + + async def run(self, sdk: ContinueSDK): + await sdk.run([command]) + await sdk.edit_file(filename=self.file_path, prompt=self.prompt) + ``` + + Please edit the code to write your own Step that does the following: + + {self.prommpt} + + It should be a subclass of Step as above, implementing the `run` method, and using pydantic attributes to define the parameters. + + """))) diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/README.md b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/README.md new file mode 100644 index 00000000..e69de29b diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py new file mode 100644 index 00000000..c0699cae --- /dev/null +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py @@ -0,0 +1,33 @@ +from textwrap import dedent + +from ...core.main import Step +from ...core.sdk import ContinueSDK +from ...steps.core.core import WaitForUserInputStep +from ...steps.main import MessageStep +from .steps import SetupPipelineStep, ValidatePipelineStep + + +class CreatePipelineRecipe(Step): + hide: bool = True + + async def run(self, sdk: ContinueSDK): + await sdk.run_step( + MessageStep(message=dedent("""\ + This recipe will walk you through the process of creating a dlt pipeline for your chosen data source. With the help of Continue, you will: + - Create a Python virtual environment with dlt installed + - Run `dlt init` to generate a pipeline template + - Write the code to call the API + - Add any required API keys to the `secrets.toml` file + - Test that the API call works + - Load the data into a local DuckDB instance + - Write a query to view the data""")) >> + WaitForUserInputStep(prompt="What API do you want to load data from?") >> + SetupPipelineStep(api_description="WeatherAPI.com API") >> + MessageStep(message=dedent("""\ + This step will validate that your dlt pipeline is working as expected: + - Test that the API call works + - Load the data into a local DuckDB instance + - Write a query to view the data + """)) >> + ValidatePipelineStep() + ) diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py new file mode 100644 index 00000000..b480223a --- /dev/null +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py @@ -0,0 +1,85 @@ +from textwrap import dedent + +from ...core.sdk import Models +from ...core.observation import DictObservation +from ...models.filesystem_edit import AddFile +from ...core.main import Step +from ...core.sdk import ContinueSDK + + +class SetupPipelineStep(Step): + hide: bool = True + name: str = "Setup dlt Pipeline" + + api_description: str # e.g. "I want to load data from the weatherapi.com API" + + async def describe(self, models: Models): + return dedent(f"""\ + This step will create a new dlt pipeline that loads data from an API, as per your request: + {self.api_description} + """) + + async def run(self, sdk: ContinueSDK): + source_name = (await sdk.models.gpt35()).complete( + f"Write a snake_case name for the data source described by {self.api_description}: ").strip() + 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', + f'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(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}) + + +class ValidatePipelineStep(Step): + hide: bool = True + + async def run(self, sdk: ContinueSDK): + source_name = sdk.history.last_observation().values["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) + ''') + + query_filename = (await sdk.ide.getWorkspaceDirectory()) + "/query.py" + await sdk.apply_filesystem_edit(AddFile(filepath=query_filename, content=tables_query_code)) + await sdk.run('env/bin/python3 query.py') diff --git a/continuedev/src/continuedev/recipes/README.md b/continuedev/src/continuedev/recipes/README.md new file mode 100644 index 00000000..5e01cc97 --- /dev/null +++ b/continuedev/src/continuedev/recipes/README.md @@ -0,0 +1,9 @@ +# This is a collaborative collection of Continue recipes + +Recipes here will automatically be made available in the [Continue VS Code extension](https://marketplace.visualstudio.com/items?itemName=Continue.continue). + +The `recipes` folder contains all recipes, each with the same structure. **If you wish to create your own recipe, please do the following:** + +1. Create a new subfolder in `recipes`, with the name of your recipe (for example `MyNewRecipe`). +2. Make 2 files in this folder: 1) a `README.md` describing your recipe and how to use it and 2) a `main.py` including a single class with the name of your recipe (e.g. `MyNewRecipe`). +3. Write any utility code other than the main recipe class in a separate file, which you can import in `main.py`. Particularly if you decide to break the recipe into multiple sub-steps, try to keep these separate. diff --git a/continuedev/src/continuedev/recipes/TemplateRecipe/README.md b/continuedev/src/continuedev/recipes/TemplateRecipe/README.md new file mode 100644 index 00000000..91d1123b --- /dev/null +++ b/continuedev/src/continuedev/recipes/TemplateRecipe/README.md @@ -0,0 +1,7 @@ +# TemplateRecipe + +This folder is a template that you can copy to create your own recipe. + +## How to use this recipe + +Explain here what users should know when using your recipe. What inputs does it have and what actions will it perform? diff --git a/continuedev/src/continuedev/recipes/TemplateRecipe/main.py b/continuedev/src/continuedev/recipes/TemplateRecipe/main.py new file mode 100644 index 00000000..94675725 --- /dev/null +++ b/continuedev/src/continuedev/recipes/TemplateRecipe/main.py @@ -0,0 +1,27 @@ +from typing import Coroutine +from continuedev.core import Step, ContinueSDK, Observation, Models + + +class TemplateRecipe(Step): + """ + A simple recipe that appends a print statement to the currently open file. + Use this as a template to create your own! + """ + + # Paremeters for the recipe + name: str + + # A title for the recipe, to be displayed in the GUI + title = "Template Recipe" + + # A description of what the recipe accomplished, to be displayed in the GUI + async def describe(self, models: Models) -> Coroutine[str, None, None]: + return f"Appended a statement to print `Hello, {self.name}!` at the end of the file." + + # The code executed when the recipe is run + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + open_files = await sdk.ide.getOpenFiles() + await sdk.edit_file( + filename=open_files[0], + prompt=f"Append a statement to print `Hello, {self.name}!` at the end of the file." + ) diff --git a/continuedev/src/continuedev/recipes/WritePytestsRecipe/README.md b/continuedev/src/continuedev/recipes/WritePytestsRecipe/README.md new file mode 100644 index 00000000..5ce33ecb --- /dev/null +++ b/continuedev/src/continuedev/recipes/WritePytestsRecipe/README.md @@ -0,0 +1,7 @@ +# CreatePytestsRecipe + +A recipe for writing unit tests in Pytest. + +# How to use this recipe + +Call this recipe with a python file open that you would like to test. It will create tests in a `tests/` folder adjacent to the file with the test file given the same name prepended by `test_`. diff --git a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py new file mode 100644 index 00000000..a35055f3 --- /dev/null +++ b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py @@ -0,0 +1,37 @@ +from textwrap import dedent +from ...models.filesystem_edit import AddDirectory, AddFile +from ...core.main import Step, ContinueSDK +import os + + +class WritePytestsRecipe(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 = (await sdk.models.gpt35()).complete(prompt) + await sdk.apply_filesystem_edit(AddFile(filepath=path, content=tests)) diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py index 5598e140..0dbfaf38 100644 --- a/continuedev/src/continuedev/server/session_manager.py +++ b/continuedev/src/continuedev/server/session_manager.py @@ -6,7 +6,6 @@ from ..models.filesystem_edit import FileEditWithFullContents from ..core.policy import DemoPolicy from ..core.main import FullState from ..core.autopilot import Autopilot -from ..libs.steps.nate import ImplementAbstractMethodStep from .ide_protocol import AbstractIdeProtocolServer import asyncio import nest_asyncio @@ -34,11 +33,6 @@ class DemoAutopilot(Autopilot): self._manual_edits_buffer.append(edit) # Note that you're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) - # FOR DEMO PURPOSES - if edit.fileEdit.filepath.endswith("filesystem.py") and "List" in self.cumulative_edit_string and ":" in edit.fileEdit.replacement: - self.cumulative_edit_string = "" - asyncio.create_task(self.run_from_step( - ImplementAbstractMethodStep())) class SessionManager: diff --git a/continuedev/src/continuedev/steps/__init__.py b/continuedev/src/continuedev/steps/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/continuedev/src/continuedev/steps/__init__.py @@ -0,0 +1 @@ + diff --git a/continuedev/src/continuedev/steps/chroma.py b/continuedev/src/continuedev/steps/chroma.py new file mode 100644 index 00000000..59a8b6e0 --- /dev/null +++ b/continuedev/src/continuedev/steps/chroma.py @@ -0,0 +1,61 @@ +from textwrap import dedent +from typing import Coroutine, Union +from ..core.observation import Observation, TextObservation +from ..core.main import Step, ContinueSDK +from .core.core import EditFileStep +from ..libs.chroma.query import query_codebase_index +from .core.core 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 = (await sdk.models.gpt35()).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/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py new file mode 100644 index 00000000..e54a9a21 --- /dev/null +++ b/continuedev/src/continuedev/steps/core/core.py @@ -0,0 +1,208 @@ +# These steps are depended upon by ContinueSDK +import subprocess +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 import FileSystem, RangeInFile, RangeInFileWithContents +from ...core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation +from ...core.main import Step, SequentialStep + + +class ContinueSDK: + pass + + +class Models: + pass + + +class ReversibleStep(Step): + async def reverse(self, sdk: ContinueSDK): + raise NotImplementedError + + +class FileSystemEditStep(ReversibleStep): + edit: FileSystemEdit + _diff: Union[EditDiff, None] = None + + hide: bool = True + + async def run(self, sdk: "ContinueSDK") -> Coroutine[Observation, None, None]: + self._diff = await sdk.ide.applyFileSystemEdit(self.edit) + return None + + async def reverse(self, sdk: "ContinueSDK"): + await sdk.ide.applyFileSystemEdit(self._diff.backward) + # Where and when should file saves happen? + + +class ShellCommandsStep(Step): + cmds: List[str] + cwd: str | None = None + name: str = "Run Shell Commands" + + async def describe(self, models: Models) -> Coroutine[str, None, None]: + cmds_str = "\n".join(self.cmds) + return (await models.gpt35()).complete(f"{cmds_str}\n\nSummarize what was done in these shell commands, using markdown bullet points:") + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + cwd = await sdk.ide.getWorkspaceDirectory() if self.cwd is None else self.cwd + + process = subprocess.Popen( + '/bin/bash', stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd) + + stdin_input = "\n".join(self.cmds) + out, err = process.communicate(stdin_input.encode()) + + # If it fails, return the error + if err is not None and err != "": + return TextObservation(text=err) + + return None + + +class EditCodeStep(Step): + # Might make an even more specific atomic step, which is "apply file edit" + range_in_files: List[RangeInFile] + prompt: str # String with {code} somewhere + name: str = "Edit code" + + _edit_diffs: Union[List[EditDiff], None] = None + _prompt: Union[str, None] = None + _completion: Union[str, None] = None + + async def describe(self, 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} + + 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 = (await sdk.models.gpt35()).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, models: Models) -> Coroutine[str, None, None]: + return "Editing file: " + self.filepath + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + file_contents = await sdk.ide.readFile(self.filepath) + await sdk.run_step(EditCodeStep( + range_in_files=[RangeInFile.from_entire_file( + self.filepath, file_contents)], + prompt=self.prompt + )) + + +class ManualEditStep(ReversibleStep): + edit_diff: EditDiff + hide: bool = True + + hide: bool = True + + async def describe(self, models: Models) -> Coroutine[str, None, None]: + return "Manual edit step" + # TODO - only handling FileEdit here, but need all other types of FileSystemEdits + # Also requires the merge_file_edit function + # return llm.complete(dedent(f"""This code was replaced: + + # {self.edit_diff.backward.replacement} + + # With this code: + + # {self.edit_diff.forward.replacement} + + # Maximally concise summary of changes in bullet points (can use markdown): + # """)) + + @classmethod + def from_sequence(cls, edits: List[FileEditWithFullContents]) -> "ManualEditStep": + diffs = [] + for edit in edits: + _, diff = FileSystem.apply_edit_to_str( + edit.fileContents, edit.fileEdit) + diffs.append(diff) + return cls(edit_diff=EditDiff.from_sequence(diffs)) + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + return None + + async def reverse(self, sdk: ContinueSDK): + await sdk.ide.applyFileSystemEdit(self.edit_diff.backward) + + +class UserInputStep(Step): + user_input: str + name: str = "User Input" + hide: bool = True + + async def describe(self, models: Models) -> Coroutine[str, None, None]: + return self.user_input + + async def run(self, sdk: ContinueSDK) -> Coroutine[UserInputObservation, None, None]: + return UserInputObservation(user_input=self.user_input) + + +class WaitForUserInputStep(Step): + prompt: str + name: str = "Waiting for user input" + + _description: Union[str, None] = None + + async def describe(self, models: Models) -> Coroutine[str, None, None]: + return self.prompt + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + 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, models: Models) -> Coroutine[str, None, None]: + return self.prompt + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + self._description = self.prompt + resp = await sdk.wait_for_user_input() + return TextObservation(text=resp) diff --git a/continuedev/src/continuedev/steps/draft/abstract_method.py b/continuedev/src/continuedev/steps/draft/abstract_method.py new file mode 100644 index 00000000..f3131c4b --- /dev/null +++ b/continuedev/src/continuedev/steps/draft/abstract_method.py @@ -0,0 +1,19 @@ +from ....core.sdk import ContinueSDK +from ....core.main import 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/steps/draft/migration.py b/continuedev/src/continuedev/steps/draft/migration.py new file mode 100644 index 00000000..ea538bb7 --- /dev/null +++ b/continuedev/src/continuedev/steps/draft/migration.py @@ -0,0 +1,25 @@ +# When an edit is made to an existing class or a new sqlalchemy class is created, +# this should be kicked off. + +from ..main import EditCodeStep, RunCommandStep +from ...core.main 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 (await sdk.models.gpt35()).complete(f"{recent_edits_string}\n\nGenerate a short description of the migration made in the above changes:\n") + await sdk.run_step(RunCommandStep(cmd=f"cd libs && poetry run alembic revision --autogenerate -m {description}")) + migration_file = f"libs/alembic/versions/{?}.py" + contents = await sdk.ide.readFile(migration_file) + 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/steps/draft/redux.py b/continuedev/src/continuedev/steps/draft/redux.py new file mode 100644 index 00000000..17506316 --- /dev/null +++ b/continuedev/src/continuedev/steps/draft/redux.py @@ -0,0 +1,47 @@ +from ...core.main import Step +from ...core.sdk import ContinueSDK +from ..core.core import 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/steps/draft/typeorm.py b/continuedev/src/continuedev/steps/draft/typeorm.py new file mode 100644 index 00000000..153c855f --- /dev/null +++ b/continuedev/src/continuedev/steps/draft/typeorm.py @@ -0,0 +1,43 @@ +from textwrap import dedent +from ...core.main import Step +from ...core.sdk import 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/steps/main.py b/continuedev/src/continuedev/steps/main.py new file mode 100644 index 00000000..8588ec92 --- /dev/null +++ b/continuedev/src/continuedev/steps/main.py @@ -0,0 +1,358 @@ +from typing import Coroutine, List, Union + +from pydantic import BaseModel + +from ..libs.util.traceback_parsers import parse_python_traceback +from ..libs.llm import LLM +from ..models.main import Traceback, Range +from ..models.filesystem_edit import EditDiff, FileEdit +from ..models.filesystem import RangeInFile, RangeInFileWithContents +from ..core.observation import Observation, TextObservation, TracebackObservation +from ..libs.llm.prompt_utils import MarkdownStyleEncoderDecoder +from textwrap import dedent +from ..core.main import Step +from ..core.sdk import ContinueSDK, Models +from ..core.observation import Observation +import subprocess +from .core.core import EditCodeStep + + +class RunCodeStep(Step): + cmd: str + + async def describe(self, models: Models) -> Coroutine[str, None, None]: + return f"Ran command: `{self.cmd}`" + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + 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 Policy(BaseModel): + pass + + +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, models: Models) -> 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 FasterEditHighlightedCodeStep(Step): + user_input: str + hide = True + _completion: str = "Edit Code" + _edit_diffs: Union[List[EditDiff], None] = None + _prompt: str = dedent("""\ + You will be given code to edit in order to perfectly satisfy the user request. All the changes you make must be described as replacements, which you should format in the following way: + FILEPATH + + REPLACE_ME + + REPLACE_WITH + + + where and can be multiple lines, but should be the mininum needed to make the edit. Be sure to maintain existing whitespace at the start of lines. + + For example, if you want to replace the code `x = 1` with `x = 2` in main.py, you would write: + FILEPATH + main.py + REPLACE_ME + x = 1 + REPLACE_WITH + x = 2 + If you wanted to delete the code + ``` + def sum(a, b): + return a + b + ``` + in main.py, you would write: + FILEPATH + main.py + REPLACE_ME + def sum(a, b): + return a + b + REPLACE_WITH + + You may need to make multiple edits; respond with exactly as many as needed. + + Below is the code before changes: + + {code} + + This is the user request: "{user_input}" + Here is the description of changes to make: +""") + + async def describe(self, models: Models) -> 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 = (await sdk.models.gpt35()).complete(prompt) + + # Temporarily doing this to generate description. + self._prompt = prompt + self._completion = completion + print(completion) + + # ALTERNATIVE DECODING STEP HERE + raw_file_edits = [] + lines = completion.split("\n") + current_edit = {} + status = "FILEPATH" + for i in range(0, len(lines)): + line = lines[i] + if line == "FILEPATH": + if "FILEPATH" in current_edit: + raw_file_edits.append(current_edit) + current_edit = {} + status = "FILEPATH" + elif line == "REPLACE_ME": + status = "REPLACE_ME" + elif line == "REPLACE_WITH": + status = "REPLACE_WITH" + elif status == "FILEPATH": + current_edit["filepath"] = line + elif status == "REPLACE_ME": + if "replace_me" in current_edit: + current_edit["replace_me"] += "\n" + line + else: + current_edit["replace_me"] = line + elif status == "REPLACE_WITH": + if "replace_with" in current_edit: + current_edit["replace_with"] += "\n" + line + else: + current_edit["replace_with"] = line + if "filepath" in current_edit: + raw_file_edits.append(current_edit) + + file_edits = [] + for edit in raw_file_edits: + filepath = edit["filepath"] + replace_me = edit["replace_me"] + replace_with = edit["replace_with"] + file_edits.append( + FileEdit(filepath=filepath, range=Range.from_lines_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 StarCoderEditHighlightedCodeStep(Step): + user_input: str + hide = False + _prompt: str = "{code}{user_request}" + + _prompt_and_completion: str = "" + + 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]: + range_in_files = await sdk.ide.getHighlightedCode() + if len(range_in_files) == 0: + # Get the full contents of all open files + files = await sdk.ide.getOpenFiles() + contents = {} + for file in files: + contents[file] = await sdk.ide.readFile(file) + + range_in_files = [RangeInFile.from_entire_file( + filepath, content) for filepath, content in contents.items()] + + rif_with_contents = [] + for range_in_file in range_in_files: + file_contents = await sdk.ide.readRangeInFile(range_in_file) + rif_with_contents.append( + RangeInFileWithContents.from_range_in_file(range_in_file, file_contents)) + + rif_dict = {} + for rif in rif_with_contents: + rif_dict[rif.filepath] = rif.contents + + for rif in rif_with_contents: + prompt = self._prompt.format( + code=rif.contents, user_request=self.user_input) + completion = str((await sdk.models.starcoder()).complete(prompt)) + eot_token = "<|endoftext|>" + if completion.endswith(eot_token): + completion = completion[:completion.rindex(eot_token)] + + self._prompt_and_completion += prompt + completion + + await sdk.ide.applyFileSystemEdit( + FileEdit(filepath=rif.filepath, range=rif.range, replacement=completion)) + await sdk.ide.saveFile(rif.filepath) + await sdk.ide.setFileOpen(rif.filepath) + + +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" + + 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, models: Models) -> 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, models: Models) -> 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 + + +class MessageStep(Step): + name: str = "Message" + message: str + + async def describe(self, models: Models) -> Coroutine[str, None, None]: + return self.message + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + return TextObservation(text=self.message) + + +class EmptyStep(Step): + hide: bool = True + + async def describe(self, models: Models) -> Coroutine[str, None, None]: + return "" + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + pass diff --git a/continuedev/src/continuedev/steps/steps_on_startup.py b/continuedev/src/continuedev/steps/steps_on_startup.py new file mode 100644 index 00000000..cd40ff56 --- /dev/null +++ b/continuedev/src/continuedev/steps/steps_on_startup.py @@ -0,0 +1,28 @@ +from ..core.main import ContinueSDK, Models, Step +from .main import UserInputStep +from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe + + +step_name_to_step_class = { + "UserInputStep": UserInputStep, + "CreatePipelineRecipe": CreatePipelineRecipe +} + + +class StepsOnStartupStep(Step): + hide: bool = True + + async def describe(self, models: Models): + return "Running steps on startup" + + async def run(self, sdk: ContinueSDK): + steps_descriptions = (await sdk.get_config()).steps_on_startup + + for step_name, step_params in steps_descriptions.items(): + try: + step = step_name_to_step_class[step_name](**step_params) + except: + print( + f"Incorrect parameters for step {step_name}. Parameters provided were: {step_params}") + continue + await sdk.run_step(step) diff --git a/docs/docs/concepts/step.md b/docs/docs/concepts/step.md index 1758fdd8..1e1a0a65 100644 --- a/docs/docs/concepts/step.md +++ b/docs/docs/concepts/step.md @@ -60,10 +60,10 @@ Create and run an alembic migration - `edited_file`: -### WritePytestsStep +### WritePytestsRecipe Write unit tests for this file. #### Parameters -- for_filepath (required): the path of the file that unit tests should be created for \ No newline at end of file +- for_filepath (required): the path of the file that unit tests should be created for diff --git a/docs/docs/walkthroughs/create-a-recipe.md b/docs/docs/walkthroughs/create-a-recipe.md index 0cf1892e..12dd3167 100644 --- a/docs/docs/walkthroughs/create-a-recipe.md +++ b/docs/docs/walkthroughs/create-a-recipe.md @@ -67,7 +67,7 @@ class CreatePytestsStep(Step): code = await sdk.ide.readFile(self.input_file_path) sdk.run_step(CreateDirStep(output_dir_path)) - sdk.run_step(WritePytestsStep(code, output_file_prefix, output_dir_path)) + sdk.run_step(WritePytestsRecipe(code, output_file_prefix, output_dir_path)) ``` ### Adjust for different OS -- cgit v1.2.3-70-g09d2 From 157a815e9126a8eb6b3c05848d2f5159d2f89844 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 00:21:39 -0400 Subject: tutorial steps: comment, pytest --- continuedev/src/continuedev/core/policy.py | 5 ++++- .../src/continuedev/recipes/WritePytestsRecipe/main.py | 18 +++++++++++++----- continuedev/src/continuedev/steps/comment_code.py | 12 ++++++++++++ continuedev/src/continuedev/steps/main.py | 1 + 4 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 continuedev/src/continuedev/steps/comment_code.py (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index f7fcf21a..dc71cfa9 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -8,6 +8,7 @@ from ..steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeSte from ..recipes.WritePytestsRecipe.main import WritePytestsRecipe # from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..recipes.ContinueRecipeRecipe.main import ContinueStepStep +from ..steps.comment_code import CommentCodeStep class DemoPolicy(Policy): @@ -21,10 +22,12 @@ class DemoPolicy(Policy): observation = history.get_current().observation if observation is not None and isinstance(observation, UserInputObservation): # This could be defined with ObservationTypePolicy. Ergonomics not right though. - if " test" in observation.user_input.lower(): + if "/pytest" in observation.user_input.lower(): return WritePytestsRecipe(instructions=observation.user_input) elif "/dlt" in observation.user_input.lower() or " dlt" in observation.user_input.lower(): return CreatePipelineRecipe() + elif "/comment" in observation.user_input.lower(): + return CommentCodeStep() # elif "/ask" in observation.user_input: # return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) # elif "/edit" in observation.user_input: diff --git a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py index a35055f3..82876a08 100644 --- a/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py +++ b/continuedev/src/continuedev/recipes/WritePytestsRecipe/main.py @@ -1,14 +1,20 @@ from textwrap import dedent +from typing import Union from ...models.filesystem_edit import AddDirectory, AddFile from ...core.main import Step, ContinueSDK import os class WritePytestsRecipe(Step): - for_filepath: str + for_filepath: Union[str, None] = None + instructions: str = "Write unit tests for this file." async def run(self, sdk: ContinueSDK): - filename, dirname = os.path.split(self.for_filepath) + 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): @@ -16,7 +22,7 @@ class WritePytestsRecipe(Step): path = os.path.join(path_dir, f"test_{filename}") if os.path.exists(path): - return + return None for_file_contents = await sdk.ide.readFile(self.for_filepath) @@ -31,7 +37,9 @@ class WritePytestsRecipe(Step): "{self.instructions}" - Here is a complete set of pytest unit tests: - """) + Here is a complete set of pytest unit tests:""") tests = (await sdk.models.gpt35()).complete(prompt) + await sdk.apply_filesystem_edit(AddFile(filepath=path, content=tests)) + + return None diff --git a/continuedev/src/continuedev/steps/comment_code.py b/continuedev/src/continuedev/steps/comment_code.py new file mode 100644 index 00000000..c538347b --- /dev/null +++ b/continuedev/src/continuedev/steps/comment_code.py @@ -0,0 +1,12 @@ +from ..core.main import ContinueSDK, Models, Step +from .main import StarCoderEditHighlightedCodeStep + + +class CommentCodeStep(Step): + hide: bool = True + + async def describe(self, models: Models): + return "Writing comments" + + async def run(self, sdk: ContinueSDK): + await sdk.run_step(StarCoderEditHighlightedCodeStep(user_input="Wrote comprehensive comments in the canonical format for every class and function")) diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index 8588ec92..a0872564 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -215,6 +215,7 @@ class FasterEditHighlightedCodeStep(Step): class StarCoderEditHighlightedCodeStep(Step): user_input: str + name: str = "Editing code" hide = False _prompt: str = "{code}{user_request}" -- cgit v1.2.3-70-g09d2 From fe22fcefa370e8a609123490ceeb7e646d949af3 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 10:26:07 -0400 Subject: README work --- continuedev/src/continuedev/recipes/README.md | 8 +++++++ .../src/continuedev/steps/draft/migration.py | 11 +++++++--- continuedev/src/continuedev/steps/main.py | 25 ---------------------- docs/docs/concepts/step.md | 10 +++++++-- extension/README.md | 21 +++++++++++++++++- 5 files changed, 44 insertions(+), 31 deletions(-) (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/recipes/README.md b/continuedev/src/continuedev/recipes/README.md index 5e01cc97..d5a006fb 100644 --- a/continuedev/src/continuedev/recipes/README.md +++ b/continuedev/src/continuedev/recipes/README.md @@ -7,3 +7,11 @@ The `recipes` folder contains all recipes, each with the same structure. **If yo 1. Create a new subfolder in `recipes`, with the name of your recipe (for example `MyNewRecipe`). 2. Make 2 files in this folder: 1) a `README.md` describing your recipe and how to use it and 2) a `main.py` including a single class with the name of your recipe (e.g. `MyNewRecipe`). 3. Write any utility code other than the main recipe class in a separate file, which you can import in `main.py`. Particularly if you decide to break the recipe into multiple sub-steps, try to keep these separate. + +# Existing Recipes + +`ContinueRecipeRecipe` - Write a Continue recipe with Continue. + +`CreatePipelineRecipe` - Build a dlt pipeline from scratch for an API of your choice. + +`WritePytestsRecipe` - Write Pytest unit tests in a folder adjacent to your Python file. diff --git a/continuedev/src/continuedev/steps/draft/migration.py b/continuedev/src/continuedev/steps/draft/migration.py index ea538bb7..b386bed8 100644 --- a/continuedev/src/continuedev/steps/draft/migration.py +++ b/continuedev/src/continuedev/steps/draft/migration.py @@ -1,7 +1,6 @@ # When an edit is made to an existing class or a new sqlalchemy class is created, # this should be kicked off. -from ..main import EditCodeStep, RunCommandStep from ...core.main import Step @@ -15,11 +14,17 @@ class MigrationStep(Step): recent_edits_string = "\n\n".join( map(lambda x: x.to_string(), recent_edits)) description = await (await sdk.models.gpt35()).complete(f"{recent_edits_string}\n\nGenerate a short description of the migration made in the above changes:\n") - await sdk.run_step(RunCommandStep(cmd=f"cd libs && poetry run alembic revision --autogenerate -m {description}")) + await sdk.run([ + "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")) + await sdk.run([ + "cd libs", + "poetry run alembic upgrade head", + ]) diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index a0872564..bb720b20 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -54,31 +54,6 @@ class RunPolicyUntilDoneStep(Step): return observation -class RunCommandStep(Step): - cmd: str - name: str = "Run command" - _description: str = None - - async def describe(self, models: Models) -> 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 FasterEditHighlightedCodeStep(Step): user_input: str hide = True diff --git a/docs/docs/concepts/step.md b/docs/docs/concepts/step.md index 1e1a0a65..0358a52f 100644 --- a/docs/docs/concepts/step.md +++ b/docs/docs/concepts/step.md @@ -40,12 +40,14 @@ the code that should run when the step is rerun with feedback ### Core -#### RunCommandStep - #### EditCodeStep +Provide a prompt and a list of file ranges to be edited by a language model. + ### ManualEditStep +A core step that tracks all user edits in the IDE so that they can be reversed along with other steps + ## Community ### CreateTableStep @@ -67,3 +69,7 @@ Write unit tests for this file. #### Parameters - for_filepath (required): the path of the file that unit tests should be created for + +### AnswerQuestionChromaStep + +Type `/ask` and ask any question about your whole codebase. The Chroma embeddings store will help find important snippets and answer your question. diff --git a/extension/README.md b/extension/README.md index 9ee59ad3..73f2df3e 100644 --- a/extension/README.md +++ b/extension/README.md @@ -2,4 +2,23 @@ **[Continue](https://continue.dev/docs) is the open-source library for accelerating software development with language models** -You can find the Continue GitHub repo [here](https://github.com/continuedev/continue) \ No newline at end of file +The Continue VS Code extension lets you make edits with natural langauge, ask questions of your codebase, automatically generate unit tests, and more. Beyond the built-in functionality, you can easily write your own recipes to automate the most repetitive sequences of tasks in your workflow. + +## Getting Started + +Get started by opening the command pallet with cmd+shift+p and then selecting Continue: Open Debug Panel. + +To test a few common recipes, open a blank python file and try the following: + +- Ask it to "write me a calculator class in python" +- /comment to write comments for the class +- /pytest to write Pytest unit tests in a separate file +- Ask in natural language for a new method + +See [here](https://continue.dev/docs/concepts/recipe) for the full list of recipes currently available to use. + +You can find the Continue GitHub repo [here](https://github.com/continuedev/continue) + +## Feedback + +Have thoughts about Continue? Please [leave an issue](https://github.com/continuedev/continue/issues/new) or email us at hi@continue.dev :) -- cgit v1.2.3-70-g09d2 From 22f6e4a01aed7955f608fcaa2198dc7da7902f3e Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 11:33:05 -0400 Subject: API tokens set through global Vsc settings --- continuedev/src/continuedev/core/sdk.py | 20 +------------ continuedev/src/continuedev/server/ide.py | 35 ++++++++++------------ continuedev/src/continuedev/server/ide_protocol.py | 4 +++ continuedev/src/continuedev/steps/core/core.py | 1 + continuedev/src/continuedev/steps/main.py | 2 +- extension/package.json | 10 +++++++ .../react-app/src/components/StepContainer.tsx | 10 +++---- extension/src/continueIdeClient.ts | 27 +++++++++++++++++ 8 files changed, 64 insertions(+), 45 deletions(-) (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 5d0f03fe..5ae471c4 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -11,7 +11,6 @@ from .observation import Observation from ..server.ide_protocol import AbstractIdeProtocolServer from .main import History, Step from ..steps.core.core import * -from .env import get_env_var, make_sure_env_exists class Autopilot: @@ -105,24 +104,7 @@ class ContinueSDK: return await self.run_step(FileSystemEditStep(edit=DeleteDirectory(path=path))) async def get_user_secret(self, env_var: str, prompt: str) -> str: - make_sure_env_exists() - - val = None - while val is None: - try: - val = get_env_var(env_var) - if val is not None: - return val - except: - pass - server_dir = os.getcwd() - env_path = os.path.join(server_dir, ".env") - await self.ide.setFileOpen(env_path) - await self.append_to_file(env_path, f'\n{env_var}=""') - await self.run_step(WaitForUserConfirmationStep(prompt=prompt)) - val = get_env_var(env_var) - - return val + return await self.ide.getUserSecret(env_var) async def get_config(self) -> ContinueConfig: dir = await self.ide.getWorkspaceDirectory() diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py index 71017ce0..eec5b607 100644 --- a/continuedev/src/continuedev/server/ide.py +++ b/continuedev/src/continuedev/server/ide.py @@ -40,46 +40,42 @@ Server.handle_exit = AppStatus.handle_exit class FileEditsUpdate(BaseModel): - messageType: str = "fileEdits" fileEdits: List[FileEditWithFullContents] class OpenFilesResponse(BaseModel): - messageType: str = "openFiles" openFiles: List[str] class HighlightedCodeResponse(BaseModel): - messageType: str = "highlightedCode" highlightedCode: List[RangeInFile] class ShowSuggestionRequest(BaseModel): - messageType: str = "showSuggestion" suggestion: FileEdit class ShowSuggestionResponse(BaseModel): - messageType: str = "showSuggestion" suggestion: FileEdit accepted: bool class ReadFileResponse(BaseModel): - messageType: str = "readFile" contents: str class EditFileResponse(BaseModel): - messageType: str = "editFile" fileEdit: FileEditWithFullContents class WorkspaceDirectoryResponse(BaseModel): - messageType: str = "workspaceDirectory" workspaceDirectory: str +class GetUserSecretResponse(BaseModel): + value: str + + T = TypeVar("T", bound=BaseModel) @@ -114,7 +110,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"]: + elif message_type in ["highlightedCode", "openFiles", "readFile", "editFile", "workspaceDirectory", "getUserSecret"]: self.sub_queue.post(message_type, data) else: raise ValueError("Unknown message type", message_type) @@ -183,31 +179,31 @@ class IdeProtocolServer(AbstractIdeProtocolServer): # Request information. Session doesn't matter. async def getOpenFiles(self) -> List[str]: - resp = await self._send_and_receive_json({ - "messageType": "openFiles" - }, OpenFilesResponse, "openFiles") + resp = await self._send_and_receive_json({}, OpenFilesResponse, "openFiles") return resp.openFiles async def getWorkspaceDirectory(self) -> str: - resp = await self._send_and_receive_json({ - "messageType": "workspaceDirectory" - }, WorkspaceDirectoryResponse, "workspaceDirectory") + resp = await self._send_and_receive_json({}, WorkspaceDirectoryResponse, "workspaceDirectory") return resp.workspaceDirectory async def getHighlightedCode(self) -> List[RangeInFile]: - resp = await self._send_and_receive_json({ - "messageType": "highlightedCode" - }, HighlightedCodeResponse, "highlightedCode") + resp = await self._send_and_receive_json({}, HighlightedCodeResponse, "highlightedCode") return resp.highlightedCode async def readFile(self, filepath: str) -> str: """Read a file""" resp = await self._send_and_receive_json({ - "messageType": "readFile", "filepath": filepath }, ReadFileResponse, "readFile") return resp.contents + async def getUserSecret(self, key: str) -> str: + """Get a user secret""" + resp = await self._send_and_receive_json({ + "key": key + }, GetUserSecretResponse, "getUserSecret") + return resp.value + async def saveFile(self, filepath: str): """Save a file""" await self._send_json("saveFile", { @@ -222,7 +218,6 @@ class IdeProtocolServer(AbstractIdeProtocolServer): async def editFile(self, edit: FileEdit) -> FileEditWithFullContents: """Edit a file""" resp = await self._send_and_receive_json({ - "messageType": "editFile", "edit": edit.dict() }, EditFileResponse, "editFile") return resp.fileEdit diff --git a/continuedev/src/continuedev/server/ide_protocol.py b/continuedev/src/continuedev/server/ide_protocol.py index 4f505e80..8f155415 100644 --- a/continuedev/src/continuedev/server/ide_protocol.py +++ b/continuedev/src/continuedev/server/ide_protocol.py @@ -78,3 +78,7 @@ class AbstractIdeProtocolServer(ABC): @abstractmethod async def saveFile(self, filepath: str): """Save a file""" + + @abstractmethod + async def getUserSecret(self, key: str): + """Get a user secret""" diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index e54a9a21..0f513f3e 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -205,4 +205,5 @@ class WaitForUserConfirmationStep(Step): async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: 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 bb720b20..dfb4f3be 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -190,7 +190,7 @@ class FasterEditHighlightedCodeStep(Step): class StarCoderEditHighlightedCodeStep(Step): user_input: str - name: str = "Editing code" + name: str = "Editing Code" hide = False _prompt: str = "{code}{user_request}" diff --git a/extension/package.json b/extension/package.json index 13086954..cc8e18c4 100644 --- a/extension/package.json +++ b/extension/package.json @@ -39,6 +39,16 @@ "type": "string", "default": "http://localhost:8000", "description": "The URL of the Continue server to use." + }, + "continue.OPENAI_API_KEY": { + "type": "string", + "default": "", + "description": "The OpenAI API key to use for code generation." + }, + "continue.HUGGING_FACE_TOKEN": { + "type": "string", + "default": "", + "description": "The Hugging Face API token to use for code generation." } } }, diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index 5e979b34..fd29f21b 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -114,12 +114,12 @@ function StepContainer(props: StepContainerProps) { hidden={props.historyNode.step.hide as any} > setOpen((prev) => !prev)} + className="m-2 overflow-hidden" + // onClick={() => setOpen((prev) => !prev)} > -

+

{open ? ( ) : ( @@ -127,14 +127,14 @@ function StepContainer(props: StepContainerProps) { )} {props.historyNode.step.name as any}:

- { e.stopPropagation(); props.onReverse(); }} > - + */}
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts index 03e5fbc5..a5a1c5dc 100644 --- a/extension/src/continueIdeClient.ts +++ b/extension/src/continueIdeClient.ts @@ -74,6 +74,12 @@ class IdeProtocolClient { this.messenger?.send("workspaceDirectory", { workspaceDirectory: this.getWorkspaceDirectory(), }); + break; + case "getUserSecret": + this.messenger?.send("getUserSecret", { + value: await this.getUserSecret(data.key), + }); + break; case "openFiles": this.messenger?.send("openFiles", { openFiles: this.getOpenFiles(), @@ -130,6 +136,27 @@ class IdeProtocolClient { openEditorAndRevealRange(filepath, undefined, vscode.ViewColumn.One); } + async getUserSecret(key: string) { + // Check if secret already exists in VS Code settings (global) + let secret = vscode.workspace.getConfiguration("continue").get(key); + if (secret && secret !== "") return secret; + + // If not, ask user for secret + while (typeof secret === "undefined" || secret === "") { + secret = await vscode.window.showInputBox({ + prompt: `Enter secret for ${key}`, + password: true, + }); + } + + // Add secret to VS Code settings + vscode.workspace + .getConfiguration("continue") + .update(key, secret, vscode.ConfigurationTarget.Global); + + return secret; + } + // ------------------------------------ // // Initiate Request -- cgit v1.2.3-70-g09d2 From 4f3ceee573268fbe9db80fea372198523b5757a6 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 11:56:09 -0400 Subject: show step details on toggle --- continuedev/src/continuedev/steps/core/core.py | 6 ++++++ extension/package-lock.json | 4 ++-- extension/package.json | 2 +- .../react-app/src/components/StepContainer.tsx | 16 ++++++++++++---- .../scripts/continuedev-0.1.1-py3-none-any.whl | Bin 56962 -> 56858 bytes 5 files changed, 21 insertions(+), 7 deletions(-) (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index 0f513f3e..d7f7a307 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -43,7 +43,12 @@ class ShellCommandsStep(Step): cwd: str | None = None name: str = "Run Shell Commands" + _err_text: str | None = None + async def describe(self, models: Models) -> Coroutine[str, None, None]: + if self._err_text is not None: + return f"Error when running shell commands:\n```\n{self._err_text}\n```" + cmds_str = "\n".join(self.cmds) return (await models.gpt35()).complete(f"{cmds_str}\n\nSummarize what was done in these shell commands, using markdown bullet points:") @@ -58,6 +63,7 @@ class ShellCommandsStep(Step): # If it fails, return the error if err is not None and err != "": + self._err_text = err return TextObservation(text=err) return None diff --git a/extension/package-lock.json b/extension/package-lock.json index 2db9cac5..32f47246 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.16", + "version": "0.0.17", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.16", + "version": "0.0.17", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index cc8e18c4..be10be80 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.16", + "version": "0.0.17", "publisher": "Continue", "engines": { "vscode": "^1.74.0" diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index fd29f21b..f962cbc9 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -114,8 +114,8 @@ function StepContainer(props: StepContainerProps) { hidden={props.historyNode.step.hide as any} > setOpen((prev) => !prev)} + className="m-2 overflow-hidden cursor-pointer" + onClick={() => setOpen((prev) => !prev)} > @@ -137,6 +137,14 @@ function StepContainer(props: StepContainerProps) { */} + {open && ( +
+              Step Details:
+              
+ {JSON.stringify(props.historyNode.step, null, 2)} +
+ )} + {props.historyNode.step.description as any} @@ -181,7 +189,7 @@ function StepContainer(props: StepContainerProps) {
- */} ); } diff --git a/extension/scripts/continuedev-0.1.1-py3-none-any.whl b/extension/scripts/continuedev-0.1.1-py3-none-any.whl index 663eb14a..d143bcf9 100644 Binary files a/extension/scripts/continuedev-0.1.1-py3-none-any.whl and b/extension/scripts/continuedev-0.1.1-py3-none-any.whl differ -- cgit v1.2.3-70-g09d2 From b8067876bc5dd425d491863bd5338f325fea35ed Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 14:20:21 -0400 Subject: error handling and step retry --- continuedev/src/continuedev/core/agent.py | 180 --------------------- continuedev/src/continuedev/core/autopilot.py | 52 +++--- continuedev/src/continuedev/core/main.py | 9 +- continuedev/src/continuedev/core/observation.py | 4 + .../src/continuedev/libs/llm/hf_inference_api.py | 9 +- continuedev/src/continuedev/server/gui.py | 6 + continuedev/src/continuedev/server/gui_protocol.py | 4 + .../react-app/src/components/StepContainer.tsx | 43 ++++- .../react-app/src/hooks/useContinueGUIProtocol.ts | 4 + extension/react-app/src/tabs/gui.tsx | 4 + 10 files changed, 106 insertions(+), 209 deletions(-) delete mode 100644 continuedev/src/continuedev/core/agent.py (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py deleted file mode 100644 index 1996abb1..00000000 --- a/continuedev/src/continuedev/core/agent.py +++ /dev/null @@ -1,180 +0,0 @@ -import traceback -import time -from typing import Callable, Coroutine, List -from ..models.filesystem_edit import FileEditWithFullContents -from ..libs.llm import LLM -from .observation import Observation -from ..server.ide_protocol import AbstractIdeProtocolServer -from ..libs.util.queue import AsyncSubscriptionQueue -from ..models.main import ContinueBaseModel -from .main import Policy, History, FullState, Step, HistoryNode -from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep -from .sdk import ContinueSDK - - -class Autopilot(ContinueBaseModel): - llm: LLM - policy: Policy - ide: AbstractIdeProtocolServer - history: History = History.from_empty() - _on_update_callbacks: List[Callable[[FullState], None]] = [] - - _active: bool = False - _should_halt: bool = False - _main_user_input_queue: List[str] = [] - - _user_input_queue = AsyncSubscriptionQueue() - - class Config: - arbitrary_types_allowed = True - - def get_full_state(self) -> FullState: - return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue) - - def on_update(self, callback: Callable[["FullState"], None]): - """Subscribe to changes to state""" - self._on_update_callbacks.append(callback) - - def update_subscribers(self): - full_state = self.get_full_state() - for callback in self._on_update_callbacks: - callback(full_state) - - def __get_step_params(self, step: "Step"): - return ContinueSDK(autopilot=self, llm=self.llm.with_system_message(step.system_message)) - - def give_user_input(self, input: str, index: int): - self._user_input_queue.post(index, input) - - async def wait_for_user_input(self) -> str: - self._active = False - self.update_subscribers() - user_input = await self._user_input_queue.get(self.history.current_index) - self._active = True - self.update_subscribers() - return user_input - - _manual_edits_buffer: List[FileEditWithFullContents] = [] - - async def reverse_to_index(self, index: int): - try: - while self.history.get_current_index() >= index: - current_step = self.history.get_current().step - self.history.step_back() - if issubclass(current_step.__class__, ReversibleStep): - await current_step.reverse(self.__get_step_params(current_step)) - - self.update_subscribers() - except Exception as e: - print(e) - - def handle_manual_edits(self, edits: List[FileEditWithFullContents]): - for edit in edits: - self._manual_edits_buffer.append(edit) - # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge. - # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit) - - def handle_traceback(self, traceback: str): - raise NotImplementedError - - _step_depth: int = 0 - - async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: - if not is_future_step: - # Check manual edits buffer, clear out if needed by creating a ManualEditStep - if len(self._manual_edits_buffer) > 0: - manualEditsStep = ManualEditStep.from_sequence( - self._manual_edits_buffer) - self._manual_edits_buffer = [] - await self._run_singular_step(manualEditsStep) - - # Update history - do this first so we get top-first tree ordering - self.history.add_node(HistoryNode( - step=step, observation=None, depth=self._step_depth)) - - # Run step - self._step_depth += 1 - observation = await step(self.__get_step_params(step)) - self._step_depth -= 1 - - # Add observation to history - self.history.get_current().observation = observation - - # Update its description - step._set_description(await step.describe(self.llm)) - - # Call all subscribed callbacks - self.update_subscribers() - - return observation - - async def run_from_step(self, step: "Step"): - # if self._active: - # raise RuntimeError("Autopilot is already running") - self._active = True - - next_step = step - is_future_step = False - while not (next_step is None or self._should_halt): - try: - if is_future_step: - # If future step, then we are replaying and need to delete the step from history so it can be replaced - self.history.remove_current_and_substeps() - - observation = await self._run_singular_step(next_step, is_future_step) - if next_step := self.policy.next(self.history): - is_future_step = False - elif next_step := self.history.take_next_step(): - is_future_step = True - else: - next_step = None - - except Exception as e: - print( - f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") - next_step = None - - self._active = False - - # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools - for callback in self._on_update_callbacks: - callback(None) - - async def run_from_observation(self, observation: Observation): - next_step = self.policy.next(self.history) - await self.run_from_step(next_step) - - async def run_policy(self): - first_step = self.policy.next(self.history) - await self.run_from_step(first_step) - - async def _request_halt(self): - if self._active: - self._should_halt = True - while self._active: - time.sleep(0.1) - self._should_halt = False - return None - - async def accept_user_input(self, user_input: str): - self._main_user_input_queue.append(user_input) - self.update_subscribers() - - if len(self._main_user_input_queue) > 1: - return - - # await self._request_halt() - # Just run the step that takes user input, and - # then up to the policy to decide how to deal with it. - self._main_user_input_queue.pop(0) - self.update_subscribers() - await self.run_from_step(UserInputStep(user_input=user_input)) - - while len(self._main_user_input_queue) > 0: - await self.run_from_step(UserInputStep( - user_input=self._main_user_input_queue.pop(0))) - - async def accept_refinement_input(self, user_input: str, index: int): - await self._request_halt() - await self.reverse_to_index(index) - await self.run_from_step(UserInputStep(user_input=user_input)) diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py index 85f65dc3..db06c975 100644 --- a/continuedev/src/continuedev/core/autopilot.py +++ b/continuedev/src/continuedev/core/autopilot.py @@ -3,7 +3,7 @@ import time from typing import Callable, Coroutine, List from ..models.filesystem_edit import FileEditWithFullContents from ..libs.llm import LLM -from .observation import Observation +from .observation import Observation, InternalErrorObservation from ..server.ide_protocol import AbstractIdeProtocolServer from ..libs.util.queue import AsyncSubscriptionQueue from ..models.main import ContinueBaseModel @@ -77,6 +77,11 @@ class Autopilot(ContinueBaseModel): _step_depth: int = 0 + async def retry_at_index(self, index: int): + last_step = self.history.pop_last_step() + await self.update_subscribers() + await self._run_singular_step(last_step) + async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]: capture_event( 'step run', {'step_name': step.name, 'params': step.dict()}) @@ -96,14 +101,28 @@ class Autopilot(ContinueBaseModel): # Call all subscribed callbacks await self.update_subscribers() - # Run step + # Try to run step and handle errors self._step_depth += 1 - observation = await step(ContinueSDK(self)) + + try: + observation = await step(ContinueSDK(self)) + except Exception as e: + # Attach an InternalErrorObservation to the step and unhide it. + error_string = '\n\n'.join( + traceback.format_tb(e.__traceback__)) + f"\n\n{e.__repr__()}" + print( + f"Error while running step: \n{error_string}\n{e}") + + observation = InternalErrorObservation( + error=error_string) + step.hide = False + self._step_depth -= 1 # Add observation to history self.history.get_last_at_depth( self._step_depth, include_current=True).observation = observation + await self.update_subscribers() # Update its description async def update_description(): @@ -122,22 +141,17 @@ class Autopilot(ContinueBaseModel): next_step = step is_future_step = False while not (next_step is None or self._should_halt): - try: - if is_future_step: - # If future step, then we are replaying and need to delete the step from history so it can be replaced - self.history.remove_current_and_substeps() - - observation = await self._run_singular_step(next_step, is_future_step) - if next_step := self.policy.next(self.history): - is_future_step = False - elif next_step := self.history.take_next_step(): - is_future_step = True - else: - next_step = None - - except Exception as e: - print( - f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}") + if is_future_step: + # If future step, then we are replaying and need to delete the step from history so it can be replaced + self.history.remove_current_and_substeps() + + await self._run_singular_step(next_step, is_future_step) + + if next_step := self.policy.next(self.history): + is_future_step = False + elif next_step := self.history.take_next_step(): + is_future_step = True + else: next_step = None self._active = False diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py index a2336671..b2b97bae 100644 --- a/continuedev/src/continuedev/core/main.py +++ b/continuedev/src/continuedev/core/main.py @@ -67,6 +67,13 @@ class History(ContinueBaseModel): return None return state.observation + def pop_last_step(self) -> Union[HistoryNode, None]: + if self.current_index < 0: + return None + node = self.timeline.pop(self.current_index) + self.current_index -= 1 + return node.step + @classmethod def from_empty(cls): return cls(timeline=[], current_index=-1) @@ -118,7 +125,7 @@ class Step(ContinueBaseModel): if self._description is not None: d["description"] = self._description else: - d["description"] = self.name + d["description"] = "`Description loading...`" return d @validator("name", pre=True, always=True) diff --git a/continuedev/src/continuedev/core/observation.py b/continuedev/src/continuedev/core/observation.py index fef04311..b6117236 100644 --- a/continuedev/src/continuedev/core/observation.py +++ b/continuedev/src/continuedev/core/observation.py @@ -33,3 +33,7 @@ class TextObservation(Observation): if v is None: return "" return v + + +class InternalErrorObservation(Observation): + error: str diff --git a/continuedev/src/continuedev/libs/llm/hf_inference_api.py b/continuedev/src/continuedev/libs/llm/hf_inference_api.py index 83852d27..734da160 100644 --- a/continuedev/src/continuedev/libs/llm/hf_inference_api.py +++ b/continuedev/src/continuedev/libs/llm/hf_inference_api.py @@ -22,4 +22,11 @@ class HuggingFaceInferenceAPI(LLM): "return_full_text": False, } }) - return response.json()[0]["generated_text"] + data = response.json() + + # Error if the response is not a list + if not isinstance(data, list): + raise Exception( + "Hugging Face returned an error response: \n\n", data) + + return data[0]["generated_text"] diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py index 3d1a5a82..b873a88f 100644 --- a/continuedev/src/continuedev/server/gui.py +++ b/continuedev/src/continuedev/server/gui.py @@ -75,6 +75,8 @@ class GUIProtocolServer(AbstractGUIProtocolServer): self.on_refinement_input(data["input"], data["index"]) elif message_type == "reverse_to_index": self.on_reverse_to_index(data["index"]) + elif message_type == "retry_at_index": + self.on_retry_at_index(data["index"]) except Exception as e: print(e) @@ -100,6 +102,10 @@ class GUIProtocolServer(AbstractGUIProtocolServer): asyncio.create_task( self.session.autopilot.accept_refinement_input(input, index)) + def on_retry_at_index(self, index: int): + asyncio.create_task( + self.session.autopilot.retry_at_index(index)) + @router.websocket("/ws") async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)): diff --git a/continuedev/src/continuedev/server/gui_protocol.py b/continuedev/src/continuedev/server/gui_protocol.py index e32d80ef..287f9e3b 100644 --- a/continuedev/src/continuedev/server/gui_protocol.py +++ b/continuedev/src/continuedev/server/gui_protocol.py @@ -26,3 +26,7 @@ class AbstractGUIProtocolServer(ABC): @abstractmethod async def send_state_update(self, state: dict): """Send a state update to the client""" + + @abstractmethod + def on_retry_at_index(self, index: int): + """Called when the user requests a retry at a previous index""" diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx index f962cbc9..903f9b94 100644 --- a/extension/react-app/src/components/StepContainer.tsx +++ b/extension/react-app/src/components/StepContainer.tsx @@ -18,6 +18,7 @@ import { ChevronDown, ChevronRight, Backward, + ArrowPath, } from "@styled-icons/heroicons-outline"; import { HistoryNode } from "../../../schema/HistoryNode"; import ReactMarkdown from "react-markdown"; @@ -29,6 +30,7 @@ interface StepContainerProps { inFuture: boolean; onRefinement: (input: string) => void; onUserInput: (input: string) => void; + onRetry: () => void; } const MainDiv = styled.div<{ stepDepth: number; inFuture: boolean }>` @@ -135,19 +137,44 @@ function StepContainer(props: StepContainerProps) { > */} + + {props.historyNode.observation?.error ? ( + { + e.stopPropagation(); + props.onRetry(); + }} + > + + + ) : ( + <> + )} {open && ( -
-              Step Details:
-              
- {JSON.stringify(props.historyNode.step, null, 2)} -
+ <> +
+                Step Details:
+                
+ {JSON.stringify(props.historyNode.step, null, 2)} +
+ )} - - {props.historyNode.step.description as any} - + {props.historyNode.observation?.error ? ( + <> + Error while running step: +
+
+                {props.historyNode.observation.error as string}
+              
+ + ) : ( + + {props.historyNode.step.description as any} + + )} {props.historyNode.step.name === "Waiting for user input" && ( { client?.reverseToIndex(index); }} + onRetry={() => { + client?.retryAtIndex(index); + setWaitingForSteps(true); + }} /> ); })} -- cgit v1.2.3-70-g09d2 From 13accc79d2a0c082304c53c1b981e33b62ed8a1c Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 15:11:36 -0400 Subject: dlt pipeline recipe fix --- continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py | 6 ------ continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py | 8 ++++++++ 2 files changed, 8 insertions(+), 6 deletions(-) (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py index c0699cae..4a4604d6 100644 --- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py @@ -23,11 +23,5 @@ class CreatePipelineRecipe(Step): - Write a query to view the data""")) >> WaitForUserInputStep(prompt="What API do you want to load data from?") >> SetupPipelineStep(api_description="WeatherAPI.com API") >> - MessageStep(message=dedent("""\ - This step will validate that your dlt pipeline is working as expected: - - Test that the API call works - - Load the data into a local DuckDB instance - - Write a query to view the data - """)) >> ValidatePipelineStep() ) diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py index b480223a..ef5e3b43 100644 --- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py @@ -1,5 +1,6 @@ from textwrap import dedent +from ...steps.main import MessageStep from ...core.sdk import Models from ...core.observation import DictObservation from ...models.filesystem_edit import AddFile @@ -53,6 +54,13 @@ class ValidatePipelineStep(Step): source_name = sdk.history.last_observation().values["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 + """))) + # test that the API call works await sdk.run(f'python3 {filename}') -- cgit v1.2.3-70-g09d2 From c5da22b981566687d2c02262d2e4c6b3abc081b8 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 17:55:52 -0400 Subject: working on chroma steps --- continuedev/src/continuedev/core/policy.py | 12 +- continuedev/src/continuedev/libs/chroma/query.py | 212 +++++++++++++++------ continuedev/src/continuedev/libs/chroma/replace.py | 19 -- continuedev/src/continuedev/libs/chroma/update.py | 161 +--------------- continuedev/src/continuedev/steps/chroma.py | 23 ++- 5 files changed, 185 insertions(+), 242 deletions(-) delete mode 100644 continuedev/src/continuedev/libs/chroma/replace.py (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index dc71cfa9..6f357ba0 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -1,12 +1,12 @@ from typing import List, Tuple, Type +from ..steps.chroma import AnswerQuestionChroma, EditFileChroma, CreateCodebaseIndexChroma from ..steps.steps_on_startup import StepsOnStartupStep from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation from ..steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep, MessageStep, EmptyStep from ..recipes.WritePytestsRecipe.main import WritePytestsRecipe -# from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..recipes.ContinueRecipeRecipe.main import ContinueStepStep from ..steps.comment_code import CommentCodeStep @@ -17,7 +17,7 @@ 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!") >> StepsOnStartupStep() + return CreateCodebaseIndexChroma() >> MessageStep(message="Welcome to Continue!") >> StepsOnStartupStep() observation = history.get_current().observation if observation is not None and isinstance(observation, UserInputObservation): @@ -28,10 +28,10 @@ class DemoPolicy(Policy): return CreatePipelineRecipe() elif "/comment" in observation.user_input.lower(): return CommentCodeStep() - # elif "/ask" in observation.user_input: - # return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) - # elif "/edit" in observation.user_input: - # return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) + elif "/ask" in observation.user_input: + return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:])) + elif "/edit" in observation.user_input: + return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:])) elif "/step" in observation.user_input: return ContinueStepStep(prompt=" ".join(observation.user_input.split(" ")[1:])) return StarCoderEditHighlightedCodeStep(user_input=observation.user_input) diff --git a/continuedev/src/continuedev/libs/chroma/query.py b/continuedev/src/continuedev/libs/chroma/query.py index 5a1c89b3..1cb178cc 100644 --- a/continuedev/src/continuedev/libs/chroma/query.py +++ b/continuedev/src/continuedev/libs/chroma/query.py @@ -1,78 +1,178 @@ +import json import subprocess -import sys -from llama_index import GPTVectorStoreIndex, StorageContext, load_index_from_storage +from typing import List, Tuple +from llama_index import GPTVectorStoreIndex, StorageContext, load_index_from_storage, Document +from llama_index.langchain_helpers.text_splitter import TokenTextSplitter import os -from typer import Typer -from enum import Enum -from .update import update_codebase_index, create_codebase_index, index_dir_for, get_current_branch -from .replace import replace_additional_index +from .update import filter_ignored_files, load_gpt_index_documents +from functools import cached_property + + +class ChromaIndexManager: + workspace_dir: str -app = Typer() + def __init__(self, workspace_dir: str): + self.workspace_dir = workspace_dir + @cached_property + def current_commit(self) -> str: + """Get the current commit.""" + return subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=self.workspace_dir).decode("utf-8").strip() -def query_codebase_index(query: str) -> str: - """Query the codebase index.""" - branch = subprocess.check_output( - ["git", "rev-parse", "--abbrev-ref", "HEAD"]).decode("utf-8").strip() - path = index_dir_for(branch) - if not os.path.exists(path): - print("No index found for the codebase at ", path) - return "" + @cached_property + def current_branch(self) -> str: + """Get the current branch.""" + return subprocess.check_output( + ["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=self.workspace_dir).decode("utf-8").strip() - storage_context = StorageContext.from_defaults( - persist_dir=index_dir_for(branch)) - index = load_index_from_storage(storage_context) - # index = GPTVectorStoreIndex.load_from_disk(path) - engine = index.as_query_engine() - return engine.query(query) + @cached_property + def index_dir(self) -> str: + return os.path.join(self.workspace_dir, ".continue", "chroma", self.current_branch) + @cached_property + def git_root_dir(self): + """Get the root directory of a Git repository.""" + try: + return subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], cwd=self.workspace_dir).strip().decode() + except subprocess.CalledProcessError: + return None -def query_additional_index(query: str) -> str: - """Query the additional index.""" - index = GPTVectorStoreIndex.load_from_disk('data/additional_index.json') - return index.query(query) + def check_index_exists(self): + return os.path.exists(self.index_dir) + def create_codebase_index(self): + """Create a new index for the current branch.""" + if not self.check_index_exists(): + os.makedirs(self.index_dir) -class IndexTypeOption(str, Enum): - codebase = "codebase" - additional = "additional" + print("ROOT DIRECTORY: ", self.git_root_dir) + documents = load_gpt_index_documents(self.git_root_dir) + chunks = {} + doc_chunks = [] + for doc in documents: + text_splitter = TokenTextSplitter() + text_chunks = text_splitter.split_text(doc.text) + filename = doc.extra_info["filename"] + chunks[filename] = len(text_chunks) + for i, text in enumerate(text_chunks): + doc_chunks.append(Document(text, doc_id=f"{filename}::{i}")) -@app.command() -def query(context: IndexTypeOption, query: str): - if context == IndexTypeOption.additional: - response = query_additional_index(query) - elif context == IndexTypeOption.codebase: - response = query_codebase_index(query) - else: - print("Error: unknown context") - print({"response": response}) + with open(f"{self.index_dir}/metadata.json", "w") as f: + json.dump({"commit": self.current_commit, + "chunks": chunks}, f, indent=4) + index = GPTVectorStoreIndex([]) -@app.command() -def check_index_exists(root_path: str): - branch = get_current_branch() - exists = os.path.exists(index_dir_for(branch)) - print({"exists": exists}) + for chunk in doc_chunks: + index.insert(chunk) + # d = 1536 # Dimension of text-ada-embedding-002 + # faiss_index = faiss.IndexFlatL2(d) + # index = GPTFaissIndex(documents, faiss_index=faiss_index) + # index.save_to_disk(f"{index_dir_for(branch)}/index.json", faiss_index_save_path=f"{index_dir_for(branch)}/index_faiss_core.index") -@app.command() -def update(): - update_codebase_index() - print("Updated codebase index") + index.storage_context.persist(persist_dir=self.index_dir) + print("Codebase index created") -@app.command("create") -def create_index(): - create_codebase_index() - print("Created file index") + def get_modified_deleted_files(self) -> Tuple[List[str], List[str]]: + """Get a list of all files that have been modified since the last commit.""" + metadata = f"{self.index_dir}/metadata.json" + with open(metadata, "r") as f: + previous_commit = json.load(f)["commit"] + modified_deleted_files = subprocess.check_output( + ["git", "diff", "--name-only", previous_commit, self.current_commit]).decode("utf-8").strip() + modified_deleted_files = modified_deleted_files.split("\n") + modified_deleted_files = [f for f in modified_deleted_files if f] -@app.command() -def replace_additional_index(info: str): - replace_additional_index() - print("Replaced additional index") + deleted_files = [ + f for f in modified_deleted_files if not os.path.exists(self.git_root_dir + "/" + f)] + modified_files = [ + f for f in modified_deleted_files if os.path.exists(self.git_root_dir + "/" + f)] + return filter_ignored_files(modified_files, self.index_dir), filter_ignored_files(deleted_files, self.index_dir) -if __name__ == '__main__': - app() + def update_codebase_index(self): + """Update the index with a list of files.""" + + if not self.check_index_exists(): + self.create_codebase_index() + else: + # index = GPTFaissIndex.load_from_disk(f"{index_dir_for(branch)}/index.json", faiss_index_save_path=f"{index_dir_for(branch)}/index_faiss_core.index") + index = GPTVectorStoreIndex.load_from_disk( + f"{self.index_dir}/index.json") + modified_files, deleted_files = self.get_modified_deleted_files() + + with open(f"{self.index_dir}/metadata.json", "r") as f: + metadata = json.load(f) + + for file in deleted_files: + + num_chunks = metadata["chunks"][file] + for i in range(num_chunks): + index.delete(f"{file}::{i}") + + del metadata["chunks"][file] + + print(f"Deleted {file}") + + for file in modified_files: + + if file in metadata["chunks"]: + + num_chunks = metadata["chunks"][file] + + for i in range(num_chunks): + index.delete(f"{file}::{i}") + + print(f"Deleted old version of {file}") + + with open(file, "r") as f: + text = f.read() + + text_splitter = TokenTextSplitter() + text_chunks = text_splitter.split_text(text) + + for i, text in enumerate(text_chunks): + index.insert(Document(text, doc_id=f"{file}::{i}")) + + metadata["chunks"][file] = len(text_chunks) + + print(f"Inserted new version of {file}") + + metadata["commit"] = self.current_commit + + with open(f"{self.index_dir}/metadata.json", "w") as f: + json.dump(metadata, f, indent=4) + + print("Codebase index updated") + + def query_codebase_index(self, query: str) -> str: + """Query the codebase index.""" + if not self.check_index_exists(): + print("No index found for the codebase at ", self.index_dir) + return "" + + storage_context = StorageContext.from_defaults( + persist_dir=self.index_dir) + index = load_index_from_storage(storage_context) + # index = GPTVectorStoreIndex.load_from_disk(path) + engine = index.as_query_engine() + return engine.query(query) + + def query_additional_index(self, query: str) -> str: + """Query the additional index.""" + index = GPTVectorStoreIndex.load_from_disk( + os.path.join(self.index_dir, 'additional_index.json')) + return index.query(query) + + def replace_additional_index(self, info: str): + """Replace the additional index with the given info.""" + with open(f'{self.index_dir}/additional_context.txt', 'w') as f: + f.write(info) + documents = [Document(info)] + index = GPTVectorStoreIndex(documents) + index.save_to_disk(f'{self.index_dir}/additional_index.json') + print("Additional index replaced") diff --git a/continuedev/src/continuedev/libs/chroma/replace.py b/continuedev/src/continuedev/libs/chroma/replace.py deleted file mode 100644 index 1868b152..00000000 --- a/continuedev/src/continuedev/libs/chroma/replace.py +++ /dev/null @@ -1,19 +0,0 @@ -import sys -from llama_index import GPTVectorStoreIndex, Document - - -def replace_additional_index(info: str): - """Replace the additional index.""" - with open('data/additional_context.txt', 'w') as f: - f.write(info) - documents = [Document(info)] - index = GPTVectorStoreIndex(documents) - index.save_to_disk('data/additional_index.json') - print("Additional index replaced") - - -if __name__ == "__main__": - """python3 replace.py """ - info = sys.argv[1] if len(sys.argv) > 1 else None - if info: - replace_additional_index(info) diff --git a/continuedev/src/continuedev/libs/chroma/update.py b/continuedev/src/continuedev/libs/chroma/update.py index 3b9eb743..23ed950f 100644 --- a/continuedev/src/continuedev/libs/chroma/update.py +++ b/continuedev/src/continuedev/libs/chroma/update.py @@ -1,11 +1,9 @@ # import faiss -import json import os import subprocess -from llama_index.langchain_helpers.text_splitter import TokenTextSplitter -from llama_index import GPTVectorStoreIndex, SimpleDirectoryReader, Document -from typing import List, Generator, Tuple +from llama_index import SimpleDirectoryReader, Document +from typing import List from dotenv import load_dotenv load_dotenv() @@ -21,7 +19,7 @@ FILE_TYPES_TO_IGNORE = [ ] -def further_filter(files: List[str], root_dir: str): +def filter_ignored_files(files: List[str], root_dir: str): """Further filter files before indexing.""" for file in files: if file.endswith(tuple(FILE_TYPES_TO_IGNORE)) or file.startswith('.git') or file.startswith('archive'): @@ -29,14 +27,6 @@ def further_filter(files: List[str], root_dir: str): yield root_dir + "/" + file -def get_git_root_dir(path: str): - """Get the root directory of a Git repository.""" - try: - return subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], cwd=path).strip().decode() - except subprocess.CalledProcessError: - return None - - def get_git_ignored_files(root_dir: str): """Get the list of ignored files in a Git repository.""" try: @@ -59,7 +49,7 @@ def get_input_files(root_dir: str): ignored_files = set(get_git_ignored_files(root_dir)) all_files = set(get_all_files(root_dir)) nonignored_files = all_files - ignored_files - return further_filter(nonignored_files, root_dir) + return filter_ignored_files(nonignored_files, root_dir) def load_gpt_index_documents(root: str) -> List[Document]: @@ -68,146 +58,3 @@ def load_gpt_index_documents(root: str) -> List[Document]: input_files = get_input_files(root) # Use SimpleDirectoryReader to load the files into Documents return SimpleDirectoryReader(root, input_files=input_files, file_metadata=lambda filename: {"filename": filename}).load_data() - - -def index_dir_for(branch: str) -> str: - return f"/Users/natesesti/Desktop/continue/continuedev/src/continuedev/libs/data/{branch}" - - -def get_git_root_dir(): - return "/Users/natesesti/Desktop/continue/extension/examples/python" - result = subprocess.run(['git', 'rev-parse', '--show-toplevel'], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - return result.stdout.decode().strip() - - -def get_current_branch() -> str: - return subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).decode("utf-8").strip() - - -def get_current_commit() -> str: - return subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("utf-8").strip() - - -def create_codebase_index(): - """Create a new index for the current branch.""" - branch = get_current_branch() - if not os.path.exists(index_dir_for(branch)): - os.makedirs(index_dir_for(branch)) - - print("ROOT DIRECTORY: ", get_git_root_dir()) - documents = load_gpt_index_documents(get_git_root_dir()) - - chunks = {} - doc_chunks = [] - for doc in documents: - text_splitter = TokenTextSplitter() - text_chunks = text_splitter.split_text(doc.text) - filename = doc.extra_info["filename"] - chunks[filename] = len(text_chunks) - for i, text in enumerate(text_chunks): - doc_chunks.append(Document(text, doc_id=f"{filename}::{i}")) - - with open(f"{index_dir_for(branch)}/metadata.json", "w") as f: - json.dump({"commit": get_current_commit(), - "chunks": chunks}, f, indent=4) - - index = GPTVectorStoreIndex([]) - - for chunk in doc_chunks: - index.insert(chunk) - - # d = 1536 # Dimension of text-ada-embedding-002 - # faiss_index = faiss.IndexFlatL2(d) - # index = GPTFaissIndex(documents, faiss_index=faiss_index) - # index.save_to_disk(f"{index_dir_for(branch)}/index.json", faiss_index_save_path=f"{index_dir_for(branch)}/index_faiss_core.index") - - index.storage_context.persist( - persist_dir=index_dir_for(branch)) - - print("Codebase index created") - - -def get_modified_deleted_files() -> Tuple[List[str], List[str]]: - """Get a list of all files that have been modified since the last commit.""" - branch = get_current_branch() - current_commit = get_current_commit() - - metadata = f"{index_dir_for(branch)}/metadata.json" - with open(metadata, "r") as f: - previous_commit = json.load(f)["commit"] - - modified_deleted_files = subprocess.check_output( - ["git", "diff", "--name-only", previous_commit, current_commit]).decode("utf-8").strip() - modified_deleted_files = modified_deleted_files.split("\n") - modified_deleted_files = [f for f in modified_deleted_files if f] - - root = get_git_root_dir() - deleted_files = [ - f for f in modified_deleted_files if not os.path.exists(root + "/" + f)] - modified_files = [ - f for f in modified_deleted_files if os.path.exists(root + "/" + f)] - - return further_filter(modified_files, index_dir_for(branch)), further_filter(deleted_files, index_dir_for(branch)) - - -def update_codebase_index(): - """Update the index with a list of files.""" - branch = get_current_branch() - - if not os.path.exists(index_dir_for(branch)): - create_codebase_index() - else: - # index = GPTFaissIndex.load_from_disk(f"{index_dir_for(branch)}/index.json", faiss_index_save_path=f"{index_dir_for(branch)}/index_faiss_core.index") - index = GPTVectorStoreIndex.load_from_disk( - f"{index_dir_for(branch)}/index.json") - modified_files, deleted_files = get_modified_deleted_files() - - with open(f"{index_dir_for(branch)}/metadata.json", "r") as f: - metadata = json.load(f) - - for file in deleted_files: - - num_chunks = metadata["chunks"][file] - for i in range(num_chunks): - index.delete(f"{file}::{i}") - - del metadata["chunks"][file] - - print(f"Deleted {file}") - - for file in modified_files: - - if file in metadata["chunks"]: - - num_chunks = metadata["chunks"][file] - - for i in range(num_chunks): - index.delete(f"{file}::{i}") - - print(f"Deleted old version of {file}") - - with open(file, "r") as f: - text = f.read() - - text_splitter = TokenTextSplitter() - text_chunks = text_splitter.split_text(text) - - for i, text in enumerate(text_chunks): - index.insert(Document(text, doc_id=f"{file}::{i}")) - - metadata["chunks"][file] = len(text_chunks) - - print(f"Inserted new version of {file}") - - metadata["commit"] = get_current_commit() - - with open(f"{index_dir_for(branch)}/metadata.json", "w") as f: - json.dump(metadata, f, indent=4) - - print("Codebase index updated") - - -if __name__ == "__main__": - """python3 update.py""" - update_codebase_index() diff --git a/continuedev/src/continuedev/steps/chroma.py b/continuedev/src/continuedev/steps/chroma.py index 59a8b6e0..556f7252 100644 --- a/continuedev/src/continuedev/steps/chroma.py +++ b/continuedev/src/continuedev/steps/chroma.py @@ -1,12 +1,25 @@ from textwrap import dedent from typing import Coroutine, Union from ..core.observation import Observation, TextObservation -from ..core.main import Step, ContinueSDK +from ..core.main import Step +from ..core.sdk import ContinueSDK from .core.core import EditFileStep -from ..libs.chroma.query import query_codebase_index +from ..libs.chroma.query import ChromaIndexManager from .core.core import EditFileStep +class CreateCodebaseIndexChroma(Step): + name: str = "Create Codebase Index" + hide: bool = True + + async def describe(self, llm) -> Coroutine[str, None, None]: + return "Creating a codebase index for the current branch." + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + index = ChromaIndexManager(await sdk.ide.getWorkspaceDirectory()) + index.create_codebase_index() + + class AnswerQuestionChroma(Step): question: str _answer: Union[str, None] = None @@ -19,7 +32,8 @@ class AnswerQuestionChroma(Step): return self._answer async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - results = query_codebase_index(self.question) + index = ChromaIndexManager(await sdk.ide.getWorkspaceDirectory()) + results = index.query_codebase_index(self.question) code_snippets = "" @@ -52,7 +66,8 @@ class EditFileChroma(Step): hide: bool = True async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: - results = query_codebase_index(self.request) + index = ChromaIndexManager(await sdk.ide.getWorkspaceDirectory()) + results = index.query_codebase_index(self.question) resource_name = list( results.source_nodes[0].node.relationships.values())[0] -- cgit v1.2.3-70-g09d2 From 939986e90772d0bbbf6c63ead00093da4c57c553 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sat, 3 Jun 2023 20:30:57 -0400 Subject: polishing embeddings steps --- .gitignore | 2 ++ continuedev/src/continuedev/core/policy.py | 2 +- continuedev/src/continuedev/libs/chroma/query.py | 17 +++++++++++------ continuedev/src/continuedev/steps/chroma.py | 10 +++++++--- 4 files changed, 21 insertions(+), 10 deletions(-) (limited to 'continuedev/src') diff --git a/.gitignore b/.gitignore index 10dfccca..a4f53b82 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,5 @@ notes.txt cached_embeddings.pkl .ruff_cache codeql + +**/.continue \ No newline at end of file diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 6f357ba0..926cc624 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -17,7 +17,7 @@ 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 CreateCodebaseIndexChroma() >> MessageStep(message="Welcome to Continue!") >> StepsOnStartupStep() + return MessageStep(message="Welcome to Continue!") >> CreateCodebaseIndexChroma() >> StepsOnStartupStep() observation = history.get_current().observation if observation is not None and isinstance(observation, UserInputObservation): diff --git a/continuedev/src/continuedev/libs/chroma/query.py b/continuedev/src/continuedev/libs/chroma/query.py index 1cb178cc..c27329f0 100644 --- a/continuedev/src/continuedev/libs/chroma/query.py +++ b/continuedev/src/continuedev/libs/chroma/query.py @@ -38,21 +38,26 @@ class ChromaIndexManager: return None def check_index_exists(self): - return os.path.exists(self.index_dir) + return os.path.exists(os.path.join(self.index_dir, "metadata.json")) def create_codebase_index(self): """Create a new index for the current branch.""" if not self.check_index_exists(): os.makedirs(self.index_dir) + else: + return - print("ROOT DIRECTORY: ", self.git_root_dir) - documents = load_gpt_index_documents(self.git_root_dir) + documents = load_gpt_index_documents(self.workspace_dir) chunks = {} doc_chunks = [] for doc in documents: text_splitter = TokenTextSplitter() - text_chunks = text_splitter.split_text(doc.text) + try: + text_chunks = text_splitter.split_text(doc.text) + except: + print("ERROR (probably found special token): ", doc.text) + continue filename = doc.extra_info["filename"] chunks[filename] = len(text_chunks) for i, text in enumerate(text_chunks): @@ -88,9 +93,9 @@ class ChromaIndexManager: modified_deleted_files = [f for f in modified_deleted_files if f] deleted_files = [ - f for f in modified_deleted_files if not os.path.exists(self.git_root_dir + "/" + f)] + f for f in modified_deleted_files if not os.path.exists(os.path.join(self.workspace_dir, f))] modified_files = [ - f for f in modified_deleted_files if os.path.exists(self.git_root_dir + "/" + f)] + f for f in modified_deleted_files if os.path.exists(os.path.join(self.workspace_dir, f))] return filter_ignored_files(modified_files, self.index_dir), filter_ignored_files(deleted_files, self.index_dir) diff --git a/continuedev/src/continuedev/steps/chroma.py b/continuedev/src/continuedev/steps/chroma.py index 556f7252..7bb9389e 100644 --- a/continuedev/src/continuedev/steps/chroma.py +++ b/continuedev/src/continuedev/steps/chroma.py @@ -13,10 +13,12 @@ class CreateCodebaseIndexChroma(Step): hide: bool = True async def describe(self, llm) -> Coroutine[str, None, None]: - return "Creating a codebase index for the current branch." + return "Indexing the codebase..." async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: index = ChromaIndexManager(await sdk.ide.getWorkspaceDirectory()) + if not index.check_index_exists(): + self.hide = False index.create_codebase_index() @@ -55,7 +57,9 @@ class AnswerQuestionChroma(Step): Here is the answer:""") answer = (await sdk.models.gpt35()).complete(prompt) - print(answer) + # Make paths relative to the workspace directory + answer = answer.replace(await sdk.ide.getWorkspaceDirectory(), "") + self._answer = answer await sdk.ide.setFileOpen(files[0]) @@ -67,7 +71,7 @@ class EditFileChroma(Step): async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: index = ChromaIndexManager(await sdk.ide.getWorkspaceDirectory()) - results = index.query_codebase_index(self.question) + results = index.query_codebase_index(self.request) resource_name = list( results.source_nodes[0].node.relationships.values())[0] -- cgit v1.2.3-70-g09d2 From e4a40479cc1aacb1d91481e047ba2790c41ec16c Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 4 Jun 2023 00:34:05 -0400 Subject: .continue/config.json --- .gitignore | 2 + continuedev/src/continuedev/core/abstract_sdk.py | 85 ++++++++++++++++++++++ continuedev/src/continuedev/core/policy.py | 4 +- continuedev/src/continuedev/core/sdk.py | 14 +++- continuedev/src/continuedev/libs/util/telemetry.py | 2 +- continuedev/src/continuedev/steps/main.py | 15 ++++ docs/docs/concepts/continue-json.md | 4 +- docs/docs/concepts/policy.md | 2 +- docs/docs/telemetry.md | 4 +- 9 files changed, 120 insertions(+), 12 deletions(-) create mode 100644 continuedev/src/continuedev/core/abstract_sdk.py (limited to 'continuedev/src') diff --git a/.gitignore b/.gitignore index 10dfccca..a4f53b82 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,5 @@ notes.txt cached_embeddings.pkl .ruff_cache codeql + +**/.continue \ No newline at end of file diff --git a/continuedev/src/continuedev/core/abstract_sdk.py b/continuedev/src/continuedev/core/abstract_sdk.py new file mode 100644 index 00000000..9278f873 --- /dev/null +++ b/continuedev/src/continuedev/core/abstract_sdk.py @@ -0,0 +1,85 @@ +from abc import ABC, abstractmethod +from typing import Coroutine, List + +from .config import ContinueConfig +from ..models.filesystem_edit import FileSystemEdit +from .observation import Observation +from .main import History, Step + + +""" +[[Generate]] +[Prompt] +Write an abstract class AbstractContinueSDK(ABC) that has all of the same methods as the ContinueSDK class, but without any implementation. +All methods should be documented with the same docstrings as the ContinueSDK class and have the same types. +[Context] +./sdk.py:ContinueSDK +""" + + +class AbstractContinueSDK(ABC): + """The SDK provided as parameters to a step""" + + @property + def history(self) -> History: + return self.__autopilot.history + + @abstractmethod + async def _ensure_absolute_path(self, path: str) -> str: + pass + + @abstractmethod + async def run_step(self, step: Step) -> Coroutine[Observation, None, None]: + pass + + @abstractmethod + async def apply_filesystem_edit(self, edit: FileSystemEdit): + pass + + @abstractmethod + async def wait_for_user_input(self) -> str: + pass + + @abstractmethod + async def wait_for_user_confirmation(self, prompt: str): + pass + + @abstractmethod + async def run(self, commands: List[str] | str, cwd: str = None): + pass + + @abstractmethod + async def edit_file(self, filename: str, prompt: str): + pass + + @abstractmethod + async def append_to_file(self, filename: str, content: str): + pass + + @abstractmethod + async def add_file(self, filename: str, content: str | None): + pass + + @abstractmethod + async def delete_file(self, filename: str): + pass + + @abstractmethod + async def add_directory(self, path: str): + pass + + @abstractmethod + async def delete_directory(self, path: str): + pass + + @abstractmethod + async def get_user_secret(self, env_var: str, prompt: str) -> str: + pass + + @abstractmethod + async def get_config(self) -> ContinueConfig: + pass + + @abstractmethod + def set_loading_message(self, message: str): + pass diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index dc71cfa9..a946200e 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -4,7 +4,7 @@ from ..steps.steps_on_startup import StepsOnStartupStep from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe from .main import Step, Validator, History, Policy from .observation import Observation, TracebackObservation, UserInputObservation -from ..steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep, MessageStep, EmptyStep +from ..steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep, MessageStep, EmptyStep, SetupContinueWorkspaceStep from ..recipes.WritePytestsRecipe.main import WritePytestsRecipe # from ..libs.steps.chroma import AnswerQuestionChroma, EditFileChroma from ..recipes.ContinueRecipeRecipe.main import ContinueStepStep @@ -17,7 +17,7 @@ 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!") >> StepsOnStartupStep() + return MessageStep(message="Welcome to Continue!") >> SetupContinueWorkspaceStep() >> StepsOnStartupStep() observation = history.get_current().observation if observation is not None and isinstance(observation, UserInputObservation): diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 5ae471c4..de14ee3c 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -1,6 +1,8 @@ -import os +from abc import ABC, abstractmethod from typing import Coroutine, Union +import os +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 @@ -37,7 +39,7 @@ class Models: return OpenAI(api_key=api_key, default_model="gpt-3.5-turbo") -class ContinueSDK: +class ContinueSDK(AbstractContinueSDK): """The SDK provided as parameters to a step""" ide: AbstractIdeProtocolServer steps: ContinueSDKSteps @@ -92,15 +94,19 @@ class ContinueSDK: await self.ide.applyFileSystemEdit(file_edit) async def add_file(self, filename: str, content: str | None): + filepath = await self._ensure_absolute_path(filename) return await self.run_step(FileSystemEditStep(edit=AddFile(filename=filename, content=content))) async def delete_file(self, filename: str): + filepath = await self._ensure_absolute_path(filename) return await self.run_step(FileSystemEditStep(edit=DeleteFile(filepath=filename))) async def add_directory(self, path: str): + filepath = await self._ensure_absolute_path(path) return await self.run_step(FileSystemEditStep(edit=AddDirectory(path=path))) async def delete_directory(self, path: str): + filepath = await self._ensure_absolute_path(path) return await self.run_step(FileSystemEditStep(edit=DeleteDirectory(path=path))) async def get_user_secret(self, env_var: str, prompt: str) -> str: @@ -108,8 +114,8 @@ class ContinueSDK: async def get_config(self) -> ContinueConfig: dir = await self.ide.getWorkspaceDirectory() - yaml_path = os.path.join(dir, 'continue.yaml') - json_path = os.path.join(dir, 'continue.json') + yaml_path = os.path.join(dir, '.continue', 'config.yaml') + json_path = os.path.join(dir, '.continue', 'config.json') if os.path.exists(yaml_path): return load_config(yaml_path) elif os.path.exists(json_path): diff --git a/continuedev/src/continuedev/libs/util/telemetry.py b/continuedev/src/continuedev/libs/util/telemetry.py index 4bff3970..d6345c25 100644 --- a/continuedev/src/continuedev/libs/util/telemetry.py +++ b/continuedev/src/continuedev/libs/util/telemetry.py @@ -7,6 +7,6 @@ posthog = Posthog('phc_JS6XFROuNbhJtVCEdTSYk6gl5ArRrTNMpCcguAXlSPs', def capture_event(event_name, event_properties): - config = load_config('~/.continue/continue.json') + config = load_config('.continue/config.json') if config.allow_anonymous_telemetry: posthog.capture("not distinct :(", event_name, event_properties) diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py index dfb4f3be..da0fc8d2 100644 --- a/continuedev/src/continuedev/steps/main.py +++ b/continuedev/src/continuedev/steps/main.py @@ -1,3 +1,4 @@ +import os from typing import Coroutine, List, Union from pydantic import BaseModel @@ -17,6 +18,20 @@ import subprocess from .core.core import EditCodeStep +class SetupContinueWorkspaceStep(Step): + async def describe(self, models: Models) -> Coroutine[str, None, None]: + return "Set up Continue workspace by adding a .continue directory" + + async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: + if not os.path.exists(os.path.join(await sdk.ide.getWorkspaceDirectory(), ".continue")): + await sdk.add_directory(".continue") + if not os.path.exists(os.path.join(await sdk.ide.getWorkspaceDirectory(), ".continue", "config.json")): + await sdk.add_file(".continue/config.json", dedent("""\ + { + "allow_anonymous_telemetry": true + }""")) + + class RunCodeStep(Step): cmd: str diff --git a/docs/docs/concepts/continue-json.md b/docs/docs/concepts/continue-json.md index 7227f660..f176bdff 100644 --- a/docs/docs/concepts/continue-json.md +++ b/docs/docs/concepts/continue-json.md @@ -1,3 +1,3 @@ -# continue.json +# config.json -**TODO: Need to explain this** \ No newline at end of file +**TODO: Need to explain this** diff --git a/docs/docs/concepts/policy.md b/docs/docs/concepts/policy.md index ea515673..3c40115d 100644 --- a/docs/docs/concepts/policy.md +++ b/docs/docs/concepts/policy.md @@ -8,7 +8,7 @@ A **policy** is decides what step to run next and is associated with a [autopilo ## Details -A relic of my original plan that ended up being the place to define slash commands, the command run on startup, and other weird stuff that you might want to inject after certain other steps. This may be the place where "hooks" turn out to be implemented. Much of this may be configurable through `continue.json/yaml` config file (this is where steps that run on GUI opening are currently configured.). Simply takes the history and returns a single step to run next. Can return None if no step to take next. Then user input will kick it off again eventually. Autopilot has a single policy that it follows, so definitely a global/user-configurable type of thing. +A relic of my original plan that ended up being the place to define slash commands, the command run on startup, and other weird stuff that you might want to inject after certain other steps. This may be the place where "hooks" turn out to be implemented. Much of this may be configurable through `.continue/config.json/yaml` config file (this is where steps that run on GUI opening are currently configured.). Simply takes the history and returns a single step to run next. Can return None if no step to take next. Then user input will kick it off again eventually. Autopilot has a single policy that it follows, so definitely a global/user-configurable type of thing. - The Policy is where slash commands are defined - The Policy is a global thing, so probably something we'll want to make user-configurable if we don't significantly change it diff --git a/docs/docs/telemetry.md b/docs/docs/telemetry.md index 47f5a171..ca1fb641 100644 --- a/docs/docs/telemetry.md +++ b/docs/docs/telemetry.md @@ -14,10 +14,10 @@ We track the following... **TODO: Define where the root of your workspace** -Create a `continue.json` file in the root of your workspace and add the following: +Create a `.continue/config.json` file in the root of your workspace and add the following: ```json { "allow_anonymous_telemetry": false } -``` \ No newline at end of file +``` -- cgit v1.2.3-70-g09d2 From 897279d79739711ad75e18ccb409e1671aa159f7 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Sun, 4 Jun 2023 18:04:39 -0400 Subject: docusaurus link fixing --- continuedev/pyproject.toml | 1 - continuedev/src/continuedev/core/sdk.py | 2 +- docs/docusaurus.config.js | 4 ++-- docs/src/pages/index.js | 26 +++++++++++---------- extension/DEV_README.md | 2 +- extension/README.md | 2 +- extension/package-lock.json | 4 ++-- extension/package.json | 24 +++++++++++++------ .../scripts/continuedev-0.1.1-py3-none-any.whl | Bin 55076 -> 55610 bytes extension/src/README.md | 2 +- extension/src/activation/activate.ts | 3 +++ extension/src/commands.ts | 4 ++-- extension/src/debugPanel.ts | 13 ++++++++++- extension/src/terminal/terminalEmulator.ts | 2 +- 14 files changed, 57 insertions(+), 32 deletions(-) (limited to 'continuedev/src') diff --git a/continuedev/pyproject.toml b/continuedev/pyproject.toml index 83a287c8..631742ec 100644 --- a/continuedev/pyproject.toml +++ b/continuedev/pyproject.toml @@ -18,7 +18,6 @@ nest-asyncio = "^1.5.6" websockets = "^11.0.2" urllib3 = "1.26.15" gpt-index = "^0.6.8" -setuptools = "^67.7.2" posthog = "^3.0.1" [tool.poetry.scripts] diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index de14ee3c..5bd77d11 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -95,7 +95,7 @@ class ContinueSDK(AbstractContinueSDK): async def add_file(self, filename: str, content: str | None): filepath = await self._ensure_absolute_path(filename) - return await self.run_step(FileSystemEditStep(edit=AddFile(filename=filename, content=content))) + return await self.run_step(FileSystemEditStep(edit=AddFile(filepath=filepath, content=content))) async def delete_file(self, filename: str): filepath = await self._ensure_absolute_path(filename) diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index c2991841..1730332a 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -15,7 +15,7 @@ const config = { url: "https://continue.dev", // Set the // pathname under which your site is served // For GitHub pages deployment, it is often '//' - baseUrl: "/", + baseUrl: "/docs", // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. @@ -82,7 +82,7 @@ const config = { items: [ { label: "Introduction", - to: "/docs/intro", + to: "/docs/docs/intro", }, ], }, diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js index e974e52a..cbe05d60 100644 --- a/docs/src/pages/index.js +++ b/docs/src/pages/index.js @@ -1,23 +1,24 @@ -import React from 'react'; -import clsx from 'clsx'; -import Link from '@docusaurus/Link'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import Layout from '@theme/Layout'; -import HomepageFeatures from '@site/src/components/HomepageFeatures'; +import React from "react"; +import clsx from "clsx"; +import Link from "@docusaurus/Link"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import Layout from "@theme/Layout"; +import HomepageFeatures from "@site/src/components/HomepageFeatures"; -import styles from './index.module.css'; +import styles from "./index.module.css"; function HomepageHeader() { - const {siteConfig} = useDocusaurusContext(); + const { siteConfig } = useDocusaurusContext(); return ( -
+

{siteConfig.title}

{siteConfig.tagline}

+ to="/docs/docs/getting-started" + > GitHub Codespaces Demo
@@ -27,11 +28,12 @@ function HomepageHeader() { } export default function Home() { - const {siteConfig} = useDocusaurusContext(); + const { siteConfig } = useDocusaurusContext(); return ( + description="Documentation for the `Continue` library" + >
diff --git a/extension/DEV_README.md b/extension/DEV_README.md index 7049da45..dd02bf59 100644 --- a/extension/DEV_README.md +++ b/extension/DEV_README.md @@ -3,5 +3,5 @@ This is the Continue VS Code Extension. Its primary jobs are 1. Implement the IDE side of the Continue IDE protocol, allowing a Continue server to interact natively in an IDE. This happens in `src/continueIdeClient.ts`. -2. Open the Continue React app in a side panel. The React app's source code lives in the `react-app` directory. The panel is opened by the `continue.openDebugPanel` command, as defined in `src/commands.ts`. +2. Open the Continue React app in a side panel. The React app's source code lives in the `react-app` directory. The panel is opened by the `continue.openContinueGUI` command, as defined in `src/commands.ts`. 3. Run a Continue server in the background, which connects to both the IDE protocol and the React app. The server is launched in `src/activation/environmentSetup.ts` by calling Python code that lives in `scripts/` (unless extension settings define a server URL other than localhost:8000, in which case the extension will just connect to that). diff --git a/extension/README.md b/extension/README.md index 7fa8022b..12e25417 100644 --- a/extension/README.md +++ b/extension/README.md @@ -6,7 +6,7 @@ The Continue VS Code extension lets you make edits with natural langauge, ask qu ## Getting Started -Get started by opening the command pallet with cmd+shift+p and then selecting Continue: Open Debug Panel. +Get started by opening the command pallet with cmd+shift+p and then selecting Continue: Open Continue GUI. To test a few common recipes, open a blank python file and try the following: diff --git a/extension/package-lock.json b/extension/package-lock.json index ed140937..b923a2b2 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -1,12 +1,12 @@ { "name": "continue", - "version": "0.0.19", + "version": "0.0.20", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "continue", - "version": "0.0.19", + "version": "0.0.20", "license": "Apache-2.0", "dependencies": { "@electron/rebuild": "^3.2.10", diff --git a/extension/package.json b/extension/package.json index dd15157d..87c78b75 100644 --- a/extension/package.json +++ b/extension/package.json @@ -14,7 +14,7 @@ "displayName": "Continue", "pricing": "Free", "description": "Refine code 10x faster", - "version": "0.0.19", + "version": "0.0.20", "publisher": "Continue", "engines": { "vscode": "^1.74.0" @@ -23,7 +23,8 @@ "Other" ], "activationEvents": [ - "onStartupFinished" + "onStartupFinished", + "onView:continueGUIView" ], "main": "./out/extension.js", "contributes": { @@ -59,9 +60,9 @@ "title": "Write a docstring for the current function" }, { - "command": "continue.openDebugPanel", + "command": "continue.openContinueGUI", "category": "Continue", - "title": "Open Debug Panel" + "title": "Open Continue GUI" }, { "command": "continue.askQuestionFromInput", @@ -149,9 +150,18 @@ "menus": { "view/title": [ { - "command": "continue.openDebugPanel", + "command": "continue.openContinueGUI", "group": "navigation", - "when": "view == continue.debugView" + "when": "view == continue.continueGUIView" + } + ] + }, + "views": { + "explorer": [ + { + "type": "webview", + "id": "continue.continueGUIView", + "name": "Continue GUI" } ] } @@ -171,7 +181,7 @@ "lint": "eslint src --ext ts", "test": "node ./out/test/runTest.js", "package": "cp ./config/prod_config.json ./config/config.json && mkdir -p ./build && vsce package --out ./build && cp ./config/dev_config.json ./config/config.json", - "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.1-py3-none-any.whl ../extension/scripts/continuedev-0.1.1-py3-none-any.whl && cd ../extension && npm run typegen && npm run clientgen && cd react-app && npm run build && cd .. && npm run package", + "full-package": "cd ../continuedev && poetry build && cp ./dist/continuedev-0.1.1-py3-none-any.whl ../extension/scripts/continuedev-0.1.1-py3-none-any.whl && cd ../extension && npm install && npm run typegen && npm run clientgen && cd react-app && npm install && npm run build && cd .. && npm run package", "install-extension": "code --install-extension ./build/continue-0.0.8.vsix", "uninstall": "code --uninstall-extension .continue", "reinstall": "rm -rf ./build && npm run package && npm run uninstall && npm run install-extension" diff --git a/extension/scripts/continuedev-0.1.1-py3-none-any.whl b/extension/scripts/continuedev-0.1.1-py3-none-any.whl index 043b0c06..5bd3ea7d 100644 Binary files a/extension/scripts/continuedev-0.1.1-py3-none-any.whl and b/extension/scripts/continuedev-0.1.1-py3-none-any.whl differ diff --git a/extension/src/README.md b/extension/src/README.md index 76b96ea0..9fd73f9f 100644 --- a/extension/src/README.md +++ b/extension/src/README.md @@ -67,7 +67,7 @@ You should always have a packaged version installed in VS Code, because when Con ## Commands - "Write a docstring for the current function" command (windows: `ctrl+alt+l`, mac: `shift+cmd+l`) -- "Open Debug Panel" command +- "Open Continue GUI" command - "Ask a question from input box" command (windows: `ctrl+alt+j`, mac: `shift+cmd+j`) - "Open Captured Terminal" command - "Ask a question from webview" command (what context is it given?) diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts index 40def480..293ee26c 100644 --- a/extension/src/activation/activate.ts +++ b/extension/src/activation/activate.ts @@ -7,6 +7,7 @@ import * as path from "path"; // import { openCapturedTerminal } from "../terminal/terminalEmulator"; import IdeProtocolClient from "../continueIdeClient"; import { getContinueServerUrl } from "../bridge"; +import { setupDebugPanel } from "../debugPanel"; export let extensionContext: vscode.ExtensionContext | undefined = undefined; @@ -21,6 +22,8 @@ export function activateExtension( registerAllCodeLensProviders(context); registerAllCommands(context); + // vscode.window.registerWebviewViewProvider("continue.continueGUIView", setupDebugPanel); + let serverUrl = getContinueServerUrl(); ideProtocolClient = new IdeProtocolClient( diff --git a/extension/src/commands.ts b/extension/src/commands.ts index f0c1744b..c98cd3c3 100644 --- a/extension/src/commands.ts +++ b/extension/src/commands.ts @@ -61,7 +61,7 @@ const commandsMap: { [command: string]: (...args: any) => any } = { "continue.suggestionUp": suggestionUpCommand, "continue.acceptSuggestion": acceptSuggestionCommand, "continue.rejectSuggestion": rejectSuggestionCommand, - "continue.openDebugPanel": () => { + "continue.openContinueGUI": () => { ideProtocolClient.openGUI(); }, "continue.focusContinueInput": async () => { @@ -111,7 +111,7 @@ const commandsMap: { [command: string]: (...args: any) => any } = { vscode.window.showInformationMessage("The test passes!"); return; } - vscode.commands.executeCommand("continue.openDebugPanel").then(() => { + vscode.commands.executeCommand("continue.openContinueGUI").then(() => { setTimeout(() => { debugPanelWebview?.postMessage({ type: "traceback", diff --git a/extension/src/debugPanel.ts b/extension/src/debugPanel.ts index 87c33da1..7407faf4 100644 --- a/extension/src/debugPanel.ts +++ b/extension/src/debugPanel.ts @@ -135,7 +135,7 @@ let streamManager = new StreamManager(); export let debugPanelWebview: vscode.Webview | undefined; export function setupDebugPanel( - panel: vscode.WebviewPanel, + panel: vscode.WebviewPanel | vscode.WebviewView, context: vscode.ExtensionContext | undefined, sessionId: string ): string { @@ -487,3 +487,14 @@ export function setupDebugPanel( `; } + +// class ContinueGUIWebviewViewProvider implements vscode.WebviewViewProvider { +// public static readonly viewType = "continue.continueGUIView"; +// resolveWebviewView( +// webviewView: vscode.WebviewView, +// context: vscode.WebviewViewResolveContext, +// token: vscode.CancellationToken +// ): void | Thenable { +// setupDebugPanel(webviewView, context, sessionId); +// } +// } diff --git a/extension/src/terminal/terminalEmulator.ts b/extension/src/terminal/terminalEmulator.ts index ba860b24..6cf65970 100644 --- a/extension/src/terminal/terminalEmulator.ts +++ b/extension/src/terminal/terminalEmulator.ts @@ -19,7 +19,7 @@ // }); // } else { // vscode.commands -// .executeCommand("continue.openDebugPanel", extensionContext) +// .executeCommand("continue.openContinueGUI", extensionContext) // .then(() => { // // TODO: Waiting for the webview to load, but should add a hook to the onLoad message event. Same thing in autodebugTest command in commands.ts // setTimeout(() => { -- cgit v1.2.3-70-g09d2 From 61ef38efa40e6ef61efa375de5daea4306b635d9 Mon Sep 17 00:00:00 2001 From: Ty Dunn Date: Mon, 5 Jun 2023 08:53:48 +0200 Subject: outcomes of work session --- README.md | 1 + continuedev/README.md | 15 +- continuedev/pyproject.toml | 1 - continuedev/src/continuedev/core/abstract_sdk.py | 6 +- continuedev/src/continuedev/core/sdk.py | 4 +- continuedev/src/continuedev/steps/core/core.py | 4 +- docs/docs/catalog.md | 4 +- docs/docs/walkthroughs/create-a-recipe.md | 6 + docs/docs/walkthroughs/use-a-recipe.md | 8 +- extension/DEV_README.md | 2 + extension/package-lock.json | 2781 ++++++++++++++++++-- extension/package.json | 7 + .../scripts/continuedev-0.1.1-py3-none-any.whl | Bin 55076 -> 55604 bytes extension/src/README.md | 42 +- extension/src/activation/environmentSetup.ts | 2 +- 15 files changed, 2636 insertions(+), 247 deletions(-) (limited to 'continuedev/src') diff --git a/README.md b/README.md index 573c6da5..92570126 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ cd continue/extension/scripts && python3 install_from_source.py - [Continue GUI README](./extension/react-app/): learn about the React app that lets users interact with the server and is placed adjacent to the text editor in any suppported IDE - [Schema README](./schema): learn about the JSON Schema types generated from Pydantic models, which we use across the `continuedev/` and `extension/` directories - [Continue Docs README](./docs): learn how our [docs](https://continue.dev/docs) are written and built +- Add steps for development README mention here # License diff --git a/continuedev/README.md b/continuedev/README.md index e27cf3ae..b7967ddd 100644 --- a/continuedev/README.md +++ b/continuedev/README.md @@ -6,13 +6,14 @@ Continue is a Python library for automating repetitive sequences of software dev ## Continue Server -The Continue server acts as a bridge between the Continue React app and your IDE, running your recipes and acting on the codebase. Start it by running the following commands: - -- `cd continuedev` -- Make sure packages are installed with `poetry install` -- `poetry shell` -- `cd ..` -- `python3 -m continuedev.src.continuedev.server.main` +The Continue server acts as a bridge between the Continue React app and your IDE, running your recipes and acting on the codebase. + +Start it by running the following commands: +1. `cd continuedev` +2. Make sure packages are installed with `poetry install` +3. `poetry shell` +4. `cd ..` +5. `python3 -m continuedev.src.continuedev.server.main` ## Scripts diff --git a/continuedev/pyproject.toml b/continuedev/pyproject.toml index 83a287c8..631742ec 100644 --- a/continuedev/pyproject.toml +++ b/continuedev/pyproject.toml @@ -18,7 +18,6 @@ nest-asyncio = "^1.5.6" websockets = "^11.0.2" urllib3 = "1.26.15" gpt-index = "^0.6.8" -setuptools = "^67.7.2" posthog = "^3.0.1" [tool.poetry.scripts] diff --git a/continuedev/src/continuedev/core/abstract_sdk.py b/continuedev/src/continuedev/core/abstract_sdk.py index 9278f873..1c800875 100644 --- a/continuedev/src/continuedev/core/abstract_sdk.py +++ b/continuedev/src/continuedev/core/abstract_sdk.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Coroutine, List +from typing import Coroutine, List, Union from .config import ContinueConfig from ..models.filesystem_edit import FileSystemEdit @@ -45,7 +45,7 @@ class AbstractContinueSDK(ABC): pass @abstractmethod - async def run(self, commands: List[str] | str, cwd: str = None): + async def run(self, commands: Union[List[str], str], cwd: str = None): pass @abstractmethod @@ -57,7 +57,7 @@ class AbstractContinueSDK(ABC): pass @abstractmethod - async def add_file(self, filename: str, content: str | None): + async def add_file(self, filename: str, content: Union[str, None]): pass @abstractmethod diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index de14ee3c..f9f3679e 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -73,7 +73,7 @@ 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: List[str] | str, cwd: str = None): + async def run(self, commands: Union[List[str], str], cwd: str = None): commands = commands if isinstance(commands, List) else [commands] return await self.run_step(ShellCommandsStep(cmds=commands, cwd=cwd)) @@ -93,7 +93,7 @@ class ContinueSDK(AbstractContinueSDK): file_edit = FileEdit.from_append(filepath, previous_content, content) await self.ide.applyFileSystemEdit(file_edit) - async def add_file(self, filename: str, content: str | None): + async def add_file(self, filename: str, content: Union[str, None]): filepath = await self._ensure_absolute_path(filename) return await self.run_step(FileSystemEditStep(edit=AddFile(filename=filename, content=content))) diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index d7f7a307..fdcd9837 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -40,10 +40,10 @@ class FileSystemEditStep(ReversibleStep): class ShellCommandsStep(Step): cmds: List[str] - cwd: str | None = None + cwd: Union[str, None] = None name: str = "Run Shell Commands" - _err_text: str | None = None + _err_text: Union[str, None] = None async def describe(self, models: Models) -> Coroutine[str, None, None]: if self._err_text is not None: diff --git a/docs/docs/catalog.md b/docs/docs/catalog.md index 25b1214d..b9e223e2 100644 --- a/docs/docs/catalog.md +++ b/docs/docs/catalog.md @@ -2,7 +2,7 @@ ## Steps -**TODO: better explain each step and link to them** +**TODO: better explain each step and link to them, find all in steps folder** #### EditCodeStep @@ -30,4 +30,4 @@ Type `/ask` and ask any question about your whole codebase. The Chroma embedding ## Recipes -**TODO: add each recipe, explain them, and link to them** \ No newline at end of file +**TODO: add each recipe, explain them, and link to them, find all in recipes folder** \ No newline at end of file diff --git a/docs/docs/walkthroughs/create-a-recipe.md b/docs/docs/walkthroughs/create-a-recipe.md index 6a32906a..60bfe9a8 100644 --- a/docs/docs/walkthroughs/create-a-recipe.md +++ b/docs/docs/walkthroughs/create-a-recipe.md @@ -11,8 +11,14 @@ Points to include - How to create recipe - Using models +1. Create a recipe here + +continue/continuedev/src/continuedev/recipes + ## 1. Create a step + + ### a. Start by creating a subclass of Step You should first consider what will be the parameters of your recipe. These are defined as attributes in the step, as with `input_file_path: str` below diff --git a/docs/docs/walkthroughs/use-a-recipe.md b/docs/docs/walkthroughs/use-a-recipe.md index a5be199e..dbe8901b 100644 --- a/docs/docs/walkthroughs/use-a-recipe.md +++ b/docs/docs/walkthroughs/use-a-recipe.md @@ -10,9 +10,15 @@ All of the recipes are located in this part of the codebase here At the moment, we only support editing the file open and focused in the code editor, so you need to make sure that is +user input step +demopolicy decides what to do +default step, starcoder to edit the current open file + ## Use its slash command to start the recipe -Every recipe can be triggered by the user using its slash command. For example, `/comment` will trigger +Every recipe can be triggered by the user using its slash command. For example, `/comment` will trigger. + +add to the if / else with `/` commands ## Review the steps and take any required manual actions diff --git a/extension/DEV_README.md b/extension/DEV_README.md index 7049da45..4df0e926 100644 --- a/extension/DEV_README.md +++ b/extension/DEV_README.md @@ -5,3 +5,5 @@ This is the Continue VS Code Extension. Its primary jobs are 1. Implement the IDE side of the Continue IDE protocol, allowing a Continue server to interact natively in an IDE. This happens in `src/continueIdeClient.ts`. 2. Open the Continue React app in a side panel. The React app's source code lives in the `react-app` directory. The panel is opened by the `continue.openDebugPanel` command, as defined in `src/commands.ts`. 3. Run a Continue server in the background, which connects to both the IDE protocol and the React app. The server is launched in `src/activation/environmentSetup.ts` by calling Python code that lives in `scripts/` (unless extension settings define a server URL other than localhost:8000, in which case the extension will just connect to that). + +1. Open Continue \ No newline at end of file diff --git a/extension/package-lock.json b/extension/package-lock.json index ed140937..c7c67fdc 100644 --- a/extension/package-lock.json +++ b/extension/package-lock.json @@ -13,11 +13,16 @@ "@reduxjs/toolkit": "^1.9.3", "@segment/analytics-node": "^0.0.1-beta.16", "@styled-icons/heroicons-outline": "^10.47.0", + "@vitejs/plugin-react-swc": "^3.3.2", "axios": "^1.2.5", "highlight.js": "^11.7.0", "octokit": "^2.0.11", + "posthog-js": "^1.63.3", + "react-markdown": "^8.0.7", "react-redux": "^8.0.5", "strip-ansi": "^7.0.1", + "tailwindcss": "^3.3.2", + "vite": "^4.3.9", "vscode-languageclient": "^8.0.2", "ws": "^8.13.0" }, @@ -27,6 +32,8 @@ "@types/mocha": "^10.0.1", "@types/node": "16.x", "@types/node-fetch": "^2.6.2", + "@types/react-dom": "^18.2.4", + "@types/styled-components": "^5.1.26", "@types/vscode": "^1.74.0", "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.45.0", @@ -44,6 +51,17 @@ "vscode": "^1.74.0" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@babel/code-frame": { "version": "7.21.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", @@ -461,7 +479,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "android" @@ -477,7 +494,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "android" @@ -493,7 +509,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "android" @@ -509,7 +524,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -525,7 +539,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -541,7 +554,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -557,7 +569,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -573,7 +584,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -589,7 +599,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -605,7 +614,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "linux" @@ -621,7 +629,6 @@ "cpu": [ "loong64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -637,7 +644,6 @@ "cpu": [ "mips64el" ], - "dev": true, "optional": true, "os": [ "linux" @@ -653,7 +659,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -669,7 +674,6 @@ "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -685,7 +689,6 @@ "cpu": [ "s390x" ], - "dev": true, "optional": true, "os": [ "linux" @@ -701,7 +704,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -717,7 +719,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "netbsd" @@ -733,7 +734,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "openbsd" @@ -749,7 +749,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "sunos" @@ -765,7 +764,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -781,7 +779,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "win32" @@ -797,7 +794,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -871,7 +867,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "peer": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -885,7 +880,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -894,7 +888,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -902,14 +895,12 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "peer": true + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.18", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -918,8 +909,7 @@ "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "peer": true + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", @@ -1048,7 +1038,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1061,7 +1050,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -1070,7 +1058,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1838,6 +1825,189 @@ "styled-components": ">=4.1.0 <6" } }, + "node_modules/@swc/core": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.62.tgz", + "integrity": "sha512-J58hWY+/G8vOr4J6ZH9hLg0lMSijZtqIIf4HofZezGog/pVX6sJyBJ40dZ1ploFkDIlWTWvJyqtpesBKS73gkQ==", + "hasInstallScript": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.62", + "@swc/core-darwin-x64": "1.3.62", + "@swc/core-linux-arm-gnueabihf": "1.3.62", + "@swc/core-linux-arm64-gnu": "1.3.62", + "@swc/core-linux-arm64-musl": "1.3.62", + "@swc/core-linux-x64-gnu": "1.3.62", + "@swc/core-linux-x64-musl": "1.3.62", + "@swc/core-win32-arm64-msvc": "1.3.62", + "@swc/core-win32-ia32-msvc": "1.3.62", + "@swc/core-win32-x64-msvc": "1.3.62" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.62.tgz", + "integrity": "sha512-MmGilibITz68LEje6vJlKzc2gUUSgzvB3wGLSjEORikTNeM7P8jXVxE4A8fgZqDeudJUm9HVWrxCV+pHDSwXhA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.62.tgz", + "integrity": "sha512-Xl93MMB3sCWVlYWuQIB+v6EQgzoiuQYK5tNt9lsHoIEVu2zLdkQjae+5FUHZb1VYqCXIiWcULFfVz0R4Sjb7JQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.62.tgz", + "integrity": "sha512-nJsp6O7kCtAjTTMcIjVB0g5y1JNiYAa5q630eiwrnaHUusEFoANDdORI3Z9vXeikMkng+6yIv9/V8Rb093xLjQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.62.tgz", + "integrity": "sha512-XGsV93vpUAopDt5y6vPwbK1Nc/MlL55L77bAZUPIiosWD1cWWPHNtNSpriE6+I+JiMHe0pqtfS/SSTk6ZkFQVw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.62.tgz", + "integrity": "sha512-ESUmJjSlTTkoBy9dMG49opcNn8BmviqStMhwyeD1G8XRnmRVCZZgoBOKdvCXmJhw8bQXDhZumeaTUB+OFUKVXg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.62.tgz", + "integrity": "sha512-wnHJkt3ZBrax3SFnUHDcncG6mrSg9ZZjMhQV9Mc3JL1x1s1Gy9rGZCoBNnV/BUZWTemxIBcQbANRSDut/WO+9A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.62.tgz", + "integrity": "sha512-9oRbuTC/VshB66Rgwi3pTq3sPxSTIb8k9L1vJjES+dDMKa29DAjPtWCXG/pyZ00ufpFZgkGEuAHH5uqUcr1JQg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.62.tgz", + "integrity": "sha512-zv14vlF2VRrxS061XkfzGjCYnOrEo5glKJjLK5PwUKysIoVrx/L8nAbFxjkX5cObdlyoqo+ekelyBPAO+4bS0w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.62.tgz", + "integrity": "sha512-8MC/PZQSsOP2iA/81tAfNRqMWyEqTS/8zKUI67vPuLvpx6NAjRn3E9qBv7iFqH79iqZNzqSMo3awnLrKZyFbcw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.62.tgz", + "integrity": "sha512-GJSmUJ95HKHZXAxiuPUmrcm/S3ivQvEzXhOZaIqYBIwUsm02vFZkClsV7eIKzWjso1t0+I/8MjrnUNaSWqh1rQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -1879,6 +2049,14 @@ "@types/responselike": "^1.0.0" } }, + "node_modules/@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/glob": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.0.0.tgz", @@ -1889,6 +2067,14 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -1936,6 +2122,14 @@ "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==" }, + "node_modules/@types/mdast": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.11.tgz", + "integrity": "sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -1948,6 +2142,11 @@ "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", "dev": true }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, "node_modules/@types/node": { "version": "16.18.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.11.tgz", @@ -1998,6 +2197,15 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-dom": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz", + "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==", + "devOptional": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -2017,6 +2225,22 @@ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, + "node_modules/@types/styled-components": { + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dev": true, + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", @@ -2224,6 +2448,17 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.3.2.tgz", + "integrity": "sha512-VJFWY5sfoZerQRvJrh518h3AcQt6f/yTuWn4/TRB+dqmYU0NX1qz7qM5Wfd+gOQqUzQW4gxKqKN3KpE/P3+zrA==", + "dependencies": { + "@swc/core": "^1.3.61" + }, + "peerDependencies": { + "vite": "^4" + } + }, "node_modules/@vscode/test-electron": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.2.2.tgz", @@ -2381,14 +2616,12 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2427,6 +2660,11 @@ "node": ">= 6" } }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2489,6 +2727,15 @@ "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==", "peer": true }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2544,7 +2791,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "engines": { "node": ">=8" } @@ -2602,7 +2848,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -2788,6 +3033,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "engines": { + "node": ">= 6" + } + }, "node_modules/camelize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", @@ -2824,6 +3077,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -2872,7 +3134,6 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "funding": [ { "type": "individual", @@ -2899,7 +3160,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -3056,6 +3316,15 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", @@ -3215,6 +3484,17 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/csstype": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", @@ -3271,6 +3551,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -3356,6 +3648,14 @@ "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", @@ -3364,11 +3664,15 @@ "node": ">=8" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, "node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, "engines": { "node": ">=0.3.1" } @@ -3385,6 +3689,11 @@ "node": ">=8" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -3585,7 +3894,6 @@ "version": "0.17.19", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", - "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -3897,6 +4205,11 @@ "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", "dev": true }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -3945,7 +4258,6 @@ "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -3961,7 +4273,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -3991,7 +4302,6 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -4005,6 +4315,11 @@ "pend": "~1.2.0" } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -4045,7 +4360,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -4187,7 +4501,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -4247,8 +4560,7 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/gauge": { "version": "4.0.4", @@ -4363,7 +4675,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -4464,7 +4775,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -4497,6 +4807,15 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, + "node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -4715,6 +5034,11 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, "node_modules/inquirer": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", @@ -4771,7 +5095,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -4779,11 +5102,43 @@ "node": ">=8" } }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4800,7 +5155,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -4825,7 +5179,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -4893,6 +5246,14 @@ "node": ">=6" } }, + "node_modules/jiti": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", + "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", @@ -4906,8 +5267,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "peer": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -5109,6 +5469,14 @@ "json-buffer": "3.0.1" } }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -5131,6 +5499,19 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, "node_modules/linkify-it": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", @@ -5191,7 +5572,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -5337,6 +5717,74 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", @@ -5363,16 +5811,435 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } }, + "node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -5663,6 +6530,14 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5678,7 +6553,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -5842,7 +6716,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5888,11 +6761,18 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -6122,62 +7002,231 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "engines": { - "node": ">=8" + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-to-regexp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", + "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.24", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", + "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, "engines": { - "node": ">=0.10.0" + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dependencies": { + "camelcase-css": "^2.0.1" + }, "engines": { - "node": ">=8" + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" } }, - "node_modules/path-to-regexp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", - "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, "engines": { - "node": ">=8" + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, "engines": { - "node": ">=8.6" + "node": ">=12.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" } }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "peer": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/posthog-js": { + "version": "1.63.3", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.63.3.tgz", + "integrity": "sha512-Ob2x1ENxx/mWAdhLhc6ouKAph9LwmitBy7M+hiaFoS0T9g7OVL4lYdrJbODbYV+HX/HeO3bsnQwYO1QLPWm/YA==", + "dependencies": { + "fflate": "^0.4.1" + } }, "node_modules/prebuild-install": { "version": "7.1.1", @@ -6252,6 +7301,30 @@ "node": ">=10" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/property-information": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -6294,7 +7367,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -6384,6 +7456,36 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "node_modules/react-markdown": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz", + "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/prop-types": "^15.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "prop-types": "^15.0.0", + "property-information": "^6.0.0", + "react-is": "^18.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, "node_modules/react-redux": { "version": "8.0.5", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", @@ -6434,6 +7536,14 @@ "node": ">=0.8" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dependencies": { + "pify": "^2.3.0" + } + }, "node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -6459,7 +7569,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -6506,6 +7615,35 @@ "url": "https://github.com/sponsors/mysticatea" } }, + "node_modules/remark-parse": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -6519,6 +7657,22 @@ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -6568,7 +7722,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -6607,6 +7760,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "3.23.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.23.1.tgz", + "integrity": "sha512-ybRdFVHOoljGEFILHLd2g/qateqUdjE6YS41WXq4p3C/WwD3xtWxV4FYWETA1u9TeXQc5K8L8zHE5d/scOvrOQ==", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -6620,7 +7788,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -6654,6 +7821,17 @@ "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -6861,6 +8039,23 @@ "node": ">= 10" } }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spawn-command": { "version": "0.0.2-1", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", @@ -6949,6 +8144,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-object": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz", + "integrity": "sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, "node_modules/styled-components": { "version": "5.3.9", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", @@ -7000,6 +8203,54 @@ "node": ">=4" } }, + "node_modules/sucrase": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", + "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7011,6 +8262,54 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", + "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tar": { "version": "6.1.13", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", @@ -7107,7 +8406,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "dependencies": { "any-promise": "^1.0.0" } @@ -7116,7 +8414,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -7165,7 +8462,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -7196,6 +8492,29 @@ "tree-kill": "cli.js" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -7306,16 +8625,45 @@ "dependencies": { "@lukeed/csprng": "^1.0.0" }, - "engines": { - "node": ">=8" + "engines": { + "node": ">=8" + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true + }, + "node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", - "dev": true - }, "node_modules/unique-filename": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", @@ -7338,6 +8686,78 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universal-github-app-jwt": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.1.1.tgz", @@ -7416,6 +8836,98 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", + "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", + "dependencies": { + "esbuild": "^0.17.5", + "postcss": "^8.4.23", + "rollup": "^3.21.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, "node_modules/vsce": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/vsce/-/vsce-2.15.0.tgz", @@ -7740,6 +9252,14 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -7815,6 +9335,11 @@ } }, "dependencies": { + "@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==" + }, "@babel/code-frame": { "version": "7.21.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", @@ -8139,154 +9664,132 @@ "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", - "dev": true, "optional": true }, "@esbuild/android-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", - "dev": true, "optional": true }, "@esbuild/android-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", - "dev": true, "optional": true }, "@esbuild/darwin-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", - "dev": true, "optional": true }, "@esbuild/darwin-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", - "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", - "dev": true, "optional": true }, "@esbuild/freebsd-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", - "dev": true, "optional": true }, "@esbuild/linux-arm": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", - "dev": true, "optional": true }, "@esbuild/linux-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", - "dev": true, "optional": true }, "@esbuild/linux-ia32": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", - "dev": true, "optional": true }, "@esbuild/linux-loong64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", - "dev": true, "optional": true }, "@esbuild/linux-mips64el": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", - "dev": true, "optional": true }, "@esbuild/linux-ppc64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", - "dev": true, "optional": true }, "@esbuild/linux-riscv64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", - "dev": true, "optional": true }, "@esbuild/linux-s390x": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", - "dev": true, "optional": true }, "@esbuild/linux-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", - "dev": true, "optional": true }, "@esbuild/netbsd-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", - "dev": true, "optional": true }, "@esbuild/openbsd-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", - "dev": true, "optional": true }, "@esbuild/sunos-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", - "dev": true, "optional": true }, "@esbuild/win32-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", - "dev": true, "optional": true }, "@esbuild/win32-ia32": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", - "dev": true, "optional": true }, "@esbuild/win32-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", - "dev": true, "optional": true }, "@eslint/eslintrc": { @@ -8338,7 +9841,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "peer": true, "requires": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -8348,26 +9850,22 @@ "@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "peer": true + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" }, "@jridgewell/set-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "peer": true + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" }, "@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "peer": true + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "@jridgewell/trace-mapping": { "version": "0.3.18", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "peer": true, "requires": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -8376,8 +9874,7 @@ "@jridgewell/sourcemap-codec": { "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "peer": true + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" } } }, @@ -8466,7 +9963,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -8475,14 +9971,12 @@ "@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" }, "@nodelib/fs.walk": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -9073,6 +10567,83 @@ "@babel/runtime": "^7.19.0" } }, + "@swc/core": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.62.tgz", + "integrity": "sha512-J58hWY+/G8vOr4J6ZH9hLg0lMSijZtqIIf4HofZezGog/pVX6sJyBJ40dZ1ploFkDIlWTWvJyqtpesBKS73gkQ==", + "requires": { + "@swc/core-darwin-arm64": "1.3.62", + "@swc/core-darwin-x64": "1.3.62", + "@swc/core-linux-arm-gnueabihf": "1.3.62", + "@swc/core-linux-arm64-gnu": "1.3.62", + "@swc/core-linux-arm64-musl": "1.3.62", + "@swc/core-linux-x64-gnu": "1.3.62", + "@swc/core-linux-x64-musl": "1.3.62", + "@swc/core-win32-arm64-msvc": "1.3.62", + "@swc/core-win32-ia32-msvc": "1.3.62", + "@swc/core-win32-x64-msvc": "1.3.62" + } + }, + "@swc/core-darwin-arm64": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.62.tgz", + "integrity": "sha512-MmGilibITz68LEje6vJlKzc2gUUSgzvB3wGLSjEORikTNeM7P8jXVxE4A8fgZqDeudJUm9HVWrxCV+pHDSwXhA==", + "optional": true + }, + "@swc/core-darwin-x64": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.62.tgz", + "integrity": "sha512-Xl93MMB3sCWVlYWuQIB+v6EQgzoiuQYK5tNt9lsHoIEVu2zLdkQjae+5FUHZb1VYqCXIiWcULFfVz0R4Sjb7JQ==", + "optional": true + }, + "@swc/core-linux-arm-gnueabihf": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.62.tgz", + "integrity": "sha512-nJsp6O7kCtAjTTMcIjVB0g5y1JNiYAa5q630eiwrnaHUusEFoANDdORI3Z9vXeikMkng+6yIv9/V8Rb093xLjQ==", + "optional": true + }, + "@swc/core-linux-arm64-gnu": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.62.tgz", + "integrity": "sha512-XGsV93vpUAopDt5y6vPwbK1Nc/MlL55L77bAZUPIiosWD1cWWPHNtNSpriE6+I+JiMHe0pqtfS/SSTk6ZkFQVw==", + "optional": true + }, + "@swc/core-linux-arm64-musl": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.62.tgz", + "integrity": "sha512-ESUmJjSlTTkoBy9dMG49opcNn8BmviqStMhwyeD1G8XRnmRVCZZgoBOKdvCXmJhw8bQXDhZumeaTUB+OFUKVXg==", + "optional": true + }, + "@swc/core-linux-x64-gnu": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.62.tgz", + "integrity": "sha512-wnHJkt3ZBrax3SFnUHDcncG6mrSg9ZZjMhQV9Mc3JL1x1s1Gy9rGZCoBNnV/BUZWTemxIBcQbANRSDut/WO+9A==", + "optional": true + }, + "@swc/core-linux-x64-musl": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.62.tgz", + "integrity": "sha512-9oRbuTC/VshB66Rgwi3pTq3sPxSTIb8k9L1vJjES+dDMKa29DAjPtWCXG/pyZ00ufpFZgkGEuAHH5uqUcr1JQg==", + "optional": true + }, + "@swc/core-win32-arm64-msvc": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.62.tgz", + "integrity": "sha512-zv14vlF2VRrxS061XkfzGjCYnOrEo5glKJjLK5PwUKysIoVrx/L8nAbFxjkX5cObdlyoqo+ekelyBPAO+4bS0w==", + "optional": true + }, + "@swc/core-win32-ia32-msvc": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.62.tgz", + "integrity": "sha512-8MC/PZQSsOP2iA/81tAfNRqMWyEqTS/8zKUI67vPuLvpx6NAjRn3E9qBv7iFqH79iqZNzqSMo3awnLrKZyFbcw==", + "optional": true + }, + "@swc/core-win32-x64-msvc": { + "version": "1.3.62", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.62.tgz", + "integrity": "sha512-GJSmUJ95HKHZXAxiuPUmrcm/S3ivQvEzXhOZaIqYBIwUsm02vFZkClsV7eIKzWjso1t0+I/8MjrnUNaSWqh1rQ==", + "optional": true + }, "@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -9108,6 +10679,14 @@ "@types/responselike": "^1.0.0" } }, + "@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "requires": { + "@types/ms": "*" + } + }, "@types/glob": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.0.0.tgz", @@ -9118,6 +10697,14 @@ "@types/node": "*" } }, + "@types/hast": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz", + "integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==", + "requires": { + "@types/unist": "*" + } + }, "@types/hoist-non-react-statics": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", @@ -9165,6 +10752,14 @@ "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==" }, + "@types/mdast": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.11.tgz", + "integrity": "sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==", + "requires": { + "@types/unist": "*" + } + }, "@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -9177,6 +10772,11 @@ "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", "dev": true }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, "@types/node": { "version": "16.18.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.11.tgz", @@ -9226,6 +10826,15 @@ "csstype": "^3.0.2" } }, + "@types/react-dom": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz", + "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==", + "devOptional": true, + "requires": { + "@types/react": "*" + } + }, "@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -9245,6 +10854,22 @@ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, + "@types/styled-components": { + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dev": true, + "requires": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "@types/unist": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", + "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==" + }, "@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", @@ -9363,6 +10988,14 @@ "eslint-visitor-keys": "^3.3.0" } }, + "@vitejs/plugin-react-swc": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.3.2.tgz", + "integrity": "sha512-VJFWY5sfoZerQRvJrh518h3AcQt6f/yTuWn4/TRB+dqmYU0NX1qz7qM5Wfd+gOQqUzQW4gxKqKN3KpE/P3+zrA==", + "requires": { + "@swc/core": "^1.3.61" + } + }, "@vscode/test-electron": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.2.2.tgz", @@ -9471,14 +11104,12 @@ "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" }, "anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -9510,6 +11141,11 @@ } } }, + "arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -9566,6 +11202,11 @@ "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==", "peer": true }, + "bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -9600,8 +11241,7 @@ "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "bl": { "version": "4.1.0", @@ -9655,7 +11295,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -9787,6 +11426,11 @@ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" + }, "camelize": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", @@ -9811,6 +11455,11 @@ "supports-color": "^7.1.0" } }, + "character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==" + }, "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -9850,7 +11499,6 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -9866,7 +11514,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -9982,6 +11629,11 @@ "delayed-stream": "~1.0.0" } }, + "comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" + }, "commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", @@ -10107,6 +11759,11 @@ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "dev": true }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" + }, "csstype": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", @@ -10142,6 +11799,14 @@ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, + "decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "requires": { + "character-entities": "^2.0.0" + } + }, "decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -10202,16 +11867,25 @@ "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" + }, "detect-libc": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" }, + "didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" }, "dir-glob": { "version": "3.0.1", @@ -10222,6 +11896,11 @@ "path-type": "^4.0.0" } }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -10385,7 +12064,6 @@ "version": "0.17.19", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", - "dev": true, "requires": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", @@ -10625,6 +12303,11 @@ } } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -10666,7 +12349,6 @@ "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -10679,7 +12361,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -10708,7 +12389,6 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "requires": { "reusify": "^1.0.4" } @@ -10722,6 +12402,11 @@ "pend": "~1.2.0" } }, + "fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -10752,7 +12437,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -10842,7 +12526,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "optional": true }, "fstream": { @@ -10885,8 +12568,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "gauge": { "version": "4.0.4", @@ -10988,7 +12670,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "requires": { "is-glob": "^4.0.3" } @@ -11049,7 +12730,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -11070,6 +12750,11 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, + "hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==" + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -11229,6 +12914,11 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, "inquirer": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", @@ -11278,16 +12968,27 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "requires": { "binary-extensions": "^2.0.0" } }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" + }, + "is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "requires": { + "has": "^1.0.3" + } + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -11298,7 +12999,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -11316,8 +13016,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-path-inside": { "version": "3.0.3", @@ -11364,6 +13063,11 @@ "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", "dev": true }, + "jiti": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", + "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==" + }, "js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", @@ -11373,8 +13077,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "peer": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { "version": "4.1.0", @@ -11536,6 +13239,11 @@ "json-buffer": "3.0.1" } }, + "kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==" + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -11552,6 +13260,16 @@ "type-check": "~0.4.0" } }, + "lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==" + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, "linkify-it": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", @@ -11600,7 +13318,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -11715,6 +13432,58 @@ } } }, + "mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "requires": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + } + }, + "mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "requires": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + } + }, + "mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "requires": { + "@types/mdast": "^3.0.0" + } + }, "mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", @@ -11740,14 +13509,223 @@ "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, + "micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "requires": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "requires": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "requires": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==" + }, + "micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==" + }, + "micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "requires": { + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "requires": { + "micromark-util-types": "^1.0.0" + } + }, + "micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "requires": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "requires": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==" + }, + "micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==" }, "micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, "requires": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -11962,6 +13940,11 @@ } } }, + "mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -11977,7 +13960,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "requires": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -12096,8 +14078,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-url": { "version": "6.1.0", @@ -12127,8 +14108,12 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==" }, "object-inspect": { "version": "1.12.3", @@ -12310,6 +14295,11 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, "path-to-regexp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", @@ -12328,16 +14318,99 @@ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==" + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==" + }, + "postcss": { + "version": "8.4.24", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", + "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "dependencies": { + "nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" + } + } + }, + "postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "requires": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "requires": { + "camelcase-css": "^2.0.1" + } + }, + "postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "requires": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + } + }, + "postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "requires": { + "postcss-selector-parser": "^6.0.11" + } + }, + "postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, "postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "peer": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "posthog-js": { + "version": "1.63.3", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.63.3.tgz", + "integrity": "sha512-Ob2x1ENxx/mWAdhLhc6ouKAph9LwmitBy7M+hiaFoS0T9g7OVL4lYdrJbODbYV+HX/HeO3bsnQwYO1QLPWm/YA==", + "requires": { + "fflate": "^0.4.1" + } }, "prebuild-install": { "version": "7.1.1", @@ -12391,6 +14464,28 @@ "retry": "^0.12.0" } }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "property-information": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==" + }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -12423,8 +14518,7 @@ "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, "quick-lru": { "version": "5.1.1", @@ -12484,6 +14578,28 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "react-markdown": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-8.0.7.tgz", + "integrity": "sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==", + "requires": { + "@types/hast": "^2.0.0", + "@types/prop-types": "^15.0.0", + "@types/unist": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^2.0.0", + "prop-types": "^15.0.0", + "property-information": "^6.0.0", + "react-is": "^18.0.0", + "remark-parse": "^10.0.0", + "remark-rehype": "^10.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0" + } + }, "react-redux": { "version": "8.0.5", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", @@ -12506,6 +14622,14 @@ "mute-stream": "~0.0.4" } }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "requires": { + "pify": "^2.3.0" + } + }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -12533,7 +14657,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -12569,6 +14692,27 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, + "remark-parse": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", + "requires": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + } + }, + "remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "requires": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -12579,6 +14723,16 @@ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, "resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -12615,8 +14769,7 @@ "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, "rimraf": { "version": "3.0.2", @@ -12641,6 +14794,14 @@ } } }, + "rollup": { + "version": "3.23.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.23.1.tgz", + "integrity": "sha512-ybRdFVHOoljGEFILHLd2g/qateqUdjE6YS41WXq4p3C/WwD3xtWxV4FYWETA1u9TeXQc5K8L8zHE5d/scOvrOQ==", + "requires": { + "fsevents": "~2.3.2" + } + }, "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -12651,7 +14812,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "requires": { "queue-microtask": "^1.2.2" } @@ -12673,6 +14833,14 @@ } } }, + "sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "requires": { + "mri": "^1.1.0" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -12809,6 +14977,16 @@ "socks": "^2.6.2" } }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, + "space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" + }, "spawn-command": { "version": "0.0.2-1", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", @@ -12877,6 +15055,14 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "style-to-object": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.1.tgz", + "integrity": "sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==", + "requires": { + "inline-style-parser": "0.1.1" + } + }, "styled-components": { "version": "5.3.9", "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.9.tgz", @@ -12912,6 +15098,40 @@ } } }, + "sucrase": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", + "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", + "requires": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12920,6 +15140,41 @@ "has-flag": "^4.0.0" } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "tailwindcss": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", + "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", + "requires": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + } + }, "tar": { "version": "6.1.13", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", @@ -13004,7 +15259,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "requires": { "any-promise": "^1.0.0" } @@ -13013,7 +15267,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "requires": { "thenify": ">= 3.1.0 < 4" } @@ -13053,7 +15306,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -13075,6 +15327,21 @@ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, + "trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" + }, + "trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==" + }, + "ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -13164,6 +15431,27 @@ "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true }, + "unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "requires": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" + } + } + }, "unique-filename": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", @@ -13180,6 +15468,54 @@ "imurmurhash": "^0.1.4" } }, + "unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==" + }, + "unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "requires": { + "@types/unist": "^2.0.0" + } + }, + "unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + } + }, + "unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + } + }, "universal-github-app-jwt": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.1.1.tgz", @@ -13250,6 +15586,48 @@ "dev": true, "peer": true }, + "uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "requires": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + } + }, + "vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "requires": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + } + }, + "vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "requires": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + } + }, + "vite": { + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz", + "integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==", + "requires": { + "esbuild": "^0.17.5", + "fsevents": "~2.3.2", + "postcss": "^8.4.23", + "rollup": "^3.21.0" + } + }, "vsce": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/vsce/-/vsce-2.15.0.tgz", @@ -13497,6 +15875,11 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==" + }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/extension/package.json b/extension/package.json index dd15157d..a3b82c1b 100644 --- a/extension/package.json +++ b/extension/package.json @@ -182,6 +182,8 @@ "@types/mocha": "^10.0.1", "@types/node": "16.x", "@types/node-fetch": "^2.6.2", + "@types/react-dom": "^18.2.4", + "@types/styled-components": "^5.1.26", "@types/vscode": "^1.74.0", "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.45.0", @@ -200,11 +202,16 @@ "@reduxjs/toolkit": "^1.9.3", "@segment/analytics-node": "^0.0.1-beta.16", "@styled-icons/heroicons-outline": "^10.47.0", + "@vitejs/plugin-react-swc": "^3.3.2", "axios": "^1.2.5", "highlight.js": "^11.7.0", "octokit": "^2.0.11", + "posthog-js": "^1.63.3", + "react-markdown": "^8.0.7", "react-redux": "^8.0.5", "strip-ansi": "^7.0.1", + "tailwindcss": "^3.3.2", + "vite": "^4.3.9", "vscode-languageclient": "^8.0.2", "ws": "^8.13.0" } diff --git a/extension/scripts/continuedev-0.1.1-py3-none-any.whl b/extension/scripts/continuedev-0.1.1-py3-none-any.whl index 043b0c06..c9e40ce3 100644 Binary files a/extension/scripts/continuedev-0.1.1-py3-none-any.whl and b/extension/scripts/continuedev-0.1.1-py3-none-any.whl differ diff --git a/extension/src/README.md b/extension/src/README.md index 76b96ea0..126a094a 100644 --- a/extension/src/README.md +++ b/extension/src/README.md @@ -2,47 +2,31 @@ ## How to get started with development -1. Clone the `continue` repo +1. Clone the Continue repo -2. Open a VS Code window with the `continue` repo +2. Open a VS Code window with the `continue` directory as your workspace -3. Package and then start the FastAPI server by following instructions outlined in `package/server/README.md` +3. Package and then start the FastAPI server by following instructions outlined in the `Continue Server` section of the `continuedev/README.md` -4. Open the `extension` sub-directory of the repo in a second VS Code window +4. Open a VS Code window with the `extension` directory as your workspace -5. Run `npm install` +5. From `continue/extension`, run `npm install` -6. Run `npm run clientgen` +6. Run `npm run full-package` -7. Run `cd react-app` +7. Open `src/activation/activate.ts` file (or any TypeScript file) -8. Run `npm run build` +8. Press `F5` on your keyboard to start `Run and Debug` mode -9. Run `cd ..` to return to `extension` directory +9. `cmd+shift+p` to look at developer console and select Continue commands -10. Then run `npm run compile` +10. Every time you make changes to the code, you need to run `npm run compile` -11. Open `src/activate.ts` file (or any TypeScript file) +11. If you run into a "command not found" error, try running `npm run rebuild` and then `npm run compile` -12. Press `F5` on your keyboard to start `Run and Debug` mode +## Alternative: Install from source -13. `cmd+shift+p` to look at developer console and select Continue commands - -14. Every time you make changes to the code, you need to run `npm run compile` - -15. If you run into a "command not found" error, try running `npm run rebuild` and then `npm run compile` - -## Alternative: Install a packaged version - -You should always have a packaged version installed in VS Code, because when Continue is broken you'll want a stable version to help you debug. There are four key commands in the `package.json`: - -1. `npm run package` will create a .vsix file in the `build/` folder that can then be installed. It is this same file that you can share with others who want to try the extension. - -2. `npm run install-extension` will install the extension to VS Code. You should then see it in your installed extensions in the VS Code sidebar. - -3. `npm run uninstall` will uninstall the extension. You don't always have to do this thanks to the reinstall command, but can be useful when you want to do so manually. - -4. `npm run reinstall` will go through the entire process of uninstalling the existing installed extension, rebuilding, and then installing the new version. You shouldn't be doing this every time you make a change to the extension, but rather when there is some significant update that you would like to make available to yourself (or if you happen to be debugging something which is specific to the packaged extension). +Update: directions to root README ## Background diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts index 170426e1..21f867b1 100644 --- a/extension/src/activation/environmentSetup.ts +++ b/extension/src/activation/environmentSetup.ts @@ -88,7 +88,7 @@ async function setupPythonEnv() { const createEnvCommand = [ `cd ${path.join(getExtensionUri().fsPath, "scripts")}`, `${pythonCmd} -m venv env`, - ].join(" && "); + ].join(" ; "); // Repeat until it is successfully created (sometimes it fails to generate the bin, need to try again) while (true) { -- cgit v1.2.3-70-g09d2 From 5cd4ff9d0183233bc4bc079e7c181372eef636e7 Mon Sep 17 00:00:00 2001 From: Ty Dunn Date: Mon, 5 Jun 2023 13:27:09 +0200 Subject: add transform to dlt pipeline reicpe --- .../recipes/AddTransformRecipe/README.md | 0 .../continuedev/recipes/AddTransformRecipe/main.py | 23 +++++ .../recipes/AddTransformRecipe/steps.py | 105 +++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 continuedev/src/continuedev/recipes/AddTransformRecipe/README.md create mode 100644 continuedev/src/continuedev/recipes/AddTransformRecipe/main.py create mode 100644 continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/README.md b/continuedev/src/continuedev/recipes/AddTransformRecipe/README.md new file mode 100644 index 00000000..e69de29b diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/main.py b/continuedev/src/continuedev/recipes/AddTransformRecipe/main.py new file mode 100644 index 00000000..974336cf --- /dev/null +++ b/continuedev/src/continuedev/recipes/AddTransformRecipe/main.py @@ -0,0 +1,23 @@ +from textwrap import dedent + +from ...core.main import Step +from ...core.sdk import ContinueSDK +from ...steps.core.core import WaitForUserInputStep +from ...steps.main import MessageStep +from .steps import SetupPipelineStep, ValidatePipelineStep + + +class AddTransformRecipe(Step): + hide: bool = True + + async def run(self, sdk: ContinueSDK): + await sdk.run_step( + MessageStep(message=dedent("""\ + This recipe will walk you through the process of adding a transform to a dlt pipeline for your chosen data source. With the help of Continue, you will: + - X + - Y + - Z""")) >> + WaitForUserInputStep(prompt="What API do you want to load data from?") >> + SetupPipelineStep(api_description="WeatherAPI.com API") >> + ValidatePipelineStep() + ) diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py b/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py new file mode 100644 index 00000000..c6059627 --- /dev/null +++ b/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py @@ -0,0 +1,105 @@ +from textwrap import dedent + +from ...steps.main import MessageStep +from ...core.sdk import Models +from ...core.observation import DictObservation +from ...models.filesystem_edit import AddFile +from ...core.main import Step +from ...core.sdk import ContinueSDK + + +""" +https://dlthub.com/docs/general-usage/resource#filter-transform-and-pivot-data + +Using chess pipeline we show how to add map and filter Python transforms. +Example: https://dlthub.com/docs/customizations/customizing-pipelines/renaming_columns +- dlt init chess duckdb +- python chess.py +- write a transform function: ideas for transform functions: using chess Python library decode the moves OR filter out certain games +- use add_map or add_filter +- run python and streamlit app +""" + +class SetupPipelineStep(Step): + hide: bool = True + name: str = "Setup dlt Pipeline" + + api_description: str # e.g. "I want to load data from the weatherapi.com API" + + async def describe(self, models: Models): + return dedent(f"""\ + This step will create a new dlt pipeline that loads data from an API, as per your request: + {self.api_description} + """) + + async def run(self, sdk: ContinueSDK): + source_name = (await sdk.models.gpt35()).complete( + f"Write a snake_case name for the data source described by {self.api_description}: ").strip() + 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', + f'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(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}) + + +class ValidatePipelineStep(Step): + hide: bool = True + + async def run(self, sdk: ContinueSDK): + source_name = sdk.history.last_observation().values["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 + """))) + + # 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) + ''') + + 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') -- cgit v1.2.3-70-g09d2 From 8d67b7ea38811957e23d3b566e5c0fce394588d5 Mon Sep 17 00:00:00 2001 From: Ty Dunn Date: Mon, 5 Jun 2023 17:59:35 +0200 Subject: more add transform progress --- .../recipes/AddTransformRecipe/README.md | 3 + .../continuedev/recipes/AddTransformRecipe/main.py | 11 +- .../recipes/AddTransformRecipe/steps.py | 194 +++++++++++++++++---- 3 files changed, 165 insertions(+), 43 deletions(-) (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/README.md b/continuedev/src/continuedev/recipes/AddTransformRecipe/README.md index e69de29b..9ad49a5f 100644 --- a/continuedev/src/continuedev/recipes/AddTransformRecipe/README.md +++ b/continuedev/src/continuedev/recipes/AddTransformRecipe/README.md @@ -0,0 +1,3 @@ +# AddTransformRecipe + +Uses the Chess.com API example to show how to add map and filter Python transforms to a dlt pipeline. \ No newline at end of file diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/main.py b/continuedev/src/continuedev/recipes/AddTransformRecipe/main.py index 974336cf..0fd96930 100644 --- a/continuedev/src/continuedev/recipes/AddTransformRecipe/main.py +++ b/continuedev/src/continuedev/recipes/AddTransformRecipe/main.py @@ -4,7 +4,7 @@ from ...core.main import Step from ...core.sdk import ContinueSDK from ...steps.core.core import WaitForUserInputStep from ...steps.main import MessageStep -from .steps import SetupPipelineStep, ValidatePipelineStep +from .steps import SetUpChessPipelineStep, AddTransformStep class AddTransformRecipe(Step): @@ -13,11 +13,12 @@ class AddTransformRecipe(Step): async def run(self, sdk: ContinueSDK): await sdk.run_step( MessageStep(message=dedent("""\ - This recipe will walk you through the process of adding a transform to a dlt pipeline for your chosen data source. With the help of Continue, you will: + This recipe will walk you through the process of adding a transform to a dlt pipeline that uses the chess.com API source. With the help of Continue, you will: - X - Y - Z""")) >> - WaitForUserInputStep(prompt="What API do you want to load data from?") >> - SetupPipelineStep(api_description="WeatherAPI.com API") >> - ValidatePipelineStep() + + SetUpChessPipelineStep() >> + WaitForUserInputStep(prompt="How do you want to transform the chess.com API data before loading it? For example, you could use the `python-chess` library to decode the moves or filter out certain games") >> + AddTransformStep() ) diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py b/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py index c6059627..8ab3eda1 100644 --- a/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py +++ b/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py @@ -11,7 +11,7 @@ from ...core.sdk import ContinueSDK """ https://dlthub.com/docs/general-usage/resource#filter-transform-and-pivot-data -Using chess pipeline we show how to add map and filter Python transforms. + Example: https://dlthub.com/docs/customizations/customizing-pipelines/renaming_columns - dlt init chess duckdb - python chess.py @@ -20,57 +20,44 @@ Example: https://dlthub.com/docs/customizations/customizing-pipelines/renaming_c - run python and streamlit app """ -class SetupPipelineStep(Step): +class SetUpChessPipelineStep(Step): hide: bool = True - name: str = "Setup dlt Pipeline" + name: str = "Setup Chess.com API dlt Pipeline" api_description: str # e.g. "I want to load data from the weatherapi.com API" async def describe(self, models: Models): return dedent(f"""\ - This step will create a new dlt pipeline that loads data from an API, as per your request: - {self.api_description} + This step will create a new dlt pipeline that loads data from the chess.com API. """) async def run(self, sdk: ContinueSDK): - source_name = (await sdk.models.gpt35()).complete( - f"Write a snake_case name for the data source described by {self.api_description}: ").strip() - filename = f'{source_name}.py' + + filename = 'chess.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', - f'dlt init {source_name} duckdb', + 'dlt init chess 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(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}) - - -class ValidatePipelineStep(Step): +class AddTransformStep(Step): hide: bool = True async def run(self, sdk: ContinueSDK): - source_name = sdk.history.last_observation().values["source_name"] + source_name = 'chess' 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 + This step will customize your resource function with a transform of your choice: + - Add a filter or map transformation depending on your request - Load the data into a local DuckDB instance - - Write a query to view the data + - Open up a Streamlit app for you to view the data """))) # test that the API call works @@ -86,19 +73,150 @@ class ValidatePipelineStep(Step): 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) - ''') + examples = dedent(f"""\ + + Task: Use either the `add_map` or `add_filter` function to transform the data. + + Below you will find some docs page that will help you understand this task. + + # Customize resources + ## Filter, transform and pivot data + + You can attach any number of transformations that are evaluated on item per item basis to your resource. The available transformation types: + + - map - transform the data item (resource.add_map) + - filter - filter the data item (resource.add_filter) + - yield map - a map that returns iterator (so single row may generate many rows - resource.add_yield_map) + + Example: We have a resource that loads a list of users from an api endpoint. We want to customize it so: + + we remove users with user_id == "me" + we anonymize user data + Here's our resource: + ```python + import dlt + + @dlt.resource(write_disposition="replace") + def users(): + ... + users = requests.get(...) + ... + yield users + ``` + + Here's our script that defines transformations and loads the data. + ```python + from pipedrive import users + + def anonymize_user(user_data): + user_data["user_id"] = hash_str(user_data["user_id"]) + user_data["user_email"] = hash_str(user_data["user_email"]) + return user_data + + # add the filter and anonymize function to users resource and enumerate + for user in users().add_filter(lambda user: user["user_id"] != "me").add_map(anonymize_user): + print(user) + ``` + + Here is a more complex example of a filter transformation: + + # Renaming columns + ## Renaming columns by replacing the special characters + + In the example below, we create a dummy source with special characters in the name. We then write a function that we intend to apply to the resource to modify its output (i.e. replacing the German umlaut): replace_umlauts_in_dict_keys. + ```python + import dlt + + # create a dummy source with umlauts (special characters) in key names (um) + @dlt.source + def dummy_source(prefix: str = None): + @dlt.resource + def dummy_data(): + for _ in range(100): + yield {f'Objekt_{_}':{'Größe':_, 'Äquivalenzprüfung':True}} + return dummy_data(), + + def replace_umlauts_in_dict_keys(d): + # Replaces umlauts in dictionary keys with standard characters. + umlaut_map = {'ä': 'ae', 'ö': 'oe', 'ü': 'ue', 'ß': 'ss', 'Ä': 'Ae', 'Ö': 'Oe', 'Ü': 'Ue'} + result = {} + for k, v in d.items(): + new_key = ''.join(umlaut_map.get(c, c) for c in k) + if isinstance(v, dict): + result[new_key] = replace_umlauts_in_dict_keys(v) + else: + result[new_key] = v + return result + + # We can add the map function to the resource + + # 1. Create an instance of the source so you can edit it. + data_source = dummy_source() + + # 2. Modify this source instance's resource + data_source = data_source.dummy_data().add_map(replace_umlauts_in_dict_keys) + + # 3. Inspect your result + for row in data_source: + print(row) + + # {'Objekt_0': {'Groesse': 0, 'Aequivalenzpruefung': True}} + # ... + ``` + + Here is a more complex example of a map transformation: + + # Pseudonymizing columns + ## Pseudonymizing (or anonymizing) columns by replacing the special characters + Pseudonymization is a deterministic way to hide personally identifiable info (PII), enabling us to consistently achieve the same mapping. If instead you wish to anonymize, you can delete the data, or replace it with a constant. In the example below, we create a dummy source with a PII column called "name", which we replace with deterministic hashes (i.e. replacing the German umlaut). + + ```python + import dlt + import hashlib + + @dlt.source + def dummy_source(prefix: str = None): + @dlt.resource + def dummy_data(): + for _ in range(3): + yield {'id':_, 'name': f'Jane Washington {_}'} + return dummy_data(), + + def pseudonymize_name(doc): + Pseudonmyisation is a deterministic type of PII-obscuring + Its role is to allow identifying users by their hash, without revealing the underlying info. + + # add a constant salt to generate + salt = 'WI@N57%zZrmk#88c' + salted_string = doc['name'] + salt + sh = hashlib.sha256() + sh.update(salted_string.encode()) + hashed_string = sh.digest().hex() + doc['name'] = hashed_string + return doc + + # run it as is + for row in dummy_source().dummy_data().add_map(pseudonymize_name): + print(row) + + #{'id': 0, 'name': '96259edb2b28b48bebce8278c550e99fbdc4a3fac8189e6b90f183ecff01c442'} + #{'id': 1, 'name': '92d3972b625cbd21f28782fb5c89552ce1aa09281892a2ab32aee8feeb3544a1'} + #{'id': 2, 'name': '443679926a7cff506a3b5d5d094dc7734861352b9e0791af5d39db5a7356d11a'} + + # Or create an instance of the data source, modify the resource and run the source. + + # 1. Create an instance of the source so you can edit it. + data_source = dummy_source() + # 2. Modify this source instance's resource + data_source = data_source.dummy_data().add_map(replace_umlauts_in_dict_keys) + # 3. Inspect your result + for row in data_source: + print(row) + + pipeline = dlt.pipeline(pipeline_name='example', destination='bigquery', dataset_name='normalized_data') + load_info = pipeline.run(data_source) + ``` + """) query_filename = (await sdk.ide.getWorkspaceDirectory()) + "/query.py" await sdk.apply_filesystem_edit(AddFile(filepath=query_filename, content=tables_query_code)) -- cgit v1.2.3-70-g09d2 From 9613750fd1ff48343e8d80ce5154733a4dbc55d7 Mon Sep 17 00:00:00 2001 From: Nate Sesti Date: Mon, 5 Jun 2023 12:09:13 -0400 Subject: Enter button on user input --- continuedev/src/continuedev/core/policy.py | 5 +- .../recipes/CreatePipelineRecipe/main.py | 9 ++- continuedev/src/continuedev/steps/core/core.py | 7 +- .../react-app/src/components/ContinueButton.tsx | 8 ++- .../react-app/src/components/InputAndButton.tsx | 77 ++++++++++++++++++++++ .../react-app/src/components/StepContainer.tsx | 20 ++---- extension/react-app/src/tabs/gui.tsx | 6 +- 7 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 extension/react-app/src/components/InputAndButton.tsx (limited to 'continuedev/src') diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py index 4287bb6e..e65b6c9d 100644 --- a/continuedev/src/continuedev/core/policy.py +++ b/continuedev/src/continuedev/core/policy.py @@ -17,7 +17,10 @@ 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(message="Welcome to Continue!") >> + SetupContinueWorkspaceStep() >> + CreateCodebaseIndexChroma() >> + StepsOnStartupStep()) observation = history.get_current().observation if observation is not None and isinstance(observation, UserInputObservation): diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py index 4a4604d6..55c25da4 100644 --- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py +++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py @@ -11,7 +11,7 @@ class CreatePipelineRecipe(Step): hide: bool = True async def run(self, sdk: ContinueSDK): - await sdk.run_step( + text_observation = await sdk.run_step( MessageStep(message=dedent("""\ This recipe will walk you through the process of creating a dlt pipeline for your chosen data source. With the help of Continue, you will: - Create a Python virtual environment with dlt installed @@ -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?") + ) + await sdk.run_step( + SetupPipelineStep(api_description=text_observation.text) >> ValidatePipelineStep() ) diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py index d7f7a307..04446787 100644 --- a/continuedev/src/continuedev/steps/core/core.py +++ b/continuedev/src/continuedev/steps/core/core.py @@ -191,13 +191,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 self.prompt + "\n\n" + self._response async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]: self._description = self.prompt resp = await sdk.wait_for_user_input() + self._response = resp return TextObservation(text=resp) diff --git a/extension/react-app/src/components/ContinueButton.tsx b/extension/react-app/src/components/ContinueButton.tsx index 11dc7a92..c6117bf9 100644 --- a/extension/react-app/src/components/ContinueButton.tsx +++ b/extension/react-app/src/components/ContinueButton.tsx @@ -24,9 +24,13 @@ let StyledButton = styled(Button)` } `; -function ContinueButton(props: { onClick?: () => void }) { +function ContinueButton(props: { onClick?: () => void; hidden?: boolean }) { return ( - +