summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.devcontainer/devcontainer.json6
-rw-r--r--README.md39
-rw-r--r--continuedev/README.md24
-rw-r--r--continuedev/poetry.lock50
-rw-r--r--continuedev/pyproject.toml4
-rw-r--r--continuedev/src/continuedev/core/autopilot.py186
-rw-r--r--continuedev/src/continuedev/core/config.py33
-rw-r--r--continuedev/src/continuedev/core/env.py30
-rw-r--r--continuedev/src/continuedev/core/main.py169
-rw-r--r--continuedev/src/continuedev/core/observation.py (renamed from continuedev/src/continuedev/libs/observation.py)0
-rw-r--r--continuedev/src/continuedev/core/policy.py (renamed from continuedev/src/continuedev/libs/policy.py)34
-rw-r--r--continuedev/src/continuedev/core/sdk.py140
-rw-r--r--continuedev/src/continuedev/libs/core.py423
-rw-r--r--continuedev/src/continuedev/libs/llm/hf_inference_api.py25
-rw-r--r--continuedev/src/continuedev/libs/llm/openai.py10
-rw-r--r--continuedev/src/continuedev/libs/steps/chroma.py11
-rw-r--r--continuedev/src/continuedev/libs/steps/continue_step.py37
-rw-r--r--continuedev/src/continuedev/libs/steps/core/core.py208
-rw-r--r--continuedev/src/continuedev/libs/steps/draft/abstract_method.py3
-rw-r--r--continuedev/src/continuedev/libs/steps/draft/dlt.py53
-rw-r--r--continuedev/src/continuedev/libs/steps/draft/redux.py7
-rw-r--r--continuedev/src/continuedev/libs/steps/draft/typeorm.py3
-rw-r--r--continuedev/src/continuedev/libs/steps/main.py351
-rw-r--r--continuedev/src/continuedev/libs/steps/migration.py4
-rw-r--r--continuedev/src/continuedev/libs/steps/nate.py15
-rw-r--r--continuedev/src/continuedev/libs/steps/pytest.py4
-rw-r--r--continuedev/src/continuedev/libs/steps/react_posthog.py0
-rw-r--r--continuedev/src/continuedev/libs/steps/steps_on_startup.py30
-rw-r--r--continuedev/src/continuedev/libs/steps/ty.py10
-rw-r--r--continuedev/src/continuedev/libs/util/telemetry.py12
-rw-r--r--continuedev/src/continuedev/models/filesystem_edit.py4
-rw-r--r--continuedev/src/continuedev/models/generate_json_schema.py11
-rw-r--r--continuedev/src/continuedev/models/main.py39
-rw-r--r--continuedev/src/continuedev/server/gui.py130
-rw-r--r--continuedev/src/continuedev/server/gui_protocol.py28
-rw-r--r--continuedev/src/continuedev/server/ide.py62
-rw-r--r--continuedev/src/continuedev/server/ide_protocol.py12
-rw-r--r--continuedev/src/continuedev/server/main.py11
-rw-r--r--continuedev/src/continuedev/server/notebook.py198
-rw-r--r--continuedev/src/continuedev/server/session_manager.py86
-rw-r--r--docs/docs/concepts/autopilot.md2
-rw-r--r--docs/docs/concepts/ide.md12
-rw-r--r--extension/.vscodeignore16
-rw-r--r--extension/DEV_README.md7
-rw-r--r--extension/LICENSE.txt18
-rw-r--r--extension/README.md21
-rw-r--r--extension/examples/README.md3
-rw-r--r--extension/logging.yaml30
-rw-r--r--extension/media/continue-gradient.pngbin0 -> 13218 bytes
-rw-r--r--extension/package-lock.json598
-rw-r--r--extension/package.json45
-rw-r--r--extension/react-app/README.md3
-rw-r--r--extension/react-app/package-lock.json39
-rw-r--r--extension/react-app/package.json1
-rw-r--r--extension/react-app/src/App.tsx6
-rw-r--r--extension/react-app/src/components/CodeBlock.tsx3
-rw-r--r--extension/react-app/src/components/DebugPanel.tsx4
-rw-r--r--extension/react-app/src/components/StepContainer.tsx31
-rw-r--r--extension/react-app/src/components/index.ts2
-rw-r--r--extension/react-app/src/hooks/ContinueGUIClientProtocol.ts13
-rw-r--r--extension/react-app/src/hooks/messenger.ts108
-rw-r--r--extension/react-app/src/hooks/useContinueGUIProtocol.ts49
-rw-r--r--extension/react-app/src/hooks/useWebsocket.ts82
-rw-r--r--extension/react-app/src/hooks/vscodeMessenger.ts71
-rw-r--r--extension/react-app/src/index.css2
-rw-r--r--extension/react-app/src/main.tsx25
-rw-r--r--extension/react-app/src/tabs/chat/MessageDiv.tsx3
-rw-r--r--extension/react-app/src/tabs/gui.tsx (renamed from extension/react-app/src/tabs/notebook.tsx)90
-rw-r--r--extension/react-app/src/vscode/index.ts1
-rw-r--r--extension/react-app/tsconfig.json2
-rw-r--r--extension/schema/History.d.ts41
-rw-r--r--extension/schema/HistoryNode.d.ts31
-rw-r--r--extension/schema/README.md3
-rw-r--r--extension/scripts/continuedev-0.1.0-py3-none-any.whlbin61142 -> 55662 bytes
-rw-r--r--extension/scripts/install_from_source.py63
-rw-r--r--extension/src/activation/activate.ts12
-rw-r--r--extension/src/activation/environmentSetup.ts154
-rw-r--r--extension/src/commands.ts4
-rw-r--r--extension/src/continueIdeClient.ts150
-rw-r--r--extension/src/debugPanel.ts111
-rw-r--r--extension/src/extension.ts7
-rw-r--r--extension/src/test/runTest.ts30
-rw-r--r--extension/src/util/messenger.ts108
-rw-r--r--logging.ini27
-rw-r--r--schema/json/FileEdit.json4
-rw-r--r--schema/json/FileEditWithFullContents.json4
-rw-r--r--schema/json/History.json73
-rw-r--r--schema/json/HistoryNode.json51
-rw-r--r--schema/json/Position.json4
-rw-r--r--schema/json/Range.json4
-rw-r--r--schema/json/RangeInFile.json4
-rw-r--r--schema/json/Traceback.json4
-rw-r--r--schema/json/TracebackFrame.json4
-rw-r--r--schema/openapi.json1027
94 files changed, 4367 insertions, 1342 deletions
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 00000000..be682b50
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,6 @@
+{
+ "image": "mcr.microsoft.com/devcontainers/universal:2",
+ "features": {
+ "postCreateCommand": "curl -sSL https://install.python-poetry.org | python3 -"
+ }
+}
diff --git a/README.md b/README.md
index 18525fcd..0e397b23 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,41 @@
+![GitHub issues](https://img.shields.io/github/issues-raw/continuedev/continue)
+[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
+![GitHub Repo stars](https://img.shields.io/github/stars/continuedev/continue?style=social)
+![Twitter URL](https://img.shields.io/twitter/url?style=social&url=https%3A%2F%2Fgithub.com%2Fcontinuedev%2Fcontinue)
+
# Continue
-Try out latest version:
+Continue is the open-source autopilot for software developers. Using our SDK you can write short scripts, called recipes, that automate sequences of tasks that are common within your codebase. This repository contains:
+
+- The Continue Python package, which offers an SDK to write recipes and the Continue server, which runs all automations
+- The Continue VSCode extension, where you can natively run recipes and use natural language to refine code much faster
+
+## Getting Started
+
+The easiest way to start using Continue is to download our VS Code extension from the marketplace:
+
+[Download for VS Code](https://marketplace.visualstudio.com/items?itemName=Continue.continue)
+
+Alternatively, you can build from source. Just clone the repo and run a Python script:
-- `cd extension`
-- `npm run package`
-- `cd build`
-- `code --install-extension continue-0.0.1.vsix`
+```bash
+git clone https://github.com/continuedev/continue && cd continue/extension/scripts && python3 install_from_source.py
+```
-Resources
+## Writing Recipes
+
+See our guides on writing recipes in the documentation.
+
+# Resources
- [Continue Github Project](https://github.com/orgs/continuedev/projects/1/views/1)
- [Continue User Guide](https://www.notion.so/continue-dev/Continue-User-Guide-1c6ad99887d0474d9e42206f6c98efa4)
- [Continue - Mission & Vision](https://continue-dev.notion.site/Continue-a8e41af9801641f79d2c8565907bbd22)
+
+# Contributing
+
+Please do
+
+# License
+
+[Apache-2.0]([https://github.com/sestinj/the-x/blob/main/LICENSE](https://opensource.org/licenses/Apache-2.0)) © 2023 Continue
diff --git a/continuedev/README.md b/continuedev/README.md
index 5af08e24..a1ddb084 100644
--- a/continuedev/README.md
+++ b/continuedev/README.md
@@ -1,19 +1,23 @@
-## Steps to start
+# Continue PyPI Package
-- `cd continue/continue`
+Continue is a Python library for automating repetitive sequences of software development tasks using language models. Using our VS Code extension, you can build, run, and refine these recipes as they natively interact with your codebase. Download on [our GitHub](https://github.com/continuedev/continue).
+
+## 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`
-
-## Steps to generate JSON Schema
+- `python3 -m continuedev.src.continuedev.server.main`
-Same up until last step and then `python3 -m continuedev.src.scripts.gen_json_schema`.
+## Scripts
-## Start the server
+`poetry run typegen` to generate JSONSchema .json files from the Pydantic types defined in the `models` directory.
-Same steps, then `uvicorn continue.src.server.main:app --reload`.
+`poetry build` will output wheel and tarball files in `./dist`.
-## To build
+## Writing Steps
-Run `poetry build` and it will output wheel and tarball files in `./dist`.
+See the `src/continuedev/libs/steps` folder for examples of writing a Continue step. See our documentation for tutorials.
diff --git a/continuedev/poetry.lock b/continuedev/poetry.lock
index 810560b6..857a7c99 100644
--- a/continuedev/poetry.lock
+++ b/continuedev/poetry.lock
@@ -177,6 +177,18 @@ 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"
description = "When they're not builtins, they're boltons."
@@ -632,6 +644,18 @@ files = [
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"
description = "multidict implementation"
@@ -939,6 +963,30 @@ test = ["hypothesis (>=6.34.2)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.17.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"
description = "Data validation and settings management using python type hints"
@@ -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 5c224c9c..0dc4d267 100644
--- a/continuedev/pyproject.toml
+++ b/continuedev/pyproject.toml
@@ -19,6 +19,10 @@ 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"
[build-system]
requires = ["poetry-core"]
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/core/config.py b/continuedev/src/continuedev/core/config.py
new file mode 100644
index 00000000..8ed41a82
--- /dev/null
+++ b/continuedev/src/continuedev/core/config.py
@@ -0,0 +1,33 @@
+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
+ 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:
+ 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/env.py b/continuedev/src/continuedev/core/env.py
new file mode 100644
index 00000000..2692c348
--- /dev/null
+++ b/continuedev/src/continuedev/core/env.py
@@ -0,0 +1,30 @@
+from dotenv import load_dotenv
+import os
+
+
+def get_env_var(var_name: str):
+ load_dotenv()
+ return os.getenv(var_name)
+
+
+def make_sure_env_exists():
+ if not os.path.exists('.env'):
+ with open('.env', 'w') as f:
+ 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:
+ 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/main.py b/continuedev/src/continuedev/core/main.py
new file mode 100644
index 00000000..a2336671
--- /dev/null
+++ b/continuedev/src/continuedev/core/main.py
@@ -0,0 +1,169 @@
+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 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:
+ 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_last_at_same_depth()
+ 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 Models:
+ 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, models: Models) -> 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 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/observation.py b/continuedev/src/continuedev/core/observation.py
index fef04311..fef04311 100644
--- a/continuedev/src/continuedev/libs/observation.py
+++ b/continuedev/src/continuedev/core/observation.py
diff --git a/continuedev/src/continuedev/libs/policy.py b/continuedev/src/continuedev/core/policy.py
index 586eaebe..6e264bab 100644
--- a/continuedev/src/continuedev/libs/policy.py
+++ b/continuedev/src/continuedev/core/policy.py
@@ -1,19 +1,24 @@
from typing import List, Tuple, Type
-from .steps.ty import CreatePipelineStep
-from .core import Step, Validator, Policy, History
+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 .steps.main import EditCodeStep, EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep
-from .steps.nate import WritePytestsStep, CreateTableStep
-from .steps.chroma import AnswerQuestionChroma, EditFileChroma
+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
class DemoPolicy(Policy):
ran_code_last: bool = False
- cmd: str
def next(self, history: History) -> Step:
- observation = history.last_observation()
+ # 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.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():
@@ -22,16 +27,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:]))
- return EditHighlightedCodeStep(user_input=observation.user_input)
+ # 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
new file mode 100644
index 00000000..af7754cc
--- /dev/null
+++ b/continuedev/src/continuedev/core/sdk.py
@@ -0,0 +1,140 @@
+import os
+from typing import Coroutine, Union
+
+from .config import ContinueConfig, load_config
+from ..models.filesystem_edit import FileEdit, FileSystemEdit, AddFile, DeleteFile, AddDirectory, DeleteDirectory
+from ..models.filesystem import RangeInFile
+from ..libs.llm import LLM
+from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI
+from ..libs.llm.openai import OpenAI
+from .observation import Observation
+from ..server.ide_protocol import AbstractIdeProtocolServer
+from .main import History, Step
+from ..libs.steps.core.core import *
+from .env import get_env_var, make_sure_env_exists
+
+
+class Autopilot:
+ pass
+
+
+class ContinueSDKSteps:
+ def __init__(self, sdk: "ContinueSDK"):
+ 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 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 add your OpenAI API key to the .env file')
+ return OpenAI(api_key=api_key, default_model="gpt-3.5-turbo")
+
+
+class ContinueSDK:
+ """The SDK provided as parameters to a step"""
+ ide: AbstractIdeProtocolServer
+ steps: ContinueSDKSteps
+ models: Models
+ __autopilot: Autopilot
+
+ 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.__autopilot.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.__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.__autopilot.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 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)
+
+ await self.ide.setFileOpen(filepath)
+ contents = await self.ide.readFile(filepath)
+ await self.run_step(EditCodeStep(
+ range_in_files=[RangeInFile.from_entire_file(filepath, contents)],
+ prompt=f'Here is the code before:\n\n{{code}}\n\nHere is the user request:\n\n{prompt}\n\nHere is the code edited to perfectly solve the user request:\n\n'
+ ))
+
+ 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)))
+
+ 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)))
+
+ 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}="<ENTER SECRET HERE>"')
+ await self.run_step(WaitForUserConfirmationStep(prompt=prompt))
+ val = get_env_var(env_var)
+
+ 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.__autopilot.set_loading_message(message)
+ raise NotImplementedError()
diff --git a/continuedev/src/continuedev/libs/core.py b/continuedev/src/continuedev/libs/core.py
deleted file mode 100644
index 36d94ad1..00000000
--- a/continuedev/src/continuedev/libs/core.py
+++ /dev/null
@@ -1,423 +0,0 @@
-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
- __autopilot: "Autopilot"
-
- def __init__(self, autopilot: "Autopilot", llm: Union[LLM, None] = None):
- if llm is None:
- self.llm = autopilot.llm
- else:
- self.llm = llm
- self.ide = autopilot.ide
- self.__autopilot = autopilot
-
- @property
- def history(self) -> History:
- return self.__autopilot.history
-
- async def run_step(self, step: "Step") -> Coroutine[Observation, None, None]:
- return await self.__autopilot._run_singular_step(step)
-
- async def apply_filesystem_edit(self, edit: FileSystemEdit):
- await self.run_step(FileSystemEditStep(edit=edit))
-
- async def wait_for_user_input(self) -> str:
- return await self.__autopilot.wait_for_user_input()
-
-
-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()
- 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("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))
-
-
-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/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/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/libs/steps/chroma.py b/continuedev/src/continuedev/libs/steps/chroma.py
index 2d8742e8..39424c5c 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):
@@ -41,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/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/core/core.py b/continuedev/src/continuedev/libs/steps/core/core.py
new file mode 100644
index 00000000..9a5d54f0
--- /dev/null
+++ b/continuedev/src/continuedev/libs/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 ...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
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..9cec5014 100644
--- a/continuedev/src/continuedev/libs/steps/draft/dlt.py
+++ b/continuedev/src/continuedev/libs/steps/draft/dlt.py
@@ -1,15 +1,29 @@
from textwrap import dedent
+
+from ....core.sdk import Models
+
+from ....core.observation import DictObservation
from ....models.filesystem_edit import AddFile
-from ...core import Step, ContinueSDK
-from ..main import WaitForUserInputStep
+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 = 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'
@@ -18,7 +32,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'
])
@@ -30,15 +44,16 @@ class SetupPipelineStep(Step):
)
# 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}
+ 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()["source_name"]
+ source_name = sdk.history.last_observation().values["source_name"]
filename = f'{source_name}.py'
# test that the API call works
@@ -67,15 +82,33 @@ 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')
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() >>
+ 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
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 70953e95..73ad3352 100644
--- a/continuedev/src/continuedev/libs/steps/main.py
+++ b/continuedev/src/continuedev/libs/steps/main.py
@@ -1,18 +1,46 @@
-import time
-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
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 Step
+from ...core.sdk import ContinueSDK, Models
+from ...core.observation import 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, 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):
@@ -31,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
@@ -51,167 +79,53 @@ class RunCommandStep(Step):
return TextObservation(text=stdout)
-class WaitForUserInputStep(Step):
- prompt: str
- name: str = "Waiting for user input"
-
- _description: Union[str, None] = None
-
- async def describe(self, llm: LLM) -> Coroutine[str, None, None]:
- return self.prompt
-
- async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
- self._description = self.prompt
- resp = await sdk.wait_for_user_input()
- return TextObservation(text=resp)
-
-
-class WaitForUserConfirmationStep(Step):
- prompt: str
- name: str = "Waiting for user confirmation"
-
- async def describe(self, llm: LLM) -> Coroutine[str, None, None]:
- return self.prompt
-
- async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
- self._description = self.prompt
- resp = await sdk.wait_for_user_input()
- return TextObservation(text=resp)
-
-
-class RunCodeStep(Step):
- cmd: str
-
- async def describe(self, llm: LLM) -> Coroutine[str, None, None]:
- return f"Ran command: `{self.cmd}`"
-
- async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
- result = subprocess.run(
- self.cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- stdout = result.stdout.decode("utf-8")
- stderr = result.stderr.decode("utf-8")
- print(stdout, stderr)
-
- # If it fails, return the error
- tb = parse_python_traceback(stdout) or parse_python_traceback(stderr)
- if tb:
- return TracebackObservation(traceback=tb)
- else:
- self.hide = True
- return None
-
-
-class EditCodeStep(Step):
- # Might make an even more specific atomic step, which is "apply file edit"
- range_in_files: List[RangeInFile]
- prompt: str # String with {code} somewhere
- name: str = "Edit code"
-
- _edit_diffs: Union[List[EditDiff], None] = None
- _prompt: Union[str, None] = None
- _completion: Union[str, None] = None
-
- async def describe(self, llm: LLM) -> Coroutine[str, None, None]:
- if self._edit_diffs is None:
- return "Editing files: " + ", ".join(map(lambda rif: rif.filepath, self.range_in_files))
- elif len(self._edit_diffs) == 0:
- return "No edits made"
- else:
- return llm.complete(dedent(f"""{self._prompt}{self._completion}
-
- Maximally concise summary of changes in bullet points (can use markdown):
- """))
-
- async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
- rif_with_contents = []
- for range_in_file in self.range_in_files:
- file_contents = await sdk.ide.readRangeInFile(range_in_file)
- rif_with_contents.append(
- RangeInFileWithContents.from_range_in_file(range_in_file, file_contents))
- enc_dec = MarkdownStyleEncoderDecoder(rif_with_contents)
- code_string = enc_dec.encode()
- prompt = self.prompt.format(code=code_string)
-
- completion = sdk.llm.complete(prompt)
-
- # Temporarily doing this to generate description.
- self._prompt = prompt
- self._completion = completion
-
- file_edits = enc_dec.decode(completion)
-
- self._edit_diffs = []
- for file_edit in file_edits:
- diff = await sdk.apply_filesystem_edit(file_edit)
- self._edit_diffs.append(diff)
-
- for filepath in set([file_edit.filepath for file_edit in file_edits]):
- await sdk.ide.saveFile(filepath)
- await sdk.ide.setFileOpen(filepath)
-
- return None
-
-
-class EditFileStep(Step):
- filepath: str
- prompt: str
- hide: bool = True
-
- async def describe(self, llm: LLM) -> Coroutine[str, None, None]:
- return "Editing file: " + self.filepath
-
- async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
- file_contents = await sdk.ide.readFile(self.filepath)
- await sdk.run_step(EditCodeStep(
- range_in_files=[RangeInFile.from_entire_file(
- self.filepath, file_contents)],
- prompt=self.prompt
- ))
-
-
class FasterEditHighlightedCodeStep(Step):
user_input: str
hide = True
_completion: str = "Edit Code"
_edit_diffs: Union[List[EditDiff], None] = None
- _prompt: str = dedent("""Below is the code before changes:
-
-{code}
-
-This is the user request:
-
-{user_input}
-
-Edit the code to perfectly satifsfy the user request. Format the changes you want to make as a comma-separated array of JSON objects of the form:
-{{
- "edits": [{{
- "filepath": <FILEPATH>,
- "replace_me": <CODE_TO_REPLACE>,
- "replace_with": <CODE_TO_REPLACE_WITH>
- }}]
-}}
-
-For example, if you want to replace the code `x = 1` with `x = 2` in main.py, you would write:
-{{
- "edits": [{{
- "filepath": "main.py",
- "replace_me": "x = 1",
- "replace_with": "x = 2"
- }}]
-}}
-If you wanted to delete the code `def sum(a, b):\\n return a + b` in main.py, you would write:
-{{
- "edits": [{{
- "filepath": "main.py",
- "replace_me": "def sum(a, b):\\n return a + b",
- "replace_with": ""
- }}]
-}}
-
-Respond with only as many edits as needed, and output only the list of json objects, no other text.
+ _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
+ <FILE_TO_EDIT>
+ REPLACE_ME
+ <CODE_TO_REPLACE>
+ REPLACE_WITH
+ <CODE_TO_REPLACE_WITH>
+
+ where <CODE_TO_REPLACE> and <CODE_TO_REPLACE_WITH> 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]:
+ async def describe(self, models: Models) -> Coroutine[str, None, None]:
return "Editing highlighted code"
async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
@@ -240,21 +154,51 @@ Respond with only as many edits as needed, and output only the list of json obje
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
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"]
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 = []
@@ -269,6 +213,54 @@ Respond with only as many edits as needed, and output only the list of json obje
return None
+class StarCoderEditHighlightedCodeStep(Step):
+ user_input: str
+ hide = False
+ _prompt: str = "<commit_before>{code}<commit_msg>{user_request}<commit_after>"
+
+ _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
@@ -283,7 +275,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]:
@@ -305,7 +297,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]:
@@ -319,7 +311,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]:
@@ -343,3 +335,24 @@ class SolveTracebackStep(Step):
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
index 04296836..7b70422d 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):
@@ -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 80436fa4..2f84e9d7 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
@@ -46,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
@@ -170,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 e53eb465..2e83ae2d 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
@@ -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/react_posthog.py b/continuedev/src/continuedev/libs/steps/react_posthog.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/continuedev/src/continuedev/libs/steps/react_posthog.py
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/libs/steps/ty.py b/continuedev/src/continuedev/libs/steps/ty.py
index 1eb6271d..9dde7c86 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"
@@ -16,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/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)
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/generate_json_schema.py b/continuedev/src/continuedev/models/generate_json_schema.py
index da78dfac..080787a5 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,13 +11,15 @@ MODELS_TO_GENERATE = [
RangeInFile, FileEdit
] + [
FileEditWithFullContents
+] + [
+ History, HistoryNode
]
RENAMES = {
"ExampleClass": "RenamedName"
}
-SCHEMA_DIR = "schema/json"
+SCHEMA_DIR = "../schema/json"
def clear_schemas():
@@ -25,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__)
@@ -37,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/continuedev/src/continuedev/models/main.py b/continuedev/src/continuedev/models/main.py
index 081ec4af..02c44aae 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
@@ -27,13 +32,17 @@ 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
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."""
@@ -88,6 +97,34 @@ 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)
+
+ @staticmethod
+ def from_position(position: Position) -> "Range":
+ return Range(start=position, end=position)
+
class AbstractModel(ABC, BaseModel):
@root_validator(pre=True)
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 167d9483..71017ce0 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
@@ -11,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
@@ -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":
- await self.openNotebook()
- elif t == "setFileOpen":
+ async def handle_json(self, message_type: str, data: Any):
+ if message_type == "openGUI":
+ await self.openGUI()
+ 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):
# Autopilot needs access to this.
- await self.websocket.send_json({
- "messageType": "setFileOpen",
+ await self._send_json("setFileOpen", {
"filepath": filepath,
"open": open
})
- async def openNotebook(self):
+ async def openGUI(self):
session_id = self.session_manager.new_session(self)
- await self._send_json({
- "messageType": "openNotebook",
+ await self._send_json("openGUI", {
"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]
})
@@ -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]):
@@ -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/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 11ad1d8f..7b7124de 100644
--- a/continuedev/src/continuedev/server/main.py
+++ b/continuedev/src/continuedev/server/main.py
@@ -1,14 +1,15 @@
+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(
@@ -32,7 +33,11 @@ args = parser.parse_args()
def run_server():
- uvicorn.run(app, host="0.0.0.0", port=args.port, log_config="logging.ini")
+ 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
deleted file mode 100644
index c26920f5..00000000
--- a/continuedev/src/continuedev/server/notebook.py
+++ /dev/null
@@ -1,198 +0,0 @@
-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 Autopilot, 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
- autopilot: Autopilot
- ws: Union[WebSocket, None]
-
- def __init__(self, session_id: str, autopilot: Autopilot):
- self.session_id = session_id
- self.autopilot = autopilot
- self.ws = None
-
-
-class DemoAutopilot(Autopilot):
- 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"
- 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, autopilot=autopilot)
- self.sessions[session_id] = session
-
- def on_update(state: FullState):
- session_manager.send_ws_data(session_id, {
- "messageType": "state",
- "state": autopilot.get_full_state().dict()
- })
-
- autopilot.on_update(on_update)
- asyncio.create_task(autopilot.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).autopilot.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.autopilot.accept_user_input(data["value"]))
- elif messageType == "step_user_input":
- asyncio.create_task(
- session.autopilot.give_user_input(data["value"], data["index"]))
- elif messageType == "refinement_input":
- asyncio.create_task(
- session.autopilot.accept_refinement_input(data["value"], data["index"]))
- elif messageType == "reverse":
- # Reverse the history to the given index
- asyncio.create_task(
- session.autopilot.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 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.autopilot.history
-
-
-@router.post("/observation")
-def post_observation(observation: Observation, session=Depends(session)):
- asyncio.create_task(session.autopilot.run_from_observation(observation))
- return "Success"
diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py
new file mode 100644
index 00000000..5598e140
--- /dev/null
+++ b/continuedev/src/continuedev/server/session_manager.py
@@ -0,0 +1,86 @@
+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.autopilot import Autopilot
+from ..libs.steps.nate import ImplementAbstractMethodStep
+from .ide_protocol import AbstractIdeProtocolServer
+import asyncio
+import nest_asyncio
+nest_asyncio.apply()
+
+
+class Session:
+ session_id: str
+ autopilot: Autopilot
+ ws: Union[WebSocket, None]
+
+ def __init__(self, session_id: str, autopilot: Autopilot):
+ self.session_id = session_id
+ self.autopilot = autopilot
+ self.ws = None
+
+
+class DemoAutopilot(Autopilot):
+ 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:
+ autopilot = DemoAutopilot(policy=DemoPolicy(), ide=ide)
+ session_id = str(uuid4())
+ 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": autopilot.get_full_state().dict()
+ })
+
+ autopilot.on_update(on_update)
+ asyncio.create_task(autopilot.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
+
+ await self.sessions[session_id].ws.send_json({
+ "messageType": message_type,
+ "data": data
+ })
+
+
+session_manager = SessionManager()
diff --git a/docs/docs/concepts/autopilot.md b/docs/docs/concepts/autopilot.md
index f3ed6fc9..71090eb0 100644
--- a/docs/docs/concepts/autopilot.md
+++ b/docs/docs/concepts/autopilot.md
@@ -21,7 +21,7 @@ The Autopilot class is the center of Continue. Every step is initiated from the
---
- An autopilot takes user input from the React app
-- You can see this happening in `server/notebook.py`
+- 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
diff --git a/docs/docs/concepts/ide.md b/docs/docs/concepts/ide.md
index dc20518e..1816d09b 100644
--- a/docs/docs/concepts/ide.md
+++ b/docs/docs/concepts/ide.md
@@ -41,9 +41,9 @@ Get the workspace directory
Set whether a file is open
-### openNotebook
+### openGUI
-Open a notebook
+Open a gui
### showSuggestionsAndWait
@@ -61,13 +61,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
diff --git a/extension/.vscodeignore b/extension/.vscodeignore
index 8fddc39a..c1a09172 100644
--- a/extension/.vscodeignore
+++ b/extension/.vscodeignore
@@ -1,5 +1,6 @@
.vscode/**
.vscode-test/**
+build/**
src/**
.gitignore
.yarnrc
@@ -8,4 +9,17 @@ vsc-extension-quickstart.md
**/.eslintrc.json
**/*.map
**/*.ts
-scripts/env/** \ No newline at end of file
+scripts/env/**
+scripts/.env
+**/.env
+**/env
+**/node_modules
+
+react-app/node_modules
+react-app/public
+react-app/src
+
+**/.pytest_cache
+**/__pycache__
+
+scripts/data \ No newline at end of file
diff --git a/extension/DEV_README.md b/extension/DEV_README.md
new file mode 100644
index 00000000..7049da45
--- /dev/null
+++ b/extension/DEV_README.md
@@ -0,0 +1,7 @@
+# Continue VS Code Extension
+
+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).
diff --git a/extension/LICENSE.txt b/extension/LICENSE.txt
index eddcb4e6..a12f2a75 100644
--- a/extension/LICENSE.txt
+++ b/extension/LICENSE.txt
@@ -1,5 +1,13 @@
-/*******************************************************
- * Copyright (C) 2023 Continue <nate@continue.dev>
- *
- * All rights reserved.
- *******************************************************/ \ No newline at end of file
+Copyright 2023 Continue
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License. \ No newline at end of file
diff --git a/extension/README.md b/extension/README.md
index df158440..51e95822 100644
--- a/extension/README.md
+++ b/extension/README.md
@@ -1,27 +1,18 @@
-# continue
+# Continue
-Bug report to fix in 1/10th the time.
+Automate software development at the level of tasks, instead of tab-autocompletions. With Continue, you can make large edits with natural language, ask questions of your codebase, and run custom recipes built with our open-source Python library.
-Our debugging assistant automatically handles tedious portions of the debugging process, such as:
-
-- Fault localization
-- Enumerating potential error sources
-- Generating fixes
-- Pulling in outside context
-- Following data flows
-- Parsing tracebacks
-- Generate unit tests
-- Generate docstrings
+Get started by opening the command pallet with `cmd+shift+p` and then selecting `Continue: Open Debug Panel`.
# Features
### Ask a Question
-`cmd+shift+j` to open up a universal search bar, where you can ask questions of your codebase in natural language.
+Ask natural language questions of your codebase, like "Where is the entry point to the VS Code extension?"
-### Fault Localization
+### Edit with Natural Language
-Either manually highlight code snippets you think are suspicious, or let us find them for you.
+Request an edit to the currently open file, for example: "Add CORS headers to this FastAPI server".
### Generate a Fix
diff --git a/extension/examples/README.md b/extension/examples/README.md
new file mode 100644
index 00000000..c95b9220
--- /dev/null
+++ b/extension/examples/README.md
@@ -0,0 +1,3 @@
+# Examples
+
+This folder contains example code used in Continue demos.
diff --git a/extension/logging.yaml b/extension/logging.yaml
new file mode 100644
index 00000000..391041ef
--- /dev/null
+++ b/extension/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
diff --git a/extension/media/continue-gradient.png b/extension/media/continue-gradient.png
new file mode 100644
index 00000000..2b382040
--- /dev/null
+++ b/extension/media/continue-gradient.png
Binary files differ
diff --git a/extension/package-lock.json b/extension/package-lock.json
index c8907210..647e3aa2 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,13 @@
{
"name": "continue",
- "version": "0.0.1",
+ "version": "0.0.13",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "continue",
- "version": "0.0.1",
+ "version": "0.0.13",
+ "license": "Apache-2.0",
"dependencies": {
"@electron/rebuild": "^3.2.10",
"@reduxjs/toolkit": "^1.9.3",
@@ -27,9 +28,11 @@
"@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",
+ "esbuild": "^0.17.19",
"eslint": "^8.28.0",
"glob": "^8.0.3",
"json-schema-to-typescript": "^12.0.0",
@@ -451,6 +454,358 @@
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
"peer": true
},
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz",
+ "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.17.19",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz",
+ "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@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==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@eslint/eslintrc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
@@ -1673,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",
@@ -3217,6 +3581,43 @@
"es6-symbol": "^3.1.1"
}
},
+ "node_modules/esbuild": {
+ "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"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.17.19",
+ "@esbuild/android-arm64": "0.17.19",
+ "@esbuild/android-x64": "0.17.19",
+ "@esbuild/darwin-arm64": "0.17.19",
+ "@esbuild/darwin-x64": "0.17.19",
+ "@esbuild/freebsd-arm64": "0.17.19",
+ "@esbuild/freebsd-x64": "0.17.19",
+ "@esbuild/linux-arm": "0.17.19",
+ "@esbuild/linux-arm64": "0.17.19",
+ "@esbuild/linux-ia32": "0.17.19",
+ "@esbuild/linux-loong64": "0.17.19",
+ "@esbuild/linux-mips64el": "0.17.19",
+ "@esbuild/linux-ppc64": "0.17.19",
+ "@esbuild/linux-riscv64": "0.17.19",
+ "@esbuild/linux-s390x": "0.17.19",
+ "@esbuild/linux-x64": "0.17.19",
+ "@esbuild/netbsd-x64": "0.17.19",
+ "@esbuild/openbsd-x64": "0.17.19",
+ "@esbuild/sunos-x64": "0.17.19",
+ "@esbuild/win32-arm64": "0.17.19",
+ "@esbuild/win32-ia32": "0.17.19",
+ "@esbuild/win32-x64": "0.17.19"
+ }
+ },
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -7734,6 +8135,160 @@
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
"peer": true
},
+ "@esbuild/android-arm": {
+ "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": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
@@ -8701,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",
@@ -9817,6 +10381,36 @@
"es6-symbol": "^3.1.1"
}
},
+ "esbuild": {
+ "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",
+ "@esbuild/android-x64": "0.17.19",
+ "@esbuild/darwin-arm64": "0.17.19",
+ "@esbuild/darwin-x64": "0.17.19",
+ "@esbuild/freebsd-arm64": "0.17.19",
+ "@esbuild/freebsd-x64": "0.17.19",
+ "@esbuild/linux-arm": "0.17.19",
+ "@esbuild/linux-arm64": "0.17.19",
+ "@esbuild/linux-ia32": "0.17.19",
+ "@esbuild/linux-loong64": "0.17.19",
+ "@esbuild/linux-mips64el": "0.17.19",
+ "@esbuild/linux-ppc64": "0.17.19",
+ "@esbuild/linux-riscv64": "0.17.19",
+ "@esbuild/linux-s390x": "0.17.19",
+ "@esbuild/linux-x64": "0.17.19",
+ "@esbuild/netbsd-x64": "0.17.19",
+ "@esbuild/openbsd-x64": "0.17.19",
+ "@esbuild/sunos-x64": "0.17.19",
+ "@esbuild/win32-arm64": "0.17.19",
+ "@esbuild/win32-ia32": "0.17.19",
+ "@esbuild/win32-x64": "0.17.19"
+ }
+ },
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
diff --git a/extension/package.json b/extension/package.json
index d957e071..1219ca8e 100644
--- a/extension/package.json
+++ b/extension/package.json
@@ -1,12 +1,20 @@
{
"name": "continue",
+ "icon": "media/continue-gradient.png",
"repository": {
"type": "git",
- "url": ""
+ "url": "https://github.com/continuedev/continue"
},
+ "bugs": {
+ "url": "https://github.com/continuedev/continue/issues",
+ "email": "nate@continue.dev"
+ },
+ "homepage": "https://continue.dev",
+ "license": "Apache-2.0",
"displayName": "Continue",
- "description": "Reduce debugging time by 10x",
- "version": "0.0.1",
+ "pricing": "Free",
+ "description": "Refine code 10x faster",
+ "version": "0.0.13",
"publisher": "Continue",
"engines": {
"vscode": "^1.74.0"
@@ -15,7 +23,7 @@
"Other"
],
"activationEvents": [
- "*"
+ "onStartupFinished"
],
"main": "./out/extension.js",
"contributes": {
@@ -108,21 +116,6 @@
],
"keybindings": [
{
- "command": "continue.writeDocstring",
- "key": "ctrl+alt+l",
- "mac": "shift+cmd+l"
- },
- {
- "command": "continue.writeUnitTest",
- "key": "ctrl+alt+i",
- "mac": "shift+cmd+i"
- },
- {
- "command": "continue.askQuestionFromInput",
- "key": "ctrl+alt+j",
- "mac": "shift+cmd+j"
- },
- {
"command": "continue.suggestionDown",
"mac": "shift+ctrl+down",
"key": "shift+ctrl+down"
@@ -154,19 +147,23 @@
}
},
"scripts": {
+ "vscode:prepublish": "npm run esbuild-base -- --minify",
+ "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 ./",
"clientgen": "rm -rf src/client/ && npx @openapitools/openapi-generator-cli generate -i ../schema/openapi.json -g typescript-fetch -o src/client/ --additional-properties=supportsES6=true,npmVersion=8.19.2,typescriptThreePlus=true",
"typegen": "node scripts/typegen.js",
- "vscode:prepublish": "npm run compile",
"rebuild": "electron-rebuild -v 19.1.8 node-pty",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"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 && 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",
+ "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"
},
"devDependencies": {
@@ -176,9 +173,11 @@
"@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",
+ "esbuild": "^0.17.19",
"eslint": "^8.28.0",
"glob": "^8.0.3",
"json-schema-to-typescript": "^12.0.0",
diff --git a/extension/react-app/README.md b/extension/react-app/README.md
new file mode 100644
index 00000000..006b6b11
--- /dev/null
+++ b/extension/react-app/README.md
@@ -0,0 +1,3 @@
+# Continue React App
+
+The Continue React app is a notebook-like interface to the Continue server. It allows the user to submit arbitrary text input, then communicates with the server to takes steps, which are displayed as a sequence of editable cells. The React app should sit beside an IDE, as in the VS Code extension.
diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json
index 1ba8cfe8..dbcbc5cc 100644
--- a/extension/react-app/package-lock.json
+++ b/extension/react-app/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@styled-icons/heroicons-outline": "^10.47.0",
"@types/vscode-webview": "^1.57.1",
+ "posthog-js": "^1.58.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^8.0.5",
@@ -1506,6 +1507,11 @@
"reusify": "^1.0.4"
}
},
+ "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/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -2528,6 +2534,15 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
+ "node_modules/posthog-js": {
+ "version": "1.58.0",
+ "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.58.0.tgz",
+ "integrity": "sha512-PpH/MwjwV6UHDsv78xFvteEWYgY3O/HTKBnotzmkNCDWgsKzNr978B1AKzgtBU2GzBsnwUfuK+u2O6mxRzFSWw==",
+ "dependencies": {
+ "fflate": "^0.4.1",
+ "rrweb-snapshot": "^1.1.14"
+ }
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -2778,6 +2793,11 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/rrweb-snapshot": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-1.1.14.tgz",
+ "integrity": "sha512-eP5pirNjP5+GewQfcOQY4uBiDnpqxNRc65yKPW0eSoU1XamDfc4M8oqpXGMyUyvLyxFDB0q0+DChuxxiU2FXBQ=="
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -4196,6 +4216,11 @@
"reusify": "^1.0.4"
}
},
+ "fflate": {
+ "version": "0.4.8",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
+ "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
+ },
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -4803,6 +4828,15 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
+ "posthog-js": {
+ "version": "1.58.0",
+ "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.58.0.tgz",
+ "integrity": "sha512-PpH/MwjwV6UHDsv78xFvteEWYgY3O/HTKBnotzmkNCDWgsKzNr978B1AKzgtBU2GzBsnwUfuK+u2O6mxRzFSWw==",
+ "requires": {
+ "fflate": "^0.4.1",
+ "rrweb-snapshot": "^1.1.14"
+ }
+ },
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -4964,6 +4998,11 @@
"fsevents": "~2.3.2"
}
},
+ "rrweb-snapshot": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-1.1.14.tgz",
+ "integrity": "sha512-eP5pirNjP5+GewQfcOQY4uBiDnpqxNRc65yKPW0eSoU1XamDfc4M8oqpXGMyUyvLyxFDB0q0+DChuxxiU2FXBQ=="
+ },
"run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
diff --git a/extension/react-app/package.json b/extension/react-app/package.json
index 3993b030..7d1211de 100644
--- a/extension/react-app/package.json
+++ b/extension/react-app/package.json
@@ -11,6 +11,7 @@
"dependencies": {
"@styled-icons/heroicons-outline": "^10.47.0",
"@types/vscode-webview": "^1.57.1",
+ "posthog-js": "^1.58.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^8.0.5",
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() {
<DebugPanel
tabs={[
{
- element: <Notebook />,
- title: "Notebook",
+ element: <GUI />,
+ title: "GUI",
},
// { element: <MainTab />, title: "Debug Panel" },
// { element: <WelcomeTab />, title: "Welcome" },
diff --git a/extension/react-app/src/components/CodeBlock.tsx b/extension/react-app/src/components/CodeBlock.tsx
index 4c10ab23..e0336554 100644
--- a/extension/react-app/src/components/CodeBlock.tsx
+++ b/extension/react-app/src/components/CodeBlock.tsx
@@ -6,7 +6,8 @@ import { defaultBorderRadius, vscBackground } from ".";
import { Clipboard } from "@styled-icons/heroicons-outline";
const StyledPre = styled.pre`
- overflow: scroll;
+ overflow-y: scroll;
+ word-wrap: normal;
border: 1px solid gray;
border-radius: ${defaultBorderRadius};
background-color: ${vscBackground};
diff --git a/extension/react-app/src/components/DebugPanel.tsx b/extension/react-app/src/components/DebugPanel.tsx
index ed00571b..9dacc624 100644
--- a/extension/react-app/src/components/DebugPanel.tsx
+++ b/extension/react-app/src/components/DebugPanel.tsx
@@ -36,7 +36,8 @@ const GradientContainer = styled.div`
const MainDiv = styled.div`
height: 100%;
border-radius: ${defaultBorderRadius};
- overflow: scroll;
+ overflow-y: scroll;
+ scrollbar-gutter: stable both-edges;
scrollbar-base-color: transparent;
/* background: ${vscBackground}; */
background-color: #1e1e1ede;
@@ -107,6 +108,7 @@ function DebugPanel(props: DebugPanelProps) {
className={
tab.title === "Chat" ? "overflow-hidden" : "overflow-scroll"
}
+ style={{ scrollbarGutter: "stable both-edges" }}
>
{tab.element}
</div>
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 03649b66..5e979b34 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 }>`
@@ -78,6 +80,13 @@ function StepContainer(props: StepContainerProps) {
const [open, setOpen] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const naturalLanguageInputRef = useRef<HTMLTextAreaElement>(null);
+ const userInputRef = useRef<HTMLInputElement>(null);
+
+ useEffect(() => {
+ if (userInputRef?.current) {
+ userInputRef.current.focus();
+ }
+ }, [userInputRef]);
useEffect(() => {
if (isHovered) {
@@ -134,6 +143,7 @@ function StepContainer(props: StepContainerProps) {
{props.historyNode.step.name === "Waiting for user input" && (
<input
+ ref={userInputRef}
className="m-auto p-2 rounded-md border-1 border-solid text-white w-3/4 border-gray-200 bg-vsc-background"
onKeyDown={(e) => {
if (e.key === "Enter") {
@@ -144,6 +154,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 +178,6 @@ function StepContainer(props: StepContainerProps) {
/>
</>
)}
-
- {open && (
- <>
- {/* {props.historyNode.observation && (
- <SubContainer title="Error">
- <CodeBlock>Error Here</CodeBlock>
- </SubContainer>
- )} */}
- {/* {props.iterationContext.suggestedChanges.map((sc) => {
- return (
- <SubContainer title="Suggested Change">
- {sc.filepath}
- <CodeBlock>{sc.replacement}</CodeBlock>
- </SubContainer>
- );
- })} */}
- </>
- )}
</StepContainerDiv>
</GradientBorder>
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/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/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<any>;
+}
+
+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<any> {
+ 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/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/useWebsocket.ts b/extension/react-app/src/hooks/useWebsocket.ts
index 147172bd..e762666f 100644
--- a/extension/react-app/src/hooks/useWebsocket.ts
+++ b/extension/react-app/src/hooks/useWebsocket.ts
@@ -1,67 +1,39 @@
import React, { useEffect, useState } from "react";
import { RootStore } from "../redux/store";
import { useSelector } from "react-redux";
+import ContinueGUIClientProtocol from "./useContinueGUIProtocol";
+import { postVscMessage } from "../vscode";
-function useContinueWebsocket(
- serverUrl: string,
- onMessage: (message: { data: any }) => void
-) {
+function useContinueGUIProtocol(useVscodeMessagePassing: boolean = true) {
const sessionId = useSelector((state: RootStore) => state.config.sessionId);
- const [websocket, setWebsocket] = useState<WebSocket | undefined>(undefined);
+ const serverHttpUrl = useSelector((state: RootStore) => state.config.apiUrl);
+ const [client, setClient] = useState<ContinueGUIClientProtocol | 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);
-
- const wsUrl =
- serverUrl.replace("http", "ws") +
- "/notebook/ws?session_id=" +
+ const serverUrlWithSessionId =
+ serverHttpUrl.replace("http", "ws") +
+ "/gui/ws?session_id=" +
encodeURIComponent(sessionId);
- const ws = new WebSocket(wsUrl);
- setWebsocket(ws);
-
- // Set up callbacks
- ws.onopen = () => {
- console.log("Websocket opened");
- ws.send(JSON.stringify({ sessionId }));
- };
-
- ws.onmessage = (msg) => {
- onMessage(msg);
- console.log("Got message", msg);
- };
-
- ws.onclose = (msg) => {
- console.log("Websocket closed");
- setWebsocket(undefined);
- };
-
- return ws;
- }
-
- 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]);
+ console.log("Creating websocket", serverUrlWithSessionId);
+ console.log("Using vscode message passing", useVscodeMessagePassing);
+ const newClient = new ContinueGUIClientProtocol(
+ serverUrlWithSessionId,
+ useVscodeMessagePassing
+ );
+ setClient(newClient);
+ }, [sessionId, serverHttpUrl]);
- return { send };
+ return client;
}
-export default useContinueWebsocket;
+export default useContinueGUIProtocol;
diff --git a/extension/react-app/src/hooks/vscodeMessenger.ts b/extension/react-app/src/hooks/vscodeMessenger.ts
new file mode 100644
index 00000000..ba19586b
--- /dev/null
+++ b/extension/react-app/src/hooks/vscodeMessenger.ts
@@ -0,0 +1,71 @@
+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") {
+ const data = JSON.parse(event.data.data);
+ if (data.messageType === messageType) {
+ callback(data.data);
+ }
+ }
+ });
+ }
+
+ onMessage(callback: (messageType: string, data: any) => void): void {
+ window.addEventListener("message", (event: any) => {
+ if (event.data.type === "websocketForwardingMessage") {
+ const data = JSON.parse(event.data.data);
+ callback(data.messageType, data.data);
+ }
+ });
+ }
+
+ sendAndReceive(messageType: string, data: any): Promise<any> {
+ return new Promise((resolve) => {
+ const handler = (event: any) => {
+ if (event.data.type === "websocketForwardingMessage") {
+ const data = JSON.parse(event.data.data);
+ if (data.messageType === messageType) {
+ window.removeEventListener("message", handler);
+ resolve(data.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/index.css b/extension/react-app/src/index.css
index dd38eec3..20599d30 100644
--- a/extension/react-app/src/index.css
+++ b/extension/react-app/src/index.css
@@ -21,7 +21,7 @@
html,
body,
#root {
- height: calc(100% - 7px);
+ height: calc(100%);
}
body {
diff --git a/extension/react-app/src/main.tsx b/extension/react-app/src/main.tsx
index 791f139e..1b94dc82 100644
--- a/extension/react-app/src/main.tsx
+++ b/extension/react-app/src/main.tsx
@@ -1,10 +1,19 @@
-import React from 'react'
-import ReactDOM from 'react-dom/client'
-import App from './App'
-import './index.css'
+import React from "react";
+import ReactDOM from "react-dom/client";
+import App from "./App";
+import "./index.css";
-ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
+import posthog from "posthog-js";
+import { PostHogProvider } from "posthog-js/react";
+
+posthog.init("phc_JS6XFROuNbhJtVCEdTSYk6gl5ArRrTNMpCcguAXlSPs", {
+ api_host: "https://app.posthog.com",
+});
+
+ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
- <App />
- </React.StrictMode>,
-)
+ <PostHogProvider client={posthog}>
+ <App />
+ </PostHogProvider>
+ </React.StrictMode>
+);
diff --git a/extension/react-app/src/tabs/chat/MessageDiv.tsx b/extension/react-app/src/tabs/chat/MessageDiv.tsx
index d7c79721..9bdd8638 100644
--- a/extension/react-app/src/tabs/chat/MessageDiv.tsx
+++ b/extension/react-app/src/tabs/chat/MessageDiv.tsx
@@ -20,7 +20,8 @@ const Container = styled.div`
margin: 3px;
width: fit-content;
max-width: 75%;
- overflow: scroll;
+ overflow-y: scroll;
+ scrollbar-gutter: stable both-edges;
word-wrap: break-word;
-ms-word-wrap: break-word;
height: fit-content;
diff --git a/extension/react-app/src/tabs/notebook.tsx b/extension/react-app/src/tabs/gui.tsx
index a9c69c5b..42ad4ed5 100644
--- a/extension/react-app/src/tabs/notebook.tsx
+++ b/extension/react-app/src/tabs/gui.tsx
@@ -14,10 +14,12 @@ 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 TopNotebookDiv = styled.div`
+let TopGUIDiv = styled.div`
display: grid;
grid-template-columns: 1fr;
+ overflow: scroll;
`;
let UserInputQueueItem = styled.div`
@@ -28,17 +30,15 @@ let UserInputQueueItem = styled.div`
text-align: center;
`;
-interface NotebookProps {
+interface GUIProps {
firstObservation?: any;
}
-function Notebook(props: NotebookProps) {
- const serverUrl = useSelector((state: RootStore) => state.config.apiUrl);
-
+function GUI(props: GUIProps) {
const [waitingForSteps, setWaitingForSteps] = useState(false);
const [userInputQueue, setUserInputQueue] = useState<string[]>([]);
const [history, setHistory] = useState<History | undefined>();
- // {
+ // {
// timeline: [
// {
// step: {
@@ -154,33 +154,19 @@ function Notebook(props: NotebookProps) {
// },
// ],
// current_index: 0,
- // } as any
- // );
+ // } 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 = useContinueGUIProtocol();
- // 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<HTMLTextAreaElement>(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,22 @@ 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 (
- <TopNotebookDiv>
+ <TopGUIDiv>
+ {typeof client === "undefined" && (
+ <>
+ <Loader></Loader>
+ <p style={{ textAlign: "center" }}>
+ Trying to reconnect with server...
+ </p>
+ </>
+ )}
{history?.timeline.map((node: HistoryNode, index: number) => {
return (
<StepContainer
@@ -237,17 +226,10 @@ function Notebook(props: NotebookProps) {
inFuture={index > 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);
}}
/>
);
@@ -278,8 +260,8 @@ function Notebook(props: NotebookProps) {
}}
></MainTextInput>
<ContinueButton onClick={onMainTextInput}></ContinueButton>
- </TopNotebookDiv>
+ </TopGUIDiv>
);
}
-export default Notebook;
+export default GUI;
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/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/schema/README.md b/extension/schema/README.md
new file mode 100644
index 00000000..9c97c0eb
--- /dev/null
+++ b/extension/schema/README.md
@@ -0,0 +1,3 @@
+# Schema
+
+These are files autogenerated by `npm run typegen`. They come originally from the `continuedev` Python package's Pydantic types.
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..fd3f48d1 100644
--- a/extension/scripts/continuedev-0.1.0-py3-none-any.whl
+++ b/extension/scripts/continuedev-0.1.0-py3-none-any.whl
Binary files differ
diff --git a/extension/scripts/install_from_source.py b/extension/scripts/install_from_source.py
new file mode 100644
index 00000000..bbb86797
--- /dev/null
+++ b/extension/scripts/install_from_source.py
@@ -0,0 +1,63 @@
+import os
+import subprocess
+
+
+def run(cmd: str):
+ return subprocess.run(cmd, shell=True, capture_output=False)
+
+
+def main():
+ # Check for Python and Node - we won't install them, but will warn
+ resp1 = run("python --version")
+ resp2 = run("python3 --version")
+ if resp1.stderr and resp2.stderr:
+ 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
+
+ resp = run("node --version")
+ if resp.stderr:
+ 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
+
+ resp = run("npm --version")
+ if resp.stderr:
+ 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
+
+ resp = run("poetry --version")
+ if resp.stderr:
+ 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
+
+ resp = run("cd ../../continuedev; poetry install; poetry run typegen")
+
+ resp = run(
+ "cd ..; npm i; cd react-app; npm i; cd ..; npm run full-package")
+
+ if resp.stderr:
+ print("Error packaging the extension. Please try again.")
+ print("This was the error: ", resp.stderr)
+ return
+
+ latest = None
+ latest_major = 0
+ latest_minor = 0
+ latest_patch = 0
+ for file in os.listdir("../build"):
+ if file.endswith(".vsix"):
+ version = file.split("-")[1].split(".vsix")[0]
+ major, minor, patch = list(
+ map(lambda x: int(x), version.split(".")))
+ if latest is None or (major >= latest_major and minor >= latest_minor and patch > latest_patch):
+ latest = file
+ latest_major = major
+ latest_minor = minor
+ latest_patch = patch
+
+ resp = run(f"cd ..; code --install-extension ./build/{latest}")
+
+ print("Continue VS Code extension installed successfully. Please restart VS Code to use it.")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/extension/src/activation/activate.ts b/extension/src/activation/activate.ts
index a0aa560b..40def480 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
);
@@ -59,12 +59,12 @@ export function activateExtension(
})
),
]).then(() => {
- ideProtocolClient?.openNotebook();
+ ideProtocolClient?.openGUI();
});
} else {
- // ideProtocolClient?.openNotebook().then(() => {
- // // openCapturedTerminal();
- // });
+ ideProtocolClient.openGUI().then(() => {
+ // openCapturedTerminal();
+ });
}
extensionContext = context;
diff --git a/extension/src/activation/environmentSetup.ts b/extension/src/activation/environmentSetup.ts
index 4816b4b1..170426e1 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 {
@@ -28,18 +29,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 +48,81 @@ async function setupPythonEnv() {
}
}
let pipCmd = pythonCmd.endsWith("3") ? "pip3" : "pip";
+ return [pythonCmd, pipCmd];
+}
- let activateCmd = "source env/bin/activate";
+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 envBinPath = 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",
+ process.platform == "win32" ? "Scripts" : "bin"
+ );
+ return (
+ fs.existsSync(path.join(envBinPath, "activate")) &&
+ fs.existsSync(path.join(envBinPath, "pip"))
+ );
+}
+
+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(
+ 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,38 +159,26 @@ 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;
}
- } catch (e) {
- console.log("Error checking for existing server", e);
- }
+ } catch (e) {}
- let activateCmd = "source env/bin/activate";
+ let activateCmd = ". env/bin/activate";
let pythonCmd = "python3";
if (process.platform == "win32") {
activateCmd = ".\\env\\Scripts\\activate";
@@ -158,26 +189,30 @@ export async function startContinuePythonServer() {
getExtensionUri().fsPath,
"scripts"
)} && ${activateCmd} && cd .. && ${pythonCmd} -m scripts.run_continue_server`;
- try {
- // exec(command);
- let 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() {
@@ -195,11 +230,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..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 6c65415f..03e5fbc5 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<string, vscode.WebviewPanel> = new Map();
- private readonly _serverUrl: string;
- private readonly _context: vscode.ExtensionContext;
+ private messenger: WebsocketMessenger | null = null;
+ private panels: Map<string, vscode.WebviewPanel> = 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,121 +56,52 @@ class IdeProtocolClient {
};
}
);
- this.send("fileEdits", { fileEdits });
+ this.messenger?.send("fileEdits", { fileEdits });
} else {
this._makingEdit--;
}
});
}
- async isConnected() {
- if (this._ws === null) {
- this._ws = new WebSocket(this._serverUrl);
- }
- // On open, return a promise
- if (this._ws!.readyState === WebSocket.OPEN) {
- return;
- }
- return new Promise((resolve, reject) => {
- this._ws!.onopen = () => {
- 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<any> {
- 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<any> {
- 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 "openGUI":
case "connected":
break;
default:
- throw Error("Unknown message type:" + message.messageType);
+ throw Error("Unknown message type:" + messageType);
}
}
getWorkspaceDirectory() {
@@ -204,18 +133,21 @@ class IdeProtocolClient {
// ------------------------------------ //
// Initiate Request
- closeNotebook(sessionId: string) {
- this._panels.get(sessionId)?.dispose();
- this._panels.delete(sessionId);
+ closeGUI(sessionId: string) {
+ 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;
+ async openGUI() {
+ console.log("OPENING GUI");
+ if (this.messenger === null) {
+ console.log("MESSENGER IS NULL");
+ }
+ const resp = await this.messenger?.sendAndReceive("openGUI", {});
+ const sessionId = resp.sessionId;
console.log("SESSION ID", sessionId);
- let column = getRightViewColumn();
+ const column = getRightViewColumn();
const panel = vscode.window.createWebviewPanel(
"continue.debugPanelView",
"Continue",
@@ -227,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 66829836..87c33da1 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 = "";
@@ -87,6 +88,49 @@ class StreamManager {
}
}
+let websocketConnections: { [url: string]: WebsocketConnection | undefined } =
+ {};
+
+class WebsocketConnection {
+ private _ws: WebSocket;
+ private _onMessage: (message: string) => void;
+ private _onOpen: () => void;
+ private _onClose: () => void;
+
+ constructor(
+ url: string,
+ onMessage: (message: string) => void,
+ onOpen: () => void,
+ onClose: () => void
+ ) {
+ this._ws = new WebSocket(url);
+ this._onMessage = onMessage;
+ this._onOpen = onOpen;
+ this._onClose = onClose;
+
+ this._ws.addEventListener("message", (event) => {
+ this._onMessage(event.data);
+ });
+ this._ws.addEventListener("close", () => {
+ this._onClose();
+ });
+ this._ws.addEventListener("open", () => {
+ this._onOpen();
+ });
+ }
+
+ public send(message: string) {
+ if (typeof message !== "string") {
+ message = JSON.stringify(message);
+ }
+ this._ws.send(message);
+ }
+
+ public close() {
+ this._ws.close();
+ }
+}
+
let streamManager = new StreamManager();
export let debugPanelWebview: vscode.Webview | undefined;
@@ -147,6 +191,39 @@ export function setupDebugPanel(
});
});
+ async function connectWebsocket(url: string) {
+ return new Promise((resolve, reject) => {
+ const onMessage = (message: any) => {
+ panel.webview.postMessage({
+ type: "websocketForwardingMessage",
+ url,
+ data: message,
+ });
+ };
+ const onOpen = () => {
+ panel.webview.postMessage({
+ type: "websocketForwardingOpen",
+ url,
+ });
+ resolve(null);
+ };
+ const onClose = () => {
+ websocketConnections[url] = undefined;
+ panel.webview.postMessage({
+ type: "websocketForwardingClose",
+ url,
+ });
+ };
+ const connection = new WebsocketConnection(
+ url,
+ onMessage,
+ onOpen,
+ onClose
+ );
+ websocketConnections[url] = connection;
+ });
+ }
+
panel.webview.onDidReceiveMessage(async (data) => {
switch (data.type) {
case "onLoad": {
@@ -156,6 +233,40 @@ 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;
+ }
+
+ case "websocketForwardingOpen": {
+ let url = data.url;
+ if (typeof websocketConnections[url] === "undefined") {
+ await connectWebsocket(url);
+ }
+ break;
+ }
+ case "websocketForwardingMessage": {
+ let url = data.url;
+ let connection = websocketConnections[url];
+ if (typeof connection === "undefined") {
+ await connectWebsocket(url);
+ }
+ connection = websocketConnections[url];
+ if (typeof connection === "undefined") {
+ throw new Error("Failed to connect websocket in VS Code Extension");
+ }
+ connection.send(data.message);
break;
}
case "listTenThings": {
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<any>;
+}
+
+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<any> {
+ 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/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/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
new file mode 100644
index 00000000..14b82d6d
--- /dev/null
+++ b/schema/json/History.json
@@ -0,0 +1,73 @@
+{
+ "title": "History",
+ "$ref": "#/definitions/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"
+ ]
+ },
+ "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..87a8729f
--- /dev/null
+++ b/schema/json/HistoryNode.json
@@ -0,0 +1,51 @@
+{
+ "title": "HistoryNode",
+ "$ref": "#/definitions/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": {}
+ },
+ "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/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": {
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"
+ }
+ }
+ }
+ }
+ }
+}