Co-authored-by: Albert Jiang <aj@mistral.ai>
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Laure Hugo <201583486+laure0303@users.noreply.github.com>
Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai>
Co-authored-by: Mert Unsal <mert.unsal@mistral.ai>
Co-authored-by: Michel Thomazo <51709227+michelTho@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: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
maiengineering 2026-07-01 19:03:09 +02:00 committed by GitHub
parent 4e495f658d
commit ac8f1a09fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
83 changed files with 1979 additions and 572 deletions

View file

@ -29,6 +29,7 @@ from vibe.core.teleport.types import (
# agent manager re-derives config via model_dump(); the default url is unavoidable.
SESSIONS_BASE_URL = "https://chat.mistral.ai"
SESSIONS_URL = f"{SESSIONS_BASE_URL}{TELEPORT_SESSIONS_PATH}"
GITHUB_REMOTE_URL = "https://github.com/owner/repo.git"
def _sessions_ok() -> httpx.Response:
@ -46,27 +47,28 @@ def _sessions_ok() -> httpx.Response:
def _commit(repo: Repo, message: str) -> str:
(Path(repo.working_dir) / "file.txt").write_text(f"{message}\n")
repo.index.add(["file.txt"])
repo.index.commit(message)
repo.git.add("file.txt")
repo.git.commit("-m", message)
return repo.head.commit.hexsha
def _init_repo(workdir: Path) -> Repo:
# origin is a local bare repo so fetch/push stay offline and instant; a
# separate github-url remote satisfies Teleport's GitHub-only detection.
# origin is intentionally not GitHub. The GitHub-looking `hub` remote is
# rewritten to a local bare repo so fetch/push stay offline and instant.
bare = Repo.init(workdir.with_name(f"{workdir.name}_origin.git"), bare=True)
repo = Repo.init(workdir, initial_branch="work")
repo.config_writer().set_value("user", "name", "Tester").release()
repo.config_writer().set_value("user", "email", "t@example.com").release()
repo.create_remote("origin", str(bare.git_dir))
repo.create_remote("hub", "https://github.com/owner/repo.git")
repo.git.config(f"url.{bare.git_dir}.insteadOf", GITHUB_REMOTE_URL)
repo.create_remote("hub", GITHUB_REMOTE_URL)
return repo
def _repo_with_pushed_branch(workdir: Path) -> Repo:
repo = _init_repo(workdir)
_commit(repo, "initial")
repo.git.push("origin", "work")
repo.git.push("hub", "work")
return repo
@ -150,7 +152,7 @@ async def test_teleport_pushes_then_completes_when_approved(
TeleportStartingWorkflowEvent,
TeleportCompleteEvent,
]
assert repo.remote("origin").refs["work"].commit.hexsha == head
assert repo.remote("hub").refs["work"].commit.hexsha == head
@pytest.mark.asyncio
@ -172,7 +174,7 @@ async def test_teleport_fails_when_push_fails(
) -> None:
repo = _repo_with_pushed_branch(tmp_working_directory)
_commit(repo, "second")
repo.git.remote("set-url", "--push", "origin", "/nonexistent/repo.git")
repo.git.remote("set-url", "--push", "hub", "/nonexistent/repo.git")
with pytest.raises(TeleportError, match="Failed to push"):
await _drain(build_e2e_agent_loop(), "ship it", approve=True)

View file

@ -40,6 +40,18 @@ def _get_auto_compact_properties(
return cast(dict[str, object], auto_compact[0]["properties"])
def _get_compaction_failed_properties(
telemetry_events: list[dict[str, object]],
) -> dict[str, object]:
failed = [
event
for event in telemetry_events
if event.get("event_name") == "vibe.compaction_failed"
]
assert len(failed) == 1
return cast(dict[str, object], failed[0]["properties"])
@pytest.mark.asyncio
async def test_auto_compact_emits_correct_events(telemetry_events: list[dict]) -> None:
backend = FakeBackend([
@ -306,7 +318,9 @@ async def test_compact_without_extra_instructions_has_no_additional_section() ->
@pytest.mark.asyncio
async def test_compact_raises_on_tool_call_when_flag_enabled() -> None:
async def test_compact_raises_on_tool_call_when_flag_enabled(
telemetry_events: list[dict],
) -> None:
"""With the flag on, a compaction that returns a tool call raises."""
backend = FakeBackend([
[
@ -333,10 +347,13 @@ async def test_compact_raises_on_tool_call_when_flag_enabled() -> None:
with pytest.raises(CompactionFailedError) as exc_info:
await agent.compact()
assert exc_info.value.reason == "tool_call"
assert _get_compaction_failed_properties(telemetry_events)["reason"] == "tool_call"
@pytest.mark.asyncio
async def test_compact_raises_on_empty_summary_when_flag_enabled() -> None:
async def test_compact_raises_on_empty_summary_when_flag_enabled(
telemetry_events: list[dict],
) -> None:
"""With the flag on, a compaction with empty content raises."""
backend = FakeBackend([[mock_llm_chunk(content=" ")]])
cfg = build_test_vibe_config(
@ -350,11 +367,20 @@ async def test_compact_raises_on_empty_summary_when_flag_enabled() -> None:
with pytest.raises(CompactionFailedError) as exc_info:
await agent.compact()
assert exc_info.value.reason == "empty_summary"
assert (
_get_compaction_failed_properties(telemetry_events)["reason"] == "empty_summary"
)
@pytest.mark.asyncio
async def test_compact_falls_back_when_flag_disabled() -> None:
"""With the flag off (default), empty content uses the legacy fallback."""
async def test_compact_falls_back_when_flag_disabled(
telemetry_events: list[dict],
) -> None:
"""With the flag off (default), empty content uses the legacy fallback.
The compaction failure telemetry event must still be sent even though the
flag is off and compact() returns normally.
"""
backend = FakeBackend([[mock_llm_chunk(content="")]])
cfg = build_test_vibe_config(models=make_test_models(auto_compact_threshold=999))
agent = build_test_agent_loop(config=cfg, backend=backend)
@ -363,6 +389,9 @@ async def test_compact_falls_back_when_flag_disabled() -> None:
summary = await agent.compact()
assert summary == "(no summary available)"
assert (
_get_compaction_failed_properties(telemetry_events)["reason"] == "empty_summary"
)
@pytest.mark.asyncio

View file

@ -17,9 +17,10 @@ from tests.conftest import (
)
from tests.mock.utils import mock_llm_chunk
from tests.stubs.fake_backend import FakeBackend
from vibe import __version__
from vibe.core.agents.models import BuiltinAgentName
from vibe.core.config import ModelConfig, ProviderConfig, VibeConfig
from vibe.core.telemetry.types import EntrypointMetadata
from vibe.core.telemetry.types import LaunchContext, TerminalEmulator
from vibe.core.tools.base import BaseToolConfig, ToolPermission
from vibe.core.types import (
Backend,
@ -32,6 +33,7 @@ from vibe.core.types import (
StopInfo,
ToolCall,
)
from vibe.core.utils import get_platform_id, get_platform_version
def _two_model_vibe_config(active_model: str) -> VibeConfig:
@ -191,19 +193,20 @@ async def test_passes_parent_session_id_to_backend_after_reset(vibe_config: Vibe
@pytest.mark.asyncio
async def test_passes_entrypoint_metadata_to_backend(vibe_config: VibeConfig):
metadata = EntrypointMetadata(
async def test_passes_launch_context_to_backend(vibe_config: VibeConfig):
launch_context = LaunchContext(
agent_entrypoint="acp",
agent_version="2.0.0",
client_name="vibe_ide",
client_version="0.5.0",
terminal_emulator=TerminalEmulator.GHOSTTY,
)
backend = FakeBackend([mock_llm_chunk(content="Response")])
agent = build_test_agent_loop(
config=vibe_config,
backend=backend,
enable_streaming=True,
entrypoint_metadata=metadata,
launch_context=launch_context,
)
[_ async for _ in agent.act("Hello")]
@ -215,6 +218,13 @@ async def test_passes_entrypoint_metadata_to_backend(vibe_config: VibeConfig):
assert meta["agent_version"] == "2.0.0"
assert meta["client_name"] == "vibe_ide"
assert meta["client_version"] == "0.5.0"
assert meta["os"] == get_platform_id()
if os_version := get_platform_version():
assert meta["os_version"] == os_version
else:
assert "os_version" not in meta
assert meta["version"] == __version__
assert meta["terminal_emulator"] == TerminalEmulator.GHOSTTY
assert meta["session_id"] == agent.session_id
assert "message_id" in meta
assert meta["call_type"] == "main_call"
@ -272,11 +282,12 @@ async def test_mcp_sampling_handler_uses_updated_config_when_agent_config_change
@pytest.mark.asyncio
async def test_mcp_sampling_handler_sends_secondary_call_telemetry_metadata():
metadata = EntrypointMetadata(
launch_context = LaunchContext(
agent_entrypoint="acp",
agent_version="2.0.0",
client_name="vibe_ide",
client_version="0.5.0",
terminal_emulator=TerminalEmulator.GHOSTTY,
)
backend = FakeBackend([
[mock_llm_chunk(content="Response")],
@ -285,7 +296,7 @@ async def test_mcp_sampling_handler_sends_secondary_call_telemetry_metadata():
agent = build_test_agent_loop(
config=_two_model_vibe_config("devstral-latest"),
backend=backend,
entrypoint_metadata=metadata,
launch_context=launch_context,
)
[_ async for _ in agent.act("Hello")]
@ -302,6 +313,13 @@ async def test_mcp_sampling_handler_sends_secondary_call_telemetry_metadata():
assert sampling_metadata["agent_version"] == "2.0.0"
assert sampling_metadata["client_name"] == "vibe_ide"
assert sampling_metadata["client_version"] == "0.5.0"
assert sampling_metadata["os"] == get_platform_id()
if os_version := get_platform_version():
assert sampling_metadata["os_version"] == os_version
else:
assert "os_version" not in sampling_metadata
assert sampling_metadata["version"] == __version__
assert sampling_metadata["terminal_emulator"] == TerminalEmulator.GHOSTTY
assert sampling_metadata["session_id"] == agent.session_id
assert sampling_metadata["parent_session_id"] == "parent-session-456"
assert sampling_metadata["message_id"] == next(

View file

@ -344,6 +344,15 @@ class TestAgentProfileOverrides:
overrides = BUILTIN_AGENTS[BuiltinAgentName.AUTO_APPROVE].overrides
assert overrides.get("bypass_tool_permissions") is True
def test_lean_agent_keeps_tool_permissions_configurable(self) -> None:
overrides = BUILTIN_AGENTS[BuiltinAgentName.LEAN].overrides
assert "bypass_tool_permissions" not in overrides
def test_lean_agent_uses_latest_model(self) -> None:
overrides = BUILTIN_AGENTS[BuiltinAgentName.LEAN].overrides
models = overrides["models"]
assert models[0]["name"] == "labs-leanstral-1-5"
def test_plan_agent_restricts_tools(self) -> None:
overrides = BUILTIN_AGENTS[BuiltinAgentName.PLAN].overrides
assert "tools" in overrides

View file

@ -16,7 +16,7 @@ from tests.stubs.fake_mcp_registry import FakeMCPRegistry
from vibe.core import agent_loop as agent_loop_module
from vibe.core.agent_loop import AgentLoop
from vibe.core.config import MCPStdio
from vibe.core.telemetry.types import TerminalEmulator
from vibe.core.telemetry.types import LaunchContext, TerminalEmulator
from vibe.core.tools.manager import ToolManager
from vibe.core.tools.mcp import AuthStatus
from vibe.core.tools.remote import RemoteTool
@ -371,7 +371,15 @@ class TestStartInitializeExperiments:
@pytest.mark.asyncio
async def test_refreshes_system_prompt_when_experiments_update(self) -> None:
loop = build_test_agent_loop(terminal_emulator=TerminalEmulator.VSCODE)
loop = build_test_agent_loop(
launch_context=LaunchContext(
agent_entrypoint="cli",
agent_version="1.0.0",
client_name="vibe_cli",
client_version="1.0.0",
terminal_emulator=TerminalEmulator.VSCODE,
)
)
refresh_mock = AsyncMock()
init_mock = AsyncMock(return_value=True)
@ -387,21 +395,31 @@ class TestStartInitializeExperiments:
init_mock.assert_awaited_once()
init_args = init_mock.await_args
assert init_args is not None
assert init_args.kwargs["terminal_emulator"] is TerminalEmulator.VSCODE
assert (
init_args.kwargs["launch_context"].terminal_emulator
is TerminalEmulator.VSCODE
)
def test_new_session_telemetry_uses_provided_terminal_emulator(self) -> None:
loop = build_test_agent_loop(terminal_emulator=TerminalEmulator.VSCODE)
send_new_session = MagicMock()
loop = build_test_agent_loop(
launch_context=LaunchContext(
agent_entrypoint="cli",
agent_version="1.0.0",
client_name="vibe_cli",
client_version="1.0.0",
terminal_emulator=TerminalEmulator.VSCODE,
)
)
send_event = MagicMock()
with patch.object(
loop.telemetry_client, "send_new_session", new=send_new_session
loop.telemetry_client, "send_telemetry_event", new=send_event
):
loop.emit_new_session_telemetry()
assert (
send_new_session.call_args.kwargs["terminal_emulator"]
is TerminalEmulator.VSCODE
)
payload = send_event.call_args.args[1]
assert payload["terminal_emulator"] == "vscode"
assert type(payload["terminal_emulator"]) is str
@pytest.mark.asyncio
async def test_does_not_refresh_system_prompt_when_experiments_unchanged(