vibe/vibe/core/tools/ui.py
Mathias Gesbert dd372ce494
v2.4.0 (#470)
Co-authored-by: Quentin Torroba <quentin.torroba@mistral.ai>
Co-authored-by: Kim-Adeline Miguel <kimadeline.miguel@mistral.ai>
Co-authored-by: Vincent Guilloux <vincent.guilloux@mistral.ai>
Co-authored-by: Clement Sirieix <clem.sirieix@gmail.com>
Co-authored-by: Antoine W <antoine.wronka@mistral.ai>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-03-09 19:28:09 +01:00

121 lines
4.1 KiB
Python

from __future__ import annotations
from abc import ABC, abstractmethod
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, cast
from pydantic import BaseModel, Field
if TYPE_CHECKING:
from vibe.core.types import ToolCallEvent, ToolResultEvent
class ToolCallDisplay(BaseModel):
summary: str # Brief description: "Writing file.txt", "Patching code.py"
content: str | None = None # Optional content preview
class ToolResultDisplay(BaseModel):
success: bool
message: str
warnings: list[str] = Field(default_factory=list)
class ToolUIData[TArgs: BaseModel, TResult: BaseModel](ABC):
@classmethod
def _display_name(cls) -> str:
get_name = cast(Callable[[], str] | None, getattr(cls, "get_name", None))
return get_name() if get_name is not None else cls.__name__.lower()
@classmethod
def get_no_args_display(cls) -> ToolCallDisplay:
return ToolCallDisplay(summary=cls._display_name())
@classmethod
def get_invalid_args_display(cls) -> ToolCallDisplay:
return ToolCallDisplay(summary="Invalid Arguments")
@classmethod
def format_call_display(cls, args: TArgs) -> ToolCallDisplay:
return ToolCallDisplay(summary=cls._display_name())
@classmethod
def get_call_display(cls, event: ToolCallEvent) -> ToolCallDisplay:
if event.args is None:
return cls.get_no_args_display()
introspect = cast(
Callable[[], tuple[type, ...]] | None,
getattr(cls, "_get_tool_args_results", None),
)
if introspect is not None:
expected_type = introspect()[0]
if not isinstance(event.args, expected_type):
return cls.get_invalid_args_display()
return cls.format_call_display(cast(TArgs, event.args))
@classmethod
def format_result_display(cls, result: TResult) -> ToolResultDisplay:
return ToolResultDisplay(success=True, message="Success")
@classmethod
def get_result_display(cls, event: ToolResultEvent) -> ToolResultDisplay:
if event.result is None:
return ToolResultDisplay(success=True, message="Success")
introspect = cast(
Callable[[], tuple[type, ...]] | None,
getattr(cls, "_get_tool_args_results", None),
)
if introspect is not None:
expected_type = introspect()[1]
if not isinstance(event.result, expected_type):
return ToolResultDisplay(success=True, message="Success")
return cls.format_result_display(cast(TResult, event.result))
@classmethod
@abstractmethod
def get_status_text(cls) -> str: ...
class ToolUIDataAdapter:
def __init__(self, tool_class: Any) -> None:
self.tool_class = tool_class
self.ui_data_class: type[ToolUIData[Any, Any]] | None = (
tool_class if issubclass(tool_class, ToolUIData) else None
)
def get_call_display(self, event: ToolCallEvent) -> ToolCallDisplay:
if self.ui_data_class:
return self.ui_data_class.get_call_display(event)
args_dict = (
event.args.model_dump()
if event.args and hasattr(event.args, "model_dump")
else {}
)
args_str = ", ".join(f"{k}={v!r}" for k, v in list(args_dict.items())[:3])
return ToolCallDisplay(summary=f"{event.tool_name}({args_str})")
def get_result_display(self, event: ToolResultEvent) -> ToolResultDisplay:
if event.error:
return ToolResultDisplay(success=False, message=event.error)
if event.skipped:
return ToolResultDisplay(
success=False, message=event.skip_reason or "Skipped"
)
if self.ui_data_class:
return self.ui_data_class.get_result_display(event)
return ToolResultDisplay(success=True, message="Success")
def get_status_text(self) -> str:
if self.ui_data_class:
return self.ui_data_class.get_status_text()
tool_name = getattr(self.tool_class, "get_name", lambda: "tool")()
return f"Running {tool_name}"