v2.9.0 (#641)
Co-authored-by: Antoine <33425718+anth2o@users.noreply.github.com> Co-authored-by: Bastien <bastien.baret@gmail.com> Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai> Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com> Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@users.noreply.github.com> Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai> Co-authored-by: Quentin <quentin.torroba@mistral.ai> Co-authored-by: Robin Gullo <robin.gullo@mistral.ai> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
a83c81ecf5
commit
632ea8c032
253 changed files with 13965 additions and 2525 deletions
171
tests/acp/test_acp_hooks.py
Normal file
171
tests/acp/test_acp_hooks.py
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import tomli_w
|
||||
|
||||
from tests.conftest import get_base_config
|
||||
from tests.stubs.fake_backend import FakeBackend
|
||||
from tests.stubs.fake_client import FakeClient
|
||||
from vibe.acp.acp_agent_loop import VibeAcpAgentLoop
|
||||
from vibe.core.agent_loop import AgentLoop
|
||||
from vibe.core.hooks.models import HookConfigResult, HookType
|
||||
from vibe.core.types import LLMChunk, LLMMessage, LLMUsage, Role, SessionMetadata
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def backend() -> FakeBackend:
|
||||
return FakeBackend(
|
||||
LLMChunk(
|
||||
message=LLMMessage(role=Role.assistant, content="Hi"),
|
||||
usage=LLMUsage(prompt_tokens=1, completion_tokens=1),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _write_config(config_dir: Path, *, enable_hooks: bool) -> None:
|
||||
config = get_base_config()
|
||||
config["enable_experimental_hooks"] = enable_hooks
|
||||
with (config_dir / "config.toml").open("wb") as f:
|
||||
tomli_w.dump(config, f)
|
||||
|
||||
|
||||
def _create_acp_agent() -> VibeAcpAgentLoop:
|
||||
agent = VibeAcpAgentLoop()
|
||||
client = FakeClient()
|
||||
agent.on_connect(client)
|
||||
client.on_connect(agent)
|
||||
return agent
|
||||
|
||||
|
||||
def _spy_agent_loop(backend: FakeBackend) -> tuple[MagicMock, type[AgentLoop]]:
|
||||
spy = MagicMock()
|
||||
|
||||
class Patched(AgentLoop):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
spy(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs, backend=backend)
|
||||
|
||||
return spy, Patched
|
||||
|
||||
|
||||
def _hook_config_from_spy(spy: MagicMock) -> HookConfigResult | None:
|
||||
hook_config_result = spy.call_args.kwargs["hook_config_result"]
|
||||
if hook_config_result is None:
|
||||
return None
|
||||
assert isinstance(hook_config_result, HookConfigResult)
|
||||
return hook_config_result
|
||||
|
||||
|
||||
async def _new_session(backend: FakeBackend) -> MagicMock:
|
||||
spy, patched_loop = _spy_agent_loop(backend)
|
||||
acp = _create_acp_agent()
|
||||
with patch("vibe.acp.acp_agent_loop.AgentLoop", side_effect=patched_loop):
|
||||
await acp.new_session(cwd=str(Path.cwd()), mcp_servers=[])
|
||||
spy.assert_called_once()
|
||||
return spy
|
||||
|
||||
|
||||
async def _load_session(
|
||||
backend: FakeBackend,
|
||||
tmp_path: Path,
|
||||
*,
|
||||
session_id: str = "session-id",
|
||||
loaded_messages: list[LLMMessage] | None = None,
|
||||
) -> MagicMock:
|
||||
spy, patched_loop = _spy_agent_loop(backend)
|
||||
acp = _create_acp_agent()
|
||||
session_dir = tmp_path / "sessions" / session_id
|
||||
session_dir.mkdir(parents=True)
|
||||
disk_metadata = SessionMetadata(
|
||||
session_id=session_id,
|
||||
start_time="2024-01-01T12:00:00Z",
|
||||
end_time="2024-01-01T12:05:00Z",
|
||||
git_commit=None,
|
||||
git_branch=None,
|
||||
environment={"working_directory": str(Path.cwd())},
|
||||
username="test-user",
|
||||
)
|
||||
(session_dir / "meta.json").write_text(disk_metadata.model_dump_json())
|
||||
loader_result = (loaded_messages or [], {"session_id": session_id})
|
||||
|
||||
with (
|
||||
patch("vibe.acp.acp_agent_loop.AgentLoop", side_effect=patched_loop),
|
||||
patch(
|
||||
"vibe.acp.acp_agent_loop.SessionLoader.find_session_by_id",
|
||||
return_value=session_dir,
|
||||
),
|
||||
patch(
|
||||
"vibe.acp.acp_agent_loop.SessionLoader.load_session",
|
||||
return_value=loader_result,
|
||||
),
|
||||
):
|
||||
await acp.load_session(
|
||||
cwd=str(Path.cwd()), session_id=session_id, mcp_servers=[]
|
||||
)
|
||||
spy.assert_called_once()
|
||||
return spy
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
class TestAcpHooksLoading:
|
||||
async def test_new_session_hooks_enabled_loads_valid_hook(
|
||||
self, backend: FakeBackend, config_dir: Path
|
||||
) -> None:
|
||||
_write_config(config_dir, enable_hooks=True)
|
||||
(config_dir / "hooks.toml").write_text(
|
||||
'[[hooks]]\nname = "lint"\ntype = "post_agent_turn"\ncommand = "eslint ."\n'
|
||||
)
|
||||
|
||||
spy = await _new_session(backend)
|
||||
|
||||
result = _hook_config_from_spy(spy)
|
||||
assert result is not None
|
||||
assert len(result.hooks) == 1
|
||||
assert result.hooks[0].name == "lint"
|
||||
assert result.hooks[0].type == HookType.POST_AGENT_TURN
|
||||
assert result.hooks[0].command == "eslint ."
|
||||
assert result.hooks[0].timeout == 30.0
|
||||
assert result.issues == []
|
||||
|
||||
async def test_new_session_hooks_enabled_invalid_toml(
|
||||
self, backend: FakeBackend, config_dir: Path
|
||||
) -> None:
|
||||
_write_config(config_dir, enable_hooks=True)
|
||||
(config_dir / "hooks.toml").write_text("{{broken toml")
|
||||
|
||||
spy = await _new_session(backend)
|
||||
|
||||
result = _hook_config_from_spy(spy)
|
||||
assert result is not None
|
||||
assert result.hooks == []
|
||||
assert len(result.issues) == 1
|
||||
assert "Failed to parse" in result.issues[0].message
|
||||
|
||||
async def test_load_session_hooks_enabled_loads_valid_hook(
|
||||
self, backend: FakeBackend, config_dir: Path, tmp_path: Path
|
||||
) -> None:
|
||||
_write_config(config_dir, enable_hooks=True)
|
||||
(config_dir / "hooks.toml").write_text(
|
||||
'[[hooks]]\nname = "lint"\ntype = "post_agent_turn"\ncommand = "eslint ."\n'
|
||||
)
|
||||
|
||||
spy = await _load_session(
|
||||
backend,
|
||||
tmp_path,
|
||||
loaded_messages=[
|
||||
LLMMessage(role=Role.system, content="system"),
|
||||
LLMMessage(role=Role.user, content="hello"),
|
||||
],
|
||||
)
|
||||
|
||||
result = _hook_config_from_spy(spy)
|
||||
assert result is not None
|
||||
assert len(result.hooks) == 1
|
||||
assert result.hooks[0].name == "lint"
|
||||
assert result.hooks[0].type == HookType.POST_AGENT_TURN
|
||||
assert result.hooks[0].command == "eslint ."
|
||||
assert result.hooks[0].timeout == 30.0
|
||||
assert result.issues == []
|
||||
Loading…
Add table
Add a link
Reference in a new issue