vibe/tests/acp/test_tool_field_meta.py
Guillaume LE GOFF cafb6d4147
v2.15.0 (#773)
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: 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: Val <102326092+vdeva@users.noreply.github.com>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-06-12 13:16:04 +02:00

297 lines
11 KiB
Python

"""Tests for field_meta and locations in ACP tool overrides."""
from __future__ import annotations
from pathlib import Path
from acp.schema import ToolCallProgress, ToolCallStart
from pydantic import BaseModel
from vibe.acp.tools.builtins.grep import Grep
from vibe.acp.tools.builtins.read import Read
from vibe.acp.tools.builtins.skill import Skill
from vibe.acp.tools.builtins.task import Task
from vibe.acp.tools.builtins.web_fetch import WebFetch
from vibe.acp.tools.builtins.web_search import WebSearch
from vibe.acp.tools.builtins.write_file import WriteFile
from vibe.acp.tools.session_update import (
tool_call_session_update,
tool_result_session_update,
)
from vibe.core.tools.builtins.grep import GrepArgs, GrepResult
from vibe.core.tools.builtins.read import ReadArgs, ReadResult
from vibe.core.tools.builtins.skill import SkillArgs, SkillResult
from vibe.core.tools.builtins.task import TaskArgs, TaskResult
from vibe.core.tools.builtins.webfetch import WebFetchArgs, WebFetchResult
from vibe.core.tools.builtins.websearch import (
WebSearchArgs,
WebSearchResult,
WebSearchSource,
)
from vibe.core.tools.builtins.write_file import WriteFileArgs, WriteFileResult
from vibe.core.types import ToolCallEvent, ToolResultEvent
def _call_event(
tool_name: str, tool_class: type, args: BaseModel | None
) -> ToolCallEvent:
return ToolCallEvent(
tool_name=tool_name, tool_call_id="tc-1", tool_class=tool_class, args=args
)
def _result_event(
tool_name: str, tool_class: type, result: BaseModel, *, error: str | None = None
) -> ToolResultEvent:
return ToolResultEvent(
tool_name=tool_name,
tool_call_id="tc-1",
tool_class=tool_class,
result=result,
error=error,
)
class TestGrepFieldMeta:
def test_call_meta_contains_query_tool_name_and_search_path(self) -> None:
event = _call_event("grep", Grep, GrepArgs(pattern="TODO", path="src"))
update = tool_call_session_update(event)
assert isinstance(update, ToolCallStart)
assert update.field_meta == {
"tool_name": "grep",
"query": "TODO",
"search_path": str(Path("src").resolve()),
}
assert update.kind == "search"
def test_call_has_no_locations_so_result_matches_are_not_replaced(self) -> None:
event = _call_event("grep", Grep, GrepArgs(pattern="TODO", path="src"))
update = tool_call_session_update(event)
assert isinstance(update, ToolCallStart)
assert update.locations is None
def test_result_locations_from_parsed_matches(self) -> None:
result = GrepResult(
matches="src/a.py:10:match\nsrc/b.py:20:other",
match_count=2,
was_truncated=False,
)
event = _result_event("grep", Grep, result)
update = tool_result_session_update(event)
assert isinstance(update, ToolCallProgress)
assert update.status == "completed"
assert update.locations is not None
assert len(update.locations) == 2
assert update.locations[0].path.endswith("src/a.py")
assert update.locations[0].line == 10
assert update.locations[1].path.endswith("src/b.py")
assert update.locations[1].line == 20
def test_fallback_on_none_args(self) -> None:
event = _call_event("grep", Grep, None)
update = tool_call_session_update(event)
assert isinstance(update, ToolCallStart)
assert update.kind == "search"
assert update.field_meta == {"tool_name": "grep"}
class TestReadFieldMeta:
def test_call_location_has_offset_and_limit(self) -> None:
event = _call_event(
"read", Read, ReadArgs(file_path="/tmp/f.txt", offset=10, limit=50)
)
update = tool_call_session_update(event)
assert isinstance(update, ToolCallStart)
assert update.locations is not None
loc = update.locations[0]
assert loc.field_meta == {"type": "file_range", "offset": 10, "limit": 50}
assert update.field_meta == {"tool_name": "read"}
def test_call_defaults_offset_none_limit_default(self) -> None:
event = _call_event("read", Read, ReadArgs(file_path="/tmp/f.txt"))
update = tool_call_session_update(event)
assert isinstance(update, ToolCallStart)
assert update.locations is not None
loc = update.locations[0]
assert loc.field_meta == {"type": "file_range", "offset": None, "limit": 2000}
def test_result_location_has_start_line_and_num_lines(self) -> None:
result = ReadResult(
file_path="/tmp/f.txt",
content=" 1→line1\n 2→line2\n 3→line3",
num_lines=3,
start_line=10,
total_lines=20,
)
event = _result_event("read", Read, result)
update = tool_result_session_update(event)
assert isinstance(update, ToolCallProgress)
assert update.locations is not None
loc = update.locations[0]
assert loc.field_meta == {"type": "file_range", "offset": 10, "limit": 3}
class TestWebSearchFieldMeta:
def test_call_meta_contains_query(self) -> None:
event = _call_event(
"web_search", WebSearch, WebSearchArgs(query="python async")
)
update = tool_call_session_update(event)
assert isinstance(update, ToolCallStart)
assert update.field_meta == {"tool_name": "web_search", "query": "python async"}
assert update.kind == "search"
def test_result_locations_are_source_urls_with_titles(self) -> None:
result = WebSearchResult(
query="python",
answer="found it",
sources=[
WebSearchSource(title="Docs", url="https://docs.python.org"),
WebSearchSource(title="Blog", url="https://blog.example.com"),
],
)
event = _result_event("web_search", WebSearch, result)
update = tool_result_session_update(event)
assert isinstance(update, ToolCallProgress)
assert update.locations is not None
assert len(update.locations) == 2
assert update.locations[0].path == "https://docs.python.org"
assert update.locations[0].field_meta == {"type": "url", "title": "Docs"}
assert update.locations[1].path == "https://blog.example.com"
assert update.locations[1].field_meta == {"type": "url", "title": "Blog"}
class TestWebFetchFieldMeta:
def test_call_location_is_normalized_url(self) -> None:
event = _call_event("web_fetch", WebFetch, WebFetchArgs(url="example.com/page"))
update = tool_call_session_update(event)
assert isinstance(update, ToolCallStart)
assert update.kind == "fetch"
assert update.locations is not None
assert update.locations[0].path == "https://example.com/page"
def test_result_location_has_char_count_and_truncated(self) -> None:
result = WebFetchResult(
url="https://example.com",
content="hello world",
content_type="text/html",
was_truncated=True,
)
event = _result_event("web_fetch", WebFetch, result)
update = tool_result_session_update(event)
assert isinstance(update, ToolCallProgress)
assert update.locations is not None
loc = update.locations[0]
assert loc.path == "https://example.com"
assert loc.field_meta == {"type": "url", "char_count": 11, "truncated": True}
class TestSkillFieldMeta:
def test_call_meta_contains_skill_name(self) -> None:
event = _call_event("skill", Skill, SkillArgs(name="debug"))
update = tool_call_session_update(event)
assert isinstance(update, ToolCallStart)
assert update.field_meta == {"tool_name": "skill", "skill_name": "debug"}
assert update.kind == "read"
def test_result_has_skill_dir_location(self) -> None:
result = SkillResult(
name="debug", content="do things", skill_dir="/home/user/.vibe/skills/debug"
)
event = _result_event("skill", Skill, result)
update = tool_result_session_update(event)
assert isinstance(update, ToolCallProgress)
assert update.locations is not None
assert len(update.locations) == 1
assert update.locations[0].path == str(
Path("/home/user/.vibe/skills/debug").resolve()
)
assert update.field_meta == {"tool_name": "skill", "skill_name": "debug"}
def test_result_no_location_when_no_skill_dir(self) -> None:
result = SkillResult(name="debug", content="do things", skill_dir=None)
event = _result_event("skill", Skill, result)
update = tool_result_session_update(event)
assert isinstance(update, ToolCallProgress)
assert update.locations is None
class TestTaskFieldMeta:
def test_call_meta_contains_agent_and_task(self) -> None:
event = _call_event(
"task", Task, TaskArgs(task="explore codebase", agent="explore")
)
update = tool_call_session_update(event)
assert isinstance(update, ToolCallStart)
assert update.field_meta == {
"tool_name": "task",
"agent": "explore",
"task": "explore codebase",
}
def test_result_meta_contains_turn_count_and_response(self) -> None:
result = TaskResult(response="found 3 files", turns_used=5, completed=True)
event = _result_event("task", Task, result)
update = tool_result_session_update(event)
assert isinstance(update, ToolCallProgress)
assert update.status == "completed"
assert update.field_meta == {
"tool_name": "task",
"turn_count": 5,
"response": "found 3 files",
}
def test_result_failed_when_not_completed(self) -> None:
result = TaskResult(response="interrupted", turns_used=2, completed=False)
event = _result_event("task", Task, result)
update = tool_result_session_update(event)
assert isinstance(update, ToolCallProgress)
assert update.status == "failed"
class TestWriteFileFieldMeta:
def test_call_meta_contains_tool_name(self) -> None:
event = _call_event(
"write_file", WriteFile, WriteFileArgs(path="out.txt", content="hello")
)
update = tool_call_session_update(event)
assert isinstance(update, ToolCallStart)
assert update.field_meta == {"tool_name": "write_file"}
assert update.kind == "edit"
def test_call_location_is_resolved_path(self) -> None:
event = _call_event(
"write_file", WriteFile, WriteFileArgs(path="out.txt", content="hello")
)
update = tool_call_session_update(event)
assert isinstance(update, ToolCallStart)
assert update.locations is not None
assert update.locations[0].path == str(Path("out.txt").resolve())
def test_result_location_is_resolved_path(self) -> None:
result = WriteFileResult(path="out.txt", content="hello", bytes_written=5)
event = _result_event("write_file", WriteFile, result)
update = tool_result_session_update(event)
assert isinstance(update, ToolCallProgress)
assert update.locations is not None
assert update.locations[0].path == str(Path("out.txt").resolve())