feat: Add synopisis core loop (#166)

Co-authored-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
Bradley Axen
2024-10-18 13:00:17 -07:00
committed by GitHub
parent 76a50659ad
commit 6cf416c128
24 changed files with 1166 additions and 333 deletions
+33
View File
@@ -0,0 +1,33 @@
from unittest.mock import patch
import pytest
from exchange.content import Text
from exchange.message import Message
from goose.synopsis.moderator import Synopsis
@pytest.fixture
def mock_exchange(exchange_factory):
exchange = exchange_factory()
exchange.messages.extend(
[
Message(role="user", content=[Text("Test content for user message")]),
Message(role="assistant", content=[Text("Test content for assistant message")]),
Message(role="user", content=[Text("Another user message")]),
]
)
return exchange
def test_rewrite_with_tool_use(mock_exchange):
tool_use_message = Message(role="user", content=[Text("Tool use message")])
mock_exchange.messages.append(tool_use_message)
with patch.object(Synopsis, "get_synopsis") as mock_get_synopsis:
message = Message(role="synopsis", content=[Text("Updated synopsis")])
mock_get_synopsis.return_value = message
synopsis = Synopsis()
synopsis.rewrite(mock_exchange)
# The first message should be replaced, and the rest are cleared
assert mock_exchange.messages == [message]
+62
View File
@@ -0,0 +1,62 @@
import pytest
import time
import requests
from goose.synopsis.toolkit import SynopsisDeveloper
from goose.synopsis.system import system
class MockNotifier:
def log(self, message):
pass
def status(self, message):
pass
@pytest.fixture
def toolkit(tmpdir):
original_cwd = system.cwd
system.cwd = str(tmpdir)
notifier = MockNotifier()
toolkit = SynopsisDeveloper(notifier=notifier)
yield toolkit
# Teardown: cancel all processes and restore original working directory
for process_id in list(system._processes.keys()):
system.cancel_process(process_id)
system.cwd = original_cwd
def test_start_process(toolkit):
process_id = toolkit.start_process("python -m http.server 8000")
assert process_id > 0
time.sleep(2) # Give the server time to start
# Check if the server is running
try:
response = requests.get("http://localhost:8000")
assert response.status_code == 200
except requests.ConnectionError:
pytest.fail("HTTP server did not start successfully")
output = toolkit.view_process_output(process_id)
assert "200" in output
def test_list_processes(toolkit):
process_id = toolkit.start_process("python -m http.server 8001")
processes = toolkit.list_processes()
assert process_id in processes
assert "python -m http.server 8001" in processes[process_id]
def test_cancel_process(toolkit):
process_id = toolkit.start_process("python -m http.server 8003")
time.sleep(2) # Give the server time to start
result = toolkit.cancel_process(process_id)
assert result == f"process {process_id} cancelled"
# Verify that the process is no longer running
with pytest.raises(ValueError):
toolkit.view_process_output(process_id)
+93
View File
@@ -0,0 +1,93 @@
import os
from unittest.mock import Mock
import pytest
from goose.synopsis.system import OperatingSystem
@pytest.fixture
def os_instance(tmpdir):
original_cwd = os.getcwd()
os.chdir(tmpdir)
yield OperatingSystem(cwd=str(tmpdir))
os.chdir(original_cwd)
def test_to_relative(os_instance, tmpdir):
abs_path = os.path.join(tmpdir, "test_file.txt")
rel_path = os_instance.to_relative(abs_path)
assert rel_path == "test_file.txt"
def test_remember_forget_file(os_instance, tmpdir):
test_file = tmpdir.join("test_file.txt")
test_file.write("test content")
os_instance.remember_file(str(test_file))
assert os_instance.is_active(str(test_file))
os_instance.forget_file(str(test_file))
assert not os_instance.is_active(str(test_file))
def test_active_files(os_instance, tmpdir):
test_file1 = tmpdir.join("test_file1.txt")
test_file2 = tmpdir.join("test_file2.py")
test_file1.write("test content 1")
test_file2.write("test content 2")
os_instance.remember_file(str(test_file1))
os_instance.remember_file(str(test_file2))
active_files = list(os_instance.active_files)
assert len(active_files) == 2
assert any(f.path == "test_file1.txt" for f in active_files)
assert any(f.path == "test_file2.py" for f in active_files)
def test_info(os_instance):
info = os_instance.info()
assert "os" in info
assert "cwd" in info
assert "shell" in info
def test_add_process(os_instance):
process = Mock()
process.pid = 1234
process.stdout = Mock()
process.stdout.fileno.return_value = 1
process_id = os_instance.add_process(process)
assert process_id == 1234
assert 1234 in os_instance._processes
def test_get_processes(os_instance):
process1 = Mock()
process1.pid = 1234
process1.args = "python -m http.server 8000"
process1.stdout = Mock()
process1.stdout.fileno.return_value = 1
os_instance.add_process(process1)
process2 = Mock()
process2.pid = 5678
process2.args = "python script.py"
process2.stdout = Mock()
process2.stdout.fileno.return_value = 2
os_instance.add_process(process2)
processes = os_instance.get_processes()
assert processes == {1234: "python -m http.server 8000", 5678: "python script.py"}
def test_cancel_process(os_instance):
process = Mock()
process.pid = 1234
process.stdout = Mock()
process.stdout.fileno.return_value = 1
os_instance.add_process(process)
result = os_instance.cancel_process(1234)
assert result is True
assert 1234 not in os_instance._processes
process.terminate.assert_called_once()
+95
View File
@@ -0,0 +1,95 @@
import os
import pytest
from goose.synopsis.toolkit import SynopsisDeveloper
from goose.synopsis.system import system
class MockNotifier:
def log(self, message):
pass
def status(self, message):
pass
@pytest.fixture
def toolkit(tmpdir):
original_cwd = os.getcwd()
os.chdir(tmpdir)
system.cwd = str(tmpdir)
notifier = MockNotifier()
toolkit = SynopsisDeveloper(notifier=notifier)
yield toolkit
# Teardown: cancel all processes and restore original working directory
for process_id in list(system._processes.keys()):
system.cancel_process(process_id)
os.chdir(original_cwd)
system.cwd = original_cwd
def test_shell(toolkit, tmpdir):
result = toolkit.shell("echo 'Hello, World!'")
assert "Hello, World!" in result
def test_read_write_file(toolkit, tmpdir):
test_file = tmpdir.join("test_file.txt")
content = "Test content"
toolkit.write_file(str(test_file), content)
assert test_file.read() == content
result = toolkit.read_file(str(test_file))
assert "The file content at" in result
assert system.is_active(str(test_file))
def test_patch_file(toolkit, tmpdir):
test_file = tmpdir.join("test_file.txt")
test_file.write("Hello, World!")
toolkit.read_file(str(test_file)) # Remember the file
result = toolkit.patch_file(str(test_file), "World", "Universe")
assert "Succesfully replaced before with after" in result
assert test_file.read() == "Hello, Universe!"
def test_change_dir(toolkit, tmpdir):
subdir = tmpdir.mkdir("subdir")
result = toolkit.change_dir(str(subdir))
assert result == str(subdir)
assert system.cwd == str(subdir)
def test_start_process(toolkit, tmpdir):
process_id = toolkit.start_process("python -m http.server 8000")
assert process_id > 0
# Check if the process is in the list of running processes
processes = toolkit.list_processes()
assert process_id in processes
assert "python -m http.server 8000" in processes[process_id]
def test_list_processes(toolkit, tmpdir):
process_id1 = toolkit.start_process("python -m http.server 8001")
process_id2 = toolkit.start_process("python -m http.server 8002")
processes = toolkit.list_processes()
assert process_id1 in processes
assert process_id2 in processes
assert "python -m http.server 8001" in processes[process_id1]
assert "python -m http.server 8002" in processes[process_id2]
def test_cancel_process(toolkit, tmpdir):
process_id = toolkit.start_process("python -m http.server 8003")
result = toolkit.cancel_process(process_id)
assert result == f"process {process_id} cancelled"
# Verify that the process is no longer in the list
processes = toolkit.list_processes()
assert process_id not in processes