# coding=utf-8
"""工具辅助类和注册表。

提供 BuiltinTool 的注册、查找、执行与组织功能，以及 MCPTool 的平台授权辅助工具。
"""

from __future__ import annotations

import asyncio
import copy
import functools
import inspect
import re
import uuid
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Any, Awaitable, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Type, Union, get_args, get_origin, get_type_hints

from pydantic import BaseModel, TypeAdapter

from turbo_agent_core.schema.agents import BuiltinTool, MCPTool
from turbo_agent_core.schema.basic import Parameter
from turbo_agent_core.schema.enums import BasicType, JSON, ParameterPosition
from turbo_agent_core.schema.external import Platform


DEFAULT_TOOL_SPACE_NAME = "default"


BuiltinToolHandler = Callable[..., Any]
BuiltinToolPermissionChecker = Callable[["BuiltinToolExecutionContext"], Union[bool, Awaitable[bool]]]
BuiltinToolPostProcessor = Callable[["BuiltinToolExecutionContext", Any, Optional[Exception]], Union[Any, Awaitable[Any], None]]

_DOC_ARG_HEADERS = ("args:", "arguments:", "parameters:", "参数:", "入参:")
_DOC_RETURN_HEADERS = ("returns:", "return:", "返回:", "返回值:")
_INJECTED_CONTEXT_PARAM_NAMES = {"context", "tool_context", "execution_context"}
_INJECTED_TOOL_PARAM_NAMES = {"tool", "tool_definition"}
_INJECTED_KWARGS_PARAM_NAMES = {"runtime_kwargs", "raw_kwargs"}


@dataclass
class BuiltinToolExecutionContext:
    """内置工具执行上下文。"""

    name: str
    registration: "BuiltinToolRegistration"
    input_data: Dict[str, Any]
    tool: Optional[BuiltinTool] = None
    runtime_kwargs: Dict[str, Any] = field(default_factory=dict)
    user_id: Optional[str] = None
    username: Optional[str] = None
    permissions: set[str] = field(default_factory=set)
    space_name: str = DEFAULT_TOOL_SPACE_NAME
    project_name_id: Optional[str] = None
    belong_to_project_path: Optional[str] = None
    platform: Optional[Platform] = None


class BuiltinToolRegistration(BaseModel):
    """单个内置工具的注册信息。"""

    name: str
    handler: BuiltinToolHandler
    builtin_name: str
    name_id: str
    description: str = ""
    input_schema: Dict[str, Any] = {}
    output_schema: Dict[str, Any] = {}
    input_parameters: List[Parameter] = []
    output_parameters: List[Parameter] = []
    raw_input_schema: Dict[str, Any] = {}
    raw_input_parameters: List[Parameter] = []
    space_name: str = DEFAULT_TOOL_SPACE_NAME
    project_name_id: Optional[str] = None
    belong_to_project_path: Optional[str] = None
    platform: Optional[Platform] = None
    required_permissions: List[str] = []
    tags: List[str] = []
    execution_hint: Optional[JSON] = None
    handler_module: Optional[str] = None
    permission_checker: Optional[BuiltinToolPermissionChecker] = None
    post_processors: List[BuiltinToolPostProcessor] = []
    preset_kwargs: Dict[str, Any] = {}
    source: str = "function"

    model_config = {"arbitrary_types_allowed": True}

    def resolved_project_path(self) -> str:
        if self.belong_to_project_path:
            return self.belong_to_project_path
        if self.project_name_id:
            return f"{self.space_name}/{self.project_name_id}"
        return self.space_name

    def scoped_name(self) -> str:
        return f"{self.builtin_name}@{self.resolved_project_path()}"


class BuiltinToolSet:
    """内置工具集合容器。

    用于按空间、项目、前缀、权限等维度组织一批内置工具：
    - 先在 toolset 上通过装饰器收集工具
    - 再由 registry.include_toolset(toolset) 统一挂载
    """

    def __init__(
        self,
        *,
        space_name: str = DEFAULT_TOOL_SPACE_NAME,
        project_name_id: Optional[str] = None,
        belong_to_project_path: Optional[str] = None,
        platform: Optional[Platform] = None,
        prefix: str = "",
        tags: Optional[Sequence[str]] = None,
        required_permissions: Optional[Sequence[str]] = None,
        execution_hint: Optional[JSON] = None,
        post_processors: Optional[Sequence[BuiltinToolPostProcessor]] = None,
    ):
        self.space_name = space_name
        self.project_name_id = project_name_id
        self.belong_to_project_path = belong_to_project_path
        self.platform = platform
        self.prefix = prefix
        self.tags = list(tags or [])
        self.required_permissions = list(required_permissions or [])
        self.execution_hint = execution_hint
        self.post_processors = list(post_processors or [])
        self._entries: List[Dict[str, Any]] = []

    def tool(
        self,
        name: Optional[str] = None,
        *,
        description: Optional[str] = None,
        input_schema: Optional[Dict[str, Any]] = None,
        output_schema: Optional[Dict[str, Any]] = None,
        name_id: Optional[str] = None,
        platform: Optional[Platform] = None,
        tags: Optional[Sequence[str]] = None,
        required_permissions: Optional[Sequence[str]] = None,
        execution_hint: Optional[JSON] = None,
        permission_checker: Optional[BuiltinToolPermissionChecker] = None,
        post_processors: Optional[Sequence[BuiltinToolPostProcessor]] = None,
        preset_kwargs: Optional[Dict[str, Any]] = None,
    ) -> Callable[[BuiltinToolHandler], BuiltinToolHandler]:
        """在 toolset 上收集单个工具。"""

        def decorator(func: BuiltinToolHandler) -> BuiltinToolHandler:
            self._entries.append(
                {
                    "kind": "callable",
                    "callable": func,
                    "name": name,
                    "description": description,
                    "input_schema": input_schema,
                    "output_schema": output_schema,
                    "name_id": name_id,
                    "platform": platform,
                    "tags": list(tags or []),
                    "required_permissions": list(required_permissions or []),
                    "execution_hint": execution_hint,
                    "permission_checker": permission_checker,
                    "post_processors": list(post_processors or []),
                    "preset_kwargs": dict(preset_kwargs or {}),
                }
            )
            return func

        return decorator

    def toolset(
        self,
        provider: Optional[object] = None,
        *,
        name_prefix: Optional[str] = None,
        tags: Optional[Sequence[str]] = None,
        required_permissions: Optional[Sequence[str]] = None,
        execution_hint: Optional[JSON] = None,
        permission_checker: Optional[BuiltinToolPermissionChecker] = None,
        post_processors: Optional[Sequence[BuiltinToolPostProcessor]] = None,
        preset_kwargs: Optional[Dict[str, Any]] = None,
    ):
        """收集类或对象上的公开方法作为内置工具。"""

        def registrar(target: object):
            self._entries.append(
                {
                    "kind": "provider",
                    "provider": target,
                    "name_prefix": name_prefix,
                    "tags": list(tags or []),
                    "required_permissions": list(required_permissions or []),
                    "execution_hint": execution_hint,
                    "permission_checker": permission_checker,
                    "post_processors": list(post_processors or []),
                    "preset_kwargs": dict(preset_kwargs or {}),
                }
            )
            return target

        if provider is None:
            return registrar
        return registrar(provider)

    @property
    def entries(self) -> List[Dict[str, Any]]:
        return list(self._entries)


class BuiltinToolRegistry:
    """内置工具注册表。"""

    _instance: Optional["BuiltinToolRegistry"] = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance

    def __init__(self):
        if self._initialized:
            return
        self._initialized = True
        self._tools: Dict[str, BuiltinToolRegistration] = {}
        self._name_index: Dict[str, List[str]] = {}
        self._default_space_name = DEFAULT_TOOL_SPACE_NAME
        self._default_project_name_id: Optional[str] = None
        self._default_belong_to_project_path: Optional[str] = None
        self._default_platform: Optional[Platform] = None

    def set_default_scope(
        self,
        *,
        space_name: Optional[str] = None,
        project_name_id: Optional[str] = None,
        belong_to_project_path: Optional[str] = None,
        platform: Optional[Platform] = None,
    ) -> None:
        if space_name:
            self._default_space_name = space_name
        if project_name_id is not None:
            self._default_project_name_id = project_name_id
        if belong_to_project_path is not None:
            self._default_belong_to_project_path = belong_to_project_path
        if platform is not None:
            self._default_platform = platform

    @contextmanager
    def scoped(
        self,
        *,
        space_name: Optional[str] = None,
        project_name_id: Optional[str] = None,
        belong_to_project_path: Optional[str] = None,
        platform: Optional[Platform] = None,
    ):
        prev = (
            self._default_space_name,
            self._default_project_name_id,
            self._default_belong_to_project_path,
            self._default_platform,
        )
        self.set_default_scope(
            space_name=space_name,
            project_name_id=project_name_id,
            belong_to_project_path=belong_to_project_path,
            platform=platform,
        )
        try:
            yield self
        finally:
            (
                self._default_space_name,
                self._default_project_name_id,
                self._default_belong_to_project_path,
                self._default_platform,
            ) = prev

    def include_toolset(
        self,
        toolset: "BuiltinToolSet",
        *,
        space_name: Optional[str] = None,
        project_name_id: Optional[str] = None,
        belong_to_project_path: Optional[str] = None,
        platform: Optional[Platform] = None,
    ) -> None:
        resolved_space_name = space_name or toolset.space_name or self._default_space_name
        resolved_project_name_id = project_name_id if project_name_id is not None else toolset.project_name_id
        resolved_project_path = belong_to_project_path or toolset.belong_to_project_path
        resolved_platform = platform or toolset.platform or self._default_platform

        for entry in toolset.entries:
            if entry["kind"] == "callable":
                tool_name = entry.get("name")
                if tool_name and toolset.prefix:
                    tool_name = f"{toolset.prefix}{tool_name}"
                elif toolset.prefix:
                    tool_name = f"{toolset.prefix}{entry['callable'].__name__}"
                self.register_handler(
                    name=tool_name,
                    handler=entry["callable"],
                    description=entry.get("description"),
                    input_schema=entry.get("input_schema"),
                    output_schema=entry.get("output_schema"),
                    name_id=entry.get("name_id"),
                    space_name=resolved_space_name,
                    project_name_id=resolved_project_name_id,
                    belong_to_project_path=resolved_project_path,
                    platform=entry.get("platform") or resolved_platform,
                    tags=[*toolset.tags, *entry.get("tags", [])],
                    required_permissions=[*toolset.required_permissions, *entry.get("required_permissions", [])],
                    execution_hint=entry.get("execution_hint") if entry.get("execution_hint") is not None else toolset.execution_hint,
                    permission_checker=entry.get("permission_checker"),
                    post_processors=[*toolset.post_processors, *entry.get("post_processors", [])],
                    preset_kwargs=entry.get("preset_kwargs"),
                )
            else:
                self.register_provider(
                    entry["provider"],
                    name_prefix=f"{toolset.prefix}{entry.get('name_prefix') or ''}",
                    space_name=resolved_space_name,
                    project_name_id=resolved_project_name_id,
                    belong_to_project_path=resolved_project_path,
                    platform=resolved_platform,
                    tags=[*toolset.tags, *entry.get("tags", [])],
                    required_permissions=[*toolset.required_permissions, *entry.get("required_permissions", [])],
                    execution_hint=entry.get("execution_hint") if entry.get("execution_hint") is not None else toolset.execution_hint,
                    permission_checker=entry.get("permission_checker"),
                    post_processors=[*toolset.post_processors, *entry.get("post_processors", [])],
                    preset_kwargs=entry.get("preset_kwargs"),
                )

    def include_router(
        self,
        router: "BuiltinToolSet",
        *,
        space_name: Optional[str] = None,
        project_name_id: Optional[str] = None,
        belong_to_project_path: Optional[str] = None,
        platform: Optional[Platform] = None,
    ) -> None:
        self.include_toolset(
            router,
            space_name=space_name,
            project_name_id=project_name_id,
            belong_to_project_path=belong_to_project_path,
            platform=platform,
        )

    def register(
        self,
        name: Optional[str] = None,
        *,
        description: Optional[str] = None,
        input_schema: Optional[Dict[str, Any]] = None,
        output_schema: Optional[Dict[str, Any]] = None,
        name_id: Optional[str] = None,
        space_name: Optional[str] = None,
        project_name_id: Optional[str] = None,
        belong_to_project_path: Optional[str] = None,
        platform: Optional[Platform] = None,
        tags: Optional[Sequence[str]] = None,
        required_permissions: Optional[Sequence[str]] = None,
        execution_hint: Optional[JSON] = None,
        permission_checker: Optional[BuiltinToolPermissionChecker] = None,
        post_processors: Optional[Sequence[BuiltinToolPostProcessor]] = None,
        preset_kwargs: Optional[Dict[str, Any]] = None,
    ):
        """装饰器方式注册单个函数，或装饰类导出其公开方法。"""

        def decorator(target: object):
            if inspect.isclass(target):
                self.register_provider(
                    target,
                    name_prefix=name or "",
                    space_name=space_name,
                    project_name_id=project_name_id,
                    belong_to_project_path=belong_to_project_path,
                    platform=platform,
                    tags=tags,
                    required_permissions=required_permissions,
                    execution_hint=execution_hint,
                    permission_checker=permission_checker,
                    post_processors=post_processors,
                    preset_kwargs=preset_kwargs,
                )
                return target

            if not callable(target):
                raise TypeError("register 仅支持函数、类或可调用对象")

            self.register_handler(
                name=name,
                handler=target,
                description=description,
                input_schema=input_schema,
                output_schema=output_schema,
                name_id=name_id,
                space_name=space_name,
                project_name_id=project_name_id,
                belong_to_project_path=belong_to_project_path,
                platform=platform,
                tags=tags,
                required_permissions=required_permissions,
                execution_hint=execution_hint,
                permission_checker=permission_checker,
                post_processors=post_processors,
                preset_kwargs=preset_kwargs,
            )
            return target

        return decorator

    def register_handler(
        self,
        name: Optional[str],
        handler: BuiltinToolHandler,
        *,
        description: Optional[str] = None,
        input_schema: Optional[Dict[str, Any]] = None,
        output_schema: Optional[Dict[str, Any]] = None,
        name_id: Optional[str] = None,
        space_name: Optional[str] = None,
        project_name_id: Optional[str] = None,
        belong_to_project_path: Optional[str] = None,
        platform: Optional[Platform] = None,
        tags: Optional[Sequence[str]] = None,
        required_permissions: Optional[Sequence[str]] = None,
        execution_hint: Optional[JSON] = None,
        permission_checker: Optional[BuiltinToolPermissionChecker] = None,
        post_processors: Optional[Sequence[BuiltinToolPostProcessor]] = None,
        preset_kwargs: Optional[Dict[str, Any]] = None,
        source: str = "function",
    ) -> BuiltinToolRegistration:
        metadata = self._build_registration(
            handler=handler,
            name=name,
            description=description,
            input_schema=input_schema,
            output_schema=output_schema,
            name_id=name_id,
            space_name=space_name,
            project_name_id=project_name_id,
            belong_to_project_path=belong_to_project_path,
            platform=platform,
            tags=tags,
            required_permissions=required_permissions,
            execution_hint=execution_hint,
            permission_checker=permission_checker,
            post_processors=post_processors,
            preset_kwargs=preset_kwargs,
            source=source,
        )
        self._store_registration(metadata)
        return metadata

    def register_provider(
        self,
        provider: object,
        *,
        name_prefix: str = "",
        space_name: Optional[str] = None,
        project_name_id: Optional[str] = None,
        belong_to_project_path: Optional[str] = None,
        platform: Optional[Platform] = None,
        tags: Optional[Sequence[str]] = None,
        required_permissions: Optional[Sequence[str]] = None,
        execution_hint: Optional[JSON] = None,
        permission_checker: Optional[BuiltinToolPermissionChecker] = None,
        post_processors: Optional[Sequence[BuiltinToolPostProcessor]] = None,
        preset_kwargs: Optional[Dict[str, Any]] = None,
    ) -> List[BuiltinToolRegistration]:
        registrations: List[BuiltinToolRegistration] = []
        for method_name, bound_callable in self._iter_provider_callables(provider):
            registrations.append(
                self.register_handler(
                    name=f"{name_prefix}{method_name}",
                    handler=bound_callable,
                    name_id=f"{name_prefix}{method_name}" if name_prefix else method_name,
                    space_name=space_name,
                    project_name_id=project_name_id,
                    belong_to_project_path=belong_to_project_path,
                    platform=platform,
                    tags=tags,
                    required_permissions=required_permissions,
                    execution_hint=execution_hint,
                    permission_checker=permission_checker,
                    post_processors=post_processors,
                    preset_kwargs=preset_kwargs,
                    source="provider",
                )
            )
        return registrations

    def set_preset_kwargs(
        self,
        name: str,
        preset_kwargs: Optional[Dict[str, Any]],
        *,
        belong_to_project_path: Optional[str] = None,
        tool: Optional[BuiltinTool] = None,
        merge: bool = True,
    ) -> BuiltinToolRegistration:
        registration = self._resolve_registration(name, tool=tool, belong_to_project_path=belong_to_project_path)
        if registration is None:
            raise KeyError(f"Builtin tool '{name}' not found")

        next_preset_kwargs = dict(registration.preset_kwargs or {}) if merge else {}
        next_preset_kwargs.update(dict(preset_kwargs or {}))
        self._apply_registration_preset_kwargs(registration, next_preset_kwargs)
        return registration

    update_preset_kwargs = set_preset_kwargs

    def clear_preset_kwargs(
        self,
        name: str,
        *,
        belong_to_project_path: Optional[str] = None,
        tool: Optional[BuiltinTool] = None,
    ) -> BuiltinToolRegistration:
        registration = self._resolve_registration(name, tool=tool, belong_to_project_path=belong_to_project_path)
        if registration is None:
            raise KeyError(f"Builtin tool '{name}' not found")
        self._apply_registration_preset_kwargs(registration, {})
        return registration

    def unregister(self, name: str, *, belong_to_project_path: Optional[str] = None) -> bool:
        resolved_key = self._resolve_key_from_name(name, belong_to_project_path)
        if not resolved_key or resolved_key not in self._tools:
            return False
        registration = self._tools.pop(resolved_key)
        self._name_index[registration.builtin_name] = [key for key in self._name_index.get(registration.builtin_name, []) if key != resolved_key]
        if not self._name_index[registration.builtin_name]:
            del self._name_index[registration.builtin_name]
        return True

    def get(self, name: str, *, belong_to_project_path: Optional[str] = None, tool: Optional[BuiltinTool] = None) -> Optional[BuiltinToolRegistration]:
        return self._resolve_registration(name, tool=tool, belong_to_project_path=belong_to_project_path)

    def get_tool_def(
        self,
        name: str,
        tool_id: Optional[str] = None,
        belong_to_project_id: Optional[str] = None,
        name_id: Optional[str] = None,
        space_name: Optional[str] = None,
        project_name_id: Optional[str] = None,
    ) -> Optional[BuiltinTool]:
        reg = self._resolve_registration(name, belong_to_project_path=belong_to_project_id)
        if not reg:
            return None

        resolved_project_path = belong_to_project_id or reg.resolved_project_path()
        resolved_space_name, resolved_project_name_id = self._split_project_path(
            resolved_project_path,
            fallback_space_name=space_name or reg.space_name,
            fallback_project_name_id=project_name_id or reg.project_name_id,
        )

        return BuiltinTool(
            id=tool_id or f"builtin_{uuid.uuid4().hex[:8]}",
            name=reg.name,
            space_name=resolved_space_name,
            project_name_id=resolved_project_name_id,
            belong_to_project_path=resolved_project_path,
            name_id=name_id or reg.name_id,
            description=reg.description,
            builtin_name=reg.builtin_name,
            platform=reg.platform,
            handler_module=reg.handler_module,
            input=reg.input_parameters,
            output=reg.output_parameters,
            input_schema=reg.input_schema,
            output_schema=reg.output_schema,
            required_permissions=list(reg.required_permissions),
            tags=list(reg.tags),
            execution_hint=reg.execution_hint,
        )

    def execute(self, name: str, input_data: JSON, *, tool: Optional[BuiltinTool] = None, **kwargs) -> Any:
        reg = self._resolve_registration(name, tool=tool, belong_to_project_path=kwargs.get("belong_to_project_path"))
        if not reg:
            raise KeyError(f"Builtin tool '{name}' not found")

        context = self._build_execution_context(registration=reg, name=name, input_data=input_data, tool=tool, runtime_kwargs=kwargs)
        self._ensure_permissions_sync(context)
        try:
            result = self._invoke_handler_sync(reg.handler, context)
        except Exception as exc:
            self._run_post_processors_sync(context, None, exc)
            raise
        return self._run_post_processors_sync(context, result, None)

    async def aexecute(self, name: str, input_data: JSON, *, tool: Optional[BuiltinTool] = None, **kwargs) -> Any:
        reg = self._resolve_registration(name, tool=tool, belong_to_project_path=kwargs.get("belong_to_project_path"))
        if not reg:
            raise KeyError(f"Builtin tool '{name}' not found")

        context = self._build_execution_context(registration=reg, name=name, input_data=input_data, tool=tool, runtime_kwargs=kwargs)
        await self._ensure_permissions_async(context)
        try:
            result = await self._invoke_handler_async(reg.handler, context)
        except Exception as exc:
            await self._run_post_processors_async(context, None, exc)
            raise
        return await self._run_post_processors_async(context, result, None)

    def list_tools(
        self,
        *,
        belong_to_project_path: Optional[str] = None,
        space_name: Optional[str] = None,
        project_name_id: Optional[str] = None,
    ) -> List[str]:
        names: List[str] = []
        for key, registration in self._tools.items():
            if self._matches_scope(
                registration,
                belong_to_project_path=belong_to_project_path,
                space_name=space_name,
                project_name_id=project_name_id,
            ):
                names.append(key)
        return sorted(names)

    def list_tool_defs(
        self,
        *,
        belong_to_project_path: Optional[str] = None,
        space_name: Optional[str] = None,
        project_name_id: Optional[str] = None,
    ) -> List[BuiltinTool]:
        tool_defs: List[BuiltinTool] = []
        for reg in self._tools.values():
            if not self._matches_scope(
                reg,
                belong_to_project_path=belong_to_project_path,
                space_name=space_name,
                project_name_id=project_name_id,
            ):
                continue
            tool_def = self.get_tool_def(reg.builtin_name, belong_to_project_id=reg.resolved_project_path())
            if tool_def is not None:
                tool_defs.append(tool_def)
        return tool_defs

    def clear(self) -> None:
        self._tools.clear()
        self._name_index.clear()

    def _store_registration(self, registration: BuiltinToolRegistration) -> None:
        key = registration.scoped_name()
        self._tools[key] = registration
        scoped_keys = self._name_index.setdefault(registration.builtin_name, [])
        if key not in scoped_keys:
            scoped_keys.append(key)

    def _apply_registration_preset_kwargs(
        self,
        registration: BuiltinToolRegistration,
        preset_kwargs: Dict[str, Any],
    ) -> None:
        filtered_schema, filtered_parameters = self._filter_preset_from_contract(
            registration.raw_input_schema or registration.input_schema,
            registration.raw_input_parameters or registration.input_parameters,
            preset_kwargs,
        )
        registration.preset_kwargs = dict(preset_kwargs)
        registration.input_schema = filtered_schema
        registration.input_parameters = filtered_parameters

    def _matches_scope(
        self,
        registration: BuiltinToolRegistration,
        *,
        belong_to_project_path: Optional[str],
        space_name: Optional[str],
        project_name_id: Optional[str],
    ) -> bool:
        resolved_project_path = registration.resolved_project_path()
        if belong_to_project_path is not None:
            return resolved_project_path == belong_to_project_path

        if space_name is not None and registration.space_name != space_name:
            return False

        if project_name_id is not None and registration.project_name_id != project_name_id:
            return False

        return True

    def _build_registration(
        self,
        *,
        handler: BuiltinToolHandler,
        name: Optional[str],
        description: Optional[str],
        input_schema: Optional[Dict[str, Any]],
        output_schema: Optional[Dict[str, Any]],
        name_id: Optional[str],
        space_name: Optional[str],
        project_name_id: Optional[str],
        belong_to_project_path: Optional[str],
        platform: Optional[Platform],
        tags: Optional[Sequence[str]],
        required_permissions: Optional[Sequence[str]],
        execution_hint: Optional[JSON],
        permission_checker: Optional[BuiltinToolPermissionChecker],
        post_processors: Optional[Sequence[BuiltinToolPostProcessor]],
        preset_kwargs: Optional[Dict[str, Any]],
        source: str,
    ) -> BuiltinToolRegistration:
        doc = inspect.getdoc(handler) or ""
        summary, param_docs, return_doc = self._parse_docstring(doc)
        builtin_name = name or handler.__name__
        resolved_space_name = space_name or self._default_space_name
        resolved_project_name_id = project_name_id if project_name_id is not None else self._default_project_name_id
        resolved_project_path = belong_to_project_path or self._default_belong_to_project_path
        if not resolved_project_path:
            resolved_project_path = self._compose_project_path(resolved_space_name, resolved_project_name_id)

        inferred_input_schema, inferred_input_parameters = self._build_input_contract(handler, param_docs)
        inferred_output_schema, inferred_output_parameters = self._build_output_contract(handler, return_doc)
        base_input_schema = copy.deepcopy(input_schema or inferred_input_schema)
        base_input_parameters = copy.deepcopy(inferred_input_parameters)
        resolved_preset_kwargs = dict(preset_kwargs or {})
        visible_input_schema, visible_input_parameters = self._filter_preset_from_contract(
            base_input_schema,
            base_input_parameters,
            resolved_preset_kwargs,
        )

        return BuiltinToolRegistration(
            name=builtin_name,
            handler=handler,
            builtin_name=builtin_name,
            name_id=name_id or builtin_name,
            description=description or summary or doc,
            input_schema=visible_input_schema,
            output_schema=output_schema or inferred_output_schema,
            input_parameters=visible_input_parameters,
            output_parameters=inferred_output_parameters,
            raw_input_schema=base_input_schema,
            raw_input_parameters=base_input_parameters,
            space_name=resolved_space_name,
            project_name_id=resolved_project_name_id,
            belong_to_project_path=resolved_project_path,
            platform=platform or self._default_platform,
            required_permissions=list(required_permissions or []),
            tags=list(tags or []),
            execution_hint=execution_hint,
            handler_module=getattr(handler, "__module__", None),
            permission_checker=permission_checker,
            post_processors=list(post_processors or []),
            preset_kwargs=resolved_preset_kwargs,
            source=source,
        )

    def _resolve_registration(
        self,
        name: str,
        *,
        tool: Optional[BuiltinTool] = None,
        belong_to_project_path: Optional[str] = None,
    ) -> Optional[BuiltinToolRegistration]:
        if name in self._tools:
            return self._tools[name]

        candidate_names = [name]
        if tool is not None:
            candidate_names = [tool.builtin_name, name, getattr(tool, "name_id", name), getattr(tool, "name", name)]
            belong_to_project_path = belong_to_project_path or getattr(tool, "belong_to_project_path", None)

        if belong_to_project_path:
            for candidate_name in candidate_names:
                scoped_key = f"{candidate_name}@{belong_to_project_path}"
                if scoped_key in self._tools:
                    return self._tools[scoped_key]

        for candidate_name in candidate_names:
            scoped_keys = self._name_index.get(candidate_name, [])
            if len(scoped_keys) == 1:
                return self._tools[scoped_keys[0]]

        return None

    def _resolve_key_from_name(self, name: str, belong_to_project_path: Optional[str]) -> Optional[str]:
        if name in self._tools:
            return name
        if belong_to_project_path:
            candidate = f"{name}@{belong_to_project_path}"
            if candidate in self._tools:
                return candidate
        scoped_keys = self._name_index.get(name, [])
        if len(scoped_keys) == 1:
            return scoped_keys[0]
        return None

    def _build_execution_context(
        self,
        *,
        registration: BuiltinToolRegistration,
        name: str,
        input_data: JSON,
        tool: Optional[BuiltinTool],
        runtime_kwargs: Dict[str, Any],
    ) -> BuiltinToolExecutionContext:
        permissions = runtime_kwargs.get("permissions") or runtime_kwargs.get("user_permissions") or []
        if isinstance(permissions, str):
            permissions = [permissions]

        resolved_path = (
            getattr(tool, "belong_to_project_path", None)
            or runtime_kwargs.get("belong_to_project_path")
            or registration.resolved_project_path()
        )
        resolved_space_name, resolved_project_name_id = self._split_project_path(
            resolved_path,
            fallback_space_name=getattr(tool, "space_name", None) or registration.space_name,
            fallback_project_name_id=getattr(tool, "project_name_id", None) or registration.project_name_id,
        )

        return BuiltinToolExecutionContext(
            name=name,
            registration=registration,
            input_data=input_data if isinstance(input_data, dict) else {"value": input_data},
            tool=tool,
            runtime_kwargs=dict(runtime_kwargs),
            user_id=runtime_kwargs.get("user_id") or runtime_kwargs.get("userId"),
            username=runtime_kwargs.get("username"),
            permissions={str(item) for item in permissions},
            space_name=resolved_space_name,
            project_name_id=resolved_project_name_id,
            belong_to_project_path=resolved_path,
            platform=getattr(tool, "platform", None) or registration.platform,
        )

    def _ensure_permissions_sync(self, context: BuiltinToolExecutionContext) -> None:
        self._ensure_required_permissions(context)
        checker = context.registration.permission_checker
        if checker is None:
            return
        result = checker(context)
        if inspect.isawaitable(result):
            raise RuntimeError("同步执行不支持异步权限校验，请改用 aexecute")
        if result is False:
            raise PermissionError(f"Builtin tool '{context.registration.builtin_name}' 权限校验失败")

    async def _ensure_permissions_async(self, context: BuiltinToolExecutionContext) -> None:
        self._ensure_required_permissions(context)
        checker = context.registration.permission_checker
        if checker is None:
            return
        result = checker(context)
        if inspect.isawaitable(result):
            result = await result
        if result is False:
            raise PermissionError(f"Builtin tool '{context.registration.builtin_name}' 权限校验失败")

    def _ensure_required_permissions(self, context: BuiltinToolExecutionContext) -> None:
        required = set(context.registration.required_permissions or [])
        if not required:
            return
        missing = sorted(required - context.permissions)
        if missing:
            raise PermissionError(
                f"Builtin tool '{context.registration.builtin_name}' 缺少权限: {', '.join(missing)}"
            )

    def _invoke_handler_sync(self, handler: BuiltinToolHandler, context: BuiltinToolExecutionContext) -> Any:
        kwargs = self._build_handler_kwargs(handler, context)
        result = handler(**kwargs)
        if inspect.isawaitable(result):
            raise RuntimeError("同步执行不支持异步 handler，请改用 aexecute")
        return result

    async def _invoke_handler_async(self, handler: BuiltinToolHandler, context: BuiltinToolExecutionContext) -> Any:
        if asyncio.iscoroutinefunction(handler):
            return await handler(**self._build_handler_kwargs(handler, context))
        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(
            None,
            functools.partial(handler, **self._build_handler_kwargs(handler, context)),
        )

    def _build_handler_kwargs(self, handler: BuiltinToolHandler, context: BuiltinToolExecutionContext) -> Dict[str, Any]:
        kwargs = dict(context.input_data)
        if context.registration.preset_kwargs:
            kwargs.update(context.registration.preset_kwargs)
        hints = self._safe_get_type_hints(handler)
        signature = inspect.signature(handler)
        for param_name, param in signature.parameters.items():
            if param_name in ("self", "cls"):
                continue
            annotation = hints.get(param_name, param.annotation)
            if self._is_context_injected_param(param_name, annotation):
                kwargs[param_name] = context
            elif self._is_tool_injected_param(param_name, annotation):
                kwargs[param_name] = context.tool
            elif self._is_runtime_kwargs_injected_param(param_name, annotation):
                kwargs[param_name] = context.runtime_kwargs
        return kwargs

    def _run_post_processors_sync(
        self,
        context: BuiltinToolExecutionContext,
        result: Any,
        error: Optional[Exception],
    ) -> Any:
        current = result
        for callback in context.registration.post_processors:
            callback_result = callback(context, current, error)
            if inspect.isawaitable(callback_result):
                raise RuntimeError("同步执行不支持异步后处理回调，请改用 aexecute")
            if callback_result is not None:
                current = callback_result
        return current

    async def _run_post_processors_async(
        self,
        context: BuiltinToolExecutionContext,
        result: Any,
        error: Optional[Exception],
    ) -> Any:
        current = result
        for callback in context.registration.post_processors:
            callback_result = callback(context, current, error)
            if inspect.isawaitable(callback_result):
                callback_result = await callback_result
            if callback_result is not None:
                current = callback_result
        return current

    def _build_input_contract(self, handler: BuiltinToolHandler, param_docs: Dict[str, str]) -> Tuple[Dict[str, Any], List[Parameter]]:
        signature = inspect.signature(handler)
        hints = self._safe_get_type_hints(handler)
        properties: Dict[str, Any] = {}
        required: List[str] = []
        parameters: List[Parameter] = []

        for idx, (param_name, param) in enumerate(signature.parameters.items()):
            if param_name in ("self", "cls"):
                continue
            annotation = hints.get(param_name, param.annotation)
            if self._is_injected_param(param_name, annotation):
                continue

            schema = self._annotation_to_json_schema(annotation)
            schema = copy.deepcopy(schema)
            if param.default is not inspect.Parameter.empty:
                schema.setdefault("default", param.default)
            else:
                required.append(param_name)

            description = param_docs.get(param_name, schema.pop("description", ""))
            if description:
                schema["description"] = description

            properties[param_name] = schema
            parameters.append(
                self._schema_to_parameter(
                    name=param_name,
                    schema=schema,
                    idx=idx,
                    required=param.default is inspect.Parameter.empty,
                    default=None if param.default is inspect.Parameter.empty else param.default,
                )
            )

        input_schema: Dict[str, Any] = {"type": "object", "properties": properties}
        if required:
            input_schema["required"] = required
        return input_schema, parameters

    def _filter_preset_from_contract(
        self,
        input_schema: Dict[str, Any],
        input_parameters: List[Parameter],
        preset_kwargs: Dict[str, Any],
    ) -> Tuple[Dict[str, Any], List[Parameter]]:
        hidden_param_names = set(preset_kwargs.keys())
        if not hidden_param_names:
            return copy.deepcopy(input_schema), copy.deepcopy(input_parameters)

        filtered_schema = copy.deepcopy(input_schema)
        properties = filtered_schema.get("properties") or {}
        for param_name in hidden_param_names:
            properties.pop(param_name, None)

        required = filtered_schema.get("required")
        if isinstance(required, list):
            filtered_required = [param_name for param_name in required if param_name not in hidden_param_names]
            if filtered_required:
                filtered_schema["required"] = filtered_required
            else:
                filtered_schema.pop("required", None)

        filtered_parameters = [
            parameter.model_copy(deep=True)
            for parameter in input_parameters
            if parameter.name not in hidden_param_names
        ]
        return filtered_schema, filtered_parameters

    def _build_output_contract(self, handler: BuiltinToolHandler, return_doc: str) -> Tuple[Dict[str, Any], List[Parameter]]:
        hints = self._safe_get_type_hints(handler)
        annotation = hints.get("return", inspect.Signature.empty)
        if annotation is inspect.Signature.empty:
            return {}, []

        schema = self._annotation_to_json_schema(annotation)
        schema = copy.deepcopy(schema)
        if return_doc:
            schema.setdefault("description", return_doc)

        if schema.get("type") == "object":
            output_parameters = self._schema_to_parameters(schema)
            output_schema = schema
        else:
            output_parameters = [
                self._schema_to_parameter(
                    name="result",
                    schema=schema,
                    idx=0,
                    required=False,
                    default=schema.get("default"),
                )
            ]
            output_schema = {
                "type": "object",
                "properties": {"result": schema},
            }
        return output_schema, output_parameters

    def _parse_docstring(self, doc: str) -> Tuple[str, Dict[str, str], str]:
        if not doc:
            return "", {}, ""

        lines = [line.rstrip() for line in doc.splitlines()]
        summary_lines: List[str] = []
        param_docs: Dict[str, str] = {}
        return_lines: List[str] = []
        section: Optional[str] = None
        current_param: Optional[str] = None

        for raw_line in lines:
            line = raw_line.strip()
            lowered = line.lower()
            if not line:
                if section is None and summary_lines:
                    section = "summary_done"
                continue

            if lowered in _DOC_ARG_HEADERS:
                section = "args"
                current_param = None
                continue
            if lowered in _DOC_RETURN_HEADERS:
                section = "returns"
                current_param = None
                continue

            if section in (None, "summary_done"):
                summary_lines.append(line)
                continue

            if section == "args":
                match = re.match(r"^([a-zA-Z_][a-zA-Z0-9_]*)\s*(?:\([^)]*\))?\s*:\s*(.+)$", line)
                if match:
                    current_param = match.group(1)
                    param_docs[current_param] = match.group(2).strip()
                elif current_param:
                    param_docs[current_param] = f"{param_docs[current_param]} {line}".strip()
                continue

            if section == "returns":
                return_lines.append(line)

        return " ".join(summary_lines).strip(), param_docs, " ".join(return_lines).strip()

    def _annotation_to_json_schema(self, annotation: Any) -> Dict[str, Any]:
        if annotation in (inspect._empty, Any):
            return {}
        try:
            return self._normalize_json_schema(TypeAdapter(annotation).json_schema())
        except Exception:
            pass

        origin = get_origin(annotation)
        if origin is Union:
            args = [arg for arg in get_args(annotation) if arg is not type(None)]
            if len(args) == 1:
                return self._annotation_to_json_schema(args[0])
        return {}

    def _json_schema_type_to_basic_type(self, schema: Dict[str, Any]) -> BasicType:
        if "enum" in schema:
            return BasicType.enum
        schema_type = schema.get("type")
        if schema_type == "string":
            if schema.get("format") == "date-time":
                return BasicType.datetime
            return BasicType.string
        if schema_type == "integer":
            return BasicType.integer
        if schema_type == "number":
            return BasicType.number
        if schema_type == "boolean":
            return BasicType.boolean
        if schema_type == "array":
            return BasicType.array
        if schema_type == "object":
            return BasicType.object
        if schema_type == "null":
            return BasicType.null
        return BasicType.object

    def _resolve_type_ref(self, schema: Dict[str, Any]) -> Optional[BasicType]:
        if schema.get("type") != "array":
            return None
        items = schema.get("items") or {}
        return self._json_schema_type_to_basic_type(items)

    def _normalize_json_schema(self, schema: Any) -> Dict[str, Any]:
        if not isinstance(schema, dict):
            return {}

        normalized = copy.deepcopy(schema)
        defs = normalized.get("$defs") if isinstance(normalized.get("$defs"), dict) else {}
        normalized = self._resolve_json_schema_refs(normalized, defs)

        variants_key = None
        if "anyOf" in normalized:
            variants_key = "anyOf"
        elif "oneOf" in normalized:
            variants_key = "oneOf"

        if variants_key:
            variants = normalized.get(variants_key) or []
            non_null_variants = []
            nullable = False
            for variant in variants:
                normalized_variant = self._normalize_json_schema(variant)
                if normalized_variant.get("type") == "null":
                    nullable = True
                    continue
                non_null_variants.append(normalized_variant)
            if len(non_null_variants) == 1:
                merged = non_null_variants[0]
                if nullable:
                    merged["nullable"] = True
                for key in ("title", "description", "default"):
                    if key in normalized and key not in merged:
                        merged[key] = normalized[key]
                normalized = merged

        properties = normalized.get("properties")
        if isinstance(properties, dict):
            normalized["properties"] = {
                key: self._normalize_json_schema(value)
                for key, value in properties.items()
            }

        items = normalized.get("items")
        if isinstance(items, dict):
            normalized["items"] = self._normalize_json_schema(items)

        additional_properties = normalized.get("additionalProperties")
        if isinstance(additional_properties, dict):
            normalized["additionalProperties"] = self._normalize_json_schema(additional_properties)

        return normalized

    def _resolve_json_schema_refs(self, schema: Any, defs: Dict[str, Any]) -> Dict[str, Any]:
        if not isinstance(schema, dict):
            return {}

        current = copy.deepcopy(schema)
        ref = current.get("$ref")
        if isinstance(ref, str) and ref.startswith("#/$defs/"):
            ref_name = ref.split("#/$defs/", 1)[1]
            ref_schema = defs.get(ref_name)
            if isinstance(ref_schema, dict):
                merged = self._resolve_json_schema_refs(ref_schema, defs)
                for key, value in current.items():
                    if key in {"$ref", "$defs"}:
                        continue
                    merged[key] = self._resolve_json_schema_refs(value, defs) if isinstance(value, dict) else copy.deepcopy(value)
                current = merged

        for key, value in list(current.items()):
            if key == "$defs":
                current.pop(key, None)
                continue
            if isinstance(value, dict):
                current[key] = self._resolve_json_schema_refs(value, defs)
            elif isinstance(value, list):
                current[key] = [
                    self._resolve_json_schema_refs(item, defs) if isinstance(item, dict) else copy.deepcopy(item)
                    for item in value
                ]
        return current

    def _schema_to_parameters(self, schema: Dict[str, Any]) -> List[Parameter]:
        properties = schema.get("properties") or {}
        if not isinstance(properties, dict):
            return []
        required_fields = set(schema.get("required") or [])
        return [
            self._schema_to_parameter(
                name=field_name,
                schema=field_schema if isinstance(field_schema, dict) else {},
                idx=idx,
                required=field_name in required_fields,
                default=(field_schema or {}).get("default") if isinstance(field_schema, dict) else None,
            )
            for idx, (field_name, field_schema) in enumerate(properties.items())
        ]

    def _schema_to_parameter(
        self,
        *,
        name: str,
        schema: Dict[str, Any],
        idx: int,
        required: bool,
        default: Any,
    ) -> Parameter:
        normalized_schema = copy.deepcopy(schema)
        parameter_type = self._json_schema_type_to_basic_type(normalized_schema)
        nested_parameters: Optional[List[Parameter]] = None
        enum_values = normalized_schema.get("enum") if isinstance(normalized_schema.get("enum"), list) else None

        if normalized_schema.get("type") == "object":
            nested_parameters = self._schema_to_parameters(normalized_schema)
        elif normalized_schema.get("type") == "array":
            items = normalized_schema.get("items") or {}
            if isinstance(items, dict) and items.get("type") == "object":
                nested_parameters = self._schema_to_parameters(items)
            elif isinstance(items, dict) and isinstance(items.get("enum"), list):
                enum_values = items.get("enum")

        return Parameter(
            idx=idx,
            required=required,
            name=name,
            description=normalized_schema.get("description", ""),
            default=default,
            enum_values=enum_values,
            position=ParameterPosition.body,
            type=parameter_type,
            type_ref=self._resolve_type_ref(normalized_schema),
            parameters=nested_parameters,
            json_schema=normalized_schema,
        )

    def _safe_get_type_hints(self, handler: BuiltinToolHandler) -> Dict[str, Any]:
        try:
            return get_type_hints(handler, include_extras=True)
        except Exception:
            return {}

    def _iter_provider_callables(self, provider: object) -> Iterable[Tuple[str, BuiltinToolHandler]]:
        if inspect.isclass(provider):
            instance = self._maybe_instantiate_provider(provider)
            for attr_name in dir(provider):
                if attr_name.startswith("_"):
                    continue
                descriptor = inspect.getattr_static(provider, attr_name)
                if isinstance(descriptor, staticmethod):
                    yield attr_name, getattr(provider, attr_name)
                    continue
                if isinstance(descriptor, classmethod):
                    yield attr_name, getattr(provider, attr_name)
                    continue
                if instance is not None:
                    bound = getattr(instance, attr_name, None)
                    if callable(bound):
                        yield attr_name, bound
            return

        for attr_name in dir(provider):
            if attr_name.startswith("_"):
                continue
            bound = getattr(provider, attr_name)
            if callable(bound):
                yield attr_name, bound

    def _maybe_instantiate_provider(self, provider_cls: Type[Any]) -> Optional[Any]:
        try:
            init_sig = inspect.signature(provider_cls)
        except (TypeError, ValueError):
            return None

        required_params = [
            param for param in init_sig.parameters.values()
            if param.default is inspect.Parameter.empty and param.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
        ]
        if required_params:
            return None
        try:
            return provider_cls()
        except Exception:
            return None

    def _is_injected_param(self, param_name: str, annotation: Any) -> bool:
        return (
            self._is_context_injected_param(param_name, annotation)
            or self._is_tool_injected_param(param_name, annotation)
            or self._is_runtime_kwargs_injected_param(param_name, annotation)
        )

    def _is_context_injected_param(self, param_name: str, annotation: Any) -> bool:
        return param_name in _INJECTED_CONTEXT_PARAM_NAMES or annotation is BuiltinToolExecutionContext

    def _is_tool_injected_param(self, param_name: str, annotation: Any) -> bool:
        return param_name in _INJECTED_TOOL_PARAM_NAMES or annotation is BuiltinTool

    def _is_runtime_kwargs_injected_param(self, param_name: str, annotation: Any) -> bool:
        origin = get_origin(annotation)
        return param_name in _INJECTED_KWARGS_PARAM_NAMES or annotation is dict or origin is dict

    def _compose_project_path(self, space_name: str, project_name_id: Optional[str]) -> str:
        return f"{space_name}/{project_name_id}" if project_name_id else space_name

    def _split_project_path(
        self,
        belong_to_project_path: Optional[str],
        *,
        fallback_space_name: Optional[str],
        fallback_project_name_id: Optional[str],
    ) -> Tuple[str, Optional[str]]:
        if belong_to_project_path:
            if "/" in belong_to_project_path:
                space_name, project_name_id = belong_to_project_path.split("/", 1)
                return space_name, project_name_id
            return belong_to_project_path, None
        return fallback_space_name or DEFAULT_TOOL_SPACE_NAME, fallback_project_name_id


def get_builtin_tool_registry() -> BuiltinToolRegistry:
    """获取全局内置工具注册表实例。"""
    return BuiltinToolRegistry()


class MCPToolAuthHelper:
    """MCP 工具平台授权辅助类。"""

    @staticmethod
    def requires_platform_auth(tool: MCPTool) -> bool:
        return tool.require_platform_auth

    @staticmethod
    def get_platform(tool: MCPTool) -> Optional[Platform]:
        if not tool.require_platform_auth:
            return None
        return tool.platform

    @staticmethod
    def validate_auth_config(tool: MCPTool) -> List[str]:
        errors = []

        if tool.require_platform_auth:
            if not tool.platform:
                errors.append("Platform is required when require_platform_auth is True")
            elif not tool.platform.authMethods:
                errors.append(f"Platform '{tool.platform.name}' has no auth methods")

        if not tool.server:
            errors.append("MCP server configuration is required")
        else:
            from turbo_agent_core.schema.external import McpTransport

            if tool.server.transport in (McpTransport.http, McpTransport.ws):
                if not tool.server.endpoint:
                    errors.append(f"Endpoint is required for {tool.server.transport} transport")
            elif tool.server.transport == McpTransport.stdio:
                if not tool.server.command:
                    errors.append("Command is required for stdio transport")

        return errors


def builtin_tool(
    name: Optional[str] = None,
    *,
    description: Optional[str] = None,
    input_schema: Optional[Dict[str, Any]] = None,
    output_schema: Optional[Dict[str, Any]] = None,
    name_id: Optional[str] = None,
    space_name: Optional[str] = None,
    project_name_id: Optional[str] = None,
    belong_to_project_path: Optional[str] = None,
    platform: Optional[Platform] = None,
    tags: Optional[Sequence[str]] = None,
    required_permissions: Optional[Sequence[str]] = None,
    execution_hint: Optional[JSON] = None,
    permission_checker: Optional[BuiltinToolPermissionChecker] = None,
    post_processors: Optional[Sequence[BuiltinToolPostProcessor]] = None,
    preset_kwargs: Optional[Dict[str, Any]] = None,
):
    """使用全局注册表注册函数或类。"""
    registry = get_builtin_tool_registry()
    return registry.register(
        name=name,
        description=description,
        input_schema=input_schema,
        output_schema=output_schema,
        name_id=name_id,
        space_name=space_name,
        project_name_id=project_name_id,
        belong_to_project_path=belong_to_project_path,
        platform=platform,
        tags=tags,
        required_permissions=required_permissions,
        execution_hint=execution_hint,
        permission_checker=permission_checker,
        post_processors=post_processors,
        preset_kwargs=preset_kwargs,
    )


def builtin_tool_provider(
    provider: Optional[object] = None,
    *,
    name_prefix: str = "",
    space_name: Optional[str] = None,
    project_name_id: Optional[str] = None,
    belong_to_project_path: Optional[str] = None,
    platform: Optional[Platform] = None,
    tags: Optional[Sequence[str]] = None,
    required_permissions: Optional[Sequence[str]] = None,
    execution_hint: Optional[JSON] = None,
    permission_checker: Optional[BuiltinToolPermissionChecker] = None,
    post_processors: Optional[Sequence[BuiltinToolPostProcessor]] = None,
    preset_kwargs: Optional[Dict[str, Any]] = None,
):
    """注册类或对象的公开方法为内置工具。"""

    registry = get_builtin_tool_registry()

    def decorator(target: object):
        registry.register_provider(
            target,
            name_prefix=name_prefix,
            space_name=space_name,
            project_name_id=project_name_id,
            belong_to_project_path=belong_to_project_path,
            platform=platform,
            tags=tags,
            required_permissions=required_permissions,
            execution_hint=execution_hint,
            permission_checker=permission_checker,
            post_processors=post_processors,
            preset_kwargs=preset_kwargs,
        )
        return target

    if provider is None:
        return decorator
    return decorator(provider)


register_builtin_tool = builtin_tool
register_builtin_tool_provider = builtin_tool_provider


def create_builtin_toolset(
    *,
    space_name: str = DEFAULT_TOOL_SPACE_NAME,
    project_name_id: Optional[str] = None,
    belong_to_project_path: Optional[str] = None,
    platform: Optional[Platform] = None,
    prefix: str = "",
    tags: Optional[Sequence[str]] = None,
    required_permissions: Optional[Sequence[str]] = None,
    execution_hint: Optional[JSON] = None,
    post_processors: Optional[Sequence[BuiltinToolPostProcessor]] = None,
) -> BuiltinToolSet:
    return BuiltinToolSet(
        space_name=space_name,
        project_name_id=project_name_id,
        belong_to_project_path=belong_to_project_path,
        platform=platform,
        prefix=prefix,
        tags=tags,
        required_permissions=required_permissions,
        execution_hint=execution_hint,
        post_processors=post_processors,
    )


BuiltinToolRouter = BuiltinToolSet
create_builtin_tool_router = create_builtin_toolset


def create_builtin_tool_def(
    name: str,
    tool_id: Optional[str] = None,
    belong_to_project_id: Optional[str] = None,
) -> Optional[BuiltinTool]:
    registry = get_builtin_tool_registry()
    return registry.get_tool_def(name, tool_id, belong_to_project_id)