Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai>
Co-authored-by: Cyprien <courtot.c@gmail.com>
Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com>
Co-authored-by: Jean Burellier <sheplu@users.noreply.github.com>
Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com>
Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai>
Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@users.noreply.github.com>
Co-authored-by: Paul VEZIA <166131032+le-codeur-rapide@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: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: josephine-delas <57808586+josephine-delas@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Laure Hugo 2026-06-25 16:08:45 +02:00 committed by GitHub
parent 725d3a56ce
commit e607ccbb00
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
242 changed files with 7372 additions and 1974 deletions

View file

@ -99,6 +99,7 @@ async def test_acp_initialize(binary: Path) -> None:
env = os.environ.copy()
env["VIBE_HOME"] = str(vibe_home)
env["MISTRAL_API_KEY"] = "smoke-test-mock-key"
env["VIBE_TEST_DISABLE_KEYRING"] = "1"
proc = await asyncio.create_subprocess_exec(
str(binary),

View file

@ -206,6 +206,7 @@ async def get_acp_agent_loop_process(
env.update(mock_env)
env["MISTRAL_API_KEY"] = "mock"
env["VIBE_HOME"] = str(vibe_home)
env["VIBE_TEST_DISABLE_KEYRING"] = "1"
process = await asyncio.create_subprocess_exec(
*cmd,

View file

@ -105,6 +105,7 @@ def _build_env(vibe_home_dir: Path, *, include_api_key: bool) -> dict[str, str]:
env = os.environ.copy()
env["PYTHONUNBUFFERED"] = "1"
env["VIBE_HOME"] = str(vibe_home_dir)
env["VIBE_TEST_DISABLE_KEYRING"] = "1"
vibe_home_dir.mkdir(parents=True, exist_ok=True)
config_file = vibe_home_dir / "config.toml"

View file

@ -260,7 +260,7 @@ class TestACPAuthSignOut:
@pytest.mark.asyncio
async def test_removes_keyring_key(self, monkeypatch: pytest.MonkeyPatch) -> None:
deleted: list[str] = []
deleted: list[tuple[str, str]] = []
monkeypatch.delenv(DEFAULT_MISTRAL_API_ENV_KEY, raising=False)
monkeypatch.setattr(
keyring, "get_password", lambda service, username: "keyring-key"
@ -268,14 +268,17 @@ class TestACPAuthSignOut:
monkeypatch.setattr(
keyring,
"delete_password",
lambda service, username: deleted.append(username),
lambda service, username: deleted.append((service, username)),
)
acp_agent_loop = build_acp_agent_loop(build_mistral_provider())
response = await acp_agent_loop.ext_method("auth/signOut", {})
assert response == {}
assert deleted == [DEFAULT_MISTRAL_API_ENV_KEY]
assert deleted == [
("ai.mistral.vibe", DEFAULT_MISTRAL_API_ENV_KEY),
("vibe", DEFAULT_MISTRAL_API_ENV_KEY),
]
@pytest.mark.asyncio
async def test_surfaces_internal_error_when_keyring_delete_fails(

View file

@ -290,6 +290,37 @@ class TestAcpBashTimeout:
assert str(exc_info.value) == "Command timed out after 1s: 'slow_command'"
assert custom_handle._killed
@pytest.mark.asyncio
async def test_run_timeout_bounded_when_kill_hangs(
self, mock_client: MockClient, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setattr(
"vibe.acp.tools.builtins.bash._TERMINAL_CLEANUP_TIMEOUT", 0.05
)
custom_handle = MockTerminalHandle(
terminal_id="hanging_kill_terminal", wait_delay=20
)
mock_client._terminal_handle = custom_handle
async def hanging_kill() -> None:
await asyncio.sleep(30)
custom_handle.kill = hanging_kill
tool = Bash(
config_getter=lambda: BashToolConfig(),
state=AcpBashState.model_construct(
client=mock_client, session_id="test_session"
),
)
args = BashArgs(command="slow_command", timeout=1)
with pytest.raises(ToolError) as exc_info:
await asyncio.wait_for(collect_result(tool.run(args)), timeout=5)
assert str(exc_info.value) == "Command timed out after 1s: 'slow_command'"
@pytest.mark.asyncio
async def test_run_timeout_handles_kill_failure(
self, mock_client: MockClient

View file

@ -156,6 +156,34 @@ class TestACPForkSession:
forked_session = acp_agent_loop.sessions[response.session_id]
assert forked_session.agent_loop.parent_session_id == source_session.id
@pytest.mark.asyncio
async def test_fork_session_inherits_limits(
self, acp_agent_loop: VibeAcpAgentLoop
) -> None:
session_response = await acp_agent_loop.new_session(
cwd=str(Path.cwd()), mcp_servers=[]
)
source_session = acp_agent_loop.sessions[session_response.session_id]
await acp_agent_loop.set_config_option(
session_id=source_session.id, config_id="max_turns", value="42"
)
await acp_agent_loop.set_config_option(
session_id=source_session.id, config_id="max_tokens", value="8192"
)
source_session.agent_loop._max_price = 1.5
source_session.agent_loop._max_session_tokens = 100_000
response = await acp_agent_loop.fork_session(
cwd=str(Path.cwd()), session_id=source_session.id, mcp_servers=[]
)
forked = acp_agent_loop.sessions[response.session_id].agent_loop
assert forked._max_turns == 42
assert forked._max_tokens == 8192
assert forked._max_price == 1.5
assert forked._max_session_tokens == 100_000
@pytest.mark.asyncio
async def test_fork_session_rejects_running_session(
self, acp_agent_loop: VibeAcpAgentLoop

View file

@ -72,7 +72,7 @@ class TestACPInitialize:
),
)
assert response.agent_info == Implementation(
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.17.1"
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.18.0"
)
assert response.auth_methods is not None
@ -172,7 +172,7 @@ class TestACPInitialize:
),
)
assert response.agent_info == Implementation(
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.17.1"
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.18.0"
)
assert response.auth_methods is not None

View file

@ -42,6 +42,7 @@ def acp_agent_with_session_config(
),
],
session_logging=session_config,
include_project_context=True,
)
class PatchedAgentLoop(AgentLoop):

View file

@ -35,6 +35,7 @@ def _workspace_trust_meta(session_response: NewSessionResponse) -> dict:
def acp_agent_loop(backend) -> VibeAcpAgentLoop:
config = build_test_vibe_config(
active_model="devstral-latest",
include_project_context=True,
models=[
ModelConfig(
name="devstral-latest", provider="mistral", alias="devstral-latest"

View file

@ -528,3 +528,67 @@ class TestACPSetConfigOptionMaxTurns:
]
assert len(turn_limits) == 1
assert turn_limits[0].max_turns == 200
class TestACPSetConfigOptionMaxTokens:
@pytest.mark.asyncio
async def test_set_config_option_max_tokens_success(
self, acp_agent_loop: VibeAcpAgentLoop
) -> None:
session_response = await acp_agent_loop.new_session(
cwd=str(Path.cwd()), mcp_servers=[]
)
session_id = session_response.session_id
acp_session = next(
(s for s in acp_agent_loop.sessions.values() if s.id == session_id), None
)
assert acp_session is not None
response = await acp_agent_loop.set_config_option(
session_id=session_id, config_id="max_tokens", value="8192"
)
assert response is not None
assert acp_session.agent_loop._max_tokens == 8192
@pytest.mark.asyncio
async def test_set_config_option_max_tokens_invalid_string_returns_none(
self, acp_agent_loop: VibeAcpAgentLoop
) -> None:
session_response = await acp_agent_loop.new_session(
cwd=str(Path.cwd()), mcp_servers=[]
)
session_id = session_response.session_id
acp_session = next(
(s for s in acp_agent_loop.sessions.values() if s.id == session_id), None
)
assert acp_session is not None
initial_max_tokens = acp_session.agent_loop._max_tokens
response = await acp_agent_loop.set_config_option(
session_id=session_id, config_id="max_tokens", value="abc"
)
assert response is None
assert acp_session.agent_loop._max_tokens == initial_max_tokens
@pytest.mark.asyncio
async def test_set_config_option_max_tokens_bool_returns_none(
self, acp_agent_loop: VibeAcpAgentLoop
) -> None:
session_response = await acp_agent_loop.new_session(
cwd=str(Path.cwd()), mcp_servers=[]
)
session_id = session_response.session_id
acp_session = next(
(s for s in acp_agent_loop.sessions.values() if s.id == session_id), None
)
assert acp_session is not None
initial_max_tokens = acp_session.agent_loop._max_tokens
response = await acp_agent_loop.set_config_option(
session_id=session_id, config_id="max_tokens", value=True
)
assert response is None
assert acp_session.agent_loop._max_tokens == initial_max_tokens