vibe/vibe/acp/tools/builtins/bash.py
Quentin Torroba fa15fc977b Initial commit
Co-Authored-By: Quentin Torroba <quentin.torroba@mistral.ai>
Co-Authored-By: Laure Hugo <laure.hugo@mistral.ai>
Co-Authored-By: Benjamin Trom <benjamin.trom@mistral.ai>
Co-Authored-By: Mathias Gesbert <mathias.gesbert@ext.mistral.ai>
Co-Authored-By: Michel Thomazo <michel.thomazo@mistral.ai>
Co-Authored-By: Clément Drouin <clement.drouin@mistral.ai>
Co-Authored-By: Vincent Guilloux <vincent.guilloux@mistral.ai>
Co-Authored-By: Valentin Berard <val@mistral.ai>
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2025-12-09 13:13:22 +01:00

144 lines
4.5 KiB
Python

from __future__ import annotations
import asyncio
import shlex
from acp import CreateTerminalRequest, TerminalHandle
from acp.schema import (
EnvVariable,
TerminalToolCallContent,
ToolCallProgress,
ToolCallStart,
WaitForTerminalExitResponse,
)
from vibe import VIBE_ROOT
from vibe.acp.tools.base import AcpToolState, BaseAcpTool
from vibe.core.tools.base import BaseToolState, ToolError
from vibe.core.tools.builtins.bash import Bash as CoreBashTool, BashArgs, BashResult
from vibe.core.types import ToolCallEvent, ToolResultEvent
from vibe.core.utils import logger
class AcpBashState(BaseToolState, AcpToolState):
pass
class Bash(CoreBashTool, BaseAcpTool[AcpBashState]):
prompt_path = VIBE_ROOT / "core" / "tools" / "builtins" / "prompts" / "bash.md"
state: AcpBashState
@classmethod
def _get_tool_state_class(cls) -> type[AcpBashState]:
return AcpBashState
async def run(self, args: BashArgs) -> BashResult:
connection, session_id, _ = self._load_state()
timeout = args.timeout or self.config.default_timeout
max_bytes = self.config.max_output_bytes
env, command, cmd_args = self._parse_command(args.command)
create_request = CreateTerminalRequest(
sessionId=session_id,
command=command,
args=cmd_args,
env=env,
cwd=str(self.config.effective_workdir),
outputByteLimit=max_bytes,
)
try:
terminal_handle = await connection.createTerminal(create_request)
except Exception as e:
raise ToolError(f"Failed to create terminal: {e!r}") from e
await self._send_in_progress_session_update([
TerminalToolCallContent(type="terminal", terminalId=terminal_handle.id)
])
try:
exit_response = await self._wait_for_terminal_exit(
terminal_handle, timeout, args.command
)
output_response = await terminal_handle.current_output()
return self._build_result(
command=args.command,
stdout=output_response.output,
stderr="",
returncode=exit_response.exitCode or 0,
)
finally:
try:
await terminal_handle.release()
except Exception as e:
logger.error(f"Failed to release terminal: {e!r}")
def _parse_command(
self, command_str: str
) -> tuple[list[EnvVariable], str, list[str]]:
parts = shlex.split(command_str)
env: list[EnvVariable] = []
command: str = ""
args: list[str] = []
for part in parts:
if "=" in part and not command:
key, value = part.split("=", 1)
env.append(EnvVariable(name=key, value=value))
elif not command:
command = part
else:
args.append(part)
return env, command, args
@classmethod
def get_summary(cls, args: BashArgs) -> str:
summary = f"{args.command}"
if args.timeout:
summary += f" (timeout {args.timeout}s)"
return summary
async def _wait_for_terminal_exit(
self, terminal_handle: TerminalHandle, timeout: int, command: str
) -> WaitForTerminalExitResponse:
try:
return await asyncio.wait_for(
terminal_handle.wait_for_exit(), timeout=timeout
)
except TimeoutError:
try:
await terminal_handle.kill()
except Exception as e:
logger.error(f"Failed to kill terminal: {e!r}")
raise self._build_timeout_error(command, timeout)
@classmethod
def tool_call_session_update(cls, event: ToolCallEvent) -> ToolCallStart:
if not isinstance(event.args, BashArgs):
raise ValueError(f"Unexpected tool args: {event.args}")
return ToolCallStart(
sessionUpdate="tool_call",
title=Bash.get_summary(event.args),
content=None,
toolCallId=event.tool_call_id,
kind="execute",
rawInput=event.args.model_dump_json(),
)
@classmethod
def tool_result_session_update(
cls, event: ToolResultEvent
) -> ToolCallProgress | None:
return ToolCallProgress(
sessionUpdate="tool_call_update",
toolCallId=event.tool_call_id,
status="failed" if event.error else "completed",
)