agentic-fabriq-sdk 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of agentic-fabriq-sdk might be problematic. Click here for more details.
- af_sdk/__init__.py +55 -0
- af_sdk/auth/__init__.py +31 -0
- af_sdk/auth/dpop.py +43 -0
- af_sdk/auth/oauth.py +247 -0
- af_sdk/auth/token_cache.py +318 -0
- af_sdk/connectors/__init__.py +23 -0
- af_sdk/connectors/base.py +231 -0
- af_sdk/connectors/registry.py +262 -0
- af_sdk/dx/__init__.py +12 -0
- af_sdk/dx/decorators.py +40 -0
- af_sdk/dx/runtime.py +170 -0
- af_sdk/events.py +699 -0
- af_sdk/exceptions.py +140 -0
- af_sdk/fabriq_client.py +198 -0
- af_sdk/models/__init__.py +47 -0
- af_sdk/models/audit.py +44 -0
- af_sdk/models/types.py +242 -0
- af_sdk/py.typed +0 -0
- af_sdk/transport/__init__.py +7 -0
- af_sdk/transport/http.py +366 -0
- af_sdk/vault.py +500 -0
- agentic_fabriq_sdk-0.1.3.dist-info/METADATA +81 -0
- agentic_fabriq_sdk-0.1.3.dist-info/RECORD +24 -0
- agentic_fabriq_sdk-0.1.3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Connector framework for Agentic Fabric SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .base import (
|
|
6
|
+
AgentConnector,
|
|
7
|
+
BaseConnector,
|
|
8
|
+
ConnectorContext,
|
|
9
|
+
HTTPConnectorMixin,
|
|
10
|
+
MCPConnector,
|
|
11
|
+
ToolConnector,
|
|
12
|
+
)
|
|
13
|
+
from .registry import ConnectorRegistry
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"BaseConnector",
|
|
17
|
+
"ToolConnector",
|
|
18
|
+
"AgentConnector",
|
|
19
|
+
"MCPConnector",
|
|
20
|
+
"ConnectorContext",
|
|
21
|
+
"HTTPConnectorMixin",
|
|
22
|
+
"ConnectorRegistry",
|
|
23
|
+
]
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base connector classes for Agentic Fabric SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import abc
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Dict, Optional
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from ..auth.token_cache import TokenManager
|
|
13
|
+
from ..exceptions import ConnectorError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConnectorContext(BaseModel):
|
|
17
|
+
"""Context object passed to connector instances."""
|
|
18
|
+
|
|
19
|
+
tenant_id: str
|
|
20
|
+
user_id: Optional[str] = None
|
|
21
|
+
http: httpx.AsyncClient
|
|
22
|
+
token_manager: "TokenManager"
|
|
23
|
+
logger: logging.Logger
|
|
24
|
+
metadata: Dict[str, Any] = {}
|
|
25
|
+
|
|
26
|
+
class Config:
|
|
27
|
+
arbitrary_types_allowed = True
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BaseConnector(abc.ABC):
|
|
31
|
+
"""Base class for all connectors."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, ctx: ConnectorContext):
|
|
34
|
+
self.ctx = ctx
|
|
35
|
+
self.session = ctx.http
|
|
36
|
+
self.logger = ctx.logger
|
|
37
|
+
self.token_manager = ctx.token_manager
|
|
38
|
+
|
|
39
|
+
@abc.abstractmethod
|
|
40
|
+
async def invoke(self, method: str, **kwargs) -> Any:
|
|
41
|
+
"""Generic fallback invocation method."""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
async def health_check(self) -> Dict[str, Any]:
|
|
45
|
+
"""Health check for the connector."""
|
|
46
|
+
return {"status": "healthy", "connector": self.__class__.__name__}
|
|
47
|
+
|
|
48
|
+
def get_metadata(self) -> Dict[str, Any]:
|
|
49
|
+
"""Get connector metadata."""
|
|
50
|
+
return {
|
|
51
|
+
"name": self.__class__.__name__,
|
|
52
|
+
"version": getattr(self, "__version__", "unknown"),
|
|
53
|
+
"description": self.__doc__ or "No description available",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ToolConnector(BaseConnector):
|
|
58
|
+
"""Base class for tool connectors."""
|
|
59
|
+
|
|
60
|
+
# Override in subclass
|
|
61
|
+
TOOL_ID: str = ""
|
|
62
|
+
|
|
63
|
+
def __init__(self, ctx: ConnectorContext):
|
|
64
|
+
super().__init__(ctx)
|
|
65
|
+
if not self.TOOL_ID:
|
|
66
|
+
raise ConnectorError("TOOL_ID must be set in connector subclass")
|
|
67
|
+
|
|
68
|
+
@abc.abstractmethod
|
|
69
|
+
async def invoke(self, method: str, **kwargs) -> Any:
|
|
70
|
+
"""
|
|
71
|
+
Invoke a tool method.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
method: The tool method to invoke
|
|
75
|
+
**kwargs: Method arguments
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The result of the tool invocation
|
|
79
|
+
"""
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
async def get_schema(self) -> Dict[str, Any]:
|
|
83
|
+
"""Get the tool schema/specification."""
|
|
84
|
+
return {
|
|
85
|
+
"tool_id": self.TOOL_ID,
|
|
86
|
+
"methods": self._get_available_methods(),
|
|
87
|
+
"auth_required": True,
|
|
88
|
+
"scopes": getattr(self, "REQUIRED_SCOPES", []),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
def _get_available_methods(self) -> list[str]:
|
|
92
|
+
"""Get list of available methods for this tool."""
|
|
93
|
+
methods = []
|
|
94
|
+
for attr_name in dir(self):
|
|
95
|
+
if not attr_name.startswith("_") and callable(getattr(self, attr_name)):
|
|
96
|
+
attr = getattr(self, attr_name)
|
|
97
|
+
if hasattr(attr, "__annotations__") and not attr_name in [
|
|
98
|
+
"invoke",
|
|
99
|
+
"health_check",
|
|
100
|
+
"get_metadata",
|
|
101
|
+
"get_schema",
|
|
102
|
+
]:
|
|
103
|
+
methods.append(attr_name)
|
|
104
|
+
return methods
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class AgentConnector(BaseConnector):
|
|
108
|
+
"""Base class for agent connectors that speak A2A or MCP."""
|
|
109
|
+
|
|
110
|
+
AGENT_ID: str = ""
|
|
111
|
+
|
|
112
|
+
def __init__(self, ctx: ConnectorContext):
|
|
113
|
+
super().__init__(ctx)
|
|
114
|
+
if not self.AGENT_ID:
|
|
115
|
+
raise ConnectorError("AGENT_ID must be set in connector subclass")
|
|
116
|
+
|
|
117
|
+
@abc.abstractmethod
|
|
118
|
+
async def invoke(self, input_text: str, **params) -> str:
|
|
119
|
+
"""
|
|
120
|
+
Invoke the agent.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
input_text: The input text/prompt for the agent
|
|
124
|
+
**params: Additional parameters for the agent
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
The agent's response
|
|
128
|
+
"""
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
async def get_capabilities(self) -> Dict[str, Any]:
|
|
132
|
+
"""Get agent capabilities."""
|
|
133
|
+
return {
|
|
134
|
+
"agent_id": self.AGENT_ID,
|
|
135
|
+
"supports_streaming": getattr(self, "SUPPORTS_STREAMING", False),
|
|
136
|
+
"max_tokens": getattr(self, "MAX_TOKENS", None),
|
|
137
|
+
"supported_models": getattr(self, "SUPPORTED_MODELS", []),
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async def validate_input(self, input_text: str, **params) -> bool:
|
|
141
|
+
"""Validate input before processing."""
|
|
142
|
+
if not input_text or not input_text.strip():
|
|
143
|
+
return False
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class MCPConnector(BaseConnector):
|
|
148
|
+
"""Base class for MCP (Model Context Protocol) connectors."""
|
|
149
|
+
|
|
150
|
+
MCP_VERSION: str = "1.0"
|
|
151
|
+
SERVER_NAME: str = ""
|
|
152
|
+
|
|
153
|
+
def __init__(self, ctx: ConnectorContext):
|
|
154
|
+
super().__init__(ctx)
|
|
155
|
+
if not self.SERVER_NAME:
|
|
156
|
+
raise ConnectorError("SERVER_NAME must be set in MCP connector subclass")
|
|
157
|
+
|
|
158
|
+
@abc.abstractmethod
|
|
159
|
+
async def list_tools(self) -> list[Dict[str, Any]]:
|
|
160
|
+
"""List available tools from the MCP server."""
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
@abc.abstractmethod
|
|
164
|
+
async def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any:
|
|
165
|
+
"""Call a specific tool on the MCP server."""
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
async def invoke(self, method: str, **kwargs) -> Any:
|
|
169
|
+
"""Generic invoke method for MCP connectors."""
|
|
170
|
+
if method == "list_tools":
|
|
171
|
+
return await self.list_tools()
|
|
172
|
+
elif method == "call_tool":
|
|
173
|
+
return await self.call_tool(
|
|
174
|
+
kwargs.get("tool_name", ""), kwargs.get("arguments", {})
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
raise ConnectorError(f"Unknown method: {method}")
|
|
178
|
+
|
|
179
|
+
async def get_server_info(self) -> Dict[str, Any]:
|
|
180
|
+
"""Get MCP server information."""
|
|
181
|
+
return {
|
|
182
|
+
"name": self.SERVER_NAME,
|
|
183
|
+
"version": self.MCP_VERSION,
|
|
184
|
+
"tools": await self.list_tools(),
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class HTTPConnectorMixin:
|
|
189
|
+
"""Mixin for connectors that make HTTP requests."""
|
|
190
|
+
|
|
191
|
+
def __init__(self, *args, **kwargs):
|
|
192
|
+
super().__init__(*args, **kwargs)
|
|
193
|
+
self.base_url = kwargs.get("base_url", "")
|
|
194
|
+
self.default_headers = kwargs.get("default_headers", {})
|
|
195
|
+
|
|
196
|
+
async def _make_request(
|
|
197
|
+
self,
|
|
198
|
+
method: str,
|
|
199
|
+
path: str,
|
|
200
|
+
headers: Optional[Dict[str, str]] = None,
|
|
201
|
+
**kwargs,
|
|
202
|
+
) -> httpx.Response:
|
|
203
|
+
"""Make an HTTP request with proper error handling."""
|
|
204
|
+
url = f"{self.base_url.rstrip('/')}/{path.lstrip('/')}"
|
|
205
|
+
request_headers = {**self.default_headers, **(headers or {})}
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
response = await self.session.request(
|
|
209
|
+
method, url, headers=request_headers, **kwargs
|
|
210
|
+
)
|
|
211
|
+
response.raise_for_status()
|
|
212
|
+
return response
|
|
213
|
+
except httpx.HTTPError as e:
|
|
214
|
+
self.logger.error(f"HTTP request failed: {e}")
|
|
215
|
+
raise ConnectorError(f"HTTP request failed: {e}")
|
|
216
|
+
|
|
217
|
+
async def _get(self, path: str, **kwargs) -> httpx.Response:
|
|
218
|
+
"""Make a GET request."""
|
|
219
|
+
return await self._make_request("GET", path, **kwargs)
|
|
220
|
+
|
|
221
|
+
async def _post(self, path: str, **kwargs) -> httpx.Response:
|
|
222
|
+
"""Make a POST request."""
|
|
223
|
+
return await self._make_request("POST", path, **kwargs)
|
|
224
|
+
|
|
225
|
+
async def _put(self, path: str, **kwargs) -> httpx.Response:
|
|
226
|
+
"""Make a PUT request."""
|
|
227
|
+
return await self._make_request("PUT", path, **kwargs)
|
|
228
|
+
|
|
229
|
+
async def _delete(self, path: str, **kwargs) -> httpx.Response:
|
|
230
|
+
"""Make a DELETE request."""
|
|
231
|
+
return await self._make_request("DELETE", path, **kwargs)
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Connector registry for plugin discovery and management.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Dict, List, Optional, Type
|
|
7
|
+
|
|
8
|
+
from stevedore import driver, named
|
|
9
|
+
|
|
10
|
+
from ..exceptions import ConnectorError
|
|
11
|
+
from .base import BaseConnector, ConnectorContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConnectorRegistry:
|
|
15
|
+
"""Registry for managing connector plugins."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, logger: Optional[logging.Logger] = None):
|
|
18
|
+
self.logger = logger or logging.getLogger(__name__)
|
|
19
|
+
self._loaded_connectors: Dict[str, Type[BaseConnector]] = {}
|
|
20
|
+
|
|
21
|
+
def load_connector(self, connector_id: str) -> Type[BaseConnector]:
|
|
22
|
+
"""
|
|
23
|
+
Load a connector by ID.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
connector_id: The connector ID (e.g., 'slack', 'github')
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
The connector class
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
ConnectorError: If connector cannot be loaded
|
|
33
|
+
"""
|
|
34
|
+
if connector_id in self._loaded_connectors:
|
|
35
|
+
return self._loaded_connectors[connector_id]
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
# Load via stevedore
|
|
39
|
+
mgr = driver.DriverManager(
|
|
40
|
+
namespace="af_connector_plugins",
|
|
41
|
+
name=connector_id,
|
|
42
|
+
invoke_on_load=False,
|
|
43
|
+
)
|
|
44
|
+
connector_class = mgr.driver
|
|
45
|
+
|
|
46
|
+
# Validate connector class
|
|
47
|
+
if not issubclass(connector_class, BaseConnector):
|
|
48
|
+
raise ConnectorError(
|
|
49
|
+
f"Connector {connector_id} must inherit from BaseConnector"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
self._loaded_connectors[connector_id] = connector_class
|
|
53
|
+
self.logger.info(f"Loaded connector: {connector_id}")
|
|
54
|
+
return connector_class
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
self.logger.error(f"Failed to load connector {connector_id}: {e}")
|
|
58
|
+
raise ConnectorError(f"Failed to load connector {connector_id}: {e}")
|
|
59
|
+
|
|
60
|
+
def list_available_connectors(self) -> List[str]:
|
|
61
|
+
"""
|
|
62
|
+
List all available connector IDs.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
List of connector IDs
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
from stevedore import extension
|
|
69
|
+
mgr = extension.ExtensionManager(
|
|
70
|
+
namespace="af_connector_plugins",
|
|
71
|
+
invoke_on_load=False,
|
|
72
|
+
)
|
|
73
|
+
return [ep.name for ep in mgr.extensions]
|
|
74
|
+
except Exception as e:
|
|
75
|
+
self.logger.error(f"Failed to list connectors: {e}")
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
def get_connector_info(self, connector_id: str) -> Dict[str, Any]:
|
|
79
|
+
"""
|
|
80
|
+
Get information about a connector.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
connector_id: The connector ID
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Connector information
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
connector_class = self.load_connector(connector_id)
|
|
90
|
+
return {
|
|
91
|
+
"id": connector_id,
|
|
92
|
+
"name": connector_class.__name__,
|
|
93
|
+
"description": connector_class.__doc__ or "No description",
|
|
94
|
+
"version": getattr(connector_class, "__version__", "unknown"),
|
|
95
|
+
"tool_id": getattr(connector_class, "TOOL_ID", None),
|
|
96
|
+
"agent_id": getattr(connector_class, "AGENT_ID", None),
|
|
97
|
+
"required_scopes": getattr(connector_class, "REQUIRED_SCOPES", []),
|
|
98
|
+
}
|
|
99
|
+
except Exception as e:
|
|
100
|
+
return {
|
|
101
|
+
"id": connector_id,
|
|
102
|
+
"error": str(e),
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
def create_connector_instance(
|
|
106
|
+
self, connector_id: str, context: ConnectorContext
|
|
107
|
+
) -> BaseConnector:
|
|
108
|
+
"""
|
|
109
|
+
Create a connector instance.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
connector_id: The connector ID
|
|
113
|
+
context: Connector context
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Connector instance
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
ConnectorError: If connector cannot be created
|
|
120
|
+
"""
|
|
121
|
+
try:
|
|
122
|
+
connector_class = self.load_connector(connector_id)
|
|
123
|
+
return connector_class(context)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
self.logger.error(f"Failed to create connector {connector_id}: {e}")
|
|
126
|
+
raise ConnectorError(f"Failed to create connector {connector_id}: {e}")
|
|
127
|
+
|
|
128
|
+
def load_multiple_connectors(self, connector_ids: List[str]) -> Dict[str, Type[BaseConnector]]:
|
|
129
|
+
"""
|
|
130
|
+
Load multiple connectors.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
connector_ids: List of connector IDs
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Dictionary of connector_id -> connector_class
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
mgr = named.NamedExtensionManager(
|
|
140
|
+
namespace="af_connector_plugins",
|
|
141
|
+
names=connector_ids,
|
|
142
|
+
invoke_on_load=False,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
result = {}
|
|
146
|
+
for ext in mgr.extensions:
|
|
147
|
+
connector_class = ext.obj
|
|
148
|
+
if not issubclass(connector_class, BaseConnector):
|
|
149
|
+
self.logger.warning(
|
|
150
|
+
f"Skipping {ext.name}: not a BaseConnector subclass"
|
|
151
|
+
)
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
result[ext.name] = connector_class
|
|
155
|
+
self._loaded_connectors[ext.name] = connector_class
|
|
156
|
+
|
|
157
|
+
return result
|
|
158
|
+
except Exception as e:
|
|
159
|
+
self.logger.error(f"Failed to load connectors {connector_ids}: {e}")
|
|
160
|
+
raise ConnectorError(f"Failed to load connectors: {e}")
|
|
161
|
+
|
|
162
|
+
def validate_connector(self, connector_id: str) -> bool:
|
|
163
|
+
"""
|
|
164
|
+
Validate a connector.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
connector_id: The connector ID
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
True if connector is valid, False otherwise
|
|
171
|
+
"""
|
|
172
|
+
try:
|
|
173
|
+
connector_class = self.load_connector(connector_id)
|
|
174
|
+
|
|
175
|
+
# Check required attributes
|
|
176
|
+
if hasattr(connector_class, "TOOL_ID"):
|
|
177
|
+
if not connector_class.TOOL_ID:
|
|
178
|
+
self.logger.error(f"Connector {connector_id} has empty TOOL_ID")
|
|
179
|
+
return False
|
|
180
|
+
elif hasattr(connector_class, "AGENT_ID"):
|
|
181
|
+
if not connector_class.AGENT_ID:
|
|
182
|
+
self.logger.error(f"Connector {connector_id} has empty AGENT_ID")
|
|
183
|
+
return False
|
|
184
|
+
else:
|
|
185
|
+
self.logger.error(f"Connector {connector_id} missing TOOL_ID or AGENT_ID")
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
# Check required methods
|
|
189
|
+
if not hasattr(connector_class, "invoke"):
|
|
190
|
+
self.logger.error(f"Connector {connector_id} missing invoke method")
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
return True
|
|
194
|
+
except Exception as e:
|
|
195
|
+
self.logger.error(f"Connector {connector_id} validation failed: {e}")
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
def get_connector_schema(self, connector_id: str) -> Dict[str, Any]:
|
|
199
|
+
"""
|
|
200
|
+
Get the schema for a connector.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
connector_id: The connector ID
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Connector schema
|
|
207
|
+
"""
|
|
208
|
+
try:
|
|
209
|
+
connector_class = self.load_connector(connector_id)
|
|
210
|
+
|
|
211
|
+
# Create a dummy context for schema generation
|
|
212
|
+
import logging
|
|
213
|
+
import httpx
|
|
214
|
+
from ..auth.token_cache import TokenManager, VaultClient
|
|
215
|
+
|
|
216
|
+
dummy_http = httpx.AsyncClient()
|
|
217
|
+
dummy_vault = VaultClient("http://localhost", dummy_http, logging.getLogger())
|
|
218
|
+
dummy_token_manager = TokenManager("default", dummy_vault)
|
|
219
|
+
|
|
220
|
+
dummy_context = ConnectorContext(
|
|
221
|
+
tenant_id="default",
|
|
222
|
+
user_id="system",
|
|
223
|
+
http=dummy_http,
|
|
224
|
+
token_manager=dummy_token_manager,
|
|
225
|
+
logger=logging.getLogger(),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Create instance to get schema
|
|
229
|
+
instance = connector_class(dummy_context)
|
|
230
|
+
|
|
231
|
+
if hasattr(instance, "get_schema"):
|
|
232
|
+
return instance.get_schema()
|
|
233
|
+
else:
|
|
234
|
+
return {
|
|
235
|
+
"connector_id": connector_id,
|
|
236
|
+
"methods": [],
|
|
237
|
+
"description": "Schema not available",
|
|
238
|
+
}
|
|
239
|
+
except Exception as e:
|
|
240
|
+
self.logger.error(f"Failed to get schema for {connector_id}: {e}")
|
|
241
|
+
return {
|
|
242
|
+
"connector_id": connector_id,
|
|
243
|
+
"error": str(e),
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
def clear_cache(self):
|
|
247
|
+
"""Clear the connector cache."""
|
|
248
|
+
self._loaded_connectors.clear()
|
|
249
|
+
self.logger.info("Connector cache cleared")
|
|
250
|
+
|
|
251
|
+
def get_loaded_connectors(self) -> Dict[str, Type[BaseConnector]]:
|
|
252
|
+
"""Get all loaded connectors."""
|
|
253
|
+
return self._loaded_connectors.copy()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# Global registry instance
|
|
257
|
+
_registry = ConnectorRegistry()
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def get_connector_registry() -> ConnectorRegistry:
|
|
261
|
+
"""Get the global connector registry."""
|
|
262
|
+
return _registry
|
af_sdk/dx/__init__.py
ADDED
af_sdk/dx/decorators.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, Optional
|
|
4
|
+
from functools import wraps
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ToolFunction:
|
|
8
|
+
"""Simple wrapper that carries metadata for a Python function tool."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, fn: Callable[..., Any], name: Optional[str] = None, description: Optional[str] = None):
|
|
11
|
+
self.fn = fn
|
|
12
|
+
self.name = name or fn.__name__
|
|
13
|
+
self.description = description or (fn.__doc__ or "")
|
|
14
|
+
|
|
15
|
+
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
16
|
+
return self.fn(*args, **kwargs)
|
|
17
|
+
|
|
18
|
+
def spec(self) -> Dict[str, Any]:
|
|
19
|
+
return {"name": self.name, "description": self.description}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def tool(fn: Callable[..., Any] | None = None, *, name: Optional[str] = None, description: Optional[str] = None):
|
|
23
|
+
"""Decorator to mark a function as a tool.
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
@tool
|
|
27
|
+
def do(x: str) -> str: ...
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def _wrap(func: Callable[..., Any]) -> ToolFunction:
|
|
31
|
+
wrapped = ToolFunction(func, name=name, description=description)
|
|
32
|
+
@wraps(func)
|
|
33
|
+
def inner(*args: Any, **kwargs: Any) -> Any:
|
|
34
|
+
return wrapped(*args, **kwargs)
|
|
35
|
+
inner._af_tool = wrapped # type: ignore[attr-defined]
|
|
36
|
+
return inner # type: ignore[return-value]
|
|
37
|
+
|
|
38
|
+
return _wrap if fn is None else _wrap(fn)
|
|
39
|
+
|
|
40
|
+
|