Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai> Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com> Co-authored-by: Hdandria <henri.dandria@mistral.ai> Co-authored-by: Ivana Dunisijevic <ivana.dunisijevic@mistral.ai> Co-authored-by: Jean Burellier <sheplu@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: Val <102326092+vdeva@users.noreply.github.com> Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com> Co-authored-by: renovate-mistral[bot] <253709520+renovate-mistral[bot]@users.noreply.github.com> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
144 lines
4.5 KiB
Python
144 lines
4.5 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from acp.schema import TextContentBlock
|
|
from pydantic import ValidationError
|
|
import pytest
|
|
|
|
from tests.stubs.fake_backend import FakeBackend
|
|
from tests.stubs.fake_client import FakeClient
|
|
from vibe.acp.acp_agent_loop import VibeAcpAgentLoop
|
|
from vibe.acp.exceptions import InvalidRequestError
|
|
from vibe.acp.user_display_content import (
|
|
USER_DISPLAY_CONTENT_META_KEY,
|
|
parse_user_display_content_metadata,
|
|
)
|
|
from vibe.core.session.session_loader import SessionLoader
|
|
from vibe.core.types import Role, UserDisplayContentMetadata
|
|
|
|
|
|
def _metadata_payload() -> dict[str, object]:
|
|
return {
|
|
"version": "1.0.0",
|
|
"host": "mistral-vscode",
|
|
"content": [
|
|
{"type": "text", "text": "Look at "},
|
|
{
|
|
"type": "workspace_mention",
|
|
"kind": "file",
|
|
"uri": "file:///repo/src/app.ts",
|
|
"name": "app.ts",
|
|
},
|
|
],
|
|
}
|
|
|
|
|
|
def _metadata_kwargs(value: object) -> dict[str, Any]:
|
|
return {USER_DISPLAY_CONTENT_META_KEY: value}
|
|
|
|
|
|
def test_user_display_content_meta_key_is_snake_case() -> None:
|
|
assert USER_DISPLAY_CONTENT_META_KEY == "user_display_content"
|
|
|
|
|
|
def test_parse_returns_none_when_metadata_is_missing() -> None:
|
|
assert parse_user_display_content_metadata(None) is None
|
|
|
|
|
|
def test_parse_validates_present_metadata() -> None:
|
|
metadata = parse_user_display_content_metadata({
|
|
"version": "1.0.0",
|
|
"host": "mistral-vscode",
|
|
"content": [],
|
|
})
|
|
|
|
assert metadata == UserDisplayContentMetadata(
|
|
version="1.0.0", host="mistral-vscode", content=[]
|
|
)
|
|
|
|
|
|
def test_parse_rejects_invalid_metadata() -> None:
|
|
with pytest.raises(ValidationError):
|
|
parse_user_display_content_metadata({
|
|
"version": 2,
|
|
"host": "mistral-vscode",
|
|
"content": [],
|
|
})
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prompt_attaches_user_display_content_to_user_message(
|
|
acp_agent_loop: VibeAcpAgentLoop, backend: FakeBackend
|
|
) -> None:
|
|
session_response = await acp_agent_loop.new_session(
|
|
cwd=str(Path.cwd()), mcp_servers=[]
|
|
)
|
|
payload = _metadata_payload()
|
|
|
|
response = await acp_agent_loop.prompt(
|
|
prompt=[TextContentBlock(type="text", text="Look at app.ts")],
|
|
session_id=session_response.session_id,
|
|
**_metadata_kwargs(payload),
|
|
)
|
|
|
|
assert response.stop_reason == "end_turn"
|
|
user_message = next(
|
|
(msg for msg in backend.requests_messages[0] if msg.role == Role.user), None
|
|
)
|
|
assert user_message is not None
|
|
assert user_message.content == "Look at app.ts"
|
|
assert (
|
|
user_message.user_display_content
|
|
== UserDisplayContentMetadata.model_validate(payload)
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prompt_rejects_invalid_user_display_content(
|
|
acp_agent_loop: VibeAcpAgentLoop,
|
|
) -> None:
|
|
session_response = await acp_agent_loop.new_session(
|
|
cwd=str(Path.cwd()), mcp_servers=[]
|
|
)
|
|
|
|
with pytest.raises(InvalidRequestError, match="Invalid user display content"):
|
|
await acp_agent_loop.prompt(
|
|
prompt=[TextContentBlock(type="text", text="Look at app.ts")],
|
|
session_id=session_response.session_id,
|
|
**_metadata_kwargs({"version": 2, "host": "mistral-vscode", "content": []}),
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_prompt_persists_user_display_content(
|
|
acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient],
|
|
temp_session_dir: Path,
|
|
) -> None:
|
|
acp_agent, _client = acp_agent_with_session_config
|
|
session_response = await acp_agent.new_session(cwd=str(Path.cwd()), mcp_servers=[])
|
|
payload = _metadata_payload()
|
|
|
|
await acp_agent.prompt(
|
|
prompt=[TextContentBlock(type="text", text="Look at app.ts")],
|
|
session_id=session_response.session_id,
|
|
**_metadata_kwargs(payload),
|
|
)
|
|
|
|
session_dir = next(temp_session_dir.glob("session_*"))
|
|
messages_file = session_dir / "messages.jsonl"
|
|
messages = [
|
|
json.loads(line)
|
|
for line in messages_file.read_text(encoding="utf-8").splitlines()
|
|
]
|
|
user_message = next(msg for msg in messages if msg["role"] == "user")
|
|
|
|
assert user_message["user_display_content"] == payload
|
|
|
|
loaded_messages, _metadata = SessionLoader.load_session(session_dir)
|
|
loaded_user_message = next(msg for msg in loaded_messages if msg.role == Role.user)
|
|
assert loaded_user_message.user_display_content == (
|
|
UserDisplayContentMetadata.model_validate(payload)
|
|
)
|