| import json |
| from dataclasses import dataclass, field |
| from typing import Any |
|
|
| from astrbot.api import FunctionTool |
| from astrbot.core.agent.run_context import ContextWrapper |
| from astrbot.core.agent.tool import ToolExecResult |
| from astrbot.core.astr_agent_context import AstrAgentContext |
|
|
| from ..computer_client import get_booter |
|
|
|
|
| def _to_json(data: Any) -> str: |
| return json.dumps(data, ensure_ascii=False, default=str) |
|
|
|
|
| def _ensure_admin(context: ContextWrapper[AstrAgentContext]) -> str | None: |
| if context.context.event.role != "admin": |
| return ( |
| "error: Permission denied. Browser and skill lifecycle tools are only allowed " |
| "for admin users." |
| ) |
| return None |
|
|
|
|
| async def _get_browser_component(context: ContextWrapper[AstrAgentContext]) -> Any: |
| booter = await get_booter( |
| context.context.context, |
| context.context.event.unified_msg_origin, |
| ) |
| browser = getattr(booter, "browser", None) |
| if browser is None: |
| raise RuntimeError( |
| "Current sandbox booter does not support browser capability. " |
| "Please switch to shipyard_neo." |
| ) |
| return browser |
|
|
|
|
| @dataclass |
| class BrowserExecTool(FunctionTool): |
| name: str = "astrbot_execute_browser" |
| description: str = "Execute one browser automation command in the sandbox." |
| parameters: dict = field( |
| default_factory=lambda: { |
| "type": "object", |
| "properties": { |
| "cmd": {"type": "string", "description": "Browser command to execute."}, |
| "timeout": {"type": "integer", "default": 30}, |
| "description": { |
| "type": "string", |
| "description": "Optional execution description.", |
| }, |
| "tags": {"type": "string", "description": "Optional tags."}, |
| "learn": { |
| "type": "boolean", |
| "description": "Whether to mark execution as learn evidence.", |
| "default": False, |
| }, |
| "include_trace": { |
| "type": "boolean", |
| "description": "Whether to include trace_ref in response.", |
| "default": False, |
| }, |
| }, |
| "required": ["cmd"], |
| } |
| ) |
|
|
| async def call( |
| self, |
| context: ContextWrapper[AstrAgentContext], |
| cmd: str, |
| timeout: int = 30, |
| description: str | None = None, |
| tags: str | None = None, |
| learn: bool = False, |
| include_trace: bool = False, |
| ) -> ToolExecResult: |
| if err := _ensure_admin(context): |
| return err |
| try: |
| browser = await _get_browser_component(context) |
| result = await browser.exec( |
| cmd=cmd, |
| timeout=timeout, |
| description=description, |
| tags=tags, |
| learn=learn, |
| include_trace=include_trace, |
| ) |
| return _to_json(result) |
| except Exception as e: |
| return f"Error executing browser command: {str(e)}" |
|
|
|
|
| @dataclass |
| class BrowserBatchExecTool(FunctionTool): |
| name: str = "astrbot_execute_browser_batch" |
| description: str = "Execute a browser command batch in the sandbox." |
| parameters: dict = field( |
| default_factory=lambda: { |
| "type": "object", |
| "properties": { |
| "commands": { |
| "type": "array", |
| "items": {"type": "string"}, |
| "description": "Ordered browser commands.", |
| }, |
| "timeout": {"type": "integer", "default": 60}, |
| "stop_on_error": {"type": "boolean", "default": True}, |
| "description": { |
| "type": "string", |
| "description": "Optional execution description.", |
| }, |
| "tags": {"type": "string", "description": "Optional tags."}, |
| "learn": { |
| "type": "boolean", |
| "description": "Whether to mark execution as learn evidence.", |
| "default": False, |
| }, |
| "include_trace": { |
| "type": "boolean", |
| "description": "Whether to include trace_ref in response.", |
| "default": False, |
| }, |
| }, |
| "required": ["commands"], |
| } |
| ) |
|
|
| async def call( |
| self, |
| context: ContextWrapper[AstrAgentContext], |
| commands: list[str], |
| timeout: int = 60, |
| stop_on_error: bool = True, |
| description: str | None = None, |
| tags: str | None = None, |
| learn: bool = False, |
| include_trace: bool = False, |
| ) -> ToolExecResult: |
| if err := _ensure_admin(context): |
| return err |
| try: |
| browser = await _get_browser_component(context) |
| result = await browser.exec_batch( |
| commands=commands, |
| timeout=timeout, |
| stop_on_error=stop_on_error, |
| description=description, |
| tags=tags, |
| learn=learn, |
| include_trace=include_trace, |
| ) |
| return _to_json(result) |
| except Exception as e: |
| return f"Error executing browser batch command: {str(e)}" |
|
|
|
|
| @dataclass |
| class RunBrowserSkillTool(FunctionTool): |
| name: str = "astrbot_run_browser_skill" |
| description: str = "Run a released browser skill in the sandbox by skill_key." |
| parameters: dict = field( |
| default_factory=lambda: { |
| "type": "object", |
| "properties": { |
| "skill_key": {"type": "string"}, |
| "timeout": {"type": "integer", "default": 60}, |
| "stop_on_error": {"type": "boolean", "default": True}, |
| "include_trace": {"type": "boolean", "default": False}, |
| "description": {"type": "string"}, |
| "tags": {"type": "string"}, |
| }, |
| "required": ["skill_key"], |
| } |
| ) |
|
|
| async def call( |
| self, |
| context: ContextWrapper[AstrAgentContext], |
| skill_key: str, |
| timeout: int = 60, |
| stop_on_error: bool = True, |
| include_trace: bool = False, |
| description: str | None = None, |
| tags: str | None = None, |
| ) -> ToolExecResult: |
| if err := _ensure_admin(context): |
| return err |
| try: |
| browser = await _get_browser_component(context) |
| result = await browser.run_skill( |
| skill_key=skill_key, |
| timeout=timeout, |
| stop_on_error=stop_on_error, |
| include_trace=include_trace, |
| description=description, |
| tags=tags, |
| ) |
| return _to_json(result) |
| except Exception as e: |
| return f"Error running browser skill: {str(e)}" |
|
|