summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNate Sesti <sestinj@gmail.com>2023-05-30 08:41:56 -0400
committerNate Sesti <sestinj@gmail.com>2023-05-30 08:41:56 -0400
commit9171567ebf2c0ddf7c430a160a50d0d47c0c991b (patch)
tree0a1f6c54ac719e472edc44c6643cd3ae258d0fb5
parent5c049f2f3d797babb91c62ec752bf6a2c4c2f16a (diff)
downloadsncontinue-9171567ebf2c0ddf7c430a160a50d0d47c0c991b.tar.gz
sncontinue-9171567ebf2c0ddf7c430a160a50d0d47c0c991b.tar.bz2
sncontinue-9171567ebf2c0ddf7c430a160a50d0d47c0c991b.zip
rename agent to autopilot
-rw-r--r--continuedev/src/continuedev/core/agent.py180
-rw-r--r--continuedev/src/continuedev/libs/core.py22
-rw-r--r--continuedev/src/continuedev/libs/util/copy_codebase.py16
-rw-r--r--continuedev/src/continuedev/server/ide.py16
-rw-r--r--continuedev/src/continuedev/server/notebook.py40
-rw-r--r--docs/docs/concepts/agent.md7
-rw-r--r--docs/docs/concepts/core.md5
-rw-r--r--docs/docs/concepts/llm.md2
-rw-r--r--docs/sidebars.js51
9 files changed, 260 insertions, 79 deletions
diff --git a/continuedev/src/continuedev/core/agent.py b/continuedev/src/continuedev/core/agent.py
new file mode 100644
index 00000000..1996abb1
--- /dev/null
+++ b/continuedev/src/continuedev/core/agent.py
@@ -0,0 +1,180 @@
+import traceback
+import time
+from typing import Callable, Coroutine, List
+from ..models.filesystem_edit import FileEditWithFullContents
+from ..libs.llm import LLM
+from .observation import Observation
+from ..server.ide_protocol import AbstractIdeProtocolServer
+from ..libs.util.queue import AsyncSubscriptionQueue
+from ..models.main import ContinueBaseModel
+from .main import Policy, History, FullState, Step, HistoryNode
+from ..libs.steps.core.core import ReversibleStep, ManualEditStep, UserInputStep
+from .sdk import ContinueSDK
+
+
+class Autopilot(ContinueBaseModel):
+ llm: LLM
+ policy: Policy
+ ide: AbstractIdeProtocolServer
+ history: History = History.from_empty()
+ _on_update_callbacks: List[Callable[[FullState], None]] = []
+
+ _active: bool = False
+ _should_halt: bool = False
+ _main_user_input_queue: List[str] = []
+
+ _user_input_queue = AsyncSubscriptionQueue()
+
+ class Config:
+ arbitrary_types_allowed = True
+
+ def get_full_state(self) -> FullState:
+ return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue)
+
+ def on_update(self, callback: Callable[["FullState"], None]):
+ """Subscribe to changes to state"""
+ self._on_update_callbacks.append(callback)
+
+ def update_subscribers(self):
+ full_state = self.get_full_state()
+ for callback in self._on_update_callbacks:
+ callback(full_state)
+
+ def __get_step_params(self, step: "Step"):
+ return ContinueSDK(autopilot=self, llm=self.llm.with_system_message(step.system_message))
+
+ def give_user_input(self, input: str, index: int):
+ self._user_input_queue.post(index, input)
+
+ async def wait_for_user_input(self) -> str:
+ self._active = False
+ self.update_subscribers()
+ user_input = await self._user_input_queue.get(self.history.current_index)
+ self._active = True
+ self.update_subscribers()
+ return user_input
+
+ _manual_edits_buffer: List[FileEditWithFullContents] = []
+
+ async def reverse_to_index(self, index: int):
+ try:
+ while self.history.get_current_index() >= index:
+ current_step = self.history.get_current().step
+ self.history.step_back()
+ if issubclass(current_step.__class__, ReversibleStep):
+ await current_step.reverse(self.__get_step_params(current_step))
+
+ self.update_subscribers()
+ except Exception as e:
+ print(e)
+
+ def handle_manual_edits(self, edits: List[FileEditWithFullContents]):
+ for edit in edits:
+ self._manual_edits_buffer.append(edit)
+ # TODO: You're storing a lot of unecessary data here. Can compress into EditDiffs on the spot, and merge.
+ # self._manual_edits_buffer = merge_file_edit(self._manual_edits_buffer, edit)
+
+ def handle_traceback(self, traceback: str):
+ raise NotImplementedError
+
+ _step_depth: int = 0
+
+ async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]:
+ if not is_future_step:
+ # Check manual edits buffer, clear out if needed by creating a ManualEditStep
+ if len(self._manual_edits_buffer) > 0:
+ manualEditsStep = ManualEditStep.from_sequence(
+ self._manual_edits_buffer)
+ self._manual_edits_buffer = []
+ await self._run_singular_step(manualEditsStep)
+
+ # Update history - do this first so we get top-first tree ordering
+ self.history.add_node(HistoryNode(
+ step=step, observation=None, depth=self._step_depth))
+
+ # Run step
+ self._step_depth += 1
+ observation = await step(self.__get_step_params(step))
+ self._step_depth -= 1
+
+ # Add observation to history
+ self.history.get_current().observation = observation
+
+ # Update its description
+ step._set_description(await step.describe(self.llm))
+
+ # Call all subscribed callbacks
+ self.update_subscribers()
+
+ return observation
+
+ async def run_from_step(self, step: "Step"):
+ # if self._active:
+ # raise RuntimeError("Autopilot is already running")
+ self._active = True
+
+ next_step = step
+ is_future_step = False
+ while not (next_step is None or self._should_halt):
+ try:
+ if is_future_step:
+ # If future step, then we are replaying and need to delete the step from history so it can be replaced
+ self.history.remove_current_and_substeps()
+
+ observation = await self._run_singular_step(next_step, is_future_step)
+ if next_step := self.policy.next(self.history):
+ is_future_step = False
+ elif next_step := self.history.take_next_step():
+ is_future_step = True
+ else:
+ next_step = None
+
+ except Exception as e:
+ print(
+ f"Error while running step: \n{''.join(traceback.format_tb(e.__traceback__))}\n{e}")
+ next_step = None
+
+ self._active = False
+
+ # Doing this so active can make it to the frontend after steps are done. But want better state syncing tools
+ for callback in self._on_update_callbacks:
+ callback(None)
+
+ async def run_from_observation(self, observation: Observation):
+ next_step = self.policy.next(self.history)
+ await self.run_from_step(next_step)
+
+ async def run_policy(self):
+ first_step = self.policy.next(self.history)
+ await self.run_from_step(first_step)
+
+ async def _request_halt(self):
+ if self._active:
+ self._should_halt = True
+ while self._active:
+ time.sleep(0.1)
+ self._should_halt = False
+ return None
+
+ async def accept_user_input(self, user_input: str):
+ self._main_user_input_queue.append(user_input)
+ self.update_subscribers()
+
+ if len(self._main_user_input_queue) > 1:
+ return
+
+ # await self._request_halt()
+ # Just run the step that takes user input, and
+ # then up to the policy to decide how to deal with it.
+ self._main_user_input_queue.pop(0)
+ self.update_subscribers()
+ await self.run_from_step(UserInputStep(user_input=user_input))
+
+ while len(self._main_user_input_queue) > 0:
+ await self.run_from_step(UserInputStep(
+ user_input=self._main_user_input_queue.pop(0)))
+
+ async def accept_refinement_input(self, user_input: str, index: int):
+ await self._request_halt()
+ await self.reverse_to_index(index)
+ await self.run_from_step(UserInputStep(user_input=user_input))
diff --git a/continuedev/src/continuedev/libs/core.py b/continuedev/src/continuedev/libs/core.py
index 6a8a83ba..36d94ad1 100644
--- a/continuedev/src/continuedev/libs/core.py
+++ b/continuedev/src/continuedev/libs/core.py
@@ -89,31 +89,31 @@ class ContinueSDK:
"""The SDK provided as parameters to a step"""
llm: LLM
ide: AbstractIdeProtocolServer
- __agent: "Agent"
+ __autopilot: "Autopilot"
- def __init__(self, agent: "Agent", llm: Union[LLM, None] = None):
+ def __init__(self, autopilot: "Autopilot", llm: Union[LLM, None] = None):
if llm is None:
- self.llm = agent.llm
+ self.llm = autopilot.llm
else:
self.llm = llm
- self.ide = agent.ide
- self.__agent = agent
+ self.ide = autopilot.ide
+ self.__autopilot = autopilot
@property
def history(self) -> History:
- return self.__agent.history
+ return self.__autopilot.history
async def run_step(self, step: "Step") -> Coroutine[Observation, None, None]:
- return await self.__agent._run_singular_step(step)
+ return await self.__autopilot._run_singular_step(step)
async def apply_filesystem_edit(self, edit: FileSystemEdit):
await self.run_step(FileSystemEditStep(edit=edit))
async def wait_for_user_input(self) -> str:
- return await self.__agent.wait_for_user_input()
+ return await self.__autopilot.wait_for_user_input()
-class Agent(ContinueBaseModel):
+class Autopilot(ContinueBaseModel):
llm: LLM
policy: Policy
ide: AbstractIdeProtocolServer
@@ -142,7 +142,7 @@ class Agent(ContinueBaseModel):
callback(full_state)
def __get_step_params(self, step: "Step"):
- return ContinueSDK(agent=self, llm=self.llm.with_system_message(step.system_message))
+ return ContinueSDK(autopilot=self, llm=self.llm.with_system_message(step.system_message))
def give_user_input(self, input: str, index: int):
self._user_input_queue.post(index, input)
@@ -210,7 +210,7 @@ class Agent(ContinueBaseModel):
async def run_from_step(self, step: "Step"):
# if self._active:
- # raise RuntimeError("Agent is already running")
+ # raise RuntimeError("Autopilot is already running")
self._active = True
next_step = step
diff --git a/continuedev/src/continuedev/libs/util/copy_codebase.py b/continuedev/src/continuedev/libs/util/copy_codebase.py
index ef1db72b..af957a34 100644
--- a/continuedev/src/continuedev/libs/util/copy_codebase.py
+++ b/continuedev/src/continuedev/libs/util/copy_codebase.py
@@ -5,7 +5,7 @@ from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
from ..models.main import FileEdit, DeleteDirectory, DeleteFile, AddDirectory, AddFile, FileSystemEdit, Position, Range, RenameFile, RenameDirectory, SequentialFileSystemEdit
from ..models.filesystem import FileSystem
-from ..libs.main import Agent
+from ..libs.main import Autopilot
from ..libs.map_path import map_path
from ..libs.steps.main import ManualEditAction
import shutil
@@ -65,15 +65,15 @@ def calculate_diff(filepath: str, original: str, updated: str) -> List[FileEdit]
# The whole usage of watchdog here should only be specific to RealFileSystem, you want to have a different "Observer" class for VirtualFileSystem, which would depend on being sent notifications
class CopyCodebaseEventHandler(PatternMatchingEventHandler):
- def __init__(self, ignore_directories: List[str], ignore_patterns: List[str], agent: Agent, orig_root: str, copy_root: str, filesystem: FileSystem):
+ def __init__(self, ignore_directories: List[str], ignore_patterns: List[str], autopilot: Autopilot, orig_root: str, copy_root: str, filesystem: FileSystem):
super().__init__(ignore_directories=ignore_directories, ignore_patterns=ignore_patterns)
- self.agent = agent
+ self.autopilot = autopilot
self.orig_root = orig_root
self.copy_root = copy_root
self.filesystem = filesystem
- # For now, we'll just make the update immediately, but eventually need to sync with agent.
- # It should be the agent that makes the update right? It's just another action, everything comes from a single stream.
+ # For now, we'll just make the update immediately, but eventually need to sync with autopilot.
+ # It should be the autopilot that makes the update right? It's just another action, everything comes from a single stream.
def _event_to_edit(self, event) -> Union[FileSystemEdit, None]:
# NOTE: You'll need to map paths to create both an action within the copy filesystem (the one you take) and one in the original fileystem (the one you'll record and allow the user to accept). Basically just need a converter built in to the FileSystemEdit class
@@ -110,13 +110,13 @@ class CopyCodebaseEventHandler(PatternMatchingEventHandler):
return
edit = edit.with_mapped_paths(self.orig_root, self.copy_root)
action = ManualEditAction(edit)
- self.agent.act(action)
+ self.autopilot.act(action)
-def maintain_copy_workspace(agent: Agent, filesystem: FileSystem, orig_root: str, copy_root: str):
+def maintain_copy_workspace(autopilot: Autopilot, filesystem: FileSystem, orig_root: str, copy_root: str):
observer = Observer()
event_handler = CopyCodebaseEventHandler(
- [".git"], [], agent, orig_root, copy_root, filesystem)
+ [".git"], [], autopilot, orig_root, copy_root, filesystem)
observer.schedule(event_handler, orig_root, recursive=True)
observer.start()
try:
diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index dd1dc463..167d9483 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -122,7 +122,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
pass
async def setFileOpen(self, filepath: str, open: bool = True):
- # Agent needs access to this.
+ # Autopilot needs access to this.
await self.websocket.send_json({
"messageType": "setFileOpen",
"filepath": filepath,
@@ -147,12 +147,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
responses = await asyncio.gather(*[
self._receive_json(ShowSuggestionResponse)
for i in range(len(suggestions))
- ]) # WORKING ON THIS FLOW HERE. Fine now to just await for response, instead of doing something fancy with a "waiting" state on the agent.
+ ]) # WORKING ON THIS FLOW HERE. Fine now to just await for response, instead of doing something fancy with a "waiting" state on the autopilot.
# Just need connect the suggestionId to the IDE (and the notebook)
return any([r.accepted for r in responses])
# ------------------------------- #
- # Here needs to pass message onto the Agent OR Agent just subscribes.
+ # Here needs to pass message onto the Autopilot OR Autopilot just subscribes.
# This is where you might have triggers: plugins can subscribe to certian events
# like file changes, tracebacks, etc...
@@ -160,12 +160,12 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
pass
def onTraceback(self, traceback: Traceback):
- # Same as below, maybe not every agent?
+ # Same as below, maybe not every autopilot?
for _, session in self.session_manager.sessions.items():
- session.agent.handle_traceback(traceback)
+ session.autopilot.handle_traceback(traceback)
def onFileSystemUpdate(self, update: FileSystemEdit):
- # Access to Agent (so SessionManager)
+ # Access to Autopilot (so SessionManager)
pass
def onCloseNotebook(self, session_id: str):
@@ -176,10 +176,10 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
pass
def onFileEdits(self, edits: List[FileEditWithFullContents]):
- # Send the file edits to ALL agents.
+ # Send the file edits to ALL autopilots.
# Maybe not ideal behavior
for _, session in self.session_manager.sessions.items():
- session.agent.handle_manual_edits(edits)
+ session.autopilot.handle_manual_edits(edits)
# Request information. Session doesn't matter.
async def getOpenFiles(self) -> List[str]:
diff --git a/continuedev/src/continuedev/server/notebook.py b/continuedev/src/continuedev/server/notebook.py
index c9d4edc5..c26920f5 100644
--- a/continuedev/src/continuedev/server/notebook.py
+++ b/continuedev/src/continuedev/server/notebook.py
@@ -6,7 +6,7 @@ from uvicorn.main import Server
from ..models.filesystem_edit import FileEditWithFullContents
from ..libs.policy import DemoPolicy
-from ..libs.core import Agent, FullState, History, Step
+from ..libs.core import Autopilot, FullState, History, Step
from ..libs.steps.nate import ImplementAbstractMethodStep
from ..libs.observation import Observation
from dotenv import load_dotenv
@@ -41,16 +41,16 @@ Server.handle_exit = AppStatus.handle_exit
class Session:
session_id: str
- agent: Agent
+ autopilot: Autopilot
ws: Union[WebSocket, None]
- def __init__(self, session_id: str, agent: Agent):
+ def __init__(self, session_id: str, autopilot: Autopilot):
self.session_id = session_id
- self.agent = agent
+ self.autopilot = autopilot
self.ws = None
-class DemoAgent(Agent):
+class DemoAutopilot(Autopilot):
first_seen: bool = False
cumulative_edit_string = ""
@@ -78,20 +78,20 @@ class SessionManager:
def new_session(self, ide: AbstractIdeProtocolServer) -> str:
cmd = "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py"
- agent = DemoAgent(llm=OpenAI(api_key=openai_api_key),
- policy=DemoPolicy(cmd=cmd), ide=ide)
+ autopilot = DemoAutopilot(llm=OpenAI(api_key=openai_api_key),
+ policy=DemoPolicy(cmd=cmd), ide=ide)
session_id = str(uuid4())
- session = Session(session_id=session_id, agent=agent)
+ session = Session(session_id=session_id, autopilot=autopilot)
self.sessions[session_id] = session
def on_update(state: FullState):
session_manager.send_ws_data(session_id, {
"messageType": "state",
- "state": agent.get_full_state().dict()
+ "state": autopilot.get_full_state().dict()
})
- agent.on_update(on_update)
- asyncio.create_task(agent.run_policy())
+ autopilot.on_update(on_update)
+ asyncio.create_task(autopilot.run_policy())
return session_id
def remove_session(self, session_id: str):
@@ -147,7 +147,7 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we
# Update any history that may have happened before connection
await websocket.send_json({
"messageType": "state",
- "state": session_manager.get_session(session.session_id).agent.get_full_state().dict()
+ "state": session_manager.get_session(session.session_id).autopilot.get_full_state().dict()
})
print("Session started", data)
while AppStatus.should_exit is False:
@@ -162,17 +162,17 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we
if messageType == "main_input":
# Do something with user input
asyncio.create_task(
- session.agent.accept_user_input(data["value"]))
+ session.autopilot.accept_user_input(data["value"]))
elif messageType == "step_user_input":
asyncio.create_task(
- session.agent.give_user_input(data["value"], data["index"]))
+ session.autopilot.give_user_input(data["value"], data["index"]))
elif messageType == "refinement_input":
asyncio.create_task(
- session.agent.accept_refinement_input(data["value"], data["index"]))
+ session.autopilot.accept_refinement_input(data["value"], data["index"]))
elif messageType == "reverse":
# Reverse the history to the given index
asyncio.create_task(
- session.agent.reverse_to_index(data["index"]))
+ session.autopilot.reverse_to_index(data["index"]))
except Exception as e:
print(e)
@@ -182,17 +182,17 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we
@router.post("/run")
def request_run(step: Step, session=Depends(session)):
- """Tell an agent to take a specific action."""
- asyncio.create_task(session.agent.run_from_step(step))
+ """Tell an autopilot to take a specific action."""
+ asyncio.create_task(session.autopilot.run_from_step(step))
return "Success"
@router.get("/history")
def get_history(session=Depends(session)) -> History:
- return session.agent.history
+ return session.autopilot.history
@router.post("/observation")
def post_observation(observation: Observation, session=Depends(session)):
- asyncio.create_task(session.agent.run_from_observation(observation))
+ asyncio.create_task(session.autopilot.run_from_observation(observation))
return "Success"
diff --git a/docs/docs/concepts/agent.md b/docs/docs/concepts/agent.md
index 0528b305..e2fa6832 100644
--- a/docs/docs/concepts/agent.md
+++ b/docs/docs/concepts/agent.md
@@ -1,9 +1,10 @@
-# Agent
+# Autopilot
+
+`Autopilot` contains the
-`Agent` contains the
- History
- LLM
- Policy
- IDE
-**Q: should we really call this abstraction agent?** \ No newline at end of file
+**Q: should we really call this abstraction autopilot?**
diff --git a/docs/docs/concepts/core.md b/docs/docs/concepts/core.md
index ee58cbb2..d60f46ac 100644
--- a/docs/docs/concepts/core.md
+++ b/docs/docs/concepts/core.md
@@ -3,11 +3,12 @@
The `Core` connects the SDK and GUI with the IDE (i.e. in VS Code, a web browser, etc), enabling the steps to make changes to your code and accelerate your software development workflows.
The `Core` includes
+
- IDE protocol
- GUI protocol
- SDK
-- Agent
+- Autopilot
There is a two-way sync between an IDE and the GUI that happens through Core.
-**Q: does this make sense as a concept?** \ No newline at end of file
+**Q: does this make sense as a concept?**
diff --git a/docs/docs/concepts/llm.md b/docs/docs/concepts/llm.md
index 11bbacc7..8c2dbcba 100644
--- a/docs/docs/concepts/llm.md
+++ b/docs/docs/concepts/llm.md
@@ -4,4 +4,4 @@
**Q: should we call this LLM? Perhaps just model?**
-**Q: should this abstraction be connected to agent?** \ No newline at end of file
+**Q: should this abstraction be connected to autopilot?**
diff --git a/docs/sidebars.js b/docs/sidebars.js
index befe4feb..d2c91388 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -13,41 +13,40 @@
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
-
docsSidebar: [
- 'intro',
- 'getting-started',
- 'install',
- 'how-continue-works',
+ "intro",
+ "getting-started",
+ "install",
+ "how-continue-works",
{
- type: 'category',
- label: 'Walkthroughs',
+ type: "category",
+ label: "Walkthroughs",
items: [
- 'walkthroughs/use-the-gui',
- 'walkthroughs/use-a-recipe',
- 'walkthroughs/create-a-recipe',
- 'walkthroughs/share-a-recipe',
+ "walkthroughs/use-the-gui",
+ "walkthroughs/use-a-recipe",
+ "walkthroughs/create-a-recipe",
+ "walkthroughs/share-a-recipe",
],
},
{
- type: 'category',
- label: 'Concepts',
+ type: "category",
+ label: "Concepts",
items: [
- 'concepts/agent',
- 'concepts/core',
- 'concepts/gui',
- 'concepts/history',
- 'concepts/ide',
- 'concepts/llm',
- 'concepts/policy',
- 'concepts/recipe',
- 'concepts/sdk',
- 'concepts/step',
+ "concepts/autopilot",
+ "concepts/core",
+ "concepts/gui",
+ "concepts/history",
+ "concepts/ide",
+ "concepts/llm",
+ "concepts/policy",
+ "concepts/recipe",
+ "concepts/sdk",
+ "concepts/step",
],
},
- 'sdk',
- 'telemetry',
+ "sdk",
+ "telemetry",
],
};
-module.exports = sidebars; \ No newline at end of file
+module.exports = sidebars;