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
af_sdk/exceptions.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exception classes for the Agentic Fabric SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AFError(Exception):
|
|
9
|
+
"""Base exception for all Agentic Fabric errors."""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
message: str,
|
|
14
|
+
error_code: str = "SERVER_ERROR",
|
|
15
|
+
request_id: Optional[str] = None,
|
|
16
|
+
details: Optional[Dict[str, Any]] = None,
|
|
17
|
+
):
|
|
18
|
+
self.message = message
|
|
19
|
+
self.error_code = error_code
|
|
20
|
+
self.request_id = request_id
|
|
21
|
+
self.details = details or {}
|
|
22
|
+
super().__init__(message)
|
|
23
|
+
|
|
24
|
+
def __str__(self) -> str:
|
|
25
|
+
return f"{self.error_code}: {self.message}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AuthenticationError(AFError):
|
|
29
|
+
"""Authentication failed - missing or invalid JWT or mTLS cert."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, message: str = "Authentication failed", **kwargs):
|
|
32
|
+
super().__init__(message, error_code="AUTHENTICATION_FAILED", **kwargs)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AuthorizationError(AFError):
|
|
36
|
+
"""Authorization failed - OPA policy denied or scope mismatch."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, message: str = "Authorization denied", **kwargs):
|
|
39
|
+
super().__init__(message, error_code="FORBIDDEN", **kwargs)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class NotFoundError(AFError):
|
|
43
|
+
"""Resource or endpoint not found."""
|
|
44
|
+
|
|
45
|
+
def __init__(self, message: str = "Resource not found", **kwargs):
|
|
46
|
+
super().__init__(message, error_code="NOT_FOUND", **kwargs)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ValidationError(AFError):
|
|
50
|
+
"""Request validation failed."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, message: str = "Validation failed", **kwargs):
|
|
53
|
+
super().__init__(message, error_code="VALIDATION_ERROR", **kwargs)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ConflictError(AFError):
|
|
57
|
+
"""Resource conflict - duplicate ID or version clash."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, message: str = "Resource conflict", **kwargs):
|
|
60
|
+
super().__init__(message, error_code="CONFLICT", **kwargs)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RateLimitError(AFError):
|
|
64
|
+
"""Rate limit exceeded."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, message: str = "Rate limit exceeded", **kwargs):
|
|
67
|
+
super().__init__(message, error_code="RATE_LIMITED", **kwargs)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class UpstreamError(AFError):
|
|
71
|
+
"""Error from upstream service."""
|
|
72
|
+
|
|
73
|
+
def __init__(self, message: str = "Upstream service error", **kwargs):
|
|
74
|
+
super().__init__(message, error_code="UPSTREAM_ERROR", **kwargs)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ServiceUnavailableError(AFError):
|
|
78
|
+
"""Service temporarily unavailable."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, message: str = "Service unavailable", **kwargs):
|
|
81
|
+
super().__init__(message, error_code="SERVICE_UNAVAILABLE", **kwargs)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ConnectorError(AFError):
|
|
85
|
+
"""Error in connector implementation."""
|
|
86
|
+
|
|
87
|
+
def __init__(self, message: str = "Connector error", **kwargs):
|
|
88
|
+
super().__init__(message, error_code="CONNECTOR_ERROR", **kwargs)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TokenRefreshError(AFError):
|
|
92
|
+
"""Failed to refresh OAuth token."""
|
|
93
|
+
|
|
94
|
+
def __init__(self, message: str = "Token refresh failed", **kwargs):
|
|
95
|
+
super().__init__(message, error_code="TOKEN_REFRESH_ERROR", **kwargs)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class VaultError(AFError):
|
|
99
|
+
"""Error accessing vault/secrets."""
|
|
100
|
+
|
|
101
|
+
def __init__(self, message: str = "Vault error", **kwargs):
|
|
102
|
+
super().__init__(message, error_code="VAULT_ERROR", **kwargs)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class EventError(AFError):
|
|
106
|
+
"""Error in event processing."""
|
|
107
|
+
|
|
108
|
+
def __init__(self, message: str = "Event error", **kwargs):
|
|
109
|
+
super().__init__(message, error_code="EVENT_ERROR", **kwargs)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Mapping from error codes to exception classes
|
|
113
|
+
ERROR_CODE_TO_EXCEPTION = {
|
|
114
|
+
"AUTHENTICATION_FAILED": AuthenticationError,
|
|
115
|
+
"FORBIDDEN": AuthorizationError,
|
|
116
|
+
"NOT_FOUND": NotFoundError,
|
|
117
|
+
"VALIDATION_ERROR": ValidationError,
|
|
118
|
+
"CONFLICT": ConflictError,
|
|
119
|
+
"RATE_LIMITED": RateLimitError,
|
|
120
|
+
"UPSTREAM_ERROR": UpstreamError,
|
|
121
|
+
"SERVICE_UNAVAILABLE": ServiceUnavailableError,
|
|
122
|
+
"CONNECTOR_ERROR": ConnectorError,
|
|
123
|
+
"TOKEN_REFRESH_ERROR": TokenRefreshError,
|
|
124
|
+
"VAULT_ERROR": VaultError,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def create_exception_from_response(response_data: Dict[str, Any]) -> AFError:
|
|
129
|
+
"""Create an exception from an API error response."""
|
|
130
|
+
error_code = response_data.get("error", "SERVER_ERROR")
|
|
131
|
+
message = response_data.get("message", "Unknown error")
|
|
132
|
+
request_id = response_data.get("request_id")
|
|
133
|
+
details = response_data.get("details", {})
|
|
134
|
+
|
|
135
|
+
exception_class = ERROR_CODE_TO_EXCEPTION.get(error_code, AFError)
|
|
136
|
+
return exception_class(
|
|
137
|
+
message=message,
|
|
138
|
+
request_id=request_id,
|
|
139
|
+
details=details,
|
|
140
|
+
)
|
af_sdk/fabriq_client.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FabriqClient: High-level async helper for Agentic Fabric Gateway.
|
|
3
|
+
|
|
4
|
+
This client wraps common Gateway API flows so agent developers can:
|
|
5
|
+
- List and invoke tools
|
|
6
|
+
- Register and invoke MCP servers (via proxy)
|
|
7
|
+
- Invoke agents (via proxy)
|
|
8
|
+
- Manage per-user secrets via the Gateway-backed Vault API
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
from af_sdk.fabriq_client import FabriqClient
|
|
12
|
+
|
|
13
|
+
async with FabriqClient(base_url="http://localhost:8000", auth_token=JWT) as af:
|
|
14
|
+
tools = await af.list_tools()
|
|
15
|
+
result = await af.invoke_tool(tool_id, method="list_items", parameters={"limit": 10})
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any, Dict, Optional
|
|
21
|
+
|
|
22
|
+
from .transport.http import HTTPClient
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FabriqClient:
|
|
26
|
+
"""Async helper around the Gateway REST API.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
base_url: Gateway base URL (e.g., "http://localhost:8000").
|
|
30
|
+
auth_token: Bearer JWT with required scopes.
|
|
31
|
+
api_prefix: API root prefix. Defaults to "/api/v1".
|
|
32
|
+
timeout: Request timeout in seconds.
|
|
33
|
+
retries: Number of retry attempts for transient errors.
|
|
34
|
+
backoff_factor: Exponential backoff base delay in seconds.
|
|
35
|
+
trace_enabled: Enable OpenTelemetry HTTPX instrumentation.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
*,
|
|
41
|
+
base_url: str,
|
|
42
|
+
auth_token: Optional[str] = None,
|
|
43
|
+
api_prefix: str = "/api/v1",
|
|
44
|
+
timeout: float = 30.0,
|
|
45
|
+
retries: int = 3,
|
|
46
|
+
backoff_factor: float = 0.5,
|
|
47
|
+
trace_enabled: bool = True,
|
|
48
|
+
extra_headers: Optional[Dict[str, str]] = None,
|
|
49
|
+
) -> None:
|
|
50
|
+
self._root = base_url.rstrip("/")
|
|
51
|
+
self._api = api_prefix if api_prefix.startswith("/") else f"/{api_prefix}"
|
|
52
|
+
self._extra_headers = extra_headers or {}
|
|
53
|
+
self._http = HTTPClient(
|
|
54
|
+
base_url=f"{self._root}{self._api}",
|
|
55
|
+
timeout=timeout,
|
|
56
|
+
retries=retries,
|
|
57
|
+
backoff_factor=backoff_factor,
|
|
58
|
+
auth_token=auth_token,
|
|
59
|
+
trace_enabled=trace_enabled,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
async def __aenter__(self) -> "FabriqClient":
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
async def __aexit__(self, exc_type, exc, tb) -> None:
|
|
66
|
+
await self.close()
|
|
67
|
+
|
|
68
|
+
async def close(self) -> None:
|
|
69
|
+
await self._http.close()
|
|
70
|
+
|
|
71
|
+
# -----------------
|
|
72
|
+
# Tools
|
|
73
|
+
# -----------------
|
|
74
|
+
async def list_tools(self, *, page: int = 1, page_size: int = 20, search: Optional[str] = None) -> Dict[str, Any]:
|
|
75
|
+
params: Dict[str, Any] = {"page": page, "page_size": page_size}
|
|
76
|
+
if search:
|
|
77
|
+
params["search"] = search
|
|
78
|
+
r = await self._http.get("/tools", params=params, headers=self._extra_headers)
|
|
79
|
+
return r.json()
|
|
80
|
+
|
|
81
|
+
async def invoke_tool(
|
|
82
|
+
self,
|
|
83
|
+
tool_id: str,
|
|
84
|
+
*,
|
|
85
|
+
method: str,
|
|
86
|
+
parameters: Optional[Dict[str, Any]] = None,
|
|
87
|
+
context: Optional[Dict[str, Any]] = None,
|
|
88
|
+
) -> Dict[str, Any]:
|
|
89
|
+
body = {"method": method}
|
|
90
|
+
if parameters is not None:
|
|
91
|
+
body["parameters"] = parameters
|
|
92
|
+
if context is not None:
|
|
93
|
+
body["context"] = context
|
|
94
|
+
r = await self._http.post(f"/tools/{tool_id}/invoke", json=body, headers=self._extra_headers)
|
|
95
|
+
return r.json()
|
|
96
|
+
|
|
97
|
+
# -----------------
|
|
98
|
+
# MCP Servers
|
|
99
|
+
# -----------------
|
|
100
|
+
async def register_mcp_server(
|
|
101
|
+
self,
|
|
102
|
+
*,
|
|
103
|
+
name: str,
|
|
104
|
+
base_url: str,
|
|
105
|
+
description: Optional[str] = None,
|
|
106
|
+
auth_type: str = "API_KEY",
|
|
107
|
+
source: str = "STATIC",
|
|
108
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
109
|
+
) -> Dict[str, Any]:
|
|
110
|
+
body: Dict[str, Any] = {
|
|
111
|
+
"name": name,
|
|
112
|
+
"base_url": base_url,
|
|
113
|
+
"auth_type": auth_type,
|
|
114
|
+
"source": source,
|
|
115
|
+
}
|
|
116
|
+
if description is not None:
|
|
117
|
+
body["description"] = description
|
|
118
|
+
if metadata is not None:
|
|
119
|
+
body["metadata"] = metadata
|
|
120
|
+
r = await self._http.post("/mcp/servers", json=body, headers=self._extra_headers)
|
|
121
|
+
return r.json()
|
|
122
|
+
|
|
123
|
+
async def list_mcp_servers(self, *, page: int = 1, page_size: int = 20, search: Optional[str] = None) -> Dict[str, Any]:
|
|
124
|
+
params: Dict[str, Any] = {"page": page, "page_size": page_size}
|
|
125
|
+
if search:
|
|
126
|
+
params["search"] = search
|
|
127
|
+
r = await self._http.get("/mcp/servers", params=params, headers=self._extra_headers)
|
|
128
|
+
return r.json()
|
|
129
|
+
|
|
130
|
+
async def invoke_mcp(self, *, server_id: str, payload: Dict[str, Any], raw: bool = False) -> Dict[str, Any]:
|
|
131
|
+
r = await self._http.post(f"/proxy/mcp/{server_id}/invoke", json={"payload": payload, "raw": raw}, headers=self._extra_headers)
|
|
132
|
+
return r.json()
|
|
133
|
+
|
|
134
|
+
# -----------------
|
|
135
|
+
# Agents
|
|
136
|
+
# -----------------
|
|
137
|
+
async def list_agents(self, *, page: int = 1, page_size: int = 20, sort_by: str = "name", sort_order: str = "asc") -> Dict[str, Any]:
|
|
138
|
+
params: Dict[str, Any] = {"page": page, "page_size": page_size, "sort_by": sort_by, "sort_order": sort_order}
|
|
139
|
+
r = await self._http.get("/agents", params=params, headers=self._extra_headers)
|
|
140
|
+
return r.json()
|
|
141
|
+
|
|
142
|
+
async def invoke_agent(self, *, agent_id: str, params: Dict[str, Any], raw: bool = False) -> Dict[str, Any]:
|
|
143
|
+
r = await self._http.post(f"/proxy/agents/{agent_id}/invoke", json={"params": params, "raw": raw}, headers=self._extra_headers)
|
|
144
|
+
return r.json()
|
|
145
|
+
|
|
146
|
+
# -----------------
|
|
147
|
+
# Secrets (Gateway-backed Vault)
|
|
148
|
+
# -----------------
|
|
149
|
+
async def get_secret(self, *, path: str, version: Optional[int] = None) -> Dict[str, Any]:
|
|
150
|
+
params = {"version": version} if version is not None else None
|
|
151
|
+
r = await self._http.get(f"/secrets/{path}", params=params, headers=self._extra_headers)
|
|
152
|
+
return r.json()
|
|
153
|
+
|
|
154
|
+
async def create_secret(
|
|
155
|
+
self,
|
|
156
|
+
*,
|
|
157
|
+
path: str,
|
|
158
|
+
value: str,
|
|
159
|
+
description: Optional[str] = None,
|
|
160
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
161
|
+
ttl: Optional[int] = None,
|
|
162
|
+
) -> Dict[str, Any]:
|
|
163
|
+
body: Dict[str, Any] = {"value": value}
|
|
164
|
+
if description is not None:
|
|
165
|
+
body["description"] = description
|
|
166
|
+
if metadata is not None:
|
|
167
|
+
body["metadata"] = metadata
|
|
168
|
+
if ttl is not None:
|
|
169
|
+
body["ttl"] = ttl
|
|
170
|
+
r = await self._http.post(f"/secrets/{path}", json=body, headers=self._extra_headers)
|
|
171
|
+
return r.json()
|
|
172
|
+
|
|
173
|
+
async def update_secret(
|
|
174
|
+
self,
|
|
175
|
+
*,
|
|
176
|
+
path: str,
|
|
177
|
+
value: Optional[str] = None,
|
|
178
|
+
description: Optional[str] = None,
|
|
179
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
180
|
+
ttl: Optional[int] = None,
|
|
181
|
+
) -> Dict[str, Any]:
|
|
182
|
+
body: Dict[str, Any] = {}
|
|
183
|
+
if value is not None:
|
|
184
|
+
body["value"] = value
|
|
185
|
+
if description is not None:
|
|
186
|
+
body["description"] = description
|
|
187
|
+
if metadata is not None:
|
|
188
|
+
body["metadata"] = metadata
|
|
189
|
+
if ttl is not None:
|
|
190
|
+
body["ttl"] = ttl
|
|
191
|
+
r = await self._http.put(f"/secrets/{path}", json=body, headers=self._extra_headers)
|
|
192
|
+
return r.json()
|
|
193
|
+
|
|
194
|
+
async def delete_secret(self, *, path: str) -> Dict[str, Any]:
|
|
195
|
+
r = await self._http.delete(f"/secrets/{path}", headers=self._extra_headers)
|
|
196
|
+
return r.json() if r.content else {"status": "deleted"}
|
|
197
|
+
|
|
198
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data models for Agentic Fabric SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .types import (
|
|
6
|
+
Agent,
|
|
7
|
+
AgentCreate,
|
|
8
|
+
AgentInvokeRequest,
|
|
9
|
+
AgentInvokeResult,
|
|
10
|
+
ErrorResponse,
|
|
11
|
+
HealthResponse,
|
|
12
|
+
McpServer,
|
|
13
|
+
McpServerCreate,
|
|
14
|
+
MetricsResponse,
|
|
15
|
+
OAuthToken,
|
|
16
|
+
PaginatedResponse,
|
|
17
|
+
Secret,
|
|
18
|
+
SecretMetadata,
|
|
19
|
+
SecretPutRequest,
|
|
20
|
+
TokenExchangeRequest,
|
|
21
|
+
TokenExchangeResponse,
|
|
22
|
+
Tool,
|
|
23
|
+
ToolInvokeRequest,
|
|
24
|
+
ToolInvokeResult,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"Agent",
|
|
29
|
+
"AgentCreate",
|
|
30
|
+
"AgentInvokeRequest",
|
|
31
|
+
"AgentInvokeResult",
|
|
32
|
+
"Tool",
|
|
33
|
+
"ToolInvokeRequest",
|
|
34
|
+
"ToolInvokeResult",
|
|
35
|
+
"McpServer",
|
|
36
|
+
"McpServerCreate",
|
|
37
|
+
"Secret",
|
|
38
|
+
"SecretMetadata",
|
|
39
|
+
"SecretPutRequest",
|
|
40
|
+
"TokenExchangeRequest",
|
|
41
|
+
"TokenExchangeResponse",
|
|
42
|
+
"OAuthToken",
|
|
43
|
+
"ErrorResponse",
|
|
44
|
+
"PaginatedResponse",
|
|
45
|
+
"HealthResponse",
|
|
46
|
+
"MetricsResponse",
|
|
47
|
+
]
|
af_sdk/models/audit.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional, Dict, Any
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AuditEvent(BaseModel):
|
|
10
|
+
"""Canonical audit event schema.
|
|
11
|
+
|
|
12
|
+
Designed to avoid storing sensitive values; callers should pre-redact.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
event_id: str = Field(default_factory=lambda: str(uuid4()))
|
|
16
|
+
timestamp: datetime = Field(default_factory=datetime.utcnow)
|
|
17
|
+
tenant_id: str
|
|
18
|
+
actor_id: Optional[str] = None
|
|
19
|
+
actor_scopes: Optional[list[str]] = None
|
|
20
|
+
source_service: str # af-gateway | af-registry | other
|
|
21
|
+
action: str # e.g., agent.create, tool.delete, auth.login
|
|
22
|
+
resource_type: Optional[str] = None
|
|
23
|
+
resource_id: Optional[str] = None
|
|
24
|
+
route: Optional[str] = None
|
|
25
|
+
method: Optional[str] = None
|
|
26
|
+
status: str = "success" # success | failure | denied
|
|
27
|
+
request_id: Optional[str] = None
|
|
28
|
+
client_ip: Optional[str] = None
|
|
29
|
+
user_agent: Optional[str] = None
|
|
30
|
+
mtls_san: Optional[str] = None
|
|
31
|
+
pop_thumbprint: Optional[str] = None
|
|
32
|
+
details: Dict[str, Any] = Field(default_factory=dict)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def build_audit_subject(source_service: str, action: str, tenant_id: str) -> str:
|
|
36
|
+
"""Generate audit subject aligned with AUDIT stream subjects.
|
|
37
|
+
|
|
38
|
+
Format: audit.<service>.<action>.<tenant>
|
|
39
|
+
"""
|
|
40
|
+
service = source_service.replace("_", "-")
|
|
41
|
+
action_norm = action.replace("/", ".")
|
|
42
|
+
return f"audit.{service}.{action_norm}.{tenant_id}"
|
|
43
|
+
|
|
44
|
+
|
af_sdk/models/types.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pydantic models for Agentic Fabric SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Agent(BaseModel):
|
|
13
|
+
"""Agent model."""
|
|
14
|
+
|
|
15
|
+
id: str
|
|
16
|
+
name: str
|
|
17
|
+
description: Optional[str] = None
|
|
18
|
+
owner: str
|
|
19
|
+
tenant_id: str
|
|
20
|
+
group_path: Optional[str] = None
|
|
21
|
+
endpoint_url: Optional[str] = None
|
|
22
|
+
auth_type: Optional[str] = None
|
|
23
|
+
scopes: List[str] = Field(default_factory=list)
|
|
24
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
25
|
+
created_at: datetime
|
|
26
|
+
updated_at: datetime
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AgentCreate(BaseModel):
|
|
30
|
+
"""Request model for creating an agent."""
|
|
31
|
+
|
|
32
|
+
id: str
|
|
33
|
+
name: str
|
|
34
|
+
description: Optional[str] = None
|
|
35
|
+
endpoint_url: Optional[str] = None
|
|
36
|
+
auth_type: Optional[str] = None
|
|
37
|
+
scopes: List[str] = Field(default_factory=list)
|
|
38
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
39
|
+
group_path: Optional[str] = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AgentInvokeRequest(BaseModel):
|
|
43
|
+
"""Request model for invoking an agent."""
|
|
44
|
+
|
|
45
|
+
input: str
|
|
46
|
+
params: Dict[str, Any] = Field(default_factory=dict)
|
|
47
|
+
max_tokens: Optional[int] = None
|
|
48
|
+
temperature: Optional[float] = None
|
|
49
|
+
stream: bool = False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class AgentInvokeResult(BaseModel):
|
|
53
|
+
"""Result model for agent invocation."""
|
|
54
|
+
|
|
55
|
+
output: str
|
|
56
|
+
logs: List[str] = Field(default_factory=list)
|
|
57
|
+
usage: Optional[Dict[str, Any]] = None
|
|
58
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
59
|
+
status: str = "success"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Tool(BaseModel):
|
|
63
|
+
"""Tool model."""
|
|
64
|
+
|
|
65
|
+
id: str
|
|
66
|
+
name: str
|
|
67
|
+
version: str
|
|
68
|
+
description: Optional[str] = None
|
|
69
|
+
protocol: str = "MCP"
|
|
70
|
+
auth_type: str = "OAuth2"
|
|
71
|
+
scopes: List[str] = Field(default_factory=list)
|
|
72
|
+
endpoints: Dict[str, Any] = Field(default_factory=dict)
|
|
73
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
74
|
+
created_at: datetime
|
|
75
|
+
updated_at: datetime
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ToolInvokeRequest(BaseModel):
|
|
79
|
+
"""Request model for invoking a tool.
|
|
80
|
+
|
|
81
|
+
DX alignment: Gateway expects `parameters` and `context`.
|
|
82
|
+
For backward compatibility, we still accept `args` on input and map it to `parameters`.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
method: str
|
|
86
|
+
parameters: Dict[str, Any] = Field(default_factory=dict)
|
|
87
|
+
context: Dict[str, Any] = Field(default_factory=dict)
|
|
88
|
+
# Back-compat input alias; not used in output
|
|
89
|
+
args: Optional[Dict[str, Any]] = None
|
|
90
|
+
timeout: Optional[int] = None
|
|
91
|
+
|
|
92
|
+
# Support pydantic v1 and v2 root validation to map args->parameters
|
|
93
|
+
@classmethod
|
|
94
|
+
def __get_validators__(cls): # type: ignore[override]
|
|
95
|
+
# Pydantic v1 compatibility hook
|
|
96
|
+
yield cls._pre_root_validator
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def _pre_root_validator(cls, values):
|
|
100
|
+
# When constructed from dict, map legacy `args` to `parameters` if provided
|
|
101
|
+
if isinstance(values, dict):
|
|
102
|
+
if "parameters" not in values and "args" in values and isinstance(values["args"], dict):
|
|
103
|
+
# Don't mutate original input dict unexpectedly
|
|
104
|
+
new_values = dict(values)
|
|
105
|
+
new_values["parameters"] = new_values.get("args", {}) or {}
|
|
106
|
+
return new_values
|
|
107
|
+
return values
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class ToolInvokeResult(BaseModel):
|
|
111
|
+
"""Result model for tool invocation.
|
|
112
|
+
|
|
113
|
+
DX alignment: Gateway returns `{ result, metadata, logs }`.
|
|
114
|
+
We keep prior fields (`status`, `data`, `headers`) optional for back-compat.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
# Gateway-aligned fields
|
|
118
|
+
result: Optional[Any] = None
|
|
119
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
120
|
+
logs: Optional[list[str]] = None
|
|
121
|
+
|
|
122
|
+
# Back-compat fields
|
|
123
|
+
status: Optional[str] = None
|
|
124
|
+
data: Optional[Any] = None
|
|
125
|
+
headers: Dict[str, str] = Field(default_factory=dict)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class McpServer(BaseModel):
|
|
129
|
+
"""MCP Server model."""
|
|
130
|
+
|
|
131
|
+
id: UUID
|
|
132
|
+
base_url: str
|
|
133
|
+
auth_type: str = Field(description="One of: API_KEY, MTLS, OIDC")
|
|
134
|
+
source: str = Field(description="One of: STATIC, DISCOVERED, SLACK, CONFIG")
|
|
135
|
+
tenant_id: str
|
|
136
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
137
|
+
created_at: datetime
|
|
138
|
+
updated_at: datetime
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class McpServerCreate(BaseModel):
|
|
142
|
+
"""Request model for creating an MCP server."""
|
|
143
|
+
|
|
144
|
+
base_url: str
|
|
145
|
+
auth_type: str = Field(description="One of: API_KEY, MTLS, OIDC")
|
|
146
|
+
source: str = Field(description="One of: STATIC, DISCOVERED, SLACK, CONFIG")
|
|
147
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class Secret(BaseModel):
|
|
151
|
+
"""Secret model."""
|
|
152
|
+
|
|
153
|
+
path: str
|
|
154
|
+
data: Dict[str, Any]
|
|
155
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
156
|
+
version: int
|
|
157
|
+
created_at: datetime
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class SecretMetadata(BaseModel):
|
|
161
|
+
"""Secret metadata model."""
|
|
162
|
+
|
|
163
|
+
path: str
|
|
164
|
+
version: int
|
|
165
|
+
created_at: datetime
|
|
166
|
+
updated_at: datetime
|
|
167
|
+
versions: List[int] = Field(default_factory=list)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class SecretPutRequest(BaseModel):
|
|
171
|
+
"""Request model for creating/updating a secret."""
|
|
172
|
+
|
|
173
|
+
path: str
|
|
174
|
+
data: Dict[str, Any]
|
|
175
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class TokenExchangeRequest(BaseModel):
|
|
179
|
+
"""Request model for token exchange (RFC 8693)."""
|
|
180
|
+
|
|
181
|
+
subject_token: str
|
|
182
|
+
actor_token: Optional[str] = None
|
|
183
|
+
scope: Optional[str] = None
|
|
184
|
+
audience: Optional[str] = None
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class TokenExchangeResponse(BaseModel):
|
|
188
|
+
"""Response model for token exchange."""
|
|
189
|
+
|
|
190
|
+
access_token: str
|
|
191
|
+
token_type: str = "Bearer"
|
|
192
|
+
expires_in: int
|
|
193
|
+
scope: Optional[str] = None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class OAuthToken(BaseModel):
|
|
197
|
+
"""OAuth token model."""
|
|
198
|
+
|
|
199
|
+
access_token: str
|
|
200
|
+
refresh_token: Optional[str] = None
|
|
201
|
+
token_type: str = "Bearer"
|
|
202
|
+
expires_at: datetime
|
|
203
|
+
scopes: List[str] = Field(default_factory=list)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class ErrorResponse(BaseModel):
|
|
207
|
+
"""Error response model."""
|
|
208
|
+
|
|
209
|
+
error: str
|
|
210
|
+
message: str
|
|
211
|
+
request_id: Optional[str] = None
|
|
212
|
+
details: Dict[str, Any] = Field(default_factory=dict)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class PaginatedResponse(BaseModel):
|
|
216
|
+
"""Paginated response model."""
|
|
217
|
+
|
|
218
|
+
items: List[Any]
|
|
219
|
+
total: int
|
|
220
|
+
page: int = 1
|
|
221
|
+
page_size: int = 50
|
|
222
|
+
has_next: bool = False
|
|
223
|
+
has_prev: bool = False
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class HealthResponse(BaseModel):
|
|
227
|
+
"""Health check response model."""
|
|
228
|
+
|
|
229
|
+
status: str
|
|
230
|
+
version: str
|
|
231
|
+
timestamp: datetime
|
|
232
|
+
components: Dict[str, str] = Field(default_factory=dict)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class MetricsResponse(BaseModel):
|
|
236
|
+
"""Metrics response model."""
|
|
237
|
+
|
|
238
|
+
requests_total: int
|
|
239
|
+
requests_per_second: float
|
|
240
|
+
avg_response_time: float
|
|
241
|
+
error_rate: float
|
|
242
|
+
active_connections: int
|
af_sdk/py.typed
ADDED
|
File without changes
|