File size: 3,291 Bytes
399b80c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
"""
RemoteTool.
Wrapper around a connector that calls a remote tool.
"""
from typing import Optional
from openspace.utils.logging import Logger
from ..types import BackendType, ToolResult, ToolSchema, ToolStatus
from .base import BaseTool
from openspace.grounding.core.transport.connectors import BaseConnector

logger = Logger.get_logger(__name__)


class RemoteTool(BaseTool):
    backend_type = BackendType.NOT_SET

    def __init__(
        self,
        schema: ToolSchema | None = None,
        connector: Optional[BaseConnector] = None,
        remote_name: str = "",
        *,
        verbose: bool = False,
        backend: BackendType = BackendType.NOT_SET,
    ):
        self._conn = connector
        self._remote_name = remote_name or (schema.name if schema else "")
        self.backend_type = backend
        super().__init__(schema=schema, verbose=verbose)

    async def _arun(self, **kwargs):
        # If no connector, tool must be invoked via grounding_client (on-demand startup)
        if self._conn is None:
            raise RuntimeError(
                f"Tool '{self.name}' has no connector. "
                "Use grounding_client.invoke_tool() to execute it with on-demand server startup."
            )
        
        raw = await self._conn.invoke(self._remote_name, kwargs)
        
        if hasattr(raw, 'content') and hasattr(raw, 'isError'):
            content_parts = []
            for item in (raw.content or []):
                # Extract text from TextContent
                if hasattr(item, 'text') and item.text:
                    content_parts.append(item.text)
                # Handle ImageContent (just note its presence)
                elif hasattr(item, 'data'):
                    content_parts.append(f"[Image data: {len(item.data) if item.data else 0} bytes]")
                # Handle EmbeddedResource
                elif hasattr(item, 'resource'):
                    content_parts.append(f"[Embedded resource: {getattr(item.resource, 'uri', 'unknown')}]")
            
            content = "\n".join(content_parts) if content_parts else ""
            is_error = getattr(raw, 'isError', False)
            
            return ToolResult(
                status=ToolStatus.ERROR if is_error else ToolStatus.SUCCESS,
                content=content,
                error=content if is_error else None,
            )
        
        # Handle dict response
        if isinstance(raw, dict):
            import json
            try:
                content = json.dumps(raw, ensure_ascii=False, indent=2)
            except (TypeError, ValueError):
                content = str(raw)
        # Handle list/tuple response
        elif isinstance(raw, (list, tuple)):
            import json
            try:
                content = json.dumps(raw, ensure_ascii=False, indent=2)
            except (TypeError, ValueError):
                content = str(raw)
        # Handle primitive types
        elif isinstance(raw, (int, float, bool)):
            content = str(raw)
        elif isinstance(raw, str):
            content = raw
        # Fallback for unknown types
        else:
            content = str(raw)
        
        return ToolResult(
            status=ToolStatus.SUCCESS,
            content=content,
        )