mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-19 10:22:04 +08:00
- Add get_default_tools/get_tools/get_system_prompt_parts to ComputerBooter base - Each booter subclass (ShipyardNeo, Shipyard, Boxlite) declares its own tools - ComputerToolProvider now delegates to booter API via computer_client helpers - Add unified query API: get_sandbox_tools, get_default_sandbox_tools, etc. - Extract Neo prompts to dedicated computer/prompts.py module - Add booter type constants (booters/constants.py) - Fix subagent tool path to pass sandbox_cfg and session_id - Fix Sourcery issues: shell injection in send_message, typo in prompts, internal tools bypass inactivated_llm_tools check
114 lines
3.4 KiB
Python
114 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
import functools
|
|
from typing import TYPE_CHECKING
|
|
|
|
from shipyard import ShipyardClient, Spec
|
|
|
|
from astrbot.api import logger
|
|
|
|
if TYPE_CHECKING:
|
|
from astrbot.core.agent.tool import FunctionTool
|
|
|
|
from ..olayer import FileSystemComponent, PythonComponent, ShellComponent
|
|
from .base import ComputerBooter
|
|
|
|
|
|
class ShipyardBooter(ComputerBooter):
|
|
@classmethod
|
|
@functools.cache
|
|
def _default_tools(cls) -> tuple[FunctionTool, ...]:
|
|
from astrbot.core.computer.tools import (
|
|
ExecuteShellTool,
|
|
FileDownloadTool,
|
|
FileUploadTool,
|
|
PythonTool,
|
|
)
|
|
|
|
return (
|
|
ExecuteShellTool(),
|
|
PythonTool(),
|
|
FileUploadTool(),
|
|
FileDownloadTool(),
|
|
)
|
|
|
|
@classmethod
|
|
def get_default_tools(cls) -> list[FunctionTool]:
|
|
return list(cls._default_tools())
|
|
|
|
def __init__(
|
|
self,
|
|
endpoint_url: str,
|
|
access_token: str,
|
|
ttl: int = 3600,
|
|
session_num: int = 10,
|
|
) -> None:
|
|
self._sandbox_client = ShipyardClient(
|
|
endpoint_url=endpoint_url, access_token=access_token
|
|
)
|
|
self._ttl = ttl
|
|
self._session_num = session_num
|
|
|
|
async def boot(self, session_id: str) -> None:
|
|
ship = await self._sandbox_client.create_ship(
|
|
ttl=self._ttl,
|
|
spec=Spec(cpus=1.0, memory="512m"),
|
|
max_session_num=self._session_num,
|
|
session_id=session_id,
|
|
)
|
|
logger.info(f"Got sandbox ship: {ship.id} for session: {session_id}")
|
|
self._ship = ship
|
|
|
|
async def shutdown(self) -> None:
|
|
logger.info("[Computer] Shipyard booter shutdown.")
|
|
|
|
@property
|
|
def fs(self) -> FileSystemComponent:
|
|
return self._ship.fs
|
|
|
|
@property
|
|
def python(self) -> PythonComponent:
|
|
return self._ship.python
|
|
|
|
@property
|
|
def shell(self) -> ShellComponent:
|
|
return self._ship.shell
|
|
|
|
async def upload_file(self, path: str, file_name: str) -> dict:
|
|
"""Upload file to sandbox"""
|
|
result = await self._ship.upload_file(path, file_name)
|
|
logger.info("[Computer] File uploaded to Shipyard sandbox: %s", file_name)
|
|
return result
|
|
|
|
async def download_file(self, remote_path: str, local_path: str):
|
|
"""Download file from sandbox."""
|
|
result = await self._ship.download_file(remote_path, local_path)
|
|
logger.info(
|
|
"[Computer] File downloaded from Shipyard sandbox: %s -> %s",
|
|
remote_path,
|
|
local_path,
|
|
)
|
|
return result
|
|
|
|
async def available(self) -> bool:
|
|
"""Check if the sandbox is available."""
|
|
try:
|
|
ship_id = self._ship.id
|
|
data = await self._sandbox_client.get_ship(ship_id)
|
|
if not data:
|
|
logger.info(
|
|
"[Computer] Shipyard sandbox health check: id=%s, healthy=False (no data)",
|
|
ship_id,
|
|
)
|
|
return False
|
|
health = bool(data.get("status", 0) == 1)
|
|
logger.info(
|
|
"[Computer] Shipyard sandbox health check: id=%s, healthy=%s",
|
|
ship_id,
|
|
health,
|
|
)
|
|
return health
|
|
except Exception as e:
|
|
logger.error(f"Error checking Shipyard sandbox availability: {e}")
|
|
return False
|