Co-authored-by: Quentin Torroba <quentin.torroba@mistral.ai>
Co-authored-by: Clément Siriex <clement.sirieix@mistral.ai>
Co-authored-by: Kim-Adeline Miguel <kimadeline.miguel@mistral.ai>
Co-authored-by: Michel Thomazo <michel.thomazo@mistral.ai>
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
This commit is contained in:
Mathias Gesbert 2026-02-17 16:23:28 +01:00 committed by GitHub
parent 51fecc67d9
commit ec7f3b25ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
107 changed files with 8002 additions and 535 deletions

View file

@ -1,5 +1,8 @@
from __future__ import annotations
from datetime import datetime
import json
from pathlib import Path
from unittest.mock import patch
import pytest
@ -40,3 +43,55 @@ def acp_agent_loop(backend: FakeBackend) -> VibeAcpAgentLoop:
patch("vibe.acp.acp_agent_loop.AgentLoop", side_effect=PatchedAgent).start()
return _create_acp_agent()
@pytest.fixture
def temp_session_dir(tmp_path: Path) -> Path:
session_dir = tmp_path / "sessions"
session_dir.mkdir()
return session_dir
@pytest.fixture
def create_test_session():
"""Create a test session with configurable messages and metadata.
Supports both messages parameter (for load_session tests) and
end_time parameter (for list_sessions tests).
"""
def _create_session(
session_dir: Path,
session_id: str,
cwd: str,
messages: list[dict] | None = None,
title: str | None = None,
end_time: str | None = None,
) -> Path:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
session_folder = session_dir / f"session_{timestamp}_{session_id[:8]}"
session_folder.mkdir(exist_ok=True)
if messages is None:
messages = [{"role": "user", "content": "Hello"}]
messages_file = session_folder / "messages.jsonl"
with messages_file.open("w", encoding="utf-8") as f:
for msg in messages:
f.write(json.dumps(msg) + "\n")
metadata = {
"session_id": session_id,
"start_time": "2024-01-01T12:00:00Z",
"end_time": end_time or "2024-01-01T12:05:00Z",
"environment": {"working_directory": cwd},
"title": title,
}
metadata_file = session_folder / "meta.json"
with metadata_file.open("w", encoding="utf-8") as f:
json.dump(metadata, f)
return session_folder
return _create_session

View file

@ -451,6 +451,18 @@ class TestSessionUpdates:
),
),
)
commands_response_text = await read_response(process)
assert commands_response_text is not None
commands_response = UpdateJsonRpcNotification.model_validate(
json.loads(commands_response_text)
)
assert commands_response.params is not None
assert (
commands_response.params.update.session_update
== "available_commands_update"
)
user_response_text = await read_response(process)
assert user_response_text is not None
user_response = UpdateJsonRpcNotification.model_validate(

View file

@ -6,6 +6,8 @@ from acp.schema import (
ClientCapabilities,
Implementation,
PromptCapabilities,
SessionCapabilities,
SessionListCapabilities,
)
import pytest
@ -19,13 +21,14 @@ class TestACPInitialize:
assert response.protocol_version == PROTOCOL_VERSION
assert response.agent_capabilities == AgentCapabilities(
load_session=False,
load_session=True,
prompt_capabilities=PromptCapabilities(
audio=False, embedded_context=True, image=False
),
session_capabilities=SessionCapabilities(list=SessionListCapabilities()),
)
assert response.agent_info == Implementation(
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.1.0"
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.2.0"
)
assert response.auth_methods == []
@ -42,13 +45,14 @@ class TestACPInitialize:
assert response.protocol_version == PROTOCOL_VERSION
assert response.agent_capabilities == AgentCapabilities(
load_session=False,
load_session=True,
prompt_capabilities=PromptCapabilities(
audio=False, embedded_context=True, image=False
),
session_capabilities=SessionCapabilities(list=SessionListCapabilities()),
)
assert response.agent_info == Implementation(
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.1.0"
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.2.0"
)
assert response.auth_methods is not None

View file

@ -0,0 +1,242 @@
from __future__ import annotations
from pathlib import Path
from unittest.mock import patch
import pytest
from tests.acp.conftest import _create_acp_agent
from vibe.core.config import MissingAPIKeyError, SessionLoggingConfig
class TestListSessions:
@pytest.mark.asyncio
async def test_list_sessions_empty(self, temp_session_dir: Path) -> None:
acp_agent = _create_acp_agent()
session_config = SessionLoggingConfig(
save_dir=str(temp_session_dir), session_prefix="session", enabled=True
)
with patch("vibe.acp.acp_agent_loop.VibeConfig.load") as mock_load:
mock_config = mock_load.return_value
mock_config.session_logging = session_config
response = await acp_agent.list_sessions()
assert response.sessions == []
@pytest.mark.asyncio
async def test_list_sessions_returns_all_sessions(
self, temp_session_dir: Path, create_test_session
) -> None:
acp_agent = _create_acp_agent()
create_test_session(
temp_session_dir,
"aaaaaaaa-1111",
"/home/user/project1",
title="First session",
end_time="2024-01-01T12:00:00Z",
)
create_test_session(
temp_session_dir,
"bbbbbbbb-2222",
"/home/user/project2",
title="Second session",
end_time="2024-01-01T13:00:00Z",
)
session_config = SessionLoggingConfig(
save_dir=str(temp_session_dir), session_prefix="session", enabled=True
)
with patch("vibe.acp.acp_agent_loop.VibeConfig.load") as mock_load:
mock_config = mock_load.return_value
mock_config.session_logging = session_config
response = await acp_agent.list_sessions()
assert len(response.sessions) == 2
session_ids = {s.session_id for s in response.sessions}
assert "aaaaaaaa-1111" in session_ids
assert "bbbbbbbb-2222" in session_ids
@pytest.mark.asyncio
async def test_list_sessions_filters_by_cwd(
self, temp_session_dir: Path, create_test_session
) -> None:
acp_agent = _create_acp_agent()
create_test_session(
temp_session_dir,
"aaaaaaaa-proj1",
"/home/user/project1",
title="Project 1 session",
)
create_test_session(
temp_session_dir,
"bbbbbbbb-proj2",
"/home/user/project2",
title="Project 2 session",
)
create_test_session(
temp_session_dir,
"cccccccc-proj1",
"/home/user/project1",
title="Another Project 1 session",
)
session_config = SessionLoggingConfig(
save_dir=str(temp_session_dir), session_prefix="session", enabled=True
)
with patch("vibe.acp.acp_agent_loop.VibeConfig.load") as mock_load:
mock_config = mock_load.return_value
mock_config.session_logging = session_config
response = await acp_agent.list_sessions(cwd="/home/user/project1")
assert len(response.sessions) == 2
for session in response.sessions:
assert session.cwd == "/home/user/project1"
@pytest.mark.asyncio
async def test_list_sessions_sorted_by_updated_at(
self, temp_session_dir: Path, create_test_session
) -> None:
acp_agent = _create_acp_agent()
create_test_session(
temp_session_dir,
"oldest-s",
"/home/user/project",
title="Oldest",
end_time="2024-01-01T10:00:00Z",
)
create_test_session(
temp_session_dir,
"newest-s",
"/home/user/project",
title="Newest",
end_time="2024-01-01T14:00:00Z",
)
create_test_session(
temp_session_dir,
"middle-s",
"/home/user/project",
title="Middle",
end_time="2024-01-01T12:00:00Z",
)
session_config = SessionLoggingConfig(
save_dir=str(temp_session_dir), session_prefix="session", enabled=True
)
with patch("vibe.acp.acp_agent_loop.VibeConfig.load") as mock_load:
mock_config = mock_load.return_value
mock_config.session_logging = session_config
response = await acp_agent.list_sessions()
assert len(response.sessions) == 3
assert response.sessions[0].title == "Newest"
assert response.sessions[1].title == "Middle"
assert response.sessions[2].title == "Oldest"
@pytest.mark.asyncio
async def test_list_sessions_includes_session_info_fields(
self, temp_session_dir: Path, create_test_session
) -> None:
acp_agent = _create_acp_agent()
create_test_session(
temp_session_dir,
"test-session-123",
"/home/user/project",
title="Test Session Title",
end_time="2024-01-15T10:30:00Z",
)
session_config = SessionLoggingConfig(
save_dir=str(temp_session_dir), session_prefix="session", enabled=True
)
with patch("vibe.acp.acp_agent_loop.VibeConfig.load") as mock_load:
mock_config = mock_load.return_value
mock_config.session_logging = session_config
response = await acp_agent.list_sessions()
assert len(response.sessions) == 1
session = response.sessions[0]
assert session.session_id == "test-session-123"
assert session.cwd == "/home/user/project"
assert session.title == "Test Session Title"
# updated_at is normalized to UTC
assert session.updated_at is not None
assert session.updated_at.endswith("+00:00")
@pytest.mark.asyncio
async def test_list_sessions_skips_invalid_sessions(
self, temp_session_dir: Path, create_test_session
) -> None:
acp_agent = _create_acp_agent()
create_test_session(
temp_session_dir, "valid-se", "/home/user/project", title="Valid Session"
)
invalid_session = temp_session_dir / "session_20240101_120000_invalid1"
invalid_session.mkdir()
(invalid_session / "meta.json").write_text('{"session_id": "invalid"}')
no_id_session = temp_session_dir / "session_20240101_120001_noid0000"
no_id_session.mkdir()
(no_id_session / "messages.jsonl").write_text(
'{"role": "user", "content": "Hello"}\n'
)
(no_id_session / "meta.json").write_text(
'{"environment": {"working_directory": "/test"}}'
)
session_config = SessionLoggingConfig(
save_dir=str(temp_session_dir), session_prefix="session", enabled=True
)
with patch("vibe.acp.acp_agent_loop.VibeConfig.load") as mock_load:
mock_config = mock_load.return_value
mock_config.session_logging = session_config
response = await acp_agent.list_sessions()
assert len(response.sessions) == 1
assert response.sessions[0].session_id == "valid-se"
@pytest.mark.asyncio
async def test_list_sessions_nonexistent_save_dir(self) -> None:
acp_agent = _create_acp_agent()
session_config = SessionLoggingConfig(
save_dir="/nonexistent/path", session_prefix="session", enabled=True
)
with patch("vibe.acp.acp_agent_loop.VibeConfig.load") as mock_load:
mock_config = mock_load.return_value
mock_config.session_logging = session_config
response = await acp_agent.list_sessions()
assert response.sessions == []
@pytest.mark.asyncio
async def test_list_sessions_without_api_key(self) -> None:
acp_agent = _create_acp_agent()
with patch("vibe.acp.acp_agent_loop.VibeConfig.load") as mock_load:
mock_load.side_effect = MissingAPIKeyError("api_key", "mistral")
response = await acp_agent.list_sessions()
assert response.sessions == []

View file

@ -0,0 +1,301 @@
from __future__ import annotations
from pathlib import Path
from acp import RequestError
from acp.schema import (
AgentMessageChunk,
AgentThoughtChunk,
ToolCallProgress,
ToolCallStart,
UserMessageChunk,
)
import pytest
from tests.conftest import build_test_vibe_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.agents.models import BuiltinAgentName
from vibe.core.config import ModelConfig, SessionLoggingConfig
from vibe.core.types import Role
@pytest.fixture
def acp_agent_with_session_config(
backend: FakeBackend, temp_session_dir: Path, monkeypatch: pytest.MonkeyPatch
) -> tuple[VibeAcpAgentLoop, FakeClient]:
session_config = SessionLoggingConfig(
save_dir=str(temp_session_dir), session_prefix="session", enabled=True
)
config = build_test_vibe_config(
active_model="devstral-latest",
models=[
ModelConfig(
name="devstral-latest", provider="mistral", alias="devstral-latest"
)
],
session_logging=session_config,
)
class PatchedAgentLoop(AgentLoop):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **{**kwargs, "backend": backend})
self._base_config = config
self.agent_manager.invalidate_config()
monkeypatch.setattr("vibe.acp.acp_agent_loop.AgentLoop", PatchedAgentLoop)
monkeypatch.setattr(VibeAcpAgentLoop, "_load_config", lambda self: config)
vibe_acp_agent = VibeAcpAgentLoop()
client = FakeClient()
vibe_acp_agent.on_connect(client)
client.on_connect(vibe_acp_agent)
return vibe_acp_agent, client
class TestLoadSession:
@pytest.mark.asyncio
async def test_load_session_returns_response_with_models_and_modes(
self,
acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient],
temp_session_dir: Path,
create_test_session,
) -> None:
acp_agent, _client = acp_agent_with_session_config
session_id = "test-sess-12345678"
cwd = str(Path.cwd())
create_test_session(temp_session_dir, session_id, cwd)
response = await acp_agent.load_session(
cwd=cwd, mcp_servers=[], session_id=session_id
)
assert response is not None
assert response.models is not None
assert response.models.current_model_id == "devstral-latest"
assert response.modes is not None
assert response.modes.current_mode_id == BuiltinAgentName.DEFAULT
@pytest.mark.asyncio
async def test_load_session_registers_session_with_original_id(
self,
acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient],
temp_session_dir: Path,
create_test_session,
) -> None:
acp_agent, _client = acp_agent_with_session_config
session_id = "orig-id-12345678"
cwd = str(Path.cwd())
create_test_session(temp_session_dir, session_id, cwd)
await acp_agent.load_session(cwd=cwd, mcp_servers=[], session_id=session_id)
assert session_id in acp_agent.sessions
assert acp_agent.sessions[session_id].id == session_id
@pytest.mark.asyncio
async def test_load_session_injects_messages_into_agent_loop(
self,
acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient],
temp_session_dir: Path,
create_test_session,
) -> None:
acp_agent, _client = acp_agent_with_session_config
session_id = "msg-test-12345678"
cwd = str(Path.cwd())
messages = [
{"role": "system", "content": "You are helpful"},
{"role": "user", "content": "First question"},
{"role": "assistant", "content": "First answer"},
{"role": "user", "content": "Second question"},
{"role": "assistant", "content": "Second answer"},
]
create_test_session(temp_session_dir, session_id, cwd, messages=messages)
await acp_agent.load_session(cwd=cwd, mcp_servers=[], session_id=session_id)
session = acp_agent.sessions[session_id]
non_system = [m for m in session.agent_loop.messages if m.role != Role.system]
assert len(non_system) == 4
@pytest.mark.asyncio
async def test_load_session_replays_user_messages(
self,
acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient],
temp_session_dir: Path,
create_test_session,
) -> None:
acp_agent, client = acp_agent_with_session_config
session_id = "replay-usr-123456"
cwd = str(Path.cwd())
messages = [{"role": "user", "content": "Hello world"}]
create_test_session(temp_session_dir, session_id, cwd, messages=messages)
await acp_agent.load_session(cwd=cwd, mcp_servers=[], session_id=session_id)
user_updates = [
u for u in client._session_updates if isinstance(u.update, UserMessageChunk)
]
assert len(user_updates) == 1
assert user_updates[0].update.content.text == "Hello world"
@pytest.mark.asyncio
async def test_load_session_replays_assistant_messages(
self,
acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient],
temp_session_dir: Path,
create_test_session,
) -> None:
acp_agent, client = acp_agent_with_session_config
session_id = "replay-ast-123456"
cwd = str(Path.cwd())
messages = [
{"role": "user", "content": "Hi"},
{"role": "assistant", "content": "Hello! How can I help?"},
]
create_test_session(temp_session_dir, session_id, cwd, messages=messages)
await acp_agent.load_session(cwd=cwd, mcp_servers=[], session_id=session_id)
agent_updates = [
u
for u in client._session_updates
if isinstance(u.update, AgentMessageChunk)
]
assert len(agent_updates) == 1
assert agent_updates[0].update.content.text == "Hello! How can I help?"
@pytest.mark.asyncio
async def test_load_session_replays_tool_calls(
self,
acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient],
temp_session_dir: Path,
create_test_session,
) -> None:
acp_agent, client = acp_agent_with_session_config
session_id = "replay-tool-12345"
cwd = str(Path.cwd())
messages = [
{"role": "user", "content": "Read the file"},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_123",
"type": "function",
"function": {
"name": "read_file",
"arguments": '{"path": "/tmp/test.txt"}',
},
}
],
},
{"role": "tool", "tool_call_id": "call_123", "content": "file contents"},
]
create_test_session(temp_session_dir, session_id, cwd, messages=messages)
await acp_agent.load_session(cwd=cwd, mcp_servers=[], session_id=session_id)
tool_call_starts = [
u for u in client._session_updates if isinstance(u.update, ToolCallStart)
]
assert len(tool_call_starts) == 1
assert tool_call_starts[0].update.title == "read_file"
assert tool_call_starts[0].update.tool_call_id == "call_123"
tool_results = [
u for u in client._session_updates if isinstance(u.update, ToolCallProgress)
]
assert len(tool_results) == 1
assert tool_results[0].update.tool_call_id == "call_123"
assert tool_results[0].update.status == "completed"
@pytest.mark.asyncio
async def test_load_session_replays_reasoning_content(
self,
acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient],
temp_session_dir: Path,
create_test_session,
) -> None:
acp_agent, client = acp_agent_with_session_config
session_id = "replay-reason-123"
cwd = str(Path.cwd())
messages = [
{"role": "user", "content": "Think about this"},
{
"role": "assistant",
"content": "Here is my answer",
"reasoning_content": "Let me think step by step...",
},
]
create_test_session(temp_session_dir, session_id, cwd, messages=messages)
await acp_agent.load_session(cwd=cwd, mcp_servers=[], session_id=session_id)
thought_updates = [
u
for u in client._session_updates
if isinstance(u.update, AgentThoughtChunk)
]
assert len(thought_updates) == 1
assert thought_updates[0].update.content.text == "Let me think step by step..."
@pytest.mark.asyncio
async def test_load_session_not_found_raises_error(
self, acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient]
) -> None:
acp_agent, _client = acp_agent_with_session_config
with pytest.raises(RequestError):
await acp_agent.load_session(
cwd=str(Path.cwd()), mcp_servers=[], session_id="nonexistent-session"
)
@pytest.mark.asyncio
async def test_load_session_replays_full_conversation(
self,
acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient],
temp_session_dir: Path,
create_test_session,
) -> None:
acp_agent, client = acp_agent_with_session_config
session_id = "full-conv-1234567"
cwd = str(Path.cwd())
messages = [
{"role": "user", "content": "First message"},
{"role": "assistant", "content": "First response"},
{"role": "user", "content": "Second message"},
{"role": "assistant", "content": "Second response"},
]
create_test_session(temp_session_dir, session_id, cwd, messages=messages)
await acp_agent.load_session(cwd=cwd, mcp_servers=[], session_id=session_id)
user_updates = [
u for u in client._session_updates if isinstance(u.update, UserMessageChunk)
]
agent_updates = [
u
for u in client._session_updates
if isinstance(u.update, AgentMessageChunk)
]
assert len(user_updates) == 2
assert len(agent_updates) == 2
assert user_updates[0].update.content.text == "First message"
assert user_updates[1].update.content.text == "Second message"
assert agent_updates[0].update.content.text == "First response"
assert agent_updates[1].update.content.text == "Second response"

View file

@ -41,12 +41,18 @@ def acp_agent_loop(backend) -> VibeAcpAgentLoop:
class TestACPNewSession:
@pytest.mark.asyncio
async def test_new_session_response_structure(
self, acp_agent_loop: VibeAcpAgentLoop
self, acp_agent_loop: VibeAcpAgentLoop, telemetry_events: list[dict]
) -> None:
session_response = await acp_agent_loop.new_session(
cwd=str(Path.cwd()), mcp_servers=[]
)
new_session_events = [
e for e in telemetry_events if e.get("event_name") == "vibe/new_session"
]
assert len(new_session_events) == 1
assert new_session_events[0]["properties"]["entrypoint"] == "acp"
assert session_response.session_id is not None
acp_session = next(
(

View file

@ -0,0 +1,271 @@
from __future__ import annotations
from pathlib import Path
from unittest.mock import patch
from acp.schema import AgentMessageChunk, AvailableCommandsUpdate, TextContentBlock
import pytest
from tests.acp.conftest import _create_acp_agent
from tests.conftest import build_test_vibe_config
from tests.stubs.fake_client import FakeClient
from vibe.acp.acp_agent_loop import VibeAcpAgentLoop
from vibe.core.agent_loop import AgentLoop
@pytest.fixture
def acp_agent_loop(backend) -> VibeAcpAgentLoop:
config = build_test_vibe_config()
class PatchedAgentLoop(AgentLoop):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **{**kwargs, "backend": backend})
self._base_config = config
self.agent_manager.invalidate_config()
patch("vibe.acp.acp_agent_loop.AgentLoop", side_effect=PatchedAgentLoop).start()
return _create_acp_agent()
def _get_fake_client(acp_agent_loop: VibeAcpAgentLoop) -> FakeClient:
assert isinstance(acp_agent_loop.client, FakeClient)
return acp_agent_loop.client
class TestAvailableCommandsUpdate:
@pytest.mark.asyncio
async def test_available_commands_sent_on_new_session(
self, acp_agent_loop: VibeAcpAgentLoop
) -> None:
import asyncio
await acp_agent_loop.new_session(cwd=str(Path.cwd()), mcp_servers=[])
await asyncio.sleep(0)
updates = _get_fake_client(acp_agent_loop)._session_updates
available_commands_updates = [
u for u in updates if isinstance(u.update, AvailableCommandsUpdate)
]
assert len(available_commands_updates) == 1
update = available_commands_updates[0].update
assert len(update.available_commands) == 1
assert update.available_commands[0].name == "proxy-setup"
assert "proxy" in update.available_commands[0].description.lower()
class TestProxySetupCommand:
@pytest.mark.asyncio
async def test_proxy_setup_shows_help_when_no_args(
self,
acp_agent_loop: VibeAcpAgentLoop,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
env_file = tmp_path / ".env"
class FakeGlobalEnvFile:
path = env_file
monkeypatch.setattr(
"vibe.core.proxy_setup.GLOBAL_ENV_FILE", FakeGlobalEnvFile()
)
session_response = await acp_agent_loop.new_session(
cwd=str(Path.cwd()), mcp_servers=[]
)
session_id = session_response.session_id
_get_fake_client(acp_agent_loop)._session_updates.clear()
response = await acp_agent_loop.prompt(
prompt=[TextContentBlock(type="text", text="/proxy-setup")],
session_id=session_id,
)
assert response.stop_reason == "end_turn"
updates = _get_fake_client(acp_agent_loop)._session_updates
message_updates = [
u for u in updates if isinstance(u.update, AgentMessageChunk)
]
assert len(message_updates) == 1
content = message_updates[0].update.content.text
assert "## Proxy Configuration" in content
assert "HTTP_PROXY" in content
@pytest.mark.asyncio
async def test_proxy_setup_sets_value(
self,
acp_agent_loop: VibeAcpAgentLoop,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
env_file = tmp_path / ".env"
class FakeGlobalEnvFile:
path = env_file
monkeypatch.setattr(
"vibe.core.proxy_setup.GLOBAL_ENV_FILE", FakeGlobalEnvFile()
)
session_response = await acp_agent_loop.new_session(
cwd=str(Path.cwd()), mcp_servers=[]
)
session_id = session_response.session_id
_get_fake_client(acp_agent_loop)._session_updates.clear()
response = await acp_agent_loop.prompt(
prompt=[
TextContentBlock(
type="text", text="/proxy-setup HTTP_PROXY http://localhost:8080"
)
],
session_id=session_id,
)
assert response.stop_reason == "end_turn"
updates = _get_fake_client(acp_agent_loop)._session_updates
message_updates = [
u for u in updates if isinstance(u.update, AgentMessageChunk)
]
assert len(message_updates) == 1
content = message_updates[0].update.content.text
assert "HTTP_PROXY" in content
assert "http://localhost:8080" in content
assert env_file.exists()
env_content = env_file.read_text()
assert "HTTP_PROXY" in env_content
assert "http://localhost:8080" in env_content
@pytest.mark.asyncio
async def test_proxy_setup_unsets_value(
self,
acp_agent_loop: VibeAcpAgentLoop,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
env_file = tmp_path / ".env"
env_file.write_text("HTTP_PROXY=http://old-proxy.com\n")
class FakeGlobalEnvFile:
path = env_file
monkeypatch.setattr(
"vibe.core.proxy_setup.GLOBAL_ENV_FILE", FakeGlobalEnvFile()
)
session_response = await acp_agent_loop.new_session(
cwd=str(Path.cwd()), mcp_servers=[]
)
session_id = session_response.session_id
_get_fake_client(acp_agent_loop)._session_updates.clear()
response = await acp_agent_loop.prompt(
prompt=[TextContentBlock(type="text", text="/proxy-setup HTTP_PROXY")],
session_id=session_id,
)
assert response.stop_reason == "end_turn"
updates = _get_fake_client(acp_agent_loop)._session_updates
message_updates = [
u for u in updates if isinstance(u.update, AgentMessageChunk)
]
assert len(message_updates) == 1
content = message_updates[0].update.content.text
assert "Removed" in content
assert "HTTP_PROXY" in content
env_content = env_file.read_text()
assert "HTTP_PROXY" not in env_content
@pytest.mark.asyncio
async def test_proxy_setup_invalid_key_returns_error(
self,
acp_agent_loop: VibeAcpAgentLoop,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
env_file = tmp_path / ".env"
class FakeGlobalEnvFile:
path = env_file
monkeypatch.setattr(
"vibe.core.proxy_setup.GLOBAL_ENV_FILE", FakeGlobalEnvFile()
)
session_response = await acp_agent_loop.new_session(
cwd=str(Path.cwd()), mcp_servers=[]
)
session_id = session_response.session_id
_get_fake_client(acp_agent_loop)._session_updates.clear()
response = await acp_agent_loop.prompt(
prompt=[
TextContentBlock(type="text", text="/proxy-setup INVALID_KEY value")
],
session_id=session_id,
)
assert response.stop_reason == "end_turn"
updates = _get_fake_client(acp_agent_loop)._session_updates
message_updates = [
u for u in updates if isinstance(u.update, AgentMessageChunk)
]
assert len(message_updates) == 1
content = message_updates[0].update.content.text
assert "Error" in content
assert "Unknown key" in content
@pytest.mark.asyncio
async def test_proxy_setup_case_insensitive(
self,
acp_agent_loop: VibeAcpAgentLoop,
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
env_file = tmp_path / ".env"
class FakeGlobalEnvFile:
path = env_file
monkeypatch.setattr(
"vibe.core.proxy_setup.GLOBAL_ENV_FILE", FakeGlobalEnvFile()
)
session_response = await acp_agent_loop.new_session(
cwd=str(Path.cwd()), mcp_servers=[]
)
session_id = session_response.session_id
_get_fake_client(acp_agent_loop)._session_updates.clear()
response = await acp_agent_loop.prompt(
prompt=[
TextContentBlock(
type="text", text="/PROXY-SETUP http_proxy http://localhost:8080"
)
],
session_id=session_id,
)
assert response.stop_reason == "end_turn"
assert env_file.exists()
env_content = env_file.read_text()
assert "HTTP_PROXY" in env_content

55
tests/acp/test_utils.py Normal file
View file

@ -0,0 +1,55 @@
from __future__ import annotations
from vibe.acp.utils import get_proxy_help_text
from vibe.core.paths.global_paths import GLOBAL_ENV_FILE
from vibe.core.proxy_setup import SUPPORTED_PROXY_VARS
def _write_env_file(content: str) -> None:
GLOBAL_ENV_FILE.path.parent.mkdir(parents=True, exist_ok=True)
GLOBAL_ENV_FILE.path.write_text(content, encoding="utf-8")
class TestGetProxyHelpText:
def test_returns_string(self) -> None:
result = get_proxy_help_text()
assert isinstance(result, str)
def test_includes_proxy_configuration_header(self) -> None:
result = get_proxy_help_text()
assert "## Proxy Configuration" in result
def test_includes_usage_section(self) -> None:
result = get_proxy_help_text()
assert "### Usage:" in result
assert "/proxy-setup" in result
def test_includes_all_supported_variables(self) -> None:
result = get_proxy_help_text()
for key in SUPPORTED_PROXY_VARS:
assert f"`{key}`" in result
def test_shows_none_configured_when_no_settings(self) -> None:
result = get_proxy_help_text()
assert "(none configured)" in result
def test_shows_current_settings_when_configured(self) -> None:
_write_env_file("HTTP_PROXY=http://proxy:8080\n")
result = get_proxy_help_text()
assert "HTTP_PROXY=http://proxy:8080" in result
assert "(none configured)" not in result
def test_shows_only_set_values(self) -> None:
_write_env_file("HTTP_PROXY=http://proxy:8080\n")
result = get_proxy_help_text()
assert "HTTP_PROXY=http://proxy:8080" in result
assert "HTTPS_PROXY=" not in result