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/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
+ )
@@ -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
@@ -0,0 +1,7 @@
1
+ """
2
+ Transport layer for Agentic Fabric SDK.
3
+ """
4
+
5
+ from .http import HTTPClient
6
+
7
+ __all__ = ["HTTPClient"]