agentfield 0.1.22rc2__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.
Files changed (42) hide show
  1. agentfield/__init__.py +66 -0
  2. agentfield/agent.py +3569 -0
  3. agentfield/agent_ai.py +1125 -0
  4. agentfield/agent_cli.py +386 -0
  5. agentfield/agent_field_handler.py +494 -0
  6. agentfield/agent_mcp.py +534 -0
  7. agentfield/agent_registry.py +29 -0
  8. agentfield/agent_server.py +1185 -0
  9. agentfield/agent_utils.py +269 -0
  10. agentfield/agent_workflow.py +323 -0
  11. agentfield/async_config.py +278 -0
  12. agentfield/async_execution_manager.py +1227 -0
  13. agentfield/client.py +1447 -0
  14. agentfield/connection_manager.py +280 -0
  15. agentfield/decorators.py +527 -0
  16. agentfield/did_manager.py +337 -0
  17. agentfield/dynamic_skills.py +304 -0
  18. agentfield/execution_context.py +255 -0
  19. agentfield/execution_state.py +453 -0
  20. agentfield/http_connection_manager.py +429 -0
  21. agentfield/litellm_adapters.py +140 -0
  22. agentfield/logger.py +249 -0
  23. agentfield/mcp_client.py +204 -0
  24. agentfield/mcp_manager.py +340 -0
  25. agentfield/mcp_stdio_bridge.py +550 -0
  26. agentfield/memory.py +723 -0
  27. agentfield/memory_events.py +489 -0
  28. agentfield/multimodal.py +173 -0
  29. agentfield/multimodal_response.py +403 -0
  30. agentfield/pydantic_utils.py +227 -0
  31. agentfield/rate_limiter.py +280 -0
  32. agentfield/result_cache.py +441 -0
  33. agentfield/router.py +190 -0
  34. agentfield/status.py +70 -0
  35. agentfield/types.py +710 -0
  36. agentfield/utils.py +26 -0
  37. agentfield/vc_generator.py +464 -0
  38. agentfield/vision.py +198 -0
  39. agentfield-0.1.22rc2.dist-info/METADATA +102 -0
  40. agentfield-0.1.22rc2.dist-info/RECORD +42 -0
  41. agentfield-0.1.22rc2.dist-info/WHEEL +5 -0
  42. agentfield-0.1.22rc2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,280 @@
1
+ """
2
+ AgentField SDK Connection Manager
3
+
4
+ Provides resilient connection handling for AgentField server connectivity.
5
+ Handles automatic reconnection, graceful degradation, and connection health monitoring.
6
+ """
7
+
8
+ import asyncio
9
+ import time
10
+ from enum import Enum
11
+ from typing import Optional, Callable, Any, Dict
12
+ from dataclasses import dataclass
13
+ from agentfield.logger import log_debug, log_info, log_warn, log_error
14
+
15
+
16
+ class ConnectionState(Enum):
17
+ """Connection states for AgentField server connectivity"""
18
+
19
+ DISCONNECTED = "disconnected"
20
+ CONNECTING = "connecting"
21
+ CONNECTED = "connected"
22
+ RECONNECTING = "reconnecting"
23
+ DEGRADED = "degraded" # Running locally without AgentField server
24
+
25
+
26
+ @dataclass
27
+ class ConnectionConfig:
28
+ """Configuration for connection management"""
29
+
30
+ retry_interval: float = 10.0 # Consistent retry interval in seconds
31
+ health_check_interval: float = 30.0 # Health check interval in seconds
32
+ connection_timeout: float = 10.0 # Connection timeout in seconds
33
+
34
+
35
+ class ConnectionManager:
36
+ """
37
+ Manages resilient connections to AgentField server with automatic reconnection,
38
+ graceful degradation, and health monitoring.
39
+
40
+ Uses a simple, consistent retry interval to ensure immediate reconnection
41
+ when AgentField server becomes available.
42
+ """
43
+
44
+ def __init__(self, agent, config: Optional[ConnectionConfig] = None):
45
+ self.agent = agent
46
+ self.config = config or ConnectionConfig()
47
+
48
+ # Connection state
49
+ self.state = ConnectionState.DISCONNECTED
50
+ self.last_successful_connection = None
51
+
52
+ # Tasks
53
+ self._reconnection_task: Optional[asyncio.Task] = None
54
+ self._health_check_task: Optional[asyncio.Task] = None
55
+ self._shutdown_requested = False
56
+
57
+ # Callbacks
58
+ self.on_connected: Optional[Callable] = None
59
+ self.on_disconnected: Optional[Callable] = None
60
+ self.on_degraded: Optional[Callable] = None
61
+
62
+ async def start(self) -> bool:
63
+ """
64
+ Start the connection manager and attempt initial connection.
65
+
66
+ Returns:
67
+ True if initial connection successful, False if entering degraded mode
68
+ """
69
+ log_info("Starting connection manager")
70
+
71
+ # Attempt initial connection
72
+ success = await self._attempt_connection()
73
+
74
+ if success:
75
+ self._on_connection_success()
76
+ # Start health monitoring
77
+ self._health_check_task = asyncio.create_task(self._health_check_loop())
78
+ else:
79
+ self._on_connection_failure()
80
+ # Start reconnection attempts
81
+ self._reconnection_task = asyncio.create_task(self._reconnection_loop())
82
+
83
+ return success
84
+
85
+ async def stop(self):
86
+ """Stop the connection manager and cleanup tasks"""
87
+ log_info("Stopping connection manager")
88
+ self._shutdown_requested = True
89
+
90
+ # Cancel tasks
91
+ if self._reconnection_task and not self._reconnection_task.done():
92
+ self._reconnection_task.cancel()
93
+ try:
94
+ await self._reconnection_task
95
+ except asyncio.CancelledError:
96
+ pass
97
+
98
+ if self._health_check_task and not self._health_check_task.done():
99
+ self._health_check_task.cancel()
100
+ try:
101
+ await self._health_check_task
102
+ except asyncio.CancelledError:
103
+ pass
104
+
105
+ async def _attempt_connection(self) -> bool:
106
+ """
107
+ Attempt to connect to AgentField server.
108
+
109
+ Returns:
110
+ True if connection successful, False otherwise
111
+ """
112
+ try:
113
+ self.state = ConnectionState.CONNECTING
114
+ log_debug("Attempting connection to AgentField server")
115
+
116
+ # Try to register with AgentField server - suppress verbose error logging
117
+ import logging
118
+
119
+ # Temporarily suppress httpx and httpcore logging to avoid verbose connection errors
120
+ httpx_logger = logging.getLogger("httpx")
121
+ httpcore_logger = logging.getLogger("httpcore")
122
+ original_httpx_level = httpx_logger.level
123
+ original_httpcore_level = httpcore_logger.level
124
+
125
+ # Set to ERROR level to suppress connection attempt logs
126
+ httpx_logger.setLevel(logging.ERROR)
127
+ httpcore_logger.setLevel(logging.ERROR)
128
+
129
+ discovery_payload = self.agent._build_callback_discovery_payload()
130
+
131
+ success = False
132
+ payload: Optional[Dict[str, Any]] = None
133
+
134
+ try:
135
+ success, payload = await self.agent.client.register_agent_with_status(
136
+ node_id=self.agent.node_id,
137
+ reasoners=self.agent.reasoners,
138
+ skills=self.agent.skills,
139
+ base_url=self.agent.base_url,
140
+ status=self.agent._current_status,
141
+ discovery=discovery_payload,
142
+ suppress_errors=True, # Suppress verbose error logging for connection attempts
143
+ vc_metadata=self.agent._build_vc_metadata(),
144
+ )
145
+ finally:
146
+ # Restore original logging levels
147
+ httpx_logger.setLevel(original_httpx_level)
148
+ httpcore_logger.setLevel(original_httpcore_level)
149
+
150
+ if success:
151
+ if payload:
152
+ self.agent._apply_discovery_response(payload)
153
+ if self.agent.did_manager and not self.agent.did_enabled:
154
+ self.agent._register_agent_with_did()
155
+ self.state = ConnectionState.CONNECTED
156
+ return True
157
+ else:
158
+ self.state = ConnectionState.DISCONNECTED
159
+ return False
160
+
161
+ except Exception as e:
162
+ # Only log at debug level to avoid spam
163
+ log_debug(f"Connection attempt failed: {type(e).__name__}")
164
+ self.state = ConnectionState.DISCONNECTED
165
+ return False
166
+
167
+ async def _health_check_loop(self):
168
+ """Background loop for monitoring connection health"""
169
+ while not self._shutdown_requested and self.state == ConnectionState.CONNECTED:
170
+ try:
171
+ await asyncio.sleep(self.config.health_check_interval)
172
+
173
+ if self._shutdown_requested:
174
+ break
175
+
176
+ # Try to send a heartbeat to check connection health
177
+ success = await self.agent.agentfield_handler.send_enhanced_heartbeat()
178
+
179
+ if not success:
180
+ log_warn("Health check failed - connection lost")
181
+ self._on_connection_failure()
182
+ # Start reconnection attempts
183
+ self._reconnection_task = asyncio.create_task(
184
+ self._reconnection_loop()
185
+ )
186
+ break
187
+
188
+ except asyncio.CancelledError:
189
+ break
190
+ except Exception as e:
191
+ log_error(f"Health check error: {e}")
192
+ self._on_connection_failure()
193
+ # Start reconnection attempts
194
+ self._reconnection_task = asyncio.create_task(self._reconnection_loop())
195
+ break
196
+
197
+ async def _reconnection_loop(self):
198
+ """Background loop for attempting reconnection"""
199
+ self.state = ConnectionState.RECONNECTING
200
+
201
+ while not self._shutdown_requested and self.state != ConnectionState.CONNECTED:
202
+ try:
203
+ log_debug(
204
+ f"Attempting reconnection in {self.config.retry_interval} seconds..."
205
+ )
206
+ await asyncio.sleep(self.config.retry_interval)
207
+
208
+ if self._shutdown_requested:
209
+ break
210
+
211
+ success = await self._attempt_connection()
212
+
213
+ if success:
214
+ self._on_connection_success()
215
+ # Start health monitoring again
216
+ self._health_check_task = asyncio.create_task(
217
+ self._health_check_loop()
218
+ )
219
+ break
220
+ else:
221
+ log_debug("Reconnection attempt failed, will retry")
222
+
223
+ except asyncio.CancelledError:
224
+ break
225
+ except Exception as e:
226
+ log_error(f"Reconnection error: {e}")
227
+ # Continue trying
228
+
229
+ def _on_connection_success(self):
230
+ """Handle successful connection"""
231
+ self.state = ConnectionState.CONNECTED
232
+ self.last_successful_connection = time.time()
233
+ self.agent.agentfield_connected = True
234
+
235
+ log_info("Connected to AgentField server")
236
+
237
+ if self.on_connected:
238
+ try:
239
+ self.on_connected()
240
+ except Exception as e:
241
+ log_error(f"Error in connection callback: {e}")
242
+
243
+ def _on_connection_failure(self):
244
+ """Handle connection failure"""
245
+ self.state = ConnectionState.DEGRADED
246
+ self.agent.agentfield_connected = False
247
+
248
+ log_warn("AgentField server unavailable - running in degraded mode")
249
+
250
+ if self.on_disconnected:
251
+ try:
252
+ self.on_disconnected()
253
+ except Exception as e:
254
+ log_error(f"Error in disconnection callback: {e}")
255
+
256
+ def is_connected(self) -> bool:
257
+ """Check if currently connected to AgentField server"""
258
+ return self.state == ConnectionState.CONNECTED
259
+
260
+ def is_degraded(self) -> bool:
261
+ """Check if running in degraded mode"""
262
+ return self.state == ConnectionState.DEGRADED
263
+
264
+ async def force_reconnect(self):
265
+ """Force an immediate reconnection attempt"""
266
+ if self.state == ConnectionState.CONNECTED:
267
+ return True
268
+
269
+ log_info("Forcing reconnection attempt")
270
+ success = await self._attempt_connection()
271
+
272
+ if success:
273
+ self._on_connection_success()
274
+ # Cancel existing reconnection task if running
275
+ if self._reconnection_task and not self._reconnection_task.done():
276
+ self._reconnection_task.cancel()
277
+ # Start health monitoring
278
+ self._health_check_task = asyncio.create_task(self._health_check_loop())
279
+
280
+ return success